diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ResourceUtils.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ResourceUtils.java index 7dbffe0501..f21b4a7315 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ResourceUtils.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ResourceUtils.java @@ -383,4 +383,17 @@ public static IMarker createWarningMarker(String type, IResource resource, Strin marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_WARNING); return marker; } + + /** + * Creates a simple info marker with the given type, id and status message to the given resource. + */ + public static IMarker createInfoMarker(String type, IResource resource, String message, int id, int start, int end) throws CoreException { + IMarker marker = resource.createMarker(type); + marker.setAttribute(IJavaModelMarker.ID, id); + marker.setAttribute(IMarker.MESSAGE, message); + marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO); + marker.setAttribute(IMarker.CHAR_START, start); + marker.setAttribute(IMarker.CHAR_END, end); + return marker; + } } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/CorrectionMessages.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/CorrectionMessages.java index cfc00e2ae2..bf8c032e02 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/CorrectionMessages.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/CorrectionMessages.java @@ -127,6 +127,7 @@ private CorrectionMessages() { public static String LocalCorrectionsSubProcessor_hiding_field_label; public static String LocalCorrectionsSubProcessor_rename_var_label; public static String LocalCorrectionsSubProcessor_hiding_argument_label; + public static String LocalCorrectionsSubProcessor_manual_rename_label; public static String LocalCorrectionsSubProcessor_setparenteses_description; public static String LocalCorrectionsSubProcessor_setparenteses_instanceof_description; public static String LocalCorrectionsSubProcessor_InferGenericTypeArguments; diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/CorrectionMessages.properties b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/CorrectionMessages.properties index 90383bfdf4..15b313a063 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/CorrectionMessages.properties +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/CorrectionMessages.properties @@ -115,6 +115,7 @@ LocalCorrectionsSubProcessor_hiding_local_label=Rename local variable ''{0}'' LocalCorrectionsSubProcessor_hiding_field_label=Rename field ''{0}'' LocalCorrectionsSubProcessor_rename_var_label=Rename ''{0}'' LocalCorrectionsSubProcessor_hiding_argument_label=Rename argument ''{0}'' +LocalCorrectionsSubProcessor_manual_rename_label=Rename usages of ''{0}'' to ''{1}'' LocalCorrectionsSubProcessor_setparenteses_description=Put ''{0}'' expression in parentheses LocalCorrectionsSubProcessor_InferGenericTypeArguments=Infer Generic Type Arguments... LocalCorrectionsSubProcessor_InferGenericTypeArguments_description=Start the 'Infer Generic Type Arguments' refactoring diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/QuickFixProcessor.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/QuickFixProcessor.java index 72274cdf19..a426bd477e 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/QuickFixProcessor.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/QuickFixProcessor.java @@ -51,6 +51,8 @@ import org.eclipse.jdt.ls.core.internal.corrections.proposals.ReplaceCorrectionProposal; import org.eclipse.jdt.ls.core.internal.corrections.proposals.TypeMismatchSubProcessor; import org.eclipse.jdt.ls.core.internal.corrections.proposals.UnresolvedElementsSubProcessor; +import org.eclipse.jdt.ls.core.internal.handlers.BaseDocumentLifeCycleHandler; +import org.eclipse.jdt.ls.core.internal.handlers.DocumentLifeCycleHandler; import org.eclipse.jdt.ls.core.internal.handlers.OrganizeImportsHandler; import org.eclipse.jdt.ls.core.internal.text.correction.ModifierCorrectionSubProcessor; import org.eclipse.lsp4j.CodeActionKind; @@ -75,7 +77,7 @@ private static int moveBack(int offset, int start, String ignoreCharacters, ICom return start; } - public List getCorrections(CodeActionParams params, IInvocationContext context, IProblemLocationCore[] locations) throws CoreException { + public List getCorrections(CodeActionParams params, IInvocationContext context, IProblemLocationCore[] locations, DocumentLifeCycleHandler documentLifeCycleHandler) throws CoreException { if (locations == null || locations.length == 0) { return Collections.emptyList(); } @@ -84,7 +86,7 @@ public List getCorrections(CodeActionParams params, II for (int i = 0; i < locations.length; i++) { IProblemLocationCore curr = locations[i]; if (handledProblems(curr, locations, handledProblems)) { - process(params, context, curr, resultingCollections); + process(params, context, curr, resultingCollections, documentLifeCycleHandler); } } return resultingCollections; @@ -107,7 +109,7 @@ private static boolean handledProblems(IProblemLocationCore location, IProblemLo return handledProblems.add(problemId); } - private void process(CodeActionParams params, IInvocationContext context, IProblemLocationCore problem, Collection proposals) throws CoreException { + private void process(CodeActionParams params, IInvocationContext context, IProblemLocationCore problem, Collection proposals, DocumentLifeCycleHandler documentLifeCycleHandler) throws CoreException { int id = problem.getProblemId(); if (id == 0) { // no proposals for none-problem locations return; @@ -668,7 +670,9 @@ private void process(CodeActionParams params, IInvocationContext context, IProbl // TypeAnnotationSubProcessor.addMoveTypeAnnotationToTypeProposal(context, // problem, proposals); // break; - + case BaseDocumentLifeCycleHandler.RENAME_REFERENCE_PROBLEM_ID: + LocalCorrectionsSubProcessor.getManualRenameCorrectionProposals(context, problem, proposals, documentLifeCycleHandler); + break; default: String str = problem.toString(); System.out.println(str); diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/proposals/LocalCorrectionsSubProcessor.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/proposals/LocalCorrectionsSubProcessor.java index e758584a0f..ae5773dbc2 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/proposals/LocalCorrectionsSubProcessor.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/proposals/LocalCorrectionsSubProcessor.java @@ -21,12 +21,16 @@ package org.eclipse.jdt.ls.core.internal.corrections.proposals; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map; +import java.util.Objects; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IType; @@ -101,7 +105,9 @@ import org.eclipse.jdt.internal.ui.fix.UnnecessaryCodeCleanUpCore; import org.eclipse.jdt.internal.ui.text.correction.IProblemLocationCore; import org.eclipse.jdt.internal.ui.util.ASTHelper; +import org.eclipse.jdt.ls.core.internal.JDTUtils; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; +import org.eclipse.jdt.ls.core.internal.Messages; import org.eclipse.jdt.ls.core.internal.corext.refactoring.surround.ExceptionAnalyzer; import org.eclipse.jdt.ls.core.internal.corext.refactoring.surround.SurroundWithTryCatchRefactoring; import org.eclipse.jdt.ls.core.internal.corrections.CorrectionMessages; @@ -111,13 +117,18 @@ import org.eclipse.jdt.ls.core.internal.corrections.proposals.ChangeMethodSignatureProposal.ChangeDescription; import org.eclipse.jdt.ls.core.internal.corrections.proposals.ChangeMethodSignatureProposal.InsertDescription; import org.eclipse.jdt.ls.core.internal.corrections.proposals.ChangeMethodSignatureProposal.RemoveDescription; +import org.eclipse.jdt.ls.core.internal.handlers.BaseDocumentLifeCycleHandler; +import org.eclipse.jdt.ls.core.internal.handlers.DocumentLifeCycleHandler; +import org.eclipse.jdt.ls.core.internal.text.correction.CUCorrectionCommandProposal; import org.eclipse.jdt.ls.core.internal.text.correction.ModifierCorrectionSubProcessor; import org.eclipse.jdt.ls.core.internal.text.correction.QuickAssistProcessor; import org.eclipse.lsp4j.CodeActionKind; +import org.eclipse.lsp4j.Range; public class LocalCorrectionsSubProcessor { private static final String ADD_STATIC_ACCESS_ID = "org.eclipse.jdt.ui.correction.changeToStatic"; //$NON-NLS-1$ + private static final String RENAME_REFERENCES_COMMAND = "java.action.renameReferences"; public static void addUncaughtExceptionProposals(IInvocationContext context, IProblemLocationCore problem, Collection proposals) throws CoreException { ICompilationUnit cu = context.getCompilationUnit(); @@ -1109,4 +1120,33 @@ public static void addValueForAnnotationProposals(IInvocationContext context, IP } } + public static void getManualRenameCorrectionProposals(IInvocationContext context, IProblemLocationCore problem, Collection proposals, DocumentLifeCycleHandler documentLifeCycleHandler) throws CoreException { + if (!JavaLanguageServerPlugin.getPreferencesManager().getPreferences().getRenameReferencesEnabled()) { + return; + } + ICompilationUnit cu = context.getCompilationUnit(); + if (cu == null) { + return; + } + IResource resource = cu.getResource(); + if (resource == null) { + return; + } + IMarker[] markers = resource.findMarkers(BaseDocumentLifeCycleHandler.RENAME_REFERENCE_MARKER_ID, false, IResource.DEPTH_ONE); + Arrays.stream(markers).forEach(marker -> { + try { + if (Objects.equals(problem.getOffset(), marker.getAttribute(IMarker.CHAR_START)) && Objects.equals(problem.getOffset() + problem.getLength(), marker.getAttribute(IMarker.CHAR_END))) { + String originalName = (String) marker.getAttribute("originalName"); + String newName = (String) marker.getAttribute("newName"); + Range range = JDTUtils.toRange(cu, problem.getOffset(), problem.getLength()); + if (originalName != null && newName != null) { + String label = Messages.format(CorrectionMessages.LocalCorrectionsSubProcessor_manual_rename_label, new String[]{ originalName, newName }); + proposals.add(new CUCorrectionCommandProposal(label, CodeActionKind.QuickFix, cu, IProposalRelevance.RENAME_REFACTORING_QUICK_FIX, RENAME_REFERENCES_COMMAND, Arrays.asList(cu.getResource().getLocationURI().toString(), originalName, newName, range))); + } + } + } catch (CoreException e) { + // Do nothing + } + }); + } } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/BaseDocumentLifeCycleHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/BaseDocumentLifeCycleHandler.java index b79d6dfb02..d48393650b 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/BaseDocumentLifeCycleHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/BaseDocumentLifeCycleHandler.java @@ -46,22 +46,30 @@ import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaModelMarker; import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IProblemRequestor; +import org.eclipse.jdt.core.ISourceRange; +import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.WorkingCopyOwner; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.manipulation.CoreASTProvider; import org.eclipse.jdt.internal.core.OpenableElementInfo; import org.eclipse.jdt.internal.core.PackageFragment; import org.eclipse.jdt.ls.core.internal.DocumentAdapter; +import org.eclipse.jdt.ls.core.internal.IConstants; import org.eclipse.jdt.ls.core.internal.JDTUtils; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; import org.eclipse.jdt.ls.core.internal.JobHelpers; +import org.eclipse.jdt.ls.core.internal.Messages; import org.eclipse.jdt.ls.core.internal.MovingAverage; import org.eclipse.jdt.ls.core.internal.ProjectUtils; +import org.eclipse.jdt.ls.core.internal.ResourceUtils; +import org.eclipse.jdt.ls.core.internal.corrections.CorrectionMessages; import org.eclipse.jdt.ls.core.internal.corrections.DiagnosticsHelper; import org.eclipse.jdt.ls.core.internal.managers.InvisibleProjectImporter; import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager; @@ -103,6 +111,9 @@ public abstract class BaseDocumentLifeCycleHandler { */ private static final long PUBLISH_DIAGNOSTICS_MAX_DEBOUNCE = 2000; /*ms*/ + public static final String RENAME_REFERENCE_MARKER_ID = IConstants.PLUGIN_ID + ".renameReferenceMarker"; + public static final int RENAME_REFERENCE_PROBLEM_ID = IProblem.Syntax + 2000; + private CoreASTProvider sharedASTProvider; private WorkspaceJob validationTimer; private WorkspaceJob publishDiagnosticsJob; @@ -111,6 +122,9 @@ public abstract class BaseDocumentLifeCycleHandler { private MovingAverage movingAverageForValidation = new MovingAverage(DOCUMENT_LIFECYCLE_MAX_DEBOUNCE); private MovingAverage movingAverageForDiagnostics = new MovingAverage(PUBLISH_DIAGNOSTICS_MIN_DEBOUNCE); + private Map> documentMemberInfos = new HashMap<>(); + private Map> documentMarkers = new HashMap<>(); + public BaseDocumentLifeCycleHandler(boolean delayValidation) { this.sharedASTProvider = CoreASTProvider.getInstance(); if (delayValidation) { @@ -303,11 +317,18 @@ public IProblemRequestor getProblemRequestor(ICompilationUnit workingCopy) { }; int flags = ICompilationUnit.FORCE_PROBLEM_DETECTION | ICompilationUnit.ENABLE_BINDINGS_RECOVERY | ICompilationUnit.ENABLE_STATEMENTS_RECOVERY; + if (JavaLanguageServerPlugin.getPreferencesManager().getPreferences().getRenameReferencesEnabled()) { + provideRenameReferencesMarkers(unit); + } unit.reconcile(ICompilationUnit.NO_AST, flags, wcOwner, monitor); } public void didClose(DidCloseTextDocumentParams params) { documentVersions.remove(params.getTextDocument().getUri()); + ICompilationUnit cu = JDTUtils.resolveCompilationUnit(params.getTextDocument().getUri()); + if (cu != null) { + this.documentMemberInfos.remove(cu.getResource().getLocationURI().toString()); + } ISchedulingRule rule = JDTUtils.getRule(params.getTextDocument().getUri()); try { ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() { @@ -410,6 +431,9 @@ public ICompilationUnit handleOpen(DidOpenTextDocumentParams params) { // see https://github.com/redhat-developer/vscode-java/issues/274 checkPackageDeclaration(uri, unit); inferInvisibleProjectSourceRoot(unit); + if (JavaLanguageServerPlugin.getPreferencesManager().getPreferences().getRenameReferencesEnabled()) { + this.documentMemberInfos.put(unit.getResource().getLocationURI().toString(), this.getMemberInfos(unit)); + } } catch (JavaModelException e) { JavaLanguageServerPlugin.logException("Error while opening document. URI: " + uri, e); } @@ -467,6 +491,87 @@ public ICompilationUnit handleChanged(DidChangeTextDocumentParams params) { return unit; } + private void provideRenameReferencesMarkers(ICompilationUnit unit) { + String uri = unit.getResource().getLocationURI().toString(); + List memberInfos = this.getMemberInfos(unit); + try { + if (this.documentMemberInfos.containsKey(uri)) { + // delete existing markers + List currentMarkers = this.documentMarkers.get(uri); + if (currentMarkers != null) { + unit.getResource().deleteMarkers(RENAME_REFERENCE_MARKER_ID, false, IResource.DEPTH_ONE); + currentMarkers.clear(); + } else { + currentMarkers = new ArrayList<>(); + this.documentMarkers.put(uri, currentMarkers); + } + List cacheMemberInfos = this.documentMemberInfos.get(uri); + if (cacheMemberInfos.size() == memberInfos.size()) { + for (int i = 0; i < memberInfos.size(); i++) { + MemberInfo memberInfo = memberInfos.get(i); + String name = memberInfo.getName(); + MemberInfo cacheMemberInfo = cacheMemberInfos.get(i); + if (name == null) { + this.documentMemberInfos.put(uri, memberInfos); + break; + } + if (cacheMemberInfo.getName().equals(name)) { + continue; + } + String message = Messages.format(CorrectionMessages.LocalCorrectionsSubProcessor_manual_rename_label, new String[]{ cacheMemberInfo.getName(), name }); + IMarker marker = ResourceUtils.createInfoMarker(RENAME_REFERENCE_MARKER_ID, unit.getResource(), message, RENAME_REFERENCE_PROBLEM_ID, memberInfo.getNameRange().getOffset(), memberInfo.getNameRange().getOffset() + memberInfo.getNameRange().getLength()); + marker.setAttribute("originalName", cacheMemberInfo.getName()); + marker.setAttribute("newName", name); + currentMarkers.add(marker); + } + } else { + // update the storage names list + this.documentMemberInfos.put(uri, memberInfos); + } + } else { + // initialize the storage names list + this.documentMemberInfos.put(uri, memberInfos); + } + } catch (CoreException e) { + // Do nothing + } + } + + private List getMemberInfos(ICompilationUnit unit) { + List members = new ArrayList<>(); + try { + IType[] types = unit.getAllTypes(); + for (IType type : types) { + for (IJavaElement child : type.getChildren()) { + if (child instanceof IMember member && Modifier.isPublic(member.getFlags())) { + members.add(new MemberInfo(member.getElementName(), member.getNameRange())); + } + } + } + } catch (JavaModelException e) { + // do nothing + } + return members; + } + + public void cleanRenameReferenceCache(ICompilationUnit unit) { + if (unit == null) { + return; + } + IResource resource = unit.getResource(); + if (resource == null) { + return; + } + String uri = resource.getLocationURI().toString(); + this.documentMemberInfos.remove(uri); + this.documentMarkers.remove(uri); + try { + unit.getResource().deleteMarkers(RENAME_REFERENCE_MARKER_ID, false, IResource.DEPTH_ONE); + } catch (CoreException e) { + // do nothing + } + } + public ICompilationUnit handleClosed(DidCloseTextDocumentParams params) { String uri = params.getTextDocument().getUri(); ICompilationUnit unit = JDTUtils.resolveCompilationUnit(uri); @@ -743,4 +848,22 @@ public void checkChanged() { } + private static class MemberInfo { + private String name; + private ISourceRange nameRange; + + public MemberInfo(String name, ISourceRange nameRange) { + this.name = name; + this.nameRange = nameRange; + } + + public String getName() { + return name; + } + + public ISourceRange getNameRange() { + return nameRange; + } + } + } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/CodeActionHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/CodeActionHandler.java index 0c353e8e63..503314b5ce 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/CodeActionHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/CodeActionHandler.java @@ -81,6 +81,7 @@ public class CodeActionHandler { private NonProjectFixProcessor nonProjectFixProcessor; private PreferenceManager preferenceManager; + private DocumentLifeCycleHandler documentLifeCycleHandler; public CodeActionHandler(PreferenceManager preferenceManager) { this.preferenceManager = preferenceManager; @@ -91,6 +92,11 @@ public CodeActionHandler(PreferenceManager preferenceManager) { this.nonProjectFixProcessor = new NonProjectFixProcessor(preferenceManager); } + public CodeActionHandler(PreferenceManager preferenceManager, DocumentLifeCycleHandler documentLifeCycleHandler) { + this(preferenceManager); + this.documentLifeCycleHandler = documentLifeCycleHandler; + } + public List> getCodeActionCommands(CodeActionParams params, IProgressMonitor monitor) { if (monitor.isCanceled()) { return Collections.emptyList(); @@ -159,7 +165,7 @@ public List> getCodeActionCommands(CodeActionParams if (containsKind(codeActionKinds, CodeActionKind.QuickFix)) { try { codeActions.addAll(nonProjectFixProcessor.getCorrections(params, context, locations)); - List quickfixProposals = this.quickFixProcessor.getCorrections(params, context, locations); + List quickfixProposals = this.quickFixProcessor.getCorrections(params, context, locations, documentLifeCycleHandler); this.quickFixProcessor.addAddAllMissingImportsProposal(context, quickfixProposals); Set quickSet = new TreeSet<>(comparator); quickSet.addAll(quickfixProposals); diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java index 6be82c7005..c7d7fd9482 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java @@ -704,7 +704,7 @@ public CompletableFuture>> docume @Override public CompletableFuture>> codeAction(CodeActionParams params) { logInfo(">> document/codeAction"); - CodeActionHandler handler = new CodeActionHandler(this.preferenceManager); + CodeActionHandler handler = new CodeActionHandler(this.preferenceManager, this.documentLifeCycleHandler); return computeAsync((monitor) -> { waitForLifecycleJobs(monitor); return handler.getCodeActionCommands(params, monitor); @@ -813,7 +813,7 @@ public CompletableFuture rename(RenameParams params) { logInfo(">> document/rename"); - RenameHandler handler = new RenameHandler(preferenceManager); + RenameHandler handler = new RenameHandler(preferenceManager, this.documentLifeCycleHandler); return computeAsync((monitor) -> { waitForLifecycleJobs(monitor); return handler.rename(params, monitor); diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/RenameHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/RenameHandler.java index d65abfcb92..8c05063b7a 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/RenameHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/RenameHandler.java @@ -41,6 +41,8 @@ public class RenameHandler { + DocumentLifeCycleHandler documentLifeCycleHandler; + public static RenameOptions createOptions() { RenameOptions renameOptions = new RenameOptions(); renameOptions.setPrepareProvider(true); @@ -53,6 +55,11 @@ public RenameHandler(PreferenceManager preferenceManager) { this.preferenceManager = preferenceManager; } + public RenameHandler(PreferenceManager preferenceManager, DocumentLifeCycleHandler documentLifeCycleHandler) { + this(preferenceManager); + this.documentLifeCycleHandler = documentLifeCycleHandler; + } + public WorkspaceEdit rename(RenameParams params, IProgressMonitor monitor) { WorkspaceEdit edit = new WorkspaceEdit(); if (!preferenceManager.getPreferences().isRenameEnabled()) { @@ -95,6 +102,10 @@ public WorkspaceEdit rename(RenameParams params, IProgressMonitor monitor) { } Change change = create.getChange(); + if (change != null && elements.length == 1 && this.documentLifeCycleHandler != null) { + // consider: update variable table + this.documentLifeCycleHandler.cleanRenameReferenceCache(unit); + } return ChangeUtil.convertToWorkspaceEdit(change); } catch (CoreException | AssertionFailedException ex) { JavaLanguageServerPlugin.logException("Problem with rename for " + params.getTextDocument().getUri(), ex); diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceDiagnosticsHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceDiagnosticsHandler.java index 8be70a4f18..632598309b 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceDiagnosticsHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceDiagnosticsHandler.java @@ -13,6 +13,7 @@ package org.eclipse.jdt.ls.core.internal.handlers; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -25,6 +26,7 @@ import org.eclipse.core.internal.resources.ValidateProjectEncoding; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IMarkerDelta; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; @@ -177,7 +179,18 @@ public boolean visit(IResourceDelta delta) throws CoreException { markers = resource.findMarkers(null, false, IResource.DEPTH_ONE); document = JsonRpcHelpers.toDocument(cu.getBuffer()); } else if (handler != null) { - handler.triggerValidation(cu); + // ignore rename marker deltas + IMarkerDelta[] markerDeltas = delta.getMarkerDeltas(); + boolean isAllRenameReferenceMarker = true; + for (IMarkerDelta markerDelta : markerDeltas) { + if (!markerDelta.getType().equals(BaseDocumentLifeCycleHandler.RENAME_REFERENCE_MARKER_ID)) { + isAllRenameReferenceMarker = false; + break; + } + } + if (isAllRenameReferenceMarker == false) { + handler.triggerValidation(cu); + } } } // or a build file else if (projectsManager.isBuildFile(file)) { diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java index 80f065fce7..914e939cbe 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java @@ -486,6 +486,7 @@ public class Preferences { public static final String JAVA_COMPILE_NULLANALYSIS_NULLABLE = "java.compile.nullAnalysis.nullable"; public static final String JAVA_COMPILE_NULLANALYSIS_MODE = "java.compile.nullAnalysis.mode"; + public static final String JAVA_REFACTORING_RENAME_REFERENCES = "java.refactoring.rename.references"; /** * Preference key for list of cleanups to run on save */ @@ -641,6 +642,7 @@ public class Preferences { private FeatureStatus nullAnalysisMode; private List cleanUpActionsOnSave; private boolean extractInterfaceReplaceEnabled; + private boolean renameReferencesEnabled; static { JAVA_IMPORT_EXCLUSIONS_DEFAULT = new LinkedList<>(); @@ -1223,6 +1225,8 @@ public static Preferences createFrom(Map configuration) { prefs.setCleanUpActionsOnSave(cleanupActionsOnSave); boolean extractInterfaceReplaceEnabled = getBoolean(configuration, JAVA_REFACTORING_EXTRACT_INTERFACE_REPLACE, false); prefs.setExtractInterfaceReplaceEnabled(extractInterfaceReplaceEnabled); + boolean renameReferencesEnabled = getBoolean(configuration, JAVA_REFACTORING_RENAME_REFERENCES, false); + prefs.setRenameReferencesEnabled(renameReferencesEnabled); return prefs; } @@ -2169,6 +2173,14 @@ public boolean getExtractInterfaceReplaceEnabled() { return this.extractInterfaceReplaceEnabled; } + public void setRenameReferencesEnabled(boolean renameReferencesEnabled) { + this.renameReferencesEnabled = renameReferencesEnabled; + } + + public boolean getRenameReferencesEnabled() { + return this.renameReferencesEnabled; + } + /** * update the null analysis options of all projects based on the null analysis mode * Returns the list of enabled clean ups. diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/DocumentLifeCycleHandlerTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/DocumentLifeCycleHandlerTest.java index a0ce634662..cd91a8a000 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/DocumentLifeCycleHandlerTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/DocumentLifeCycleHandlerTest.java @@ -43,8 +43,10 @@ import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.compiler.IProblem; @@ -204,6 +206,7 @@ private Preferences mockPreferences() { Mockito.lenient().when(mockPreferences.getIncompleteClasspathSeverity()).thenReturn(Severity.ignore); Mockito.lenient().when(this.preferenceManager.getClientPreferences()).thenReturn(clientPreferences); Mockito.lenient().when(clientPreferences.isSupportedCodeActionKind(CodeActionKind.QuickFix)).thenReturn(true); + Mockito.lenient().when(preferenceManager.getPreferences().getRenameReferencesEnabled()).thenReturn(true); return mockPreferences; } @@ -699,6 +702,67 @@ public void testNotExpectedPackage() throws Exception { assertEquals("Unexpected number of errors", 0, problems.length); } + @Test + public void testRenameReferences() throws Exception { + try { + Mockito.lenient().when(preferenceManager.getPreferences().getRenameReferencesEnabled()).thenReturn(true); + newDefaultProject(); + // @formatter:off + String content = + "package org;\n" + + "public class Foo {\n" + + "\tpublic void test(){\n" + + "\t}\n" + + "\n" + + "\tpublic String getString(){\n" + + "\t}\n" + + "}"; + // @formatter:on + temp = createTempFolder(); + File file = createTempFile(temp, "Foo.java", content); + URI uri = file.toURI(); + ICompilationUnit cu = JDTUtils.resolveCompilationUnit(uri); + openDocument(cu, cu.getSource(), 1); + + // change 1 + IType[] types = cu.getAllTypes(); + assertEquals("Unexpected number of types", 1, types.length); + IMethod[] methods = types[0].getMethods(); + assertEquals("Unexpected number of methods", 2, methods.length); + changeDocument(cu, "test1", 2, JDTUtils.toRange(cu, methods[0].getNameRange().getOffset(), methods[0].getNameRange().getLength())); + IMarker[] markers = cu.getResource().findMarkers(BaseDocumentLifeCycleHandler.RENAME_REFERENCE_MARKER_ID, false, IResource.DEPTH_ONE); + assertEquals("Unexpected number of markers", 1, markers.length); + String originalName = (String) markers[0].getAttribute("originalName"); + assertEquals("Unexpected originalName", "test", originalName); + String newName = (String) markers[0].getAttribute("newName"); + assertEquals("Unexpected newName", "test1", newName); + + // change 2 + types = cu.getAllTypes(); + assertEquals("Unexpected number of types", 1, types.length); + methods = types[0].getMethods(); + assertEquals("Unexpected number of methods", 2, methods.length); + changeDocument(cu, "test12", 3, JDTUtils.toRange(cu, methods[0].getNameRange().getOffset(), methods[0].getNameRange().getLength())); + markers = cu.getResource().findMarkers(BaseDocumentLifeCycleHandler.RENAME_REFERENCE_MARKER_ID, false, IResource.DEPTH_ONE); + assertEquals("Unexpected number of markers", 1, markers.length); + originalName = (String) markers[0].getAttribute("originalName"); + assertEquals("Unexpected originalName", "test", originalName); + newName = (String) markers[0].getAttribute("newName"); + assertEquals("Unexpected newName", "test12", newName); + + // rollback change + types = cu.getAllTypes(); + assertEquals("Unexpected number of types", 1, types.length); + methods = types[0].getMethods(); + assertEquals("Unexpected number of methods", 2, methods.length); + changeDocument(cu, "test", 4, JDTUtils.toRange(cu, methods[0].getNameRange().getOffset(), methods[0].getNameRange().getLength())); + markers = cu.getResource().findMarkers(BaseDocumentLifeCycleHandler.RENAME_REFERENCE_MARKER_ID, false, IResource.DEPTH_ONE); + assertEquals("Unexpected number of markers", 0, markers.length); + } finally { + Mockito.lenient().when(preferenceManager.getPreferences().getRenameReferencesEnabled()).thenReturn(false); + } + } + @Test public void testCreateCompilationUnit() throws Exception { IJavaProject javaProject = newEmptyProject();