[LS] overhauled rename service

- delegate to rename language of the declaration of the renamed target
- allow to trigger rename with caret right after the identifier
- support versioned documents

Closes #917, closes #916 and closes #1072
This commit is contained in:
Jan Koehnlein 2019-03-14 14:56:39 +01:00
parent 61bdf5d638
commit a290419be4
16 changed files with 516 additions and 61 deletions

View file

@ -39,13 +39,24 @@ class RenameTest extends AbstractTestLangLanguageServerTest {
}
@Test
def void testRenameOnDeclaration() {
doTest(firstFile, new Position(0,5))
def void testRenameBeforeDeclaration() {
doTest(firstFile, new Position(0, 4))
}
@Test
def void testRenameOnDeclaration() {
doTest(firstFile, new Position(0, 5))
}
@Test
def void testRenameAfterDeclaration() {
doTest(firstFile, new Position(0, 8))
}
@Test
def void testRenameOnReference() {
doTest(firstFile, new Position(1,5))
doTest(firstFile, new Position(1, 5))
}
@Test

View file

@ -12,6 +12,9 @@ import org.eclipse.lsp4j.RenameParams
import org.eclipse.lsp4j.TextDocumentIdentifier
import org.eclipse.xtext.testing.AbstractLanguageServerTest
import org.junit.Test
import org.eclipse.lsp4j.ClientCapabilities
import org.eclipse.lsp4j.WorkspaceClientCapabilities
import org.eclipse.lsp4j.WorkspaceEditCapabilities
/**
* @author koehnlein - Initial contribution and API
@ -38,9 +41,9 @@ class RenameTest2 extends AbstractLanguageServerTest {
val workspaceEdit = languageServer.rename(params).get
assertEquals('''
changes :
Foo.fileawaretestlanguage : Bar [[2, 8] .. [2, 11]]
Bar [[3, 5] .. [3, 8]]
documentChanges :
Foo.fileawaretestlanguage <1> : Bar [[2, 8] .. [2, 11]]
Bar [[3, 5] .. [3, 8]]
'''.toString, toExpectation(workspaceEdit))
}
@ -64,11 +67,21 @@ class RenameTest2 extends AbstractLanguageServerTest {
val workspaceEdit = languageServer.rename(params).get
assertEquals('''
changes :
Foo.fileawaretestlanguage : Baz [[2, 8] .. [2, 11]]
documentChanges :
Foo.fileawaretestlanguage <1> : Baz [[2, 8] .. [2, 11]]
Bar [[5, 5] .. [5, 16]]
Bar [[6, 5] .. [6, 12]]
documentChanges :
'''.toString, toExpectation(workspaceEdit))
}
override protected initialize() {
super.initialize([params | params.capabilities = new ClientCapabilities => [
workspace = new WorkspaceClientCapabilities => [
workspaceEdit = new WorkspaceEditCapabilities => [
documentChanges = true
]
]
]])
}
}

View file

@ -28,7 +28,9 @@ import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SemanticHighlightingInformation;
import org.eclipse.lsp4j.SignatureHelp;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.TextDocumentEdit;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.xtend2.lib.StringConcatenation;
@ -334,6 +336,8 @@ public class CompletionTest extends AbstractTestLangLanguageServerTest {
return _toExpectation((DocumentHighlightKind)it);
} else if (it instanceof String) {
return _toExpectation((String)it);
} else if (it instanceof VersionedTextDocumentIdentifier) {
return _toExpectation((VersionedTextDocumentIdentifier)it);
} else if (it instanceof Pair) {
return _toExpectation((Pair<SemanticHighlightingInformation, List<List<String>>>)it);
} else if (it == null) {
@ -366,6 +370,8 @@ public class CompletionTest extends AbstractTestLangLanguageServerTest {
return _toExpectation((SignatureHelp)it);
} else if (it instanceof SymbolInformation) {
return _toExpectation((SymbolInformation)it);
} else if (it instanceof TextDocumentEdit) {
return _toExpectation((TextDocumentEdit)it);
} else if (it instanceof TextEdit) {
return _toExpectation((TextEdit)it);
} else if (it instanceof WorkspaceEdit) {

View file

@ -51,12 +51,24 @@ public class RenameTest extends AbstractTestLangLanguageServerTest {
this.initialize();
}
@Test
public void testRenameBeforeDeclaration() {
Position _position = new Position(0, 4);
this.doTest(this.firstFile, _position);
}
@Test
public void testRenameOnDeclaration() {
Position _position = new Position(0, 5);
this.doTest(this.firstFile, _position);
}
@Test
public void testRenameAfterDeclaration() {
Position _position = new Position(0, 8);
this.doTest(this.firstFile, _position);
}
@Test
public void testRenameOnReference() {
Position _position = new Position(1, 5);

View file

@ -7,13 +7,20 @@
*/
package org.eclipse.xtext.ide.tests.server;
import org.eclipse.lsp4j.ClientCapabilities;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.RenameParams;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.WorkspaceClientCapabilities;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.WorkspaceEditCapabilities;
import org.eclipse.xtend2.lib.StringConcatenation;
import org.eclipse.xtext.testing.AbstractLanguageServerTest;
import org.eclipse.xtext.xbase.lib.Exceptions;
import org.eclipse.xtext.xbase.lib.ObjectExtensions;
import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;
import org.junit.Test;
/**
@ -49,14 +56,14 @@ public class RenameTest2 extends AbstractLanguageServerTest {
StringConcatenation _builder_1 = new StringConcatenation();
_builder_1.append("changes :");
_builder_1.newLine();
_builder_1.append("documentChanges : ");
_builder_1.newLine();
_builder_1.append(" ");
_builder_1.append("Foo.fileawaretestlanguage : Bar [[2, 8] .. [2, 11]]");
_builder_1.append("Foo.fileawaretestlanguage <1> : Bar [[2, 8] .. [2, 11]]");
_builder_1.newLine();
_builder_1.append(" ");
_builder_1.append("Bar [[3, 5] .. [3, 8]]");
_builder_1.newLine();
_builder_1.append("documentChanges : ");
_builder_1.newLine();
this.assertEquals(_builder_1.toString(), this.toExpectation(workspaceEdit));
} catch (Throwable _e) {
throw Exceptions.sneakyThrow(_e);
@ -99,8 +106,10 @@ public class RenameTest2 extends AbstractLanguageServerTest {
StringConcatenation _builder_1 = new StringConcatenation();
_builder_1.append("changes :");
_builder_1.newLine();
_builder_1.append("documentChanges : ");
_builder_1.newLine();
_builder_1.append(" ");
_builder_1.append("Foo.fileawaretestlanguage : Baz [[2, 8] .. [2, 11]]");
_builder_1.append("Foo.fileawaretestlanguage <1> : Baz [[2, 8] .. [2, 11]]");
_builder_1.newLine();
_builder_1.append(" ");
_builder_1.append("Bar [[5, 5] .. [5, 16]]");
@ -108,11 +117,32 @@ public class RenameTest2 extends AbstractLanguageServerTest {
_builder_1.append(" ");
_builder_1.append("Bar [[6, 5] .. [6, 12]]");
_builder_1.newLine();
_builder_1.append("documentChanges : ");
_builder_1.newLine();
this.assertEquals(_builder_1.toString(), this.toExpectation(workspaceEdit));
} catch (Throwable _e) {
throw Exceptions.sneakyThrow(_e);
}
}
@Override
protected InitializeResult initialize() {
final Procedure1<InitializeParams> _function = (InitializeParams params) -> {
ClientCapabilities _clientCapabilities = new ClientCapabilities();
final Procedure1<ClientCapabilities> _function_1 = (ClientCapabilities it) -> {
WorkspaceClientCapabilities _workspaceClientCapabilities = new WorkspaceClientCapabilities();
final Procedure1<WorkspaceClientCapabilities> _function_2 = (WorkspaceClientCapabilities it_1) -> {
WorkspaceEditCapabilities _workspaceEditCapabilities = new WorkspaceEditCapabilities();
final Procedure1<WorkspaceEditCapabilities> _function_3 = (WorkspaceEditCapabilities it_2) -> {
it_2.setDocumentChanges(Boolean.valueOf(true));
};
WorkspaceEditCapabilities _doubleArrow = ObjectExtensions.<WorkspaceEditCapabilities>operator_doubleArrow(_workspaceEditCapabilities, _function_3);
it_1.setWorkspaceEdit(_doubleArrow);
};
WorkspaceClientCapabilities _doubleArrow = ObjectExtensions.<WorkspaceClientCapabilities>operator_doubleArrow(_workspaceClientCapabilities, _function_2);
it.setWorkspace(_doubleArrow);
};
ClientCapabilities _doubleArrow = ObjectExtensions.<ClientCapabilities>operator_doubleArrow(_clientCapabilities, _function_1);
params.setCapabilities(_doubleArrow);
};
return super.initialize(_function);
}
}

View file

@ -102,6 +102,7 @@ import static org.eclipse.xtext.diagnostics.Severity.*
import com.google.common.collect.ImmutableMultimap
import com.google.common.collect.ImmutableMap
import org.eclipse.xtext.findReferences.IReferenceFinder.IResourceAccess
import org.eclipse.xtext.ide.server.rename.IRenameServiceExtension
/**
* @author Sven Efftinge - Initial contribution and API
@ -607,14 +608,19 @@ import org.eclipse.xtext.findReferences.IReferenceFinder.IResourceAccess
throw new UnsupportedOperationException("TODO: auto-generated method stub")
}
override rename(RenameParams params) {
override rename(RenameParams renameParams) {
return requestManager.runRead[ cancelIndicator |
val uri = params.textDocument.uri.toUri
val uri = renameParams.textDocument.uri.toUri
val resourceServiceProvider = uri.resourceServiceProvider
val renameService = resourceServiceProvider?.get(IRenameService)
if (renameService === null)
return new WorkspaceEdit
renameService.rename(workspaceManager, params, cancelIndicator)
if (renameService instanceof IRenameServiceExtension) {
val options = new IRenameServiceExtension.Options(params?.capabilities?.workspace?.workspaceEdit?.documentChanges === Boolean.TRUE)
renameService.rename(workspaceManager, renameParams, options, cancelIndicator)
} else {
renameService.rename(workspaceManager, renameParams, cancelIndicator)
}
]
}

View file

@ -14,10 +14,14 @@ import org.eclipse.emf.common.util.URI
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.emf.ecore.xmi.XMLResource
import org.eclipse.lsp4j.Range
import org.eclipse.lsp4j.TextDocumentEdit
import org.eclipse.lsp4j.TextEdit
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier
import org.eclipse.lsp4j.WorkspaceEdit
import org.eclipse.lsp4j.jsonrpc.messages.Either
import org.eclipse.xtext.ide.serializer.IEmfResourceChange
import org.eclipse.xtext.ide.serializer.ITextDocumentChange
import org.eclipse.xtext.ide.server.Document
import org.eclipse.xtext.ide.server.WorkspaceManager
import org.eclipse.xtext.parser.IEncodingProvider
import org.eclipse.xtext.resource.IResourceServiceProvider
@ -31,21 +35,35 @@ class ChangeConverter implements IAcceptor<IEmfResourceChange> {
static class Factory {
@Inject IResourceServiceProvider.Registry registry
@Inject protected IResourceServiceProvider.Registry registry
/**
* @deprecated use {@link #create(WorkspaceManager, WorkspaceEdit, boolean} instead.
*/
@Deprecated
def ChangeConverter create(WorkspaceManager workspaceManager, WorkspaceEdit edit) {
new ChangeConverter(workspaceManager, registry, edit)
new ChangeConverter(workspaceManager, registry, edit, null)
}
def ChangeConverter create(WorkspaceManager workspaceManager, WorkspaceEdit edit, IRenameServiceExtension.Options options) {
new ChangeConverter(workspaceManager, registry, edit, options)
}
}
val WorkspaceManager workspaceManager
val IResourceServiceProvider.Registry registry
val WorkspaceEdit edit
val IRenameServiceExtension.Options options
protected new(WorkspaceManager workspaceManager, IResourceServiceProvider.Registry registry, WorkspaceEdit edit) {
protected new(WorkspaceManager workspaceManager, IResourceServiceProvider.Registry registry, WorkspaceEdit edit, IRenameServiceExtension.Options options) {
this.workspaceManager = workspaceManager
this.registry = registry
this.edit = edit
this.options = options
if (options?.clientSupportsVerisonedDocuments)
this.edit.documentChanges = newArrayList
else
this.edit.changes = newLinkedHashMap
}
override accept(IEmfResourceChange change) {
@ -61,7 +79,7 @@ class ChangeConverter implements IAcceptor<IEmfResourceChange> {
workspaceManager.doRead(uri) [ document, resource |
val range = new Range(document.getPosition(0), document.getPosition(document.contents.length))
val textEdit = new TextEdit(range, newContent)
addTextEdit(uri, textEdit)
addTextEdit(uri, document, textEdit)
]
} finally {
outputStream.close
@ -88,12 +106,24 @@ class ChangeConverter implements IAcceptor<IEmfResourceChange> {
val range = new Range(start, end)
new TextEdit(range, replacement.replacementText)
]
addTextEdit(uri, textEdits)
addTextEdit(uri, document, textEdits)
]
}
}
protected def addTextEdit(URI uri, TextEdit... textEdit) {
edit.changes.put(uri.toString, textEdit)
protected def addTextEdit(URI theUri, Document document, TextEdit... textEdits) {
if (options?.clientSupportsVerisonedDocuments) {
edit.documentChanges +=
Either.forLeft(new TextDocumentEdit => [
textDocument = new VersionedTextDocumentIdentifier => [
uri = theUri.toString
version = document.version
]
edits = textEdits
])
} else {
edit.changes.put(theUri.toString, textEdits)
}
}
}

