diff --git a/composer/packages/documentation/src/components/Documentation.jsx b/composer/packages/documentation/src/components/Documentation.jsx index 77701e321147..dd9498e2d313 100644 --- a/composer/packages/documentation/src/components/Documentation.jsx +++ b/composer/packages/documentation/src/components/Documentation.jsx @@ -32,7 +32,7 @@ const Documentation = ({ docDetails }) => { }[kind]; return ( -
+
{ icon && () }{title} { kind === 'Function' && '()' } diff --git a/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/LSCompiler.java b/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/LSCompiler.java index 60fba7986522..976b04dda16d 100644 --- a/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/LSCompiler.java +++ b/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/LSCompiler.java @@ -15,7 +15,6 @@ */ package org.ballerinalang.langserver.compiler; -import org.antlr.v4.runtime.DefaultErrorStrategy; import org.ballerinalang.compiler.CompilerPhase; import org.ballerinalang.langserver.compiler.common.LSDocument; import org.ballerinalang.langserver.compiler.common.modal.BallerinaFile; @@ -124,12 +123,7 @@ public BallerinaFile compileFile(Path filePath, CompilerPhase phase) { CompilerContext context = prepareCompilerContext(packageID, packageRepository, sourceDocument, true, documentManager, phase); - // In order to capture the syntactic errors, need to go through the default error strategy - context.put(DefaultErrorStrategy.class, null); BLangPackage bLangPackage = null; - if (context.get(DiagnosticListener.class) instanceof CollectDiagnosticListener) { - ((CollectDiagnosticListener) context.get(DiagnosticListener.class)).clearAll(); - } boolean isProjectDir = (LSCompilerUtil.isBallerinaProject(sourceRoot, filePath.toUri().toString())); try { BLangDiagnosticLog.getInstance(context).errorCount = 0; diff --git a/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/LSCompilerUtil.java b/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/LSCompilerUtil.java index db7cc35aeb38..aaed30e6553b 100644 --- a/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/LSCompilerUtil.java +++ b/language-server/modules/langserver-compiler/src/main/java/org/ballerinalang/langserver/compiler/LSCompilerUtil.java @@ -27,6 +27,7 @@ import org.ballerinalang.repository.PackageRepository; import org.ballerinalang.toml.model.Manifest; import org.ballerinalang.toml.parser.ManifestProcessor; +import org.ballerinalang.util.diagnostic.DiagnosticListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.wso2.ballerinalang.compiler.Compiler; @@ -143,6 +144,10 @@ public static CompilerContext prepareCompilerContext(PackageID packageID, Packag // In order to capture the syntactic errors, need to go through the default error strategy context.put(DefaultErrorStrategy.class, null); + if (context.get(DiagnosticListener.class) instanceof CollectDiagnosticListener) { + ((CollectDiagnosticListener) context.get(DiagnosticListener.class)).clearAll(); + } + Path sourceRootPath = document.getSourceRootPath(); if (isBallerinaProject(document.getSourceRoot(), document.getURIString())) { LangServerFSProjectDirectory projectDirectory = diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/BallerinaLanguageServer.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/BallerinaLanguageServer.java index fff5f5f333d4..cb5e42ce3d19 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/BallerinaLanguageServer.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/BallerinaLanguageServer.java @@ -18,6 +18,7 @@ import com.google.gson.Gson; import org.ballerinalang.langserver.client.ExtendedLanguageClient; import org.ballerinalang.langserver.client.ExtendedLanguageClientAware; +import org.ballerinalang.langserver.codelenses.LSCodeLensesProviderFactory; import org.ballerinalang.langserver.command.LSCommandExecutorProvider; import org.ballerinalang.langserver.common.utils.CommonUtil; import org.ballerinalang.langserver.compiler.workspace.WorkspaceDocumentManager; @@ -37,6 +38,7 @@ import org.ballerinalang.langserver.extensions.ballerina.traces.Listener; import org.ballerinalang.langserver.extensions.ballerina.traces.ProviderOptions; import org.ballerinalang.langserver.index.LSIndexImpl; +import org.eclipse.lsp4j.CodeLensOptions; import org.eclipse.lsp4j.CompletionOptions; import org.eclipse.lsp4j.ExecuteCommandOptions; import org.eclipse.lsp4j.InitializeParams; @@ -96,6 +98,7 @@ public BallerinaLanguageServer(WorkspaceDocumentManager documentManager) { this.ballerinaFragmentService = new BallerinaFragmentServiceImpl(lsGlobalContext); LSAnnotationCache.initiate(); + LSCodeLensesProviderFactory.getInstance().initiate(); } public ExtendedLanguageClient getClient() { @@ -123,6 +126,7 @@ public CompletableFuture initialize(InitializeParams params) { res.getCapabilities().setRenameProvider(true); res.getCapabilities().setWorkspaceSymbolProvider(true); res.getCapabilities().setImplementationProvider(true); + res.getCapabilities().setCodeLensProvider(new CodeLensOptions()); TextDocumentClientCapabilities textDocCapabilities = params.getCapabilities().getTextDocument(); ((BallerinaTextDocumentService) this.textService).setClientCapabilities(textDocCapabilities); diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/BallerinaTextDocumentService.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/BallerinaTextDocumentService.java index 9fe4c888dec0..1dee8b6d1632 100755 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/BallerinaTextDocumentService.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/BallerinaTextDocumentService.java @@ -16,10 +16,15 @@ package org.ballerinalang.langserver; import com.google.gson.JsonObject; +import org.ballerinalang.langserver.codelenses.CodeLensesProviderKeys; +import org.ballerinalang.langserver.codelenses.LSCodeLensesProvider; +import org.ballerinalang.langserver.codelenses.LSCodeLensesProviderException; +import org.ballerinalang.langserver.codelenses.LSCodeLensesProviderFactory; import org.ballerinalang.langserver.command.CommandUtil; import org.ballerinalang.langserver.common.constants.NodeContextKeys; import org.ballerinalang.langserver.common.position.PositionTreeVisitor; import org.ballerinalang.langserver.common.utils.CommonUtil; +import org.ballerinalang.langserver.compiler.CollectDiagnosticListener; import org.ballerinalang.langserver.compiler.DocumentServiceKeys; import org.ballerinalang.langserver.compiler.LSCompiler; import org.ballerinalang.langserver.compiler.LSCompilerException; @@ -52,6 +57,7 @@ import org.ballerinalang.langserver.symbols.SymbolFindingVisitor; import org.ballerinalang.langserver.util.Debouncer; import org.ballerinalang.model.elements.PackageID; +import org.ballerinalang.util.diagnostic.DiagnosticListener; import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.CodeActionParams; import org.eclipse.lsp4j.CodeLens; @@ -116,7 +122,7 @@ * Text document service implementation for ballerina. */ class BallerinaTextDocumentService implements TextDocumentService { - private static final Logger logger = LoggerFactory.getLogger(BallerinaTextDocumentService.class); + private static final Logger LOGGER = LoggerFactory.getLogger(BallerinaTextDocumentService.class); // indicates the frequency to send diagnostics to server upon document did change private static final int DIAG_PUSH_DEBOUNCE_DELAY = 500; @@ -175,7 +181,7 @@ public CompletableFuture, CompletionList>> completio } catch (Exception | AssertionError e) { if (CommonUtil.LS_DEBUG_ENABLED) { String msg = e.getMessage(); - logger.error("Error while resolving symbols" + ((msg != null) ? ": " + msg : ""), e); + LOGGER.error("Error while resolving symbols" + ((msg != null) ? ": " + msg : ""), e); } } finally { lock.ifPresent(Lock::unlock); @@ -209,7 +215,7 @@ public CompletableFuture hover(TextDocumentPositionParams position) { } catch (Exception | AssertionError e) { if (CommonUtil.LS_DEBUG_ENABLED) { String msg = e.getMessage(); - logger.error("Error while retrieving hover content" + ((msg != null) ? ": " + msg : ""), e); + LOGGER.error("Error while retrieving hover content" + ((msg != null) ? ": " + msg : ""), e); } hover = new Hover(); List> contents = new ArrayList<>(); @@ -250,7 +256,7 @@ public CompletableFuture signatureHelp(TextDocumentPositionParams } catch (Exception | ZipError | AssertionError e) { if (CommonUtil.LS_DEBUG_ENABLED) { String msg = e.getMessage(); - logger.error("Error while retrieving signature help" + ((msg != null) ? ": " + msg : ""), e); + LOGGER.error("Error while retrieving signature help" + ((msg != null) ? ": " + msg : ""), e); } return new SignatureHelp(); } finally { @@ -281,7 +287,7 @@ public CompletableFuture> definition(TextDocumentPositi } catch (Exception e) { if (CommonUtil.LS_DEBUG_ENABLED) { String msg = e.getMessage(); - logger.error("Error while retrieving definition" + ((msg != null) ? ": " + msg : ""), e); + LOGGER.error("Error while retrieving definition" + ((msg != null) ? ": " + msg : ""), e); } contents = new ArrayList<>(); } finally { @@ -338,7 +344,7 @@ public CompletableFuture> references(ReferenceParams pa } catch (Exception e) { if (CommonUtil.LS_DEBUG_ENABLED) { String msg = e.getMessage(); - logger.error("Error while retrieving references" + ((msg != null) ? ": " + msg : ""), e); + LOGGER.error("Error while retrieving references" + ((msg != null) ? ": " + msg : ""), e); } return contents; } finally { @@ -383,7 +389,7 @@ public CompletableFuture> documentHighlight( } catch (Exception e) { if (CommonUtil.LS_DEBUG_ENABLED) { String msg = e.getMessage(); - logger.error("Error while retrieving document symbols" + ((msg != null) ? ": " + msg : ""), e); + LOGGER.error("Error while retrieving document symbols" + ((msg != null) ? ": " + msg : ""), e); } return symbols; } finally { @@ -439,7 +445,7 @@ public CompletableFuture>> codeAction(CodeActio } catch (Exception e) { if (CommonUtil.LS_DEBUG_ENABLED) { String msg = e.getMessage(); - logger.error("Error while retrieving code actions" + ((msg != null) ? ": " + msg : ""), e); + LOGGER.error("Error while retrieving code actions" + ((msg != null) ? ": " + msg : ""), e); } return commands; } @@ -448,7 +454,65 @@ public CompletableFuture>> codeAction(CodeActio @Override public CompletableFuture> codeLens(CodeLensParams params) { - return null; + return CompletableFuture.supplyAsync(() -> { + List lenses = new ArrayList<>(); + + if (!LSCodeLensesProviderFactory.getInstance().isEnabled()) { + // Disabled ballerina codeLens feature + clientCapabilities.setCodeLens(null); + // Skip code lenses if codeLens disabled + return lenses; + } + + String fileUri = params.getTextDocument().getUri(); + Path docSymbolFilePath = new LSDocument(fileUri).getPath(); + Path compilationPath = getUntitledFilePath(docSymbolFilePath.toString()).orElse(docSymbolFilePath); + Optional lock = documentManager.lockFile(compilationPath); + try { + LSServiceOperationContext codeLensContext = new LSServiceOperationContext(); + codeLensContext.put(DocumentServiceKeys.FILE_URI_KEY, fileUri); + BLangPackage bLangPackage = lsCompiler.getBLangPackage(codeLensContext, documentManager, false, + LSCustomErrorStrategy.class, false); + Optional documentCUnit = bLangPackage.getCompilationUnits().stream() + .filter(cUnit -> (fileUri.endsWith(cUnit.getName()))) + .findFirst(); + + CompilerContext compilerContext = codeLensContext.get(DocumentServiceKeys.COMPILER_CONTEXT_KEY); + final List diagnostics = new ArrayList<>(); + if (compilerContext.get(DiagnosticListener.class) instanceof CollectDiagnosticListener) { + CollectDiagnosticListener listener = + (CollectDiagnosticListener) compilerContext.get(DiagnosticListener.class); + diagnostics.addAll(listener.getDiagnostics()); + listener.clearAll(); + } + + codeLensContext.put(CodeLensesProviderKeys.BLANG_PACKAGE_KEY, bLangPackage); + codeLensContext.put(CodeLensesProviderKeys.FILE_URI_KEY, fileUri); + codeLensContext.put(CodeLensesProviderKeys.DIAGNOSTIC_KEY, diagnostics); + + documentCUnit.ifPresent(cUnit -> { + codeLensContext.put(CodeLensesProviderKeys.COMPILATION_UNIT_KEY, cUnit); + + List providers = LSCodeLensesProviderFactory.getInstance().getProviders(); + for (LSCodeLensesProvider provider : providers) { + try { + lenses.addAll(provider.getLenses(codeLensContext)); + } catch (LSCodeLensesProviderException e) { + LOGGER.error("Error while retrieving lenses from: " + provider.getName()); + } + } + }); + return lenses; + } catch (Exception e) { + if (CommonUtil.LS_DEBUG_ENABLED) { + String msg = e.getMessage(); + LOGGER.error("Error while retrieving code lenses " + ((msg != null) ? ": " + msg : ""), e); + } + return lenses; + } finally { + lock.ifPresent(Lock::unlock); + } + }); } @Override @@ -498,7 +562,7 @@ public CompletableFuture> formatting(DocumentFormatting } catch (Exception e) { if (CommonUtil.LS_DEBUG_ENABLED) { String msg = e.getMessage(); - logger.error("Error while formatting" + ((msg != null) ? ": " + msg : ""), e); + LOGGER.error("Error while formatting" + ((msg != null) ? ": " + msg : ""), e); } return Collections.singletonList(textEdit); } finally { @@ -573,7 +637,7 @@ public CompletableFuture rename(RenameParams params) { } catch (Exception e) { if (CommonUtil.LS_DEBUG_ENABLED) { String msg = e.getMessage(); - logger.error("Error while renaming" + ((msg != null) ? ": " + msg : ""), e); + LOGGER.error("Error while renaming" + ((msg != null) ? ": " + msg : ""), e); } return workspaceEdit; } finally { @@ -604,7 +668,7 @@ public CompletableFuture> implementation(TextDocumentPo } catch (LSCompilerException e) { if (CommonUtil.LS_DEBUG_ENABLED) { String msg = e.getMessage(); - logger.error("Error while go to implementation" + ((msg != null) ? ": " + msg : ""), e); + LOGGER.error("Error while go to implementation" + ((msg != null) ? ": " + msg : ""), e); } } finally { lock.ifPresent(Lock::unlock); @@ -626,7 +690,7 @@ public void didOpen(DidOpenTextDocumentParams params) { LanguageClient client = this.ballerinaLanguageServer.getClient(); diagnosticsHelper.compileAndSendDiagnostics(client, lsCompiler, openedPath, compilationPath); } catch (WorkspaceDocumentException e) { - logger.error("Error while opening file:" + openedPath.toString()); + LOGGER.error("Error while opening file:" + openedPath.toString()); } finally { lock.ifPresent(Lock::unlock); } @@ -647,7 +711,7 @@ public void didChange(DidChangeTextDocumentParams params) { diagnosticsHelper.compileAndSendDiagnostics(client, lsCompiler, changedPath, compilationPath); }); } catch (WorkspaceDocumentException e) { - logger.error("Error while updating change in file:" + changedPath.toString(), e); + LOGGER.error("Error while updating change in file:" + changedPath.toString(), e); } finally { lock.ifPresent(Lock::unlock); } @@ -666,7 +730,7 @@ public void didClose(DidCloseTextDocumentParams params) { Path compilationPath = getUntitledFilePath(closedPath.toString()).orElse(closedPath); this.documentManager.closeFile(compilationPath); } catch (WorkspaceDocumentException e) { - logger.error("Error occurred while closing file:" + closedPath.toString(), e); + LOGGER.error("Error occurred while closing file:" + closedPath.toString(), e); } } diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/BallerinaWorkspaceService.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/BallerinaWorkspaceService.java index 76c793262fe5..c38769fd7ad6 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/BallerinaWorkspaceService.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/BallerinaWorkspaceService.java @@ -15,6 +15,10 @@ */ package org.ballerinalang.langserver; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import org.ballerinalang.langserver.client.config.BallerinaClientConfig; +import org.ballerinalang.langserver.client.config.BallerinaClientConfigHolder; import org.ballerinalang.langserver.command.ExecuteCommandKeys; import org.ballerinalang.langserver.command.LSCommandExecutor; import org.ballerinalang.langserver.command.LSCommandExecutorProvider; @@ -58,6 +62,8 @@ public class BallerinaWorkspaceService implements WorkspaceService { private DiagnosticsHelper diagnosticsHelper; private LSCompiler lsCompiler; private Map experimentalClientCapabilities; + private static final Gson GSON = new Gson(); + private BallerinaClientConfigHolder configHolder = BallerinaClientConfigHolder.getInstance(); BallerinaWorkspaceService(LSGlobalContext globalContext) { this.ballerinaLanguageServer = globalContext.get(LSGlobalContextKeys.LANGUAGE_SERVER_KEY); @@ -109,7 +115,10 @@ private String generateHash(BLangCompilationUnit compUnit, String basePath) { @Override public void didChangeConfiguration(DidChangeConfigurationParams params) { - // Operation not supported + if (!(params.getSettings() instanceof JsonObject)) { + return; + } + configHolder.updateConfig(GSON.fromJson((JsonObject) params.getSettings(), BallerinaClientConfig.class)); } @Override diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/client/config/BallerinaClientConfig.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/client/config/BallerinaClientConfig.java new file mode 100644 index 000000000000..e4da0f310a9e --- /dev/null +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/client/config/BallerinaClientConfig.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://wso2.com) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ballerinalang.langserver.client.config; + +/** + * Ballerina Client Configuration. + */ +public class BallerinaClientConfig { + private final String home; + private final boolean allowExperimental; + private final boolean debugLog; + private final CodeLensConfig codeLens; + private final boolean showLSErrors; + + private BallerinaClientConfig() { + this.home = ""; + this.allowExperimental = false; + this.debugLog = false; + this.codeLens = new CodeLensConfig(); + this.showLSErrors = false; + } + + /** + * Returns default ballerina client configuration. + * + * @return {@link BallerinaClientConfig} + */ + public static BallerinaClientConfig getDefault() { + return new BallerinaClientConfig(); + } + + /** + * Returns home. + * + * @return home + */ + public String getHome() { + return home; + } + + /** + * Returns True if allow experimental enabled, False otherwise. + * + * @return True if enabled, False otherwise + */ + public boolean isAllowExperimental() { + return allowExperimental; + } + + /** + * Returns True if allow debug log enabled, False otherwise. + * + * @return True if enabled, False otherwise + */ + public boolean isDebugLog() { + return debugLog; + } + + /** + * Returns Code Lens Configs. + * + * @return {@link CodeLensConfig} + */ + public CodeLensConfig getCodeLens() { + return codeLens; + } + + /** + * Returns True if show LS errors enabled, False otherwise. + * + * @return True if enabled, False otherwise + */ + public boolean isShowLSErrors() { + return showLSErrors; + } +} diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/client/config/BallerinaClientConfigHolder.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/client/config/BallerinaClientConfigHolder.java new file mode 100644 index 000000000000..8fe30be83d05 --- /dev/null +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/client/config/BallerinaClientConfigHolder.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://wso2.com) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ballerinalang.langserver.client.config; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class is holding the latest client configuration. + */ +public class BallerinaClientConfigHolder { + private static final BallerinaClientConfigHolder INSTANCE = new BallerinaClientConfigHolder(); + + private List listeners = new ArrayList<>(); + + // Init ballerina client configuration with defaults + private BallerinaClientConfig clientConfig = BallerinaClientConfig.getDefault(); + + private BallerinaClientConfigHolder() { + } + + public static BallerinaClientConfigHolder getInstance() { + return INSTANCE; + } + + /** + * Returns current client configuration. + * + * @return {@link BallerinaClientConfig} + */ + public BallerinaClientConfig getConfig() { + return this.clientConfig; + } + + /** + * Register config listener. + * + * @param listener Config change listener to register + */ + public void register(ConfigChangeListener listener) { + listeners.add(listener); + } + + /** + * Unregister config listener. + * + * @param listener Config change listener to unregister + */ + public void unregister(ConfigChangeListener listener) { + listeners.remove(listener); + } + + /** + * Update current client configuration. + * + * @param newConfig {@link BallerinaClientConfig} new configuration + */ + public void updateConfig(BallerinaClientConfig newConfig) { + // Update config + BallerinaClientConfig oldConfig = clientConfig; + clientConfig = newConfig; + // Notify listeners + listeners.stream().parallel().forEach(listener -> listener.didChange(oldConfig, newConfig)); + } +} diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/client/config/CodeLensConfig.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/client/config/CodeLensConfig.java new file mode 100644 index 000000000000..eaf3b5831f67 --- /dev/null +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/client/config/CodeLensConfig.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://wso2.com) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ballerinalang.langserver.client.config; + +/** + * Ballerina CodeLens Configuration. + */ +public class CodeLensConfig { + private final Enabled all; + private final Enabled docs; + private final Enabled services; + private final Enabled endpoints; + + CodeLensConfig() { + this.all = new Enabled(true); + this.docs = new Enabled(true); + this.services = new Enabled(true); + this.endpoints = new Enabled(true); + } + + /** + * Returns true if `all` is Enabled, False otherwise. + * + * @return True or False + */ + public Enabled getAll() { + return all; + } + + /** + * Returns true if `all` is Enabled and `docs` is Enabled, False otherwise. + * + * @return True or False + */ + public Enabled getDocs() { + return (all.isEnabled()) ? docs : all; + } + + /** + * Returns true if `all` is Enabled and `services` is Enabled, False otherwise. + * + * @return True or False + */ + public Enabled getServices() { + return (all.isEnabled()) ? services : all; + } + + /** + * Returns true if `all` is Enabled and `endpoints` is Enabled, False otherwise. + * + * @return True or False + */ + public Enabled getEndpoints() { + return (all.isEnabled()) ? endpoints : all; + } + + /** + * Represents a boolean holder to facilitate client configs. + */ + public static class Enabled { + private final boolean enabled; + + public Enabled(boolean enabled) { + this.enabled = enabled; + } + + /** + * Returns True if enabled, False otherwise. + * + * @return True or False + */ + public boolean isEnabled() { + return enabled; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Enabled)) { + return false; + } + Enabled cc = (Enabled) obj; + return cc.enabled == enabled; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + ((enabled) ? 1 : 0); + return result; + } + } +} diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/client/config/ConfigChangeListener.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/client/config/ConfigChangeListener.java new file mode 100644 index 000000000000..a0bcd68db47f --- /dev/null +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/client/config/ConfigChangeListener.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://wso2.com) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ballerinalang.langserver.client.config; + +/** + * Represents a ballerina client config change listener. + */ +public interface ConfigChangeListener { + /** + * Callback method for configuration changes. + * + * @param oldConfig old configuration + * @param newConfig new configuration + */ + void didChange(BallerinaClientConfig oldConfig, BallerinaClientConfig newConfig); +} diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/CodeLensesProviderKeys.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/CodeLensesProviderKeys.java new file mode 100644 index 000000000000..0bb85c11fb49 --- /dev/null +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/CodeLensesProviderKeys.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://wso2.com) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ballerinalang.langserver.codelenses; + +import org.ballerinalang.langserver.compiler.LSContext; +import org.ballerinalang.util.diagnostic.Diagnostic; +import org.wso2.ballerinalang.compiler.tree.BLangCompilationUnit; +import org.wso2.ballerinalang.compiler.tree.BLangPackage; + +import java.util.List; + +/** + * Keys associated to execute code lenses operations. + * + * @since 0.990.3 + */ +public class CodeLensesProviderKeys { + + public static final LSContext.Key COMPILATION_UNIT_KEY = new LSContext.Key<>(); + public static final LSContext.Key BLANG_PACKAGE_KEY = new LSContext.Key<>(); + public static final LSContext.Key FILE_URI_KEY = new LSContext.Key<>(); + public static final LSContext.Key> DIAGNOSTIC_KEY = new LSContext.Key<>(); + + private CodeLensesProviderKeys() { + } +} diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/EndpointFindVisitor.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/EndpointFindVisitor.java new file mode 100644 index 000000000000..e6a4cb906dac --- /dev/null +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/EndpointFindVisitor.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://wso2.com) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ballerinalang.langserver.codelenses; + +import org.ballerinalang.langserver.common.LSNodeVisitor; +import org.ballerinalang.langserver.common.utils.CommonUtil; +import org.ballerinalang.model.tree.TopLevelNode; +import org.wso2.ballerinalang.compiler.tree.BLangCompilationUnit; +import org.wso2.ballerinalang.compiler.tree.BLangFunction; +import org.wso2.ballerinalang.compiler.tree.BLangNode; +import org.wso2.ballerinalang.compiler.tree.BLangService; +import org.wso2.ballerinalang.compiler.tree.BLangSimpleVariable; +import org.wso2.ballerinalang.compiler.tree.statements.BLangSimpleVariableDef; +import org.wso2.ballerinalang.compiler.tree.types.BLangObjectTypeNode; +import org.wso2.ballerinalang.util.Flags; + +import java.util.ArrayList; +import java.util.List; + +/** + * Find the endpoints visiting functions and services of a BLangCompilationUnit. + * + * @since 0.990.3 + */ +public class EndpointFindVisitor extends LSNodeVisitor { + + private List endpoints; + + public EndpointFindVisitor() { + this.endpoints = new ArrayList<>(); + } + + public List getEndpoints() { + return endpoints; + } + + @Override + public void visit(BLangCompilationUnit compilationUnit) { + List topLevelNodes = compilationUnit.getTopLevelNodes(); + + topLevelNodes.stream() + .filter(CommonUtil.checkInvalidTypesDefs()) + .forEach(topLevelNode -> acceptNode((BLangNode) topLevelNode)); + } + + @Override + public void visit(BLangSimpleVariable varNode) { + List variables = resolveEndpoints(varNode); + this.endpoints.addAll(variables); + } + + @Override + public void visit(BLangSimpleVariableDef varNode) { + if (varNode.var != null) { + List variables = resolveEndpoints(varNode.var); + this.endpoints.addAll(variables); + } + } + + @Override + public void visit(BLangFunction funcNode) { + if (funcNode.body != null) { + funcNode.body.stmts.forEach(f -> f.accept(this)); + } + } + + @Override + public void visit(BLangService serviceNode) { + ((BLangObjectTypeNode) serviceNode.serviceTypeDefinition.typeNode).getFunctions().stream() + .filter(bLangFunction -> (bLangFunction.symbol.flags & Flags.RESOURCE) == Flags.RESOURCE) + .forEach(this::acceptNode); + } + + private void acceptNode(BLangNode node) { + node.accept(this); + } + + private List resolveEndpoints(BLangSimpleVariable variable) { + List list = new ArrayList<>(); + boolean isClientObj = CommonUtil.isClientObject(variable.symbol); + if (isClientObj) { + list.add(variable); + } + return list; + } +} diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/LSCodeLensesProvider.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/LSCodeLensesProvider.java new file mode 100644 index 000000000000..45a542eb1141 --- /dev/null +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/LSCodeLensesProvider.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://wso2.com) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ballerinalang.langserver.codelenses; + +import org.ballerinalang.langserver.compiler.LSContext; +import org.eclipse.lsp4j.CodeLens; + +import java.util.List; + +/** + * Represents the SPI interface for the Language Server Code Lenses Provider. + * + * @since 0.990.3 + */ +public interface LSCodeLensesProvider { + /** + * Returns name of the code lenses provider. + * + * @return name + */ + String getName(); + + /** + * Execute the Command. + * + * @param context Language Server Context + * @return {@link List} List of code lenses + * @throws LSCodeLensesProviderException exception while executing the code lenses provider + */ + List getLenses(LSContext context) throws LSCodeLensesProviderException; + + /** + * Mark code lenses provider is enabled or not. + * + * @return True when enabled, false otherwise + */ + boolean isEnabled(); +} diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/LSCodeLensesProviderException.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/LSCodeLensesProviderException.java new file mode 100644 index 000000000000..89480c1d77b6 --- /dev/null +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/LSCodeLensesProviderException.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://wso2.com) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ballerinalang.langserver.codelenses; + +/** + * Exception for Language Server Code Lenses. + * + * @since 0.990.3 + */ +public class LSCodeLensesProviderException extends Exception { + public LSCodeLensesProviderException(String message) { + super(message); + } + + public LSCodeLensesProviderException(String message, Throwable cause) { + super(message, cause); + } + + public LSCodeLensesProviderException(Throwable cause) { + super(cause); + } + + public LSCodeLensesProviderException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/LSCodeLensesProviderFactory.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/LSCodeLensesProviderFactory.java new file mode 100644 index 000000000000..a5b43de6a1c6 --- /dev/null +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/LSCodeLensesProviderFactory.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://wso2.com) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ballerinalang.langserver.codelenses; + +import org.ballerinalang.langserver.client.config.BallerinaClientConfigHolder; + +import java.util.ArrayList; +import java.util.List; +import java.util.ServiceLoader; + +/** + * Loads and provides the Code Lenses Providers. + * + * @since 0.990.3 + */ +public class LSCodeLensesProviderFactory { + + private static final LSCodeLensesProviderFactory INSTANCE = new LSCodeLensesProviderFactory(); + + private List providersList = new ArrayList<>(); + + private boolean isEnabled = true; + + private boolean isInitialized = false; + + private LSCodeLensesProviderFactory() { + initiate(); + } + + public static LSCodeLensesProviderFactory getInstance() { + return INSTANCE; + } + + /** + * Returns True if enabled, False otherwise. + * + * @return True if enabled + */ + public boolean isEnabled() { + return isEnabled; + } + + /** + * Initializes the code lenses factory. + */ + public void initiate() { + if (isInitialized) { + return; + } + ServiceLoader providers = ServiceLoader.load(LSCodeLensesProvider.class); + for (LSCodeLensesProvider executor : providers) { + if (executor != null && executor.isEnabled()) { + providersList.add(executor); + } + } + BallerinaClientConfigHolder.getInstance().register((oldConfig, newConfig) -> { + isEnabled = newConfig.getCodeLens().getAll().isEnabled(); + }); + isInitialized = true; + } + + /** + * Get the list of all active providers. + * + * @return {@link List} Providers List + */ + public List getProviders() { + List activeProviders = new ArrayList<>(); + for (LSCodeLensesProvider provider : providersList) { + if (provider != null && provider.isEnabled()) { + activeProviders.add(provider); + } + } + return activeProviders; + } + + /** + * Add a code lens provider. + * + * @param provider code lens provider to register + */ + public void register(LSCodeLensesProvider provider) { + this.providersList.add(provider); + } + + /** + * Remove code lens provider. + * + * @param provider code lens provider to unregister + */ + public void unregister(LSCodeLensesProvider provider) { + this.providersList.remove(provider); + } +} diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/providers/DocsCodeLensesProvider.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/providers/DocsCodeLensesProvider.java new file mode 100644 index 000000000000..82bdd4944ed0 --- /dev/null +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/providers/DocsCodeLensesProvider.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://wso2.com) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ballerinalang.langserver.codelenses.providers; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.ballerinalang.annotation.JavaSPIService; +import org.ballerinalang.langserver.client.config.BallerinaClientConfigHolder; +import org.ballerinalang.langserver.codelenses.CodeLensesProviderKeys; +import org.ballerinalang.langserver.codelenses.LSCodeLensesProvider; +import org.ballerinalang.langserver.codelenses.LSCodeLensesProviderException; +import org.ballerinalang.langserver.command.CommandUtil.CommandArgument; +import org.ballerinalang.langserver.command.docs.DocAttachmentInfo; +import org.ballerinalang.langserver.common.constants.CommandConstants; +import org.ballerinalang.langserver.compiler.LSContext; +import org.ballerinalang.model.elements.Flag; +import org.ballerinalang.model.tree.AnnotatableNode; +import org.ballerinalang.model.tree.TopLevelNode; +import org.eclipse.lsp4j.CodeLens; +import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.wso2.ballerinalang.compiler.tree.BLangCompilationUnit; +import org.wso2.ballerinalang.compiler.tree.BLangFunction; +import org.wso2.ballerinalang.compiler.tree.BLangMarkdownDocumentation; +import org.wso2.ballerinalang.compiler.tree.BLangPackage; +import org.wso2.ballerinalang.compiler.tree.BLangService; +import org.wso2.ballerinalang.compiler.tree.BLangTypeDefinition; +import org.wso2.ballerinalang.compiler.tree.types.BLangObjectTypeNode; +import org.wso2.ballerinalang.compiler.tree.types.BLangRecordTypeNode; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.ballerinalang.langserver.command.docs.DocumentationGenerator.getDocumentationEditForNodeByPosition; + +/** + * Code lenses provider for adding all documentation for top level items. + * + * @since 0.990.3 + */ +@JavaSPIService("org.ballerinalang.langserver.codelenses.LSCodeLensesProvider") +public class DocsCodeLensesProvider implements LSCodeLensesProvider { + private boolean isEnabled = true; + + /** + * {@inheritDoc} + */ + @Override + public String getName() { + return "docs.CodeLenses"; + } + + public DocsCodeLensesProvider() { + BallerinaClientConfigHolder.getInstance().register((oldConfig, newConfig) -> { + isEnabled = newConfig.getCodeLens().getDocs().isEnabled(); + }); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEnabled() { + return isEnabled; + } + + /** + * {@inheritDoc} + */ + @Override + public List getLenses(LSContext context) throws LSCodeLensesProviderException { + List lenses = new ArrayList<>(); + BLangCompilationUnit cUnit = context.get(CodeLensesProviderKeys.COMPILATION_UNIT_KEY); + String fileUri = context.get(CodeLensesProviderKeys.FILE_URI_KEY); + BLangPackage bLangPackage = context.get(CodeLensesProviderKeys.BLANG_PACKAGE_KEY); + for (TopLevelNode topLevelNode : cUnit.getTopLevelNodes()) { + addDocLenses(lenses, fileUri, bLangPackage, topLevelNode); + } + return lenses; + } + + private void addDocLenses(List lenses, String fileUri, BLangPackage bLangPackage, + TopLevelNode topLevelNode) { + Pair node = resolveTopLevelType(topLevelNode); + String nodeType = node.getLeft(); + String nodeName = node.getRight(); + DocAttachmentInfo info = getDocumentationEditForNodeByPosition( + nodeType, bLangPackage, topLevelNode.getPosition().getStartLine() - 1); + boolean isDocumentable = (info != null); + Class aTopLeveNodeClass = topLevelNode.getClass(); + if (topLevelNode instanceof AnnotatableNode && + (((AnnotatableNode) topLevelNode).getFlags() == null || + !((AnnotatableNode) topLevelNode).getFlags().contains(Flag.PUBLIC))) { + // If NOT Public; skip + return; + } + int line = topLevelNode.getPosition().getStartLine() - 1; + int col = topLevelNode.getPosition().getStartColumn(); + boolean hasDocumentation = false; + for (Field field : aTopLeveNodeClass.getFields()) { + try { + if (field.getName().equals("markdownDocumentationAttachment") + && field.get(topLevelNode) != null + && field.get(topLevelNode) instanceof BLangMarkdownDocumentation) { + BLangMarkdownDocumentation docs = (BLangMarkdownDocumentation) field.get(topLevelNode); + line = docs.getPosition().getStartLine() - 1; + col = docs.getPosition().getStartColumn(); + hasDocumentation = true; + } + } catch (IllegalAccessException e) { + // ignore + } + } + if (isDocumentable) { + Command command; + if (hasDocumentation) { + CommandArgument nameArg = new CommandArgument(CommandConstants.ARG_KEY_FUNCTION_NAME, nodeName); + List args = new ArrayList<>(Collections.singletonList(nameArg)); + command = new Command("Preview Docs", "ballerina.showDocs", args); + } else { + CommandArgument nodeTypeArg = new CommandArgument(CommandConstants.ARG_KEY_NODE_TYPE, nodeType); + CommandArgument docUriArg = new CommandArgument(CommandConstants.ARG_KEY_DOC_URI, fileUri); + CommandArgument lineStart = new CommandArgument(CommandConstants.ARG_KEY_NODE_LINE, + String.valueOf(line)); + List args = new ArrayList<>(Arrays.asList(nodeTypeArg, docUriArg, lineStart)); + command = new Command(CommandConstants.ADD_DOCUMENTATION_TITLE, + CommandConstants.CMD_ADD_DOCUMENTATION, args); + } + Position pos = new Position(line, col); + CodeLens lens = new CodeLens(new Range(pos, pos), command, null); + lenses.add(lens); + } + } + + private Pair resolveTopLevelType(TopLevelNode topLevelNode) { + if (topLevelNode instanceof BLangTypeDefinition) { + BLangTypeDefinition definition = (BLangTypeDefinition) topLevelNode; + if (definition.typeNode instanceof BLangObjectTypeNode) { + if (!((BLangObjectTypeNode) definition.typeNode).flagSet.contains(Flag.SERVICE)) { + return new ImmutablePair<>("object", definition.name.value); + } + } else if (definition.typeNode instanceof BLangRecordTypeNode) { + return new ImmutablePair<>("record", definition.name.value); + } + } else if (topLevelNode instanceof BLangFunction) { + BLangFunction func = (BLangFunction) topLevelNode; + return new ImmutablePair<>("function", func.name.value); + } else if (topLevelNode instanceof BLangService) { + BLangService service = (BLangService) topLevelNode; + return new ImmutablePair<>("service", service.name.value); + } + return new ImmutablePair<>("", ""); + } +} diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/providers/EndpointsCodeLensesProvider.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/providers/EndpointsCodeLensesProvider.java new file mode 100644 index 000000000000..df97c38f3ffd --- /dev/null +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/providers/EndpointsCodeLensesProvider.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://wso2.com) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ballerinalang.langserver.codelenses.providers; + +import org.ballerinalang.annotation.JavaSPIService; +import org.ballerinalang.langserver.client.config.BallerinaClientConfigHolder; +import org.ballerinalang.langserver.codelenses.CodeLensesProviderKeys; +import org.ballerinalang.langserver.codelenses.EndpointFindVisitor; +import org.ballerinalang.langserver.codelenses.LSCodeLensesProvider; +import org.ballerinalang.langserver.codelenses.LSCodeLensesProviderException; +import org.ballerinalang.langserver.compiler.LSContext; +import org.eclipse.lsp4j.CodeLens; +import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.wso2.ballerinalang.compiler.tree.BLangCompilationUnit; +import org.wso2.ballerinalang.compiler.tree.BLangNode; +import org.wso2.ballerinalang.compiler.tree.BLangPackage; + +import java.util.ArrayList; +import java.util.List; + +/** + * Code lenses provider for adding code lenses for all services. + * + * @since 0.990.3 + */ +@JavaSPIService("org.ballerinalang.langserver.codelenses.LSCodeLensesProvider") +public class EndpointsCodeLensesProvider implements LSCodeLensesProvider { + private boolean isEnabled = true; + + /** + * {@inheritDoc} + */ + @Override + public String getName() { + return "endpoints.CodeLenses"; + } + + public EndpointsCodeLensesProvider() { + BallerinaClientConfigHolder.getInstance().register((oldConfig, newConfig) -> { + isEnabled = newConfig.getCodeLens().getEndpoints().isEnabled(); + }); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEnabled() { + return isEnabled; + } + + /** + * {@inheritDoc} + */ + @Override + public List getLenses(LSContext context) throws LSCodeLensesProviderException { + List lenses = new ArrayList<>(); + BLangCompilationUnit cUnit = context.get(CodeLensesProviderKeys.COMPILATION_UNIT_KEY); + addEndpointLenses(lenses, cUnit, context); + return lenses; + } + + private void addEndpointLenses(List lenses, BLangCompilationUnit cUnit, LSContext context) { + EndpointFindVisitor symbolFindVisitor = new EndpointFindVisitor(); + BLangPackage bLangPackage = context.get(CodeLensesProviderKeys.BLANG_PACKAGE_KEY); + if (bLangPackage != null) { + symbolFindVisitor.visit(cUnit); + List endpoints = symbolFindVisitor.getEndpoints(); + for (BLangNode node : endpoints) { + Command endpointCmd = new Command("Endpoint", null); + Position pos = new Position(node.pos.sLine - 1, node.pos.sCol); + CodeLens endpointLens = new CodeLens(new Range(pos, pos), endpointCmd, null); + lenses.add(endpointLens); + } + } + } +} diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/providers/ServicesBasedCodeLensesProvider.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/providers/ServicesBasedCodeLensesProvider.java new file mode 100644 index 000000000000..3ec98bc3ca93 --- /dev/null +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/codelenses/providers/ServicesBasedCodeLensesProvider.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://wso2.com) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ballerinalang.langserver.codelenses.providers; + +import org.ballerinalang.annotation.JavaSPIService; +import org.ballerinalang.langserver.client.config.BallerinaClientConfigHolder; +import org.ballerinalang.langserver.codelenses.CodeLensesProviderKeys; +import org.ballerinalang.langserver.codelenses.LSCodeLensesProvider; +import org.ballerinalang.langserver.codelenses.LSCodeLensesProviderException; +import org.ballerinalang.langserver.command.CommandUtil; +import org.ballerinalang.langserver.common.constants.CommandConstants; +import org.ballerinalang.langserver.compiler.LSContext; +import org.ballerinalang.model.tree.TopLevelNode; +import org.eclipse.lsp4j.CodeLens; +import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.wso2.ballerinalang.compiler.tree.BLangCompilationUnit; +import org.wso2.ballerinalang.compiler.tree.BLangService; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Code lenses provider for adding code lenses for all services. + * + * @since 0.990.3 + */ +@JavaSPIService("org.ballerinalang.langserver.codelenses.LSCodeLensesProvider") +public class ServicesBasedCodeLensesProvider implements LSCodeLensesProvider { + private boolean isEnabled = true; + + /** + * {@inheritDoc} + */ + @Override + public String getName() { + return "services.CodeLenses"; + } + + public ServicesBasedCodeLensesProvider() { + BallerinaClientConfigHolder.getInstance().register((oldConfig, newConfig) -> { + isEnabled = newConfig.getCodeLens().getServices().isEnabled(); + }); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEnabled() { + return isEnabled; + } + + /** + * {@inheritDoc} + */ + @Override + public List getLenses(LSContext context) throws LSCodeLensesProviderException { + List lenses = new ArrayList<>(); + BLangCompilationUnit cUnit = context.get(CodeLensesProviderKeys.COMPILATION_UNIT_KEY); + for (TopLevelNode topLevelNode : cUnit.getTopLevelNodes()) { + addServiceLenses(lenses, topLevelNode); + } + return lenses; + } + + private void addServiceLenses(List lenses, TopLevelNode topLevelNode) { + if (topLevelNode instanceof BLangService) { + BLangService service = (BLangService) topLevelNode; + String owner = (service.listenerType != null) ? service.listenerType.tsymbol.owner.name.value : null; + String serviceTypeName = (service.listenerType != null) ? service.listenerType.tsymbol.name.value : null; + if (!("http".equals(owner) && "Listener".equals(serviceTypeName))) { + // Skip non-HTTP services + return; + } + int line = service.getPosition().getStartLine() - 1; + int col = service.getPosition().getStartColumn(); + Position pos = new Position(line, col); + // Show API Editor + CommandUtil.CommandArgument serviceNameArg = new CommandUtil.CommandArgument( + CommandConstants.ARG_KEY_SERVICE_NAME, service.name.value); + List args = new ArrayList<>(Collections.singletonList(serviceNameArg)); + Command showApiEditor = new Command("Show API Design", "ballerina.showAPIEditor", args); + CodeLens apiEditorLens = new CodeLens(new Range(pos, pos), showApiEditor, null); + lenses.add(apiEditorLens); + } + } +} diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/CommandUtil.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/CommandUtil.java index 58510418e716..0dbdddf4d04b 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/CommandUtil.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/CommandUtil.java @@ -391,7 +391,7 @@ public static class CommandArgument { private String argumentV; - CommandArgument(String argumentK, String argumentV) { + public CommandArgument(String argumentK, String argumentV) { this.argumentK = argumentK; this.argumentV = argumentV; } diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/common/constants/CommandConstants.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/common/constants/CommandConstants.java index f2b3e70d939b..03d23d1d1266 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/common/constants/CommandConstants.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/common/constants/CommandConstants.java @@ -34,17 +34,9 @@ public class CommandConstants { public static final String ARG_KEY_MODULE_NAME = "module"; - public static final String ARG_KEY_FUNC_NAME = "function.name"; + public static final String ARG_KEY_SERVICE_NAME = "service.name"; - public static final String ARG_KEY_FUNC_LOCATION = "function.location"; - - public static final String ARG_KEY_RETURN_TYPE = "function.returns"; - - public static final String ARG_KEY_VAR_NAME = "var.name"; - - public static final String ARG_KEY_RETURN_DEFAULT_VAL = "function.returns.default"; - - public static final String ARG_KEY_FUNC_ARGS = "function.arguments"; + public static final String ARG_KEY_FUNCTION_NAME = "function.name"; public static final String ARG_KEY_NODE_TYPE = "node.type"; diff --git a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codelenses/CodeLensTest.java b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codelenses/CodeLensTest.java new file mode 100755 index 000000000000..4924cb70f6a6 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/codelenses/CodeLensTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.ballerinalang.langserver.codelenses; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonParser; +import org.ballerinalang.langserver.compiler.LSContextManager; +import org.ballerinalang.langserver.util.FileUtils; +import org.ballerinalang.langserver.util.TestUtil; +import org.eclipse.lsp4j.CodeLens; +import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.jsonrpc.Endpoint; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +/** + * Test code lens feature in language server. + */ +public class CodeLensTest { + private Path servicesBalPath = FileUtils.RES_DIR.resolve("codelens").resolve("services.bal"); + private Path functionsBalPath = FileUtils.RES_DIR.resolve("codelens").resolve("functions.bal"); + private Endpoint serviceEndpoint; + private static final JsonParser JSON_PARSER = new JsonParser(); + private static final Logger log = LoggerFactory.getLogger(CodeLensTest.class); + private static final Gson GSON = new Gson(); + + @BeforeClass + public void loadLangServer() throws IOException { + serviceEndpoint = TestUtil.initializeLanguageSever(); + TestUtil.openDocument(serviceEndpoint, servicesBalPath); + TestUtil.openDocument(serviceEndpoint, functionsBalPath); + } + + @BeforeMethod + public void clearPackageCache() { + LSContextManager.getInstance().clearAllContexts(); + } + + @Test(description = "Test Code Lenses for functions", dataProvider = "codeLensFunctionPositions") + public void codeLensForBuiltInFunctionTest(String expectedConfigName) throws IOException { + String response = TestUtil.getCodeLensesResponse(functionsBalPath.toString(), serviceEndpoint); + testCodeLenses(expectedConfigName, response); + } + + @Test(description = "Test Code Lenses for services", dataProvider = "codeLensServicesPositions") + public void codeLensForCurrentPackageFunctionTest(String expectedConfigName) throws IOException { + String response = TestUtil.getCodeLensesResponse(servicesBalPath.toString(), serviceEndpoint); + testCodeLenses(expectedConfigName, response); + } + + private void testCodeLenses(String expectedConfigName, String response) throws IOException { + String expected = getExpectedValue(expectedConfigName); + + List expectedItemList = getFlattenItemList( + JSON_PARSER.parse(expected).getAsJsonObject().getAsJsonArray("result")); + List responseItemList = getFlattenItemList( + JSON_PARSER.parse(response).getAsJsonObject().getAsJsonArray("result")); + + boolean isSubList = getStringListForEvaluation(responseItemList).containsAll( + getStringListForEvaluation(expectedItemList)); + Assert.assertTrue(isSubList, "Did not match the codelens content for " + expectedConfigName); + } + + @AfterClass + public void shutDownLanguageServer() { + TestUtil.closeDocument(this.serviceEndpoint, servicesBalPath); + TestUtil.closeDocument(this.serviceEndpoint, functionsBalPath); + TestUtil.shutdownLanguageServer(this.serviceEndpoint); + } + + @DataProvider(name = "codeLensFunctionPositions") + public Object[][] getCodeLensFunctionPositions() { + log.info("Test textDocument/codeLens for functions"); + return new Object[][]{ + {"functions.json"}, + }; + } + + @DataProvider(name = "codeLensServicesPositions") + public Object[][] getCodeLensServicesPositions() { + log.info("Test textDocument/codeLens for services"); + return new Object[][]{ + {"services.json"} + }; + } + + /** + * Get the expected value from the expected file. + * + * @param expectedFile json file which contains expected content. + * @return string content read from the json file. + */ + private String getExpectedValue(String expectedFile) throws IOException { + Path expectedFilePath = FileUtils.RES_DIR.resolve("codelens").resolve("expected").resolve(expectedFile); + byte[] expectedByte = Files.readAllBytes(expectedFilePath); + return new String(expectedByte); + } + + private static List getFlattenItemList(JsonArray expectedItems) { + List flattenList = new ArrayList<>(); + expectedItems.forEach(jsonElement -> { + CodeLens codeLens = GSON.fromJson(jsonElement, CodeLens.class); + flattenList.add(codeLens); + }); + + return flattenList; + } + + private static String getCompletionItemPropertyString(CodeLens completionItem) { + String additionalTextEdits = ""; + if (completionItem.getRange() != null) { + additionalTextEdits = "," + GSON.toJson(completionItem.getRange()); + } + + Command command = completionItem.getCommand(); + if (command != null) { + additionalTextEdits = "," + command.getTitle() + ", " + command.getCommand(); + } + + return ("{" + additionalTextEdits + "}"); + } + + private static List getStringListForEvaluation(List completionItems) { + List evalList = new ArrayList<>(); + completionItems.forEach(completionItem -> evalList.add(getCompletionItemPropertyString(completionItem))); + return evalList; + } +} diff --git a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/util/TestUtil.java b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/util/TestUtil.java index 3da230fc862f..6dd21ff37670 100644 --- a/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/util/TestUtil.java +++ b/language-server/modules/langserver-core/src/test/java/org/ballerinalang/langserver/util/TestUtil.java @@ -24,6 +24,7 @@ import org.eclipse.lsp4j.ClientCapabilities; import org.eclipse.lsp4j.CodeActionContext; import org.eclipse.lsp4j.CodeActionParams; +import org.eclipse.lsp4j.CodeLensParams; import org.eclipse.lsp4j.CompletionCapabilities; import org.eclipse.lsp4j.CompletionItemCapabilities; import org.eclipse.lsp4j.CompletionParams; @@ -65,6 +66,8 @@ public class TestUtil { private static final String HOVER = "textDocument/hover"; + private static final String CODELENS = "textDocument/codeLens"; + private static final String COMPLETION = "textDocument/completion"; private static final String SIGNATURE_HELP = "textDocument/signatureHelp"; @@ -105,6 +108,20 @@ public static String getHoverResponse(String filePath, Position position, Endpoi return getResponseString(result); } + /** + * Get the textDocument/codeLens response. + * + * @param filePath Path of the Bal file + * @param serviceEndpoint Service Endpoint to Language Server + * @return {@link String} Response as String + */ + public static String getCodeLensesResponse(String filePath, Endpoint serviceEndpoint) { + TextDocumentIdentifier identifier = getTextDocumentIdentifier(filePath); + CodeLensParams codeLensParams = new CodeLensParams(identifier); + CompletableFuture result = serviceEndpoint.request(CODELENS, codeLensParams); + return getResponseString(result); + } + /** * Get the textDocument/completion response. * diff --git a/language-server/modules/langserver-core/src/test/resources/codelens/expected/functions.json b/language-server/modules/langserver-core/src/test/resources/codelens/expected/functions.json new file mode 100644 index 000000000000..b9aebcec76a0 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codelens/expected/functions.json @@ -0,0 +1,216 @@ +{ + "result": [ + { + "range": { + "start": { + "line": 5, + "character": 1 + }, + "end": { + "line": 5, + "character": 1 + } + }, + "command": { + "title": "Endpoint" + } + }, + { + "range": { + "start": { + "line": 44, + "character": 5 + }, + "end": { + "line": 44, + "character": 5 + } + }, + "command": { + "title": "Endpoint" + } + }, + { + "range": { + "start": { + "line": 7, + "character": 1 + }, + "end": { + "line": 7, + "character": 1 + } + }, + "command": { + "title": "Document This", + "command": "ADD_DOC", + "arguments": [ + { + "argumentK": "node.type", + "argumentV": "object" + }, + { + "argumentK": "node.line", + "argumentV": "7" + } + ] + } + }, + { + "range": { + "start": { + "line": 11, + "character": 1 + }, + "end": { + "line": 11, + "character": 1 + } + }, + "command": { + "title": "Document This", + "command": "ADD_DOC", + "arguments": [ + { + "argumentK": "node.type", + "argumentV": "record" + }, + { + "argumentK": "node.line", + "argumentV": "11" + } + ] + } + }, + { + "range": { + "start": { + "line": 15, + "character": 1 + }, + "end": { + "line": 15, + "character": 1 + } + }, + "command": { + "title": "Document This", + "command": "ADD_DOC", + "arguments": [ + { + "argumentK": "node.type", + "argumentV": "function" + }, + { + "argumentK": "node.line", + "argumentV": "15" + } + ] + } + }, + { + "range": { + "start": { + "line": 19, + "character": 1 + }, + "end": { + "line": 19, + "character": 1 + } + }, + "command": { + "title": "Document This", + "command": "ADD_DOC", + "arguments": [ + { + "argumentK": "node.type", + "argumentV": "function" + }, + { + "argumentK": "node.line", + "argumentV": "19" + } + ] + } + }, + { + "range": { + "start": { + "line": 24, + "character": 1 + }, + "end": { + "line": 24, + "character": 1 + } + }, + "command": { + "title": "Document This", + "command": "ADD_DOC", + "arguments": [ + { + "argumentK": "node.type", + "argumentV": "function" + }, + { + "argumentK": "node.line", + "argumentV": "24" + } + ] + } + }, + { + "range": { + "start": { + "line": 28, + "character": 1 + }, + "end": { + "line": 28, + "character": 1 + } + }, + "command": { + "title": "Preview Docs", + "command": "ballerina.showDocs", + "arguments": [ + { + "argumentK": "function.name", + "argumentV": "returnMap" + } + ] + } + }, + { + "range": { + "start": { + "line": 39, + "character": 1 + }, + "end": { + "line": 39, + "character": 1 + } + }, + "command": { + "title": "Document This", + "command": "ADD_DOC", + "arguments": [ + { + "argumentK": "node.type", + "argumentV": "function" + }, + { + "argumentK": "node.line", + "argumentV": "39" + } + ] + } + } + ], + "id": { + "left": "324" + }, + "jsonrpc": "2.0" +} \ No newline at end of file diff --git a/language-server/modules/langserver-core/src/test/resources/codelens/expected/services.json b/language-server/modules/langserver-core/src/test/resources/codelens/expected/services.json new file mode 100644 index 000000000000..fcafd7387c10 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codelens/expected/services.json @@ -0,0 +1,52 @@ +{ + "result": [ + { + "range": { + "start": { + "line": 6, + "character": 1 + }, + "end": { + "line": 6, + "character": 1 + } + }, + "command": { + "title": "Show API Design", + "command": "ballerina.showAPIEditor", + "arguments": [ + { + "argumentK": "service.name", + "argumentV": "httpService" + } + ] + } + }, + { + "range": { + "start": { + "line": 26, + "character": 1 + }, + "end": { + "line": 26, + "character": 1 + } + }, + "command": { + "title": "Show API Design", + "command": "ballerina.showAPIEditor", + "arguments": [ + { + "argumentK": "service.name", + "argumentV": "httpsService" + } + ] + } + } + ], + "id": { + "left": "324" + }, + "jsonrpc": "2.0" +} \ No newline at end of file diff --git a/language-server/modules/langserver-core/src/test/resources/codelens/functions.bal b/language-server/modules/langserver-core/src/test/resources/codelens/functions.bal new file mode 100644 index 000000000000..6c97187bcd7c --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codelens/functions.bal @@ -0,0 +1,47 @@ +import ballerina/http; +import ballerina/task; +import ballerina/io; +import ballerina/websub; + +http:Client client2 = new("https://postman-echo.com/get?foo1=bar1&foo2=bar2"); + +public type ObjectName object { + +}; + +public type RecordName record { + +}; + +public function returnInt(int n1, int n2, int n3) returns int { + return 1; +} + +public function returnTuple(int n1, int n2, int n3) returns (int, float) { + io:println(""); + return (300, 4.0); +} + +public function returnUnion(int n1, int n2, int n3) returns (int|string) { + return 5000; +} + +# Description +# +# + n1 - n1 Parameter Description +# + n2 - n2 Parameter Description +# + n3 - n3 Parameter Description +# + return - Return Value Description +public function returnMap(int n1, int n2, int n3) returns map { + map m = { key: "n1" }; + return m; +} + +public function complexInput(task:Timer timer) returns error? { + return (); +} + +function complexReturnType(string url) returns http:Client { + http:Client myclient = new("https://postman-echo.com/get?foo1=bar1&foo2=bar2"); + return myclient; +} \ No newline at end of file diff --git a/language-server/modules/langserver-core/src/test/resources/codelens/services.bal b/language-server/modules/langserver-core/src/test/resources/codelens/services.bal new file mode 100644 index 000000000000..8b0116512b97 --- /dev/null +++ b/language-server/modules/langserver-core/src/test/resources/codelens/services.bal @@ -0,0 +1,74 @@ +import ballerina/http; +import ballerina/task; +import ballerina/io; +import ballerina/websub; +import ballerina/log; + +service httpService on new http:Listener(9090) { + resource function sayHello(http:Caller caller, http:Request req) { + } +} + +listener http:Listener securedListener = new( + 9092, + config = {secureSocket: { + keyStore: { + path: "${ballerina.home}/bre/security/ballerinaKeystore.p12", + password: "ballerina" + }, + trustStore: { + path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", + password: "ballerina" + } + } + } +); + +service httpsService on securedListener { + resource function sayHello(http:Caller caller, http:Request req) { + } +} + +service wssService on new http:WebSocketListener(9094) { + resource function onOpen(http:WebSocketCaller caller) { + } + resource function onText(http:WebSocketCaller caller, string text, boolean finalFrame) { + io:println("received: " + text); + } + resource function onClose(http:WebSocketCaller caller, int statusCode, string reason) { + } +} + +listener http:WebSocketListener wsEP = new( + 9093, + config = {secureSocket: { + keyStore: { + path: "${ballerina.home}/bre/security/ballerinaKeystore.p12", + password: "ballerina" + }, + trustStore: { + path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", + password: "ballerina" + } + } + } +); + +service wsssService on wsEP { + resource function onOpen(http:WebSocketCaller caller) { + } + resource function onText(http:WebSocketCaller caller, string text, boolean finalFrame) { + io:println("received: " + text); + } + resource function onClose(http:WebSocketCaller caller, int statusCode, string reason) { + } +} + +service websubSubscriber on websubEP { + resource function onIntentVerification(websub:Caller caller, websub:IntentVerificationRequest request) { + } + resource function onNotification(websub:Notification notification) { + } +} + +listener websub:Listener websubEP = new websub:Listener(8181); \ No newline at end of file diff --git a/tool-plugins/vscode/src/api-editor/activator.ts b/tool-plugins/vscode/src/api-editor/activator.ts index aaff4029764d..46244bd5999b 100644 --- a/tool-plugins/vscode/src/api-editor/activator.ts +++ b/tool-plugins/vscode/src/api-editor/activator.ts @@ -42,7 +42,7 @@ function updateOASWebView(docUri: Uri, resp: string, stale: boolean): void { } } -function showAPIEditorPanel(context: ExtensionContext, langClient: ExtendedLangClient) : any { +function showAPIEditorPanel(context: ExtensionContext, langClient: ExtendedLangClient, serviceName: string) : any { workspace.onDidChangeTextDocument( _.debounce((e: TextDocumentChangeEvent) => { @@ -99,7 +99,6 @@ function showAPIEditorPanel(context: ExtensionContext, langClient: ExtendedLangC } }); - let selectedService : string; const editor = window.activeTextEditor; // TODO : proper handler if not the active editor @@ -108,25 +107,29 @@ function showAPIEditorPanel(context: ExtensionContext, langClient: ExtendedLangC } activeEditor = editor; - langClient.getServiceListForActiveFile(activeEditor.document.uri).then(resp => { - if (resp.services.length === 0) { - window.showInformationMessage(API_DESIGNER_NO_SERVICE); - } else if (resp.services && resp.services.length > 1) { - window.showQuickPick(resp.services).then(service => { - if (service && activeEditor ) { - selectedService = service; - let renderHtml = apiEditorRender(context, - langClient, editor.document.uri, service); - createAPIEditorPanel(selectedService, renderHtml, langClient, context); - } - }); - } else { - selectedService = resp.services[0]; - let renderHtml = apiEditorRender(context, - langClient, editor.document.uri, selectedService); - createAPIEditorPanel(selectedService, renderHtml, langClient, context); - } - }); + let executeCreateAPIEditor = function (serviceName: string) { + let renderHtml = apiEditorRender(context, + langClient, editor.document.uri, serviceName); + createAPIEditorPanel(serviceName, renderHtml, langClient, context); + }; + + if (serviceName) { + executeCreateAPIEditor(serviceName); + } else { + langClient.getServiceListForActiveFile(activeEditor.document.uri).then(resp => { + if (resp.services.length === 0) { + window.showInformationMessage(API_DESIGNER_NO_SERVICE); + } else if (resp.services && resp.services.length > 1) { + window.showQuickPick(resp.services).then(service => { + if (service && activeEditor) { + executeCreateAPIEditor(service); + } + }); + } else { + executeCreateAPIEditor(resp.services[0]); + } + }); + } } function createAPIEditorPanel(selectedService: string, renderHtml: string, @@ -184,7 +187,7 @@ function createAPIEditorPanel(selectedService: string, renderHtml: string, export function activate(ballerinaExtInstance: BallerinaExtension) { let context = ballerinaExtInstance.context; let langClient = ballerinaExtInstance.langClient; - const showAPIRenderer = commands.registerCommand('ballerina.showAPIEditor', () => { + const showAPIRenderer = commands.registerCommand('ballerina.showAPIEditor', serviceNameArg => { ballerinaExtInstance.onReady() .then(() => { const { experimental } = langClient.initializeResult!.capabilities; @@ -194,8 +197,7 @@ export function activate(ballerinaExtInstance: BallerinaExtension) { ballerinaExtInstance.showMessageServerMissingCapability(); return; } - - showAPIEditorPanel(context, langClient); + showAPIEditorPanel(context, langClient, (serviceNameArg) ? serviceNameArg.argumentV : ""); }) .catch((e) => { if (!ballerinaExtInstance.isValidBallerinaHome()) { diff --git a/tool-plugins/vscode/src/core/extension.ts b/tool-plugins/vscode/src/core/extension.ts index c3e65273b8f7..fd86ede192a0 100644 --- a/tool-plugins/vscode/src/core/extension.ts +++ b/tool-plugins/vscode/src/core/extension.ts @@ -30,7 +30,7 @@ import { import * as path from 'path'; import * as fs from 'fs'; import { exec, execSync } from 'child_process'; -import { LanguageClientOptions, State as LS_STATE, RevealOutputChannelOn } from "vscode-languageclient"; +import { LanguageClientOptions, State as LS_STATE, RevealOutputChannelOn, DidChangeConfigurationParams } from "vscode-languageclient"; import { getServerOptions } from '../server/server'; import { ExtendedLangClient } from './extended-language-client'; import { log, getOutputChannel } from '../utils/index'; @@ -152,6 +152,12 @@ export class BallerinaExtension { params.affectsConfiguration('ballerina.debugLog')) { this.showMsgAndRestart(CONFIG_CHANGED); } + if (params.affectsConfiguration('ballerina')) { + const args: DidChangeConfigurationParams = { + settings: workspace.getConfiguration('ballerina'), + }; + this.langClient!.sendNotification("workspace/didChangeConfiguration", args); + } }); languages.setLanguageConfiguration('ballerina', { diff --git a/tool-plugins/vscode/src/diagram/activator.ts b/tool-plugins/vscode/src/diagram/activator.ts index 4ae17917b51f..2365b77ef515 100644 --- a/tool-plugins/vscode/src/diagram/activator.ts +++ b/tool-plugins/vscode/src/diagram/activator.ts @@ -23,6 +23,7 @@ import { ExtendedLangClient } from '../core/extended-language-client'; import { BallerinaExtension } from '../core'; import { WebViewRPCHandler } from '../utils'; import { join } from "path"; +import { DidChangeConfigurationParams } from 'vscode-languageclient'; const DEBOUNCE_WAIT = 500; @@ -137,6 +138,10 @@ export function activate(ballerinaExtInstance: BallerinaExtension) { }); ballerinaExtInstance.onReady().then(() => { + const args: DidChangeConfigurationParams = { + settings: workspace.getConfiguration('ballerina'), + }; + langClient.sendNotification("workspace/didChangeConfiguration", args); langClient.onNotification('window/showTextDocument', (location: Location) => { if (location.uri !== undefined) { window.showTextDocument(Uri.parse(location.uri.toString()), {selection: location.range}); diff --git a/tool-plugins/vscode/src/docs/activator.ts b/tool-plugins/vscode/src/docs/activator.ts index e9e0d6d51721..95edad5262de 100644 --- a/tool-plugins/vscode/src/docs/activator.ts +++ b/tool-plugins/vscode/src/docs/activator.ts @@ -40,7 +40,7 @@ function updateWebView(ast: BallerinaAST, docUri: Uri): void { } } -function showDocs(context: ExtensionContext, langClient: ExtendedLangClient): void { +function showDocs(context: ExtensionContext, langClient: ExtendedLangClient, nodeName: string): void { const didChangeDisposable = workspace.onDidChangeTextDocument( _.debounce((e: TextDocumentChangeEvent) => { if (activeEditor && (e.document === activeEditor.document) && @@ -73,6 +73,7 @@ function showDocs(context: ExtensionContext, langClient: ExtendedLangClient): vo if (previewPanel) { previewPanel.reveal(ViewColumn.Two, true); + scrollToTitle(previewPanel, nodeName); return; } // Create and show a new webview @@ -106,6 +107,9 @@ function showDocs(context: ExtensionContext, langClient: ExtendedLangClient): vo .then((resp) => { if (resp.ast) { updateWebView(resp.ast, editor.document.uri); + if (previewPanel) { + scrollToTitle(previewPanel, nodeName); + } } }); }); @@ -120,7 +124,7 @@ function showDocs(context: ExtensionContext, langClient: ExtendedLangClient): vo export function activate(ballerinaExtInstance: BallerinaExtension) { let context = ballerinaExtInstance.context; let langClient = ballerinaExtInstance.langClient; - const docsRenderDisposable = commands.registerCommand('ballerina.showDocs', () => { + const docsRenderDisposable = commands.registerCommand('ballerina.showDocs', nodeNameArg => { return ballerinaExtInstance.onReady() .then(() => { const { experimental } = langClient.initializeResult!.capabilities; @@ -130,7 +134,7 @@ export function activate(ballerinaExtInstance: BallerinaExtension) { ballerinaExtInstance.showMessageServerMissingCapability(); return {}; } - showDocs(context, langClient); + showDocs(context, langClient, (nodeNameArg) ? nodeNameArg.argumentV : ""); }) .catch((e) => { if (!ballerinaExtInstance.isValidBallerinaHome()) { @@ -143,3 +147,12 @@ export function activate(ballerinaExtInstance: BallerinaExtension) { context.subscriptions.push(docsRenderDisposable); } + +function scrollToTitle(previewPanel:WebviewPanel, anchorId: string){ + if (previewPanel && anchorId) { + previewPanel.webview.postMessage({ + command: 'scroll', + anchor: anchorId + }); + } +} diff --git a/tool-plugins/vscode/src/docs/renderer.ts b/tool-plugins/vscode/src/docs/renderer.ts index 05aee17067e2..c8cc262f0651 100644 --- a/tool-plugins/vscode/src/docs/renderer.ts +++ b/tool-plugins/vscode/src/docs/renderer.ts @@ -18,6 +18,10 @@ export function render(context: ExtensionContext, langClient: ExtendedLangClient ballerinaComposer.renderDocPreview(astJson, el); } break; + case 'scroll': + const anchor = event.data.anchor; + location.href = "#" + anchor; + break; } });