diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/annotation/AnnotationModelImpl.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/annotation/AnnotationModelImpl.java index 5957edb29b0..d2684270299 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/annotation/AnnotationModelImpl.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/annotation/AnnotationModelImpl.java @@ -21,6 +21,7 @@ import org.eclipse.che.ide.api.editor.text.Position; import org.eclipse.che.ide.api.editor.text.TextPosition; import org.eclipse.che.ide.api.editor.text.annotation.Annotation; +import org.eclipse.che.ide.util.loging.Log; import java.util.ArrayList; import java.util.Collections; @@ -70,6 +71,7 @@ protected void addAnnotation(final Annotation annotation, final Position positio try { addPosition(position); } catch (BadLocationException ignore) { + Log.error(getClass(), "BadLocation for " + annotation); //ignore invalid location } getAnnotationModelEvent().annotationAdded(annotation); diff --git a/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorWidget.java b/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorWidget.java index d0a2ccd0f1a..33f5787e94c 100644 --- a/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorWidget.java +++ b/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorWidget.java @@ -101,6 +101,7 @@ import org.eclipse.che.requirejs.ModuleHolder; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -687,14 +688,16 @@ public void scrollToLine(int line) { } public void showErrors(AnnotationModelEvent event) { - List addedAnnotations = event.getAddedAnnotations(); JsArray jsArray = JsArray.createArray().cast(); AnnotationModel annotationModel = event.getAnnotationModel(); OrionAnnotationSeverityProvider severityProvider = null; if (annotationModel instanceof OrionAnnotationSeverityProvider) { severityProvider = (OrionAnnotationSeverityProvider)annotationModel; } - for (Annotation annotation : addedAnnotations) { + + Iterator annotationIterator = annotationModel.getAnnotationIterator(); + while (annotationIterator.hasNext()) { + Annotation annotation = annotationIterator.next(); Position position = annotationModel.getPosition(annotation); OrionProblemOverlay problem = JavaScriptObject.createObject().cast(); diff --git a/plugins/plugin-csharp/che-plugin-csharp-lang-server/src/main/java/org/eclipse/che/plugin/csharp/languageserver/CSharpLanguageServerLauncher.java b/plugins/plugin-csharp/che-plugin-csharp-lang-server/src/main/java/org/eclipse/che/plugin/csharp/languageserver/CSharpLanguageServerLauncher.java index 5a591546e8e..249b16141dd 100644 --- a/plugins/plugin-csharp/che-plugin-csharp-lang-server/src/main/java/org/eclipse/che/plugin/csharp/languageserver/CSharpLanguageServerLauncher.java +++ b/plugins/plugin-csharp/che-plugin-csharp-lang-server/src/main/java/org/eclipse/che/plugin/csharp/languageserver/CSharpLanguageServerLauncher.java @@ -14,6 +14,8 @@ import com.google.inject.Singleton; import org.eclipse.che.api.languageserver.exception.LanguageServerException; import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncherTemplate; +import org.eclipse.che.api.languageserver.registry.DocumentFilter; +import org.eclipse.che.api.languageserver.registry.LanguageServerDescription; import org.eclipse.che.commons.lang.IoUtil; import org.eclipse.che.plugin.csharp.inject.CSharpModule; import org.eclipse.lsp4j.jsonrpc.Launcher; @@ -25,6 +27,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; /** @@ -32,7 +35,10 @@ */ @Singleton public class CSharpLanguageServerLauncher extends LanguageServerLauncherTemplate { + private static final String REGEX = ".*\\.(cs|csx)"; + + private static final LanguageServerDescription DESCRIPTION = createServerDescription(); private final Path launchScript; @@ -81,13 +87,19 @@ protected LanguageServer connectToLanguageServer(final Process languageServerPro } @Override - public String getLanguageId() { - return CSharpModule.LANGUAGE_ID; + public LanguageServerDescription getDescription() { + return DESCRIPTION; + } + + + private static LanguageServerDescription createServerDescription() { + LanguageServerDescription description = new LanguageServerDescription("org.eclipse.che.plugin.csharp.languageserver", null, + Arrays.asList(new DocumentFilter(CSharpModule.LANGUAGE_ID, REGEX, null))); + return description; } @Override public boolean isAbleToLaunch() { return Files.exists(launchScript); } - } diff --git a/plugins/plugin-json/che-plugin-json-server/src/main/java/org/eclipse/che/plugin/json/inject/JsonModule.java b/plugins/plugin-json/che-plugin-json-server/src/main/java/org/eclipse/che/plugin/json/inject/JsonModule.java index 8a77ca401a4..0868ce1db5f 100644 --- a/plugins/plugin-json/che-plugin-json-server/src/main/java/org/eclipse/che/plugin/json/inject/JsonModule.java +++ b/plugins/plugin-json/che-plugin-json-server/src/main/java/org/eclipse/che/plugin/json/inject/JsonModule.java @@ -38,4 +38,4 @@ protected void configure() { description.setMimeType(MIME_TYPE); Multibinder.newSetBinder(binder(), LanguageDescription.class).addBinding().toInstance(description); } -} +} diff --git a/plugins/plugin-json/che-plugin-json-server/src/main/java/org/eclipse/che/plugin/json/languageserver/JsonLanguageServerLauncher.java b/plugins/plugin-json/che-plugin-json-server/src/main/java/org/eclipse/che/plugin/json/languageserver/JsonLanguageServerLauncher.java index 7f74d4eb98e..28f6c11fbfd 100644 --- a/plugins/plugin-json/che-plugin-json-server/src/main/java/org/eclipse/che/plugin/json/languageserver/JsonLanguageServerLauncher.java +++ b/plugins/plugin-json/che-plugin-json-server/src/main/java/org/eclipse/che/plugin/json/languageserver/JsonLanguageServerLauncher.java @@ -13,9 +13,11 @@ import com.google.inject.Inject; import com.google.inject.Singleton; import org.eclipse.che.api.languageserver.exception.LanguageServerException; +import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher; import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncherTemplate; +import org.eclipse.che.api.languageserver.registry.DocumentFilter; +import org.eclipse.che.api.languageserver.registry.LanguageServerDescription; import org.eclipse.che.api.languageserver.registry.ServerInitializerObserver; -import org.eclipse.che.api.languageserver.shared.model.LanguageDescription; import org.eclipse.che.plugin.json.inject.JsonModule; import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.jsonrpc.Endpoint; @@ -28,15 +30,19 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; + /** * @author Evgen Vidolob * @author Anatolii Bazko */ @Singleton public class JsonLanguageServerLauncher extends LanguageServerLauncherTemplate implements ServerInitializerObserver { + private static final String REGEX = ".*\\.(json|bowerrc|jshintrc|jscsrc|eslintrc|babelrc)"; + private static final LanguageServerDescription DESCRIPTION = createServerDescription(); private final Path launchScript; @@ -45,11 +51,6 @@ public JsonLanguageServerLauncher() { launchScript = Paths.get(System.getenv("HOME"), "che/ls-json/launch.sh"); } - @Override - public String getLanguageId() { - return JsonModule.LANGUAGE_ID; - } - @Override public boolean isAbleToLaunch() { return Files.exists(launchScript); @@ -75,7 +76,10 @@ protected Process startLanguageServerProcess(String projectPath) throws Language } @Override - public void onServerInitialized(LanguageServer server, ServerCapabilities capabilities, LanguageDescription languageDescription, String projectPath) { + public void onServerInitialized(LanguageServerLauncher launcher, + LanguageServer server, + ServerCapabilities capabilities, + String projectPath) { Endpoint endpoint = ServiceEndpoints.toEndpoint(server); JsonExtension serviceObject = ServiceEndpoints.toServiceObject(endpoint, JsonExtension.class); Map associations = new HashMap<>(); @@ -89,4 +93,14 @@ public void onServerInitialized(LanguageServer server, ServerCapabilities capabi associations.put("/tsconfig.json", new String[]{"http://json.schemastore.org/tsconfig"}); serviceObject.jsonSchemaAssociation(associations); } + + public LanguageServerDescription getDescription() { + return DESCRIPTION; + } + + private static LanguageServerDescription createServerDescription() { + LanguageServerDescription description = new LanguageServerDescription("org.eclipse.che.plugin.json.languageserver", null, + Arrays.asList(new DocumentFilter(JsonModule.LANGUAGE_ID, REGEX, null))); + return description; + } } diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/LanguageServerFileTypeRegister.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/LanguageServerFileTypeRegister.java index 96d4fc77952..b8c28d42433 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/LanguageServerFileTypeRegister.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/LanguageServerFileTypeRegister.java @@ -63,9 +63,9 @@ public LanguageServerFileTypeRegister(LanguageServerRegistryServiceClient server OrionContentTypeRegistrant contentTypeRegistrant, OrionHoverRegistrant orionHoverRegistrant, OrionOccurrencesRegistrant orionOccurrencesRegistrant, - LanguageServerEditorProvider editorProvider, + LanguageServerEditorProvider editorProvider, HoverProvider hoverProvider, - OccurrencesProvider occurrencesProvider) { + OccurrencesProvider occurrencesProvider) { this.serverLanguageRegistry = serverLanguageRegistry; this.lsRegistry = lsRegistry; this.resources = resources; @@ -97,6 +97,7 @@ public void apply(List langs) throws OperationException { final FileType fileType = new FileType(resources.file(), null, RegExp.quote(fileName)); lsRegistry.registerFileType(fileType, lang); editorRegistry.registerDefaultEditor(fileType, editorProvider); + } String mimeType = lang.getMimeType(); contentTypes.push(mimeType); diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/DiagnosticCollector.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/DiagnosticCollector.java index 1f943d1798a..75cca712932 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/DiagnosticCollector.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/DiagnosticCollector.java @@ -19,21 +19,24 @@ public interface DiagnosticCollector { /** * Notification of a diagnostic. * + * @param diagnosticsCollection an identifier for the set of diagnostics the reported diagnostic belongs to. * @param diagnostic * Diagnostic - The discovered diagnostic. */ - void acceptDiagnostic(Diagnostic diagnostic); + void acceptDiagnostic(String diagnosticsCollection, Diagnostic diagnostic); /** - * Notification sent before starting the diagnostic process. - * Typically, this would tell a diagnostic collector to clear previously recorded diagnostic. + * Notification sent before starting the reporting of a collection of diagnostics + * Typically, this would tell a diagnostic collector to clear previously recorded diagnostics in the same collections. + * @param diagnosticsCollection the diagnostic collection to start reporting for */ - void beginReporting(); + void beginReporting(String diagnosticsCollection); /** * Notification sent after having completed diagnostic process. * Typically, this would tell a diagnostic collector that no more diagnostics should be expected in this - * iteration. + * collection. + * @param diagnosticsCollection the diagnostic collection to end reporting for */ - void endReporting(); + void endReporting(String diagnosticsCollection); } diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerAnnotationModel.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerAnnotationModel.java index 3aba79e7bec..f9fc82cdee6 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerAnnotationModel.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerAnnotationModel.java @@ -12,7 +12,6 @@ import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; - import org.eclipse.che.ide.api.editor.annotation.AnnotationModelImpl; import org.eclipse.che.ide.api.editor.document.Document; import org.eclipse.che.ide.api.editor.document.DocumentHandle; @@ -21,11 +20,13 @@ import org.eclipse.che.ide.api.editor.text.TextPosition; import org.eclipse.che.ide.api.editor.texteditor.EditorResources; import org.eclipse.che.ide.editor.orion.client.OrionAnnotationSeverityProvider; +import org.eclipse.che.ide.util.loging.Log; import org.eclipse.che.plugin.languageserver.ide.LanguageServerResources; import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.Range; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -34,10 +35,11 @@ * @author Evgen Vidolob */ public class LanguageServerAnnotationModel extends AnnotationModelImpl implements DiagnosticCollector, OrionAnnotationSeverityProvider { - private final LanguageServerResources.LSCss lsCss; - private final EditorResources.EditorCss editorCss; - private List diagnostics; - private List generatedAnnotations = new ArrayList<>(); + private final LanguageServerResources.LSCss lsCss; + private final EditorResources.EditorCss editorCss; + private final Map> diagnostics = new HashMap<>(); + private final Map> collectedDiagnostics = new HashMap<>(); + private final Map generatedAnnotations = new HashMap<>(); @AssistedInject public LanguageServerAnnotationModel(@Assisted final DocumentPositionMap docPositionMap, @@ -75,49 +77,66 @@ protected Position createPositionFromDiagnostic(final Diagnostic diagnostic) { } @Override - public void acceptDiagnostic(final Diagnostic problem) { - diagnostics.add(problem); + public void acceptDiagnostic(String diagnosticCollection, final Diagnostic problem) { + collectedDiagnostics.get(diagnosticCollection).add(problem); } @Override - public void beginReporting() { - diagnostics = new ArrayList<>(); + public void beginReporting(String diagnosticCollection) { + collectedDiagnostics.put(diagnosticCollection, new ArrayList<>()); } @Override - public void endReporting() { - reportDiagnostic(); + public void endReporting(String diagnosticCollection) { + reportDiagnostic(diagnosticCollection, collectedDiagnostics.remove(diagnosticCollection)); } - private void reportDiagnostic() { + private void reportDiagnostic(String diagnosticCollection, List newDiagnostics) { boolean temporaryProblemsChanged = false; - if (!generatedAnnotations.isEmpty()) { - temporaryProblemsChanged = true; - super.clear(); - generatedAnnotations.clear(); - } - - if (diagnostics != null && !diagnostics.isEmpty()) { - - for (final Diagnostic diagnostic : diagnostics) { - final Position position = createPositionFromDiagnostic(diagnostic); - - if (position != null) { - final DiagnosticAnnotation annotation = new DiagnosticAnnotation(diagnostic); - addAnnotation(annotation, position, false); - generatedAnnotations.add(annotation); - - temporaryProblemsChanged = true; - } + List currentDiagnostics = diagnostics.getOrDefault(diagnosticCollection, Collections.emptyList()); + // new list becomes previous list, but make a copy; the collection is modified below. + diagnostics.put(diagnosticCollection, new ArrayList<>(newDiagnostics)); + + for (Diagnostic diagnostic : currentDiagnostics) { + // go through the current set and remove those that are not present + // in the new set. + if (!newDiagnostics.contains(diagnostic)) { + removeAnnotationFor(diagnostic); + temporaryProblemsChanged= true; + } else { + newDiagnostics.remove(diagnostic); } } - + for (Diagnostic diagnostic : newDiagnostics) { + // now go through the ones left in the new set: they are new + addAnnotationFor(diagnostic); + temporaryProblemsChanged= true; + } + if (temporaryProblemsChanged) { fireModelChanged(); } } + private void addAnnotationFor(Diagnostic diagnostic) { + Log.debug(getClass(), "adding annotation for "+diagnostic); + DiagnosticAnnotation annotation = new DiagnosticAnnotation(diagnostic); + generatedAnnotations.put(diagnostic, annotation); + Position position = createPositionFromDiagnostic(diagnostic); + if (position != null) { + addAnnotation(annotation, position, false); + } else { + Log.error(getClass(), "Position is null for "+diagnostic); + } + } + + private void removeAnnotationFor(Diagnostic diagnostic) { + Log.debug(getClass(), "removing annotation for "+diagnostic); + DiagnosticAnnotation annotation = generatedAnnotations.remove(diagnostic); + removeAnnotation(annotation, false); + } + @Override public Map getAnnotationDecorations() { final Map decorations = new HashMap<>(); diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerEditorProvider.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerEditorProvider.java index bda8c3a6c41..253394f60fe 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerEditorProvider.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerEditorProvider.java @@ -10,23 +10,23 @@ *******************************************************************************/ package org.eclipse.che.plugin.languageserver.ide.editor; -import org.eclipse.che.api.languageserver.shared.model.ExtendedInitializeResult; import org.eclipse.che.api.promises.client.Function; import org.eclipse.che.api.promises.client.FunctionException; import org.eclipse.che.api.promises.client.Promise; -import org.eclipse.che.api.promises.client.PromiseProvider; import org.eclipse.che.ide.api.editor.AsyncEditorProvider; import org.eclipse.che.ide.api.editor.EditorPartPresenter; import org.eclipse.che.ide.api.editor.EditorProvider; import org.eclipse.che.ide.api.editor.defaulteditor.AbstractTextEditorProvider; import org.eclipse.che.ide.api.editor.defaulteditor.EditorBuilder; import org.eclipse.che.ide.api.editor.editorconfig.DefaultTextEditorConfiguration; +import org.eclipse.che.ide.api.editor.editorconfig.TextEditorConfiguration; import org.eclipse.che.ide.api.editor.texteditor.TextEditor; import org.eclipse.che.ide.api.resources.File; import org.eclipse.che.ide.api.resources.VirtualFile; import org.eclipse.che.ide.ui.loaders.request.LoaderFactory; import org.eclipse.che.ide.util.loging.Log; import org.eclipse.che.plugin.languageserver.ide.registry.LanguageServerRegistry; +import org.eclipse.lsp4j.ServerCapabilities; import javax.inject.Inject; @@ -36,21 +36,16 @@ public class LanguageServerEditorProvider implements AsyncEditorProvider, EditorProvider { private final LanguageServerRegistry registry; - private final LoaderFactory loaderFactory; private final LanguageServerEditorConfigurationFactory editorConfigurationFactory; @com.google.inject.Inject private EditorBuilder editorBuilder; - private final PromiseProvider promiseProvider; @Inject public LanguageServerEditorProvider(LanguageServerEditorConfigurationFactory editorConfigurationFactory, - LanguageServerRegistry registry, LoaderFactory loaderFactory, - PromiseProvider promiseProvider) { + LanguageServerRegistry registry, LoaderFactory loaderFactory) { this.editorConfigurationFactory = editorConfigurationFactory; this.registry = registry; - this.loaderFactory = loaderFactory; - this.promiseProvider= promiseProvider; } @Override @@ -81,19 +76,19 @@ public Promise createEditor(VirtualFile file) { if (file instanceof File) { File resource = (File)file; - Promise promise = registry.getOrInitializeServer(resource.getProject().getPath(), file); - return promise.thenPromise(new Function>() { + Promise promise = registry.getOrInitializeServer(resource.getProject().getPath(), file); + return promise.then(new Function() { @Override - public Promise apply(ExtendedInitializeResult arg) throws FunctionException { + public EditorPartPresenter apply(ServerCapabilities capabilities) throws FunctionException { if (editorBuilder == null) { Log.debug(AbstractTextEditorProvider.class, "No builder registered for default editor type - giving up."); - return promiseProvider.resolve(null); + return null; } final TextEditor editor = editorBuilder.buildEditor(); - LanguageServerEditorConfiguration configuration = editorConfigurationFactory.build(editor, arg.getCapabilities()); + TextEditorConfiguration configuration = capabilities == null ? new DefaultTextEditorConfiguration(): editorConfigurationFactory.build(editor, capabilities); editor.initialize(configuration); - return promiseProvider.resolve(editor); + return editor; } }); diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/PublishDiagnosticsProcessor.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/PublishDiagnosticsProcessor.java index 6d7fafbe640..b351a32c422 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/PublishDiagnosticsProcessor.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/PublishDiagnosticsProcessor.java @@ -12,7 +12,7 @@ import com.google.inject.Inject; import com.google.inject.Singleton; - +import org.eclipse.che.api.languageserver.shared.model.ExtendedPublishDiagnosticsParams; import org.eclipse.che.ide.api.editor.EditorAgent; import org.eclipse.che.ide.api.editor.EditorPartPresenter; import org.eclipse.che.ide.api.editor.annotation.AnnotationModel; @@ -20,7 +20,6 @@ import org.eclipse.che.ide.api.editor.texteditor.TextEditor; import org.eclipse.che.ide.resource.Path; import org.eclipse.lsp4j.Diagnostic; -import org.eclipse.lsp4j.PublishDiagnosticsParams; /** * @author Anatolii Bazko @@ -35,8 +34,8 @@ public PublishDiagnosticsProcessor(EditorAgent editorAgent) { this.editorAgent = editorAgent; } - public void processDiagnostics(PublishDiagnosticsParams diagnosticsMessage) { - EditorPartPresenter openedEditor = editorAgent.getOpenedEditor(new Path(diagnosticsMessage.getUri())); + public void processDiagnostics(ExtendedPublishDiagnosticsParams diagnosticsMessage) { + EditorPartPresenter openedEditor = editorAgent.getOpenedEditor(new Path(diagnosticsMessage.getParams().getUri())); //TODO add markers if (openedEditor == null) { return; @@ -47,13 +46,14 @@ public void processDiagnostics(PublishDiagnosticsParams diagnosticsMessage) { AnnotationModel annotationModel = editorConfiguration.getAnnotationModel(); if (annotationModel != null && annotationModel instanceof DiagnosticCollector) { DiagnosticCollector collector = (DiagnosticCollector)annotationModel; - collector.beginReporting(); + String languageServerId = diagnosticsMessage.getLanguageServerId(); + collector.beginReporting(languageServerId); try { - for (Diagnostic diagnostic : diagnosticsMessage.getDiagnostics()) { - collector.acceptDiagnostic(diagnostic); + for (Diagnostic diagnostic : diagnosticsMessage.getParams().getDiagnostics()) { + collector.acceptDiagnostic(languageServerId, diagnostic); } } finally { - collector.endReporting(); + collector.endReporting(languageServerId); } } } diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/codeassist/CompletionItemBasedCompletionProposal.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/codeassist/CompletionItemBasedCompletionProposal.java index 1411cc67eac..2e64bbc6b79 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/codeassist/CompletionItemBasedCompletionProposal.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/codeassist/CompletionItemBasedCompletionProposal.java @@ -17,7 +17,6 @@ import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.Widget; - import org.eclipse.che.api.languageserver.shared.model.ExtendedCompletionItem; import org.eclipse.che.api.promises.client.Operation; import org.eclipse.che.api.promises.client.OperationException; @@ -35,7 +34,6 @@ import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.ServerCapabilities; -import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.TextEdit; import java.util.List; @@ -50,7 +48,6 @@ public class CompletionItemBasedCompletionProposal implements CompletionProposal private final String currentWord; private final TextDocumentServiceClient documentServiceClient; - private final TextDocumentIdentifier documentId; private final LanguageServerResources resources; private final Icon icon; private final ServerCapabilities serverCapabilities; @@ -62,7 +59,6 @@ public class CompletionItemBasedCompletionProposal implements CompletionProposal CompletionItemBasedCompletionProposal(ExtendedCompletionItem completionItem, String currentWord, TextDocumentServiceClient documentServiceClient, - TextDocumentIdentifier documentId, LanguageServerResources resources, Icon icon, ServerCapabilities serverCapabilities, @@ -71,7 +67,6 @@ public class CompletionItemBasedCompletionProposal implements CompletionProposal this.completionItem = completionItem; this.currentWord = currentWord; this.documentServiceClient = documentServiceClient; - this.documentId = documentId; this.resources = resources; this.icon = icon; this.serverCapabilities = serverCapabilities; @@ -82,7 +77,7 @@ public class CompletionItemBasedCompletionProposal implements CompletionProposal @Override public void getAdditionalProposalInfo(final AsyncCallback callback) { - if (completionItem.getDocumentation() == null && canResolve()) { + if (completionItem.getItem().getDocumentation() == null && canResolve()) { resolve().then(new Operation() { @Override public void apply(ExtendedCompletionItem item) throws OperationException { @@ -102,7 +97,7 @@ public void apply(PromiseError e) throws OperationException { } private Widget createAdditionalInfoWidget() { - String documentation = completionItem.getDocumentation(); + String documentation = completionItem.getItem().getDocumentation(); if (documentation == null || documentation.trim().isEmpty()) { documentation = "No documentation found."; } @@ -122,7 +117,7 @@ private Widget createAdditionalInfoWidget() { public String getDisplayString() { SafeHtmlBuilder builder = new SafeHtmlBuilder(); - String label = completionItem.getLabel(); + String label = completionItem.getItem().getLabel(); int pos = 0; for (Match highlight : highlights) { if (highlight.getStart() == highlight.getEnd()) { @@ -141,8 +136,8 @@ public String getDisplayString() { appendPlain(builder, label.substring(pos)); } - if (completionItem.getDetail() != null) { - appendDetail(builder, completionItem.getDetail()); + if (completionItem.getItem().getDetail() != null) { + appendDetail(builder, completionItem.getItem().getDetail()); } return builder.toSafeHtml().asString(); @@ -173,10 +168,10 @@ public Icon getIcon() { public void getCompletion(final CompletionCallback callback) { if (canResolve()) { resolve().then(completionItem -> { - callback.onCompletion(new CompletionImpl(completionItem, currentWord, offset)); + callback.onCompletion(new CompletionImpl(completionItem.getItem(), currentWord, offset)); }); } else { - callback.onCompletion(new CompletionImpl(completionItem, currentWord, offset)); + callback.onCompletion(new CompletionImpl(completionItem.getItem(), currentWord, offset)); } } @@ -188,7 +183,6 @@ private boolean canResolve() { } private Promise resolve() { - completionItem.setTextDocumentIdentifier(documentId); return documentServiceClient.resolveCompletionItem(completionItem); } diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/codeassist/LanguageServerCodeAssistProcessor.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/codeassist/LanguageServerCodeAssistProcessor.java index 75076d3d090..359d21fdee8 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/codeassist/LanguageServerCodeAssistProcessor.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/codeassist/LanguageServerCodeAssistProcessor.java @@ -142,14 +142,13 @@ private List filter(String word, String label, String filterText) { private void computeProposals(String currentWord, int offset, CodeAssistCallback callback) { List proposals = newArrayList(); for (ExtendedCompletionItem item : latestCompletionResult.getCompletionList().getItems()) { - List highlights = filter(currentWord, item); + List highlights = filter(currentWord, item.getItem()); if (highlights != null) { proposals.add(new CompletionItemBasedCompletionProposal(item, currentWord, documentServiceClient, - latestCompletionResult.getDocumentId(), resources, - imageProvider.getIcon(item.getKind()), + imageProvider.getIcon(item.getItem().getKind()), serverCapabilities, highlights, offset)); diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/quickassist/LanguageServerQuickAssistProcessor.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/quickassist/LanguageServerQuickAssistProcessor.java index 8b4211652e9..ab7ac7862cd 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/quickassist/LanguageServerQuickAssistProcessor.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/quickassist/LanguageServerQuickAssistProcessor.java @@ -12,7 +12,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Widget; - +import org.eclipse.che.api.languageserver.shared.dto.DtoClientImpls.CodeActionParamsDto; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.ide.api.action.Action; import org.eclipse.che.ide.api.action.ActionManager; @@ -135,10 +135,9 @@ public void respond(Map> codeAction = textDocumentService.codeAction(params); + Promise> codeAction = textDocumentService.codeAction(new CodeActionParamsDto(params)); List proposals = new ArrayList<>(); codeAction.then((commands) -> { for (Command command : commands) { diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/inject/LanguageServerGinModule.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/inject/LanguageServerGinModule.java index 8f5960b88b1..52191f545ca 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/inject/LanguageServerGinModule.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/inject/LanguageServerGinModule.java @@ -13,6 +13,7 @@ import com.google.gwt.inject.client.AbstractGinModule; import com.google.gwt.inject.client.assistedinject.GinFactoryModuleBuilder; import com.google.gwt.inject.client.multibindings.GinMapBinder; +import com.google.inject.Singleton; import org.eclipse.che.ide.api.component.WsAgentComponent; import org.eclipse.che.ide.api.extension.ExtensionGinModule; import org.eclipse.che.plugin.languageserver.ide.LanguageServerFileTypeRegister; @@ -24,6 +25,7 @@ import org.eclipse.che.plugin.languageserver.ide.editor.quickassist.LanguageServerQuickAssistProcessorFactory; import org.eclipse.che.plugin.languageserver.ide.editor.signature.LanguageServerSignatureHelpFactory; import org.eclipse.che.plugin.languageserver.ide.location.OpenLocationPresenterFactory; +import org.eclipse.che.plugin.languageserver.ide.registry.LanguageServerRegistry; import org.eclipse.che.plugin.languageserver.ide.service.PublishDiagnosticsReceiver; import org.eclipse.che.plugin.languageserver.ide.service.ShowMessageJsonRpcReceiver; diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/registry/LanguageServerRegistry.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/registry/LanguageServerRegistry.java index 6e73d9282f5..d7a81ef9c2b 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/registry/LanguageServerRegistry.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/registry/LanguageServerRegistry.java @@ -10,65 +10,37 @@ *******************************************************************************/ package org.eclipse.che.plugin.languageserver.ide.registry; -import com.google.gwt.core.client.Callback; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.web.bindery.event.shared.EventBus; -import org.eclipse.che.api.languageserver.shared.ProjectLangugageKey; -import org.eclipse.che.api.languageserver.shared.event.LanguageServerInitializeEvent; -import org.eclipse.che.api.languageserver.shared.model.ExtendedInitializeResult; import org.eclipse.che.api.languageserver.shared.model.LanguageDescription; -import org.eclipse.che.api.promises.client.Operation; -import org.eclipse.che.api.promises.client.OperationException; import org.eclipse.che.api.promises.client.Promise; -import org.eclipse.che.api.promises.client.PromiseProvider; -import org.eclipse.che.api.promises.client.callback.CallbackPromiseHelper; import org.eclipse.che.ide.api.filetypes.FileType; import org.eclipse.che.ide.api.filetypes.FileTypeRegistry; -import org.eclipse.che.ide.api.machine.events.WsAgentStateEvent; -import org.eclipse.che.ide.api.machine.events.WsAgentStateHandler; import org.eclipse.che.ide.api.notification.NotificationManager; -import org.eclipse.che.ide.api.notification.StatusNotification; import org.eclipse.che.ide.api.resources.VirtualFile; -import org.eclipse.che.ide.rest.DtoUnmarshallerFactory; import org.eclipse.che.ide.ui.loaders.request.LoaderFactory; import org.eclipse.che.ide.ui.loaders.request.MessageLoader; -import org.eclipse.che.ide.util.loging.Log; -import org.eclipse.che.ide.websocket.MessageBus; -import org.eclipse.che.ide.websocket.MessageBusProvider; -import org.eclipse.che.ide.websocket.WebSocketException; -import org.eclipse.che.ide.websocket.rest.SubscriptionHandler; -import org.eclipse.che.ide.websocket.rest.Unmarshallable; import org.eclipse.che.plugin.languageserver.ide.service.LanguageServerRegistryJsonRpcClient; import org.eclipse.che.plugin.languageserver.ide.service.LanguageServerRegistryServiceClient; import org.eclipse.lsp4j.ServerCapabilities; -import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import static org.eclipse.che.api.languageserver.shared.ProjectLangugageKey.createProjectKey; import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.EMERGE_MODE; import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL; -import static org.eclipse.che.ide.api.notification.StatusNotification.Status.WARNING; - /** * @author Anatoliy Bazko */ @Singleton public class LanguageServerRegistry { - private final EventBus eventBus; - private final LanguageServerRegistryJsonRpcClient jsonRpcClient; - private final LoaderFactory loaderFactory; - private NotificationManager notificationManager; - private final LanguageServerRegistryServiceClient client; + private final LanguageServerRegistryJsonRpcClient jsonRpcClient; + private LoaderFactory loaderFactory; + private NotificationManager notificationManager; - private final Map projectToInitResult; - private final Map> callbackMap; private final Map registeredFileTypes = new ConcurrentHashMap<>(); - private final PromiseProvider promiseProvider; private final FileTypeRegistry fileTypeRegistry; @Inject @@ -77,133 +49,34 @@ public LanguageServerRegistry(EventBus eventBus, NotificationManager notificationManager, LanguageServerRegistryJsonRpcClient jsonRpcClient, LanguageServerRegistryServiceClient client, - PromiseProvider promiseProvider, FileTypeRegistry fileTypeRegistry) { - this.eventBus = eventBus; + + this.loaderFactory = loaderFactory; this.notificationManager = notificationManager; this.jsonRpcClient = jsonRpcClient; - this.client = client; - this.promiseProvider = promiseProvider; - this.projectToInitResult = new HashMap<>(); - this.callbackMap = new HashMap<>(); - this.fileTypeRegistry = fileTypeRegistry; - } - - /** - * Registers language server description and capabilities. - */ - protected void register(String projectPath, LanguageDescription languageDescription, ServerCapabilities capabilities) { - ExtendedInitializeResult initializeResult = new ExtendedInitializeResult(projectPath, capabilities, languageDescription); - ProjectLangugageKey key = createProjectKey(projectPath, languageDescription.getLanguageId()); - projectToInitResult.put(key, initializeResult); - - if (callbackMap.containsKey(key)) { - Callback callback = callbackMap.remove(key); - callback.onSuccess(initializeResult); - } - } - - public Promise getOrInitializeServer(String projectPath, VirtualFile file) { - LanguageDescription languageDescription = getLanguageDescription(file); - final ProjectLangugageKey key = createProjectKey(projectPath, languageDescription.getLanguageId()); - if (projectToInitResult.containsKey(key)) { - return promiseProvider.resolve(projectToInitResult.get(key)); - } else { - //call initialize service - final MessageLoader loader = loaderFactory.newLoader("Initializing Language Server for " + file.getName()); - loader.show(); - - jsonRpcClient.initializeServer(file.getLocation().toString()) - .onSuccess(loader::hide) - .onFailure((error) -> { - notificationManager - .notify("Failed to initialize language server for " + file.getLocation().toString() + " : ", error.getMessage(), FAIL, - EMERGE_MODE); - loader.hide(); - }) - .onTimeout(() -> { - notificationManager - .notify("Possible problem starting a language server", "The server either hangs or starts long", - WARNING, - EMERGE_MODE); - loader.hide(); - }); - - //wait for response - return CallbackPromiseHelper.createFromCallback(callback -> callbackMap.put(key, callback)); - } + this.fileTypeRegistry= fileTypeRegistry; } - @Inject - protected void registerAllServers() { - eventBus.addHandler(WsAgentStateEvent.TYPE, new WsAgentStateHandler() { - @Override - public void onWsAgentStarted(WsAgentStateEvent event) { - Promise> registeredLanguages = client.getRegisteredLanguages(); - - registeredLanguages.then(new Operation>() { - @Override - public void apply(List initialResults) throws OperationException { - for (ExtendedInitializeResult initializeResultDTO : initialResults) { - for (LanguageDescription languageDescription : initializeResultDTO.getSupportedLanguages()) { - register(initializeResultDTO.getProject(), languageDescription, initializeResultDTO.getCapabilities()); - } - } - } - }); - } - - @Override - public void onWsAgentStopped(WsAgentStateEvent event) { - } - }); - } - - @Inject - protected void subscribeToInitializeEvent(final DtoUnmarshallerFactory unmarshallerFactory, - final MessageBusProvider messageBusProvider, - final NotificationManager notificationManager, - final EventBus eventBus) { - eventBus.addHandler(WsAgentStateEvent.TYPE, new WsAgentStateHandler() { - @Override - public void onWsAgentStarted(WsAgentStateEvent event) { - MessageBus messageBus = messageBusProvider.getMachineMessageBus(); - Unmarshallable unmarshaller = - unmarshallerFactory.newWSUnmarshaller(LanguageServerInitializeEvent.class); - - try { - messageBus.subscribe("languageserver", new SubscriptionHandler(unmarshaller) { - @Override - protected void onMessageReceived(LanguageServerInitializeEvent initializeEvent) { - register(initializeEvent.getProjectPath(), - initializeEvent.getSupportedLanguages(), - initializeEvent.getServerCapabilities()); - } - - @Override - protected void onErrorReceived(Throwable exception) { - notificationManager.notify(exception.getMessage(), - FAIL, - StatusNotification.DisplayMode.NOT_EMERGE_MODE); - } - }); - } catch (WebSocketException exception) { - Log.error(getClass(), exception); - } - } - - @Override - public void onWsAgentStopped(WsAgentStateEvent event) { - projectToInitResult.clear(); - } + public Promise getOrInitializeServer(String projectPath, VirtualFile file) { + // call initialize service + final MessageLoader loader = loaderFactory.newLoader("Initializing Language Server for " + file.getName()); + loader.show(); + return jsonRpcClient.initializeServer(file.getLocation().toString()).then((ServerCapabilities arg) -> { + loader.hide(); + return arg; + }).catchError(arg -> { + notificationManager.notify("Initializing Language Server for " + file.getName(), arg.getMessage(), FAIL, EMERGE_MODE); + loader.hide(); + return null; }); } /** - * Register a che file type and associate it with the given language - * @param type the che file type - * @param description the language description to associate with the file type + * Register file type for a language description + * + * @param type + * @param description */ public void registerFileType(FileType type, LanguageDescription description) { fileTypeRegistry.registerFileType(type); @@ -214,8 +87,8 @@ public void registerFileType(FileType type, LanguageDescription description) { * Get the language that is registered for this file. May return null if * none is found. * - * @param file the file in question - * @return the langauge that is associated with the given file or null if not found. + * @param file + * @return */ public LanguageDescription getLanguageDescription(VirtualFile file) { FileType fileType = fileTypeRegistry.getFileTypeByFile(file); diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/LanguageServerRegistryJsonRpcClient.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/LanguageServerRegistryJsonRpcClient.java index e993281b937..465f0e53b38 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/LanguageServerRegistryJsonRpcClient.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/LanguageServerRegistryJsonRpcClient.java @@ -12,12 +12,15 @@ import com.google.inject.Inject; import com.google.inject.Singleton; - -import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcPromise; +import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcError; +import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcException; import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter; -import org.eclipse.che.ide.api.app.AppContext; -import org.eclipse.che.ide.rest.AsyncRequestFactory; -import org.eclipse.che.ide.rest.DtoUnmarshallerFactory; +import org.eclipse.che.api.promises.client.Promise; +import org.eclipse.che.api.promises.client.PromiseError; +import org.eclipse.che.api.promises.client.js.Promises; +import org.eclipse.lsp4j.ServerCapabilities; + +import java.util.concurrent.TimeoutException; @Singleton public class LanguageServerRegistryJsonRpcClient { @@ -29,12 +32,40 @@ public LanguageServerRegistryJsonRpcClient(RequestTransmitter requestTransmitter this.requestTransmitter = requestTransmitter; } - public JsonRpcPromise initializeServer(String path) { - return requestTransmitter.newRequest() - .endpointId("ws-agent") - .methodName("languageServer/initialize") - .paramsAsString(path) - .sendAndReceiveResultAsBoolean(30_000); + public Promise initializeServer(String path) { + return Promises.create((resolve, reject) -> requestTransmitter.newRequest().endpointId("ws-agent") + .methodName("languageServer/initialize").paramsAsString(path) + .sendAndReceiveResultAsDto(ServerCapabilities.class, 30000).onSuccess(resolve::apply) + .onFailure(error -> reject.apply(getPromiseError(error))) + .onTimeout(() -> { + final TimeoutException e = new TimeoutException(); + reject.apply(new PromiseError() { + + @Override + public String getMessage() { + return "Timeout initializing error"; + } + + @Override + public Throwable getCause() { + return e; + } + }); + })); + } + + private PromiseError getPromiseError(JsonRpcError jsonRpcError) { + return new PromiseError() { + @Override + public String getMessage() { + return jsonRpcError.getMessage(); + } + + @Override + public Throwable getCause() { + return new JsonRpcException(jsonRpcError.getCode(), jsonRpcError.getMessage()); + } + }; } } diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/LanguageServerRegistryServiceClient.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/LanguageServerRegistryServiceClient.java index 741dbd3811c..34ae1fdd8dd 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/LanguageServerRegistryServiceClient.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/LanguageServerRegistryServiceClient.java @@ -11,8 +11,6 @@ package org.eclipse.che.plugin.languageserver.ide.service; import com.google.inject.Inject; - -import org.eclipse.che.api.languageserver.shared.model.ExtendedInitializeResult; import org.eclipse.che.api.languageserver.shared.model.LanguageDescription; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.ide.api.app.AppContext; @@ -55,19 +53,4 @@ public Promise> getSupportedLanguages() { .send(unmarshallerFactory.newListUnmarshaller(LanguageDescription.class)); } - /** - * @return all registered languages - */ - public Promise> getRegisteredLanguages() { - String requestUrl = appContext.getDevMachine().getWsAgentBaseUrl() + BASE_URI + "/registered"; - return asyncRequestFactory.createGetRequest(requestUrl) - .header(ACCEPT, APPLICATION_JSON) - .send(unmarshallerFactory.newListUnmarshaller(ExtendedInitializeResult.class)); - } - - public Promise initializeServer(String path) { - String requestUrl = appContext.getDevMachine().getWsAgentBaseUrl() + BASE_URI + "/initialize?path=" + path; - return asyncRequestFactory.createPostRequest(requestUrl, null).send(); - } - } diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/PublishDiagnosticsReceiver.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/PublishDiagnosticsReceiver.java index 2fc6e590206..c579305d8f8 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/PublishDiagnosticsReceiver.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/PublishDiagnosticsReceiver.java @@ -13,11 +13,10 @@ import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; - import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator; import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter; +import org.eclipse.che.api.languageserver.shared.model.ExtendedPublishDiagnosticsParams; import org.eclipse.che.plugin.languageserver.ide.editor.PublishDiagnosticsProcessor; -import org.eclipse.lsp4j.PublishDiagnosticsParams; /** * Subscribes and receives JSON-RPC messages related to 'textDocument/publishDiagnostics' events @@ -28,7 +27,7 @@ public class PublishDiagnosticsReceiver { private void configureReceiver(Provider provider, RequestHandlerConfigurator configurator) { configurator.newConfiguration() .methodName("textDocument/publishDiagnostics") - .paramsAsDto(PublishDiagnosticsParams.class) + .paramsAsDto(ExtendedPublishDiagnosticsParams.class) .noResult() .withConsumer(params -> provider.get().processDiagnostics(params)); } diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/TextDocumentServiceClient.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/TextDocumentServiceClient.java index 6c873f4762d..20cca2b4b64 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/TextDocumentServiceClient.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/service/TextDocumentServiceClient.java @@ -12,7 +12,6 @@ import com.google.inject.Inject; import com.google.inject.Singleton; - import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcError; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcException; import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter; @@ -71,7 +70,7 @@ public Promise completion(TextDocumentPositionParams pos * @param completionItem * @return */ - public Promise resolveCompletionItem(CompletionItem completionItem) { + public Promise resolveCompletionItem(ExtendedCompletionItem completionItem) { return transmitDtoAndReceiveDto(completionItem, "textDocument/completionItem/resolve", ExtendedCompletionItem.class); } diff --git a/plugins/plugin-php/che-plugin-php-lang-server/src/main/java/org/eclipse/che/plugin/php/languageserver/PhpLanguageServerLauncher.java b/plugins/plugin-php/che-plugin-php-lang-server/src/main/java/org/eclipse/che/plugin/php/languageserver/PhpLanguageServerLauncher.java index 16c3a879d06..73ed6353e47 100644 --- a/plugins/plugin-php/che-plugin-php-lang-server/src/main/java/org/eclipse/che/plugin/php/languageserver/PhpLanguageServerLauncher.java +++ b/plugins/plugin-php/che-plugin-php-lang-server/src/main/java/org/eclipse/che/plugin/php/languageserver/PhpLanguageServerLauncher.java @@ -14,6 +14,8 @@ import com.google.inject.Singleton; import org.eclipse.che.api.languageserver.exception.LanguageServerException; import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncherTemplate; +import org.eclipse.che.api.languageserver.registry.DocumentFilter; +import org.eclipse.che.api.languageserver.registry.LanguageServerDescription; import org.eclipse.che.plugin.php.inject.PhpModule; import org.eclipse.lsp4j.jsonrpc.Launcher; import org.eclipse.lsp4j.services.LanguageClient; @@ -23,6 +25,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; /** * @author Evgen Vidolob @@ -31,18 +34,18 @@ */ @Singleton public class PhpLanguageServerLauncher extends LanguageServerLauncherTemplate { + + private static final String REGEX = ".*\\.php"; + private final Path launchScript; + private static final LanguageServerDescription DESCRIPTION = createServerDescription(); + @Inject public PhpLanguageServerLauncher() { this.launchScript = Paths.get(System.getenv("HOME"), "che/ls-php/launch.sh"); } - @Override - public String getLanguageId() { - return PhpModule.LANGUAGE_ID; - } - @Override public boolean isAbleToLaunch() { return Files.exists(launchScript); @@ -66,4 +69,14 @@ protected Process startLanguageServerProcess(String projectPath) throws Language } } + @Override + public LanguageServerDescription getDescription() { + return DESCRIPTION; + } + + private static LanguageServerDescription createServerDescription() { + LanguageServerDescription description = new LanguageServerDescription("org.eclipse.che.plugin.php.languageserver", null, + Arrays.asList(new DocumentFilter(PhpModule.LANGUAGE_ID, REGEX, null))); + return description; + } } diff --git a/plugins/plugin-python/che-plugin-python-lang-server/src/main/java/org/eclipse/che/plugin/python/languageserver/PythonLanguageSeverLauncher.java b/plugins/plugin-python/che-plugin-python-lang-server/src/main/java/org/eclipse/che/plugin/python/languageserver/PythonLanguageSeverLauncher.java index 27569f8f3c7..88b64db7973 100644 --- a/plugins/plugin-python/che-plugin-python-lang-server/src/main/java/org/eclipse/che/plugin/python/languageserver/PythonLanguageSeverLauncher.java +++ b/plugins/plugin-python/che-plugin-python-lang-server/src/main/java/org/eclipse/che/plugin/python/languageserver/PythonLanguageSeverLauncher.java @@ -12,6 +12,8 @@ import org.eclipse.che.api.languageserver.exception.LanguageServerException; import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncherTemplate; +import org.eclipse.che.api.languageserver.registry.DocumentFilter; +import org.eclipse.che.api.languageserver.registry.LanguageServerDescription; import org.eclipse.che.plugin.python.shared.ProjectAttributes; import org.eclipse.lsp4j.jsonrpc.Launcher; import org.eclipse.lsp4j.services.LanguageClient; @@ -22,6 +24,7 @@ import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; /** * Launches language server for Python @@ -29,17 +32,16 @@ @Singleton public class PythonLanguageSeverLauncher extends LanguageServerLauncherTemplate { + + private static final LanguageServerDescription DESCRIPTION = createServerDescription(); + private static final String REGEX = ".*\\.py"; + private final Path launchScript; public PythonLanguageSeverLauncher() { launchScript = Paths.get(System.getenv("HOME"), "che/ls-python/launch.sh"); } - @Override - public String getLanguageId() { - return ProjectAttributes.PYTHON_ID; - } - @Override public boolean isAbleToLaunch() { return launchScript.toFile().exists(); @@ -66,4 +68,15 @@ protected LanguageServer connectToLanguageServer(final Process languageServerPro launcher.startListening(); return launcher.getRemoteProxy(); } -} + + @Override + public LanguageServerDescription getDescription() { + return DESCRIPTION; + } + + private static LanguageServerDescription createServerDescription() { + LanguageServerDescription description = new LanguageServerDescription("org.eclipse.che.plugin.python.languageserver", null, + Arrays.asList(new DocumentFilter(ProjectAttributes.PYTHON_ID, REGEX, null))); + return description; + } + } diff --git a/plugins/plugin-web/che-plugin-web-ext-server/src/main/java/org/eclipse/che/plugin/web/typescript/TSLSLauncher.java b/plugins/plugin-web/che-plugin-web-ext-server/src/main/java/org/eclipse/che/plugin/web/typescript/TSLSLauncher.java index 604ce09f758..a3c374d2fb8 100644 --- a/plugins/plugin-web/che-plugin-web-ext-server/src/main/java/org/eclipse/che/plugin/web/typescript/TSLSLauncher.java +++ b/plugins/plugin-web/che-plugin-web-ext-server/src/main/java/org/eclipse/che/plugin/web/typescript/TSLSLauncher.java @@ -12,6 +12,8 @@ import org.eclipse.che.api.languageserver.exception.LanguageServerException; import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncherTemplate; +import org.eclipse.che.api.languageserver.registry.DocumentFilter; +import org.eclipse.che.api.languageserver.registry.LanguageServerDescription; import org.eclipse.che.plugin.web.shared.Constants; import org.eclipse.lsp4j.jsonrpc.Launcher; import org.eclipse.lsp4j.services.LanguageClient; @@ -23,6 +25,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; /** @@ -30,6 +33,10 @@ */ @Singleton public class TSLSLauncher extends LanguageServerLauncherTemplate { + + private static final String REGEX = ".*\\.ts"; + private static final LanguageServerDescription DESCRIPTION = createServerDescription(); + private final Path launchScript; public TSLSLauncher() { @@ -56,9 +63,17 @@ protected LanguageServer connectToLanguageServer(final Process languageServerPro return launcher.getRemoteProxy(); } + @Override - public String getLanguageId() { - return Constants.TS_LANG; + public LanguageServerDescription getDescription() { + return DESCRIPTION; + } + + + private static LanguageServerDescription createServerDescription() { + LanguageServerDescription description = new LanguageServerDescription("org.eclipse.che.plugin.web.typescript", null, + Arrays.asList(new DocumentFilter(Constants.TS_LANG, REGEX, null))); + return description; } @Override diff --git a/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/ExtendedCompletionItem.java b/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/ExtendedCompletionItem.java index 2696f1c56ef..ffcd6b8cedf 100644 --- a/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/ExtendedCompletionItem.java +++ b/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/ExtendedCompletionItem.java @@ -1,22 +1,29 @@ package org.eclipse.che.api.languageserver.shared.model; import org.eclipse.lsp4j.CompletionItem; -import org.eclipse.lsp4j.TextDocumentIdentifier; /** * Extended version of lsp4j {@link CompletionItem} for communication with the IDE. * * @author Thomas Mäder */ -public class ExtendedCompletionItem extends CompletionItem { +public class ExtendedCompletionItem { + private String languageServerId; + private CompletionItem item; - private TextDocumentIdentifier textDocumentIdentifier; - - public TextDocumentIdentifier getTextDocumentIdentifier() { - return textDocumentIdentifier; + public CompletionItem getItem() { + return item; } - - public void setTextDocumentIdentifier(TextDocumentIdentifier documentIdentifier) { - this.textDocumentIdentifier = documentIdentifier; + + public void setItem(CompletionItem item) { + this.item = item; + } + + public String getLanguageServerId() { + return languageServerId; + } + + public void setLanguageServerId(String languageServerId) { + this.languageServerId = languageServerId; } } diff --git a/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/ExtendedPublishDiagnosticsParams.java b/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/ExtendedPublishDiagnosticsParams.java new file mode 100644 index 00000000000..6972f9c3c2c --- /dev/null +++ b/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/ExtendedPublishDiagnosticsParams.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Red Hat + * 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 + * + * Contributors: + * Red Hat - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.languageserver.shared.model; + +import org.eclipse.lsp4j.PublishDiagnosticsParams; + +/** + * Extends diagnostics notification with a server id, to keep diagnostics in + * different name spaces with a different update cycle + * + * @author Thomas Mäder + * + */ +public class ExtendedPublishDiagnosticsParams { + private PublishDiagnosticsParams params; + private String languageServerId; + + public ExtendedPublishDiagnosticsParams() { + } + + public ExtendedPublishDiagnosticsParams(String serverId, PublishDiagnosticsParams diagnostics) { + this.languageServerId = serverId; + this.params = diagnostics; + } + + public PublishDiagnosticsParams getParams() { + return params; + } + + public void setParams(PublishDiagnosticsParams params) { + this.params = params; + } + + public String getLanguageServerId() { + return languageServerId; + } + + public void setLanguageServerId(String languageServerId) { + this.languageServerId = languageServerId; + } +} diff --git a/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/LanguageDescription.java b/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/LanguageDescription.java index 2010646a884..322e8252328 100644 --- a/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/LanguageDescription.java +++ b/wsagent/che-core-api-languageserver-shared/src/main/java/org/eclipse/che/api/languageserver/shared/model/LanguageDescription.java @@ -31,7 +31,6 @@ public class LanguageDescription { * The fileExtension this language is associated with. */ private List fileExtensions = Collections.emptyList(); - /** * The file names this language is associated with. */ @@ -82,7 +81,7 @@ public List getFileNames() { public void setFileNames(List fileNames) { this.fileNames = new ArrayList<>(fileNames); } - + public String getHighlightingConfiguration() { return this.highlightingConfiguration; } @@ -93,14 +92,14 @@ public void setHighlightingConfiguration(final String highlightingConfiguration) @Override public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - LanguageDescription that = (LanguageDescription) o; - return Objects.equals(languageId, that.languageId) && Objects.equals(mimeType, that.mimeType) - && Objects.equals(fileExtensions, that.fileExtensions) && Objects.equals(fileNames, that.fileNames) - && Objects.equals(highlightingConfiguration, that.highlightingConfiguration); + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LanguageDescription that = (LanguageDescription)o; + return Objects.equals(languageId, that.languageId) && + Objects.equals(mimeType, that.mimeType) && + Objects.equals(fileExtensions, that.fileExtensions) && + Objects.equals(fileNames, that.fileNames) && + Objects.equals(highlightingConfiguration, that.highlightingConfiguration); } @Override @@ -110,8 +109,12 @@ public int hashCode() { @Override public String toString() { - return "LanguageDescriptionImpl{" + "languageId='" + languageId + '\'' + ", mimeTypes=" + mimeType + ", fileExtensions=" - + fileExtensions + ", fileNames=" + fileNames + ", highlightingConfiguration='" + highlightingConfiguration + '\'' - + '}'; + return "LanguageDescriptionImpl{" + + "languageId='" + languageId + '\'' + + ", mimeTypes=" + mimeType + + ", fileExtensions=" + fileExtensions + + ", fileNames=" + fileNames + + ", highlightingConfiguration='" + highlightingConfiguration + '\'' + + '}'; } } diff --git a/wsagent/che-core-api-languageserver/pom.xml b/wsagent/che-core-api-languageserver/pom.xml index 9cd30bb8352..8931d79d0e6 100644 --- a/wsagent/che-core-api-languageserver/pom.xml +++ b/wsagent/che-core-api-languageserver/pom.xml @@ -29,6 +29,10 @@ com.google.code.gson gson + + com.google.guava + guava + com.google.inject guice @@ -45,10 +49,6 @@ javax.inject javax.inject - - javax.websocket - javax.websocket-api - javax.ws.rs javax.ws.rs-api @@ -69,10 +69,6 @@ org.eclipse.che.core che-core-api-project - - org.eclipse.che.core - che-core-commons-annotations - org.eclipse.che.core che-core-commons-lang @@ -81,10 +77,6 @@ org.eclipse.lsp4j org.eclipse.lsp4j - - org.everrest - everrest-websockets - org.slf4j slf4j-api @@ -144,6 +136,13 @@ **/EitherUtil.java **/JsonUtil.java **/DtoConversionTest.java + **/LanguageDescription.java + **/ServerCapabilitiesOverlay.java + **/DocumentFilter.java + **/CheLanguageClient.java + **/LanguageServerDescription.java + **/LSOperation.java + **/OperationUtil.java diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/LanguageServerModule.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/LanguageServerModule.java index a446b533b3f..d777c197eda 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/LanguageServerModule.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/LanguageServerModule.java @@ -12,9 +12,7 @@ import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; - import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher; -import org.eclipse.che.api.languageserver.messager.InitializeEventMessenger; import org.eclipse.che.api.languageserver.messager.PublishDiagnosticsParamsJsonRpcTransmitter; import org.eclipse.che.api.languageserver.messager.ShowMessageJsonRpcTransmitter; import org.eclipse.che.api.languageserver.registry.LanguageServerRegistry; @@ -35,13 +33,13 @@ protected void configure() { bind(ServerInitializer.class).to(ServerInitializerImpl.class); bind(LanguageRegistryService.class); bind(WorkspaceService.class); - bind(InitializeEventMessenger.class); Multibinder.newSetBinder(binder(), LanguageServerLauncher.class); bind(TextDocumentService.class).asEagerSingleton(); bind(PublishDiagnosticsParamsJsonRpcTransmitter.class).asEagerSingleton(); bind(ShowMessageJsonRpcTransmitter.class).asEagerSingleton(); - bind(LanguageServerInitializationHandler.class).asEagerSingleton(); Multibinder.newSetBinder(binder(), LanguageDescription.class); + + bind(LanguageServerInitializationHandler.class).asEagerSingleton(); } } diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/launcher/LanguageServerLauncher.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/launcher/LanguageServerLauncher.java index 0f2b3623a92..88104fad3bc 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/launcher/LanguageServerLauncher.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/launcher/LanguageServerLauncher.java @@ -11,6 +11,7 @@ package org.eclipse.che.api.languageserver.launcher; import org.eclipse.che.api.languageserver.exception.LanguageServerException; +import org.eclipse.che.api.languageserver.registry.LanguageServerDescription; import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.LanguageServer; @@ -20,14 +21,22 @@ public interface LanguageServerLauncher { /** - * Starts {@link io.typefox.lsapi.services.LanguageServer}. + * Initializes and starts a language server. + * + * @param projectPath + * absolute path to the project + * @param client + * an interface implementing handlers for server->client communication */ LanguageServer launch(String projectPath, LanguageClient client) throws LanguageServerException; /** - * Gets supported language ID. + * Gets the language server description */ - String getLanguageId(); + LanguageServerDescription getDescription(); + /** + * Indicates if language server is installed and is ready to be started. + */ boolean isAbleToLaunch(); } diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/messager/InitializeEventMessenger.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/messager/InitializeEventMessenger.java deleted file mode 100644 index c6559452937..00000000000 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/messager/InitializeEventMessenger.java +++ /dev/null @@ -1,82 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012-2017 Codenvy, S.A. - * 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 - * - * Contributors: - * Codenvy, S.A. - initial API and implementation - *******************************************************************************/ -package org.eclipse.che.api.languageserver.messager; - -import org.eclipse.che.api.languageserver.registry.LanguageServerRegistryImpl; -import org.eclipse.che.api.languageserver.registry.ServerInitializer; -import org.eclipse.che.api.languageserver.registry.ServerInitializerObserver; -import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls; -import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.LanguageServerInitializeEventDto; -import org.eclipse.che.api.languageserver.shared.model.LanguageDescription; -import org.eclipse.lsp4j.ServerCapabilities; -import org.eclipse.lsp4j.services.LanguageServer; -import org.everrest.websockets.WSConnectionContext; -import org.everrest.websockets.message.ChannelBroadcastMessage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.websocket.EncodeException; -import java.io.IOException; - -/** - * @author Anatolii Bazko - */ -@Singleton -public class InitializeEventMessenger implements ServerInitializerObserver { - private final static Logger LOG = LoggerFactory.getLogger(InitializeEventMessenger.class); - - private ServerInitializer initializer; - - @Inject - public InitializeEventMessenger(ServerInitializer initializer) { - this.initializer = initializer; - } - - @Override - public void onServerInitialized(LanguageServer server, - ServerCapabilities serverCapabilities, - LanguageDescription languageDescription, - String projectPath) { - - LanguageServerInitializeEventDto initializeEventDto = new DtoServerImpls.LanguageServerInitializeEventDto(); - initializeEventDto.setSupportedLanguages(new DtoServerImpls.LanguageDescriptionDto(languageDescription)); - initializeEventDto.setServerCapabilities(new DtoServerImpls.ServerCapabilitiesDto(serverCapabilities)); - initializeEventDto.setProjectPath(projectPath.substring(LanguageServerRegistryImpl.PROJECT_FOLDER_PATH.length())); - - send(initializeEventDto); - } - - @PostConstruct - public void addObserver() { - initializer.addObserver(this); - } - - @PreDestroy - public void removeObserver() { - initializer.removeObserver(this); - } - - protected void send(final LanguageServerInitializeEventDto message) { - try { - final ChannelBroadcastMessage bm = new ChannelBroadcastMessage(); - bm.setChannel("languageserver"); - bm.setBody(message.toJson()); - WSConnectionContext.sendMessage(bm); - } catch (EncodeException | IOException e) { - LOG.error(e.getMessage(), e); - } - } - -} diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/messager/PublishDiagnosticsParamsJsonRpcTransmitter.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/messager/PublishDiagnosticsParamsJsonRpcTransmitter.java index 3d2233510c3..00e7b048953 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/messager/PublishDiagnosticsParamsJsonRpcTransmitter.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/messager/PublishDiagnosticsParamsJsonRpcTransmitter.java @@ -13,11 +13,13 @@ import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator; import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter; import org.eclipse.che.api.core.notification.EventService; -import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.PublishDiagnosticsParamsDto; +import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.ExtendedPublishDiagnosticsParamsDto; +import org.eclipse.che.api.languageserver.shared.model.ExtendedPublishDiagnosticsParams; import org.eclipse.lsp4j.PublishDiagnosticsParams; import javax.inject.Inject; import javax.inject.Singleton; + import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; @@ -31,15 +33,16 @@ public class PublishDiagnosticsParamsJsonRpcTransmitter { @Inject private void subscribe(EventService eventService, RequestTransmitter requestTransmitter) { eventService.subscribe(event -> { - if(event.getUri() != null) { - event.setUri(event.getUri().substring(16)); + PublishDiagnosticsParams params = event.getParams(); + if(params.getUri() != null) { + params.setUri(params.getUri().substring(16)); } endpointIds.forEach(endpointId -> requestTransmitter.newRequest() .endpointId(endpointId) .methodName("textDocument/publishDiagnostics") - .paramsAsDto(new PublishDiagnosticsParamsDto(event)) + .paramsAsDto(new ExtendedPublishDiagnosticsParamsDto(event)) .sendAndSkipResult()); - }, PublishDiagnosticsParams.class); + }, ExtendedPublishDiagnosticsParams.class); } @Inject diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/CheLanguageClient.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/CheLanguageClient.java new file mode 100644 index 00000000000..a42cb4f9d63 --- /dev/null +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/CheLanguageClient.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Red Hat + * 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 + * + * Contributors: + * Red Hat - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.languageserver.registry; + +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.languageserver.shared.model.ExtendedPublishDiagnosticsParams; +import org.eclipse.lsp4j.MessageParams; +import org.eclipse.lsp4j.PublishDiagnosticsParams; +import org.eclipse.lsp4j.ShowMessageRequestParams; +import org.eclipse.lsp4j.services.LanguageClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.CompletableFuture; + +/** + * A LSP language client implementation for Che + * + * @author Thomas Mäder + * + */ +public class CheLanguageClient implements LanguageClient { + private final static Logger LOG = LoggerFactory.getLogger(CheLanguageClient.class); + + private EventService eventService; + private String serverId; + + public CheLanguageClient(EventService eventService, String serverId) { + this.eventService = eventService; + this.serverId = serverId; + } + + @Override + public void telemetryEvent(Object object) { + // not supported yet. + } + + @Override + public CompletableFuture showMessageRequest(ShowMessageRequestParams requestParams) { + return CompletableFuture.completedFuture(null); + } + + @Override + public void showMessage(MessageParams messageParams) { + eventService.publish(messageParams); + } + + @Override + public void publishDiagnostics(PublishDiagnosticsParams diagnostics) { + eventService.publish(new ExtendedPublishDiagnosticsParams(serverId, diagnostics)); + } + + @Override + public void logMessage(MessageParams message) { + switch (message.getType()) { + case Error: + LOG.error(message.getMessage()); + break; + case Warning: + LOG.warn(message.getMessage()); + break; + case Info: + LOG.info(message.getMessage()); + break; + case Log: + LOG.debug(message.getMessage()); + break; + } + } +} diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/DocumentFilter.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/DocumentFilter.java new file mode 100644 index 00000000000..5245bd66558 --- /dev/null +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/DocumentFilter.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Red Hat + * 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 + * + * Contributors: + * Red Hat - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.languageserver.registry; + +public class DocumentFilter { + + private final String pathRegex; + private final String languageId; + private final String scheme; + + public DocumentFilter(String languageId, String pathRegex, String scheme) { + this.pathRegex = pathRegex; + this.languageId = languageId; + this.scheme = scheme; + } + + public String getLanguageId() { + return languageId; + } + + public String getPathRegex() { + return pathRegex; + } + + public String getScheme() { + return scheme; + } + +} diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/InitializedLanguageServer.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/InitializedLanguageServer.java new file mode 100644 index 00000000000..e50694efbdd --- /dev/null +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/InitializedLanguageServer.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Codenvy, S.A. + * 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 + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.languageserver.registry; + +import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher; +import org.eclipse.lsp4j.InitializeResult; +import org.eclipse.lsp4j.services.LanguageServer; + +/** + * Simple container for {@link LanguageServerLauncher}, {@link InitializeResult} + * and {@link LanguageServer} + * + * @author Evgen Vidolob + */ +public class InitializedLanguageServer { + private final String id; + private final LanguageServer server; + private final InitializeResult initializeResult; + private final LanguageServerLauncher launcher; + + public InitializedLanguageServer(String id, LanguageServer server, InitializeResult initializeResult, LanguageServerLauncher launcher) { + this.id = id; + this.server = server; + this.initializeResult = initializeResult; + this.launcher = launcher; + } + + public String getId() { + return id; + } + + public LanguageServer getServer() { + return server; + } + + public InitializeResult getInitializeResult() { + return initializeResult; + } + + public LanguageServerLauncher getLauncher() { + return launcher; + } +} diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerDescription.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerDescription.java index acd7cc72ab7..f7dea5eb09e 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerDescription.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerDescription.java @@ -1,39 +1,38 @@ /******************************************************************************* - * Copyright (c) 2012-2017 Codenvy, S.A. + * Copyright (c) 2012-2017 Red Hat * 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 * * Contributors: - * Codenvy, S.A. - initial API and implementation + * Red Hat - initial API and implementation *******************************************************************************/ package org.eclipse.che.api.languageserver.registry; -import org.eclipse.che.api.languageserver.shared.model.LanguageDescription; -import org.eclipse.lsp4j.InitializeResult; +import java.util.List; +public class LanguageServerDescription { + private final String id; + private final List languageIds; + private final List documentFilters; -/** - * Simple container for {@link InitializeResult} and {@link LanguageDescription} - * - * @author Evgen Vidolob - */ -public class LanguageServerDescription { - private final InitializeResult initializeResult; - private final LanguageDescription languageDescription; + public LanguageServerDescription(String id, List languageIds, List documentFilters) { + this.id = id; + this.languageIds = languageIds; + this.documentFilters = documentFilters; + } - public LanguageServerDescription(InitializeResult initializeResult, - LanguageDescription languageDescription) { - this.initializeResult = initializeResult; - this.languageDescription = languageDescription; + public String getId() { + return id; } - public InitializeResult getInitializeResult() { - return initializeResult; + public List getLanguageIds() { + return languageIds; } - public LanguageDescription getLanguageDescription() { - return languageDescription; + public List getDocumentFilters() { + return documentFilters; } + } diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistry.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistry.java index fc2c181a418..798ee1c60cb 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistry.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistry.java @@ -11,28 +11,36 @@ package org.eclipse.che.api.languageserver.registry; import org.eclipse.che.api.languageserver.exception.LanguageServerException; -import org.eclipse.che.api.languageserver.shared.ProjectLangugageKey; import org.eclipse.che.api.languageserver.shared.model.LanguageDescription; -import org.eclipse.che.commons.annotation.Nullable; -import org.eclipse.lsp4j.services.LanguageServer; +import org.eclipse.lsp4j.ServerCapabilities; +import java.util.Collection; import java.util.List; -import java.util.Map; /** * @author Anatoliy Bazko */ public interface LanguageServerRegistry { /** - * Finds appropriate language server according to file name. + * Finds appropriate language servers according to file uri. + * @throws LanguageServerException */ - @Nullable - LanguageServer findServer(String fileUri) throws LanguageServerException; + List> getApplicableLanguageServers(String fileUri) throws LanguageServerException; /** * Returns all available servers. */ List getSupportedLanguages(); + + /** + * Initialize the language servers that apply to this file + * @param fileUri + * @return + * @throws LanguageServerException + */ + ServerCapabilities initialize(String fileUri) throws LanguageServerException; - Map getInitializedLanguages(); + ServerCapabilities getCapabilities(String fileUri) throws LanguageServerException; + + InitializedLanguageServer getServer(String id); } diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImpl.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImpl.java index e32bd2e65f1..724710f4918 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImpl.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImpl.java @@ -13,71 +13,66 @@ import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; - import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.languageserver.exception.LanguageServerException; import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher; -import org.eclipse.che.api.languageserver.shared.ProjectLangugageKey; import org.eclipse.che.api.languageserver.shared.model.LanguageDescription; import org.eclipse.che.api.project.server.FolderEntry; import org.eclipse.che.api.project.server.ProjectManager; import org.eclipse.che.api.project.server.VirtualFileEntry; -import org.eclipse.che.commons.annotation.Nullable; +import org.eclipse.lsp4j.MessageParams; +import org.eclipse.lsp4j.MessageType; import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.services.LanguageServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PreDestroy; -import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toMap; -import static org.eclipse.che.api.languageserver.shared.ProjectLangugageKey.createProjectKey; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Pattern; +import java.util.stream.Collectors; @Singleton -public class LanguageServerRegistryImpl implements LanguageServerRegistry, ServerInitializerObserver { - public final static String PROJECT_FOLDER_PATH = "/projects"; - private final List languages; - private final Map launchers; +public class LanguageServerRegistryImpl implements LanguageServerRegistry { + private final static Logger LOG = LoggerFactory.getLogger(LanguageServerRegistryImpl.class); + private final static String PROJECT_FOLDER_PATH = "file:///projects"; + private final List languages; + private final List launchers; + private final AtomicInteger serverId = new AtomicInteger(); /** * Started {@link LanguageServer} by project. */ - private final ConcurrentHashMap projectToServer = new ConcurrentHashMap<>(); + private final Map> launchedServers; + private final Map> initializedServers; private final Provider projectManagerProvider; private final ServerInitializer initializer; + private EventService eventService; @Inject - public LanguageServerRegistryImpl(Set languageServerLaunchers, - Set languages, - Provider projectManagerProvider, - ServerInitializer initializer) { - this.launchers = languageServerLaunchers.stream() - .filter(LanguageServerLauncher::isAbleToLaunch) - .collect(toMap(LanguageServerLauncher::getLanguageId, identity())); - this.languages = languages.stream() - .filter(language -> launchers.containsKey(language.getLanguageId())) - .collect(toList()); + public LanguageServerRegistryImpl(Set languageServerLaunchers, Set languages, + Provider projectManagerProvider, ServerInitializer initializer, + EventService eventService) { + this.languages = new ArrayList<>(languages); + this.launchers = new ArrayList<>(languageServerLaunchers); this.projectManagerProvider = projectManagerProvider; this.initializer = initializer; - this.initializer.addObserver(this); - } - - @Override - public LanguageServer findServer(String fileUri) throws LanguageServerException { - String path = URI.create(fileUri).getPath(); - - String projectPath = extractProjectPath(path); - - return doFindServer(projectPath, findLanguageId(path)); + this.eventService = eventService; + this.launchedServers = new HashMap<>(); + this.initializedServers = new HashMap<>(); } - private LanguageDescription findLanguageId(String path) { + private LanguageDescription findLanguage(String path) { for (LanguageDescription language : languages) { if (matchesFilenames(language, path) || matchesExtensions(language, path)) { return language; @@ -94,27 +89,96 @@ private boolean matchesFilenames(LanguageDescription language, String path) { return language.getFileNames().stream().anyMatch(name -> path.endsWith(name)); } - @Nullable - protected LanguageServer doFindServer(String projectPath, LanguageDescription language) throws LanguageServerException { - if (language == null || projectPath == null) { + @Override + public ServerCapabilities getCapabilities(String fileUri) throws LanguageServerException { + return getApplicableLanguageServers(fileUri).stream().flatMap(Collection::stream) + .map(s -> s.getInitializeResult().getCapabilities()) + .reduce(null, (left, right) -> left == null ? right : new ServerCapabilitiesOverlay(left, right).compute()); + } + + public ServerCapabilities initialize(String fileUri) throws LanguageServerException { + String projectPath = extractProjectPath(fileUri); + if (projectPath == null) { return null; } - ProjectLangugageKey projectKey = createProjectKey(projectPath, language.getLanguageId()); - LanguageServerLauncher launcher = launchers.get(language.getLanguageId()); - - if (language != null && launcher != null) { - synchronized (launcher) { - // we're relying on the fact that the following if condition - // will - // not change unless someone else uses the same launcher - if (!projectToServer.containsKey(projectKey)) { - LanguageServer server = initializer.initialize(language, launcher, projectPath); - projectToServer.put(projectKey, server); + List launchers = findLaunchers(projectPath, fileUri); + // launchers is the set of things we need to have initialized + + for (LanguageServerLauncher launcher : new ArrayList<>(launchers)) { + synchronized (initializedServers) { + List servers = launchedServers.get(projectPath); + + if (servers == null) { + servers = new ArrayList<>(); + launchedServers.put(projectPath, servers); + } + List servers2 = servers; + if (!servers2.contains(launcher)) { + servers2.add(launcher); + String id = String.valueOf(serverId.incrementAndGet()); + initializer.initialize(launcher, new CheLanguageClient(eventService, id), projectPath).thenAccept(pair -> { + synchronized (initializedServers) { + List initialized = initializedServers.get(projectPath); + if (initialized == null) { + initialized = new ArrayList<>(); + initializedServers.put(projectPath, initialized); + } + initialized.add(new InitializedLanguageServer(id, pair.first, pair.second, launcher)); + launchers.remove(launcher); + initializedServers.notifyAll(); + } + }).exceptionally(t -> { + eventService.publish(new MessageParams(MessageType.Error, "Failed to initialized LS "+launcher.getDescription().getId()+": "+t.getMessage())); + LOG.error("Error launching language server " + launcher, t); + synchronized (initializedServers) { + launchers.remove(launcher); + servers2.remove(launcher); + initializedServers.notifyAll(); + } + return null; + }); + } + } + } + + // now wait for all launchers to arrive at initialized + // eventually, all launchers will either fail or succeed, regardless of + // which request thread started them. Thus the loop below will + // end. + synchronized (initializedServers) { + List initForProject = initializedServers.get(projectPath); + if (initForProject != null) { + for (InitializedLanguageServer initialized : initForProject) { + launchers.remove(initialized.getLauncher()); + } + } + while (!launchers.isEmpty()) { + try { + initializedServers.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return null; } } } - return projectToServer.get(projectKey); + return getCapabilities(fileUri); + } + private List findLaunchers(String projectPath, String fileUri) { + LanguageDescription language = findLanguage(fileUri); + if (language == null) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); + for (LanguageServerLauncher launcher : launchers) { + if (launcher.isAbleToLaunch()) { + int score = matchScore(launcher.getDescription(), fileUri, language.getLanguageId()); + if (score > 0) { + result.add(launcher); + } + } + } + return result; } @Override @@ -122,12 +186,6 @@ public List getSupportedLanguages() { return Collections.unmodifiableList(languages); } - @Override - public Map getInitializedLanguages() { - Map initializedServers = initializer.getInitializedServers(); - return projectToServer.entrySet().stream().collect(toMap(Map.Entry::getKey, e -> initializedServers.get(e.getValue()))); - } - protected String extractProjectPath(String filePath) throws LanguageServerException { FolderEntry root; try { @@ -154,11 +212,116 @@ protected String extractProjectPath(String filePath) throws LanguageServerExcept return PROJECT_FOLDER_PATH + fileEntry.getProject(); } + public List> getApplicableLanguageServers(String fileUri) throws LanguageServerException { + String projectPath = extractProjectPath(fileUri); + LanguageDescription language = findLanguage(fileUri); + if (projectPath == null || language == null) { + return Collections.emptyList(); + } + + Map> result = new HashMap<>(); + + List servers = null; + synchronized (initializedServers) { + List list = initializedServers.get(projectPath); + if (list == null) { + return Collections.emptyList(); + } + servers = new ArrayList(list); + } + for (InitializedLanguageServer server : servers) { + int score = matchScore(server.getLauncher().getDescription(), fileUri, language.getLanguageId()); + if (score > 0) { + List list = result.get(score); + if (list == null) { + list = new ArrayList<>(); + result.put(score, list); + } + list.add(server); + } + } + // sort lists highest score first + return result.entrySet().stream().sorted((left, right) -> right.getKey() - left.getKey()).map(entry -> entry.getValue()) + .collect(Collectors.toList()); + } + + private int matchScore(LanguageServerDescription desc, String path, String languageId) { + int match = matchLanguageId(desc, languageId); + if (match == 10) { + return 10; + } + + for (DocumentFilter filter : desc.getDocumentFilters()) { + if (filter.getLanguageId() != null && filter.getLanguageId().length() > 0) { + match = Math.max(match, matchLanguageId(filter.getLanguageId(), languageId)); + if (match == 10) { + return 10; + } + } + if (filter.getScheme() != null && path.startsWith(filter.getScheme() + ":")) { + return 10; + } + String pattern = filter.getPathRegex(); + if (pattern != null) { + if (pattern.equals(path)) { + return 10; + } + Pattern regex = Pattern.compile(pattern); + if (regex.matcher(path).matches()) { + match = Math.max(match, 5); + } + } + } + return match; + } + + private int matchLanguageId(String id, String languageId) { + if (id.equals(languageId)) { + return 10; + } else if ("*".equals(id)) { + return 5; + } + return 0; + } + + private int matchLanguageId(LanguageServerDescription desc, String languageId) { + int match = 0; + List languageIds = desc.getLanguageIds(); + if (languageIds == null) { + return 0; + } + for (String id : languageIds) { + if (id.equals(languageId)) { + match = 10; + break; + } else if ("*".equals(id)) { + match = 5; + } + } + return match; + } + + @PreDestroy + protected void shutdown() { + List allServers; + synchronized (initializedServers) { + allServers = initializedServers.values().stream().flatMap(l -> l.stream()).map(s -> s.getServer()).collect(Collectors.toList()); + } + for (LanguageServer server : allServers) { + server.shutdown(); + server.exit(); + } + } + @Override - public void onServerInitialized(LanguageServer server, ServerCapabilities capabilities, LanguageDescription languageDescription, - String projectPath) { - for (String ext : languageDescription.getFileExtensions()) { - projectToServer.put(createProjectKey(projectPath, ext), server); + public InitializedLanguageServer getServer(String id) { + for (List list : initializedServers.values()) { + for (InitializedLanguageServer initializedLanguageServer : list) { + if (initializedLanguageServer.getId().equals(id)) { + return initializedLanguageServer; + } + } } + return null; } } diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerCapabilitiesOverlay.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerCapabilitiesOverlay.java new file mode 100644 index 00000000000..71fd45b4122 --- /dev/null +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerCapabilitiesOverlay.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Red Hat + * 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 + * + * Contributors: + * Red Hat - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.languageserver.registry; + +import com.google.common.base.Function; +import org.eclipse.lsp4j.CodeLensOptions; +import org.eclipse.lsp4j.CompletionOptions; +import org.eclipse.lsp4j.DocumentOnTypeFormattingOptions; +import org.eclipse.lsp4j.ServerCapabilities; +import org.eclipse.lsp4j.SignatureHelpOptions; +import org.eclipse.lsp4j.TextDocumentSyncKind; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ServerCapabilitiesOverlay { + private ServerCapabilities left; + private ServerCapabilities right; + + public ServerCapabilitiesOverlay(ServerCapabilities left, ServerCapabilities right) { + this.left = left; + this.right = right; + } + + public CodeLensOptions getCodeLensProvider() { + CodeLensOptions leftOptions = left.getCodeLensProvider(); + CodeLensOptions rightOptions = right.getCodeLensProvider(); + if (leftOptions == null) { + return rightOptions; + } + if (rightOptions == null) { + return leftOptions; + } + CodeLensOptions result = new CodeLensOptions(); + if (leftOptions != null && leftOptions.isResolveProvider() || rightOptions != null && leftOptions.isResolveProvider()) { + result.setResolveProvider(true); + } + return result; + } + + public CompletionOptions getCompletionProvider() { + CompletionOptions leftOptions = left.getCompletionProvider(); + CompletionOptions rightOptions = right.getCompletionProvider(); + if (leftOptions == null) { + return rightOptions; + } + if (rightOptions == null) { + return leftOptions; + } + + CompletionOptions result = new CompletionOptions(); + List triggerChars = new ArrayList<>(); + + if (leftOptions != null) { + triggerChars.addAll(listish(leftOptions.getTriggerCharacters())); + } + if (rightOptions != null) { + triggerChars.addAll(listish(rightOptions.getTriggerCharacters())); + } + result.setTriggerCharacters(triggerChars); + return result; + } + + public DocumentOnTypeFormattingOptions getDocumentOnTypeFormattingProvider() { + DocumentOnTypeFormattingOptions leftOptions = left.getDocumentOnTypeFormattingProvider(); + DocumentOnTypeFormattingOptions rightOptions = right.getDocumentOnTypeFormattingProvider(); + if (leftOptions == null) { + return rightOptions; + } + if (rightOptions == null) { + return leftOptions; + } + + DocumentOnTypeFormattingOptions result = new DocumentOnTypeFormattingOptions(); + List triggerChars = new ArrayList<>(); + + if (leftOptions != null) { + result.setFirstTriggerCharacter(leftOptions.getFirstTriggerCharacter()); + triggerChars.addAll(listish(leftOptions.getMoreTriggerCharacter())); + } + if (rightOptions != null) { + triggerChars.addAll(listish(rightOptions.getMoreTriggerCharacter())); + } + result.setMoreTriggerCharacter(triggerChars); + return result; + } + + public SignatureHelpOptions getSignatureHelpProvider() { + SignatureHelpOptions leftOptions = left.getSignatureHelpProvider(); + SignatureHelpOptions rightOptions = right.getSignatureHelpProvider(); + if (leftOptions == null) { + return rightOptions; + } + if (rightOptions == null) { + return leftOptions; + } + SignatureHelpOptions result = new SignatureHelpOptions(); + + List triggerChars = new ArrayList<>(); + + triggerChars.addAll(listish(leftOptions.getTriggerCharacters())); + triggerChars.addAll(listish(rightOptions.getTriggerCharacters())); + result.setTriggerCharacters(triggerChars); + return result; + } + + public TextDocumentSyncKind getTextDocumentSync() { + return mergeTextDocumentSync(left.getTextDocumentSync(), right.getTextDocumentSync()); + } + + private TextDocumentSyncKind mergeTextDocumentSync(TextDocumentSyncKind left, TextDocumentSyncKind right) { + if (left == null) { + return right; + } + if (right == null) { + return left; + } + if (left.equals(right)) { + return left; + } + if (left == TextDocumentSyncKind.Full) { + return left; + } + if (left == TextDocumentSyncKind.Incremental) { + if (right == TextDocumentSyncKind.Full) { + return TextDocumentSyncKind.Full; + } else { + return TextDocumentSyncKind.Incremental; + } + } + return right; + } + + private Boolean or(Function f) { + Boolean leftVal = f.apply(left); + Boolean rightVal = f.apply(right); + if (leftVal == null) { + return rightVal; + } + if (rightVal == null) { + return leftVal; + } + return leftVal || rightVal; + } + + private List listish(List list) { + return list == null ? Collections.emptyList() : list; + } + + public ServerCapabilities compute() { + + ServerCapabilities result = new ServerCapabilities(); + result.setCodeActionProvider(or(ServerCapabilities::getCodeActionProvider)); + result.setCodeLensProvider(getCodeLensProvider()); + result.setCompletionProvider(getCompletionProvider()); + result.setDefinitionProvider(or(ServerCapabilities::getDefinitionProvider)); + result.setDocumentFormattingProvider(or(ServerCapabilities::getDocumentFormattingProvider)); + result.setDocumentHighlightProvider(or(ServerCapabilities::getDocumentHighlightProvider)); + result.setDocumentOnTypeFormattingProvider(getDocumentOnTypeFormattingProvider()); + result.setDocumentRangeFormattingProvider(or(ServerCapabilities::getDocumentRangeFormattingProvider)); + result.setDocumentSymbolProvider(or(ServerCapabilities::getDocumentSymbolProvider)); + result.setHoverProvider(or(ServerCapabilities::getHoverProvider)); + result.setReferencesProvider(or(ServerCapabilities::getReferencesProvider)); + result.setRenameProvider(or(ServerCapabilities::getRenameProvider)); + result.setSignatureHelpProvider(getSignatureHelpProvider()); + result.setTextDocumentSync(getTextDocumentSync()); + result.setWorkspaceSymbolProvider(or(ServerCapabilities::getWorkspaceSymbolProvider)); + + return result; + } +} diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializer.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializer.java index 8522097ca14..f1c557fae98 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializer.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializer.java @@ -12,10 +12,12 @@ import org.eclipse.che.api.languageserver.exception.LanguageServerException; import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher; -import org.eclipse.che.api.languageserver.shared.model.LanguageDescription; +import org.eclipse.che.commons.lang.Pair; +import org.eclipse.lsp4j.InitializeResult; +import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.LanguageServer; -import java.util.Map; +import java.util.concurrent.CompletableFuture; /** * Is responsible to start new {@link LanguageServer}. @@ -25,11 +27,8 @@ public interface ServerInitializer extends ServerInitializerObservable { /** * Initialize new {@link LanguageServer} with given project path. + * @return */ - LanguageServer initialize(LanguageDescription language, LanguageServerLauncher launcher, String projectPath) throws LanguageServerException; + CompletableFuture> initialize(LanguageServerLauncher launcher, LanguageClient client, String projectPath) throws LanguageServerException; - /** - * Returns initialized servers. - */ - Map getInitializedServers(); } diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializerImpl.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializerImpl.java index 9d298a6e676..87df54cd1b0 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializerImpl.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializerImpl.java @@ -15,7 +15,7 @@ import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.languageserver.exception.LanguageServerException; import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher; -import org.eclipse.che.api.languageserver.shared.model.LanguageDescription; +import org.eclipse.che.commons.lang.Pair; import org.eclipse.lsp4j.ClientCapabilities; import org.eclipse.lsp4j.InitializeParams; import org.eclipse.lsp4j.InitializeResult; @@ -28,15 +28,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.PreDestroy; import java.lang.management.ManagementFactory; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; /** * @author Anatoliy Bazko @@ -50,43 +45,6 @@ public class ServerInitializerImpl implements ServerInitializer { private final List observers = new ArrayList<>(); - private final ConcurrentHashMap serversToInitResult; - - private LanguageClient languageClient; - - @Inject - public ServerInitializerImpl(EventService eventService) { - this.serversToInitResult = new ConcurrentHashMap<>(); - - languageClient = new LanguageClient() { - - @Override - public void telemetryEvent(Object object) { - // TODO Auto-generated method stub - } - - @Override - public CompletableFuture showMessageRequest(ShowMessageRequestParams requestParams) { - return CompletableFuture.completedFuture(null); - } - - @Override - public void showMessage(MessageParams messageParams) { - eventService.publish(messageParams); - } - - @Override - public void publishDiagnostics(PublishDiagnosticsParams diagnostics) { - eventService.publish(diagnostics); - } - - @Override - public void logMessage(MessageParams message) { - LOG.error(message.getType() + " " + message.getMessage()); - } - }; - } - private static int getProcessId() { String name = ManagementFactory.getRuntimeMXBean().getName(); int prefixEnd = name.indexOf('@'); @@ -113,52 +71,39 @@ public void removeObserver(ServerInitializerObserver observer) { } @Override - public LanguageServer initialize(LanguageDescription language, LanguageServerLauncher launcher, String projectPath) throws LanguageServerException { - synchronized (launcher) { - LanguageServer server = doInitialize(language, launcher, projectPath); - onServerInitialized(server, serversToInitResult.get(server).getInitializeResult().getCapabilities(), - language, projectPath); - return server; - } - } - - @Override - public Map getInitializedServers() { - return Collections.unmodifiableMap(serversToInitResult); - } - - protected LanguageServer doInitialize(LanguageDescription language, LanguageServerLauncher launcher, String projectPath) throws LanguageServerException { - String languageId = launcher.getLanguageId(); + public CompletableFuture> initialize(LanguageServerLauncher launcher, LanguageClient client, String projectPath) + throws LanguageServerException { InitializeParams initializeParams = prepareInitializeParams(projectPath); + String launcherId = launcher.getDescription().getId(); + CompletableFuture> result= new CompletableFuture>(); LanguageServer server; try { - server = launcher.launch(projectPath, languageClient); + server = launcher.launch(projectPath, client); } catch (LanguageServerException e) { - throw new LanguageServerException( - "Can't initialize Language Server " + languageId + " on " + projectPath + ". " + e.getMessage(), e); + result.completeExceptionally(new LanguageServerException( + "Can't initialize Language Server " + launcherId + " on " + projectPath + ". " + e.getMessage(), e)); + return result; } registerCallbacks(server, launcher); CompletableFuture completableFuture = server.initialize(initializeParams); - try { - InitializeResult initializeResult = completableFuture.get(); - serversToInitResult.put(server, new LanguageServerDescription(initializeResult, language)); - } catch (InterruptedException | ExecutionException e) { + completableFuture.thenAccept((InitializeResult res) -> { + onServerInitialized(launcher, server, res.getCapabilities(), projectPath); + result.complete(Pair.of(server, res)); + LOG.info("Initialized Language Server {} on project {}", launcherId, projectPath); + }).exceptionally((Throwable e) -> { server.shutdown(); server.exit(); - - throw new LanguageServerException("Error fetching server capabilities " + languageId + ". " + e.getMessage(), e); - } - - LOG.info("Initialized Language Server {} on project {}", languageId, projectPath); - return server; + result.completeExceptionally(e); + return null; + }); + return result; } protected void registerCallbacks(LanguageServer server, LanguageServerLauncher launcher) { - if (server instanceof ServerInitializerObserver) { - addObserver((ServerInitializerObserver)server); + addObserver((ServerInitializerObserver) server); } if (launcher instanceof ServerInitializerObserver) { @@ -166,7 +111,7 @@ protected void registerCallbacks(LanguageServer server, LanguageServerLauncher l } } - protected InitializeParams prepareInitializeParams(String projectPath) { + private InitializeParams prepareInitializeParams(String projectPath) { InitializeParams initializeParams = new InitializeParams(); initializeParams.setProcessId(PROCESS_ID); initializeParams.setRootPath(projectPath); @@ -175,19 +120,7 @@ protected InitializeParams prepareInitializeParams(String projectPath) { return initializeParams; } - protected void onServerInitialized(LanguageServer server, - ServerCapabilities capabilities, - LanguageDescription languageDescription, - String projectPath) { - observers.forEach(observer -> observer.onServerInitialized(server, capabilities, languageDescription, projectPath)); - } - - @PreDestroy - protected void shutdown() { - for (LanguageServer server : serversToInitResult.keySet()) { - server.shutdown(); - server.exit(); - } + private void onServerInitialized(LanguageServerLauncher launcher, LanguageServer server, ServerCapabilities capabilities, String projectPath) { + observers.forEach(observer -> observer.onServerInitialized(launcher, server, capabilities , projectPath)); } - } diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializerObserver.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializerObserver.java index 4ae6f1581b7..6da7f252d9f 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializerObserver.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializerObserver.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.eclipse.che.api.languageserver.registry; +import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher; import org.eclipse.che.api.languageserver.shared.model.LanguageDescription; import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.services.LanguageServer; @@ -29,8 +30,8 @@ public interface ServerInitializerObserver { * @param languageDescription * @param projectPath */ - void onServerInitialized(LanguageServer server, + void onServerInitialized(LanguageServerLauncher launcher, + LanguageServer server, ServerCapabilities capabilities, - LanguageDescription languageDescription, String projectPath); } diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/LanguageRegistryService.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/LanguageRegistryService.java index 59a98190698..7bb7540036b 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/LanguageRegistryService.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/LanguageRegistryService.java @@ -12,16 +12,11 @@ import com.google.inject.Inject; import com.google.inject.Singleton; - import org.eclipse.che.api.languageserver.exception.LanguageServerException; -import org.eclipse.che.api.languageserver.registry.LanguageServerDescription; import org.eclipse.che.api.languageserver.registry.LanguageServerRegistry; -import org.eclipse.che.api.languageserver.registry.LanguageServerRegistryImpl; -import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls; -import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.ExtendedInitializeResultDto; -import org.eclipse.che.api.languageserver.shared.ProjectLangugageKey; -import org.eclipse.che.api.languageserver.shared.model.ExtendedInitializeResult; +import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.ServerCapabilitiesDto; import org.eclipse.che.api.languageserver.shared.model.LanguageDescription; +import org.eclipse.lsp4j.ServerCapabilities; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -29,10 +24,8 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; -import java.util.Collections; -import java.util.List; -import static java.util.stream.Collectors.toList; +import java.util.List; @Singleton @Path("languageserver") @@ -51,34 +44,13 @@ public LanguageRegistryService(LanguageServerRegistry registry) { public List getSupportedLanguages() { return registry.getSupportedLanguages(); } - - @GET - @Produces(MediaType.APPLICATION_JSON) - @Path("registered") - public List getRegisteredLanguages() { - return registry.getInitializedLanguages() - .entrySet() - .stream() - .map(entry -> { - ProjectLangugageKey projectExtensionKey = entry.getKey(); - LanguageServerDescription serverDescription = entry.getValue(); - - - ExtendedInitializeResult dto = new ExtendedInitializeResult(); - dto.setProject( - projectExtensionKey.getProject().substring(LanguageServerRegistryImpl.PROJECT_FOLDER_PATH.length())); - dto.setSupportedLanguages(Collections.singletonList(serverDescription.getLanguageDescription())); - dto.setCapabilities(serverDescription.getInitializeResult().getCapabilities()); - return new DtoServerImpls.ExtendedInitializeResultDto(dto); - }) - .collect(toList()); - - } - + @POST + @Produces(MediaType.APPLICATION_JSON) @Path("initialize") - public void initialize(@QueryParam("path") String path) throws LanguageServerException { + public ServerCapabilitiesDto initialize(@QueryParam("path") String path) throws LanguageServerException { //in most cases starts new LS if not already started - registry.findServer(TextDocumentServiceUtils.prefixURI(path)); + ServerCapabilities capabilities = registry.initialize(TextDocumentServiceUtils.prefixURI(path)); + return capabilities == null ? null : new ServerCapabilitiesDto(capabilities); } } diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/LanguageServerInitializationHandler.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/LanguageServerInitializationHandler.java index ec8bb29cf82..f626ce85899 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/LanguageServerInitializationHandler.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/LanguageServerInitializationHandler.java @@ -12,11 +12,12 @@ import com.google.inject.Inject; import com.google.inject.Singleton; - import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcException; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator; import org.eclipse.che.api.languageserver.exception.LanguageServerException; import org.eclipse.che.api.languageserver.registry.LanguageServerRegistry; +import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.ServerCapabilitiesDto; +import org.eclipse.lsp4j.ServerCapabilities; @Singleton public class LanguageServerInitializationHandler { @@ -26,10 +27,11 @@ public LanguageServerInitializationHandler(RequestHandlerConfigurator requestHan requestHandlerConfigurator.newConfiguration() .methodName("languageServer/initialize") .paramsAsString() - .resultAsBoolean() + .resultAsDto(ServerCapabilitiesDto.class) .withFunction(path -> { try { - return registry.findServer(TextDocumentServiceUtils.prefixURI(path)) != null; + ServerCapabilities capabilities = registry.initialize(TextDocumentServiceUtils.prefixURI(path)); + return capabilities == null ? null : new ServerCapabilitiesDto(capabilities); } catch (LanguageServerException e) { throw new JsonRpcException(-27000, e.getMessage()); } diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/TextDocumentService.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/TextDocumentService.java index 77b956b6865..54511ef4989 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/TextDocumentService.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/TextDocumentService.java @@ -10,35 +10,30 @@ *******************************************************************************/ package org.eclipse.che.api.languageserver.service; -import static org.eclipse.che.api.languageserver.service.TextDocumentServiceUtils.prefixURI; -import static org.eclipse.che.api.languageserver.service.TextDocumentServiceUtils.removePrefixUri; - -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; - -import javax.annotation.PostConstruct; -import javax.inject.Inject; - import com.google.inject.Singleton; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcException; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator; import org.eclipse.che.api.languageserver.exception.LanguageServerException; +import org.eclipse.che.api.languageserver.registry.InitializedLanguageServer; import org.eclipse.che.api.languageserver.registry.LanguageServerRegistry; import org.eclipse.che.api.languageserver.registry.LanguageServerRegistryImpl; +import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.CommandDto; import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.CompletionItemDto; -import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.CompletionListDto; import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.DocumentHighlightDto; import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.ExtendedCompletionItemDto; +import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.ExtendedCompletionListDto; import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.HoverDto; import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.LocationDto; import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.SignatureHelpDto; import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.SymbolInformationDto; import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.TextEditDto; import org.eclipse.che.api.languageserver.shared.model.ExtendedCompletionItem; +import org.eclipse.che.api.languageserver.util.LSOperation; +import org.eclipse.che.api.languageserver.util.OperationUtil; +import org.eclipse.lsp4j.CodeActionParams; +import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.CompletionList; import org.eclipse.lsp4j.DidChangeTextDocumentParams; import org.eclipse.lsp4j.DidCloseTextDocumentParams; import org.eclipse.lsp4j.DidOpenTextDocumentParams; @@ -51,12 +46,31 @@ import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.ReferenceParams; +import org.eclipse.lsp4j.SignatureHelp; +import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.TextDocumentPositionParams; -import org.eclipse.lsp4j.services.LanguageServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.PostConstruct; +import javax.inject.Inject; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.eclipse.che.api.languageserver.service.TextDocumentServiceUtils.prefixURI; +import static org.eclipse.che.api.languageserver.service.TextDocumentServiceUtils.removePrefixUri; + /** * Json RPC API for the textDoc *

@@ -78,15 +92,16 @@ public TextDocumentService(LanguageServerRegistry languageServerRegistry, Reques @PostConstruct public void configureMethods() { dtoToDtoList("definition", TextDocumentPositionParams.class, LocationDto.class, this::definition); + dtoToDtoList("codeAction", CodeActionParams.class, CommandDto.class, this::codeAction); dtoToDtoList("documentSymbol", DocumentSymbolParams.class, SymbolInformationDto.class, this::documentSymbol); dtoToDtoList("formatting", DocumentFormattingParams.class, TextEditDto.class, this::formatting); dtoToDtoList("rangeFormatting", DocumentRangeFormattingParams.class, TextEditDto.class, this::rangeFormatting); dtoToDtoList("references", ReferenceParams.class, LocationDto.class, this::references); dtoToDtoList("onTypeFormatting", DocumentOnTypeFormattingParams.class, TextEditDto.class, this::onTypeFormatting); - dtoToDto("completionItem/resolve", ExtendedCompletionItemDto.class, CompletionItemDto.class, this::completionItemResolve); - dtoToDtoList("documentHighlight", TextDocumentPositionParams.class, DocumentHighlightDto.class, this::documentHighlight); - dtoToDto("completion", TextDocumentPositionParams.class, CompletionListDto.class, this::completion); + dtoToDto("completionItem/resolve", ExtendedCompletionItem.class, ExtendedCompletionItemDto.class, this::completionItemResolve); + dtoToDto("documentHighlight", TextDocumentPositionParams.class, DocumentHighlightDto.class, this::documentHighlight); + dtoToDto("completion", TextDocumentPositionParams.class, ExtendedCompletionListDto.class, this::completion); dtoToDto("hover", TextDocumentPositionParams.class, HoverDto.class, this::hover); dtoToDto("signatureHelp", TextDocumentPositionParams.class, SignatureHelpDto.class, this::signatureHelp); @@ -96,135 +111,322 @@ public void configureMethods() { dtoToNothing("didSave", DidSaveTextDocumentParams.class, this::didSave); } - private CompletionListDto completion(TextDocumentPositionParams textDocumentPositionParams) { + private List codeAction(CodeActionParams params) { + TextDocumentIdentifier textDocument = params.getTextDocument(); + String uri = prefixURI(textDocument.getUri()); + textDocument.setUri(uri); + List result = new ArrayList<>(); + try { + List servers = languageServerRegistry.getApplicableLanguageServers(uri).stream() + .flatMap(Collection::stream).collect(Collectors.toList()); + LSOperation> op = new LSOperation>() { + + @Override + public boolean canDo(InitializedLanguageServer server) { + return truish(server.getInitializeResult().getCapabilities().getCodeActionProvider()); + } + + public CompletableFuture> start(InitializedLanguageServer element) { + return element.getServer().getTextDocumentService().codeAction(params); + }; + + @Override + public boolean handleResult(InitializedLanguageServer element, List res) { + for (Command cmd : res) { + result.add(new CommandDto(cmd)); + } + return false; + }; + }; + OperationUtil.doInParallel(servers, op, 10000); + return result; + } catch (LanguageServerException e) { + throw new JsonRpcException(-27000, e.getMessage()); + } + } + + private ExtendedCompletionListDto completion(TextDocumentPositionParams textDocumentPositionParams) { try { TextDocumentIdentifier textDocument = textDocumentPositionParams.getTextDocument(); - textDocument.setUri(prefixURI(textDocument.getUri())); + String uri = prefixURI(textDocument.getUri()); + textDocument.setUri(uri); textDocumentPositionParams.setUri(prefixURI(textDocumentPositionParams.getUri())); - LanguageServer server = getServer(textDocument.getUri()); - return server != null ? new CompletionListDto(server.getTextDocumentService().completion(textDocumentPositionParams).get()) - : null; + ExtendedCompletionListDto[] result = new ExtendedCompletionListDto[1]; - } catch (LanguageServerException | InterruptedException | ExecutionException e) { + LSOperation, ExtendedCompletionListDto> op = new LSOperation, ExtendedCompletionListDto>() { + + @Override + public boolean canDo(Collection servers) { + return true; + } + + @Override + public CompletableFuture start(Collection element) { + return CompletableFuture.supplyAsync(() -> { + ExtendedCompletionListDto res = new ExtendedCompletionListDto(); + List items = new ArrayList(); + res.setItems(items); + LSOperation op2 = new LSOperation() { + + @Override + public boolean canDo(InitializedLanguageServer element) { + return element.getInitializeResult().getCapabilities().getCompletionProvider() != null; + } + + @Override + public CompletableFuture start(InitializedLanguageServer element) { + return element.getServer().getTextDocumentService().completion(textDocumentPositionParams); + } + + @Override + public boolean handleResult(InitializedLanguageServer element, CompletionList result) { + res.setInComplete(res.isInComplete() && result.isIncomplete()); + for (CompletionItem item : result.getItems()) { + ExtendedCompletionItemDto exItem = new ExtendedCompletionItemDto(); + exItem.setItem(new CompletionItemDto(item)); + exItem.setLanguageServerId(element.getId()); + items.add(exItem); + } + return false; + } + }; + OperationUtil.doInParallel(element, op2, 30000); + + return res; + }); + } + + @Override + public boolean handleResult(Collection element, ExtendedCompletionListDto list) { + result[0] = list; + return !list.getItems().isEmpty(); + } + }; + OperationUtil.doInSequence(languageServerRegistry.getApplicableLanguageServers(uri), op, 10000); + return result[0]; + } catch (LanguageServerException e) { throw new JsonRpcException(-27000, e.getMessage()); } } private List documentSymbol(DocumentSymbolParams documentSymbolParams) { + String uri = prefixURI(documentSymbolParams.getTextDocument().getUri()); + documentSymbolParams.getTextDocument().setUri(uri); + List result = new ArrayList<>(); try { - documentSymbolParams.getTextDocument().setUri(prefixURI(documentSymbolParams.getTextDocument().getUri())); - LanguageServer server = getServer(documentSymbolParams.getTextDocument().getUri()); - return server == null ? Collections.emptyList() : server.getTextDocumentService() - .documentSymbol(documentSymbolParams) - .get() - .stream() - .map(SymbolInformationDto::new) - .collect(Collectors.toList()); - - } catch (ExecutionException | InterruptedException | LanguageServerException e) { + List servers = languageServerRegistry.getApplicableLanguageServers(uri).stream() + .flatMap(Collection::stream).collect(Collectors.toList()); + OperationUtil.doInParallel(servers, new LSOperation>() { + + @Override + public boolean canDo(InitializedLanguageServer element) { + return truish(element.getInitializeResult().getCapabilities().getDocumentSymbolProvider()); + } + + @Override + public CompletableFuture> start(InitializedLanguageServer element) { + return element.getServer().getTextDocumentService().documentSymbol(documentSymbolParams); + } + + @Override + public boolean handleResult(InitializedLanguageServer element, List locations) { + locations.forEach(o -> { + o.getLocation().setUri(removePrefixUri(o.getLocation().getUri())); + result.add(new SymbolInformationDto(o)); + }); + return true; + } + }, 10000); + return result; + + } catch (LanguageServerException e) { throw new JsonRpcException(-27000, e.getMessage()); } } private List references(ReferenceParams referenceParams) { + String uri = prefixURI(referenceParams.getTextDocument().getUri()); + referenceParams.getTextDocument().setUri(uri); + List result = new ArrayList<>(); try { - referenceParams.getTextDocument().setUri(prefixURI(referenceParams.getTextDocument().getUri())); - LanguageServer server = getServer(referenceParams.getTextDocument().getUri()); - if (server == null) { - return Collections.emptyList(); - } + List servers = languageServerRegistry.getApplicableLanguageServers(uri).stream() + .flatMap(Collection::stream).collect(Collectors.toList()); + OperationUtil.doInParallel(servers, new LSOperation>() { - List locations = server.getTextDocumentService().references(referenceParams).get(); - locations.forEach(o -> o.setUri(removePrefixUri(o.getUri()))); - return locations.stream().map(LocationDto::new).collect(Collectors.toList()); - } catch (ExecutionException | InterruptedException | LanguageServerException e) { - throw new JsonRpcException(-27000, e.getMessage()); + @Override + public boolean canDo(InitializedLanguageServer element) { + return truish(element.getInitializeResult().getCapabilities().getReferencesProvider()); + } + + @Override + public CompletableFuture> start(InitializedLanguageServer element) { + return element.getServer().getTextDocumentService().references(referenceParams); + } + @Override + public boolean handleResult(InitializedLanguageServer element, List locations) { + locations.forEach(o -> { + o.setUri(removePrefixUri(o.getUri())); + result.add(new LocationDto(o)); + }); + return true; + } + }, 30000); + return result; + } catch (LanguageServerException e) { + throw new JsonRpcException(-27000, e.getMessage()); } } private List definition(TextDocumentPositionParams textDocumentPositionParams) { + String uri = prefixURI(textDocumentPositionParams.getTextDocument().getUri()); + textDocumentPositionParams.getTextDocument().setUri(uri); try { - textDocumentPositionParams.getTextDocument().setUri(prefixURI(textDocumentPositionParams.getTextDocument().getUri())); - LanguageServer server = getServer(textDocumentPositionParams.getTextDocument().getUri()); - if (server == null) { - return Collections.emptyList(); - } + List servers = languageServerRegistry.getApplicableLanguageServers(uri).stream() + .flatMap(Collection::stream).collect(Collectors.toList()); + List result = new ArrayList<>(); + OperationUtil.doInParallel(servers, new LSOperation>() { + + @Override + public boolean canDo(InitializedLanguageServer element) { + return truish(element.getInitializeResult().getCapabilities().getDefinitionProvider()); + } - List locations = server.getTextDocumentService().definition(textDocumentPositionParams).get(); - locations.forEach(o -> o.setUri(removePrefixUri(o.getUri()))); - return locations.stream().map(LocationDto::new).collect(Collectors.toList()); - } catch (InterruptedException | ExecutionException | LanguageServerException e) { + @Override + public CompletableFuture> start(InitializedLanguageServer element) { + return element.getServer().getTextDocumentService().definition(textDocumentPositionParams); + } + + @Override + public boolean handleResult(InitializedLanguageServer element, List locations) { + locations.forEach(o -> { + o.setUri(removePrefixUri(o.getUri())); + result.add(new LocationDto(o)); + }); + return true; + } + }, 30000); + return result; + } catch (LanguageServerException e) { throw new JsonRpcException(-27000, e.getMessage()); } } - private CompletionItemDto completionItemResolve(ExtendedCompletionItem unresolved) { + private ExtendedCompletionItemDto completionItemResolve(ExtendedCompletionItem unresolved) { try { - LanguageServer server = getServer(prefixURI(unresolved.getTextDocumentIdentifier().getUri())); + InitializedLanguageServer server = languageServerRegistry.getServer(unresolved.getLanguageServerId()); - return server != null ? new CompletionItemDto(server.getTextDocumentService().resolveCompletionItem(unresolved).get()) - : new CompletionItemDto(unresolved); - } catch (InterruptedException | ExecutionException | LanguageServerException e) { + if (server != null) { + ExtendedCompletionItem res = new ExtendedCompletionItem(); + res.setItem(server.getServer().getTextDocumentService().resolveCompletionItem(unresolved.getItem()).get()); + res.setLanguageServerId(unresolved.getLanguageServerId()); + return new ExtendedCompletionItemDto(res); + } + return new ExtendedCompletionItemDto(unresolved); + } catch (InterruptedException | ExecutionException e) { throw new JsonRpcException(-27000, e.getMessage()); } } private HoverDto hover(TextDocumentPositionParams positionParams) { + String uri = prefixURI(positionParams.getTextDocument().getUri()); + positionParams.getTextDocument().setUri(uri); + positionParams.setUri(prefixURI(positionParams.getUri())); + HoverDto result = new HoverDto(); + result.setContents(new ArrayList<>()); try { - positionParams.getTextDocument().setUri(prefixURI(positionParams.getTextDocument().getUri())); - positionParams.setUri(prefixURI(positionParams.getUri())); - LanguageServer server = getServer(positionParams.getTextDocument().getUri()); - if(server != null) { - Hover hover = server.getTextDocumentService().hover(positionParams).get(); - if (hover != null) { - return new HoverDto(hover); + + List servers = languageServerRegistry.getApplicableLanguageServers(uri).stream() + .flatMap(Collection::stream).collect(Collectors.toList()); + OperationUtil.doInParallel(servers, new LSOperation() { + + @Override + public boolean canDo(InitializedLanguageServer element) { + return truish(element.getInitializeResult().getCapabilities().getHoverProvider()); } - } - return null; - } catch (InterruptedException | ExecutionException | LanguageServerException e) { + + @Override + public CompletableFuture start(InitializedLanguageServer element) { + return element.getServer().getTextDocumentService().hover(positionParams); + } + + @Override + public boolean handleResult(InitializedLanguageServer element, Hover hover) { + if (hover != null) { + result.getContents().addAll(hover.getContents()); + } + return true; + } + }, 10000); + return result; + } catch (LanguageServerException e) { throw new JsonRpcException(-27000, e.getMessage()); } } private SignatureHelpDto signatureHelp(TextDocumentPositionParams positionParams) { + String uri = prefixURI(positionParams.getTextDocument().getUri()); + positionParams.getTextDocument().setUri(uri); + positionParams.setUri(prefixURI(positionParams.getUri())); + SignatureHelpDto[] result = new SignatureHelpDto[1]; try { - positionParams.getTextDocument().setUri(prefixURI(positionParams.getTextDocument().getUri())); - positionParams.setUri(prefixURI(positionParams.getUri())); - LanguageServer server = getServer(positionParams.getTextDocument().getUri()); - return server != null ? new SignatureHelpDto(server.getTextDocumentService().signatureHelp(positionParams).get()) : null; - } catch (InterruptedException | ExecutionException | LanguageServerException e) { + List servers = languageServerRegistry.getApplicableLanguageServers(uri).stream() + .flatMap(Collection::stream).collect(Collectors.toList()); + LSOperation op = new LSOperation() { + + @Override + public boolean canDo(InitializedLanguageServer element) { + return element.getInitializeResult().getCapabilities().getSignatureHelpProvider() != null; + } + + @Override + public CompletableFuture start(InitializedLanguageServer element) { + return element.getServer().getTextDocumentService().signatureHelp(positionParams); + } + + @Override + public boolean handleResult(InitializedLanguageServer element, SignatureHelp res) { + if (res != null && !res.getSignatures().isEmpty()) { + result[0] = new SignatureHelpDto(res); + return true; + } + return false; + } + }; + OperationUtil.doInSequence(servers, op, 10000); + return result[0]; + } catch (LanguageServerException e) { throw new JsonRpcException(-27000, e.getMessage()); } } private List formatting(DocumentFormattingParams documentFormattingParams) { try { - documentFormattingParams.getTextDocument().setUri(prefixURI(documentFormattingParams.getTextDocument().getUri())); - LanguageServer server = getServer(documentFormattingParams.getTextDocument().getUri()); + String uri = prefixURI(documentFormattingParams.getTextDocument().getUri()); + documentFormattingParams.getTextDocument().setUri(uri); + InitializedLanguageServer server = languageServerRegistry.getApplicableLanguageServers(uri).stream().flatMap(Collection::stream) + .filter(s -> truish(s.getInitializeResult().getCapabilities().getDocumentFormattingProvider())).findFirst() + .get(); return server == null ? Collections.emptyList() - : server.getTextDocumentService() - .formatting(documentFormattingParams) - .get().stream() - .map(TextEditDto::new) - .collect(Collectors.toList()); + : server.getServer().getTextDocumentService().formatting(documentFormattingParams) + .get(5000, TimeUnit.MILLISECONDS).stream().map(TextEditDto::new).collect(Collectors.toList()); - } catch (InterruptedException | ExecutionException | LanguageServerException e) { + } catch (InterruptedException | ExecutionException | LanguageServerException | TimeoutException e) { throw new JsonRpcException(-27000, e.getMessage()); } } private List rangeFormatting(DocumentRangeFormattingParams documentRangeFormattingParams) { try { - documentRangeFormattingParams.getTextDocument().setUri(prefixURI(documentRangeFormattingParams.getTextDocument().getUri())); - LanguageServer server = getServer(documentRangeFormattingParams.getTextDocument().getUri()); + String uri = prefixURI(documentRangeFormattingParams.getTextDocument().getUri()); + documentRangeFormattingParams.getTextDocument().setUri(uri); + InitializedLanguageServer server = languageServerRegistry.getApplicableLanguageServers(uri).stream().flatMap(Collection::stream) + .filter(s -> truish(s.getInitializeResult().getCapabilities().getDocumentRangeFormattingProvider())).findFirst() + .get(); return server == null ? Collections.emptyList() - : server.getTextDocumentService() - .rangeFormatting(documentRangeFormattingParams) - .get().stream() - .map(TextEditDto::new) - .collect(Collectors.toList()); + : server.getServer().getTextDocumentService().rangeFormatting(documentRangeFormattingParams).get().stream() + .map(TextEditDto::new).collect(Collectors.toList()); } catch (InterruptedException | ExecutionException | LanguageServerException e) { throw new JsonRpcException(-27000, e.getMessage()); } @@ -232,13 +434,14 @@ private List rangeFormatting(DocumentRangeFormattingParams document private List onTypeFormatting(DocumentOnTypeFormattingParams documentOnTypeFormattingParams) { try { - documentOnTypeFormattingParams.getTextDocument().setUri(prefixURI(documentOnTypeFormattingParams.getTextDocument().getUri())); - LanguageServer server = getServer(documentOnTypeFormattingParams.getTextDocument().getUri()); - return server == null ? Collections.emptyList() : server.getTextDocumentService() - .onTypeFormatting(documentOnTypeFormattingParams) - .get().stream() - .map(TextEditDto::new) - .collect(Collectors.toList()); + String uri = prefixURI(documentOnTypeFormattingParams.getTextDocument().getUri()); + documentOnTypeFormattingParams.getTextDocument().setUri(uri); + InitializedLanguageServer server = languageServerRegistry.getApplicableLanguageServers(uri).stream().flatMap(Collection::stream) + .filter(s -> s.getInitializeResult().getCapabilities().getDocumentOnTypeFormattingProvider() != null) + .findFirst().get(); + return server == null ? Collections.emptyList() + : server.getServer().getTextDocumentService().onTypeFormatting(documentOnTypeFormattingParams).get().stream() + .map(TextEditDto::new).collect(Collectors.toList()); } catch (InterruptedException | ExecutionException | LanguageServerException e) { throw new JsonRpcException(-27000, e.getMessage()); } @@ -246,12 +449,13 @@ private List onTypeFormatting(DocumentOnTypeFormattingParams docume private void didChange(DidChangeTextDocumentParams didChangeTextDocumentParams) { try { - didChangeTextDocumentParams.getTextDocument().setUri(prefixURI(didChangeTextDocumentParams.getTextDocument().getUri())); + String uri = prefixURI(didChangeTextDocumentParams.getTextDocument().getUri()); + didChangeTextDocumentParams.getTextDocument().setUri(uri); didChangeTextDocumentParams.setUri(prefixURI(didChangeTextDocumentParams.getUri())); - LanguageServer server = getServer(didChangeTextDocumentParams.getTextDocument().getUri()); - if (server != null) { - server.getTextDocumentService().didChange(didChangeTextDocumentParams); - } + languageServerRegistry.getApplicableLanguageServers(uri).stream().flatMap(Collection::stream) + .map(InitializedLanguageServer::getServer).forEach(server -> { + server.getTextDocumentService().didChange(didChangeTextDocumentParams); + }); } catch (LanguageServerException e) { LOG.error("Error trying to process textDocument/didChange", e); } @@ -259,11 +463,12 @@ private void didChange(DidChangeTextDocumentParams didChangeTextDocumentParams) private void didOpen(DidOpenTextDocumentParams openTextDocumentParams) { try { - openTextDocumentParams.getTextDocument().setUri(prefixURI(openTextDocumentParams.getTextDocument().getUri())); - LanguageServer server = getServer(openTextDocumentParams.getTextDocument().getUri()); - if (server != null) { - server.getTextDocumentService().didOpen(openTextDocumentParams); - } + String uri = prefixURI(openTextDocumentParams.getTextDocument().getUri()); + openTextDocumentParams.getTextDocument().setUri(uri); + languageServerRegistry.getApplicableLanguageServers(uri).stream().flatMap(Collection::stream) + .map(InitializedLanguageServer::getServer).forEach(server -> { + server.getTextDocumentService().didOpen(openTextDocumentParams); + }); } catch (LanguageServerException e) { LOG.error("Error trying to process textDocument/didOpen", e); } @@ -271,11 +476,12 @@ private void didOpen(DidOpenTextDocumentParams openTextDocumentParams) { private void didClose(DidCloseTextDocumentParams didCloseTextDocumentParams) { try { - didCloseTextDocumentParams.getTextDocument().setUri(prefixURI(didCloseTextDocumentParams.getTextDocument().getUri())); - LanguageServer server = getServer(didCloseTextDocumentParams.getTextDocument().getUri()); - if (server != null) { - server.getTextDocumentService().didClose(didCloseTextDocumentParams); - } + String uri = prefixURI(didCloseTextDocumentParams.getTextDocument().getUri()); + didCloseTextDocumentParams.getTextDocument().setUri(uri); + languageServerRegistry.getApplicableLanguageServers(uri).stream().flatMap(Collection::stream) + .map(InitializedLanguageServer::getServer).forEach(server -> { + server.getTextDocumentService().didClose(didCloseTextDocumentParams); + }); } catch (LanguageServerException e) { LOG.error("Error trying to process textDocument/didOpen", e); } @@ -283,56 +489,91 @@ private void didClose(DidCloseTextDocumentParams didCloseTextDocumentParams) { private void didSave(DidSaveTextDocumentParams didSaveTextDocumentParams) { try { - didSaveTextDocumentParams.getTextDocument().setUri(prefixURI(didSaveTextDocumentParams.getTextDocument().getUri())); - LanguageServer server = getServer(didSaveTextDocumentParams.getTextDocument().getUri()); - if (server != null) { - server.getTextDocumentService().didSave(didSaveTextDocumentParams); - } + String uri = prefixURI(didSaveTextDocumentParams.getTextDocument().getUri()); + didSaveTextDocumentParams.getTextDocument().setUri(uri); + languageServerRegistry.getApplicableLanguageServers(uri).stream().flatMap(Collection::stream) + .map(InitializedLanguageServer::getServer).forEach(server -> { + server.getTextDocumentService().didSave(didSaveTextDocumentParams); + }); } catch (LanguageServerException e) { LOG.error("Error trying to process textDocument/didSave", e); } } - private List documentHighlight(TextDocumentPositionParams textDocumentPositionParams) { + private DocumentHighlightDto documentHighlight(TextDocumentPositionParams textDocumentPositionParams) { try { - textDocumentPositionParams.getTextDocument().setUri(prefixURI(textDocumentPositionParams.getTextDocument().getUri())); - LanguageServer server = getServer(textDocumentPositionParams.getTextDocument().getUri()); - if (server != null) { - List result = server.getTextDocumentService().documentHighlight(textDocumentPositionParams).get(); - return result.stream().map(DocumentHighlightDto::new).collect(Collectors.toList()); + String uri = prefixURI(textDocumentPositionParams.getTextDocument().getUri()); + textDocumentPositionParams.getTextDocument().setUri(uri); + @SuppressWarnings("unchecked") + List[] result = new List[1]; + LSOperation, List> op = new LSOperation, List>() { + + @Override + public boolean canDo(Collection servers) { + return true; + } + + @Override + public CompletableFuture> start(Collection element) { + return CompletableFuture.supplyAsync(() -> { + List res = new ArrayList<>(); + LSOperation> op2 = new LSOperation>() { + + @Override + public boolean canDo(InitializedLanguageServer element) { + return truish(element.getInitializeResult().getCapabilities().getDocumentHighlightProvider()); + } + + @Override + public CompletableFuture> start(InitializedLanguageServer element) { + return element.getServer().getTextDocumentService().documentHighlight(textDocumentPositionParams); + } + + @Override + public boolean handleResult(InitializedLanguageServer element, List result) { + + return false; + } + }; + OperationUtil.doInParallel(element, op2, 10000); + + return res; + }); + } + + @Override + public boolean handleResult(Collection element, List list) { + result[0] = list; + return !list.isEmpty(); + } + }; + OperationUtil.doInSequence(languageServerRegistry.getApplicableLanguageServers(uri), op, 10000); + + if (!result[0].isEmpty()) { + return result[0].get(0); } return null; - } catch (LanguageServerException | InterruptedException | ExecutionException e) { + } catch (LanguageServerException e) { throw new JsonRpcException(-27000, e.getMessage()); - } - } - private LanguageServer getServer(String uri) throws LanguageServerException { - return languageServerRegistry.findServer(uri); + } } - private

void dtoToNothing(String name, Class

pClass, Consumer

consumer) { - requestHandler.newConfiguration() - .methodName("textDocument/" + name) - .paramsAsDto(pClass) - .noResult() - .withConsumer(consumer); + requestHandler.newConfiguration().methodName("textDocument/" + name).paramsAsDto(pClass).noResult().withConsumer(consumer); } private void dtoToDtoList(String name, Class

pClass, Class rClass, Function> function) { - requestHandler.newConfiguration() - .methodName("textDocument/" + name) - .paramsAsDto(pClass) - .resultAsListOfDto(rClass) - .withFunction(function); + requestHandler.newConfiguration().methodName("textDocument/" + name).paramsAsDto(pClass).resultAsListOfDto(rClass) + .withFunction(function); } private void dtoToDto(String name, Class

pClass, Class rClass, Function function) { - requestHandler.newConfiguration() - .methodName("textDocument/" + name) - .paramsAsDto(pClass) - .resultAsDto(rClass) - .withFunction(function); + requestHandler.newConfiguration().methodName("textDocument/" + name).paramsAsDto(pClass).resultAsDto(rClass).withFunction(function); } + + private boolean truish(Boolean b) { + return b != null && b; + } + } diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/TextDocumentServiceUtils.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/TextDocumentServiceUtils.java index 092216d57c2..e6e81cd76d4 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/TextDocumentServiceUtils.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/TextDocumentServiceUtils.java @@ -23,4 +23,9 @@ static String prefixURI(String relativePath) { static String removePrefixUri(String uri) { return uri.startsWith(FILE_PROJECTS) ? uri.substring(FILE_PROJECTS.length()) : uri; } + + static boolean truish(Boolean b) { + return b != null && b; + } + } diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/WorkspaceService.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/WorkspaceService.java index 0a725ad73c2..49da3b209d7 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/WorkspaceService.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/service/WorkspaceService.java @@ -12,32 +12,36 @@ import com.google.inject.Inject; import com.google.inject.Singleton; - import org.eclipse.che.api.languageserver.exception.LanguageServerException; +import org.eclipse.che.api.languageserver.registry.InitializedLanguageServer; import org.eclipse.che.api.languageserver.registry.LanguageServerRegistry; import org.eclipse.che.api.languageserver.registry.LanguageServerRegistryImpl; import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.SymbolInformationDto; import org.eclipse.che.api.languageserver.shared.model.ExtendedWorkspaceSymbolParams; -import org.eclipse.lsp4j.Location; +import org.eclipse.che.api.languageserver.util.LSOperation; +import org.eclipse.che.api.languageserver.util.OperationUtil; import org.eclipse.lsp4j.SymbolInformation; -import org.eclipse.lsp4j.services.LanguageServer; import javax.ws.rs.Consumes; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; + +import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; -import static java.util.Collections.emptyList; -import static org.eclipse.che.api.languageserver.service.TextDocumentServiceUtils.prefixURI; import static org.eclipse.che.api.languageserver.service.TextDocumentServiceUtils.removePrefixUri; +import static org.eclipse.che.api.languageserver.service.TextDocumentServiceUtils.truish; /** - * REST API for the workspace/* services defined in https://github.com/Microsoft/vscode-languageserver-protocol - * Dispatches onto the {@link LanguageServerRegistryImpl}. + * REST API for the workspace/* services defined in + * https://github.com/Microsoft/vscode-languageserver-protocol Dispatches onto + * the {@link LanguageServerRegistryImpl}. * * @author Evgen Vidolob */ @@ -55,24 +59,32 @@ public WorkspaceService(LanguageServerRegistry registry) { @Path("symbol") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - public List documentSymbol(ExtendedWorkspaceSymbolParams workspaceSymbolParams) - throws ExecutionException, - InterruptedException, - LanguageServerException { - LanguageServer server = getServer(prefixURI(workspaceSymbolParams.getFileUri())); - if (server == null) { - return emptyList(); - } + public List symbol(ExtendedWorkspaceSymbolParams workspaceSymbolParams) + throws ExecutionException, InterruptedException, LanguageServerException { + List result = new ArrayList<>(); + List servers = registry.getApplicableLanguageServers(workspaceSymbolParams.getFileUri()).stream() + .flatMap(Collection::stream).collect(Collectors.toList()); + OperationUtil.doInParallel(servers, new LSOperation>() { - List informations = server.getWorkspaceService().symbol(workspaceSymbolParams).get(); - informations.forEach(o -> { - Location location = o.getLocation(); - location.setUri(removePrefixUri(location.getUri())); - }); - return informations.stream().map(o -> new SymbolInformationDto(o)).collect(Collectors.toList()); - } + @Override + public boolean canDo(InitializedLanguageServer element) { + return truish(element.getInitializeResult().getCapabilities().getWorkspaceSymbolProvider()); + } + + @Override + public CompletableFuture> start(InitializedLanguageServer element) { + return element.getServer().getWorkspaceService().symbol(workspaceSymbolParams); + } - private LanguageServer getServer(String uri) throws LanguageServerException { - return registry.findServer(uri); + @Override + public boolean handleResult(InitializedLanguageServer element, List locations) { + locations.forEach(o -> { + o.getLocation().setUri(removePrefixUri(o.getLocation().getUri())); + result.add(new SymbolInformationDto(o)); + }); + return true; + } + }, 10000); + return result; } } diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/util/JsonUtil.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/util/JsonUtil.java index 76e8c258d66..a6203b078f7 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/util/JsonUtil.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/util/JsonUtil.java @@ -10,11 +10,17 @@ *******************************************************************************/ package org.eclipse.che.api.languageserver.util; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import org.eclipse.che.dto.server.JsonSerializable; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + /** * Utility to convert stuff that is not statically typed in lsp4j (java.lang.Object) * @@ -35,6 +41,24 @@ public static JsonElement convertToJson(Object value) { return ((JsonSerializable)value).toJsonElement(); } else if (value instanceof JsonElement) { return (JsonElement)value; + } else if (value instanceof Map) { + // assumption here is that this is a json-like structure with map for object, list for array, etc. + @SuppressWarnings("unchecked") + Map object= (Map) value; + JsonObject result= new JsonObject(); + for (Entry prop : object.entrySet()) { + result.add(prop.getKey(), convertToJson(prop.getValue())); + } + return result; + } else if (value instanceof List) { + // assumption here is that this is a json-like structure with map for object, list for array, etc. + @SuppressWarnings("unchecked") + List array= (List) value; + JsonArray result= new JsonArray(); + for (Object object : array) { + result.add(convertToJson(object)); + } + return result; } throw new RuntimeException("Unexpected runtime value: " + value); } diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/util/LSOperation.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/util/LSOperation.java new file mode 100644 index 00000000000..38d668544ce --- /dev/null +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/util/LSOperation.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Red Hat + * 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 + * + * Contributors: + * Red Hat - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.languageserver.util; + +import java.util.concurrent.CompletableFuture; + +/** + * An operation to be executed against collections of language servers. See {@link OperationUtil} + * @author Thomas Mäder + * + * @param The type this operation acts upon + * @param The type this operation produces + */ +public interface LSOperation { + + /** + * Returns whether the operation can be performed on the given element + * @param element + * @return true if the operations should be performed + */ + boolean canDo(C element); + + /** + * Start the operation on the given element + * @param element + * @return a future that produces the result of running the operation on the given element + */ + CompletableFuture start(C element); + + /** + * Handle the result of of processing an element. + * + * @param result + * @return true if the result is valid (non-empty, not null) + */ + boolean handleResult(C element, R result); +} diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/util/OperationUtil.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/util/OperationUtil.java new file mode 100644 index 00000000000..b1847e28bf5 --- /dev/null +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/util/OperationUtil.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2012-2017 Red Hat + * 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 + * + * Contributors: + * Red Hat - initial API and implementation + *******************************************************************************/ +package org.eclipse.che.api.languageserver.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class OperationUtil { + private final static Logger LOG = LoggerFactory.getLogger(OperationUtil.class); + + /** + * Execute the given operation on each element of the collection in + * sequence. Stops as soon as {@link LSOperation#canDo(Object)} returns + * true. + * + * @param collection + * @param op + * @param timeoutMillis + */ + public static void doInSequence(Collection collection, LSOperation op, long timeoutMillis) { + long endTime = System.currentTimeMillis() + timeoutMillis; + for (C element : collection) { + if (op.canDo(element)) { + CompletableFuture future = op.start(element); + try { + R result = future.get(Math.max(endTime - timeoutMillis, 1), TimeUnit.MILLISECONDS); + if (op.handleResult(element, result)) { + return; + } + } catch (InterruptedException e) { + LOG.info("Thread interrupted", e); + Thread.currentThread().interrupt(); + } catch (ExecutionException e) { + LOG.info("Exception occurred in op", e); + } catch (TimeoutException e) { + future.cancel(true); + } + } + } + } + + /** + * Executes the given operation in parallel for each element in the + * collection. Failures in any of the operations are ignored. + * + * @param collection + * @param op + * @param timeoutMillis + */ + public static void doInParallel(Collection collection, LSOperation op, long timeoutMillis) { + Object lock = new Object(); + List> pendingResponses = new ArrayList<>(); + + for (C element : collection) { + if (op.canDo(element)) { + CompletableFuture future = op.start(element); + synchronized (lock) { + pendingResponses.add(future); + lock.notifyAll(); + } + future.thenAccept(result -> { + synchronized (lock) { + if (!future.isCancelled()) { + op.handleResult(element, result); + pendingResponses.remove(future); + lock.notifyAll(); + } + } + }).exceptionally((t) -> { + LOG.info("Exception occurred in request", t); + synchronized (lock) { + pendingResponses.remove(future); + lock.notifyAll(); + } + return null; + }); + } + } + + long endTime = System.currentTimeMillis() + 5000; + + try { + synchronized (lock) { + while (System.currentTimeMillis() < endTime && pendingResponses.size() > 0) { + lock.wait(endTime - System.currentTimeMillis()); + } + } + } catch (InterruptedException e) { + LOG.info("Thread interrupted", e); + Thread.currentThread().interrupt(); + } + synchronized (lock) { + for (CompletableFuture pending : new ArrayList<>(pendingResponses)) { + pending.cancel(true); + } + lock.notifyAll(); + } + } + +} diff --git a/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/dto/DtoConversionTest.java b/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/dto/DtoConversionTest.java index ad9caed4cec..b9c640b3bfc 100644 --- a/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/dto/DtoConversionTest.java +++ b/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/dto/DtoConversionTest.java @@ -17,6 +17,7 @@ import org.eclipse.che.api.languageserver.server.dto.DtoServerImpls.WorkspaceEditDto; import org.eclipse.che.api.languageserver.shared.model.ExtendedCompletionItem; import org.eclipse.che.api.languageserver.shared.model.ExtendedCompletionList; +import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.ParameterInformation; import org.eclipse.lsp4j.Position; @@ -43,11 +44,12 @@ public void testListConversion() { ecl.setInComplete(true); List items = new ArrayList<>(); ExtendedCompletionItem item = new ExtendedCompletionItemDto(); - item.setTextEdit(new TextEdit(new Range(new Position(1, 2), new Position(3, 4)), "changed text")); + item.setItem(new CompletionItem()); + item.getItem().setTextEdit(new TextEdit(new Range(new Position(1, 2), new Position(3, 4)), "changed text")); // cannot unmarshal object type stuff from json. So need to set json // element for equality test to work. - item.setData(new ParameterInformationDto(new ParameterInformation("the label", "the doc")).toJsonElement()); + item.getItem().setData(new ParameterInformationDto(new ParameterInformation("the label", "the doc")).toJsonElement()); items.add(item); ecl.setItems(items); diff --git a/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImplTest.java b/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImplTest.java index 9070a92c90a..00f6c7c4658 100644 --- a/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImplTest.java +++ b/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImplTest.java @@ -10,12 +10,18 @@ *******************************************************************************/ package org.eclipse.che.api.languageserver.registry; +import com.google.inject.Provider; import org.eclipse.che.api.languageserver.exception.LanguageServerException; import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher; import org.eclipse.che.api.languageserver.shared.model.LanguageDescription; +import org.eclipse.che.api.project.server.FolderEntry; +import org.eclipse.che.api.project.server.ProjectManager; +import org.eclipse.che.api.vfs.Path; +import org.eclipse.che.commons.lang.Pair; import org.eclipse.lsp4j.InitializeParams; import org.eclipse.lsp4j.InitializeResult; import org.eclipse.lsp4j.ServerCapabilities; +import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.LanguageServer; import org.eclipse.lsp4j.services.TextDocumentService; import org.mockito.Mock; @@ -24,16 +30,12 @@ import org.testng.annotations.Listeners; import org.testng.annotations.Test; -import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; -import java.util.List; import java.util.concurrent.CompletableFuture; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -47,34 +49,42 @@ @Listeners(MockitoTestNGListener.class) public class LanguageServerRegistryImplTest { - private static final String PREFIX = "file://"; - private static final String FILE_PATH = "/projects/1/test.txt"; - private static final String PROJECT_PATH = "/1"; + private static final String PROJECTS_ROOT = "file:///projects"; + private static final String PREFIX = "file://"; + private static final String FILE_PATH = "/projects/1/test.txt"; + private static final String PROJECT_PATH = "file:///projects/1"; @Mock - private ServerInitializer initializer; + private ServerInitializer initializer; @Mock - private LanguageServerLauncher languageServerLauncher; + private LanguageServerLauncher languageServerLauncher; @Mock - private LanguageDescription languageDescription; + private LanguageDescription languageDescription; @Mock - private LanguageServer languageServer; + private LanguageServer languageServer; @Mock - private InitializeResult initializeResult; + private Provider pmp; @Mock - private ServerCapabilities serverCapabilities; + private ProjectManager pm; @Mock - private CompletableFuture completableFuture; + private FolderEntry projectsRoot; - private LanguageServerRegistryImpl registry; + private LanguageServerRegistryImpl registry; + private LanguageServerDescription serverDescription; + private InitializeResult initializeResult; + private CompletableFuture completableFuture; + private ServerCapabilities serverCapabilities; @BeforeMethod public void setUp() throws Exception { - when(completableFuture.get()).thenReturn(initializeResult); - when(initializeResult.getCapabilities()).thenReturn(serverCapabilities); + this.serverCapabilities= new ServerCapabilities(); + serverDescription = new LanguageServerDescription("foo", Collections.singletonList("id"), Collections.emptyList()); + initializeResult = new InitializeResult(serverCapabilities); + + completableFuture = CompletableFuture.completedFuture(initializeResult); - when(languageServerLauncher.getLanguageId()).thenReturn("id"); when(languageServerLauncher.isAbleToLaunch()).thenReturn(true); + when(languageServerLauncher.getDescription()).thenReturn(serverDescription); when(languageDescription.getLanguageId()).thenReturn("id"); when(languageDescription.getFileExtensions()).thenReturn(Collections.singletonList("txt")); when(languageDescription.getMimeType()).thenReturn("plain/text"); @@ -82,64 +92,29 @@ public void setUp() throws Exception { when(languageServer.getTextDocumentService()).thenReturn(mock(TextDocumentService.class)); when(languageServer.initialize(any(InitializeParams.class))).thenReturn(completableFuture); - registry = spy(new LanguageServerRegistryImpl(Collections.singleton(languageServerLauncher), - Collections.singleton(languageDescription), null, initializer)); - - when(initializer.initialize(any(LanguageDescription.class), any(LanguageServerLauncher.class), anyString())) - .thenAnswer(invocation -> { - Object[] arguments = invocation.getArguments(); - registry.onServerInitialized(languageServer, serverCapabilities, languageDescription, (String) arguments[2]); - return languageServer; - }); - - doReturn(PROJECT_PATH).when(registry).extractProjectPath(FILE_PATH); - } - - private LanguageServerLauncher createLauncher(String id, ServerCapabilities capabilities) throws LanguageServerException { - LanguageServerLauncher launcher = mock(LanguageServerLauncher.class); - when(launcher.getLanguageId()).thenReturn(id); - when(launcher.isAbleToLaunch()).thenReturn(true); - return launcher; - } - - private LanguageDescription createDescription(String id, List extensions, List names) { - LanguageDescription description = new LanguageDescription(); - description.setFileExtensions(extensions); - description.setFileNames(names); - description.setLanguageId(id); - return description; - } - - @Test - public void testFindServer() throws Exception { - LanguageServer server = registry.findServer(PREFIX + FILE_PATH); + when(pmp.get()).thenReturn(pm); + when(projectsRoot.getPath()).thenReturn(Path.of(PROJECTS_ROOT)); + when(pm.getProjectsRoot()).thenReturn(projectsRoot); - assertNotNull(server); - assertEquals(server, languageServer); - verify(initializer).initialize(eq(languageDescription), eq(languageServerLauncher), eq(PROJECT_PATH)); - verify(registry).onServerInitialized(eq(languageServer), eq(serverCapabilities), eq(languageDescription), eq(PROJECT_PATH)); - } - - @Test - void testFindByPattern() throws Exception { - LanguageServerLauncher xmlLauncher = createLauncher("xml", null); - LanguageServerLauncher pomLauncher = createLauncher("pom", null); - LanguageDescription xmlDesc = createDescription("xml", Arrays.asList("xml"), Collections.emptyList()); - LanguageDescription pomDesc = createDescription("pom", Arrays.asList(), Arrays.asList("pom.xml")); - LanguageServer xmlServer = mock(LanguageServer.class); - LanguageServer pomServer = mock(LanguageServer.class); - ServerInitializer initializer = mock(ServerInitializer.class); - when(initializer.initialize(eq(pomDesc), any(LanguageServerLauncher.class), anyString())).thenReturn(pomServer); - when(initializer.initialize(eq(xmlDesc), any(LanguageServerLauncher.class), anyString())).thenReturn(xmlServer); - LanguageServerRegistryImpl registry = spy(new LanguageServerRegistryImpl(new HashSet<>(Arrays.asList(xmlLauncher, pomLauncher)), - new HashSet<>(Arrays.asList(xmlDesc, pomDesc)), null, initializer) { + registry = spy(new LanguageServerRegistryImpl(Collections.singleton(languageServerLauncher), + Collections.singleton(languageDescription), pmp, initializer, null) { @Override protected String extractProjectPath(String filePath) throws LanguageServerException { return PROJECT_PATH; } }); - assertEquals(xmlServer, registry.findServer("/foo/bar/foo.xml")); - assertEquals(pomServer, registry.findServer("/foo/bar/pom.xml")); + when(initializer.initialize(any(LanguageServerLauncher.class), any(LanguageClient.class), anyString())).thenAnswer(invocation -> { + return CompletableFuture.completedFuture(Pair.of(languageServer, initializeResult)); + }); + } + + @Test + public void testFindServer() throws Exception { + ServerCapabilities cap = registry.initialize(PREFIX + FILE_PATH); + + assertNotNull(cap); + assertEquals(cap, serverCapabilities); + verify(initializer).initialize(eq(languageServerLauncher), any(LanguageClient.class), eq(PROJECT_PATH)); } } diff --git a/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/registry/ServerInitializerImplTest.java b/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/registry/ServerInitializerImplTest.java index a1ad1bb764a..d4fe1c72df3 100644 --- a/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/registry/ServerInitializerImplTest.java +++ b/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/registry/ServerInitializerImplTest.java @@ -13,6 +13,7 @@ import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher; import org.eclipse.che.api.languageserver.shared.model.LanguageDescription; +import org.eclipse.che.commons.lang.Pair; import org.eclipse.lsp4j.InitializeParams; import org.eclipse.lsp4j.InitializeResult; import org.eclipse.lsp4j.ServerCapabilities; @@ -29,8 +30,8 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; @@ -42,39 +43,41 @@ public class ServerInitializerImplTest { @Mock - private ServerInitializerObserver observer; + private ServerInitializerObserver observer; @Mock - private LanguageDescription languageDescription; + private LanguageDescription languageDescription; @Mock - private LanguageServerLauncher launcher; + private LanguageServerDescription serverDescription; @Mock - private LanguageServer server; + private LanguageServerLauncher launcher; @Mock - private CompletableFuture completableFuture; + private LanguageServer server; @Mock - private EventService eventService; + private EventService eventService; + private CompletableFuture completableFuture; private ServerInitializerImpl initializer; @BeforeMethod public void setUp() throws Exception { - initializer = spy(new ServerInitializerImpl(eventService)); + initializer = spy(new ServerInitializerImpl()); + completableFuture = CompletableFuture.completedFuture(new InitializeResult(new ServerCapabilities())); } @Test public void initializerShouldNotifyObservers() throws Exception { when(languageDescription.getLanguageId()).thenReturn("languageId"); when(server.initialize(any(InitializeParams.class))).thenReturn(completableFuture); - when(completableFuture.get()).thenReturn(mock(InitializeResult.class)); - when(launcher.getLanguageId()).thenReturn("languageId"); when(launcher.launch(anyString(), any())).thenReturn(server); - doNothing().when(initializer).registerCallbacks(server, launcher); + when(launcher.getDescription()).thenReturn(serverDescription); + when(serverDescription.getId()).thenReturn("launcherId"); + doNothing().when(initializer).registerCallbacks(any(), any()); initializer.addObserver(observer); - LanguageServer languageServer = initializer.initialize(languageDescription, launcher, "/path"); + Pair initResult = initializer.initialize(launcher, null, "/path").get(); - assertEquals(server, languageServer); - verify(observer).onServerInitialized(eq(server), any(ServerCapabilities.class), eq(languageDescription), eq("/path")); + assertEquals(server, initResult.first); + verify(observer, timeout(2000)).onServerInitialized(eq(launcher), eq(server), any(ServerCapabilities.class), eq("/path")); } }