Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Update external references on successful rename via LSP (issue 670) #674

Merged
merged 18 commits into from
Dec 11, 2024

Conversation

SCWells72
Copy link
Contributor

Once the user has chosen a different name for the identifier under the caret, external references to that identifier are collected. If any were found, they are updated via PsiReference#handleElementRename() on successful completion of the LSP rename action.

…o that identifier before performing the actual rename, and then on successful rename, any references are updated regarding the new name.
// Update any found external references with the new name
externalReferences.forEach(externalReference -> {
// Don't let a single failed external reference keep us from updating other references
try {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For relational integrity purposes, it's important that failure of a single rename propagation not short-circuit the rest.

String wordText = wordElement.getText();
if (StringUtil.isNotEmpty(wordText)) {
// When testing, just collect references on the current thread
if (ApplicationManager.getApplication().isUnitTestMode()) {
Copy link
Contributor Author

@SCWells72 SCWells72 Dec 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love this, but it's important that the textual search occur under a modal progress indicator, and that doesn't work well with the rename refactoring unit tests. The same logic occurs either way, but in the real app it occurs under a modal progress indicator, and during tests it occurs on the current thread. It is consistent with quite a few other places in LSP4IJ that do the same thing, though, including some in rename refactoring support.

SCWells72 and others added 2 commits December 7, 2024 12:28
…ll as can be. If language servers involved in the rename refactoring have mixed case-sensitivity, searching and matching is performed in a case-sensitive manner. As it says in the comment, in that (likely unusual) situation, it's much better not to have changed something due to a case-sensitivity mismatch than to change things that should not have been changed.
if (!externalReferencesByKey.containsKey(referenceKey)) {
PsiElement targetElement = reference.resolve();
PsiFile targetFile = targetElement != null ? targetElement.getContainingFile() : null;
TextRange targetTextRange = targetFile != null ? targetElement.getTextRange() : null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move declaration of targetTextRange and targetText in the ifblock (L297))

LSPRenameFeature renameFeature = clientFeatures.getRenameFeature();
if (renameFeature.isRenameSupported(psiFile)) {
LSPCompletionFeature completionFeature = clientFeatures.getCompletionFeature();
if (completionFeature.isCaseSensitive(psiFile)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is strange to use completion feature for rename. We discussed about this caseSenstive property and perhaps it would be better to move caseSensitive in the clientFeatures (we can do that since wehave not released) and the JSON client configuration content will declare just

{
  "caseSensitive": true
}

If you do that, please update JSON Schema docs and description (this caseSensitive is used n completion and rename both).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is what I meant when that flag was introduced and I said that case-sensitivity is a language/grammar-level setting and not specific to any single feature associated with that language/grammar. I agree that the setting should be up-leveled, particularly before it has ever been included in a release. I'm happy to do that as part of this work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is what I meant when that flag was introduced and I said that case-sensitivity is a language/grammar-level setting and not specific to any single feature associated with that language/grammar.

I am sorry it was difficult for me to understand without usecases. Now I underdtand more with this new usecase.

I agree that the setting should be up-leveled, particularly before it has ever been included in a release. I'm happy to do that as part of this work.

Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, not an issue at all. That wasn't meant as an "I told you so." I think we took the right approach of solving the problem we had until we saw another aspect of it. Here we are, and thankfully before the thing that has to be changed has been put into end user hands. No issues at all! I'll get it moved to a feature-independent location in the next day or two.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to discuss with @fbricon but I think we should create asap a new release to rmbed the fix #671

If you want we czn merge your PR and I will do the fix in order to embed the PR in the release.

…c feature. This commit also includes an improvement for how bundled files are refreshed in VFS so that the latest-and-greatest contents are always used. Currently this is used specifically for bundled JSON schema files, but it should work for any bundled file that's needed by an IDE feature.
*/
public static void refreshBundledFile(@NotNull VirtualFile bundledFile) {
// Refresh asynchronously then synchronously because otherwise bundled virtual files won't refresh properly
bundledFile.refresh(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this may seems strange, but it addresses a problem that I've also had in my plugin with bundled virtual files -- JSON schema, XSDs, etc., -- being stale until actually opened in an editor tab. After chatting with JetBrains, this two-stage forced VFS refresh seems to work reliably.

docs/LSPApi.md Outdated
@@ -2,6 +2,7 @@

The [LSPClientFeatures](https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPClientFeatures.java) API allows you to customize the behavior of LSP features, including:

- [Client-only features](#client-only-features)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if this break-out of client-only features isn't what you'd prefer.

*/
@ApiStatus.Internal
@NotNull
public Set<LanguageServerWrapper> getMatchedLanguageServerWrappersSync(@NotNull VirtualFile file) {
Copy link
Contributor Author

@SCWells72 SCWells72 Dec 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's a better way to do this without adding this method, please let me know. It's needed to encapsulate finding external references by just a PsiFile, an offset, and a processor in a synchronous manner as required by the rename refactoring since updating the external references is just a step in that editor transaction.

UPDATE: I've added a similar method in #669 for checking isSupported(PsiFile, ...). I could refactor that one and extract a method to answer this same question as if that makes sense.

Set<PsiReference> externalReferences = new LinkedHashSet<>();

// When testing, just collect references on the current thread
if (ApplicationManager.getApplication().isUnitTestMode()) {
Copy link
Contributor Author

@SCWells72 SCWells72 Dec 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is consistent with the existing precedent of keeping things a bit simpler when tests execute. In this case, no progress indicator is used during test execution.

/**
* Client-side code workspace symbol settings.
*/
public static class ClientConfigurationWorkspaceSymbolSettings {
/**
* Whether or not completions should be offered as case-sensitive. Defaults to false.
* Whether or not the language server can support the IDE's Go To Class action efficiently. Defaults to false.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed a copy-paste comment from earlier.

/**
* Utility class that helps to process/find external references to LSP4IJ-based (pseudo-)elements.
*/
public final class LSPExternalReferencesFinder {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the common utility class for finding/processing external usages of LSP4IJ's pseudo-elements.

@@ -92,6 +94,15 @@ public void processElementUsages(@NotNull PsiElement element, @NotNull Processor
} catch (Exception e) {
LOGGER.error("Error while collection LSP Usages", e);
}

// For completeness' sake, also collect external usages to LSP (pseudo-)elements
int offset = ReadAction.compute(() -> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how external usages are also included.

@@ -89,9 +91,14 @@ public void processElementUsages(@NotNull PsiElement element, @NotNull Processor
}
}
}
} catch (ProcessCanceledException pce) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this behavior in one place based on an observed error. This same change may need to be rolled out anywhere there is try ... catch (Exception) ... where the caught exception could be ProcessCanceledException. That should only be caught/handled in VERY specific situations. Otherwise it should be propagated to the IDE.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be done like this with the main classes.

docs/LSPApi.md Outdated
@@ -163,6 +164,14 @@ public class MyLSPCodeLensFeature extends LSPCodeLensFeature {
}
```

## Client-only Features
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It already exists a table which list methods from LSPClientFeatures (ex: do a asearch with keepServerAlive). Your new method should be added here. If you think you need a
Client-only features please add it.

// miss changing things that should have been changed than to change things that should not
for (LanguageServerWrapper languageServerWrapper : languageServerWrappers) {
LSPClientFeatures clientFeatures = languageServerWrapper.getClientFeatures();
if ((clientFeatures != null) && clientFeatures.isCaseSensitive(psiFile)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clientFeatures != null should never happen

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll annotate that method as @NotNull in the commit that removes that check so that there's no confusion going forward.

}

Project project = file.getProject();
Set<LanguageServerWrapper> languageServerWrappers = LanguageServiceAccessor.getInstance(project).getMatchedLanguageServerWrappersSync(virtualFile);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace with:

boolean caseSensitive =
                LanguageServiceAccessor.getInstance(file.getProject())
                .hasAny(file.getVirtualFile(), ls -> ls.getClientFeatures().isCaseSensitive(file));

and remove your new method getMatchedLanguageServerWrappersSync.

@angelozerr
Copy link
Contributor

I cannot test the PR but code looks good.

Thanks @SCWells72 !

@angelozerr angelozerr merged commit acadcf0 into redhat-developer:main Dec 11, 2024
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants