[lsp] made workspace manager more independent of LSP

This commit is contained in:
Sven Efftinge 2016-05-25 10:52:50 +02:00
parent 3cf774385d
commit cea7256665
8 changed files with 173 additions and 58 deletions

View file

@ -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<? extends TextDocumentContentChangeEvent> changes) {
def Document applyChanges(Iterable<? extends TextEdit> 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)
}

View file

@ -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
]
}
}

View file

@ -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<WorkspaceManager> 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<NotificationCallback<PublishDiagnosticsParams>> diagnosticListeners = newArrayList()
@ -122,7 +148,7 @@ import org.eclipse.xtext.validation.Issue
private def void publishDiagnostics(URI uri, Iterable<? extends Issue> issues) {
val diagnostics = new PublishDiagnosticsParamsImpl => [
it.uri = workspaceManager.toPath(uri)
it.uri = toPath(uri)
it.diagnostics = issues.map[toDiagnostic].toList
]
for (diagnosticsCallback : diagnosticListeners) {

View file

@ -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)
}

View file

@ -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<URI> dirtyFiles, List<URI> 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<TextEdit> 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 <T> void doRead(URI uri, (Document, XtextResource)=>T work) {
def <T> 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)

View file

@ -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<PublishDiagnosticsParams> {
@ -44,8 +44,7 @@ class AbstractLanguageServerTest implements NotificationCallback<PublishDiagnost
root.deleteOnExit
}
@Inject
protected LanguageServerImpl languageServer
@Inject protected LanguageServerImpl languageServer
protected Map<String, List<? extends Diagnostic>> diagnostics = newHashMap()
protected File root

View file

@ -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
]
}

View file

@ -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<URI, List<Issue>> 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))
}
}