Skip to content

Commit

Permalink
feat: Update external references on successful rename via LSP (issue …
Browse files Browse the repository at this point in the history
…670) (#674)

feat: Update external references on successful rename via LSP 

Fixes #670
  • Loading branch information
SCWells72 authored Dec 11, 2024
1 parent 7dd0dec commit acadcf0
Show file tree
Hide file tree
Showing 21 changed files with 263 additions and 104 deletions.
3 changes: 2 additions & 1 deletion docs/LSPApi.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public class MyLanguageServerFactory implements LanguageServerFactory {
| boolean keepServerAlive() | Returns `true` if the server is kept alive even if all files associated with the language server are closed and `false` otherwise. | `false` |
| boolean canStopServerByUser() | Returns `true` if the user can stop the language server in LSP console from the context menu and `false` otherwise. | `true` |
| boolean isEnabled(VirtualFile) | Returns `true` if the language server is enabled for the given file and `false` otherwise. | `true` |
| boolean isCaseSensitive(PsiFile file) | Returns `true` if the language grammar for the given file is case-sensitive and `false` otherwise. | `false` |

```java
package my.language.server;
Expand Down Expand Up @@ -229,7 +230,7 @@ public class MyLSPCodeLensFeature extends LSPCodeLensFeature {
| boolean isStrikeout(CompletionItem item) | Returns true if the IntelliJ lookup is strike out and false otherwise. | use `item.getDeprecated()` or `item.getTags().contains(CompletionItemTag.Deprecated)` |
| String getTailText(CompletionItem item) | Returns the IntelliJ lookup tail text from the given LSP completion item and null otherwise. | `item.getLabelDetails().getDetail()` |
| boolean isItemTextBold(CompletionItem item) | Returns the IntelliJ lookup item text bold from the given LSP completion item and null otherwise. | `item.getKind() == CompletionItemKind.Keyword` |
| boolean isCaseSensitive(PsiFile file) | Determines whether or not completions should be offered in a case-sensitive manner. | Case-insensitive. |
|

## LSP Declaration Feature

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1169,13 +1169,15 @@ public boolean isDidRenameFilesSupported(@NotNull PsiFile file) {
return fileOperationsManager.canDidRenameFiles(LSPIJUtils.toUri(file), file.isDirectory());
}

@NotNull
public LSPClientFeatures getClientFeatures() {
if (clientFeatures == null) {
clientFeatures = getOrCreateClientFeatures();
}
return clientFeatures;
}

@NotNull
private synchronized LSPClientFeatures getOrCreateClientFeatures() {
if (clientFeatures != null) {
return clientFeatures;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.intellij.openapi.Disposable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.redhat.devtools.lsp4ij.LSPRequestConstants;
import com.redhat.devtools.lsp4ij.LanguageServerWrapper;
import com.redhat.devtools.lsp4ij.ServerStatus;
Expand Down Expand Up @@ -964,6 +965,17 @@ public boolean isEnabled(@NotNull VirtualFile file) {
return true;
}

/**
* Determines whether or not the language grammar for the file is case-sensitive.
*
* @param file the file
* @return true if the file's language grammar is case-sensitive; otherwise false
*/
public boolean isCaseSensitive(@NotNull PsiFile file) {
// Default to case-insensitive
return false;
}

/**
* Set the language server wrapper.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,8 @@ public void addLookupItem(@NotNull LSPCompletionContext context,
@NotNull LookupElement lookupItem,
int priority,
@NotNull CompletionItem item) {
// Determine whether or not completions should be case-sensitive
boolean caseSensitive = isCaseSensitive(context.getParameters().getOriginalFile());
// Determine whether or not completions in this language should be case-sensitive
boolean caseSensitive = getClientFeatures().isCaseSensitive(context.getParameters().getOriginalFile());

var prioritizedLookupItem = PrioritizedLookupElement.withPriority(lookupItem, priority);

Expand Down Expand Up @@ -293,15 +293,4 @@ public void setServerCapabilities(@Nullable ServerCapabilities serverCapabilitie
completionCapabilityRegistry.setServerCapabilities(serverCapabilities);
}
}

/**
* Determines whether or not completions for the file should be offered in a case-sensitive manner.
*
* @param file the file
* @return true if completions should be offered in a case-sensitive manner; otherwise false
*/
public boolean isCaseSensitive(@NotNull PsiFile file) {
// Default to case-insensitive
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@
*******************************************************************************/
package com.redhat.devtools.lsp4ij.features.rename;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileTypes.FileTypes;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.ui.NameSuggestionsField;
import com.intellij.refactoring.ui.RefactoringDialog;
Expand All @@ -27,11 +33,16 @@
import com.redhat.devtools.lsp4ij.features.refactoring.WorkspaceEditData;
import com.redhat.devtools.lsp4ij.internal.CancellationUtil;
import com.redhat.devtools.lsp4ij.internal.StringUtils;
import com.redhat.devtools.lsp4ij.usages.LSPExternalReferencesFinder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
Expand All @@ -47,6 +58,8 @@
*/
class LSPRenameRefactoringDialog extends RefactoringDialog {

private static final Logger LOGGER = LoggerFactory.getLogger(LSPRenameRefactoringDialog.class);

@NotNull
private final LSPRenameParams renameParams;

Expand Down Expand Up @@ -143,6 +156,10 @@ protected boolean areButtonsValid() {
static void doRename(@NotNull LSPRenameParams renameParams,
@NotNull PsiFile psiFile,
@NotNull Editor editor) {
// Before having the language server perform the rename, see if there are any external references that should
// also be updated upon successful completion by the language server
int offset = editor.getCaretModel().getOffset();
Set<PsiReference> externalReferences = getExternalReferences(psiFile, offset);

CompletableFuture<List<WorkspaceEditData>> future = LSPFileSupport.getSupport(psiFile)
.getRenameSupport()
Expand All @@ -152,7 +169,8 @@ static void doRename(@NotNull LSPRenameParams renameParams,
// The 'rename' is stopped:
// - if user change the editor content
// - if it cancels the Task
String title = LanguageServerBundle.message("lsp.refactor.rename.progress.title", psiFile.getVirtualFile().getName(), renameParams.getNewName());
String newName = renameParams.getNewName();
String title = LanguageServerBundle.message("lsp.refactor.rename.progress.title", psiFile.getVirtualFile().getName(), newName);
waitUntilDoneAsync(future, title, psiFile);

future.handle((workspaceEdits, error) -> {
Expand All @@ -174,8 +192,19 @@ static void doRename(@NotNull LSPRenameParams renameParams,
LSPRenameHandler.showErrorHint(editor, LanguageServerBundle.message("lsp.refactor.rename.cannot.be.renamed.error"));
} else {
// Apply the rename from the LSP WorkspaceEdit list
WriteCommandAction
.runWriteCommandAction(psiFile.getProject(), () -> workspaceEdits.forEach(workspaceEditData -> LSPIJUtils.applyWorkspaceEdit(workspaceEditData.edit())));
WriteCommandAction.runWriteCommandAction(psiFile.getProject(), () -> {
workspaceEdits.forEach(workspaceEditData -> LSPIJUtils.applyWorkspaceEdit(workspaceEditData.edit()));

// 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 {
externalReference.handleElementRename(newName);
} catch (Exception e) {
LOGGER.warn("External reference rename failed.", e);
}
});
});
}
return workspaceEdits;
});
Expand All @@ -192,4 +221,32 @@ protected void dispose() {
super.dispose();
}

@NotNull
private static Set<PsiReference> getExternalReferences(@NotNull PsiFile file, int offset) {
Set<PsiReference> externalReferences = new LinkedHashSet<>();

// When testing, just collect references on the current thread
if (ApplicationManager.getApplication().isUnitTestMode()) {
LSPExternalReferencesFinder.processExternalReferences(file, offset, reference -> {
externalReferences.add(reference);
return true;
});
} else {
// This should happen on a progress indicator since it's performing a textual search of project
// sources, and it must be modal as we need the results synchronously
Project project = file.getProject();
ProgressManager.getInstance().run(new Task.Modal(project, "Finding External References", true) {
@Override
public void run(@NotNull ProgressIndicator progressIndicator) {
progressIndicator.setIndeterminate(true);
LSPExternalReferencesFinder.processExternalReferences(file, offset, reference -> {
externalReferences.add(reference);
return true;
});
}
});
}

return externalReferences;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,20 @@
* Client-side settings for a user-defined language server configuration.
*/
public class ClientConfigurationSettings {
/**
* Client-side code completion settings.
*/
public static class ClientConfigurationCompletionSettings {
/**
* Whether or not completions should be offered as case-sensitive. Defaults to false.
*/
public boolean caseSensitive = false;
}

/**
* 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.
*/
public boolean supportsGotoClass = false;
}

/**
* Client-side code completion settings
* Whether or not the language grammar is case-sensitive. Defaults to false.
*/
public @NotNull ClientConfigurationCompletionSettings completions = new ClientConfigurationCompletionSettings();
public boolean caseSensitive = false;

/**
* Client-side code workspace symbol settings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
*******************************************************************************/
package com.redhat.devtools.lsp4ij.server.definition.launching;

import com.intellij.psi.PsiFile;
import com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures;
import org.jetbrains.annotations.NotNull;

/**
* Adds client-side configuration features.
Expand All @@ -24,7 +26,12 @@ public UserDefinedClientFeatures() {
super();

// Use the extended feature implementations
setCompletionFeature(new UserDefinedCompletionFeature());
setWorkspaceSymbolFeature(new UserDefinedWorkspaceSymbolFeature());
}

public boolean isCaseSensitive(@NotNull PsiFile file) {
UserDefinedLanguageServerDefinition serverDefinition = (UserDefinedLanguageServerDefinition) getServerDefinition();
ClientConfigurationSettings clientConfiguration = serverDefinition.getLanguageServerClientConfiguration();
return (clientConfiguration != null) && clientConfiguration.caseSensitive;
}
}

This file was deleted.

Loading

0 comments on commit acadcf0

Please sign in to comment.