diff --git a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/DocumentExtensions.xtend b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/DocumentExtensions.xtend index 59afc2c32..e3f71c962 100644 --- a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/DocumentExtensions.xtend +++ b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/DocumentExtensions.xtend @@ -51,6 +51,7 @@ class DocumentExtensions { } def RangeImpl newRange(Resource resource, ITextRegion region) { + if (region === null) return null return resource.newRange(region.offset, region.offset + region.length) } 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 7438a48ab..94c33ad8f 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 @@ -33,6 +33,7 @@ import io.typefox.lsapi.FileEvent import io.typefox.lsapi.InitializeParams import io.typefox.lsapi.InitializeResult import io.typefox.lsapi.InitializeResultImpl +import io.typefox.lsapi.LanguageDescriptionImpl import io.typefox.lsapi.Location import io.typefox.lsapi.MessageParams import io.typefox.lsapi.PublishDiagnosticsParams @@ -55,21 +56,21 @@ import java.util.concurrent.CompletableFuture import java.util.function.Consumer import org.eclipse.emf.common.util.URI import org.eclipse.xtend.lib.annotations.Accessors +import org.eclipse.xtext.LanguageInfo import org.eclipse.xtext.ide.editor.contentassist.ContentAssistEntry +import org.eclipse.xtext.ide.editor.syntaxcoloring.IEditorHighlightingConfigurationProvider import org.eclipse.xtext.ide.server.concurrent.CancellableIndicator 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.hover.HoverService import org.eclipse.xtext.ide.server.symbol.DocumentSymbolService import org.eclipse.xtext.ide.server.symbol.WorkspaceSymbolService +import org.eclipse.xtext.resource.FileExtensionProvider import org.eclipse.xtext.resource.IResourceServiceProvider import org.eclipse.xtext.validation.Issue import static io.typefox.lsapi.util.LsapiFactories.* -import io.typefox.lsapi.LanguageDescriptionImpl -import org.eclipse.xtext.resource.FileExtensionProvider -import org.eclipse.xtext.LanguageInfo -import org.eclipse.xtext.ide.editor.syntaxcoloring.IEditorHighlightingConfigurationProvider /** * @@ -99,6 +100,7 @@ import org.eclipse.xtext.ide.editor.syntaxcoloring.IEditorHighlightingConfigurat val result = new InitializeResultImpl result.capabilities = new ServerCapabilitiesImpl => [ + hoverProvider = true definitionProvider = true referencesProvider = true documentSymbolProvider = true @@ -335,6 +337,25 @@ import org.eclipse.xtext.ide.editor.syntaxcoloring.IEditorHighlightingConfigurat // end symbols + // hover + + override hover(TextDocumentPositionParams params) { + return requestManager.runRead[ cancelIndicator | + val uri = params.textDocument.uri.toUri + val resourceServiceProvider = uri.resourceServiceProvider + val hoverService = resourceServiceProvider?.get(HoverService) + if (hoverService === null) + return emptyHover + + return workspaceManager.doRead(uri) [ document, resource | + val offset = document.getOffSet(params.position) + return hoverService.hover(resource, offset) + ] + ] + } + + // end hover + override didChangeConfiguraton(DidChangeConfigurationParams params) { throw new UnsupportedOperationException("TODO: auto-generated method stub") } @@ -343,10 +364,6 @@ import org.eclipse.xtext.ide.editor.syntaxcoloring.IEditorHighlightingConfigurat return CompletableFuture.completedFuture(unresolved) } - override hover(TextDocumentPositionParams position) { - throw new UnsupportedOperationException("TODO: auto-generated method stub") - } - override signatureHelp(TextDocumentPositionParams position) { throw new UnsupportedOperationException("TODO: auto-generated method stub") } diff --git a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/hover/HoverService.xtend b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/hover/HoverService.xtend new file mode 100644 index 000000000..6818bb248 --- /dev/null +++ b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/hover/HoverService.xtend @@ -0,0 +1,62 @@ +/******************************************************************************* + * 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.hover + +import com.google.inject.Inject +import com.google.inject.Singleton +import io.typefox.lsapi.Hover +import org.eclipse.xtext.documentation.IEObjectDocumentationProvider +import org.eclipse.xtext.ide.server.DocumentExtensions +import org.eclipse.xtext.resource.EObjectAtOffsetHelper +import org.eclipse.xtext.resource.ILocationInFileProvider +import org.eclipse.xtext.resource.XtextResource + +import static io.typefox.lsapi.util.LsapiFactories.* + +/** + * @author kosyakov - Initial contribution and API + */ +@Singleton +class HoverService { + + @Inject + extension DocumentExtensions + + @Inject + extension EObjectAtOffsetHelper + + @Inject + extension ILocationInFileProvider + + @Inject + extension IEObjectDocumentationProvider + + def Hover hover(XtextResource resource, int offset) { + val element = resource.resolveElementAt(offset) + if (element === null) + return emptyHover + + val documentation = element.documentation + if (documentation === null) + return emptyHover + + val contents = #[newMarkedString(documentation, null)] + + val containedElement = resource.resolveContainedElementAt(offset) + val textRegion = containedElement.significantTextRegion + if (textRegion === null) + return newHover(contents, null) + + if (!textRegion.contains(offset)) + return emptyHover + + val range = resource.newRange(textRegion) + return newHover(contents, range) + } + +} 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 fd4983a47..7255f854d 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 @@ -131,6 +131,8 @@ class AbstractLanguageServerTest implements Consumer { «element.toExpectation» «ENDFOR» ''' + + protected def dispatch String toExpectation(Void it) { '' } protected def dispatch String toExpectation(Location it) '''«uri.relativize» «range.toExpectation»''' diff --git a/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/HoverTest.xtend b/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/HoverTest.xtend new file mode 100644 index 000000000..b7757e3ac --- /dev/null +++ b/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/HoverTest.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 io.typefox.lsapi.Hover +import io.typefox.lsapi.MarkedString +import org.eclipse.xtend.lib.annotations.Accessors +import org.junit.Test + +import static io.typefox.lsapi.util.LsapiFactories.* +import static org.junit.Assert.* + +/** + * @author kosyakov - Initial contribution and API + */ +class HoverTest extends AbstractLanguageServerTest { + + @Test + def void testHover_01() { + testHover[ + model = ''' + /** + * Some documentation. + */ + type Foo { + } + ''' + line = 3 + column = 'type F'.length + expectedHover = ''' + [[3, 5] .. [3, 8]] + Some documentation. + ''' + ] + } + + @Test + def void testHover_02() { + testHover[ + model = ''' + /** + * Some documentation. + */ + type Foo {} + ''' + line = 3 + column = '{'.length + ] + } + + @Test + def void testHover_03() { + testHover[ + model = ''' + /** + * Some documentation. + */ + type Foo { + Foo foo + } + ''' + line = 4 + column = ' F'.length + expectedHover = ''' + [[4, 1] .. [4, 4]] + Some documentation. + ''' + ] + } + + protected def void testHover((HoverTestConfiguration)=>void configurator) { + val extension configuration = new HoverTestConfiguration + configurator.apply(configuration) + + val fileUri = filePath -> model + + initialize + open(fileUri, model) + + val hover = languageServer.hover(newTextDocumentPositionParams(fileUri, line, column)) + val actualHover = hover.get.toExpectation + assertEquals(expectedHover, actualHover) + } + + @Accessors + static class HoverTestConfiguration { + String model = '' + String filePath = 'MyModel.testlang' + int line = 0 + int column = 0 + String expectedHover = '' + } + + protected dispatch def String toExpectation(Hover it) ''' + «range.toExpectation» + «FOR content : contents» + «content.toExpectation» + «ENDFOR» + ''' + + protected dispatch def String toExpectation( + MarkedString it + ) '''«IF !language.nullOrEmpty»«language» -> «ENDIF»«value»''' + +}