From cea7256665d61398445a66902cda4a93da3c7d76 Mon Sep 17 00:00:00 2001 From: Sven Efftinge Date: Wed, 25 May 2016 10:52:50 +0200 Subject: [PATCH] [lsp] made workspace manager more independent of LSP --- .../eclipse/xtext/ide/server/Document.xtend | 7 +- .../xtext/ide/server/LSPExtensions.xtend | 32 +++++++ .../xtext/ide/server/LanguageServerImpl.xtend | 38 ++++++-- .../xtext/ide/server/ProjectManager.xtend | 9 +- .../xtext/ide/server/WorkspaceManager.xtend | 45 ++-------- .../server/AbstractLanguageServerTest.xtend | 5 +- .../xtext/ide/tests/server/DocumentTest.xtend | 6 +- .../tests/server/WorkspaceManagerTest.xtend | 89 +++++++++++++++++++ 8 files changed, 173 insertions(+), 58 deletions(-) create mode 100644 plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/LSPExtensions.xtend create mode 100644 tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/WorkspaceManagerTest.xtend diff --git a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/Document.xtend b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/Document.xtend index f64360d6b..e2d7f00ef 100644 --- a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/Document.xtend +++ b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/Document.xtend @@ -8,9 +8,8 @@ package org.eclipse.xtext.ide.server import io.typefox.lsapi.Position +import io.typefox.lsapi.TextEdit import org.eclipse.xtend.lib.annotations.Data -import java.util.List -import io.typefox.lsapi.TextDocumentContentChangeEvent /** * @author Sven Efftinge - Initial contribution and API @@ -43,12 +42,12 @@ import io.typefox.lsapi.TextDocumentContentChangeEvent throw new IndexOutOfBoundsException(position.toString + " text was : "+contents) } - def Document applyChanges(List changes) { + def Document applyChanges(Iterable changes) { var newContent = contents for (change : changes) { val start = getOffSet(change.range.start) val end = getOffSet(change.range.end) - newContent = newContent.substring(0, start) + change.text + newContent.substring(end) + newContent = newContent.substring(0, start) + change.newText + newContent.substring(end) } return new Document(version + 1, newContent) } diff --git a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/LSPExtensions.xtend b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/LSPExtensions.xtend new file mode 100644 index 000000000..76d6e3c93 --- /dev/null +++ b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/LSPExtensions.xtend @@ -0,0 +1,32 @@ +/******************************************************************************* + * 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 + +import io.typefox.lsapi.PositionImpl +import io.typefox.lsapi.RangeImpl + +/** + * @author Sven Efftinge - Initial contribution and API + */ +class LSPExtensions { + + + static def PositionImpl newPosition(int line, int character) { + new PositionImpl => [ + it.line = line + it.character = character + ] + } + + static def RangeImpl newRange(PositionImpl start, PositionImpl end) { + new RangeImpl => [ + it.start = start + it.end = end + ] + } +} \ No newline at end of file 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 95b6e9196..942fb9ff2 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 @@ -26,6 +26,7 @@ import io.typefox.lsapi.DocumentFormattingParams import io.typefox.lsapi.DocumentOnTypeFormattingParams import io.typefox.lsapi.DocumentRangeFormattingParams import io.typefox.lsapi.DocumentSymbolParams +import io.typefox.lsapi.FileEvent import io.typefox.lsapi.InitializeParams import io.typefox.lsapi.InitializeResult import io.typefox.lsapi.InitializeResultImpl @@ -42,6 +43,7 @@ import io.typefox.lsapi.ServerCapabilitiesImpl import io.typefox.lsapi.ShowMessageRequestParams import io.typefox.lsapi.TextDocumentPositionParams import io.typefox.lsapi.TextDocumentService +import io.typefox.lsapi.TextEditImpl import io.typefox.lsapi.WindowService import io.typefox.lsapi.WorkspaceService import io.typefox.lsapi.WorkspaceSymbolParams @@ -59,9 +61,11 @@ import org.eclipse.xtext.validation.Issue InitializeParams params @Inject Provider workspaceManagerProvider WorkspaceManager workspaceManager + String rootPath override InitializeResult initialize(InitializeParams params) { this.params = params + this.rootPath = URI.createFileURI(params.rootPath).toString workspaceManager = workspaceManagerProvider.get workspaceManager.initialize(URI.createFileURI(params.rootPath), [ this.publishDiagnostics($0, $1) ]) return new InitializeResultImpl => [ @@ -92,26 +96,48 @@ import org.eclipse.xtext.validation.Issue // file/content change events override didOpen(DidOpenTextDocumentParams params) { - workspaceManager.didOpen(params) + workspaceManager.didOpen(params.textDocument.uri.toUri, params.textDocument.version, params.textDocument.text) } override didChange(DidChangeTextDocumentParams params) { - workspaceManager.didChange(params) + workspaceManager.didChange(params.textDocument.uri.toUri, params.textDocument.version, params.contentChanges.map [ event | + val edit = new TextEditImpl + edit.range = event.range as RangeImpl + edit.newText = event.text + return edit + ]) } override didClose(DidCloseTextDocumentParams params) { - workspaceManager.didClose(params) + workspaceManager.didClose(params.textDocument.uri.toUri) } override didSave(DidSaveTextDocumentParams params) { - workspaceManager.didSave(params) + workspaceManager.didSave(params.textDocument.uri.toUri) } override didChangeWatchedFiles(DidChangeWatchedFilesParams params) { - workspaceManager.didChangeWatchedFiles(params) + val dirtyFiles = newArrayList + val deletedFiles = newArrayList + for (fileEvent : params.changes) { + if (fileEvent.type === FileEvent.TYPE_DELETED) { + deletedFiles += toUri(fileEvent.uri) + } else { + dirtyFiles += toUri(fileEvent.uri) + } + } + workspaceManager.doBuild(dirtyFiles, deletedFiles) } // end file/content change events + def URI toUri(String path) { + URI.createURI(rootPath + path) + } + + def toPath(URI uri) { + uri.toString.substring(rootPath.length) + } + // validation stuff private List> diagnosticListeners = newArrayList() @@ -122,7 +148,7 @@ import org.eclipse.xtext.validation.Issue private def void publishDiagnostics(URI uri, Iterable issues) { val diagnostics = new PublishDiagnosticsParamsImpl => [ - it.uri = workspaceManager.toPath(uri) + it.uri = toPath(uri) it.diagnostics = issues.map[toDiagnostic].toList ] for (diagnosticsCallback : diagnosticListeners) { diff --git a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/ProjectManager.xtend b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/ProjectManager.xtend index 2f0cdde67..b25519078 100644 --- a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/ProjectManager.xtend +++ b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/ProjectManager.xtend @@ -10,22 +10,21 @@ package org.eclipse.xtext.ide.server import com.google.inject.Inject import com.google.inject.Provider import java.util.List +import java.util.Map import org.eclipse.emf.common.util.URI +import org.eclipse.emf.ecore.resource.Resource import org.eclipse.xtext.build.BuildRequest import org.eclipse.xtext.build.IncrementalBuilder +import org.eclipse.xtext.build.IncrementalBuilder.Result import org.eclipse.xtext.build.IndexState import org.eclipse.xtext.resource.IExternalContentSupport import org.eclipse.xtext.resource.IExternalContentSupport.IExternalContentProvider -import org.eclipse.xtext.resource.IResourceDescription import org.eclipse.xtext.resource.IResourceServiceProvider import org.eclipse.xtext.resource.XtextResourceSet import org.eclipse.xtext.resource.impl.ChunkedResourceDescriptions import org.eclipse.xtext.resource.impl.ProjectDescription import org.eclipse.xtext.resource.impl.ResourceDescriptionsData import org.eclipse.xtext.validation.Issue -import java.util.Map -import org.eclipse.xtext.build.IncrementalBuilder.Result -import org.eclipse.emf.ecore.resource.Resource /** * @author Sven Efftinge - Initial contribution and API @@ -92,7 +91,7 @@ class ProjectManager { ] } - def Resource getResource(org.eclipse.emf.common.util.URI uri) { + def Resource getResource(URI uri) { resourceSet.getResource(uri, true) } diff --git a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/WorkspaceManager.xtend b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/WorkspaceManager.xtend index 192181143..9bdfb4c04 100644 --- a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/WorkspaceManager.xtend +++ b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/WorkspaceManager.xtend @@ -9,12 +9,7 @@ package org.eclipse.xtext.ide.server import com.google.inject.Inject import com.google.inject.Provider -import io.typefox.lsapi.DidChangeTextDocumentParams -import io.typefox.lsapi.DidChangeWatchedFilesParams -import io.typefox.lsapi.DidCloseTextDocumentParams -import io.typefox.lsapi.DidOpenTextDocumentParams -import io.typefox.lsapi.DidSaveTextDocumentParams -import io.typefox.lsapi.FileEvent +import io.typefox.lsapi.TextEdit import java.util.ArrayList import java.util.List import java.util.Map @@ -60,19 +55,6 @@ class WorkspaceManager { fullIndex.put("DEFAULT", indexResult.indexState.resourceDescriptions) } - def didChangeWatchedFiles(DidChangeWatchedFilesParams fileChanges) { - val dirtyFiles = newArrayList - val deletedFiles = newArrayList - for (fileEvent : fileChanges.changes) { - if (fileEvent.type === FileEvent.TYPE_DELETED) { - deletedFiles += toUri(fileEvent.uri) - } else { - dirtyFiles += toUri(fileEvent.uri) - } - } - doBuild(dirtyFiles, deletedFiles) - } - def void doBuild(List dirtyFiles, List deletedFiles) { //TODO sort projects by dependency val allDirty = new ArrayList(dirtyFiles) @@ -100,38 +82,27 @@ class WorkspaceManager { return baseDir2ProjectManager.get(projectBaseDir) } - def URI toUri(String path) { - URI.createURI(baseDir.toString + path) - } - - def toPath(URI uri) { - uri.toString.substring(baseDir.toString.length) - } - - def didChange(DidChangeTextDocumentParams changeEvent) { - val uri = toUri(changeEvent.textDocument.uri) + def didChange(URI uri, int version, Iterable changes) { val contents = openDocuments.get(uri) - openDocuments.put(uri, contents.applyChanges(changeEvent.contentChanges)) + openDocuments.put(uri, contents.applyChanges(changes)) doBuild(#[uri], newArrayList) } - def didOpen(DidOpenTextDocumentParams changeEvent) { - val uri = toUri(changeEvent.textDocument.uri) - openDocuments.put(uri, new Document(changeEvent.textDocument.version, changeEvent.textDocument.text)) + def didOpen(URI uri, int version, String contents) { + openDocuments.put(uri, new Document(version, contents)) doBuild(#[uri], newArrayList) } - def didClose(DidCloseTextDocumentParams changeEvent) { - val uri = toUri(changeEvent.textDocument.uri) + def didClose(URI uri) { openDocuments.remove(uri) doBuild(#[uri], newArrayList) } - def didSave(DidSaveTextDocumentParams changeEvent) { + def didSave(URI uri) { // do nothing for now } - def void doRead(URI uri, (Document, XtextResource)=>T work) { + def T doRead(URI uri, (Document, XtextResource)=>T work) { val projectMnr = getProjectManager(uri) val doc = openDocuments.get(uri) work.apply(doc, projectMnr.getResource(uri) as XtextResource) 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 6a63a60c0..dcdccf35b 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 @@ -25,7 +25,7 @@ import org.eclipse.xtext.util.Files import org.junit.Before /** - * @author efftinge - Initial contribution and API + * @author Sven Efftinge - Initial contribution and API */ class AbstractLanguageServerTest implements NotificationCallback { @@ -44,8 +44,7 @@ class AbstractLanguageServerTest implements NotificationCallback> diagnostics = newHashMap() protected File root diff --git a/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/DocumentTest.xtend b/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/DocumentTest.xtend index ef90cfa3e..7a2292163 100644 --- a/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/DocumentTest.xtend +++ b/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/DocumentTest.xtend @@ -9,7 +9,7 @@ package org.eclipse.xtext.ide.tests.server import io.typefox.lsapi.PositionImpl import io.typefox.lsapi.RangeImpl -import io.typefox.lsapi.TextDocumentContentChangeEventImpl +import io.typefox.lsapi.TextEditImpl import org.eclipse.xtext.ide.server.Document import org.junit.Test @@ -97,12 +97,12 @@ class DocumentTest { } private def change(PositionImpl startPos, PositionImpl endPos, String newText) { - new TextDocumentContentChangeEventImpl => [ + new TextEditImpl => [ range = new RangeImpl => [ start = startPos end = endPos ] - text = newText + it.newText = newText ] } diff --git a/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/WorkspaceManagerTest.xtend b/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/WorkspaceManagerTest.xtend new file mode 100644 index 000000000..93f10ca54 --- /dev/null +++ b/tests/org.eclipse.xtext.ide.tests/src/org/eclipse/xtext/ide/tests/server/WorkspaceManagerTest.xtend @@ -0,0 +1,89 @@ +/******************************************************************************* + * 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 com.google.inject.Guice +import com.google.inject.Inject +import java.io.File +import java.io.FileWriter +import java.util.List +import java.util.Map +import org.eclipse.emf.common.util.URI +import org.eclipse.xtext.ide.server.ServerModule +import org.eclipse.xtext.ide.server.WorkspaceManager +import org.eclipse.xtext.ide.tests.testlanguage.TestLanguageStandaloneSetup +import org.eclipse.xtext.resource.FileExtensionProvider +import org.eclipse.xtext.resource.IResourceServiceProvider +import org.eclipse.xtext.util.Files +import org.eclipse.xtext.validation.Issue +import org.junit.Before +import org.junit.Test +import org.junit.Assert + +/** + * @author Sven Efftinge - Initial contribution and API + */ +class WorkspaceManagerTest { + + @Test def void testDoRead() { + val path = 'MyType1.testlang' -> ''' + type Test { + string foo + } + ''' + + workspaceManger.doBuild(#[path], emptyList) + + val String inMemContents = ''' + type Test { + Test foo + } + ''' + + workspaceManger.didOpen(path, 1, inMemContents) + + Assert.assertEquals(inMemContents, workspaceManger.doRead(path, [$0.contents])) + } + + @Inject protected WorkspaceManager workspaceManger + + @Before + def void setup() { + val injector = Guice.createInjector(new ServerModule()) + injector.injectMembers(this) + // register notification callbacks + // create workingdir + root = new File("./test-data/test-project") + if (!root.mkdirs) { + Files.cleanFolder(root, null, true, false) + } + root.deleteOnExit + workspaceManger.initialize(URI.createFileURI(root.absolutePath), [diagnostics.put($0, $1.toList)]) + } + + protected Map> diagnostics = newHashMap() + protected File root + + def URI ->(String path, CharSequence contents) { + val file = new File(root, path) + file.parentFile.mkdirs + file.createNewFile + new FileWriter(file) => [ + write(contents.toString) + close + ] + return URI.createFileURI(file.absolutePath) + } + + @Inject def voidRegisterTestLanguage(IResourceServiceProvider.Registry registry) { + val injector = new TestLanguageStandaloneSetup().createInjectorAndDoEMFRegistration + registry.extensionToFactoryMap.put(injector.getInstance(FileExtensionProvider).primaryFileExtension, + injector.getInstance(IResourceServiceProvider)) + } + +}