From 4193f4aaaef3209fb4b2294545f8fe5d651b1ae6 Mon Sep 17 00:00:00 2001 From: akosyakov Date: Fri, 27 May 2016 15:19:35 +0200 Subject: [PATCH] [lsi] Support of (cancellable) read/write requests Change-Id: If294db6305bf836f0f7d75e47683e39730a975c0 Signed-off-by: akosyakov --- .../META-INF/MANIFEST.MF | 6 +- .../xtext/ide/server/LanguageServerImpl.xtend | 189 ++++++++++-------- .../xtext/ide/server/ProjectManager.xtend | 12 +- .../xtext/ide/server/ServerModule.xtend | 10 + .../xtext/ide/server/WorkspaceManager.xtend | 51 +++-- .../concurrent/CancellableIndicator.xtend | 17 ++ .../concurrent/RequestCancelIndicator.xtend | 24 +++ .../server/concurrent/RequestManager.xtend | 84 ++++++++ .../contentassist/ContentAssistService.xtend | 32 +-- .../server/symbol/DocumentSymbolService.xtend | 34 +++- .../util/CancelIndicatorProgressMonitor.xtend | 50 +++++ .../server/AbstractLanguageServerTest.xtend | 21 +- .../tests/server/WorkspaceManagerTest.xtend | 6 +- 13 files changed, 413 insertions(+), 123 deletions(-) create mode 100644 plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/concurrent/CancellableIndicator.xtend create mode 100644 plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/concurrent/RequestCancelIndicator.xtend create mode 100644 plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/concurrent/RequestManager.xtend create mode 100644 plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/util/CancelIndicatorProgressMonitor.xtend diff --git a/plugins/org.eclipse.xtext.ide/META-INF/MANIFEST.MF b/plugins/org.eclipse.xtext.ide/META-INF/MANIFEST.MF index 8ceab6aee..f4cd5b390 100644 --- a/plugins/org.eclipse.xtext.ide/META-INF/MANIFEST.MF +++ b/plugins/org.eclipse.xtext.ide/META-INF/MANIFEST.MF @@ -27,4 +27,8 @@ Export-Package: org.eclipse.xtext.ide;x-friends:="org.eclipse.xtend.ide", org.eclipse.xtext.ide.editor.partialEditing, org.eclipse.xtext.ide.editor.syntaxcoloring, org.eclipse.xtext.ide.labels;x-friends:="org.eclipse.xtext.web", - org.eclipse.xtext.ide.server + org.eclipse.xtext.ide.server, + org.eclipse.xtext.ide.server.concurrent, + org.eclipse.xtext.ide.server.contentassist, + org.eclipse.xtext.ide.server.findReferences, + org.eclipse.xtext.ide.server.symbol 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 d9810bc7f..2b9e21595 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 @@ -32,6 +32,7 @@ import io.typefox.lsapi.InitializeParams import io.typefox.lsapi.InitializeResult import io.typefox.lsapi.InitializeResultImpl import io.typefox.lsapi.LanguageServer +import io.typefox.lsapi.Location import io.typefox.lsapi.MessageParams import io.typefox.lsapi.NotificationCallback import io.typefox.lsapi.PublishDiagnosticsParams @@ -42,6 +43,7 @@ import io.typefox.lsapi.RenameParams import io.typefox.lsapi.ServerCapabilities import io.typefox.lsapi.ServerCapabilitiesImpl import io.typefox.lsapi.ShowMessageRequestParams +import io.typefox.lsapi.SymbolInformation import io.typefox.lsapi.TextDocumentPositionParams import io.typefox.lsapi.TextDocumentService import io.typefox.lsapi.WindowService @@ -51,6 +53,8 @@ import java.util.List import org.eclipse.emf.common.util.URI import org.eclipse.xtend.lib.annotations.Accessors import org.eclipse.xtext.ide.editor.contentassist.ContentAssistEntry +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.symbol.DocumentSymbolService @@ -65,6 +69,9 @@ import static io.typefox.lsapi.util.LsapiFactories.* */ @Accessors class LanguageServerImpl implements LanguageServer, WorkspaceService, WindowService, TextDocumentService { + @Inject + RequestManager requestManager + InitializeParams params @Inject Provider workspaceManagerProvider WorkspaceManager workspaceManager @@ -72,26 +79,31 @@ import static io.typefox.lsapi.util.LsapiFactories.* @Inject extension IResourceServiceProvider.Registry languagesRegistry override InitializeResult initialize(InitializeParams params) { + if (params.rootPath === null) { + throw new IllegalArgumentException("Bad initialization request. rootPath must not be null.") + } this.params = params workspaceManager = workspaceManagerProvider.get - if (params.rootPath === null) { - throw new IllegalArgumentException("Bad initialization request. rootPath must not be null.") - } - val rootURI = URI.createFileURI(params.rootPath) resourceAccess = new WorkspaceResourceAccess(workspaceManager) - workspaceManager.initialize(rootURI)[this.publishDiagnostics($0, $1)] - return new InitializeResultImpl => [ - capabilities = new ServerCapabilitiesImpl => [ - definitionProvider = true - referencesProvider = true - documentSymbolProvider = true - textDocumentSync = ServerCapabilities.SYNC_INCREMENTAL - completionProvider = new CompletionOptionsImpl => [ - resolveProvider = false - triggerCharacters = #["."] - ] + + val result = new InitializeResultImpl + result.capabilities = new ServerCapabilitiesImpl => [ + definitionProvider = true + referencesProvider = true + documentSymbolProvider = true + textDocumentSync = ServerCapabilities.SYNC_INCREMENTAL + completionProvider = new CompletionOptionsImpl => [ + resolveProvider = false + triggerCharacters = #["."] ] ] + + requestManager.runWrite([ cancelIndicator | + val rootURI = URI.createFileURI(params.rootPath) + workspaceManager.initialize(rootURI, [this.publishDiagnostics($0, $1)], cancelIndicator) + ], CancellableIndicator.NullImpl) + + return result } override exit() { @@ -128,34 +140,44 @@ import static io.typefox.lsapi.util.LsapiFactories.* // end notification callbacks // file/content change events override didOpen(DidOpenTextDocumentParams params) { - workspaceManager.didOpen(params.textDocument.uri.toUri, params.textDocument.version, params.textDocument.text) + requestManager.runWrite [ cancelIndicator | + workspaceManager.didOpen(params.textDocument.uri.toUri, params.textDocument.version, params.textDocument.text, cancelIndicator) + ] } override didChange(DidChangeTextDocumentParams params) { - workspaceManager.didChange(params.textDocument.uri.toUri, params.textDocument.version, params.contentChanges.map [ event | - newTextEdit(event.range as RangeImpl, event.text) - ]) + requestManager.runWrite [ cancelIndicator | + workspaceManager.didChange(params.textDocument.uri.toUri, params.textDocument.version, params.contentChanges.map [ event | + newTextEdit(event.range as RangeImpl, event.text) + ], cancelIndicator) + ] } override didClose(DidCloseTextDocumentParams params) { - workspaceManager.didClose(params.textDocument.uri.toUri) + requestManager.runWrite [ cancelIndicator | + workspaceManager.didClose(params.textDocument.uri.toUri, cancelIndicator) + ] } override didSave(DidSaveTextDocumentParams params) { - workspaceManager.didSave(params.textDocument.uri.toUri) + requestManager.runWrite [ cancelIndicator | + workspaceManager.didSave(params.textDocument.uri.toUri, cancelIndicator) + ] } override didChangeWatchedFiles(DidChangeWatchedFilesParams 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) + requestManager.runWrite [ cancelIndicator | + 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) + workspaceManager.doBuild(dirtyFiles, deletedFiles, cancelIndicator) + ] } // end file/content change events @@ -198,17 +220,19 @@ import static io.typefox.lsapi.util.LsapiFactories.* // end validation stuff // completion stuff override completion(TextDocumentPositionParams params) { - val uri = params.textDocument.uri.toUri - val resourceServiceProvider = uri.resourceServiceProvider - val contentAssistService = resourceServiceProvider?.get(ContentAssistService) - if (contentAssistService === null) - return emptyList - - val entries = workspaceManager.doRead(uri) [ document, resource | - val offset = document.getOffSet(params.position) - return contentAssistService.createProposals(document.contents, offset, resource) - ] - return entries.map[toCompletionItem].toList + return requestManager.runRead[ cancelIndicator | + val uri = params.textDocument.uri.toUri + val resourceServiceProvider = uri.resourceServiceProvider + val contentAssistService = resourceServiceProvider?.get(ContentAssistService) + if (contentAssistService === null) + return emptyList + + val entries = workspaceManager.doRead(uri) [ document, resource | + val offset = document.getOffSet(params.position) + return contentAssistService.createProposals(document.contents, offset, resource, cancelIndicator) + ] + return entries.map[toCompletionItem].toList + ].get } protected def CompletionItem toCompletionItem(ContentAssistEntry entry) { @@ -222,50 +246,57 @@ import static io.typefox.lsapi.util.LsapiFactories.* // end completion stuff // symbols override definition(TextDocumentPositionParams params) { - val uri = params.textDocument.uri.toUri - val resourceServiceProvider = uri.resourceServiceProvider - val documentSymbolService = resourceServiceProvider?.get(DocumentSymbolService) - if (documentSymbolService === null) - return emptyList - - return workspaceManager.doRead(uri) [ document, resource | - val offset = document.getOffSet(params.position) - return documentSymbolService.getDefinitions(resource, offset, resourceAccess) - ] + return requestManager.>runRead[ cancelIndicator | + val uri = params.textDocument.uri.toUri + val resourceServiceProvider = uri.resourceServiceProvider + val documentSymbolService = resourceServiceProvider?.get(DocumentSymbolService) + if (documentSymbolService === null) + return emptyList + + val definitions = workspaceManager.doRead(uri) [ document, resource | + val offset = document.getOffSet(params.position) + return documentSymbolService.getDefinitions(resource, offset, resourceAccess, cancelIndicator) + ] + return definitions + ].get } override references(ReferenceParams params) { - val uri = params.textDocument.uri.toUri - val resourceServiceProvider = uri.resourceServiceProvider - val documentSymbolService = resourceServiceProvider?.get(DocumentSymbolService) - if (documentSymbolService === null) - return emptyList - - return workspaceManager.doRead(uri) [ document, resource | - val offset = document.getOffSet(params.position) - - val definitions = if (params.context.includeDeclaration) - documentSymbolService.getDefinitions(resource, offset, resourceAccess) - else - emptyList - - val indexData = workspaceManager.index - val references = documentSymbolService.getReferences(resource, offset, resourceAccess, indexData) - val result = definitions + references - return result.toList - ] + return requestManager.>runRead[ cancelIndicator | + val uri = params.textDocument.uri.toUri + val resourceServiceProvider = uri.resourceServiceProvider + val documentSymbolService = resourceServiceProvider?.get(DocumentSymbolService) + if (documentSymbolService === null) + return emptyList + + return workspaceManager.doRead(uri) [ document, resource | + val offset = document.getOffSet(params.position) + + val definitions = if (params.context.includeDeclaration) + documentSymbolService.getDefinitions(resource, offset, resourceAccess, cancelIndicator) + else + emptyList + + val indexData = workspaceManager.index + val references = documentSymbolService.getReferences(resource, offset, resourceAccess, indexData, cancelIndicator) + val result = definitions + references + return result.toList + ] + ].get } override documentSymbol(DocumentSymbolParams params) { - val uri = params.textDocument.uri.toUri - val resourceServiceProvider = uri.resourceServiceProvider - val documentSymbolService = resourceServiceProvider?.get(DocumentSymbolService) - if (documentSymbolService === null) - return emptyList - - return workspaceManager.doRead(uri) [ document, resource | - return documentSymbolService.getSymbols(resource) - ] + return requestManager.>runRead[ cancelIndicator | + val uri = params.textDocument.uri.toUri + val resourceServiceProvider = uri.resourceServiceProvider + val documentSymbolService = resourceServiceProvider?.get(DocumentSymbolService) + if (documentSymbolService === null) + return emptyList + + return workspaceManager.doRead(uri) [ document, resource | + return documentSymbolService.getSymbols(resource, cancelIndicator) + ] + ].get } // end symbols 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 f02d868e4..7447be356 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 @@ -27,6 +27,7 @@ import org.eclipse.xtext.resource.impl.ProjectDescription import org.eclipse.xtext.resource.impl.ResourceDescriptionsData import org.eclipse.xtext.util.IFileSystemScanner import org.eclipse.xtext.validation.Issue +import org.eclipse.xtext.util.CancelIndicator /** * @author Sven Efftinge - Initial contribution and API @@ -49,18 +50,18 @@ class ProjectManager { @Accessors(PUBLIC_GETTER) XtextResourceSet resourceSet - def Result initialize(URI baseDir, (URI, Iterable)=>void acceptor, IExternalContentProvider openedDocumentsContentProvider, Provider> indexProvider) { + def Result initialize(URI baseDir, (URI, Iterable)=>void acceptor, IExternalContentProvider openedDocumentsContentProvider, Provider> indexProvider, CancelIndicator cancelIndicator) { val uris = newArrayList this.baseDir = baseDir this.issueAcceptor = acceptor this.openedDocumentsContentProvider = openedDocumentsContentProvider this.indexProvider = indexProvider this.fileSystemScanner.scan(baseDir)[uris += it] - return doBuild(uris, emptyList) + return doBuild(uris, emptyList, cancelIndicator) } - def Result doBuild(List dirtyFiles, List deletedFiles) { - val request = newBuildRequest(dirtyFiles, deletedFiles) + def Result doBuild(List dirtyFiles, List deletedFiles, CancelIndicator cancelIndicator) { + val request = newBuildRequest(dirtyFiles, deletedFiles, cancelIndicator) val result = incrementalBuilder.build(request, [ languagesRegistry.getResourceServiceProvider(it) ]) @@ -69,7 +70,7 @@ class ProjectManager { return result; } - protected def BuildRequest newBuildRequest(List changedFiles, List deletedFiles) { + protected def BuildRequest newBuildRequest(List changedFiles, List deletedFiles, CancelIndicator cancelIndicator) { new BuildRequest => [ it.baseDir = baseDir it.resourceSet = createFreshResourceSet(new ResourceDescriptionsData(emptyList)) @@ -81,6 +82,7 @@ class ProjectManager { issueAcceptor.apply(uri, issues) return true ] + it.cancelIndicator = cancelIndicator ] } diff --git a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/ServerModule.xtend b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/ServerModule.xtend index 6c6e0b9e6..16cd3d8aa 100644 --- a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/ServerModule.xtend +++ b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/ServerModule.xtend @@ -8,7 +8,11 @@ package org.eclipse.xtext.ide.server import com.google.inject.AbstractModule +import com.google.inject.name.Names import io.typefox.lsapi.LanguageServer +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import org.eclipse.xtext.ide.server.concurrent.RequestManager import org.eclipse.xtext.resource.IResourceServiceProvider import org.eclipse.xtext.resource.ResourceServiceProviderServiceLoader @@ -18,6 +22,12 @@ import org.eclipse.xtext.resource.ResourceServiceProviderServiceLoader class ServerModule extends AbstractModule { override protected configure() { + val readExecutorService = Executors.newCachedThreadPool + bind(ExecutorService).annotatedWith(Names.named(RequestManager.READ_EXECUTOR_SERVICE)).toInstance(readExecutorService) + + val writeExecutorService = Executors.newSingleThreadExecutor + bind(ExecutorService).annotatedWith(Names.named(RequestManager.WRITE_EXECUTOR_SERVICE)).toInstance(writeExecutorService) + bind(LanguageServer).to(LanguageServerImpl) bind(IResourceServiceProvider.Registry).toProvider(ResourceServiceProviderServiceLoader) } 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 182ac3723..aa10f7d5f 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 @@ -11,15 +11,18 @@ import com.google.inject.Inject import com.google.inject.Provider import io.typefox.lsapi.TextEdit import java.util.ArrayList +import java.util.Collection import java.util.List import java.util.Map +import java.util.Set import org.eclipse.emf.common.util.URI import org.eclipse.xtext.resource.IExternalContentSupport.IExternalContentProvider +import org.eclipse.xtext.resource.IResourceDescriptions import org.eclipse.xtext.resource.XtextResource import org.eclipse.xtext.resource.impl.ChunkedResourceDescriptions import org.eclipse.xtext.resource.impl.ResourceDescriptionsData +import org.eclipse.xtext.util.CancelIndicator import org.eclipse.xtext.validation.Issue -import org.eclipse.xtext.resource.IResourceDescriptions /** * @author Sven Efftinge - Initial contribution and API @@ -46,25 +49,43 @@ class WorkspaceManager { } }; Map fullIndex = newHashMap() + + val dirtyFiles = newLinkedHashSet + val deletedFiles = newLinkedHashSet + + protected def void queue(Set files, Collection toRemove, Collection toAdd) { + files -= toRemove + files += toAdd + } - def void initialize(URI baseDir, (URI, Iterable)=>void acceptor) { + def void initialize(URI baseDir, (URI, Iterable)=>void acceptor, CancelIndicator cancelIndicator) { this.baseDir = baseDir // TODO support multi-projects // We will need to figure out how we can identify project structure, dependencies, source folders, etc... val projectManager = projectManagerProvider.get - val indexResult = projectManager.initialize(baseDir, acceptor, openedDocumentsContentProvider, [fullIndex]) + val indexResult = projectManager.initialize(baseDir, acceptor, openedDocumentsContentProvider, [fullIndex], cancelIndicator) baseDir2ProjectManager.put(baseDir, projectManager) fullIndex.put("DEFAULT", indexResult.indexState.resourceDescriptions) } - def void doBuild(List dirtyFiles, List deletedFiles) { - //TODO sort projects by dependency + def void doBuild(List dirtyFiles, List deletedFiles, CancelIndicator cancelIndicator) { + queue(this.dirtyFiles, deletedFiles, dirtyFiles) + queue(this.deletedFiles, dirtyFiles, deletedFiles) + internalBuild(cancelIndicator) + } + + protected def void internalBuild(CancelIndicator cancelIndicator) { + //TODO sort projects by dependency val allDirty = new ArrayList(dirtyFiles) for (entry : baseDir2ProjectManager.entrySet) { - val projectDirtyFiles = allDirty.filter[isPrefix(entry.key)].toList + val projectDirtyFiles = allDirty.filter[isPrefix(entry.key)].toList val projectDeletedFiles = deletedFiles.filter[isPrefix(entry.key)].toList - val result = entry.value.doBuild(projectDirtyFiles, projectDeletedFiles) - allDirty.addAll(result.affectedResources.map[uri]) + + val result = entry.value.doBuild(projectDirtyFiles, projectDeletedFiles, cancelIndicator) + allDirty.addAll(result.affectedResources.map[uri]) + + this.dirtyFiles -= projectDirtyFiles + this.deletedFiles -= projectDeletedFiles } } @@ -90,23 +111,23 @@ class WorkspaceManager { return baseDir2ProjectManager.get(projectBaseDir) } - def didChange(URI uri, int version, Iterable changes) { + def didChange(URI uri, int version, Iterable changes, CancelIndicator cancelIndicator) { val contents = openDocuments.get(uri) openDocuments.put(uri, contents.applyChanges(changes)) - doBuild(#[uri], newArrayList) + doBuild(#[uri], newArrayList, cancelIndicator) } - def didOpen(URI uri, int version, String contents) { + def didOpen(URI uri, int version, String contents, CancelIndicator cancelIndicator) { openDocuments.put(uri, new Document(version, contents)) - doBuild(#[uri], newArrayList) + doBuild(#[uri], newArrayList, cancelIndicator) } - def didClose(URI uri) { + def didClose(URI uri, CancelIndicator cancelIndicator) { openDocuments.remove(uri) - doBuild(#[uri], newArrayList) + doBuild(#[uri], newArrayList, cancelIndicator) } - def didSave(URI uri) { + def didSave(URI uri, CancelIndicator cancelIndicator) { // do nothing for now } diff --git a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/concurrent/CancellableIndicator.xtend b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/concurrent/CancellableIndicator.xtend new file mode 100644 index 000000000..67ed80cbb --- /dev/null +++ b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/concurrent/CancellableIndicator.xtend @@ -0,0 +1,17 @@ +/******************************************************************************* + * 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.concurrent + +import org.eclipse.xtext.util.CancelIndicator + +/** + * @author kosyakov - Initial contribution and API + */ +interface CancellableIndicator extends CancelIndicator { + def void cancel() +} \ No newline at end of file diff --git a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/concurrent/RequestCancelIndicator.xtend b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/concurrent/RequestCancelIndicator.xtend new file mode 100644 index 000000000..5e54645db --- /dev/null +++ b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/concurrent/RequestCancelIndicator.xtend @@ -0,0 +1,24 @@ +/******************************************************************************* + * 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.concurrent + +import org.eclipse.xtend.lib.annotations.Accessors + +/** + * @author kosyakov - Initial contribution and API + */ +class RequestCancelIndicator implements CancellableIndicator { + + @Accessors(PUBLIC_GETTER) + boolean canceled + + override cancel() { + canceled = true + } + +} \ No newline at end of file diff --git a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/concurrent/RequestManager.xtend b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/concurrent/RequestManager.xtend new file mode 100644 index 000000000..8217d0c88 --- /dev/null +++ b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/concurrent/RequestManager.xtend @@ -0,0 +1,84 @@ +/******************************************************************************* + * 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.concurrent + +import com.google.inject.Inject +import com.google.inject.Singleton +import com.google.inject.name.Named +import java.util.concurrent.ExecutorService +import java.util.concurrent.Future +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.Semaphore +import org.eclipse.xtext.util.CancelIndicator + +/** + * @author kosyakov - Initial contribution and API + */ +@Singleton +class RequestManager { + + public static val READ_EXECUTOR_SERVICE = 'org.eclipse.xtext.ide.server.concurrent.RequestManager.readExecutorService' + public static val WRITE_EXECUTOR_SERVICE = 'org.eclipse.xtext.ide.server.concurrent.RequestManager.writeExecutorService' + + val final MAX_PERMITS = Integer.MAX_VALUE + + @Inject + @Named(READ_EXECUTOR_SERVICE) + ExecutorService readExecutorService + + @Inject + @Named(WRITE_EXECUTOR_SERVICE) + ExecutorService writeExecutorService + + val cancelIndicators = new LinkedBlockingQueue + + val semaphore = new Semaphore(MAX_PERMITS) + + def Future runWrite((CancelIndicator)=>void writeRequest) { + runWrite(writeRequest, new RequestCancelIndicator) + } + + def Future runWrite((CancelIndicator)=>void writeRequest, CancelIndicator cancelIndicator) { + cancelIndicators.forEach[cancel] + writeExecutorService.run([ + writeRequest.apply(it) + return null + ], MAX_PERMITS, cancelIndicator) + } + + def Future runRead((CancelIndicator)=>V readRequest) { + runRead(readRequest, new RequestCancelIndicator) + } + + def Future runRead((CancelIndicator)=>V readRequest, CancelIndicator cancelIndicator) { + readExecutorService.run(readRequest, 1, cancelIndicator) + } + + protected def Future run( + ExecutorService executorService, + (CancelIndicator)=>V request, + int permits, + CancelIndicator cancelIndicator + ) { + return executorService.submit [ + semaphore.acquire(permits) + try { + if (cancelIndicator instanceof CancellableIndicator) + cancelIndicators += cancelIndicator + + return request.apply(cancelIndicator) + } finally { + if (cancelIndicator instanceof CancellableIndicator) + cancelIndicators -= cancelIndicator + + semaphore.release(permits) + } + ] + } + +} diff --git a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/contentassist/ContentAssistService.xtend b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/contentassist/ContentAssistService.xtend index 7714bc26c..52305f95a 100644 --- a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/contentassist/ContentAssistService.xtend +++ b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/server/contentassist/ContentAssistService.xtend @@ -18,6 +18,8 @@ import org.eclipse.xtext.ide.editor.contentassist.IdeContentProposalProvider import org.eclipse.xtext.ide.editor.contentassist.antlr.ContentAssistContextFactory import org.eclipse.xtext.resource.XtextResource import org.eclipse.xtext.util.TextRegion +import org.eclipse.xtext.util.CancelIndicator +import org.eclipse.xtext.service.OperationCanceledManager /** * @author kosyakov - Initial contribution and API @@ -32,23 +34,17 @@ class ContentAssistService { @Inject ExecutorService executorService @Inject IdeContentProposalProvider proposalProvider + + @Inject OperationCanceledManager operationCanceledManager def Iterable createProposals( String document, int caretOffset, - XtextResource resource + XtextResource resource, + CancelIndicator cancelIndicator ) { val selection = new TextRegion(caretOffset, 0) - return createProposals(document, selection, caretOffset, resource) - } - - def Iterable createProposals( - String document, - TextRegion selection, - int caretOffset, - XtextResource resource - ) { - return createProposals(document, selection, caretOffset, resource, DEFAULT_PROPOSALS_LIMIT) + return createProposals(document, selection, caretOffset, resource, cancelIndicator) } def Iterable createProposals( @@ -56,7 +52,18 @@ class ContentAssistService { TextRegion selection, int caretOffset, XtextResource resource, - int proposalsLimit + CancelIndicator cancelIndicator + ) { + return createProposals(document, selection, caretOffset, resource, DEFAULT_PROPOSALS_LIMIT, cancelIndicator) + } + + def Iterable createProposals( + String document, + TextRegion selection, + int caretOffset, + XtextResource resource, + int proposalsLimit, + CancelIndicator cancelIndicator ) { val entries = new TreeSet> [ p1, p2 | val prioResult = p2.key.compareTo(p1.key) @@ -74,6 +81,7 @@ class ContentAssistService { throw new IllegalArgumentException('proposal must not be null.') entries.add(priority -> entry) } + operationCanceledManager.checkCanceled(cancelIndicator) } override canAcceptMoreProposals() { 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 d3e8012f5..07c31f4c8 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 @@ -21,11 +21,14 @@ import org.eclipse.xtext.findReferences.ReferenceAcceptor import org.eclipse.xtext.findReferences.TargetURICollector 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.resource.EObjectAtOffsetHelper import org.eclipse.xtext.resource.IResourceDescriptions import org.eclipse.xtext.resource.IResourceServiceProvider import org.eclipse.xtext.resource.XtextResource +import org.eclipse.xtext.service.OperationCanceledManager +import org.eclipse.xtext.util.CancelIndicator import static extension org.eclipse.emf.ecore.util.EcoreUtil.* @@ -52,11 +55,19 @@ class DocumentSymbolService { @Inject Provider targetURIProvider + + @Inject + OperationCanceledManager operationCanceledManager @Inject IResourceServiceProvider.Registry resourceServiceProviderRegistry - def List getDefinitions(XtextResource resource, int offset, IResourceAccess resourceAccess) { + def List getDefinitions( + XtextResource resource, + int offset, + IResourceAccess resourceAccess, + CancelIndicator cancelIndicator + ) { val element = resource.resolveElementAt(offset) if (element === null) return emptyList @@ -64,8 +75,13 @@ class DocumentSymbolService { val locations = newArrayList val targetURIs = element.collectTargetURIs for (targetURI : targetURIs) { + operationCanceledManager.checkCanceled(cancelIndicator) + resourceAccess.readOnly(targetURI) [ resourceSet | - locations += resourceSet.getEObject(targetURI, true).newLocation + val object = resourceSet.getEObject(targetURI, true) + if (object !== null) + locations += object.newLocation + return null ] } return locations @@ -75,7 +91,8 @@ class DocumentSymbolService { XtextResource resource, int offset, IResourceAccess resourceAccess, - IResourceDescriptions indexData + IResourceDescriptions indexData, + CancelIndicator cancelIndicator ) { val element = resource.resolveElementAt(offset) if (element === null) @@ -90,12 +107,13 @@ class DocumentSymbolService { new ReferenceAcceptor(resourceServiceProviderRegistry) [ reference | resourceAccess.readOnly(reference.sourceEObjectUri) [ resourceSet | val targetObject = resourceSet.getEObject(reference.sourceEObjectUri, true) - locations += targetObject.newLocation(reference.EReference, reference.indexInList) + if (targetObject !== null) + locations += targetObject.newLocation(reference.EReference, reference.indexInList) + return null ] ], - null + new CancelIndicatorProgressMonitor(cancelIndicator) ) - return locations } @@ -105,10 +123,12 @@ class DocumentSymbolService { return targetURIs } - def List getSymbols(XtextResource resource) { + def List getSymbols(XtextResource resource, CancelIndicator cancelIndicator) { val symbols = newLinkedHashMap val contents = resource.getAllProperContents(true) while (contents.hasNext) { + operationCanceledManager.checkCanceled(cancelIndicator) + val obj = contents.next val symbol = obj.createSymbol if (symbol !== null) { diff --git a/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/util/CancelIndicatorProgressMonitor.xtend b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/util/CancelIndicatorProgressMonitor.xtend new file mode 100644 index 000000000..f307ba420 --- /dev/null +++ b/plugins/org.eclipse.xtext.ide/src/org/eclipse/xtext/ide/util/CancelIndicatorProgressMonitor.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.util + +import org.eclipse.core.runtime.IProgressMonitor +import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor +import org.eclipse.xtext.util.CancelIndicator + +/** + * @author kosyakov - Initial contribution and API + */ +@FinalFieldsConstructor +class CancelIndicatorProgressMonitor implements IProgressMonitor { + + val CancelIndicator delegate + + boolean canceled + + override isCanceled() { + canceled || delegate.isCanceled + } + + override setCanceled(boolean value) { + canceled = value + } + + override beginTask(String name, int totalWork) { + } + + override setTaskName(String name) { + } + + override subTask(String name) { + } + + override internalWorked(double work) { + } + + override worked(int work) { + } + + override done() { + } + +} \ No newline at end of file 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 7518e5912..8d781e7af 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 @@ -7,6 +7,7 @@ *******************************************************************************/ package org.eclipse.xtext.ide.tests.server +import com.google.inject.AbstractModule import com.google.inject.Guice import com.google.inject.Inject import io.typefox.lsapi.Diagnostic @@ -28,13 +29,18 @@ import java.nio.file.Path import java.nio.file.Paths import java.util.List import java.util.Map +import java.util.concurrent.ExecutorService import org.eclipse.xtext.ide.server.LanguageServerImpl import org.eclipse.xtext.ide.server.ServerModule import org.eclipse.xtext.ide.server.UriExtensions +import org.eclipse.xtext.ide.server.concurrent.RequestManager +import org.eclipse.xtext.util.CancelIndicator import org.eclipse.xtext.util.Files +import org.eclipse.xtext.util.Modules2 import org.junit.Before import static io.typefox.lsapi.util.LsapiFactories.* +import com.google.common.util.concurrent.Futures /** * @author Sven Efftinge - Initial contribution and API @@ -43,7 +49,20 @@ class AbstractLanguageServerTest implements NotificationCallback run(ExecutorService executorService, (CancelIndicator)=>V request, int permits, CancelIndicator cancelIndicator) { + Futures.immediateCheckedFuture(request.apply(cancelIndicator)) + } + + }) + } + + }) + val injector = Guice.createInjector(module) injector.injectMembers(this) // register notification callbacks languageServer.getTextDocumentService.onPublishDiagnostics(this) 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 index 0f891e1e5..a1d460766 100644 --- 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 @@ -34,7 +34,7 @@ class WorkspaceManagerTest { } ''' - workspaceManger.doBuild(#[path], emptyList) + workspaceManger.doBuild(#[path], emptyList, null) val String inMemContents = ''' type Test { @@ -42,7 +42,7 @@ class WorkspaceManagerTest { } ''' - workspaceManger.didOpen(path, 1, inMemContents) + workspaceManger.didOpen(path, 1, inMemContents, null) Assert.assertEquals(inMemContents, workspaceManger.doRead(path, [$0.contents])) } @@ -60,7 +60,7 @@ class WorkspaceManagerTest { Files.cleanFolder(root, null, true, false) } root.deleteOnExit - workspaceManger.initialize(URI.createFileURI(root.absolutePath), [diagnostics.put($0, $1.toList)]) + workspaceManger.initialize(URI.createFileURI(root.absolutePath), [diagnostics.put($0, $1.toList)], null) } protected Map> diagnostics = newHashMap()