-
Notifications
You must be signed in to change notification settings - Fork 28
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
Conversation
…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 { |
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
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.
src/main/java/com/redhat/devtools/lsp4ij/features/rename/LSPRenameRefactoringDialog.java
Outdated
Show resolved
Hide resolved
…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; |
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
…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( |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
…d checks based on PR review feedback.
… logic to find those references is extracted into the common class LSPExternalReferencesFinder.
*/ | ||
@ApiStatus.Internal | ||
@NotNull | ||
public Set<LanguageServerWrapper> getMatchedLanguageServerWrappersSync(@NotNull VirtualFile file) { |
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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(() -> { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
…esence of that method is causing merge conflicts.
I cannot test the PR but code looks good. Thanks @SCWells72 ! |
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.