View file

@ -9,13 +9,34 @@ package org.eclipse.xtext.ide.server.rename
import org.eclipse.lsp4j.RenameParams
import org.eclipse.lsp4j.WorkspaceEdit
import org.eclipse.xtend.lib.annotations.Data
import org.eclipse.xtext.ide.server.WorkspaceManager
import org.eclipse.xtext.util.CancelIndicator
/**
* @author koehnlein - Initial contribution and API
* @since 2.13
* @deprectated implement IRenameService2 instead.
*/
@Deprecated
interface IRenameService {
def WorkspaceEdit rename(WorkspaceManager workspaceManager, RenameParams renameParams, CancelIndicator cancelIndicator)
}
/**
* The implementation of rename refactoring for a language.
*
* As opposed to {@link IRenameService} this returns {@link TextDocumentChanges} if the
* client supports versioned documents.
*
* @author koehnlein - Initial contribution and API
* @since 2.17
*/
interface IRenameServiceExtension {
def WorkspaceEdit rename(WorkspaceManager workspaceManager, RenameParams renameParams, Options options, CancelIndicator cancelIndicator)
@Data
class Options {
boolean clientSupportsVerisonedDocuments
}
}

View file

@ -9,59 +9,86 @@ package org.eclipse.xtext.ide.server.rename
import com.google.inject.Inject
import com.google.inject.Provider
import org.eclipse.emf.ecore.EObject
import org.eclipse.emf.ecore.util.EcoreUtil
import org.eclipse.lsp4j.Position
import org.eclipse.lsp4j.RenameParams
import org.eclipse.lsp4j.WorkspaceEdit
import org.eclipse.xtend.lib.annotations.Accessors
import org.eclipse.xtext.TerminalRule
import org.eclipse.xtext.ide.refactoring.IRenameStrategy2
import org.eclipse.xtext.ide.refactoring.RenameChange
import org.eclipse.xtext.ide.refactoring.RenameContext
import org.eclipse.xtext.ide.serializer.IChangeSerializer
import org.eclipse.xtext.ide.server.Document
import org.eclipse.xtext.ide.server.UriExtensions
import org.eclipse.xtext.ide.server.WorkspaceManager
import org.eclipse.xtext.nodemodel.ILeafNode
import org.eclipse.xtext.nodemodel.util.NodeModelUtils
import org.eclipse.xtext.parsetree.reconstr.impl.TokenUtil
import org.eclipse.xtext.resource.EObjectAtOffsetHelper
import org.eclipse.xtext.resource.IResourceServiceProvider
import org.eclipse.xtext.resource.XtextResource
import org.eclipse.xtext.util.CancelIndicator
import static org.eclipse.xtext.ide.refactoring.RefactoringIssueAcceptor.Severity.*
import org.eclipse.xtext.ide.refactoring.IRenameStrategy2
import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider
import org.eclipse.xtext.util.CancelIndicator
import static org.eclipse.xtext.ide.refactoring.RefactoringIssueAcceptor.Severity.*
/**
* @author koehnlein - Initial contribution and API
* @since 2.13
*/
class RenameService implements IRenameService {
@Accessors(PROTECTED_GETTER)
class RenameService implements IRenameService, IRenameServiceExtension {
@Inject extension EObjectAtOffsetHelper
@Inject extension EObjectAtOffsetHelper eObjectAtOffsetHelper
@Inject IRenameStrategy2 renameStrategy
@Inject ChangeConverter.Factory converterFactory
@Inject extension UriExtensions
@Inject extension UriExtensions uriExtensions
@Inject Provider<IChangeSerializer> changeSerializerProvider
@Inject Provider<ServerRefactoringIssueAcceptor> issueProvider
@Inject IResourceServiceProvider.Registry serviceProviderRegistry
@Inject TokenUtil tokenUtil
/**
* @deprecated use {@link #rename(WorkspaceManager, RenameParams, Options, CancelIndicator)}
* instead.
*/
@Deprecated
override rename(WorkspaceManager workspaceManager, RenameParams renameParams, CancelIndicator cancelIndicator) {
rename(workspaceManager, renameParams, new Options(false), cancelIndicator)
}
override rename(WorkspaceManager workspaceManager, RenameParams renameParams, Options options, CancelIndicator cancelIndicator) {
val uri = renameParams.textDocument.uri.toUri
val issueAcceptor = issueProvider.get
workspaceManager.doRead(uri) [ document, resource |
workspaceManager.doRead(uri) [ document, resource |
val projectManager = workspaceManager.getProjectManager(uri)
val resourceSet = projectManager.createNewResourceSet(projectManager.indexState.resourceDescriptions)
resourceSet.loadOptions.put(ResourceDescriptionsProvider.LIVE_SCOPE, true)
val offset = document.getOffSet(renameParams.position)
val workspaceEdit = new WorkspaceEdit
val xtextResource = resourceSet.getResource(resource.URI, true)
if (xtextResource instanceof XtextResource) {
val element = xtextResource.resolveElementAt(offset)
val element = xtextResource.getElementAtOffset(document, renameParams.position)
if (element === null || element.eIsProxy) {
issueAcceptor.add(FATAL, '''No element found at position line:«renameParams.position.line» column:«renameParams.position.character»''')
issueAcceptor.add(
FATAL, '''No element found at position line:«renameParams.position.line» column:«renameParams.position.character»''')
} else {
val services = serviceProviderRegistry.getResourceServiceProvider(element.eResource.URI)
val changeSerializer = services.get(IChangeSerializer)
val change = new RenameChange(renameParams.newName, EcoreUtil.getURI(element))
val changeSerializer = changeSerializerProvider.get
val context = new RenameContext(#[change], resourceSet, changeSerializer, issueAcceptor)
val renameStrategy = services.get(IRenameStrategy2)
renameStrategy.applyRename(context)
val changeConverter = converterFactory.create(workspaceManager, workspaceEdit)
val converterFactory = services.get(ChangeConverter.Factory)
val changeConverter = converterFactory.create(workspaceManager, workspaceEdit, options)
changeSerializer.applyModifications(changeConverter)
}
} else {
@ -70,4 +97,19 @@ class RenameService implements IRenameService {
return workspaceEdit
]
}
protected def EObject getElementAtOffset(XtextResource xtextResource, Document document, Position caretPosition) {
val caretOffset = document.getOffSet(caretPosition)
val leafNode = NodeModelUtils.findLeafNodeAtOffset(xtextResource.parseResult.rootNode, caretOffset)
val offset = if (caretOffset > 0 && leafNode.offset === caretOffset && !isIdentifier(leafNode))
caretOffset - 1
else
caretOffset
return xtextResource.resolveElementAt(offset)
}
protected def isIdentifier(ILeafNode leafNode) {
return leafNode.grammarElement instanceof TerminalRule
&& !tokenUtil.isWhitespaceOrCommentNode(leafNode)
}
}

View file

@ -83,6 +83,7 @@ import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.WorkspaceClientCapabilities;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.WorkspaceEditCapabilities;
import org.eclipse.lsp4j.WorkspaceSymbolParams;
import org.eclipse.lsp4j.jsonrpc.Endpoint;
import org.eclipse.lsp4j.jsonrpc.json.JsonRpcMethod;
@ -120,6 +121,7 @@ import org.eclipse.xtext.ide.server.formatting.FormattingService;
import org.eclipse.xtext.ide.server.hover.IHoverService;
import org.eclipse.xtext.ide.server.occurrences.IDocumentHighlightService;
import org.eclipse.xtext.ide.server.rename.IRenameService;
import org.eclipse.xtext.ide.server.rename.IRenameServiceExtension;
import org.eclipse.xtext.ide.server.semanticHighlight.SemanticHighlightingRegistry;
import org.eclipse.xtext.ide.server.signatureHelp.ISignatureHelpService;
import org.eclipse.xtext.ide.server.symbol.DocumentSymbolService;
@ -925,11 +927,11 @@ public class LanguageServerImpl implements LanguageServer, WorkspaceService, Tex
}
@Override
public CompletableFuture<WorkspaceEdit> rename(final RenameParams params) {
public CompletableFuture<WorkspaceEdit> rename(final RenameParams renameParams) {
final Function1<CancelIndicator, WorkspaceEdit> _function = (CancelIndicator cancelIndicator) -> {
WorkspaceEdit _xblockexpression = null;
{
final URI uri = this._uriExtensions.toUri(params.getTextDocument().getUri());
final URI uri = this._uriExtensions.toUri(renameParams.getTextDocument().getUri());
final IResourceServiceProvider resourceServiceProvider = this.languagesRegistry.getResourceServiceProvider(uri);
IRenameService _get = null;
if (resourceServiceProvider!=null) {
@ -939,7 +941,35 @@ public class LanguageServerImpl implements LanguageServer, WorkspaceService, Tex
if ((renameService == null)) {
return new WorkspaceEdit();
}
_xblockexpression = renameService.rename(this.workspaceManager, params, cancelIndicator);
WorkspaceEdit _xifexpression = null;
if ((renameService instanceof IRenameServiceExtension)) {
WorkspaceEdit _xblockexpression_1 = null;
{
ClientCapabilities _capabilities = null;
if (this.params!=null) {
_capabilities=this.params.getCapabilities();
}
WorkspaceClientCapabilities _workspace = null;
if (_capabilities!=null) {
_workspace=_capabilities.getWorkspace();
}
WorkspaceEditCapabilities _workspaceEdit = null;
if (_workspace!=null) {
_workspaceEdit=_workspace.getWorkspaceEdit();
}
Boolean _documentChanges = null;
if (_workspaceEdit!=null) {
_documentChanges=_workspaceEdit.getDocumentChanges();
}
boolean _tripleEquals = (_documentChanges == Boolean.TRUE);
final IRenameServiceExtension.Options options = new IRenameServiceExtension.Options(_tripleEquals);
_xblockexpression_1 = ((IRenameServiceExtension)renameService).rename(this.workspaceManager, renameParams, options, cancelIndicator);
}
_xifexpression = _xblockexpression_1;
} else {
_xifexpression = renameService.rename(this.workspaceManager, renameParams, cancelIndicator);
}
_xblockexpression = _xifexpression;
}
return _xblockexpression;
};

View file

@ -17,22 +17,30 @@ import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.ResourceOperation;
import org.eclipse.lsp4j.TextDocumentEdit;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.xtext.formatting2.regionaccess.ITextReplacement;
import org.eclipse.xtext.ide.serializer.IEmfResourceChange;
import org.eclipse.xtext.ide.serializer.ITextDocumentChange;
import org.eclipse.xtext.ide.server.Document;
import org.eclipse.xtext.ide.server.WorkspaceManager;
import org.eclipse.xtext.ide.server.rename.IRenameServiceExtension;
import org.eclipse.xtext.parser.IEncodingProvider;
import org.eclipse.xtext.resource.IResourceServiceProvider;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.util.IAcceptor;
import org.eclipse.xtext.xbase.lib.CollectionLiterals;
import org.eclipse.xtext.xbase.lib.Conversions;
import org.eclipse.xtext.xbase.lib.Exceptions;
import org.eclipse.xtext.xbase.lib.Functions.Function1;
import org.eclipse.xtext.xbase.lib.Functions.Function2;
import org.eclipse.xtext.xbase.lib.ListExtensions;
import org.eclipse.xtext.xbase.lib.ObjectExtensions;
import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;
/**
* @author koehnlein - Initial contribution and API
@ -42,10 +50,18 @@ import org.eclipse.xtext.xbase.lib.ListExtensions;
public class ChangeConverter implements IAcceptor<IEmfResourceChange> {
public static class Factory {
@Inject
private IResourceServiceProvider.Registry registry;
protected IResourceServiceProvider.Registry registry;
/**
* @deprecated use {@link #create(WorkspaceManager, WorkspaceEdit, boolean} instead.
*/
@Deprecated
public ChangeConverter create(final WorkspaceManager workspaceManager, final WorkspaceEdit edit) {
return new ChangeConverter(workspaceManager, this.registry, edit);
return new ChangeConverter(workspaceManager, this.registry, edit, null);
}
public ChangeConverter create(final WorkspaceManager workspaceManager, final WorkspaceEdit edit, final IRenameServiceExtension.Options options) {
return new ChangeConverter(workspaceManager, this.registry, edit, options);
}
}
@ -55,10 +71,22 @@ public class ChangeConverter implements IAcceptor<IEmfResourceChange> {
private final WorkspaceEdit edit;
protected ChangeConverter(final WorkspaceManager workspaceManager, final IResourceServiceProvider.Registry registry, final WorkspaceEdit edit) {
private final IRenameServiceExtension.Options options;
protected ChangeConverter(final WorkspaceManager workspaceManager, final IResourceServiceProvider.Registry registry, final WorkspaceEdit edit, final IRenameServiceExtension.Options options) {
this.workspaceManager = workspaceManager;
this.registry = registry;
this.edit = edit;
this.options = options;
boolean _isClientSupportsVerisonedDocuments = false;
if (options!=null) {
_isClientSupportsVerisonedDocuments=options.isClientSupportsVerisonedDocuments();
}
if (_isClientSupportsVerisonedDocuments) {
this.edit.setDocumentChanges(CollectionLiterals.<Either<TextDocumentEdit, ResourceOperation>>newArrayList());
} else {
this.edit.setChanges(CollectionLiterals.<String, List<TextEdit>>newLinkedHashMap());
}
}
@Override
@ -75,18 +103,18 @@ public class ChangeConverter implements IAcceptor<IEmfResourceChange> {
byte[] _byteArray = outputStream.toByteArray();
String _charset = this.getCharset(change.getResource());
final String newContent = new String(_byteArray, _charset);
final Function2<Document, XtextResource, List<TextEdit>> _function = (Document document, XtextResource resource) -> {
List<TextEdit> _xblockexpression = null;
final Function2<Document, XtextResource, Object> _function = (Document document, XtextResource resource) -> {
Object _xblockexpression = null;
{
Position _position = document.getPosition(0);
Position _position_1 = document.getPosition(document.getContents().length());
final Range range = new Range(_position, _position_1);
final TextEdit textEdit = new TextEdit(range, newContent);
_xblockexpression = this.addTextEdit(uri, textEdit);
_xblockexpression = this.addTextEdit(uri, document, textEdit);
}
return _xblockexpression;
};
this.workspaceManager.<List<TextEdit>>doRead(uri, _function);
this.workspaceManager.<Object>doRead(uri, _function);
} finally {
outputStream.close();
}
@ -121,8 +149,8 @@ public class ChangeConverter implements IAcceptor<IEmfResourceChange> {
boolean _greaterThan = (_size > 0);
if (_greaterThan) {
final URI uri = change.getNewURI();
final Function2<Document, XtextResource, List<TextEdit>> _function = (Document document, XtextResource resource) -> {
List<TextEdit> _xblockexpression = null;
final Function2<Document, XtextResource, Object> _function = (Document document, XtextResource resource) -> {
Object _xblockexpression = null;
{
final Function1<ITextReplacement, TextEdit> _function_1 = (ITextReplacement replacement) -> {
TextEdit _xblockexpression_1 = null;
@ -139,16 +167,40 @@ public class ChangeConverter implements IAcceptor<IEmfResourceChange> {
return _xblockexpression_1;
};
final List<TextEdit> textEdits = ListExtensions.<ITextReplacement, TextEdit>map(change.getReplacements(), _function_1);
_xblockexpression = this.addTextEdit(uri, ((TextEdit[])Conversions.unwrapArray(textEdits, TextEdit.class)));
_xblockexpression = this.addTextEdit(uri, document, ((TextEdit[])Conversions.unwrapArray(textEdits, TextEdit.class)));
}
return _xblockexpression;
};
this.workspaceManager.<List<TextEdit>>doRead(uri, _function);
this.workspaceManager.<Object>doRead(uri, _function);
}
}
protected List<TextEdit> addTextEdit(final URI uri, final TextEdit... textEdit) {
return this.edit.getChanges().put(uri.toString(), ((List<TextEdit>)Conversions.doWrapArray(textEdit)));
protected Object addTextEdit(final URI theUri, final Document document, final TextEdit... textEdits) {
Object _xifexpression = null;
boolean _isClientSupportsVerisonedDocuments = false;
if (this.options!=null) {
_isClientSupportsVerisonedDocuments=this.options.isClientSupportsVerisonedDocuments();
}
if (_isClientSupportsVerisonedDocuments) {
List<Either<TextDocumentEdit, ResourceOperation>> _documentChanges = this.edit.getDocumentChanges();
TextDocumentEdit _textDocumentEdit = new TextDocumentEdit();
final Procedure1<TextDocumentEdit> _function = (TextDocumentEdit it) -> {
VersionedTextDocumentIdentifier _versionedTextDocumentIdentifier = new VersionedTextDocumentIdentifier();
final Procedure1<VersionedTextDocumentIdentifier> _function_1 = (VersionedTextDocumentIdentifier it_1) -> {
it_1.setUri(theUri.toString());
it_1.setVersion(document.getVersion());
};
VersionedTextDocumentIdentifier _doubleArrow = ObjectExtensions.<VersionedTextDocumentIdentifier>operator_doubleArrow(_versionedTextDocumentIdentifier, _function_1);
it.setTextDocument(_doubleArrow);
it.setEdits(((List<TextEdit>)Conversions.doWrapArray(textEdits)));
};
TextDocumentEdit _doubleArrow = ObjectExtensions.<TextDocumentEdit>operator_doubleArrow(_textDocumentEdit, _function);
Either<TextDocumentEdit, ResourceOperation> _forLeft = Either.<TextDocumentEdit, ResourceOperation>forLeft(_doubleArrow);
_xifexpression = Boolean.valueOf(_documentChanges.add(_forLeft));
} else {
_xifexpression = this.edit.getChanges().put(theUri.toString(), ((List<TextEdit>)Conversions.doWrapArray(textEdits)));
}
return _xifexpression;
}
protected void handleReplacements(final IEmfResourceChange change) {

View file

@ -15,7 +15,9 @@ import org.eclipse.xtext.util.CancelIndicator;
/**
* @author koehnlein - Initial contribution and API
* @since 2.13
* @deprectated implement IRenameService2 instead.
*/
@Deprecated
@SuppressWarnings("all")
public interface IRenameService {
public abstract WorkspaceEdit rename(final WorkspaceManager workspaceManager, final RenameParams renameParams, final CancelIndicator cancelIndicator);

View file

@ -0,0 +1,74 @@
/**
* Copyright (c) 2017 TypeFox GmbH (http://www.typefox.io) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.xtext.ide.server.rename;
import org.eclipse.lsp4j.RenameParams;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.xtend.lib.annotations.Data;
import org.eclipse.xtext.ide.server.WorkspaceManager;
import org.eclipse.xtext.util.CancelIndicator;
import org.eclipse.xtext.xbase.lib.Pure;
import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
/**
* The implementation of rename refactoring for a language.
*
* As opposed to {@link IRenameService} this returns {@link TextDocumentChanges} if the
* client supports versioned documents.
*
* @author koehnlein - Initial contribution and API
* @since 2.17
*/
@SuppressWarnings("all")
public interface IRenameServiceExtension {
@Data
public static class Options {
private final boolean clientSupportsVerisonedDocuments;
public Options(final boolean clientSupportsVerisonedDocuments) {
super();
this.clientSupportsVerisonedDocuments = clientSupportsVerisonedDocuments;
}
@Override
@Pure
public int hashCode() {
return 31 * 1 + (this.clientSupportsVerisonedDocuments ? 1231 : 1237);
}
@Override
@Pure
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
IRenameServiceExtension.Options other = (IRenameServiceExtension.Options) obj;
if (other.clientSupportsVerisonedDocuments != this.clientSupportsVerisonedDocuments)
return false;
return true;
}
@Override
@Pure
public String toString() {
ToStringBuilder b = new ToStringBuilder(this);
b.add("clientSupportsVerisonedDocuments", this.clientSupportsVerisonedDocuments);
return b.toString();
}
@Pure
public boolean isClientSupportsVerisonedDocuments() {
return this.clientSupportsVerisonedDocuments;
}
}
public abstract WorkspaceEdit rename(final WorkspaceManager workspaceManager, final RenameParams renameParams, final IRenameServiceExtension.Options options, final CancelIndicator cancelIndicator);
}

View file

@ -14,9 +14,13 @@ import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.RenameParams;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.xtend.lib.annotations.AccessorType;
import org.eclipse.xtend.lib.annotations.Accessors;
import org.eclipse.xtend2.lib.StringConcatenation;
import org.eclipse.xtext.TerminalRule;
import org.eclipse.xtext.ide.refactoring.IRenameStrategy2;
import org.eclipse.xtext.ide.refactoring.RefactoringIssueAcceptor;
import org.eclipse.xtext.ide.refactoring.RenameChange;
@ -28,8 +32,13 @@ import org.eclipse.xtext.ide.server.UriExtensions;
import org.eclipse.xtext.ide.server.WorkspaceManager;
import org.eclipse.xtext.ide.server.rename.ChangeConverter;
import org.eclipse.xtext.ide.server.rename.IRenameService;
import org.eclipse.xtext.ide.server.rename.IRenameServiceExtension;
import org.eclipse.xtext.ide.server.rename.ServerRefactoringIssueAcceptor;
import org.eclipse.xtext.nodemodel.ILeafNode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.parsetree.reconstr.impl.TokenUtil;
import org.eclipse.xtext.resource.EObjectAtOffsetHelper;
import org.eclipse.xtext.resource.IResourceServiceProvider;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.resource.XtextResourceSet;
import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider;
@ -37,16 +46,18 @@ import org.eclipse.xtext.util.CancelIndicator;
import org.eclipse.xtext.xbase.lib.CollectionLiterals;
import org.eclipse.xtext.xbase.lib.Extension;
import org.eclipse.xtext.xbase.lib.Functions.Function2;
import org.eclipse.xtext.xbase.lib.Pure;
/**
* @author koehnlein - Initial contribution and API
* @since 2.13
*/
@Accessors(AccessorType.PROTECTED_GETTER)
@SuppressWarnings("all")
public class RenameService implements IRenameService {
public class RenameService implements IRenameService, IRenameServiceExtension {
@Inject
@Extension
private EObjectAtOffsetHelper _eObjectAtOffsetHelper;
private EObjectAtOffsetHelper eObjectAtOffsetHelper;
@Inject
private IRenameStrategy2 renameStrategy;
@ -56,7 +67,7 @@ public class RenameService implements IRenameService {
@Inject
@Extension
private UriExtensions _uriExtensions;
private UriExtensions uriExtensions;
@Inject
private Provider<IChangeSerializer> changeSerializerProvider;
@ -64,21 +75,37 @@ public class RenameService implements IRenameService {
@Inject
private Provider<ServerRefactoringIssueAcceptor> issueProvider;
@Inject
private IResourceServiceProvider.Registry serviceProviderRegistry;
@Inject
private TokenUtil tokenUtil;
/**
* @deprecated use {@link #rename(WorkspaceManager, RenameParams, Options, CancelIndicator)}
* instead.
*/
@Deprecated
@Override
public WorkspaceEdit rename(final WorkspaceManager workspaceManager, final RenameParams renameParams, final CancelIndicator cancelIndicator) {
IRenameServiceExtension.Options _options = new IRenameServiceExtension.Options(false);
return this.rename(workspaceManager, renameParams, _options, cancelIndicator);
}
@Override
public WorkspaceEdit rename(final WorkspaceManager workspaceManager, final RenameParams renameParams, final IRenameServiceExtension.Options options, final CancelIndicator cancelIndicator) {
WorkspaceEdit _xblockexpression = null;
{
final URI uri = this._uriExtensions.toUri(renameParams.getTextDocument().getUri());
final URI uri = this.uriExtensions.toUri(renameParams.getTextDocument().getUri());
final ServerRefactoringIssueAcceptor issueAcceptor = this.issueProvider.get();
final Function2<Document, XtextResource, WorkspaceEdit> _function = (Document document, XtextResource resource) -> {
final ProjectManager projectManager = workspaceManager.getProjectManager(uri);
final XtextResourceSet resourceSet = projectManager.createNewResourceSet(projectManager.getIndexState().getResourceDescriptions());
resourceSet.getLoadOptions().put(ResourceDescriptionsProvider.LIVE_SCOPE, Boolean.valueOf(true));
final int offset = document.getOffSet(renameParams.getPosition());
final WorkspaceEdit workspaceEdit = new WorkspaceEdit();
final Resource xtextResource = resourceSet.getResource(resource.getURI(), true);
if ((xtextResource instanceof XtextResource)) {
final EObject element = this._eObjectAtOffsetHelper.resolveElementAt(((XtextResource)xtextResource), offset);
final EObject element = this.getElementAtOffset(((XtextResource)xtextResource), document, renameParams.getPosition());
if (((element == null) || element.eIsProxy())) {
StringConcatenation _builder = new StringConcatenation();
_builder.append("No element found at position line:");
@ -87,15 +114,19 @@ public class RenameService implements IRenameService {
_builder.append(" column:");
int _character = renameParams.getPosition().getCharacter();
_builder.append(_character);
issueAcceptor.add(RefactoringIssueAcceptor.Severity.FATAL, _builder.toString());
issueAcceptor.add(
RefactoringIssueAcceptor.Severity.FATAL, _builder.toString());
} else {
final IResourceServiceProvider services = this.serviceProviderRegistry.getResourceServiceProvider(element.eResource().getURI());
final IChangeSerializer changeSerializer = services.<IChangeSerializer>get(IChangeSerializer.class);
String _newName = renameParams.getNewName();
URI _uRI = EcoreUtil.getURI(element);
final RenameChange change = new RenameChange(_newName, _uRI);
final IChangeSerializer changeSerializer = this.changeSerializerProvider.get();
final RenameContext context = new RenameContext(Collections.<RenameChange>unmodifiableList(CollectionLiterals.<RenameChange>newArrayList(change)), resourceSet, changeSerializer, issueAcceptor);
this.renameStrategy.applyRename(context);
final ChangeConverter changeConverter = this.converterFactory.create(workspaceManager, workspaceEdit);
final IRenameStrategy2 renameStrategy = services.<IRenameStrategy2>get(IRenameStrategy2.class);
renameStrategy.applyRename(context);
final ChangeConverter.Factory converterFactory = services.<ChangeConverter.Factory>get(ChangeConverter.Factory.class);
final ChangeConverter changeConverter = converterFactory.create(workspaceManager, workspaceEdit, options);
changeSerializer.applyModifications(changeConverter);
}
} else {
@ -107,4 +138,61 @@ public class RenameService implements IRenameService {
}
return _xblockexpression;
}
protected EObject getElementAtOffset(final XtextResource xtextResource, final Document document, final Position caretPosition) {
final int caretOffset = document.getOffSet(caretPosition);
final ILeafNode leafNode = NodeModelUtils.findLeafNodeAtOffset(xtextResource.getParseResult().getRootNode(), caretOffset);
int _xifexpression = (int) 0;
if ((((caretOffset > 0) && (leafNode.getOffset() == caretOffset)) && (!this.isIdentifier(leafNode)))) {
_xifexpression = (caretOffset - 1);
} else {
_xifexpression = caretOffset;
}
final int offset = _xifexpression;
return this.eObjectAtOffsetHelper.resolveElementAt(xtextResource, offset);
}
protected boolean isIdentifier(final ILeafNode leafNode) {
return ((leafNode.getGrammarElement() instanceof TerminalRule) && (!this.tokenUtil.isWhitespaceOrCommentNode(leafNode)));
}
@Pure
protected EObjectAtOffsetHelper getEObjectAtOffsetHelper() {
return this.eObjectAtOffsetHelper;
}
@Pure
protected IRenameStrategy2 getRenameStrategy() {
return this.renameStrategy;
}
@Pure
protected ChangeConverter.Factory getConverterFactory() {
return this.converterFactory;
}
@Pure
protected UriExtensions getUriExtensions() {
return this.uriExtensions;
}
@Pure
protected Provider<IChangeSerializer> getChangeSerializerProvider() {
return this.changeSerializerProvider;
}
@Pure
protected Provider<ServerRefactoringIssueAcceptor> getIssueProvider() {
return this.issueProvider;
}
@Pure
protected IResourceServiceProvider.Registry getServiceProviderRegistry() {
return this.serviceProviderRegistry;
}
@Pure
protected TokenUtil getTokenUtil() {
return this.tokenUtil;
}
}

View file

@ -54,10 +54,12 @@ import org.eclipse.lsp4j.SemanticHighlightingInformation
import org.eclipse.lsp4j.SemanticHighlightingParams
import org.eclipse.lsp4j.SignatureHelp
import org.eclipse.lsp4j.SymbolInformation
import org.eclipse.lsp4j.TextDocumentEdit
import org.eclipse.lsp4j.TextDocumentIdentifier
import org.eclipse.lsp4j.TextDocumentItem
import org.eclipse.lsp4j.TextDocumentPositionParams
import org.eclipse.lsp4j.TextEdit
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier
import org.eclipse.lsp4j.WorkspaceEdit
import org.eclipse.lsp4j.WorkspaceSymbolParams
import org.eclipse.lsp4j.jsonrpc.Endpoint
@ -419,6 +421,15 @@ abstract class AbstractLanguageServerTest implements Endpoint {
edit : «edit.toExpectation»
'''
protected def dispatch String toExpectation(TextDocumentEdit e) '''
«e.textDocument.toExpectation» : «e.edits.toExpectation»
'''
protected def dispatch String toExpectation(VersionedTextDocumentIdentifier v)
'''«org.eclipse.emf.common.util.URI.createURI(v.uri).lastSegment» <«v.version»>'''
@Accessors static class TestCodeActionConfiguration extends TextDocumentPositionConfiguration {
String expectedCodeActions = ''

View file

@ -67,6 +67,7 @@ import org.eclipse.lsp4j.SignatureHelp;
import org.eclipse.lsp4j.SignatureInformation;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.TextDocumentClientCapabilities;
import org.eclipse.lsp4j.TextDocumentEdit;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentItem;
import org.eclipse.lsp4j.TextDocumentPositionParams;
@ -936,6 +937,28 @@ public abstract class AbstractLanguageServerTest implements Endpoint {
return _builder.toString();
}
protected String _toExpectation(final TextDocumentEdit e) {
StringConcatenation _builder = new StringConcatenation();
String _expectation = this.toExpectation(e.getTextDocument());
_builder.append(_expectation);
_builder.append(" : ");
String _expectation_1 = this.toExpectation(e.getEdits());
_builder.append(_expectation_1);
_builder.newLineIfNotEmpty();
return _builder.toString();
}
protected String _toExpectation(final VersionedTextDocumentIdentifier v) {
StringConcatenation _builder = new StringConcatenation();
String _lastSegment = org.eclipse.emf.common.util.URI.createURI(v.getUri()).lastSegment();
_builder.append(_lastSegment);
_builder.append(" <");
Integer _version = v.getVersion();
_builder.append(_version);
_builder.append(">");
return _builder.toString();
}
protected void testCodeAction(final Procedure1<? super AbstractLanguageServerTest.TestCodeActionConfiguration> configurator) {
try {
@Extension
@ -1459,6 +1482,8 @@ public abstract class AbstractLanguageServerTest implements Endpoint {
return _toExpectation((DocumentHighlightKind)it);
} else if (it instanceof String) {
return _toExpectation((String)it);
} else if (it instanceof VersionedTextDocumentIdentifier) {
return _toExpectation((VersionedTextDocumentIdentifier)it);
} else if (it instanceof Pair) {
return _toExpectation((Pair<SemanticHighlightingInformation, List<List<String>>>)it);
} else if (it == null) {
@ -1491,6 +1516,8 @@ public abstract class AbstractLanguageServerTest implements Endpoint {
return _toExpectation((SignatureHelp)it);
} else if (it instanceof SymbolInformation) {
return _toExpectation((SymbolInformation)it);
} else if (it instanceof TextDocumentEdit) {
return _toExpectation((TextDocumentEdit)it);
} else if (it instanceof TextEdit) {
return _toExpectation((TextEdit)it);
} else if (it instanceof WorkspaceEdit) {