From 206b0d4eaf318b0298f13b919b5fcfe325c23928 Mon Sep 17 00:00:00 2001 From: akosyakov Date: Wed, 1 Jun 2016 09:06:41 +0200 Subject: [PATCH] [lsi] Implemented list workspace symbols feature Change-Id: I34346c5ac5dd73b2f6f12a1e34a1eb0260d216d6 Signed-off-by: akosyakov --- .../xtext/ide/server/LanguageServerImpl.xtend | 15 ++- .../server/symbol/DocumentSymbolService.xtend | 88 +++++++++++--- .../symbol/WorkspaceSymbolService.xtend | 50 ++++++++ .../server/AbstractLanguageServerTest.xtend | 11 ++ .../ide/tests/server/DocumentSymbolTest.xtend | 11 -- .../tests/server/WorkspaceSymbolTest.xtend | 110 ++++++++++++++++++ 6 files changed, 258 insertions(+), 27 deletions(-) create mode 100644 plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/symbol/WorkspaceSymbolService.xtend create mode 100644 tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/WorkspaceSymbolTest.xtend diff --git a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/LanguageServerImpl.xtend b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/LanguageServerImpl.xtend index a74c8bf38..7438a48ab 100644 --- a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/LanguageServerImpl.xtend +++ b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/LanguageServerImpl.xtend @@ -61,6 +61,7 @@ import org.eclipse.xtext.ide.server.concurrent.RequestManager import org.eclipse.xtext.ide.server.contentassist.ContentAssistService import org.eclipse.xtext.ide.server.findReferences.WorkspaceResourceAccess import org.eclipse.xtext.ide.server.symbol.DocumentSymbolService +import org.eclipse.xtext.ide.server.symbol.WorkspaceSymbolService import org.eclipse.xtext.resource.IResourceServiceProvider import org.eclipse.xtext.validation.Issue @@ -78,6 +79,9 @@ import org.eclipse.xtext.ide.editor.syntaxcoloring.IEditorHighlightingConfigurat @Inject RequestManager requestManager + + @Inject + WorkspaceSymbolService workspaceSymbolService InitializeParams params @Inject Provider workspaceManagerProvider @@ -98,6 +102,7 @@ import org.eclipse.xtext.ide.editor.syntaxcoloring.IEditorHighlightingConfigurat definitionProvider = true referencesProvider = true documentSymbolProvider = true + workspaceSymbolProvider = true textDocumentSync = ServerCapabilities.SYNC_INCREMENTAL completionProvider = new CompletionOptionsImpl => [ resolveProvider = false @@ -247,7 +252,7 @@ import org.eclipse.xtext.ide.editor.syntaxcoloring.IEditorHighlightingConfigurat val result = new CompletionListImpl if (contentAssistService === null) return result - + val entries = workspaceManager.doRead(uri) [ document, resource | val offset = document.getOffSet(params.position) return contentAssistService.createProposals(document.contents, offset, resource, cancelIndicator) @@ -321,11 +326,15 @@ import org.eclipse.xtext.ide.editor.syntaxcoloring.IEditorHighlightingConfigurat ] } - // end symbols override symbol(WorkspaceSymbolParams params) { - throw new UnsupportedOperationException("TODO: auto-generated method stub") + return requestManager.>runRead [ cancelIndicator | + val indexData = workspaceManager.index + return workspaceSymbolService.getSymbols(params.query, resourceAccess, indexData, cancelIndicator) + ] } + // end symbols + override didChangeConfiguraton(DidChangeConfigurationParams params) { throw new UnsupportedOperationException("TODO: auto-generated method stub") } diff --git a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/symbol/DocumentSymbolService.xtend b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/symbol/DocumentSymbolService.xtend index 07c31f4c8..b457577bc 100644 --- a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/symbol/DocumentSymbolService.xtend +++ b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/symbol/DocumentSymbolService.xtend @@ -14,6 +14,8 @@ import io.typefox.lsapi.Location import io.typefox.lsapi.SymbolInformation import io.typefox.lsapi.SymbolInformationImpl import java.util.List +import org.eclipse.emf.common.util.URI +import org.eclipse.emf.ecore.EClass import org.eclipse.emf.ecore.EObject import org.eclipse.xtext.findReferences.IReferenceFinder import org.eclipse.xtext.findReferences.IReferenceFinder.IResourceAccess @@ -23,7 +25,10 @@ import org.eclipse.xtext.findReferences.TargetURIs import org.eclipse.xtext.ide.server.DocumentExtensions import org.eclipse.xtext.ide.util.CancelIndicatorProgressMonitor import org.eclipse.xtext.naming.IQualifiedNameProvider +import org.eclipse.xtext.naming.QualifiedName import org.eclipse.xtext.resource.EObjectAtOffsetHelper +import org.eclipse.xtext.resource.IEObjectDescription +import org.eclipse.xtext.resource.IResourceDescription import org.eclipse.xtext.resource.IResourceDescriptions import org.eclipse.xtext.resource.IResourceServiceProvider import org.eclipse.xtext.resource.XtextResource @@ -55,7 +60,7 @@ class DocumentSymbolService { @Inject Provider targetURIProvider - + @Inject OperationCanceledManager operationCanceledManager @@ -77,11 +82,8 @@ class DocumentSymbolService { for (targetURI : targetURIs) { operationCanceledManager.checkCanceled(cancelIndicator) - resourceAccess.readOnly(targetURI) [ resourceSet | - val object = resourceSet.getEObject(targetURI, true) - if (object !== null) - locations += object.newLocation - return null + resourceAccess.doRead(targetURI) [ obj | + locations += obj.newLocation ] } return locations @@ -105,11 +107,8 @@ class DocumentSymbolService { resourceAccess, indexData, new ReferenceAcceptor(resourceServiceProviderRegistry) [ reference | - resourceAccess.readOnly(reference.sourceEObjectUri) [ resourceSet | - val targetObject = resourceSet.getEObject(reference.sourceEObjectUri, true) - if (targetObject !== null) - locations += targetObject.newLocation(reference.EReference, reference.indexInList) - return null + resourceAccess.doRead(reference.sourceEObjectUri) [ obj | + locations += obj.newLocation(reference.EReference, reference.indexInList) ] ], new CancelIndicatorProgressMonitor(cancelIndicator) @@ -129,7 +128,7 @@ class DocumentSymbolService { while (contents.hasNext) { operationCanceledManager.checkCanceled(cancelIndicator) - val obj = contents.next + val obj = contents.next as EObject val symbol = obj.createSymbol if (symbol !== null) { symbols.put(obj, symbol) @@ -158,11 +157,74 @@ class DocumentSymbolService { } protected def String getSymbolName(EObject object) { - return object.fullyQualifiedName?.toString + return object.fullyQualifiedName.symbolName } protected def int getSymbolKind(EObject object) { + return object.eClass.symbolKind + } + + def List getSymbols( + IResourceDescription resourceDescription, + String query, + IResourceAccess resourceAccess, + CancelIndicator cancelIndicator + ) { + val symbols = newLinkedList + for (description : resourceDescription.exportedObjects) { + operationCanceledManager.checkCanceled(cancelIndicator) + if (description.filter(query)) { + val symbol = description.createSymbol + if (symbol !== null) { + symbols += symbol + resourceAccess.doRead(description.EObjectURI) [ obj | + symbol.location = obj.newLocation + ] + } + } + } + return symbols + } + + protected def boolean filter(IEObjectDescription description, String query) { + return description.qualifiedName.toLowerCase.toString.contains(query.toLowerCase) + } + + protected def SymbolInformationImpl createSymbol(IEObjectDescription description) { + val symbolName = description.symbolName + if(symbolName === null) return null + + val symbol = new SymbolInformationImpl + symbol.name = symbolName + symbol.kind = description.symbolKind + return symbol + } + + protected def String getSymbolName(IEObjectDescription description) { + return description.qualifiedName.symbolName + } + + protected def int getSymbolKind(IEObjectDescription description) { + return description.EClass.symbolKind + } + + protected def String getSymbolName(QualifiedName qualifiedName) { + return qualifiedName?.toString + } + + protected def int getSymbolKind(EClass type) { return 0 +// return SymbolInformation.KIND_PROPERTY + } + + protected def void doRead(IResourceAccess resourceAccess, URI objectURI, (EObject)=>void acceptor) { + resourceAccess.readOnly(objectURI) [ resourceSet | + val object = resourceSet.getEObject(objectURI, true) + if (object !== null) { + acceptor.apply(object) + } + return null + ] } } diff --git a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/symbol/WorkspaceSymbolService.xtend b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/symbol/WorkspaceSymbolService.xtend new file mode 100644 index 000000000..9a63378af --- /dev/null +++ b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/symbol/WorkspaceSymbolService.xtend @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2016 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.symbol + +import com.google.inject.Inject +import com.google.inject.Singleton +import io.typefox.lsapi.SymbolInformation +import java.util.List +import org.eclipse.xtext.findReferences.IReferenceFinder.IResourceAccess +import org.eclipse.xtext.resource.IResourceDescriptions +import org.eclipse.xtext.resource.IResourceServiceProvider +import org.eclipse.xtext.service.OperationCanceledManager +import org.eclipse.xtext.util.CancelIndicator + +/** + * @author kosyakov - Initial contribution and API + */ +@Singleton +class WorkspaceSymbolService { + + @Inject + extension IResourceServiceProvider.Registry + + @Inject + OperationCanceledManager operationCanceledManager + + def List getSymbols( + String query, + IResourceAccess resourceAccess, + IResourceDescriptions indexData, + CancelIndicator cancelIndicator + ) { + val result = newLinkedList + for (resourceDescription : indexData.allResourceDescriptions) { + operationCanceledManager.checkCanceled(cancelIndicator) + val resourceServiceProvider = resourceDescription.URI.resourceServiceProvider + val documentSymbolService = resourceServiceProvider?.get(DocumentSymbolService) + if (documentSymbolService !== null) { + result += documentSymbolService.getSymbols(resourceDescription, query, resourceAccess, cancelIndicator) + } + } + return result + } + +} diff --git a/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/AbstractLanguageServerTest.xtend b/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/AbstractLanguageServerTest.xtend index 6abae5801..fd4983a47 100644 --- a/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/AbstractLanguageServerTest.xtend +++ b/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/AbstractLanguageServerTest.xtend @@ -17,6 +17,7 @@ import io.typefox.lsapi.Location import io.typefox.lsapi.Position import io.typefox.lsapi.PublishDiagnosticsParams import io.typefox.lsapi.Range +import io.typefox.lsapi.SymbolInformation import java.io.File import java.io.FileWriter import java.net.URI @@ -137,4 +138,14 @@ class AbstractLanguageServerTest implements Consumer { protected def dispatch String toExpectation(Position it) '''[«line», «character»]''' + protected def dispatch String toExpectation(SymbolInformation it) ''' + symbol "«name»" { + kind: «kind» + location: «location.toExpectation» + «IF !container.nullOrEmpty» + container: "«container»" + «ENDIF» + } + ''' + } diff --git a/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/DocumentSymbolTest.xtend b/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/DocumentSymbolTest.xtend index 935973a8d..d671fd011 100644 --- a/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/DocumentSymbolTest.xtend +++ b/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/DocumentSymbolTest.xtend @@ -7,7 +7,6 @@ *******************************************************************************/ package org.eclipse.xtext.ide.tests.server -import io.typefox.lsapi.SymbolInformation import org.eclipse.xtend.lib.annotations.Accessors import org.junit.Test @@ -79,14 +78,4 @@ class DocumentSymbolTest extends AbstractLanguageServerTest { String expectedSymbols = '' } - protected def dispatch String toExpectation(SymbolInformation it) ''' - symbol "«name»" { - kind: «kind» - location: «location.toExpectation» - «IF !container.nullOrEmpty» - container: "«container»" - «ENDIF» - } - ''' - } diff --git a/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/WorkspaceSymbolTest.xtend b/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/WorkspaceSymbolTest.xtend new file mode 100644 index 000000000..f8b22d4a5 --- /dev/null +++ b/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/WorkspaceSymbolTest.xtend @@ -0,0 +1,110 @@ +/******************************************************************************* + * Copyright (c) 2016 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.tests.server + +import org.eclipse.xtend.lib.annotations.Accessors +import org.junit.Test + +import static org.junit.Assert.* + +import static extension io.typefox.lsapi.util.LsapiFactories.* + +/** + * @author kosyakov - Initial contribution and API + */ +class WorkspaceSymbolTest extends AbstractLanguageServerTest { + + @Test + def void testDocumentSymbol_01() { + testDocumentSymbol[ + model = ''' + type Foo { + int bar + } + type Bar { + Foo foo + } + ''' + query = 'F' + expectedSymbols = ''' + symbol "Foo" { + kind: 0 + location: MyModel.testlang [[0, 5] .. [0, 8]] + } + symbol "Foo.bar" { + kind: 0 + location: MyModel.testlang [[1, 5] .. [1, 8]] + } + symbol "Foo.bar.int" { + kind: 0 + location: MyModel.testlang [[1, 1] .. [1, 4]] + } + symbol "Bar.foo" { + kind: 0 + location: MyModel.testlang [[4, 5] .. [4, 8]] + } + ''' + ] + } + + @Test + def void testDocumentSymbol_02() { + testDocumentSymbol[ + model = ''' + type Foo { + int bar + } + type Bar { + Foo foo + } + ''' + query = 'oO' + expectedSymbols = ''' + symbol "Foo" { + kind: 0 + location: MyModel.testlang [[0, 5] .. [0, 8]] + } + symbol "Foo.bar" { + kind: 0 + location: MyModel.testlang [[1, 5] .. [1, 8]] + } + symbol "Foo.bar.int" { + kind: 0 + location: MyModel.testlang [[1, 1] .. [1, 4]] + } + symbol "Bar.foo" { + kind: 0 + location: MyModel.testlang [[4, 5] .. [4, 8]] + } + ''' + ] + } + + protected def void testDocumentSymbol((WorkspaceSymbolConfiguraiton)=>void configurator) { + val extension configuration = new WorkspaceSymbolConfiguraiton + configurator.apply(configuration) + val fileUri = filePath -> model + + initialize + open(fileUri, model) + + val symbols = languageServer.symbol(query.newWorkspaceSymbolParams).get + + val String actualSymbols = symbols.toExpectation + assertEquals(expectedSymbols, actualSymbols) + } + + @Accessors + static class WorkspaceSymbolConfiguraiton { + String model = '' + String filePath = 'MyModel.testlang' + String query = '' + String expectedSymbols = '' + } + +} \ No newline at end of file