diff --git a/integration/schema-language-server/clients/intellij/.gitignore b/integration/schema-language-server/clients/intellij/.gitignore index f272288afa29..8806173ad559 100644 --- a/integration/schema-language-server/clients/intellij/.gitignore +++ b/integration/schema-language-server/clients/intellij/.gitignore @@ -2,6 +2,7 @@ .intellijPlatform/ .run/ build/ +bin/ gradle/ gradle.properties gradlew diff --git a/integration/schema-language-server/clients/vscode/.vscode/tasks.json b/integration/schema-language-server/clients/vscode/.vscode/tasks.json index 790a4b890939..ad585d177264 100644 --- a/integration/schema-language-server/clients/vscode/.vscode/tasks.json +++ b/integration/schema-language-server/clients/vscode/.vscode/tasks.json @@ -20,7 +20,7 @@ { "label": "langserver", "type": "npm", - "script": "langserver-install", + "script": "vscode:prepublish", "group": { "kind": "build" }, diff --git a/integration/schema-language-server/clients/vscode/README.md b/integration/schema-language-server/clients/vscode/README.md index 70f9ee97d3c9..47983f3ea6e6 100644 --- a/integration/schema-language-server/clients/vscode/README.md +++ b/integration/schema-language-server/clients/vscode/README.md @@ -22,3 +22,8 @@ The extension requires Java 17 or greater. Upon activation, the extension will l - User setting: vespaSchemaLS.javaHome - JDK_HOME environment variable - JAVA_HOME environment variable + +## XML support +This extension bundles with an extension to the [LemMinX XML Language server](https://github.com/eclipse/lemminx). +This is to provide additional support when editing the services.xml file in Vespa applications. +For the best possible experience, install the [VSCode XML extension](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-xml) as well. diff --git a/integration/schema-language-server/clients/vscode/package.json b/integration/schema-language-server/clients/vscode/package.json index 685cd212bb81..ab32feee1c4a 100644 --- a/integration/schema-language-server/clients/vscode/package.json +++ b/integration/schema-language-server/clients/vscode/package.json @@ -11,18 +11,21 @@ "categories": [ "Programming Languages", "Snippets", - "Other" + "Other" ], "keywords": [ "Vespa", "Schema" ], "repository": { - "type": "git", + "type": "git", "url": "https://github.com/vespa-engine/vespa" }, "icon": "images/icon.png", - "activationEvents": [], + "activationEvents": [ + "onLanguage:xml", + "onLanguage:vespaSchema" + ], "main": "./dist/extension.js", "contributes": { "languages": [ @@ -33,11 +36,15 @@ ], "extensions": [ ".sd", - ".profile" + ".profile", + ".yql" ], "configuration": "./language-configuration.json" } ], + "xml.javaExtensions": [ + "./server/lemminx-vespa-jar-with-dependencies.jar" + ], "configuration": { "title": "Vespa Schema Language Support", "properties": { @@ -65,7 +72,8 @@ "test": "vscode-test", "check-types": "tsc --noEmit", "copy-images": "cp ../../resources/* ./images", - "langserver-install": "mkdir -p server && cp ../../language-server/target/schema-language-server-jar-with-dependencies.jar ./server/", + "langserver-install": "mkdir -p server && cp ../../language-server/target/schema-language-server-jar-with-dependencies.jar ./server/ && npm run lemminx-install", + "lemminx-install": "mkdir -p server && cp ../../lemminx-vespa/target/lemminx-vespa-jar-with-dependencies.jar ./server/", "publish": "npm run compile && node out/publish.js" }, "devDependencies": { diff --git a/integration/schema-language-server/clients/vscode/src/extension.ts b/integration/schema-language-server/clients/vscode/src/extension.ts index 3ea3315dada0..78b797e05ce7 100644 --- a/integration/schema-language-server/clients/vscode/src/extension.ts +++ b/integration/schema-language-server/clients/vscode/src/extension.ts @@ -3,14 +3,17 @@ import * as vscode from 'vscode'; import fs from 'fs'; import hasbin from 'hasbin'; -import { LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node'; +import { ExecuteCommandParams, ExecuteCommandRequest, LanguageClient, LanguageClientOptions, ProtocolRequestType, RequestType, ServerOptions, ExecuteCommandRegistrationOptions } from 'vscode-languageclient/node'; -let client: LanguageClient | null = null; +let schemaClient: LanguageClient | null = null; -const JAVA_HOME_SETTING = 'vespaSchemaLS.javaHome'; +const EXTENSION_NAME = 'vespaSchemaLS'; +const JAVA_HOME_SETTING = 'javaHome'; +const RECOMMEND_XML_SETTING = 'recommendXML'; // update if something changes const JAVA_DOWNLOAD_URL = 'https://www.oracle.com/java/technologies/downloads/#java17'; + type maybeString = string|null|undefined; function javaExecutableExists(javaHome: maybeString) { @@ -22,7 +25,7 @@ function javaExecutableExists(javaHome: maybeString) { function findJavaHomeExecutable(): maybeString { // Try workspace setting first - const config = vscode.workspace.getConfiguration(); + const config = vscode.workspace.getConfiguration(EXTENSION_NAME); const javaHome = config.get(JAVA_HOME_SETTING) as maybeString; if (javaExecutableExists(javaHome)) { return path.join(javaHome as string, 'bin', 'java'); @@ -92,7 +95,10 @@ function createAndStartClient(serverPath: string): LanguageClient | null { } }; const client = new LanguageClient('vespaSchemaLS', 'Vespa Schema Language Server', serverOptions, clientOptions); - client.start(); + + client.start().then(result => { + console.log(result); + }); return client; } @@ -118,25 +124,79 @@ function showJavaErrorMessage() { } export function activate(context: vscode.ExtensionContext) { + + checkForXMLExtension(); + const jarPath = path.join(__dirname, '..', 'server', 'schema-language-server-jar-with-dependencies.jar'); - client = createAndStartClient(jarPath); + schemaClient = createAndStartClient(jarPath); + + const logger = vscode.window.createOutputChannel("Vespa language client", {log: true}); context.subscriptions.push(vscode.commands.registerCommand("vespaSchemaLS.restart", (() => { - if (client === null) { - client = createAndStartClient(jarPath); - } else if (client.isRunning()) { - client.restart(); + if (schemaClient === null) { + schemaClient = createAndStartClient(jarPath); + } else if (schemaClient.isRunning()) { + schemaClient.restart(); } else { - client.start(); + schemaClient.start(); } }))); + + + context.subscriptions.push(vscode.commands.registerCommand("vespaSchemaLS.servicesxml.findDocument", async (fileName) => { + if (schemaClient !== null) { + try { + const result = await schemaClient.sendRequest("workspace/executeCommand", { + command: "FIND_SCHEMA_DEFINITION", + arguments: [fileName] + }); + return result; + } catch (err) { + logger.error("Error when sending command: ", err); + } + } + return null; + })); + + // This command exists to setup schema language server workspace in case the first opened document is an xml file (which not handled by schema language server) + context.subscriptions.push(vscode.commands.registerCommand("vespaSchemaLS.servicesxml.setupWorkspace", async (fileURI) => { + if (schemaClient !== null) { + try { + schemaClient.sendRequest("workspace/executeCommand", { + command: "SETUP_WORKSPACE", + arguments: [fileURI] + }); + } catch (err) { + logger.error("Error when trying to send setup workspace command: ", err); + } + } + })); + + logger.info("Vespa language client activated"); } export function deactivate() { - if (!client) { + if (!schemaClient) { return undefined; } - return client.stop(); + return schemaClient.stop(); +} + +async function checkForXMLExtension() { + const xmlExtensionName = "redhat.vscode-xml"; + + const xmlExtension = vscode.extensions.getExtension(xmlExtensionName); + + if (!xmlExtension && vscode.workspace.getConfiguration(EXTENSION_NAME).get(RECOMMEND_XML_SETTING, true)) { + const message = "It is recommended to install the Red Hat XML extension in order to get support when writing the services.xml file. Do you want to install it now?"; + const choice = await vscode.window.showInformationMessage(message, "Install", "Not now", "Do not show again"); + if (choice === "Install") { + await vscode.commands.executeCommand("extension.open", xmlExtensionName); + await vscode.commands.executeCommand("workbench.extensions.installExtension", xmlExtensionName); + } else if (choice === "Do not show again") { + vscode.workspace.getConfiguration(EXTENSION_NAME).set(RECOMMEND_XML_SETTING, false); + } + } } diff --git a/integration/schema-language-server/language-server/pom.xml b/integration/schema-language-server/language-server/pom.xml index 5f293ebc0988..e51416b59154 100644 --- a/integration/schema-language-server/language-server/pom.xml +++ b/integration/schema-language-server/language-server/pom.xml @@ -187,6 +187,17 @@ 17 + + 4 + + ccc-generate + + + ${project.basedir}/src/main/ccc/yqlplus/YQLPlus.ccc + ${project.basedir}/target/generated-sources/ccc + 17 + + @@ -197,7 +208,8 @@ ai.vespa.schemals.parser, ai.vespa.schemals.parser.indexinglanguage, - ai.vespa.schemals.parser.rankingexpression + ai.vespa.schemals.parser.rankingexpression, + ai.vespa.schemals.parser.yqlplus @@ -214,9 +226,6 @@ ${project.basedir}/target/generated-sources/ccc - ${project.parent.basedir}/../config-model/target/generated-sources/javacc - ${project.parent.basedir}/../indexinglanguage/target/generated-sources/javacc - ${project.parent.basedir}/../searchlib/target/generated-sources/javacc diff --git a/integration/schema-language-server/language-server/src/main/ccc/yqlplus/YQLPlus.ccc b/integration/schema-language-server/language-server/src/main/ccc/yqlplus/YQLPlus.ccc new file mode 100644 index 000000000000..d01c92410632 --- /dev/null +++ b/integration/schema-language-server/language-server/src/main/ccc/yqlplus/YQLPlus.ccc @@ -0,0 +1,703 @@ +PARSER_CLASS=YQLPlusParser; +PARSER_PACKAGE=ai.vespa.schemals.parser.yqlplus; +FAULT_TOLERANT=true; +SMART_NODE_CREATION=false; // Will create a tree node for every rule + +INJECT YQLPlusParser: + + import java.util.Deque; + import java.util.ArrayDeque; + import com.yahoo.search.yql.*; +{ + protected Deque expression_stack = new ArrayDeque<>(); +} + +// -------------------------------------------------------------------------------- +// +// Token declarations. +// +// -------------------------------------------------------------------------------- + +SKIP : + " " | "\t" | "\r" | "\n" +; + +TOKEN : + < SELECT: 'select' > + +| < LIMIT: 'limit' > +| < OFFSET: 'offset' > +| < WHERE: 'where' > +| < ORDER: 'order' > +| < BY: 'by' > +| < ORDERBY: > +| < DESC: 'desc' > +| < ASC: 'asc' > +| < FROM: 'from' > +| < SOURCES: 'sources' > +| < AS: 'as' > + +| < COMMA: ',' > +| < OUTPUT: 'output' > +| < COUNT: 'count' > + +| < TRUE: 'true' > +| < FALSE: 'false' > + +// brackets and other tokens in literals +| < LPAREN: '(' > +| < RPAREN: ')' > +| < LBRACKET: '[' > +| < RBRACKET: ']' > +| < LBRACE: '{' > +| < RBRACE: '}' > +| < COLON: ':' > +| < PIPE: '|' > + +// operators +| < AND: 'and' > +| < OR: 'or' > +| < NOT_IN: 'not in' > +| < IN: 'in' > + +| < LT: '<' > +| < GT: '>' > +| < LTEQ: '<=' > +| < GTEQ: '>=' > +| < NEQ: '!=' > +| < STAR: '*' > +| < EQ: '=' > +| < LIKE: 'like' > +| < CONTAINS: 'contains' > +| < NOTLIKE: 'not like' > +| < MATCHES: 'matches' > +| < NOTMATCHES: 'not matches' > + +| < PLUS: '+' > +| < MINUS: '-' > +| < DIV: '/' > +| < MODULO: '%' > +| < EXCLAMATION: '!' > + +// effectively unary operators +| < NULL: 'null' > +| < IS_NULL: 'is' > +| < IS_NOT_NULL: 'is not' > + +// dereference +| < DOT: '.' > +| < AT: '@' > + +// quotes +| < SQ: '\'' > +| < DQ: '"' > + +// statement delimiter +| < SEMI: ';' > + +| < TIMEOUT: 'timeout' > + +| < ALL: 'all' > +| < EACH: 'each' > + +// identifier +| < IDENTIFIER: ["a"-"z","A"-"Z", "_"] (["a"-"z","A"-"Z","0"-"9","_","-"])* > +| < LONG_INT: ("-")?(["0"-"9"])+("L"|"l") > +| < INT: ("-")?(["0"-"9"])+ > +| < FLOAT: ("-")?(((["0"-"9"])+"."(["0"-"9"])* ()?)|("."(["0"-"9"])+ ()?)|((["0"-"9"])+ )) > +| < EXPONENT: ("e"|"E") ("+"|"-")? (["0"-"9"])+ > +//| < DIGIT: ["0"-"9"] > This is apparently never used +| < LETTER: ["a"-"z","A"-"Z"] > + +| < HEX_DIGIT: ["0"-"9","a"-"f","A"-"F"] > +| < UNICODE_ESC: '\\' 'u' > +| < ESC_SEQ: '\\' ('b'|'t'|'n'|'f'|'r'|'"'|'\''|'\\'|'/') | > +| < STRING: ( ( | ~['\\','"'])* ) | ( ( | ~['\\','"'])* ) > + +; + +UNPARSED : + +; + +// -------------------------------------------------------------------------------- +// +// Production rules. +// +// -------------------------------------------------------------------------------- + +String identifierStr: + ( + + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | identifierStr + ) +; + +// TODO: This should match all tokens not containing ()[]<> +vespa_grouping_node: + ( + unary_op + | additive_op + | mult_op + | scalar_literal + | vespa_grouping_identifier + ) +; + +vespa_grouping: + ( + ( | ) vespa_grouping_arg + ( vespa_grouping_arg)? + ( vespa_grouping_arg)? + ) +; + +vespa_grouping_arg: + ( + ( | | ) + ( vespa_grouping_arg | (vespa_grouping_node)+ )* + ( | | ) + ) +; + +String ident: { + String identifier; +} + + ( identifier = identifierStr { return identifier; } ) + | (identifier = keyword_as_ident { return identifier; }) +; + +String keyword_as_ident: + ( + select_field_spec (select_source)? (where_fun)? (orderby_fun)? (limit_fun)? (offset_fun)? (timeout_fun)? + ) +; + +// TODO: Missing field +select_field_spec: + ( + | project_spec + ) +; + +project_spec: + ( + field_def ( field_def)* + ) +; + +timeout_fun: + ( + fixed_or_parameter + ) +; + +select_source: + ( + ( + select_source_sources + | select_source_from + ) + ) +; + +select_source_sources: + ( + (select_source_all | select_source_multi) + ) +; + +select_source_all: + ( + + ) +; + +select_source_multi: + ( + source_list + ) +; + +select_source_from: + ( + source_spec + ) +; + +source_list: + ( + namespaced_name ( namespaced_name)* + ) +; + +source_spec: + ( + data_source (alias_def)? // TODO: Add missing java code + ) +; + +alias_def: + ( + ()? identifierStr + ) +; + +data_source: + ( + call_source | + ( source_statement ) | + sequence_source + ) +; + +call_source: + ( + namespaced_name (arguments(true))? + ) +; + +sequence_source: + ( + ident + ) +; + +namespaced_name: + ( + dotted_idents ( )? + ) +; + +orderby_fun: + ( + orderby_fields + ) +; + +orderby_fields: + ( + orderby_field ( orderby_field)* + ) +; + +orderby_field: + ( + (expression(true) ) + | (expression(true) ()? ) + ) +; + +limit_fun: + ( + fixed_or_parameter + ) +; + +offset_fun: + ( + fixed_or_parameter + ) +; + +where_fun: + ( + expression(true) + ) +; + +field_def: + ( + expression(true) (alias_def)? + ) +; + +map_expression: + ( + (property_name_and_value)? ( property_name_and_value)* + ) +; + +arguments(boolean in_select): + ( + (argument(in_select) ( argument(in_select))*)? + ) +; + +argument(boolean in_select): + ( + expression(in_select) + ) +; + +// --------- expressions ------------ + +expression(boolean select): + ( + null_operator + | annotate_expression + | logical_OR_expression + ) +; +// TODO: Add expression_stack code + +null_operator: + ( + + ) +; + +annotate_expression: + ( + annotation logical_OR_expression + ) +; + +annotation: + ( + ( map_expression ) + | map_expression + ) +; + +logical_OR_expression: + ( + logical_AND_expression ( logical_AND_expression )* + ) +; + +logical_AND_expression: + ( + equality_expression ( equality_expression )* + ) +; + +equality_expression: + ( + relational_expression ( + ( ( | ) in_not_in_target ) + | ( | ) + | ( equality_op relational_expression ) + )? + ) +; + +in_not_in_target: + ( // TODO: Add expression stack peek + ( select_statement ) + | literal_list + ) +; + +equality_op: + ( + ( | | | | | | ) + ) +; + +relational_expression: + ( + additive_expression (relational_op additive_expression)? + ) +; + +relational_op: + ( + | | | + ) +; + +additive_expression: + ( + multiplicative_expression ( additive_op additive_expression)? + ) +; + +additive_op: + ( + | + ) +; + +multiplicative_expression: + ( + unary_expression (mult_op multiplicative_expression)? + ) +; + +mult_op: + ( + |
| + ) +; + +unary_expression: + ( + (unary_op)? dereferenced_expression + ) +; + +unary_op: + ( + | + ) +; + +dereferenced_expression: +{ + boolean in_select = true; // TODO: Peek into epxression stack +} + ( + primary_expression ( + indexref(in_select) + | propertyref + )* + ) +; + +indexref(boolean in_select): + ( // TODO: Store idx + expression(in_select) + ) +; + +propertyref: +{ + String nm; // TODO: handle this further +} + ( + nm = identifierStr + ) +; + +primary_expression: +{ + boolean in_select = true; // TODO: peek into expression stack +} + ( + ( expression(in_select) ) + | constant_expression + | ( + (SCAN 2 => call_expression(in_select)) + | fieldref + ) + ) +; + +call_expression(boolean in_select): + ( + namespaced_name arguments(in_select) + ) +; + +fieldref: + ( + namespaced_name + ) +; + +parameter: + ( + ident + ) +; + +property_name_and_value: + ( + property_name constant_expression + ) +; + +property_name: + ( + dotted_idents | + ) +; + +dotted_idents: + ( + ident ( ident)* + ) +; + +constant_expression: + ( + scalar_literal + | array_literal + | parameter + | map_expression + ) +; + +array_literal: + ( + (constant_expression)? ( constant_expression)* + ) +; + +scalar_literal: + ( + + | + | + | + | + | + ) +; + +array_parameter: + ( + ident //TODO: Add isArrayParameter + ) +; + +literal_list: + ( + literal_element ( literal_element)* + ) +; + +literal_element: + ( + scalar_literal | parameter + ) +; + +fixed_or_parameter: + ( + | parameter + ) +; \ No newline at end of file diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/SchemaLanguageServer.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/SchemaLanguageServer.java index 12267ad3f481..a645d9912702 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/SchemaLanguageServer.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/SchemaLanguageServer.java @@ -1,11 +1,13 @@ package ai.vespa.schemals; import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.PrintStream; import java.net.URL; import java.net.URLDecoder; import java.nio.file.Files; @@ -21,6 +23,7 @@ import org.eclipse.lsp4j.CodeActionKind; import org.eclipse.lsp4j.CodeActionOptions; +import org.eclipse.lsp4j.CodeLensOptions; import org.eclipse.lsp4j.CompletionOptions; import org.eclipse.lsp4j.ExecuteCommandOptions; import org.eclipse.lsp4j.InitializeParams; @@ -42,8 +45,8 @@ import ai.vespa.schemals.common.ClientLogger; import ai.vespa.schemals.documentation.FetchDocumentation; import ai.vespa.schemals.index.SchemaIndex; -import ai.vespa.schemals.lsp.command.CommandRegistry; -import ai.vespa.schemals.lsp.semantictokens.SchemaSemanticTokens; +import ai.vespa.schemals.lsp.common.command.CommandRegistry; +import ai.vespa.schemals.lsp.common.semantictokens.CommonSemanticTokens; import ai.vespa.schemals.schemadocument.SchemaDocumentScheduler; /** @@ -101,9 +104,10 @@ public CompletableFuture initialize(InitializeParams initializ initializeResult.getCapabilities().setDefinitionProvider(true); initializeResult.getCapabilities().setReferencesProvider(true); initializeResult.getCapabilities().setRenameProvider(new RenameOptions(true)); - initializeResult.getCapabilities().setSemanticTokensProvider(SchemaSemanticTokens.getSemanticTokensRegistrationOptions()); + initializeResult.getCapabilities().setSemanticTokensProvider(CommonSemanticTokens.getSemanticTokensRegistrationOptions()); initializeResult.getCapabilities().setDocumentSymbolProvider(true); initializeResult.getCapabilities().setExecuteCommandProvider(new ExecuteCommandOptions(CommandRegistry.getSupportedCommandList())); + initializeResult.getCapabilities().setCodeLensProvider(new CodeLensOptions()); var options = new CodeActionOptions(List.of( CodeActionKind.QuickFix, @@ -170,6 +174,12 @@ public void connect(LanguageClient languageClient) { setupDocumentation(docPath); } catch (IOException ioex) { this.logger.error("Failed to set up documentation. Error: " + ioex.getMessage()); + try (ByteArrayOutputStream os = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(os)) { + ioex.printStackTrace(ps); + logger.error(os.toString()); + } catch (IOException bruh) { + logger.error("Error inside error " + bruh.getMessage()); + } } } @@ -181,6 +191,7 @@ public void setupDocumentation(Path documentationPath) throws IOException { Files.createDirectories(documentationPath); Files.createDirectories(documentationPath.resolve("schema")); Files.createDirectories(documentationPath.resolve("rankExpression")); + Files.createDirectories(documentationPath.resolve("services")); ensureLocalDocumentationLoaded(documentationPath); @@ -188,7 +199,8 @@ public void setupDocumentation(Path documentationPath) throws IOException { @Override public void run() { try { - FetchDocumentation.fetchDocs(documentationPath); + FetchDocumentation.fetchSchemaDocs(documentationPath); + FetchDocumentation.fetchServicesDocs(documentationPath); } catch(Exception e) { throw new RuntimeException(e.getMessage()); } @@ -243,6 +255,7 @@ public boolean accept(File pathname) { JarEntry entry = entries.nextElement(); if (!entry.isDirectory() && entry.getName().startsWith(documentationPath.getFileName().toString())) { Path destination = documentationPath.getParent().resolve(entry.getName()); + Files.createDirectories(destination.getParent()); try (InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(entry.getName())) { BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String content = reader.lines().collect(Collectors.joining(System.lineSeparator())); diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/SchemaTextDocumentService.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/SchemaTextDocumentService.java index ddc724b526bb..2e56292e2602 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/SchemaTextDocumentService.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/SchemaTextDocumentService.java @@ -47,6 +47,7 @@ import org.eclipse.lsp4j.TextDocumentItem; import org.eclipse.lsp4j.TextEdit; import org.eclipse.lsp4j.WorkspaceEdit; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; import org.eclipse.lsp4j.jsonrpc.CompletableFutures; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.jsonrpc.messages.Either3; @@ -55,17 +56,21 @@ import ai.vespa.schemals.common.ClientLogger; import ai.vespa.schemals.context.EventContextCreator; import ai.vespa.schemals.context.EventDocumentContext; +import ai.vespa.schemals.context.InvalidContextException; import ai.vespa.schemals.index.SchemaIndex; -import ai.vespa.schemals.lsp.codeaction.SchemaCodeAction; -import ai.vespa.schemals.lsp.completion.SchemaCompletion; -import ai.vespa.schemals.lsp.definition.SchemaDefinition; -import ai.vespa.schemals.lsp.documentsymbols.SchemaDocumentSymbols; -import ai.vespa.schemals.lsp.hover.SchemaHover; -import ai.vespa.schemals.lsp.references.SchemaReferences; -import ai.vespa.schemals.lsp.rename.SchemaPrepareRename; -import ai.vespa.schemals.lsp.rename.SchemaRename; -import ai.vespa.schemals.lsp.semantictokens.SchemaSemanticTokens; +import ai.vespa.schemals.lsp.schema.codeaction.SchemaCodeAction; +import ai.vespa.schemals.lsp.schema.completion.SchemaCompletion; +import ai.vespa.schemals.lsp.schema.definition.SchemaDefinition; +import ai.vespa.schemals.lsp.schema.documentsymbols.SchemaDocumentSymbols; +import ai.vespa.schemals.lsp.schema.hover.SchemaHover; +import ai.vespa.schemals.lsp.schema.references.SchemaReferences; +import ai.vespa.schemals.lsp.schema.rename.SchemaPrepareRename; +import ai.vespa.schemals.lsp.schema.rename.SchemaRename; +import ai.vespa.schemals.lsp.schema.semantictokens.SchemaSemanticTokens; +import ai.vespa.schemals.lsp.yqlplus.codelens.YQLPlusCodeLens; +import ai.vespa.schemals.lsp.yqlplus.semantictokens.YQLPlusSemanticTokens; import ai.vespa.schemals.schemadocument.SchemaDocumentScheduler; +import ai.vespa.schemals.schemadocument.DocumentManager.DocumentType; /** * SchemaTextDocumentService handles incomming requests from the client. @@ -101,6 +106,8 @@ public CompletableFuture, CompletionList>> completio return result; } catch(CancellationException ignore) { // Ignore + } catch(InvalidContextException ignore) { + // Ignore } // Return the list of completion items. @@ -119,6 +126,8 @@ public CompletableFuture>> codeAction(CodeActio return CompletableFutures.computeAsync((cancelChecker) -> { try { return SchemaCodeAction.provideActions(eventContextCreator.createContext(params)); + } catch(InvalidContextException ignore) { + // Ignore } catch(Exception e) { logger.error("Error during code action handling: " + e.getMessage()); } @@ -138,7 +147,12 @@ public CompletableFuture resolveCodeAction(CodeAction unresolved) { @Override public CompletableFuture> references(ReferenceParams params) { return CompletableFutures.computeAsync((cancelChecker) -> { - return SchemaReferences.getReferences(eventContextCreator.createContext(params)); + try { + return SchemaReferences.getReferences(eventContextCreator.createContext(params)); + } catch(InvalidContextException ignore) { + // Ignore + } + return List.of(); }); } @@ -149,6 +163,8 @@ public CompletableFuture>> docume try { EventDocumentContext context = eventContextCreator.createContext(params); return SchemaDocumentSymbols.documentSymbols(context); + } catch(InvalidContextException ignore) { + // Ignore } catch(Exception e) { logger.error("Error during document symbol handling: " + e.getMessage()); } @@ -158,7 +174,18 @@ public CompletableFuture>> docume @Override public CompletableFuture> codeLens(CodeLensParams codeLensParams) { - return null; + + return CompletableFutures.computeAsync((cancelChecker) -> { + try { + EventDocumentContext context = eventContextCreator.createContext(codeLensParams); + return YQLPlusCodeLens.codeLens(context); + } catch(InvalidContextException ignore) { + // Ignore + } catch (Exception e) { + logger.error("Error during code lens request: " + e.getMessage()); + } + return List.of(); + }); } @Override @@ -185,7 +212,12 @@ public CompletableFuture> onTypeFormatting(DocumentOnTy public CompletableFuture> prepareRename(PrepareRenameParams params) { return CompletableFutures.computeAsync((cancelChecker) -> { - return SchemaPrepareRename.prepareRename(eventContextCreator.createContext(params)); + try { + return SchemaPrepareRename.prepareRename(eventContextCreator.createContext(params)); + } catch(InvalidContextException ignore) { + // Ignore + } + return null; }); } @@ -193,7 +225,12 @@ public CompletableFuture rename(RenameParams params) { return CompletableFutures.computeAsync((cancelChecker) -> { - return SchemaRename.rename(eventContextCreator.createContext(params), params.getNewName()); + try { + return SchemaRename.rename(eventContextCreator.createContext(params), params.getNewName()); + } catch(InvalidContextException ignore) { + // Ignore + } + return null; }); } @@ -253,10 +290,14 @@ public CompletableFuture> documentHighlight(Do public CompletableFuture semanticTokensFull(SemanticTokensParams params) { return CompletableFutures.computeAsync((cancelChecker) -> { try { - - var result = SchemaSemanticTokens.getSemanticTokens(eventContextCreator.createContext(params)); - return result; + EventDocumentContext context = eventContextCreator.createContext(params); + if (context.document.getDocumentType() == DocumentType.YQL) { + return YQLPlusSemanticTokens.getSemanticTokens(context); + } + return SchemaSemanticTokens.getSemanticTokens(context); + } catch (InvalidContextException ex) { + // ignore } catch (CancellationException ignore) { // Ignore cancellation exception } catch (Exception e) { @@ -279,6 +320,8 @@ public CompletableFuture hover(HoverParams params) { return SchemaHover.getHover(eventContextCreator.createContext(params)); + } catch (InvalidContextException ex) { + // ignore } catch (CancellationException ignore) { // Ignore } catch (Exception e) { @@ -297,6 +340,8 @@ public CompletableFuture, List executeCommand(ExecuteCommandParams params) { return CompletableFutures.computeAsync(cancelChecker -> { - return ExecuteCommand.executeCommand(eventContextCreator.createContext(params)); + try { + return ExecuteCommand.executeCommand(eventContextCreator.createContext(params)); + } catch (Exception ex) { + logger.error("Internal error when executing command: " + ex.getMessage()); + return null; + } }); } } diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/common/FileUtils.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/common/FileUtils.java index ef149aba7b9b..1df1ec419f4c 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/common/FileUtils.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/common/FileUtils.java @@ -71,6 +71,9 @@ public static String firstPathComponentAfterPrefix(String pathURIStr, String pre return components[0]; } + /** + * Searches among the parents for a directory named "schemas" + */ public static Optional findSchemaDirectory(URI initialURI) { Path path = Paths.get(initialURI); while (path != null && path.getFileName() != null) { diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventCodeActionContext.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventCodeActionContext.java index 02bf6bd87e2d..a1a118aae3a6 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventCodeActionContext.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventCodeActionContext.java @@ -22,8 +22,14 @@ public class EventCodeActionContext extends EventPositionContext { public final List diagnostics; public final List codeActionKinds; - public EventCodeActionContext(SchemaDocumentScheduler scheduler, SchemaIndex schemaIndex, - SchemaMessageHandler messageHandler, TextDocumentIdentifier documentIdentifier, Range range, List quickfixDiagnostics, List onlyKinds) { + public EventCodeActionContext( + SchemaDocumentScheduler scheduler, + SchemaIndex schemaIndex, + SchemaMessageHandler messageHandler, + TextDocumentIdentifier documentIdentifier, + Range range, + List quickfixDiagnostics, + List onlyKinds) throws InvalidContextException { super(scheduler, schemaIndex, messageHandler, documentIdentifier, range.getStart()); this.range = range; this.diagnostics = quickfixDiagnostics; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventCompletionContext.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventCompletionContext.java index 44ba88744fa9..531fb47e05ff 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventCompletionContext.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventCompletionContext.java @@ -15,8 +15,13 @@ public class EventCompletionContext extends EventPositionContext { public final Character triggerCharacter; - public EventCompletionContext(SchemaDocumentScheduler scheduler, SchemaIndex schemaIndex, - SchemaMessageHandler messageHandler, TextDocumentIdentifier documentIdentifier, Position position, String triggerCharacter) { + public EventCompletionContext( + SchemaDocumentScheduler scheduler, + SchemaIndex schemaIndex, + SchemaMessageHandler messageHandler, + TextDocumentIdentifier documentIdentifier, + Position position, + String triggerCharacter) throws InvalidContextException { super(scheduler, schemaIndex, messageHandler, documentIdentifier, position); if (triggerCharacter == null || triggerCharacter.length() == 0) { diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventContextCreator.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventContextCreator.java index a7d1a90b4e66..ab6830dc1715 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventContextCreator.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventContextCreator.java @@ -3,6 +3,7 @@ import java.io.PrintStream; import org.eclipse.lsp4j.CodeActionParams; +import org.eclipse.lsp4j.CodeLensParams; import org.eclipse.lsp4j.CompletionParams; import org.eclipse.lsp4j.DocumentSymbolParams; import org.eclipse.lsp4j.ExecuteCommandParams; @@ -13,6 +14,8 @@ import ai.vespa.schemals.index.SchemaIndex; import ai.vespa.schemals.schemadocument.SchemaDocumentScheduler; +import ai.vespa.schemals.context.InvalidContextException; + public class EventContextCreator { public final SchemaDocumentScheduler scheduler; public final SchemaIndex schemaIndex; @@ -28,7 +31,7 @@ public EventContextCreator( this.messageHandler = messageHandler; } - public EventPositionContext createContext(TextDocumentPositionParams params) { + public EventPositionContext createContext(TextDocumentPositionParams params) throws InvalidContextException { return new EventPositionContext( scheduler, schemaIndex, @@ -38,7 +41,7 @@ public EventPositionContext createContext(TextDocumentPositionParams params) { ); } - public EventCompletionContext createContext(CompletionParams params) { + public EventCompletionContext createContext(CompletionParams params) throws InvalidContextException { return new EventCompletionContext( scheduler, schemaIndex, @@ -48,15 +51,15 @@ public EventCompletionContext createContext(CompletionParams params) { params.getContext().getTriggerCharacter()); } - public EventDocumentContext createContext(SemanticTokensParams params) { + public EventDocumentContext createContext(SemanticTokensParams params) throws InvalidContextException { return new EventDocumentContext(scheduler, schemaIndex, messageHandler, params.getTextDocument()); } - public EventDocumentContext createContext(DocumentSymbolParams params) { + public EventDocumentContext createContext(DocumentSymbolParams params) throws InvalidContextException { return new EventDocumentContext(scheduler, schemaIndex, messageHandler, params.getTextDocument()); } - public EventCodeActionContext createContext(CodeActionParams params) { + public EventCodeActionContext createContext(CodeActionParams params) throws InvalidContextException { if (params.getContext() == null) return null; if (params.getRange() == null) return null; if (params.getContext().getDiagnostics() == null) return null; @@ -77,4 +80,8 @@ public EventExecuteCommandContext createContext(ExecuteCommandParams params) { return new EventExecuteCommandContext(scheduler, schemaIndex, messageHandler, params); } + public EventDocumentContext createContext(CodeLensParams params) throws InvalidContextException { + return new EventDocumentContext(scheduler, schemaIndex, messageHandler, params.getTextDocument()); + } + } diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventDocumentContext.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventDocumentContext.java index 8180cd2ce64e..9a3667587565 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventDocumentContext.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventDocumentContext.java @@ -19,10 +19,13 @@ public EventDocumentContext( SchemaIndex schemaIndex, SchemaMessageHandler messageHandler, TextDocumentIdentifier documentIdentifier - ) { + ) throws InvalidContextException { super(scheduler, schemaIndex, messageHandler); this.documentIdentifier = documentIdentifier; this.document = scheduler.getDocument(documentIdentifier.getUri()); + if (this.document == null) { + throw new InvalidContextException(); + } } } diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventPositionContext.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventPositionContext.java index f7c9952c1cd5..16b5bcdf0785 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventPositionContext.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/EventPositionContext.java @@ -18,7 +18,7 @@ public EventPositionContext( SchemaMessageHandler messageHandler, TextDocumentIdentifier documentIdentifier, Position position - ) { + ) throws InvalidContextException { super(scheduler, schemaIndex, messageHandler, documentIdentifier); this.position = position; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/InvalidContextException.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/InvalidContextException.java new file mode 100644 index 000000000000..15ba09f13fa6 --- /dev/null +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/context/InvalidContextException.java @@ -0,0 +1,7 @@ +package ai.vespa.schemals.context; + +public class InvalidContextException extends Exception { + public InvalidContextException() { + super(); + } +} diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/documentation/FetchDocumentation.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/documentation/FetchDocumentation.java index 906c56c7d81c..07f358581740 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/documentation/FetchDocumentation.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/documentation/FetchDocumentation.java @@ -1,6 +1,5 @@ package ai.vespa.schemals.documentation; -import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -17,6 +16,19 @@ public class FetchDocumentation { private final static String SCHEMA_URL = "en/reference/schema-reference.html"; private final static String RANK_FEATURE_URL = "en/reference/rank-features.html"; + private record ServicesLocation(String relativeUrl, String relativeSavePath) {} + + private final static ServicesLocation[] SERVICES_PATHS = { + new ServicesLocation("en/reference/services.html", ""), + new ServicesLocation("en/reference/services-admin.html", "admin"), + new ServicesLocation("en/reference/services-container.html", "container"), + new ServicesLocation("en/reference/services-content.html", "content"), + new ServicesLocation("en/reference/services-docproc.html", "container/document-processing"), + new ServicesLocation("en/reference/services-http.html", "container/http"), + new ServicesLocation("en/reference/services-processing.html", "container/processing"), + new ServicesLocation("en/reference/services-search.html", "container/search") + }; + private final static Map> REPLACE_FILENAME_MAP = new HashMap<>(){{ put("EXPRESSION", List.of( "EXPRESSION_SL", "EXPRESSION_ML" )); put("RANK_FEATURES", List.of( "RANKFEATURES_SL", "RANKFEATURES_ML" )); @@ -26,7 +38,7 @@ public class FetchDocumentation { put("IMPORT FIELD", List.of( "IMPORT" )); }}; - public static void fetchDocs(Path targetPath) throws IOException { + public static void fetchSchemaDocs(Path targetPath) throws IOException { Files.createDirectories(targetPath); Files.createDirectories(targetPath.resolve("schema")); Files.createDirectories(targetPath.resolve("rankExpression")); @@ -56,10 +68,28 @@ public static void fetchDocs(Path targetPath) throws IOException { } } + public static void fetchServicesDocs(Path targetPath) throws IOException { + Files.createDirectories(targetPath); + Files.createDirectories(targetPath.resolve("services")); + targetPath = targetPath.resolve("services"); + + for (ServicesLocation locationEntry : SERVICES_PATHS) { + Map markdownContent = new ServicesDocumentationFetcher(locationEntry.relativeUrl()).getMarkdownContent(); + Path writePath = targetPath.resolve(locationEntry.relativeSavePath()); + Files.createDirectories(writePath); // mkdir -p + + for (var entry : markdownContent.entrySet()) { + if (entry.getKey().contains("/")) continue; + Files.write(writePath.resolve(entry.getKey() + ".md"), entry.getValue().getBytes(), StandardOpenOption.CREATE); + } + } + } + private static String convertToToken(String h2Id) { return h2Id.toUpperCase().replaceAll("-", "_"); } + // Runs during build public static void main(String[] args) { if (args.length < 1) { System.err.println("FetchDocumentation requires one argument: "); @@ -68,7 +98,8 @@ public static void main(String[] args) { Path targetPath = Paths.get(args[0]); try { System.out.println("Fetching docs to " + args[0]); - fetchDocs(targetPath); + fetchSchemaDocs(targetPath); + fetchServicesDocs(targetPath); } catch (IOException ex) { System.err.println("FetchDocumentation failed to download documentation: " + ex.getMessage()); System.exit(1); diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/documentation/SchemaDocumentationFetcher.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/documentation/SchemaDocumentationFetcher.java index c84412702381..5746e5a63834 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/documentation/SchemaDocumentationFetcher.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/documentation/SchemaDocumentationFetcher.java @@ -18,6 +18,8 @@ * SchemaDocumentationFetcher */ public class SchemaDocumentationFetcher extends ContentFetcher { + // Store html content as well as id of h2 element. The id is used to append a read more link at the end. + private record HTMLContentEntry(StringBuilder htmlContent, String h2ID) { } private final static Set IGNORE_H2_IDS = Set.of( "syntax", @@ -30,8 +32,6 @@ public class SchemaDocumentationFetcher extends ContentFetcher { super(relativeFileUrl); } - // Store html content as well as id of h2 element. The id is used to append a read more link at the end. - private record HTMLContentEntry(StringBuilder htmlContent, String h2ID) { } @Override Map getMarkdownContent() throws IOException { @@ -39,11 +39,9 @@ Map getMarkdownContent() throws IOException { Element prevH2 = null; - Node nodeIterator = schemaDoc.selectFirst("h2#schema"); - Map htmlContents = new HashMap<>(); - for (; nodeIterator != null; nodeIterator = nodeIterator.nextSibling()) { + for (Node nodeIterator = schemaDoc.selectFirst("h2#schema"); nodeIterator != null; nodeIterator = nodeIterator.nextSibling()) { Element element = null; if (nodeIterator instanceof Element) { element = (Element)nodeIterator; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/documentation/ServicesDocumentationFetcher.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/documentation/ServicesDocumentationFetcher.java new file mode 100644 index 000000000000..266a03362f3e --- /dev/null +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/documentation/ServicesDocumentationFetcher.java @@ -0,0 +1,89 @@ +package ai.vespa.schemals.documentation; + +import java.io.IOException; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; +import org.jsoup.parser.Tag; +import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter; + +public class ServicesDocumentationFetcher extends ContentFetcher { + // Store html content as well as id of h2 element. The id is used to append a read more link at the end. + private record HTMLContentEntry(StringBuilder htmlContent, String h2ID) { } + + ServicesDocumentationFetcher(String relativeFileUrl) { + super(relativeFileUrl); + } + + @Override + Map getMarkdownContent() throws IOException { + Document schemaDoc = Jsoup.connect(ContentFetcher.URL_PREFIX + this.fileUrl).get(); + + Element prevH2 = null; + Map htmlContents = new HashMap<>(); + + for (Node nodeIterator = schemaDoc.selectFirst("h2"); nodeIterator != null; nodeIterator = nodeIterator.nextSibling()) { + Element element = null; + if (nodeIterator instanceof Element) { + element = (Element)nodeIterator; + + if (element.tag().equals(Tag.valueOf("h2"))) { + prevH2 = element; + } + } + if (prevH2 == null) continue; + + String contentKey = prevH2.text(); + + if (!htmlContents.containsKey(contentKey)) { + htmlContents.put(contentKey, + new HTMLContentEntry(new StringBuilder().append(prevH2.outerHtml()), prevH2.id()) + ); + continue; + } + StringBuilder currentBuilder = htmlContents.get(contentKey).htmlContent(); + + currentBuilder.append("\n"); + + if (element == null) { + if (!nodeIterator.toString().isBlank()) + currentBuilder.append(nodeIterator.toString()); + continue; + } + + if (element.tag().equals(Tag.valueOf("table"))) { + Element tbody = element.selectFirst("tbody"); + // replace all in tbody with + tbody.select("th").tagName("td"); + } + + currentBuilder.append(element.outerHtml()); + } + + Map result = new HashMap<>(); + + FlexmarkHtmlConverter converter = this.getHtmlParser(); + + for (var entry : htmlContents.entrySet()) { + StringBuilder htmlContent = entry.getValue().htmlContent(); + String h2id = entry.getValue().h2ID(); + + URI readMoreLink = URI.create(ContentFetcher.URL_PREFIX).resolve(fileUrl).resolve("#" + h2id); + htmlContent.append("Read more"); + + String md = converter.convert(htmlContent.toString()); + + // Edge case occuring at "bolding" html, don't know why. + md = md.replaceAll("````\n", ""); + + result.put(entry.getKey(), md); + } + return result; + } +} diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/command/CommandRegistry.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/command/CommandRegistry.java deleted file mode 100644 index c346febe8e43..000000000000 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/command/CommandRegistry.java +++ /dev/null @@ -1,58 +0,0 @@ -package ai.vespa.schemals.lsp.command; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import org.eclipse.lsp4j.Command; -import org.eclipse.lsp4j.ExecuteCommandParams; - -import ai.vespa.schemals.lsp.command.commandtypes.DocumentOpen; -import ai.vespa.schemals.lsp.command.commandtypes.DocumentParse; -import ai.vespa.schemals.lsp.command.commandtypes.CommandList; -import ai.vespa.schemals.lsp.command.commandtypes.SchemaCommand; - -/** - * SchemaCommand - */ -public class CommandRegistry { - public interface GenericCommandType { - public String title(); - public SchemaCommand construct(); - } - public enum CommandType implements GenericCommandType { - DOCUMENT_OPEN { - public String title() { return "Open document"; } - public SchemaCommand construct() { return new DocumentOpen(); } - }, - DOCUMENT_PARSE { - public String title() { return "Parse document"; } - public SchemaCommand construct() { return new DocumentParse(); } - }, - COMMAND_LIST { - public String title() { return "Command list"; } - public SchemaCommand construct() { return new CommandList(); } - } - } - - public static List getSupportedCommandList() { - return Arrays.stream(CommandType.values()).map(commandType -> commandType.name()).toList(); - } - - public static Optional getCommand(ExecuteCommandParams params) { - try { - CommandType commandType = CommandType.valueOf(params.getCommand()); - SchemaCommand command = commandType.construct(); - - if (command.getArity() != -1 && command.getArity() != params.getArguments().size()) return Optional.empty(); - if (!command.setArguments(params.getArguments())) return Optional.empty(); - return Optional.of(command); - } catch(Exception e) { - return Optional.empty(); - } - } - - public static Command createLSPCommand(CommandType commandType, List arguments) { - return new Command(commandType.title(), commandType.name(), arguments); - } -} diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/CommandRegistry.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/CommandRegistry.java new file mode 100644 index 000000000000..432dcaaf20e5 --- /dev/null +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/CommandRegistry.java @@ -0,0 +1,127 @@ +package ai.vespa.schemals.lsp.common.command; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.ExecuteCommandParams; + +import ai.vespa.schemals.lsp.common.command.commandtypes.CommandList; +import ai.vespa.schemals.lsp.common.command.commandtypes.DocumentOpen; +import ai.vespa.schemals.lsp.common.command.commandtypes.DocumentParse; +import ai.vespa.schemals.lsp.common.command.commandtypes.RunVespaQuery; +import ai.vespa.schemals.lsp.common.command.commandtypes.FindDocument; +import ai.vespa.schemals.lsp.common.command.commandtypes.SchemaCommand; +import ai.vespa.schemals.lsp.common.command.commandtypes.SetupWorkspace; + +/** + * SchemaCommand + */ +public class CommandRegistry { + public interface GenericCommandType { + public String title(); + public SchemaCommand construct(); + } + public enum CommandType implements GenericCommandType { + DOCUMENT_OPEN { + /* + * Sends a window/showDocument request to the client. + * + * Parameters: + * fileURI: String -- path to document + * + * Return value: + * null + */ + public String title() { return "Open document"; } + public SchemaCommand construct() { return new DocumentOpen(); } + }, + DOCUMENT_PARSE { + /* + * Reparse a given document. + * If the language server does not know about the document (it is not opened), nothing will happen. + * + * Parameters: + * fileURI: String -- path to document + * + * Return value: + * null + */ + public String title() { return "Parse document"; } + public SchemaCommand construct() { return new DocumentParse(); } + }, + COMMAND_LIST { + /* + * Execute a list of commands in the given order. + * + * Parameters: + * commands: List -- commands to execute + * + * Return value: + * null + */ + public String title() { return "Command list"; } + public SchemaCommand construct() { return new CommandList(); } + }, + RUN_VESPA_QUERY { + /* + * Runs a Vespa query. + * + * Parameters: + * + * Return value: + * null + */ + public String title() { return "Run Vespa query"; } + public SchemaCommand construct() { return new RunVespaQuery(); } + }, + FIND_SCHEMA_DEFINITION { + /* + * Locates a schema definition. + * + * Parameters: + * schemaName: String -- Schema to locate. + * + * Return value: + * List -- definitions found. + */ + public String title() { return "Find schema document"; } + public SchemaCommand construct() { return new FindDocument(); } + }, + SETUP_WORKSPACE { + /* + * Set the workspace directory and parse *sd files within. If it is already set up, nothing will happen. + * + * Parameters: + * baseURI: String -- Directory to set as workspace. + * + * Return value: + * null + */ + public String title() { return "Setup workspace"; } + public SchemaCommand construct() { return new SetupWorkspace(); } + } + } + + public static List getSupportedCommandList() { + return Arrays.stream(CommandType.values()).map(commandType -> commandType.name()).toList(); + } + + public static Optional getCommand(ExecuteCommandParams params) { + try { + CommandType commandType = CommandType.valueOf(params.getCommand()); + SchemaCommand command = commandType.construct(); + + if (command.getArity() != -1 && command.getArity() != params.getArguments().size()) return Optional.empty(); + if (!command.setArguments(params.getArguments())) return Optional.empty(); + return Optional.of(command); + } catch(Exception e) { + return Optional.empty(); + } + } + + public static Command createLSPCommand(CommandType commandType, List arguments) { + return new Command(commandType.title(), commandType.name(), arguments); + } +} diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/CommandUtils.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/CommandUtils.java new file mode 100644 index 000000000000..2a2e3361a184 --- /dev/null +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/CommandUtils.java @@ -0,0 +1,14 @@ +package ai.vespa.schemals.lsp.common.command; + +import java.util.Optional; + +import com.google.gson.JsonPrimitive; + +public class CommandUtils { + public static Optional getStringArgument(Object jsonObject) { + if (!(jsonObject instanceof JsonPrimitive)) + return Optional.empty(); + JsonPrimitive arg = (JsonPrimitive) jsonObject; + return Optional.of(arg.getAsString()); + } +} diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/command/ExecuteCommand.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/ExecuteCommand.java similarity index 53% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/command/ExecuteCommand.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/ExecuteCommand.java index 7bcc28e0fca7..4c97221f590a 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/command/ExecuteCommand.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/ExecuteCommand.java @@ -1,9 +1,9 @@ -package ai.vespa.schemals.lsp.command; +package ai.vespa.schemals.lsp.common.command; import java.util.Optional; import ai.vespa.schemals.context.EventExecuteCommandContext; -import ai.vespa.schemals.lsp.command.commandtypes.SchemaCommand; +import ai.vespa.schemals.lsp.common.command.commandtypes.SchemaCommand; /** * Responsible for LSP workspace/executeCommand requests. @@ -12,13 +12,18 @@ public class ExecuteCommand { public static Object executeCommand(EventExecuteCommandContext context) { Optional command = CommandRegistry.getCommand(context.params); + context.logger.info("Received command: " + context.params.getCommand()); + if (command.isEmpty()) { + context.logger.error("Unknown command " + context.params.getCommand()); + context.logger.error("Arguments:"); for (Object obj : context.params.getArguments()) { - context.logger.info(obj.getClass().toString() + " ||| " + obj.toString()); + context.logger.info(obj.getClass().toString() + ": " + obj.toString()); } + return null; } - command.ifPresent(cmd -> cmd.execute(context)); - return null; + Object resultOrNull = command.get().execute(context); + return resultOrNull; } } diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/command/commandtypes/CommandList.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/CommandList.java similarity index 85% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/command/commandtypes/CommandList.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/CommandList.java index 8bab89c0fe8f..6ce526c7abbb 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/command/commandtypes/CommandList.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/CommandList.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.command.commandtypes; +package ai.vespa.schemals.lsp.common.command.commandtypes; import java.util.ArrayList; import java.util.List; @@ -9,12 +9,13 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import ai.vespa.schemals.lsp.command.CommandRegistry; import ai.vespa.schemals.context.EventExecuteCommandContext; +import ai.vespa.schemals.lsp.common.command.CommandRegistry; /** * CommandList * Represents a chain of commands to be executed one after another. + * Currently their return values are ignored. */ public class CommandList implements SchemaCommand { @@ -46,9 +47,10 @@ public boolean setArguments(List arguments) { } @Override - public void execute(EventExecuteCommandContext context) { + public Object execute(EventExecuteCommandContext context) { for (SchemaCommand cmd : commandsToExecute) { cmd.execute(context); } + return null; } } diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/command/commandtypes/DocumentOpen.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/DocumentOpen.java similarity index 56% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/command/commandtypes/DocumentOpen.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/DocumentOpen.java index df9817d0e4c3..a32cb09d7ce8 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/command/commandtypes/DocumentOpen.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/DocumentOpen.java @@ -1,12 +1,14 @@ -package ai.vespa.schemals.lsp.command.commandtypes; +package ai.vespa.schemals.lsp.common.command.commandtypes; import java.util.List; +import java.util.Optional; import org.eclipse.lsp4j.ShowDocumentResult; import com.google.gson.JsonPrimitive; import ai.vespa.schemals.context.EventExecuteCommandContext; +import ai.vespa.schemals.lsp.common.command.CommandUtils; /** * OpenDocument @@ -17,21 +19,22 @@ public class DocumentOpen implements SchemaCommand { private String fileURI; @Override - public void execute(EventExecuteCommandContext context) { + public Object execute(EventExecuteCommandContext context) { if (fileURI == null) - return; - ShowDocumentResult result = context.messageHandler.showDocument(fileURI).join(); + return null; + // No return value, as the execution **is** issuing an action on the client side. + context.messageHandler.showDocument(fileURI).join(); + return null; } @Override public boolean setArguments(List arguments) { assert arguments.size() == getArity(); - if (!(arguments.get(0) instanceof JsonPrimitive)) - return false; + Optional argument = CommandUtils.getStringArgument(arguments.get(0)); + if (argument.isEmpty()) return false; - JsonPrimitive arg = (JsonPrimitive) arguments.get(0); - fileURI = arg.getAsString(); + fileURI = argument.get(); return true; } diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/command/commandtypes/DocumentParse.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/DocumentParse.java similarity index 58% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/command/commandtypes/DocumentParse.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/DocumentParse.java index a8fd312881f5..5549b54e268f 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/command/commandtypes/DocumentParse.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/DocumentParse.java @@ -1,10 +1,12 @@ -package ai.vespa.schemals.lsp.command.commandtypes; +package ai.vespa.schemals.lsp.common.command.commandtypes; import java.util.List; +import java.util.Optional; import com.google.gson.JsonPrimitive; import ai.vespa.schemals.context.EventExecuteCommandContext; +import ai.vespa.schemals.lsp.common.command.CommandUtils; import ai.vespa.schemals.schemadocument.DocumentManager; /** @@ -22,18 +24,18 @@ public int getArity() { public boolean setArguments(List arguments) { assert arguments.size() == getArity(); - if (!(arguments.get(0) instanceof JsonPrimitive)) - return false; + Optional argument = CommandUtils.getStringArgument(arguments.get(0)); + if (argument.isEmpty()) return false; - JsonPrimitive arg = (JsonPrimitive) arguments.get(0); - this.fileURI = arg.getAsString(); + fileURI = argument.get(); return true; } @Override - public void execute(EventExecuteCommandContext context) { + public Object execute(EventExecuteCommandContext context) { DocumentManager document = context.scheduler.getDocument(fileURI); - if (document == null) return; + if (document == null) return null; document.reparseContent(); + return null; } } diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/FindDocument.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/FindDocument.java new file mode 100644 index 000000000000..792292c32cd6 --- /dev/null +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/FindDocument.java @@ -0,0 +1,49 @@ +package ai.vespa.schemals.lsp.common.command.commandtypes; + +import java.util.List; +import java.util.Optional; + +import com.google.gson.JsonPrimitive; + +import ai.vespa.schemals.context.EventExecuteCommandContext; +import ai.vespa.schemals.index.Symbol; +import ai.vespa.schemals.index.Symbol.SymbolType; +import ai.vespa.schemals.lsp.common.command.CommandUtils; + +/** + * FindDocument + * Param: schema identifier + * Returns List of Location, possibly empty. + */ +public class FindDocument implements SchemaCommand { + private String schemaName; + + @Override + public Object execute(EventExecuteCommandContext context) { + if (schemaName == null) + return null; + List schemaDefinitions = context.schemaIndex.findSymbols(null, SymbolType.SCHEMA, schemaName); + // LSP expects the result of "definition" to be a list of Location (rather than one Location) + // so we adhere to this here, because that is the most natural use of this command + return schemaDefinitions.stream() + .map(symbol -> symbol.getLocation()) + .toList(); + } + + @Override + public boolean setArguments(List arguments) { + assert arguments.size() == getArity(); + + Optional argument = CommandUtils.getStringArgument(arguments.get(0)); + if (argument.isEmpty()) return false; + + schemaName = argument.get(); + + return true; + } + + @Override + public int getArity() { + return 1; + } +} diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/RunVespaQuery.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/RunVespaQuery.java new file mode 100644 index 000000000000..e497abc90f91 --- /dev/null +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/RunVespaQuery.java @@ -0,0 +1,22 @@ +package ai.vespa.schemals.lsp.common.command.commandtypes; + +import java.util.List; + +import ai.vespa.schemals.context.EventExecuteCommandContext; + +public class RunVespaQuery implements SchemaCommand { + + public int getArity() { + return -1; + } + + public boolean setArguments(List arguments) { + return true; + } + + public Object execute(EventExecuteCommandContext context) { + context.logger.info("Running Vespa query..."); + return null; + } + +} diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/command/commandtypes/SchemaCommand.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/SchemaCommand.java similarity index 65% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/command/commandtypes/SchemaCommand.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/SchemaCommand.java index 6d341019227c..d25a9ebef63a 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/command/commandtypes/SchemaCommand.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/SchemaCommand.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.command.commandtypes; +package ai.vespa.schemals.lsp.common.command.commandtypes; import java.util.List; @@ -13,5 +13,5 @@ public interface SchemaCommand { public boolean setArguments(List arguments); - public void execute(EventExecuteCommandContext context); + public Object execute(EventExecuteCommandContext context); } diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/SetupWorkspace.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/SetupWorkspace.java new file mode 100644 index 000000000000..e232ee15df46 --- /dev/null +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/SetupWorkspace.java @@ -0,0 +1,51 @@ +package ai.vespa.schemals.lsp.common.command.commandtypes; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; + +import com.google.gson.JsonPrimitive; + +import ai.vespa.schemals.context.EventExecuteCommandContext; +import ai.vespa.schemals.lsp.common.command.CommandUtils; + +public class SetupWorkspace implements SchemaCommand { + + URI baseURI; + + @Override + public int getArity() { + return 1; + } + + @Override + public boolean setArguments(List arguments) { + assert arguments.size() == getArity(); + + Optional argument = CommandUtils.getStringArgument(arguments.get(0)); + if (argument.isEmpty()) return false; + + String suppliedURI = argument.get(); + + try { + Path schemasPath = Paths.get(new URI(suppliedURI)).getParent().resolve("schemas"); + if (schemasPath.toFile().exists()) { + baseURI = schemasPath.toUri(); + } + } catch (URISyntaxException exception) { + return false; + } + return true; + } + + @Override + public Object execute(EventExecuteCommandContext context) { + if (context.scheduler.getWorkspaceURI() == null && baseURI != null) { + context.scheduler.setupWorkspace(baseURI); + } + return null; + } +} diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/semantictokens/CommonSemanticTokens.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/semantictokens/CommonSemanticTokens.java new file mode 100644 index 000000000000..9132e48683ca --- /dev/null +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/semantictokens/CommonSemanticTokens.java @@ -0,0 +1,66 @@ +package ai.vespa.schemals.lsp.common.semantictokens; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.lsp4j.SemanticTokensLegend; +import org.eclipse.lsp4j.SemanticTokensServerFull; +import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions; + +import ai.vespa.schemals.lsp.schema.semantictokens.SchemaSemanticTokens; +import ai.vespa.schemals.lsp.yqlplus.semantictokens.YQLPlusSemanticTokens; + +public class CommonSemanticTokens { + + private static List tokenTypes = new ArrayList<>(); + private static List tokenModifiers = new ArrayList<>(); + + public static SemanticTokensWithRegistrationOptions getSemanticTokensRegistrationOptions() { + + // Make sure that SchemaSemanticToken is initiated + SchemaSemanticTokens.init(); + YQLPlusSemanticTokens.init(); + + return new SemanticTokensWithRegistrationOptions( + new SemanticTokensLegend(tokenTypes, tokenModifiers), + new SemanticTokensServerFull(false) + ); + } + + private static int addUniqueToList(List list, String element) { + int index = list.indexOf(element); + if (index == -1) { + index = list.size(); + list.add(element); + } + return index; + } + + public static int addTokenType(String name) { + return addUniqueToList(tokenTypes, name); + } + + public static void addTokenTypes(List names) { + for (String name : names) { + addTokenType(name); + } + } + + public static Integer getType(String token) { + return tokenTypes.indexOf(token); + } + + public static int addTokenModifier(String modifier) { + return addUniqueToList(tokenModifiers, modifier); + } + + public static void addTokenModifiers(List modifiers) { + for (String modifier : modifiers) { + addTokenModifier(modifier); + } + } + + public static Integer getModifier(String modifier) { + return tokenModifiers.indexOf(modifier); + } +} diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/semantictokens/SemanticTokenMarker.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/semantictokens/SemanticTokenMarker.java new file mode 100644 index 000000000000..ccb7acbfd178 --- /dev/null +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/semantictokens/SemanticTokenMarker.java @@ -0,0 +1,71 @@ +package ai.vespa.schemals.lsp.common.semantictokens; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.lsp4j.Range; + +import ai.vespa.schemals.tree.SchemaNode; + +public class SemanticTokenMarker { + private static final int LINE_INDEX = 0; + private static final int COLUMN_INDEX = 1; + + private int tokenType; + private int modifierValue = 0; + private Range range; + + public SemanticTokenMarker(int tokenType, SchemaNode node) { + this(tokenType, node.getRange()); + } + + public SemanticTokenMarker(int tokenType, Range range) { + this.tokenType = tokenType; + this.range = range; + } + + public Range getRange() { return range; } + + public void addModifier(String modifier) { + int modifierIndex = CommonSemanticTokens.getModifier(modifier); + if (modifierIndex == -1) { + throw new IllegalArgumentException("Could not find the semantic token modifier '" + modifier + "'. Remember to add the modifier to the tokenModifiers list."); + } + int bitMask = 1 << modifierIndex; + modifierValue = modifierValue | bitMask; + } + + private ArrayList compactForm() { + int length = range.getEnd().getCharacter() - range.getStart().getCharacter(); + + return new ArrayList() {{ + add(range.getStart().getLine()); + add(range.getStart().getCharacter()); + add(length); + add(tokenType); + add(modifierValue); + }}; + } + + public static List concatCompactForm(List markers) { + List ret = new ArrayList<>(markers.size() * 5); + + if (markers.size() == 0) { + return ret; + } + + ret.addAll(markers.get(0).compactForm()); + + for (int i = 1; i < markers.size(); i++) { + ArrayList markerCompact = markers.get(i).compactForm(); + ArrayList lastMarkerCompact = markers.get(i - 1).compactForm(); + markerCompact.set(LINE_INDEX, markerCompact.get(LINE_INDEX) - lastMarkerCompact.get(LINE_INDEX)); + if (markerCompact.get(LINE_INDEX) == 0) { + markerCompact.set(COLUMN_INDEX, markerCompact.get(COLUMN_INDEX) - lastMarkerCompact.get(COLUMN_INDEX)); + } + ret.addAll(markerCompact); + } + + return ret; + } +} diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/codeaction/SchemaCodeAction.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/codeaction/SchemaCodeAction.java similarity index 81% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/codeaction/SchemaCodeAction.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/codeaction/SchemaCodeAction.java index 045d1bdf508b..4f3c4120ec6c 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/codeaction/SchemaCodeAction.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/codeaction/SchemaCodeAction.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.codeaction; +package ai.vespa.schemals.lsp.schema.codeaction; import java.util.ArrayList; import java.util.HashMap; @@ -10,9 +10,9 @@ import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.jsonrpc.messages.Either; -import ai.vespa.schemals.lsp.codeaction.provider.CodeActionProvider; -import ai.vespa.schemals.lsp.codeaction.provider.QuickFixProvider; -import ai.vespa.schemals.lsp.codeaction.provider.RefactorRewriteProvider; +import ai.vespa.schemals.lsp.schema.codeaction.provider.CodeActionProvider; +import ai.vespa.schemals.lsp.schema.codeaction.provider.QuickFixProvider; +import ai.vespa.schemals.lsp.schema.codeaction.provider.RefactorRewriteProvider; import ai.vespa.schemals.context.EventCodeActionContext; /** diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/codeaction/provider/CodeActionProvider.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/codeaction/provider/CodeActionProvider.java similarity index 86% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/codeaction/provider/CodeActionProvider.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/codeaction/provider/CodeActionProvider.java index 1f310a81a13d..61d89ab4f72d 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/codeaction/provider/CodeActionProvider.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/codeaction/provider/CodeActionProvider.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.codeaction.provider; +package ai.vespa.schemals.lsp.schema.codeaction.provider; import java.util.List; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/codeaction/provider/QuickFixProvider.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/codeaction/provider/QuickFixProvider.java similarity index 99% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/codeaction/provider/QuickFixProvider.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/codeaction/provider/QuickFixProvider.java index c520412f9f18..1bc265be9894 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/codeaction/provider/QuickFixProvider.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/codeaction/provider/QuickFixProvider.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.codeaction.provider; +package ai.vespa.schemals.lsp.schema.codeaction.provider; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -19,8 +19,8 @@ import ai.vespa.schemals.context.EventCodeActionContext; import ai.vespa.schemals.index.Symbol; import ai.vespa.schemals.index.Symbol.SymbolType; -import ai.vespa.schemals.lsp.codeaction.utils.CodeActionUtils; -import ai.vespa.schemals.lsp.rename.SchemaRename; +import ai.vespa.schemals.lsp.schema.codeaction.utils.CodeActionUtils; +import ai.vespa.schemals.lsp.schema.rename.SchemaRename; import ai.vespa.schemals.parser.Node; import ai.vespa.schemals.parser.ast.NL; import ai.vespa.schemals.parser.ast.RBRACE; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/codeaction/provider/RefactorRewriteProvider.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/codeaction/provider/RefactorRewriteProvider.java similarity index 94% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/codeaction/provider/RefactorRewriteProvider.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/codeaction/provider/RefactorRewriteProvider.java index 8d07d9482912..ee9e34d450f3 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/codeaction/provider/RefactorRewriteProvider.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/codeaction/provider/RefactorRewriteProvider.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.codeaction.provider; +package ai.vespa.schemals.lsp.schema.codeaction.provider; import java.net.URI; import java.nio.file.Path; @@ -20,8 +20,8 @@ import ai.vespa.schemals.common.StringUtils; import ai.vespa.schemals.common.editbuilder.WorkspaceEditBuilder; import ai.vespa.schemals.context.EventCodeActionContext; -import ai.vespa.schemals.lsp.command.CommandRegistry; -import ai.vespa.schemals.lsp.command.CommandRegistry.CommandType; +import ai.vespa.schemals.lsp.common.command.CommandRegistry; +import ai.vespa.schemals.lsp.common.command.CommandRegistry.CommandType; import ai.vespa.schemals.parser.ast.rankProfile; import ai.vespa.schemals.schemadocument.SchemaDocument; import ai.vespa.schemals.tree.CSTUtils; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/codeaction/utils/CodeActionUtils.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/codeaction/utils/CodeActionUtils.java similarity index 97% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/codeaction/utils/CodeActionUtils.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/codeaction/utils/CodeActionUtils.java index 5480c58934ff..930164722b50 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/codeaction/utils/CodeActionUtils.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/codeaction/utils/CodeActionUtils.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.codeaction.utils; +package ai.vespa.schemals.lsp.schema.codeaction.utils; import java.util.List; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/SchemaCompletion.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/SchemaCompletion.java similarity index 54% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/SchemaCompletion.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/SchemaCompletion.java index 44c31c92eaa6..3b0764a00fb4 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/SchemaCompletion.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/SchemaCompletion.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.completion; +package ai.vespa.schemals.lsp.schema.completion; import java.io.PrintStream; import java.util.ArrayList; @@ -7,17 +7,18 @@ import ai.vespa.schemals.common.StringUtils; import ai.vespa.schemals.context.EventCompletionContext; -import ai.vespa.schemals.lsp.completion.provider.BodyKeywordCompletion; -import ai.vespa.schemals.lsp.completion.provider.CompletionProvider; -import ai.vespa.schemals.lsp.completion.provider.EmptyFileCompletion; -import ai.vespa.schemals.lsp.completion.provider.FieldsCompletion; -import ai.vespa.schemals.lsp.completion.provider.IndexingLangaugeCompletion; -import ai.vespa.schemals.lsp.completion.provider.InheritanceCompletion; -import ai.vespa.schemals.lsp.completion.provider.InheritsCompletion; -import ai.vespa.schemals.lsp.completion.provider.RankingExpressionCompletion; -import ai.vespa.schemals.lsp.completion.provider.SimpleColonCompletion; -import ai.vespa.schemals.lsp.completion.provider.StructFieldCompletion; -import ai.vespa.schemals.lsp.completion.provider.TypeCompletion; +import ai.vespa.schemals.lsp.schema.completion.provider.BodyKeywordCompletion; +import ai.vespa.schemals.lsp.schema.completion.provider.CompletionProvider; +import ai.vespa.schemals.lsp.schema.completion.provider.EmptyFileCompletion; +import ai.vespa.schemals.lsp.schema.completion.provider.FieldsCompletion; +import ai.vespa.schemals.lsp.schema.completion.provider.IndexingLangaugeCompletion; +import ai.vespa.schemals.lsp.schema.completion.provider.InheritanceCompletion; +import ai.vespa.schemals.lsp.schema.completion.provider.InheritsCompletion; +import ai.vespa.schemals.lsp.schema.completion.provider.RankingExpressionCompletion; +import ai.vespa.schemals.lsp.schema.completion.provider.SimpleColonCompletion; +import ai.vespa.schemals.lsp.schema.completion.provider.StructFieldCompletion; +import ai.vespa.schemals.lsp.schema.completion.provider.TypeCompletion; +import ai.vespa.schemals.schemadocument.DocumentManager.DocumentType; /** * Responsible for LSP textDocument/completion requests. @@ -44,6 +45,10 @@ public static ArrayList getCompletionItems(EventCompletionContex return ret; } + if (context.document.getDocumentType() != DocumentType.SCHEMA && context.document.getDocumentType() != DocumentType.PROFILE) { + return ret; + } + for (CompletionProvider provider : providers) { try { ret.addAll(provider.getCompletionItems(context)); diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/BodyKeywordCompletion.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/BodyKeywordCompletion.java similarity index 98% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/BodyKeywordCompletion.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/BodyKeywordCompletion.java index a1a9a31c4b98..404b9a2b9c88 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/BodyKeywordCompletion.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/BodyKeywordCompletion.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.completion.provider; +package ai.vespa.schemals.lsp.schema.completion.provider; import java.util.HashMap; import java.util.List; @@ -11,8 +11,8 @@ import org.eclipse.lsp4j.Range; import ai.vespa.schemals.context.EventCompletionContext; -import ai.vespa.schemals.lsp.completion.utils.CompletionUtils; -import ai.vespa.schemals.lsp.hover.SchemaHover; +import ai.vespa.schemals.lsp.schema.completion.utils.CompletionUtils; +import ai.vespa.schemals.lsp.schema.hover.SchemaHover; import ai.vespa.schemals.parser.ast.NL; import ai.vespa.schemals.parser.ast.RootRankProfile; import ai.vespa.schemals.parser.ast.annotationBody; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/CompletionProvider.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/CompletionProvider.java similarity index 81% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/CompletionProvider.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/CompletionProvider.java index c22126227c0c..12a7695ca64c 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/CompletionProvider.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/CompletionProvider.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.completion.provider; +package ai.vespa.schemals.lsp.schema.completion.provider; import java.util.List; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/EmptyFileCompletion.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/EmptyFileCompletion.java similarity index 97% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/EmptyFileCompletion.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/EmptyFileCompletion.java index 366d8f2647e1..17b9239728b4 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/EmptyFileCompletion.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/EmptyFileCompletion.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.completion.provider; +package ai.vespa.schemals.lsp.schema.completion.provider; import java.util.ArrayList; import java.util.List; @@ -6,9 +6,9 @@ import org.eclipse.lsp4j.CompletionItem; import ai.vespa.schemals.common.FileUtils; -import ai.vespa.schemals.lsp.completion.utils.CompletionUtils; import ai.vespa.schemals.parser.ast.NL; import ai.vespa.schemals.context.EventCompletionContext; +import ai.vespa.schemals.lsp.schema.completion.utils.CompletionUtils; import ai.vespa.schemals.schemadocument.DocumentManager; import ai.vespa.schemals.tree.SchemaNode; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/FieldsCompletion.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/FieldsCompletion.java similarity index 96% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/FieldsCompletion.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/FieldsCompletion.java index 69764ecab4a5..42291e18ce49 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/FieldsCompletion.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/FieldsCompletion.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.completion.provider; +package ai.vespa.schemals.lsp.schema.completion.provider; import java.util.ArrayList; import java.util.EnumSet; @@ -11,10 +11,10 @@ import org.eclipse.lsp4j.Position; import ai.vespa.schemals.common.StringUtils; -import ai.vespa.schemals.lsp.completion.utils.CompletionUtils; import ai.vespa.schemals.context.EventCompletionContext; import ai.vespa.schemals.index.Symbol; import ai.vespa.schemals.index.Symbol.SymbolType; +import ai.vespa.schemals.lsp.schema.completion.utils.CompletionUtils; import ai.vespa.schemals.parser.Token.TokenType; import ai.vespa.schemals.schemadocument.SchemaDocument; import ai.vespa.schemals.tree.CSTUtils; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/FixedKeywordBodies.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/FixedKeywordBodies.java similarity index 98% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/FixedKeywordBodies.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/FixedKeywordBodies.java index aaecad13de78..42f64e83e24e 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/FixedKeywordBodies.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/FixedKeywordBodies.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.completion.provider; +package ai.vespa.schemals.lsp.schema.completion.provider; import java.util.List; @@ -6,7 +6,7 @@ import org.eclipse.lsp4j.CompletionItemKind; import ai.vespa.schemals.common.LocaleList; -import ai.vespa.schemals.lsp.completion.utils.CompletionUtils; +import ai.vespa.schemals.lsp.schema.completion.utils.CompletionUtils; import ai.vespa.schemals.parser.Node; import ai.vespa.schemals.parser.Token.TokenType; import ai.vespa.schemals.parser.ast.attributeElm; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/IndexingLangaugeCompletion.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/IndexingLangaugeCompletion.java similarity index 97% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/IndexingLangaugeCompletion.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/IndexingLangaugeCompletion.java index 0d2d42b0e228..fee778da81f9 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/IndexingLangaugeCompletion.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/IndexingLangaugeCompletion.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.completion.provider; +package ai.vespa.schemals.lsp.schema.completion.provider; import java.util.List; @@ -6,7 +6,7 @@ import org.eclipse.lsp4j.Position; import ai.vespa.schemals.context.EventCompletionContext; -import ai.vespa.schemals.lsp.completion.utils.CompletionUtils; +import ai.vespa.schemals.lsp.schema.completion.utils.CompletionUtils; import ai.vespa.schemals.parser.ast.COLON; import ai.vespa.schemals.parser.ast.INDEXING; import ai.vespa.schemals.tree.CSTUtils; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/InheritanceCompletion.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/InheritanceCompletion.java similarity index 96% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/InheritanceCompletion.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/InheritanceCompletion.java index f056cb2ac36a..32ced628c5cb 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/InheritanceCompletion.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/InheritanceCompletion.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.completion.provider; +package ai.vespa.schemals.lsp.schema.completion.provider; import java.util.ArrayList; import java.util.List; @@ -7,10 +7,10 @@ import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.Position; -import ai.vespa.schemals.lsp.completion.utils.CompletionUtils; import ai.vespa.schemals.context.EventCompletionContext; import ai.vespa.schemals.index.Symbol; import ai.vespa.schemals.index.Symbol.SymbolType; +import ai.vespa.schemals.lsp.schema.completion.utils.CompletionUtils; import ai.vespa.schemals.parser.Token.TokenType; import ai.vespa.schemals.parser.ast.COMMA; import ai.vespa.schemals.parser.ast.IDENTIFIER; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/InheritsCompletion.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/InheritsCompletion.java similarity index 97% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/InheritsCompletion.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/InheritsCompletion.java index 6de8a3205e2d..98347ab8fe3e 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/InheritsCompletion.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/InheritsCompletion.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.completion.provider; +package ai.vespa.schemals.lsp.schema.completion.provider; import java.util.ArrayList; import java.util.List; @@ -7,7 +7,6 @@ import org.eclipse.lsp4j.CompletionItem; -import ai.vespa.schemals.lsp.completion.utils.*; import ai.vespa.schemals.parser.Token.TokenType; import ai.vespa.schemals.parser.ast.identifierStr; import ai.vespa.schemals.parser.ast.identifierWithDashStr; @@ -17,6 +16,7 @@ import ai.vespa.schemals.tree.SchemaNode; import ai.vespa.schemals.index.Symbol; import ai.vespa.schemals.index.Symbol.SymbolType; +import ai.vespa.schemals.lsp.schema.completion.utils.*; /** * InheritsCompletionProvider diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/RankingExpressionCompletion.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/RankingExpressionCompletion.java similarity index 98% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/RankingExpressionCompletion.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/RankingExpressionCompletion.java index 81983964ffbc..386f82e45c6a 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/RankingExpressionCompletion.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/RankingExpressionCompletion.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.completion.provider; +package ai.vespa.schemals.lsp.schema.completion.provider; import java.util.ArrayList; import java.util.HashSet; @@ -12,8 +12,8 @@ import ai.vespa.schemals.context.EventCompletionContext; import ai.vespa.schemals.index.Symbol; import ai.vespa.schemals.index.Symbol.SymbolType; -import ai.vespa.schemals.lsp.completion.utils.CompletionUtils; -import ai.vespa.schemals.lsp.hover.SchemaHover; +import ai.vespa.schemals.lsp.schema.completion.utils.CompletionUtils; +import ai.vespa.schemals.lsp.schema.hover.SchemaHover; import ai.vespa.schemals.parser.ast.NL; import ai.vespa.schemals.parser.ast.expression; import ai.vespa.schemals.parser.ast.featureListElm; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/SimpleColonCompletion.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/SimpleColonCompletion.java similarity index 93% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/SimpleColonCompletion.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/SimpleColonCompletion.java index 21f9b6ce65e9..1b32a262a52b 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/SimpleColonCompletion.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/SimpleColonCompletion.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.completion.provider; +package ai.vespa.schemals.lsp.schema.completion.provider; import java.util.List; @@ -6,7 +6,7 @@ import org.eclipse.lsp4j.CompletionItemKind; import ai.vespa.schemals.context.EventCompletionContext; -import ai.vespa.schemals.lsp.completion.provider.FixedKeywordBodies.FixedKeywordBody; +import ai.vespa.schemals.lsp.schema.completion.provider.FixedKeywordBodies.FixedKeywordBody; import ai.vespa.schemals.parser.Token.TokenType; import ai.vespa.schemals.schemadocument.SchemaDocument; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/StructFieldCompletion.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/StructFieldCompletion.java similarity index 95% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/StructFieldCompletion.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/StructFieldCompletion.java index 8c306e5ae7a3..f881b96fb486 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/StructFieldCompletion.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/StructFieldCompletion.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.completion.provider; +package ai.vespa.schemals.lsp.schema.completion.provider; import java.util.EnumSet; import java.util.List; @@ -9,7 +9,7 @@ import ai.vespa.schemals.context.EventCompletionContext; import ai.vespa.schemals.index.Symbol; import ai.vespa.schemals.index.Symbol.SymbolType; -import ai.vespa.schemals.lsp.completion.utils.CompletionUtils; +import ai.vespa.schemals.lsp.schema.completion.utils.CompletionUtils; import ai.vespa.schemals.parser.ast.NL; import ai.vespa.schemals.parser.ast.fieldElm; import ai.vespa.schemals.parser.ast.openLbrace; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/TypeCompletion.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/TypeCompletion.java similarity index 97% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/TypeCompletion.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/TypeCompletion.java index c0c1f21cf22e..38e57f44dfa9 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/provider/TypeCompletion.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/provider/TypeCompletion.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.completion.provider; +package ai.vespa.schemals.lsp.schema.completion.provider; import java.util.ArrayList; import java.util.List; @@ -8,7 +8,6 @@ import ai.vespa.schemals.common.StringUtils; import ai.vespa.schemals.context.EventCompletionContext; -import ai.vespa.schemals.lsp.completion.utils.CompletionUtils; import ai.vespa.schemals.parser.Token.TokenType; import ai.vespa.schemals.schemadocument.SchemaDocumentLexer; import ai.vespa.schemals.schemadocument.SchemaDocument; @@ -16,6 +15,7 @@ import ai.vespa.schemals.tree.SchemaNode; import ai.vespa.schemals.index.Symbol; import ai.vespa.schemals.index.Symbol.SymbolType; +import ai.vespa.schemals.lsp.schema.completion.utils.CompletionUtils; /** * For completing types for a field, as well as type parameters in tensor types. diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/utils/CompletionUtils.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/utils/CompletionUtils.java similarity index 98% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/utils/CompletionUtils.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/utils/CompletionUtils.java index d7815d058c3e..cbe868dd6090 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/completion/utils/CompletionUtils.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/completion/utils/CompletionUtils.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.completion.utils; +package ai.vespa.schemals.lsp.schema.completion.utils; import java.util.ArrayList; import java.util.List; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/definition/SchemaDefinition.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/definition/SchemaDefinition.java similarity index 96% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/definition/SchemaDefinition.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/definition/SchemaDefinition.java index b0fb33bdd136..f44f07d61c9f 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/definition/SchemaDefinition.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/definition/SchemaDefinition.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.definition; +package ai.vespa.schemals.lsp.schema.definition; import java.util.ArrayList; import java.util.List; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/documentsymbols/SchemaDocumentSymbols.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/documentsymbols/SchemaDocumentSymbols.java similarity index 98% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/documentsymbols/SchemaDocumentSymbols.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/documentsymbols/SchemaDocumentSymbols.java index ddd9fc27ff3d..9eab2a42e8d9 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/documentsymbols/SchemaDocumentSymbols.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/documentsymbols/SchemaDocumentSymbols.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.documentsymbols; +package ai.vespa.schemals.lsp.schema.documentsymbols; import java.util.ArrayList; import java.util.EnumSet; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/hover/SchemaHover.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/hover/SchemaHover.java similarity index 98% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/hover/SchemaHover.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/hover/SchemaHover.java index 8bd7be8536ba..6504f9666153 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/hover/SchemaHover.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/hover/SchemaHover.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.hover; +package ai.vespa.schemals.lsp.schema.hover; import java.io.BufferedReader; import java.io.IOException; @@ -23,7 +23,7 @@ import ai.vespa.schemals.index.Symbol; import ai.vespa.schemals.index.Symbol.SymbolStatus; import ai.vespa.schemals.index.Symbol.SymbolType; -import ai.vespa.schemals.lsp.completion.SchemaCompletion; +import ai.vespa.schemals.lsp.schema.completion.SchemaCompletion; import ai.vespa.schemals.parser.indexinglanguage.ast.ATTRIBUTE; import ai.vespa.schemals.parser.indexinglanguage.ast.INDEX; import ai.vespa.schemals.parser.indexinglanguage.ast.SUMMARY; @@ -39,8 +39,6 @@ * As a TODO it would have been nice with some highlighting on the generated Markdown. */ public class SchemaHover { - private static final String markdownPathRoot = "hover/"; - private static Map> markdownContentCache = new HashMap<>(); /** @@ -220,9 +218,9 @@ private static Hover getSymbolHover(SchemaNode node, EventPositionContext contex private static Hover getIndexingHover(SchemaNode hoverNode, EventPositionContext context) { // Taken from: // https://docs.vespa.ai/en/reference/schema-reference.html#indexing - // Too specific to include in buildDocs.py + // Too specific to include in documentation fetcher IMO. - // Note: these classes belong to the indexinglanguage parser + // Note: these AST classes belong to the indexinglanguage parser if (hoverNode.isASTInstance(ATTRIBUTE.class)) { return new Hover(new MarkupContent(MarkupKind.MARKDOWN, "## Attribute\n" diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/references/SchemaReferences.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/references/SchemaReferences.java similarity index 97% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/references/SchemaReferences.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/references/SchemaReferences.java index cfca6ca5a9f2..7fa53713ad0e 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/references/SchemaReferences.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/references/SchemaReferences.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.references; +package ai.vespa.schemals.lsp.schema.references; import java.util.ArrayList; import java.util.List; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/rename/SchemaPrepareRename.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/rename/SchemaPrepareRename.java similarity index 95% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/rename/SchemaPrepareRename.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/rename/SchemaPrepareRename.java index 30cde4aa25ca..857ecc3da503 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/rename/SchemaPrepareRename.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/rename/SchemaPrepareRename.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.rename; +package ai.vespa.schemals.lsp.schema.rename; import org.eclipse.lsp4j.PrepareRenameDefaultBehavior; import org.eclipse.lsp4j.PrepareRenameResult; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/rename/SchemaRename.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/rename/SchemaRename.java similarity index 99% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/rename/SchemaRename.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/rename/SchemaRename.java index 7703c6c52a46..c7df532f4800 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/rename/SchemaRename.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/rename/SchemaRename.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.rename; +package ai.vespa.schemals.lsp.schema.rename; import java.util.ArrayList; import java.util.List; diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/semantictokens/SemanticTokenConfig.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/semantictokens/SchemaSemanticTokenConfig.java similarity index 99% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/semantictokens/SemanticTokenConfig.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/semantictokens/SchemaSemanticTokenConfig.java index c5d4f788585f..6696b9094381 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/semantictokens/SemanticTokenConfig.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/semantictokens/SchemaSemanticTokenConfig.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.semantictokens; +package ai.vespa.schemals.lsp.schema.semantictokens; import java.util.ArrayList; import java.util.HashMap; @@ -13,7 +13,7 @@ import ai.vespa.schemals.index.Symbol.SymbolType; import ai.vespa.schemals.parser.Token.TokenType; -class SemanticTokenConfig { +class SchemaSemanticTokenConfig { static final ArrayList manuallyRegisteredLSPNames = new ArrayList() {{ add(SemanticTokenTypes.Type); add(SemanticTokenTypes.Comment); @@ -23,12 +23,6 @@ class SemanticTokenConfig { add(SemanticTokenTypes.Property); }}; - static final List tokenModifiers = new ArrayList<>() {{ - add(SemanticTokenModifiers.Definition); - add(SemanticTokenModifiers.Readonly); - add(SemanticTokenModifiers.DefaultLibrary); - }}; - static final List userDefinedSymbolTypes = new ArrayList() {{ add(SymbolType.SCHEMA); add(SymbolType.DOCUMENT); @@ -50,6 +44,12 @@ class SemanticTokenConfig { add(SymbolType.DIMENSION); }}; + static final List tokenModifiers = new ArrayList<>() {{ + add(SemanticTokenModifiers.Definition); + add(SemanticTokenModifiers.Readonly); + add(SemanticTokenModifiers.DefaultLibrary); + }}; + // Keyword static final ArrayList keywordTokens = new ArrayList() {{ add(TokenType.ALIAS); diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/semantictokens/SchemaSemanticTokens.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/semantictokens/SchemaSemanticTokens.java similarity index 68% rename from integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/semantictokens/SchemaSemanticTokens.java rename to integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/semantictokens/SchemaSemanticTokens.java index 9abcd9aabe27..0854255916ae 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/semantictokens/SchemaSemanticTokens.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/schema/semantictokens/SchemaSemanticTokens.java @@ -1,4 +1,4 @@ -package ai.vespa.schemals.lsp.semantictokens; +package ai.vespa.schemals.lsp.schema.semantictokens; import java.util.ArrayList; import java.util.HashMap; @@ -12,9 +12,6 @@ import org.eclipse.lsp4j.SemanticTokenModifiers; import org.eclipse.lsp4j.SemanticTokenTypes; import org.eclipse.lsp4j.SemanticTokens; -import org.eclipse.lsp4j.SemanticTokensLegend; -import org.eclipse.lsp4j.SemanticTokensServerFull; -import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions; import ai.vespa.schemals.tree.CSTUtils; import ai.vespa.schemals.tree.SchemaNode; @@ -23,6 +20,8 @@ import ai.vespa.schemals.context.EventDocumentContext; import ai.vespa.schemals.index.Symbol.SymbolStatus; import ai.vespa.schemals.index.Symbol.SymbolType; +import ai.vespa.schemals.lsp.common.semantictokens.CommonSemanticTokens; +import ai.vespa.schemals.lsp.common.semantictokens.SemanticTokenMarker; import ai.vespa.schemals.parser.Token.TokenType; import ai.vespa.schemals.parser.TokenSource; import ai.vespa.schemals.parser.ast.FILTER; @@ -46,138 +45,58 @@ public class SchemaSemanticTokens { private static Map identifierTypeMap; - private static ArrayList tokenTypes; private static Map schemaTokenTypeMap; private static Map rankExpressionTokenTypeMap; - private static int addTokenType(String name) { - int index = tokenTypes.indexOf(name); - if (index == -1) { - index = tokenTypes.size(); - tokenTypes.add(name); - } - return index; - } - - static { - tokenTypes = new ArrayList(); + public static void init() { // Manually added semantic tokens - tokenTypes.addAll(SemanticTokenConfig.manuallyRegisteredLSPNames); - int keywordIndex = addTokenType(SemanticTokenTypes.Keyword); + CommonSemanticTokens.addTokenTypes(SchemaSemanticTokenConfig.manuallyRegisteredLSPNames); + int keywordIndex = CommonSemanticTokens.addTokenType(SemanticTokenTypes.Keyword); // Add symbol semantic tokens identifierTypeMap = new HashMap(); - for (Map.Entry set : SemanticTokenConfig.identifierTypeLSPNameMap.entrySet()) { - int index = addTokenType(set.getValue()); + for (Map.Entry set : SchemaSemanticTokenConfig.identifierTypeLSPNameMap.entrySet()) { + int index = CommonSemanticTokens.addTokenType(set.getValue()); identifierTypeMap.put(set.getKey(), index); } // Create Map for Schema Tokens schemaTokenTypeMap = new HashMap(); - for (var set : SemanticTokenConfig.schemaTokenTypeLSPNameMap.entrySet()) { - int index = addTokenType(set.getValue()); + for (var set : SchemaSemanticTokenConfig.schemaTokenTypeLSPNameMap.entrySet()) { + int index = CommonSemanticTokens.addTokenType(set.getValue()); schemaTokenTypeMap.put(set.getKey(), index); } - for (TokenType type : SemanticTokenConfig.keywordTokens) { + for (TokenType type : SchemaSemanticTokenConfig.keywordTokens) { schemaTokenTypeMap.put(type, keywordIndex); } // Create Map for RankExpression Tokens rankExpressionTokenTypeMap = new HashMap(); - for (var set : SemanticTokenConfig.rankingExpressionTokenTypeLSPNameMap.entrySet()) { - int index = addTokenType(set.getValue()); + for (var set : SchemaSemanticTokenConfig.rankingExpressionTokenTypeLSPNameMap.entrySet()) { + int index = CommonSemanticTokens.addTokenType(set.getValue()); rankExpressionTokenTypeMap.put(set.getKey(), index); } - for (var type : SemanticTokenConfig.rankingExpressionKeywordTokens) { + for (var type : SchemaSemanticTokenConfig.rankingExpressionKeywordTokens) { rankExpressionTokenTypeMap.put(type, keywordIndex); } - int operationIndex = addTokenType(SemanticTokenTypes.Operator); - for (var type : SemanticTokenConfig.rankingExpressionOperationTokens) { + int operationIndex = CommonSemanticTokens.addTokenType(SemanticTokenTypes.Operator); + for (var type : SchemaSemanticTokenConfig.rankingExpressionOperationTokens) { rankExpressionTokenTypeMap.put(type, operationIndex); } - int functionIndex = addTokenType(SemanticTokenTypes.Function); - for (var type : SemanticTokenConfig.rankingExpressioFunctionTokens) { + int functionIndex = CommonSemanticTokens.addTokenType(SemanticTokenTypes.Function); + for (var type : SchemaSemanticTokenConfig.rankingExpressioFunctionTokens) { rankExpressionTokenTypeMap.put(type, functionIndex); } - } - - public static SemanticTokensWithRegistrationOptions getSemanticTokensRegistrationOptions() { - return new SemanticTokensWithRegistrationOptions( - new SemanticTokensLegend(tokenTypes, SemanticTokenConfig.tokenModifiers), - new SemanticTokensServerFull(false) - ); - } - - private static class SemanticTokenMarker { - private static final int LINE_INDEX = 0; - private static final int COLUMN_INDEX = 1; - - private int tokenType; - private int modifierValue = 0; - private Range range; - - SemanticTokenMarker(int tokenType, SchemaNode node) { - this(tokenType, node.getRange()); - } - - SemanticTokenMarker(int tokenType, Range range) { - this.tokenType = tokenType; - this.range = range; - } - - Range getRange() { return range; } - - void addModifier(String modifier) { - int modifierIndex = SemanticTokenConfig.tokenModifiers.indexOf(modifier); - if (modifierIndex == -1) { - throw new IllegalArgumentException("Could not find the semantic token modifier '" + modifier + "'. Remeber to add the modifer to the tokenModifiers list."); - } - int bitMask = 1 << modifierIndex; - modifierValue = modifierValue | bitMask; - } - - private ArrayList compactForm() { - int length = range.getEnd().getCharacter() - range.getStart().getCharacter(); - - return new ArrayList() {{ - add(range.getStart().getLine()); - add(range.getStart().getCharacter()); - add(length); - add(tokenType); - add(modifierValue); - }}; - } - - static ArrayList concatCompactForm(ArrayList markers) { - ArrayList ret = new ArrayList<>(markers.size() * 5); - - if (markers.size() == 0) { - return ret; - } - - ret.addAll(markers.get(0).compactForm()); - - for (int i = 1; i < markers.size(); i++) { - ArrayList markerCompact = markers.get(i).compactForm(); - ArrayList lastMarkerCompact = markers.get(i - 1).compactForm(); - markerCompact.set(LINE_INDEX, markerCompact.get(LINE_INDEX) - lastMarkerCompact.get(LINE_INDEX)); - if (markerCompact.get(LINE_INDEX) == 0) { - markerCompact.set(COLUMN_INDEX, markerCompact.get(COLUMN_INDEX) - lastMarkerCompact.get(COLUMN_INDEX)); - } - ret.addAll(markerCompact); - } - - return ret; - } + CommonSemanticTokens.addTokenModifiers(SchemaSemanticTokenConfig.tokenModifiers); } private static ArrayList traverseCST(SchemaNode node, ClientLogger logger) { @@ -190,7 +109,7 @@ private static ArrayList traverseCST(SchemaNode node, Clien // TODO: this became a bit ugly with the map stuff if (node.isASTInstance(dataType.class) && (!node.hasSymbol() || node.getSymbol().getType() == SymbolType.MAP_KEY || node.getSymbol().getType() == SymbolType.MAP_VALUE)) { - Integer tokenType = tokenTypes.indexOf("type"); + Integer tokenType = CommonSemanticTokens.getType("type"); if (tokenType != -1) { // this will leave <> uncolored, as we are only interested in marking the actual token Range markerRange = CSTUtils.findFirstLeafChild(node).getRange(); @@ -203,7 +122,7 @@ private static ArrayList traverseCST(SchemaNode node, Clien } else if (node.isASTInstance(valueType.class)) { - Integer tokenType = tokenTypes.indexOf("type"); + Integer tokenType = CommonSemanticTokens.getType("type"); if (tokenType != -1) { ret.add(new SemanticTokenMarker(tokenType, node)); } @@ -213,7 +132,7 @@ private static ArrayList traverseCST(SchemaNode node, Clien Integer tokenType = null; String modifier = null; if (isEnumLike(node)) { - tokenType = tokenTypes.indexOf(SemanticTokenTypes.Property); + tokenType = CommonSemanticTokens.getType(SemanticTokenTypes.Property); modifier = SemanticTokenModifiers.Readonly; } if (tokenType == null) { @@ -233,20 +152,20 @@ private static ArrayList traverseCST(SchemaNode node, Clien } } else if (indexinglanguageType != null) { - if (SemanticTokenConfig.indexingLanguageOutputs.contains(indexinglanguageType)) { - Integer tokenType = tokenTypes.indexOf("type"); + if (SchemaSemanticTokenConfig.indexingLanguageOutputs.contains(indexinglanguageType)) { + Integer tokenType = CommonSemanticTokens.getType("type"); Range markerRange = CSTUtils.findFirstLeafChild(node).getRange(); ret.add(new SemanticTokenMarker(tokenType, markerRange)); - } else if (SemanticTokenConfig.indexingLanguageKeywords.contains(indexinglanguageType)) { - Integer tokenType = tokenTypes.indexOf("keyword"); + } else if (SchemaSemanticTokenConfig.indexingLanguageKeywords.contains(indexinglanguageType)) { + Integer tokenType = CommonSemanticTokens.getType("keyword"); Range markerRange = CSTUtils.findFirstLeafChild(node).getRange(); ret.add(new SemanticTokenMarker(tokenType, markerRange)); - } else if (SemanticTokenConfig.indexingLanguageOperators.contains(indexinglanguageType)) { - Integer tokenType = tokenTypes.indexOf("function"); + } else if (SchemaSemanticTokenConfig.indexingLanguageOperators.contains(indexinglanguageType)) { + Integer tokenType = CommonSemanticTokens.getType("function"); Range markerRange = CSTUtils.findFirstLeafChild(node).getRange(); ret.add(new SemanticTokenMarker(tokenType, markerRange)); } else if (indexinglanguageType == ai.vespa.schemals.parser.indexinglanguage.Token.TokenType.STRING) { - Integer tokenType = tokenTypes.indexOf("string"); + Integer tokenType = CommonSemanticTokens.getType("string"); Range markerRange = CSTUtils.findFirstLeafChild(node).getRange(); ret.add(new SemanticTokenMarker(tokenType, markerRange)); } @@ -298,7 +217,7 @@ private static List findSemanticMarkersForSymbol(SchemaNode if (!node.hasSymbol()) return ret; - if (SemanticTokenConfig.userDefinedSymbolTypes.contains(node.getSymbol().getType()) && ( + if (SchemaSemanticTokenConfig.userDefinedSymbolTypes.contains(node.getSymbol().getType()) && ( node.getSymbol().getStatus() == SymbolStatus.REFERENCE || node.getSymbol().getStatus() == SymbolStatus.DEFINITION )) { @@ -317,7 +236,7 @@ private static List findSemanticMarkersForSymbol(SchemaNode Integer tokenType = identifierTypeMap.get(type); if (type == SymbolType.FUNCTION && node.getLanguageType() == LanguageType.RANK_EXPRESSION) { - tokenType = tokenTypes.indexOf("macro"); + tokenType = CommonSemanticTokens.getType("macro"); } if (tokenType != null && tokenType != -1) { @@ -333,7 +252,7 @@ private static List findSemanticMarkersForSymbol(SchemaNode private static ArrayList convertCommentRanges(ArrayList comments) { ArrayList ret = new ArrayList<>(); - int tokenType = tokenTypes.indexOf("comment"); + int tokenType = CommonSemanticTokens.getType("comment"); for (Range range : comments) { ret.add(new SemanticTokenMarker(tokenType, range)); @@ -418,7 +337,7 @@ public static SemanticTokens getSemanticTokens(EventDocumentContext context) { ArrayList comments = findComments(context.document.getRootNode()); ArrayList markers = traverseCST(node, context.logger); - ArrayList compactMarkers = SemanticTokenMarker.concatCompactForm( + List compactMarkers = SemanticTokenMarker.concatCompactForm( mergeSemanticTokenMarkers(markers, comments) ); diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/yqlplus/codelens/YQLPlusCodeLens.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/yqlplus/codelens/YQLPlusCodeLens.java new file mode 100644 index 000000000000..a10ed3b646ae --- /dev/null +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/yqlplus/codelens/YQLPlusCodeLens.java @@ -0,0 +1,29 @@ +package ai.vespa.schemals.lsp.yqlplus.codelens; + +import java.util.List; + +import org.eclipse.lsp4j.CodeLens; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; + +import ai.vespa.schemals.context.EventDocumentContext; +import ai.vespa.schemals.lsp.common.command.CommandRegistry; +import ai.vespa.schemals.lsp.common.command.CommandRegistry.CommandType; +import ai.vespa.schemals.schemadocument.DocumentManager.DocumentType; + +public class YQLPlusCodeLens { + + + static public List codeLens(EventDocumentContext context) { + + if (context.document.getDocumentType() != DocumentType.YQL) { + return List.of(); + } + + return List.of(new CodeLens( + new Range(new Position(1, 1), new Position(1, 4)), + CommandRegistry.createLSPCommand(CommandType.RUN_VESPA_QUERY, List.of()), + null + )); + } +} diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/yqlplus/semantictokens/YQLPlusSemanticTokenConfig.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/yqlplus/semantictokens/YQLPlusSemanticTokenConfig.java new file mode 100644 index 000000000000..b0d94d1458bc --- /dev/null +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/yqlplus/semantictokens/YQLPlusSemanticTokenConfig.java @@ -0,0 +1,16 @@ +package ai.vespa.schemals.lsp.yqlplus.semantictokens; + +import java.util.ArrayList; +import java.util.List; + +import ai.vespa.schemals.parser.yqlplus.Token.TokenType; + +class YQLPlusSemanticTokenConfig { + + // Keyword + static final List keywordTokens = new ArrayList() {{ + add(TokenType.SELECT); + add(TokenType.FROM); + add(TokenType.WHERE); + }}; +} diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/yqlplus/semantictokens/YQLPlusSemanticTokens.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/yqlplus/semantictokens/YQLPlusSemanticTokens.java new file mode 100644 index 000000000000..527183110140 --- /dev/null +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/yqlplus/semantictokens/YQLPlusSemanticTokens.java @@ -0,0 +1,43 @@ +package ai.vespa.schemals.lsp.yqlplus.semantictokens; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.lsp4j.SemanticTokenTypes; +import org.eclipse.lsp4j.SemanticTokens; + +import ai.vespa.schemals.common.ClientLogger; +import ai.vespa.schemals.context.EventDocumentContext; +import ai.vespa.schemals.lsp.common.semantictokens.CommonSemanticTokens; +import ai.vespa.schemals.lsp.common.semantictokens.SemanticTokenMarker; +import ai.vespa.schemals.tree.YQLNode; + +public class YQLPlusSemanticTokens { + + public static void init() { + CommonSemanticTokens.addTokenType(SemanticTokenTypes.Keyword); + } + + private static List traverseCST(YQLNode node, ClientLogger logger) { + + + return new ArrayList(); + } + + public static SemanticTokens getSemanticTokens(EventDocumentContext context) { + + if (context.document == null) { + return new SemanticTokens(new ArrayList<>()); + } + + YQLNode node = context.document.getRootYQLNode(); + if (node == null) { + return new SemanticTokens(new ArrayList<>()); + } + + List markers = traverseCST(node, context.logger); + List compactMarkers = SemanticTokenMarker.concatCompactForm(markers); + + return new SemanticTokens(compactMarkers); + } +} diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/DocumentManager.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/DocumentManager.java index 0f4e5c1979a4..3b1132e77000 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/DocumentManager.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/DocumentManager.java @@ -3,6 +3,7 @@ import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; import ai.vespa.schemals.tree.SchemaNode; +import ai.vespa.schemals.tree.YQLNode; /** * DocumentManager @@ -10,15 +11,22 @@ */ public interface DocumentManager { + public enum DocumentType { + SCHEMA, + PROFILE, + YQL + } + public void updateFileContent(String content); public void updateFileContent(String content, Integer version); public void reparseContent(); - public boolean setIsOpen(boolean isOpen); + public void setIsOpen(boolean isOpen); public boolean getIsOpen(); public SchemaNode getRootNode(); + public YQLNode getRootYQLNode(); public SchemaDocumentLexer lexer(); @@ -27,4 +35,6 @@ public interface DocumentManager { public String getCurrentContent(); public VersionedTextDocumentIdentifier getVersionedTextDocumentIdentifier(); + + public DocumentType getDocumentType(); } diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/RankProfileDocument.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/RankProfileDocument.java index 478a92849f0e..2904f5c124eb 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/RankProfileDocument.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/RankProfileDocument.java @@ -15,6 +15,7 @@ import ai.vespa.schemals.schemadocument.resolvers.InheritanceResolver; import ai.vespa.schemals.schemadocument.resolvers.ResolverTraversal; import ai.vespa.schemals.tree.SchemaNode; +import ai.vespa.schemals.tree.YQLNode; /** * RankProfileDocumnet parses and represents .profile files @@ -113,9 +114,8 @@ public void reparseContent() { } @Override - public boolean setIsOpen(boolean isOpen) { + public void setIsOpen(boolean isOpen) { this.isOpen = isOpen; - return isOpen; } @Override @@ -128,6 +128,11 @@ public SchemaNode getRootNode() { return this.CST; } + @Override + public YQLNode getRootYQLNode() { + return null; + } + @Override public String getCurrentContent() { return this.content; @@ -142,4 +147,7 @@ public VersionedTextDocumentIdentifier getVersionedTextDocumentIdentifier() { public SchemaDocumentLexer lexer() { return this.lexer; } + + @Override + public DocumentType getDocumentType() { return DocumentType.PROFILE; } } diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/SchemaDocument.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/SchemaDocument.java index 61dbcc967fbb..d1cdd91fcf57 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/SchemaDocument.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/SchemaDocument.java @@ -38,6 +38,7 @@ import ai.vespa.schemals.schemadocument.resolvers.RankExpression.argument.FieldArgument.UnresolvedFieldArgument; import ai.vespa.schemals.tree.CSTUtils; import ai.vespa.schemals.tree.SchemaNode; +import ai.vespa.schemals.tree.YQLNode; import ai.vespa.schemals.tree.SchemaNode.LanguageType; import ai.vespa.schemals.tree.indexinglanguage.ILUtils; @@ -156,9 +157,8 @@ private List verifyFileName() { public boolean getIsOpen() { return isOpen; } @Override - public boolean setIsOpen(boolean value) { + public void setIsOpen(boolean value) { isOpen = value; - return isOpen; } public String getSchemaIdentifier() { @@ -190,6 +190,10 @@ public SchemaNode getRootNode() { return CST; } + public YQLNode getRootYQLNode() { + return null; + } + public static ParseResult parseContent(ParseContext context) { CharSequence sequence = context.content(); @@ -348,4 +352,7 @@ public String getCurrentContent() { public SchemaDocumentLexer lexer() { return this.lexer; } + + @Override + public DocumentType getDocumentType() { return DocumentType.SCHEMA; } } diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/SchemaDocumentScheduler.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/SchemaDocumentScheduler.java index ea082e3a3868..e38bedd2f15d 100644 --- a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/SchemaDocumentScheduler.java +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/SchemaDocumentScheduler.java @@ -16,11 +16,12 @@ import ai.vespa.schemals.SchemaMessageHandler; import ai.vespa.schemals.common.ClientLogger; import ai.vespa.schemals.common.FileUtils; -import ai.vespa.schemals.common.StringUtils; import ai.vespa.schemals.index.SchemaIndex; import ai.vespa.schemals.index.Symbol; import ai.vespa.schemals.index.Symbol.SymbolType; +import ai.vespa.schemals.schemadocument.DocumentManager.DocumentType; + /** * Class responsible for maintaining the set of open documents and reparsing them. * When {@link SchemaDocumentScheduler#updateFile} is called, it will call {@link DocumentManager#updateFileContent} on the appropriate file @@ -28,6 +29,12 @@ */ public class SchemaDocumentScheduler { + private Map fileExtensions = new HashMap<>() {{ + put("sd", DocumentType.SCHEMA); + put("profile", DocumentType.PROFILE); + put("yql", DocumentType.YQL); + }}; + private ClientLogger logger; private SchemaDiagnosticsHandler diagnosticsHandler; private SchemaIndex schemaIndex; @@ -47,13 +54,30 @@ public void updateFile(String fileURI, String content) { updateFile(fileURI, content, null); } + private Optional getDocumentTypeFromURI(String fileURI) { + int dotIndex = fileURI.lastIndexOf('.'); + String fileExtension = fileURI.substring(dotIndex + 1).toLowerCase(); + + DocumentType documentType = fileExtensions.get(fileExtension); + if (documentType == null) return Optional.empty(); + return Optional.of(documentType); + } + public void updateFile(String fileURI, String content, Integer version) { - boolean isSchemaFile = fileURI.toLowerCase().endsWith(".sd"); + Optional documentType = getDocumentTypeFromURI(fileURI); + if (documentType.isEmpty()) return; + if (!workspaceFiles.containsKey(fileURI)) { - if (isSchemaFile) { - workspaceFiles.put(fileURI, new SchemaDocument(logger, diagnosticsHandler, schemaIndex, this, fileURI)); - } else { - workspaceFiles.put(fileURI, new RankProfileDocument(logger, diagnosticsHandler, schemaIndex, this, fileURI)); + switch(documentType.get()) { + case PROFILE: + workspaceFiles.put(fileURI, new RankProfileDocument(logger, diagnosticsHandler, schemaIndex, this, fileURI)); + break; + case SCHEMA: + workspaceFiles.put(fileURI, new SchemaDocument(logger, diagnosticsHandler, schemaIndex, this, fileURI)); + break; + case YQL: + workspaceFiles.put(fileURI, new YQLDocument(logger, diagnosticsHandler, fileURI)); + break; } } @@ -61,7 +85,7 @@ public void updateFile(String fileURI, String content, Integer version) { workspaceFiles.get(fileURI).updateFileContent(content, version); boolean needsReparse = false; - if (isSchemaFile && reparseDescendants) { + if (documentType.get() == DocumentType.SCHEMA && reparseDescendants) { Set parsedURIs = new HashSet<>() {{ add(fileURI); }}; for (String descendantURI : schemaIndex.getDocumentInheritanceGraph().getAllDescendants(fileURI)) { if (descendantURI.equals(fileURI)) continue; @@ -104,13 +128,19 @@ public void updateFile(String fileURI, String content, Integer version) { } public String getWorkspaceURI() { + if (this.workspaceURI == null) return null; return this.workspaceURI.toString(); } public void openDocument(TextDocumentItem document) { logger.info("Opening document: " + document.getUri()); - if (workspaceURI == null) { + Optional documentType = getDocumentTypeFromURI(document.getUri()); + + if (workspaceURI == null && documentType.isPresent() && ( + documentType.get() == DocumentType.SCHEMA || + documentType.get() == DocumentType.PROFILE + )) { Optional workspaceURI = FileUtils.findSchemaDirectory(URI.create(document.getUri())); if (workspaceURI.isEmpty()) { messageHandler.sendMessage(MessageType.Warning, @@ -200,6 +230,9 @@ public void setReparseDescendants(boolean reparseDescendants) { public void setupWorkspace(URI workspaceURI) { + // already set up + if (this.workspaceURI != null) return; + this.workspaceURI = workspaceURI; //messageHandler.messageTrace("Scanning workspace: " + workspaceURI.toString()); diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/YQLDocument.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/YQLDocument.java new file mode 100644 index 000000000000..9f6497fb839c --- /dev/null +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/YQLDocument.java @@ -0,0 +1,102 @@ +package ai.vespa.schemals.schemadocument; + +import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; + +import ai.vespa.schemals.SchemaDiagnosticsHandler; +import ai.vespa.schemals.common.ClientLogger; +import ai.vespa.schemals.parser.yqlplus.Node; +import ai.vespa.schemals.parser.yqlplus.ParseException; +import ai.vespa.schemals.parser.yqlplus.YQLPlusParser; +import ai.vespa.schemals.tree.SchemaNode; +import ai.vespa.schemals.tree.YQLNode; +import ai.vespa.schemals.tree.YQL.YQLUtils; + +public class YQLDocument implements DocumentManager { + + boolean isOpen = false; + String fileURI; + String fileContent = ""; + + ClientLogger logger; + SchemaDiagnosticsHandler diagnosticsHandler; + + private YQLNode CST; + + YQLDocument(ClientLogger logger, SchemaDiagnosticsHandler diagnosticsHandler, String fileURI) { + this.fileURI = fileURI; + this.logger = logger; + this.diagnosticsHandler = diagnosticsHandler; + } + + @Override + public void updateFileContent(String content) { + fileContent = content; + + reparseContent(); + } + + @Override + public void updateFileContent(String content, Integer version) { + updateFileContent(content); + } + + @Override + public void reparseContent() { + CharSequence charSequence = fileContent.toLowerCase(); + YQLPlusParser parser = new YQLPlusParser(charSequence); + + try { + parser.statement(); + + } catch (ParseException exception) { + logger.error(exception.getMessage()); + } + + Node node = parser.rootNode(); + CST = new YQLNode(node); + YQLUtils.printTree(logger, node); + + } + + @Override + public void setIsOpen(boolean isOpen) { + this.isOpen = isOpen; + } + + @Override + public boolean getIsOpen() { return isOpen; } + + @Override + public SchemaNode getRootNode() { + return null; + } + + @Override + public YQLNode getRootYQLNode() { + return CST; + } + + @Override + public SchemaDocumentLexer lexer() { + return new SchemaDocumentLexer(); + } + + @Override + public String getFileURI() { + return fileURI; + } + + @Override + public String getCurrentContent() { + return fileContent; + } + + @Override + public VersionedTextDocumentIdentifier getVersionedTextDocumentIdentifier() { + return new VersionedTextDocumentIdentifier(fileURI, 0); + } + + @Override + public DocumentType getDocumentType() { return DocumentType.YQL; } + +} diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/tree/YQL/YQLUtils.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/tree/YQL/YQLUtils.java new file mode 100644 index 000000000000..e061e3cfc2d8 --- /dev/null +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/tree/YQL/YQLUtils.java @@ -0,0 +1,44 @@ +package ai.vespa.schemals.tree.YQL; + +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; + +import ai.vespa.schemals.common.ClientLogger; +import ai.vespa.schemals.parser.yqlplus.Node; +import ai.vespa.schemals.parser.yqlplus.TokenSource; + +public class YQLUtils { + + private static Position getPositionFromOffset(TokenSource tokenSource, int offset) { + int line = tokenSource.getLineFromOffset(offset) - 1; + int startOfLineOffset = tokenSource.getLineStartOffset(line + 1); + int column = offset - startOfLineOffset; + return new Position(line, column); + } + + public static Range getRangeFromOffsets(TokenSource tokenSource, int beginOffset, int endOffset) { + Position begin = getPositionFromOffset(tokenSource, beginOffset); + Position end = getPositionFromOffset(tokenSource, endOffset); + return new Range(begin, end); + } + + public static Range getNodeRange(Node node) { + TokenSource tokenSource = node.getTokenSource(); + return getRangeFromOffsets(tokenSource, node.getBeginOffset(), node.getEndOffset()); + } + + public static void printTree(ClientLogger logger, Node node) { + printTree(logger, node, 0); + } + + public static void printTree(ClientLogger logger, Node node, Integer indent) { + Range range = getNodeRange(node); + logger.info(new String(new char[indent]).replace("\0", "\t") + node.getClass().getName() + + ": (" + range.getStart().getLine() + ", " + range.getStart().getCharacter() + ") - (" + range.getEnd().getLine() + ", " + range.getEnd().getCharacter() + ")" + ); + + for (Node child : node) { + printTree(logger, child, indent + 1); + } + } +} diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/tree/YQLNode.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/tree/YQLNode.java new file mode 100644 index 000000000000..a64780b9eb4d --- /dev/null +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/tree/YQLNode.java @@ -0,0 +1,33 @@ +package ai.vespa.schemals.tree; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.lsp4j.Range; + +import ai.vespa.schemals.parser.yqlplus.Node; +import ai.vespa.schemals.tree.YQL.YQLUtils; + +public class YQLNode { + + private Node originalYQLNode; + + private Range range; + + private List children; + + public YQLNode(Node node) { + originalYQLNode = node; + range = YQLUtils.getNodeRange(node); + + children = new ArrayList<>(); + + for (Node child : node.children()) { + children.add(new YQLNode(child)); + } + } + + public Range getRange() { return range; } + + +} diff --git a/integration/schema-language-server/language-server/src/test/java/ai/vespa/schemals/FetchDocsTest.java b/integration/schema-language-server/language-server/src/test/java/ai/vespa/schemals/FetchDocsTest.java index cf43e51dddad..8a88cb2f2a95 100644 --- a/integration/schema-language-server/language-server/src/test/java/ai/vespa/schemals/FetchDocsTest.java +++ b/integration/schema-language-server/language-server/src/test/java/ai/vespa/schemals/FetchDocsTest.java @@ -17,7 +17,8 @@ public class FetchDocsTest { @Test public void testFetchDocs() { try { - FetchDocumentation.fetchDocs(Paths.get("").resolve("tmp").resolve("generated-resources").resolve("hover")); + FetchDocumentation.fetchSchemaDocs(Paths.get("").resolve("tmp").resolve("generated-resources").resolve("hover")); + FetchDocumentation.fetchServicesDocs(Paths.get("").resolve("tmp").resolve("generated-resources").resolve("hover")); } catch(IOException ioe) { assertEquals(0, 1, ioe.getMessage()); } diff --git a/integration/schema-language-server/language-server/src/test/java/ai/vespa/schemals/HoverTest.java b/integration/schema-language-server/language-server/src/test/java/ai/vespa/schemals/HoverTest.java index 4f4ccb3f0df9..d89aa6405493 100644 --- a/integration/schema-language-server/language-server/src/test/java/ai/vespa/schemals/HoverTest.java +++ b/integration/schema-language-server/language-server/src/test/java/ai/vespa/schemals/HoverTest.java @@ -23,6 +23,7 @@ import ai.vespa.schemals.common.FileUtils; import ai.vespa.schemals.context.ParseContext; import ai.vespa.schemals.index.SchemaIndex; +import ai.vespa.schemals.lsp.schema.hover.SchemaHover; import ai.vespa.schemals.schemadocument.SchemaDocument; import ai.vespa.schemals.schemadocument.SchemaDocument.ParseResult; import ai.vespa.schemals.schemadocument.SchemaDocumentScheduler; @@ -31,8 +32,6 @@ import ai.vespa.schemals.tree.SchemaNode; import ai.vespa.schemals.context.EventPositionContext; -import ai.vespa.schemals.lsp.hover.SchemaHover; - import ai.vespa.schemals.schemadocument.DocumentManager; diff --git a/integration/schema-language-server/language-server/src/test/java/ai/vespa/schemals/LSPTest.java b/integration/schema-language-server/language-server/src/test/java/ai/vespa/schemals/LSPTest.java index bd22f08abb69..df59d234f8cc 100644 --- a/integration/schema-language-server/language-server/src/test/java/ai/vespa/schemals/LSPTest.java +++ b/integration/schema-language-server/language-server/src/test/java/ai/vespa/schemals/LSPTest.java @@ -18,8 +18,9 @@ import ai.vespa.schemals.common.ClientLogger; import ai.vespa.schemals.context.EventPositionContext; +import ai.vespa.schemals.context.InvalidContextException; import ai.vespa.schemals.index.SchemaIndex; -import ai.vespa.schemals.lsp.definition.SchemaDefinition; +import ai.vespa.schemals.lsp.schema.definition.SchemaDefinition; import ai.vespa.schemals.schemadocument.DocumentManager; import ai.vespa.schemals.schemadocument.SchemaDocumentScheduler; import ai.vespa.schemals.schemadocument.parser.IdentifySymbolDefinition; @@ -48,7 +49,7 @@ private record DefinitionTestPair(Position startPosition, Range resultRange) {} * - Symbol reference resolving in {@link SymbolReferenceResolver#resolveSymbolReference} */ @Test - void definitionTest() throws IOException { + void definitionTest() throws IOException, InvalidContextException { String fileName = "src/test/sdfiles/single/definition.sd"; File file = new File(fileName); String fileURI = file.toURI().toString(); diff --git a/integration/schema-language-server/lemminx-vespa/pom.xml b/integration/schema-language-server/lemminx-vespa/pom.xml new file mode 100644 index 000000000000..b8ce57e73b3f --- /dev/null +++ b/integration/schema-language-server/lemminx-vespa/pom.xml @@ -0,0 +1,102 @@ + + + + 4.0.0 + + com.yahoo.vespa + parent + 8-SNAPSHOT + ../../../parent/pom.xml + + lemminx-vespa + jar + 8-SNAPSHOT + Vespa LemminX Extension + + + org.eclipse.lemminx + org.eclipse.lemminx + 0.28.0 + provided + + + + + lemminx-releases + https://repo.eclipse.org/content/repositories/lemminx-releases/ + + false + + + true + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -Xlint:all + -Xlint:-deprecation + -Xlint:-unchecked + -Xlint:-rawtypes + + + + + org.apache.maven.plugins + maven-install-plugin + true + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.7.0 + + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + prepare-package + + copy-resources + + + + ${project.build.outputDirectory} + + + ${project.basedir}/../../../config-model/target/generated-sources/trang + false + + + + + + + + com.yahoo.vespa + bundle-plugin + + + package + assemble-fat-jar + + + + + com.github.os72 + protoc-jar-maven-plugin + + + + diff --git a/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/DefinitionParticipant.java b/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/DefinitionParticipant.java new file mode 100644 index 000000000000..5424b80118cb --- /dev/null +++ b/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/DefinitionParticipant.java @@ -0,0 +1,36 @@ +package ai.vespa.lemminx; + +import java.util.List; +import java.util.logging.Logger; + +import org.eclipse.lemminx.dom.DOMAttr; +import org.eclipse.lemminx.services.extensions.IDefinitionParticipant; +import org.eclipse.lemminx.services.extensions.IDefinitionRequest; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.LocationLink; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; + +public class DefinitionParticipant implements IDefinitionParticipant { + private static final Logger logger = Logger.getLogger(DefinitionParticipant.class.getName()); + + @Override + public void findDefinition(IDefinitionRequest request, List locations, CancelChecker cancelChecker) { + + DOMAttr attribute = request.getCurrentAttribute(); + + if ( + attribute != null && + attribute.getValue() != null && + attribute.getOwnerElement() != null && + attribute.getOwnerElement().getNodeName().equals("document")) { + String schemaName = attribute.getValue(); + + List locationResult = SchemaLSCommands.instance().findSchemaDefinition(schemaName); + + locationResult.stream() + .map(loc -> new LocationLink(loc.getUri(), loc.getRange(), loc.getRange())) + .forEach(locations::add); + } + } + +} diff --git a/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/DiagnosticsParticipant.java b/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/DiagnosticsParticipant.java new file mode 100644 index 000000000000..371ed71fdfc3 --- /dev/null +++ b/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/DiagnosticsParticipant.java @@ -0,0 +1,36 @@ +package ai.vespa.lemminx; + +import java.util.List; +import java.util.logging.Logger; + +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.dom.DOMElement; +import org.eclipse.lemminx.dom.DOMNode; +import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationSettings; +import org.eclipse.lemminx.services.extensions.diagnostics.IDiagnosticsParticipant; +import org.eclipse.lemminx.utils.XMLPositionUtility; +import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; + +public class DiagnosticsParticipant implements IDiagnosticsParticipant { + private static final Logger logger = Logger.getLogger(DiagnosticsParticipant.class.getName()); + + @Override + public void doDiagnostics(DOMDocument xmlDocument, List diagnostics, + XMLValidationSettings validationSettings, CancelChecker cancelChecker) { + traverse(xmlDocument, xmlDocument.getDocumentElement(), diagnostics); + } + + private void traverse(DOMDocument xmlDocument, DOMNode node, List diagnostics) { + if (node instanceof DOMElement) { + DOMElement element = (DOMElement)node; + // Diagnostics here + } + for (DOMNode child : node.getChildren()) { + traverse(xmlDocument, child, diagnostics); + } + } +} diff --git a/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/DocumentLifecycleParticipant.java b/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/DocumentLifecycleParticipant.java new file mode 100644 index 000000000000..5de56b012fdb --- /dev/null +++ b/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/DocumentLifecycleParticipant.java @@ -0,0 +1,41 @@ +package ai.vespa.lemminx; + +import java.util.List; +import java.util.logging.Logger; + +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.services.extensions.IDocumentLifecycleParticipant; +import org.eclipse.lemminx.services.extensions.commands.IXMLCommandService; +import org.eclipse.lsp4j.ExecuteCommandParams; + +public class DocumentLifecycleParticipant implements IDocumentLifecycleParticipant { + private static final Logger logger = Logger.getLogger(DocumentLifecycleParticipant.class.getName()); + private IXMLCommandService commandService; + + public DocumentLifecycleParticipant(IXMLCommandService commandService) { + this.commandService = commandService; + } + + @Override + public void didOpen(DOMDocument document) { + try { + String fileURI = document.getTextDocument().getUri(); + SchemaLSCommands.instance().sendSetupWorkspaceRequest(fileURI); + } catch (Exception ex) { + // not very severe from our point of view + logger.warning("Error when issuing setup workspace command: " + ex.getMessage()); + } + } + + @Override + public void didChange(DOMDocument document) { + } + + @Override + public void didSave(DOMDocument document) { + } + + @Override + public void didClose(DOMDocument document) { + } +} diff --git a/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/HoverParticipant.java b/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/HoverParticipant.java new file mode 100644 index 000000000000..09ef2ab17fad --- /dev/null +++ b/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/HoverParticipant.java @@ -0,0 +1,123 @@ +package ai.vespa.lemminx; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.logging.Logger; +import java.util.stream.Stream; + +import org.eclipse.lemminx.dom.DOMNode; +import org.eclipse.lemminx.services.extensions.hover.IHoverParticipant; +import org.eclipse.lemminx.services.extensions.hover.IHoverRequest; +import org.eclipse.lsp4j.Hover; +import org.eclipse.lsp4j.MarkupContent; +import org.eclipse.lsp4j.MarkupKind; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; + +public class HoverParticipant implements IHoverParticipant { + private static final Logger logger = Logger.getLogger(HoverParticipant.class.getName()); + private Path serverPath; + + public HoverParticipant(Path serverPath) { + this.serverPath = serverPath; + } + + @Override + public Hover onTag(IHoverRequest request, CancelChecker cancelChecker) throws Exception { + if (request.getCurrentTag() == null) return null; + + DOMNode node = request.getNode(); + + String logMsg = ""; + while (node != null) { + logMsg += node.getNodeName() + " -> "; + node = node.getParentNode(); + } + logger.info(logMsg); + + Optional content = getFileHover(request.getCurrentTag()); + + if (content.isEmpty()) return null; + + return new Hover(content.get(), request.getHoverRange()); + } + + @Override + public Hover onAttributeName(IHoverRequest request, CancelChecker cancelChecker) throws Exception { + return null; + } + + @Override + public Hover onAttributeValue(IHoverRequest request, CancelChecker cancelChecker) throws Exception { + return null; + } + + @Override + public Hover onText(IHoverRequest request, CancelChecker cancelChecker) throws Exception { + return null; + } + + private Optional getFileHover(String tagName) { + Path servicesPath = serverPath.resolve("hover").resolve("services"); + + if (!serverPath.toFile().exists()) { + logger.warning("Could not get hover content because services hover does not exist!"); + return Optional.empty(); + } + + // key: tag -> value: path + Map markdownFiles = new HashMap<>(); + + try (Stream stream = Files.list(servicesPath)) { + stream.forEach(path -> { + if (Files.isDirectory(path)) { + try (Stream innerStream = Files.list(path)) { + innerStream.forEach(innerPath -> { + String tag = innerPath.getFileName().toString(); + if (tag.endsWith(".md")) { + tag = tag.substring(0, tag.length() - 3); + if (markdownFiles.containsKey(tag)) { + //logger.warning("Duplicate key: " + tag); + } + markdownFiles.put(tag, innerPath); + } + }); + } catch (IOException ex) { + logger.severe("Inner ioexception"); + } + } else { + String tag = path.getFileName().toString(); + if (tag.endsWith(".md")) { + tag = tag.substring(0, tag.length() - 3); + if (markdownFiles.containsKey(tag)) { + //logger.warning("Duplicate key: " + tag); + } + markdownFiles.put(tag, path); + } + } + }); + } catch (IOException ex) { + logger.severe("Failed to read documentation files: " + ex.getMessage()); + } + + if (!markdownFiles.containsKey(tagName)) { + logger.warning("Found no hover file with name " + tagName + ".md"); + return Optional.empty(); + } + + try { + Path markdownPath = markdownFiles.get(tagName); + String markdown = Files.readString(markdownPath); + + MarkupContent mdContent = new MarkupContent(MarkupKind.MARKDOWN, markdown); + return Optional.of(mdContent); + } catch (Exception ex) { + logger.severe("Unknown error when getting hover: " + ex.getMessage()); + } + + return Optional.empty(); + } +} diff --git a/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/SchemaLSCommands.java b/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/SchemaLSCommands.java new file mode 100644 index 000000000000..cbe358cc9616 --- /dev/null +++ b/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/SchemaLSCommands.java @@ -0,0 +1,58 @@ +package ai.vespa.lemminx; + +import java.lang.reflect.Type; +import java.util.List; +import java.util.logging.Logger; + +import org.eclipse.lemminx.services.extensions.commands.IXMLCommandService; +import org.eclipse.lsp4j.ExecuteCommandParams; +import org.eclipse.lsp4j.Location; + +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; + +/** + * Singleton to interface the Schema Language server through the client. + */ +public class SchemaLSCommands { + private static final Logger logger = Logger.getLogger(SchemaLSCommands.class.getName()); + private IXMLCommandService commandService; + + private static SchemaLSCommands INSTANCE; + private SchemaLSCommands() {} + private SchemaLSCommands(IXMLCommandService commandService) { + this.commandService = commandService; + } + + public static void init(IXMLCommandService commandService) { + INSTANCE = new SchemaLSCommands(commandService); + } + + public static SchemaLSCommands instance() { + return INSTANCE; + } + + public void sendSetupWorkspaceRequest(String fileURI) { + commandService.executeClientCommand(new ExecuteCommandParams("vespaSchemaLS.servicesxml.setupWorkspace", List.of(fileURI))); + } + + /** + * Sends the FIND_SCHEMA_DEFINITION request to the Schema language server. + */ + public List findSchemaDefinition(String schemaName) { + // run sync + Object findDocumentResult = commandService.executeClientCommand( + new ExecuteCommandParams("vespaSchemaLS.servicesxml.findDocument", List.of(schemaName))).join(); + + if (findDocumentResult == null) return List.of(); + try { + Gson gson = new Gson(); + String json = gson.toJson(findDocumentResult); + Type listOfLocationType = new TypeToken>() {}.getType(); + return gson.fromJson(json, listOfLocationType); + } catch (Exception ex) { + logger.severe("Error when parsing json: " + ex.getMessage()); + return List.of(); + } + } +} diff --git a/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/ServicesURIResolverExtension.java b/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/ServicesURIResolverExtension.java new file mode 100644 index 000000000000..90cf6f5de76d --- /dev/null +++ b/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/ServicesURIResolverExtension.java @@ -0,0 +1,21 @@ +package ai.vespa.lemminx; + +import java.nio.file.Path; +import java.util.logging.Logger; + +import org.eclipse.lemminx.uriresolver.URIResolverExtension; + +public class ServicesURIResolverExtension implements URIResolverExtension { + private String resourceURI; + + public ServicesURIResolverExtension(Path serverPath) { + resourceURI = serverPath.resolve("resources").resolve("schema").resolve("services.rng").toUri().toString(); + } + + @Override + public String resolve(String baseLocation, String publicId, String systemId) { + if (baseLocation != null && baseLocation.endsWith("services.xml")) { + return resourceURI; + } else return null; + } +} diff --git a/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/UnpackRNGFiles.java b/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/UnpackRNGFiles.java new file mode 100644 index 000000000000..3efe9968921e --- /dev/null +++ b/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/UnpackRNGFiles.java @@ -0,0 +1,55 @@ +package ai.vespa.lemminx; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Collectors; + +/** + * LemminX isn't quite able to use .rng files from inside a jar for validation. + * We embed them in the fat-jar and unpack them to the server installation location on start. + * This is similar to the documentation fetching in schema language-server. + */ +public class UnpackRNGFiles { + public static void unpackRNGFiles(Path serverPath) throws IOException { + Files.createDirectories(serverPath.resolve("resources").resolve("schema")); // mkdir -p + final String basePath = "resources/schema/"; + + var resources = Thread.currentThread().getContextClassLoader().getResources(basePath); + + if (!resources.hasMoreElements()) { + throw new IOException("Could not find RNG files in jar file!"); + } + + URL resourceURL = resources.nextElement(); + + if (!resourceURL.getProtocol().equals("jar")) { + throw new IOException("Unhandled protocol for resource " + resourceURL.toString()); + } + + String jarPath = resourceURL.getPath().substring(5, resourceURL.getPath().indexOf('!')); + try (JarFile jarFile = new JarFile(URLDecoder.decode(jarPath, "UTF-8"))) { + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (!entry.isDirectory() && entry.getName().endsWith(".rng")) { + Path writePath = serverPath.resolve(entry.getName()); + try (InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(entry.getName())) { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + String content = reader.lines().collect(Collectors.joining(System.lineSeparator())); + Files.write(writePath, content.getBytes(), StandardOpenOption.CREATE); + } + } + } + } + } +} diff --git a/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/VespaExtension.java b/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/VespaExtension.java new file mode 100644 index 000000000000..adabeec89c05 --- /dev/null +++ b/integration/schema-language-server/lemminx-vespa/src/main/java/ai/vespa/lemminx/VespaExtension.java @@ -0,0 +1,69 @@ +package ai.vespa.lemminx; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.logging.Logger; + +import org.eclipse.lemminx.services.IXMLValidationService; +import org.eclipse.lemminx.services.extensions.IDefinitionParticipant; +import org.eclipse.lemminx.services.extensions.IDocumentLifecycleParticipant; +import org.eclipse.lemminx.services.extensions.IXMLExtension; +import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry; +import org.eclipse.lemminx.services.extensions.diagnostics.IDiagnosticsParticipant; +import org.eclipse.lemminx.services.extensions.save.ISaveContext; +import org.eclipse.lemminx.uriresolver.URIResolverExtension; +import org.eclipse.lsp4j.InitializeParams; + +public class VespaExtension implements IXMLExtension { + private static final Logger logger = Logger.getLogger(VespaExtension.class.getName()); + + HoverParticipant hoverParticipant; + IXMLValidationService validationService; + URIResolverExtension uriResolverExtension; + IDefinitionParticipant definitionParticipant; + IDocumentLifecycleParticipant documentLifecycleParticipant; + IDiagnosticsParticipant diagnosticsParticipant; + Path serverPath; + + @Override + public void doSave(ISaveContext context) { } + + @Override + public void start(InitializeParams params, XMLExtensionsRegistry registry) { + try { + serverPath = Paths.get(VespaExtension.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getParent(); + + UnpackRNGFiles.unpackRNGFiles( + serverPath + ); + } catch (Exception ex) { + if (logger != null) { + logger.severe("Exception occured during start: " + ex.getMessage()); + } + return; + } + + SchemaLSCommands.init(registry.getCommandService()); + + hoverParticipant = new HoverParticipant(serverPath); + uriResolverExtension = new ServicesURIResolverExtension(serverPath); + definitionParticipant = new DefinitionParticipant(); + documentLifecycleParticipant = new DocumentLifecycleParticipant(registry.getCommandService()); + diagnosticsParticipant = new DiagnosticsParticipant(); + + registry.getResolverExtensionManager().registerResolver(uriResolverExtension); + registry.registerHoverParticipant(hoverParticipant); + registry.registerDefinitionParticipant(definitionParticipant); + registry.registerDocumentLifecycleParticipant(documentLifecycleParticipant); + registry.registerDiagnosticsParticipant(diagnosticsParticipant); + + logger.info("Vespa LemminX extension activated"); + + } + + @Override + public void stop(XMLExtensionsRegistry registry) { + // calls unregister for each registered extension + registry.dispose(); + } +} diff --git a/integration/schema-language-server/lemminx-vespa/src/main/resources/META-INF/services/org.eclipse.lemminx.services.extensions.IXMLExtension b/integration/schema-language-server/lemminx-vespa/src/main/resources/META-INF/services/org.eclipse.lemminx.services.extensions.IXMLExtension new file mode 100644 index 000000000000..12b51735d0db --- /dev/null +++ b/integration/schema-language-server/lemminx-vespa/src/main/resources/META-INF/services/org.eclipse.lemminx.services.extensions.IXMLExtension @@ -0,0 +1 @@ +ai.vespa.lemminx.VespaExtension diff --git a/pom.xml b/pom.xml index 1f75bb9667a2..e9d09d9373ca 100644 --- a/pom.xml +++ b/pom.xml @@ -143,6 +143,7 @@ zookeeper-common zookeeper-server integration/schema-language-server/language-server + integration/schema-language-server/lemminx-vespa diff --git a/vespa-dependencies-enforcer/allowed-maven-dependencies.txt b/vespa-dependencies-enforcer/allowed-maven-dependencies.txt index b1fa4676a2c2..e054f32f1c65 100644 --- a/vespa-dependencies-enforcer/allowed-maven-dependencies.txt +++ b/vespa-dependencies-enforcer/allowed-maven-dependencies.txt @@ -21,6 +21,7 @@ com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${jackson2.vespa.version} com.fasterxml.woodstox:woodstox-core:${woodstox.vespa.version} com.github.luben:zstd-jni:${luben.zstd.vespa.version} com.google.code.findbugs:jsr305:${findbugs.vespa.version} +com.google.code.gson:gson:2.10.1 com.google.code.gson:gson:2.11.0 com.google.errorprone:error_prone_annotations:${error-prone-annotations.vespa.version} com.google.guava:failureaccess:${failureaccess.vespa.version} @@ -31,6 +32,7 @@ com.google.jimfs:jimfs:${jimfs.vespa.version} com.google.protobuf:protobuf-java:${protobuf.vespa.version} com.huaban:jieba-analysis:${jieba.vespa.version} com.ibm.icu:icu4j:${icu4j.vespa.version} +com.kotcrab.remark:remark:1.0.0 com.microsoft.onnxruntime:onnxruntime:${onnxruntime.vespa.version} com.sun.activation:javax.activation:${sun.javax.activation.vespa.version} com.sun.istack:istack-commons-runtime:4.1.2 @@ -98,6 +100,7 @@ io.prometheus:simpleclient_common:${prometheus.client.vespa.version} io.prometheus:simpleclient_tracer_common:${prometheus.client.vespa.version} io.prometheus:simpleclient_tracer_otel:${prometheus.client.vespa.version} io.prometheus:simpleclient_tracer_otel_agent:${prometheus.client.vespa.version} +isorelax:isorelax:20030108 jakarta.inject:jakarta.inject-api:${jakarta.inject.vespa.version} javax.activation:javax.activation-api:${javax.activation-api.vespa.version} javax.inject:javax.inject:${javax.inject.vespa.version} @@ -187,10 +190,17 @@ org.eclipse.jetty:jetty-security:${jetty.vespa.version} org.eclipse.jetty:jetty-server:${jetty.vespa.version} org.eclipse.jetty:jetty-servlet:${jetty.vespa.version} org.eclipse.jetty:jetty-util:${jetty.vespa.version} +org.eclipse.lemminx:org.eclipse.lemminx:0.28.0 +org.eclipse.lsp4j:org.eclipse.lsp4j.generator:0.20.1 +org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.20.1 org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.23.1 +org.eclipse.lsp4j:org.eclipse.lsp4j:0.20.1 org.eclipse.lsp4j:org.eclipse.lsp4j:0.23.1 org.eclipse.sisu:org.eclipse.sisu.inject:${eclipse-sisu.vespa.version} org.eclipse.sisu:org.eclipse.sisu.plexus:${eclipse-sisu.vespa.version} +org.eclipse.xtend:org.eclipse.xtend.lib.macro:2.28.0 +org.eclipse.xtend:org.eclipse.xtend.lib:2.28.0 +org.eclipse.xtext:org.eclipse.xtext.xbase.lib:2.28.0 org.fusesource.jansi:jansi:1.18 org.glassfish.jaxb:jaxb-core:${jaxb.runtime.vespa.version} org.glassfish.jaxb:jaxb-runtime:${jaxb.runtime.vespa.version} @@ -200,6 +210,7 @@ org.hamcrest:hamcrest:${hamcrest.vespa.version} org.hdrhistogram:HdrHistogram:${hdrhistogram.vespa.version} org.jetbrains:annotations:24.0.1 org.json:json:${org.json.vespa.version} +org.jsoup:jsoup:1.14.2 org.jsoup:jsoup:1.17.2 org.junit.jupiter:junit-jupiter-api:${junit.vespa.tenant.version} org.junit.jupiter:junit-jupiter-api:${junit.vespa.version} @@ -226,6 +237,7 @@ org.ow2.asm:asm-util:${asm.vespa.version} org.ow2.asm:asm:${asm.vespa.version} org.questdb:questdb:${questdb.vespa.version} org.reactivestreams:reactive-streams:1.0.4 +org.relaxng:jing:20220510 org.slf4j:jcl-over-slf4j:${slf4j.vespa.version} org.slf4j:log4j-over-slf4j:${slf4j.vespa.version} org.slf4j:slf4j-api:${slf4j.vespa.version} @@ -263,3 +275,5 @@ software.amazon.awssdk:third-party-jackson-core:${aws-sdk2.vespa.version} software.amazon.awssdk:utils:${aws-sdk2.vespa.version} software.amazon.eventstream:eventstream:1.0.1 xerces:xercesImpl:${xerces.vespa.version} +xml-apis:xml-apis:1.0.b2 +xml-resolver:xml-resolver:${javax.annotation.vespa.version}