diff --git a/integration/schema-language-server/clients/vscode/src/extension.ts b/integration/schema-language-server/clients/vscode/src/extension.ts index 78b797e05ce7..80c97636da12 100644 --- a/integration/schema-language-server/clients/vscode/src/extension.ts +++ b/integration/schema-language-server/clients/vscode/src/extension.ts @@ -144,33 +144,52 @@ export function activate(context: vscode.ExtensionContext) { }))); - 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); - } + context.subscriptions.push(vscode.commands.registerCommand("vespaSchemaLS.commands.findSchemaDefinition", async (fileName) => { + if (schemaClient === null) { + return 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); - } + context.subscriptions.push(vscode.commands.registerCommand("vespaSchemaLS.commands.setupWorkspace", async (fileURI) => { + if (schemaClient === null) { + return; } + try { + schemaClient.sendRequest("workspace/executeCommand", { + command: "SETUP_WORKSPACE", + arguments: [fileURI] + }); + } catch (err) { + logger.error("Error when trying to send setup workspace command: ", err); + } + })); + + context.subscriptions.push(vscode.commands.registerCommand("vespaSchemaLS.commands.hasSetupWorkspace", async () => { + if (schemaClient === null) { + return false; + } + + try { + const result: boolean = await schemaClient.sendRequest("workspace/executeCommand", { + command: "HAS_SETUP_WORKSPACE", + arguments: [] + }); + return result; + } catch (err) { + logger.error("Error when sending command: ", err); + } + return false; })); logger.info("Vespa language client activated"); 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 index 432dcaaf20e5..5c4e735be033 100644 --- 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 @@ -14,6 +14,7 @@ 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; +import ai.vespa.schemals.lsp.common.command.commandtypes.HasSetupWorkspace; /** * SchemaCommand @@ -89,6 +90,18 @@ public enum CommandType implements GenericCommandType { public String title() { return "Find schema document"; } public SchemaCommand construct() { return new FindDocument(); } }, + HAS_SETUP_WORKSPACE { + /* + * Ask if the language server has setup a workspace directory yet. + * + * Parameters: + * + * Return value: + * boolean + */ + public String title() { return "Has setup workspace"; } + public SchemaCommand construct() { return new HasSetupWorkspace(); } + }, SETUP_WORKSPACE { /* * Set the workspace directory and parse *sd files within. If it is already set up, nothing will happen. diff --git a/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/HasSetupWorkspace.java b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/HasSetupWorkspace.java new file mode 100644 index 000000000000..8d33161086bf --- /dev/null +++ b/integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/lsp/common/command/commandtypes/HasSetupWorkspace.java @@ -0,0 +1,24 @@ +package ai.vespa.schemals.lsp.common.command.commandtypes; + +import java.util.List; + +import ai.vespa.schemals.context.EventExecuteCommandContext; + +public class HasSetupWorkspace implements SchemaCommand { + + @Override + public int getArity() { + return 0; + } + + @Override + public boolean setArguments(List arguments) { + assert arguments.size() == getArity(); + return true; + } + + @Override + public Object execute(EventExecuteCommandContext context) { + return Boolean.valueOf(context.scheduler.getWorkspaceURI() != null); + } +} diff --git a/integration/schema-language-server/lemminx-vespa/pom.xml b/integration/schema-language-server/lemminx-vespa/pom.xml index b8ce57e73b3f..cea7129d0be0 100644 --- a/integration/schema-language-server/lemminx-vespa/pom.xml +++ b/integration/schema-language-server/lemminx-vespa/pom.xml @@ -19,6 +19,11 @@ 0.28.0 provided + + com.yahoo.vespa + config-model + 8-SNAPSHOT + 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 index 371ed71fdfc3..0f29575d4562 100644 --- 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 @@ -3,6 +3,7 @@ import java.util.List; import java.util.logging.Logger; +import org.eclipse.lemminx.dom.DOMAttr; import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.dom.DOMElement; import org.eclipse.lemminx.dom.DOMNode; @@ -14,23 +15,49 @@ import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.CancelChecker; +import org.w3c.dom.Node; public class DiagnosticsParticipant implements IDiagnosticsParticipant { private static final Logger logger = Logger.getLogger(DiagnosticsParticipant.class.getName()); + private static final String DIAGNOSTIC_SOURCE = "LemMinX Vespa Extension"; + private record DiagnosticsContext(DOMDocument xmlDocument, List diagnostics, boolean hasSetupWorkspace) {} + @Override public void doDiagnostics(DOMDocument xmlDocument, List diagnostics, XMLValidationSettings validationSettings, CancelChecker cancelChecker) { - traverse(xmlDocument, xmlDocument.getDocumentElement(), diagnostics); + DiagnosticsContext context = new DiagnosticsContext(xmlDocument, diagnostics, SchemaLSCommands.instance().hasSetupWorkspace()); + traverse(xmlDocument.getDocumentElement(), context); } - private void traverse(DOMDocument xmlDocument, DOMNode node, List diagnostics) { + private void traverse(DOMNode node, DiagnosticsContext context) { if (node instanceof DOMElement) { DOMElement element = (DOMElement)node; - // Diagnostics here + if (context.hasSetupWorkspace() && element.getTagName().equals("document")) { + validateDocumentElement(element, context); + } } for (DOMNode child : node.getChildren()) { - traverse(xmlDocument, child, diagnostics); + traverse(child, context); + } + } + + private void validateDocumentElement(DOMElement element, DiagnosticsContext context) { + DOMAttr typeAttribute = element.getAttributeNode("type"); + if (typeAttribute != null) { + String docName = typeAttribute.getValue(); + Range range = XMLPositionUtility.createRange(typeAttribute.getStart(), typeAttribute.getEnd(), context.xmlDocument); + + // TODO: (possibly) slow blocking call. Could be grouped + List locations = SchemaLSCommands.instance().findSchemaDefinition(docName); + if (locations.isEmpty()) { + context.diagnostics.add(new Diagnostic( + range, + "Document " + docName + " does not exist in the current application.", + DiagnosticSeverity.Warning, + DIAGNOSTIC_SOURCE + )); + } } } } 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 index cbe358cc9616..1743cb81e990 100644 --- 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 @@ -17,11 +17,13 @@ public class SchemaLSCommands { private static final Logger logger = Logger.getLogger(SchemaLSCommands.class.getName()); private IXMLCommandService commandService; + private Gson gson; private static SchemaLSCommands INSTANCE; private SchemaLSCommands() {} private SchemaLSCommands(IXMLCommandService commandService) { this.commandService = commandService; + this.gson = new Gson(); } public static void init(IXMLCommandService commandService) { @@ -33,7 +35,22 @@ public static SchemaLSCommands instance() { } public void sendSetupWorkspaceRequest(String fileURI) { - commandService.executeClientCommand(new ExecuteCommandParams("vespaSchemaLS.servicesxml.setupWorkspace", List.of(fileURI))); + commandService.executeClientCommand(new ExecuteCommandParams("vespaSchemaLS.commands.setupWorkspace", List.of(fileURI))); + } + + public boolean hasSetupWorkspace() { + Object result = commandService.executeClientCommand( + new ExecuteCommandParams("vespaSchemaLS.commands.hasSetupWorkspace", List.of())).join(); + if (result == null) return false; + try { + String json = gson.toJson(result); + Type booleanType = new TypeToken() {}.getType(); + return gson.fromJson(json, booleanType); + } catch (Exception ex) { + logger.severe("Error when parsing json: " + ex.getMessage()); + } + + return false; } /** @@ -42,11 +59,10 @@ public void sendSetupWorkspaceRequest(String fileURI) { public List findSchemaDefinition(String schemaName) { // run sync Object findDocumentResult = commandService.executeClientCommand( - new ExecuteCommandParams("vespaSchemaLS.servicesxml.findDocument", List.of(schemaName))).join(); + new ExecuteCommandParams("vespaSchemaLS.commands.findSchemaDefinition", 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);