diff --git a/.vscode/launch.json b/.vscode/launch.json index 7c03cce50..dca73dbc3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,8 @@ "name": "Launch Client", "runtimeExecutable": "${execPath}", "args": [ - "--extensionDevelopmentPath=${workspaceRoot}/packages/vscode-ui5-language-assistant" + "--extensionDevelopmentPath=${workspaceRoot}/packages/vscode-ui5-language-assistant", + "--disable-extensions" ], "outFiles": [ "${workspaceRoot}/packages/vscode-ui5-language-assistant/lib/src/**/*.js" diff --git a/package.json b/package.json index a10462e23..158bd9ad5 100644 --- a/package.json +++ b/package.json @@ -15,17 +15,24 @@ "build:quick": "lerna run compile && lerna run bundle && lerna run package", "release:version": "lerna version --force-publish", "release:publish": "lerna publish from-git --yes", - "ci": "npm-run-all format:validate ci:subpackages coverage:merge legal:*", - "compile": "yarn run clean && tsc --build", + "compile": "turbo run compile", + "ci": "turbo run ci", + "preci": "turbo run coverage", "compile:watch": "yarn run clean && tsc --build --watch", "format:fix": "prettier --write \"**/*.@(js|ts|json|md)\" --ignore-path=.gitignore", - "format:validate": "prettier --check \"**/*.@(js|ts|json|md)\" --ignore-path=.gitignore", - "lint": "eslint . --ext .ts --fix --max-warnings=0 --ignore-path=.gitignore", + "lint": "turbo run lint", "ci:subpackages": "lerna run ci", - "test": "lerna run test", - "coverage": "lerna run coverage", - "coverage:merge": "node ./scripts/merge-coverage", - "clean": "lerna run clean", + "test": "turbo run test", + "pretest": "turbo run compile", + "precoverage": "turbo run pretest", + "prebuild": "yarn run clean", + "prebundle": "turbo run clean", + "build": "turbo run build", + "bundle": "turbo run bundle", + "package": "turbo run package", + "format:validate": "turbo run format:validate", + "coverage": "turbo run coverage", + "clean": "turbo run clean", "update-snapshots": "lerna run update-snapshots", "legal:delete": "lerna exec \"shx rm -rf .reuse LICENSES\" || true", "legal:copy": "lerna exec \"shx cp -r ../../.reuse .reuse && shx cp -r ../../LICENSES LICENSES\"", @@ -48,6 +55,7 @@ "@typescript-eslint/eslint-plugin": "4.14.0", "@typescript-eslint/parser": "4.14.0", "chai": "4.2.0", + "turbo": "^1.4.5", "conventional-changelog-cli": "2.1.1", "coveralls": "3.1.0", "cz-conventional-changelog": "3.3.0", @@ -72,6 +80,7 @@ "sinon": "9.2.3", "sinon-chai": "3.5.0", "source-map-support": "0.5.19", + "turborepo": "^0.0.1", "typescript": "3.9.7", "webpack": "5.36.2", "webpack-cli": "4.4.0" diff --git a/packages/language-server/api.d.ts b/packages/language-server/api.d.ts index 87ec846c9..6aed2f0d5 100644 --- a/packages/language-server/api.d.ts +++ b/packages/language-server/api.d.ts @@ -2,8 +2,8 @@ * Absolute path to the server's "main" module * This is useful when launching the server in a separate process (e.g via spawn). */ -import { LogLevel } from "@vscode-logging/types"; +import { LogLevel } from "@vscode-logging/types"; export declare const SERVER_PATH: string; export type ServerInitializationOptions = { @@ -25,6 +25,13 @@ export type ServerInitializationOptions = { logLevel?: LogLevel; }; +export function getNodeName( + text: string, + offsetAt: number, + cachePath?: string, + framework?: string, + ui5Version?: string +): Promise; export type FetchResponse = { ok: boolean; status: number; diff --git a/packages/language-server/package.json b/packages/language-server/package.json index cf4a2e4f0..217f76196 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -62,7 +62,7 @@ "scripts": { "ci": "npm-run-all clean compile lint coverage bundle", "clean": "rimraf ./dist ./lib ./coverage ./nyc_output", - "compile": "yarn run clean && tsc -p .", + "compile": "tsc -p .", "compile:watch": "tsc -p . --watch", "lint": "eslint . --ext .ts --max-warnings=0 --ignore-path=../../.gitignore", "test": "mocha", diff --git a/packages/language-server/src/api.ts b/packages/language-server/src/api.ts index cfc5daf8d..e5576d85f 100644 --- a/packages/language-server/src/api.ts +++ b/packages/language-server/src/api.ts @@ -1,5 +1,6 @@ import { resolve } from "path"; import { existsSync } from "fs"; +import { getNodeDetail, getUI5NodeName } from "./documentation"; // for use in productive flows const bundledPath = resolve(__dirname, "..", "..", "dist", "server.js"); @@ -19,3 +20,25 @@ export const SERVER_PATH: string = isDevelopmentRun ? /* istanbul ignore else - no tests (yet?) on bundled artifacts */ sourcesPath : bundledPath; + +export async function getNodeName( + text: string, + offsetAt: number, + cachePath?: string | undefined, + framework?: string | undefined, + ui5Version?: string | undefined +): Promise { + const ui5Node = await getUI5NodeName( + offsetAt, + text, + undefined, + cachePath, + framework, + ui5Version + ); + return ui5Node + ? ui5Node.kind !== "UI5Class" + ? `${ui5Node.parent?.library}.${ui5Node.parent?.name}` + : getNodeDetail(ui5Node) + : undefined; +} diff --git a/packages/language-server/src/documentation.ts b/packages/language-server/src/documentation.ts index 2f4a8894d..5b39b3380 100644 --- a/packages/language-server/src/documentation.ts +++ b/packages/language-server/src/documentation.ts @@ -17,6 +17,11 @@ import { getLink, } from "@ui5-language-assistant/logic-utils"; import { GENERATED_LIBRARY } from "@ui5-language-assistant/semantic-model"; +import { DocumentCstNode, parse } from "@xml-tools/parser"; +import { buildAst } from "@xml-tools/ast"; +import { astPositionAtOffset } from "@xml-tools/ast-position"; +import { findUI5HoverNodeAtOffset } from "@ui5-language-assistant/xml-views-tooltip"; +import { getSemanticModel } from "./ui5-model"; export function getNodeDocumentation( node: BaseUI5Node, @@ -108,3 +113,30 @@ export function getNodeDetail(node: BaseUI5Node): string { return node.name; } } + +export async function getUI5NodeName( + offsetAt: number, + text: string, + model?: UI5SemanticModel, + cachePath?: string, + framework?: string, + ui5Version?: string +): Promise { + const documentText = text; + const { cst, tokenVector } = parse(documentText); + const ast = buildAst(cst as DocumentCstNode, tokenVector); + const offset = offsetAt; + const astPosition = astPositionAtOffset(ast, offset); + if (!model) { + model = await fetchModel(cachePath, framework, ui5Version); + } + if (astPosition !== undefined) { + return findUI5HoverNodeAtOffset(astPosition, model); + } + return undefined; +} + +async function fetchModel(cachePath, framework, ui5Version) { + const ui5Model = await getSemanticModel(cachePath, framework, ui5Version); + return ui5Model; +} diff --git a/packages/language-server/src/hover.ts b/packages/language-server/src/hover.ts index 04b92f80c..04ba66a8f 100644 --- a/packages/language-server/src/hover.ts +++ b/packages/language-server/src/hover.ts @@ -5,33 +5,30 @@ import { MarkupContent, MarkupKind, } from "vscode-languageserver"; -import { parse, DocumentCstNode } from "@xml-tools/parser"; -import { buildAst } from "@xml-tools/ast"; -import { astPositionAtOffset } from "@xml-tools/ast-position"; import { UI5SemanticModel, BaseUI5Node, } from "@ui5-language-assistant/semantic-model-types"; -import { findUI5HoverNodeAtOffset } from "@ui5-language-assistant/xml-views-tooltip"; -import { getNodeDocumentation, getNodeDetail } from "./documentation"; +import { + getNodeDocumentation, + getNodeDetail, + getUI5NodeName, +} from "./documentation"; import { track } from "./swa"; -export function getHoverResponse( +export async function getHoverResponse( model: UI5SemanticModel, textDocumentPosition: TextDocumentPositionParams, document: TextDocument -): Hover | undefined { - const documentText = document.getText(); - const { cst, tokenVector } = parse(documentText); - const ast = buildAst(cst as DocumentCstNode, tokenVector); - const offset = document.offsetAt(textDocumentPosition.position); - const astPosition = astPositionAtOffset(ast, offset); - if (astPosition !== undefined) { - const ui5Node = findUI5HoverNodeAtOffset(astPosition, model); - if (ui5Node !== undefined) { - track("XML_UI5_DOC_HOVER", ui5Node.kind); - return transformToLspHover(ui5Node, model); - } +): Promise { + const ui5Node = await getUI5NodeName( + document.offsetAt(textDocumentPosition.position), + document.getText(), + model + ); + if (ui5Node !== undefined) { + track("XML_UI5_DOC_HOVER", ui5Node.kind); + return transformToLspHover(ui5Node, model); } return undefined; diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index 76a0deb72..3bce10dbd 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -10,6 +10,7 @@ import { InitializeParams, Hover, DidChangeConfigurationNotification, + DefinitionParams, } from "vscode-languageserver"; import { URI } from "vscode-uri"; import { TextDocument } from "vscode-languageserver-textdocument"; @@ -94,6 +95,8 @@ connection.onInitialize((params: InitializeParams) => { commands.QUICK_FIX_STABLE_ID_FILE_ERRORS.name, ], }, + referencesProvider: false, + definitionProvider: true, }, }; }); @@ -108,6 +111,46 @@ connection.onInitialized(async () => { } }); +connection.onDefinition(async (params: DefinitionParams) => { + const documentUri = params.textDocument.uri; + const document = documents.get(documentUri); + if (document) { + const documentPath = URI.parse(documentUri).fsPath; + const minUI5Version = getMinUI5VersionForXMLFile(documentPath); + const framework = getUI5FrameworkForXMLFile(documentPath); + const model = await getSemanticModel( + initializationOptions?.modelCachePath, + framework, + minUI5Version + ); + + const ui5Url = getCDNBaseUrl(framework, model.version); + + getLogger().debug("`onDefinition` event", { + url: ui5Url, + }); + + // const documentUri = params.textDocument.uri; + // const document = documents.get(documentUri); + // if (document) { + // const documentPath = URI.parse(documentUri).fsPath; + // const minUI5Version = getMinUI5VersionForXMLFile(documentPath); + // const framework = getUI5FrameworkForXMLFile(documentPath); + // const model = await getSemanticModel( + // initializationOptions?.modelCachePath, + // framework, + // minUI5Version + // ); + // const ui5Url = getCDNBaseUrl(framework, model.version) + + // } + connection.sendNotification("UI5LanguageAssistant/ui5Definition", { + url: ui5Url, + }); + } + return null; +}); + connection.onCompletion( async ( textDocumentPosition: TextDocumentPositionParams @@ -128,6 +171,7 @@ connection.onCompletion( minUI5Version ); connection.sendNotification("UI5LanguageAssistant/ui5Model", { + cachePath: initializationOptions?.modelCachePath, url: getCDNBaseUrl(framework, model.version), framework, version: model.version, @@ -172,6 +216,7 @@ connection.onHover( minUI5Version ); connection.sendNotification("UI5LanguageAssistant/ui5Model", { + cachePath: initializationOptions?.modelCachePath, url: getCDNBaseUrl(framework, ui5Model.version), framework, version: ui5Model.version, @@ -226,6 +271,8 @@ documents.onDidChangeContent(async (changeEvent) => { minUI5Version ); connection.sendNotification("UI5LanguageAssistant/ui5Model", { + cachePath: initializationOptions?.modelCachePath, + url: getCDNBaseUrl(framework, ui5Model.version), framework, version: ui5Model.version, @@ -258,6 +305,8 @@ connection.onCodeAction(async (params) => { minUI5Version ); connection.sendNotification("UI5LanguageAssistant/ui5Model", { + cachePath: initializationOptions?.modelCachePath, + url: getCDNBaseUrl(framework, ui5Model.version), framework, version: ui5Model.version, diff --git a/packages/language-server/test/documentation-spec.ts b/packages/language-server/test/documentation-spec.ts index ae9a548db..fb505a263 100644 --- a/packages/language-server/test/documentation-spec.ts +++ b/packages/language-server/test/documentation-spec.ts @@ -5,7 +5,16 @@ import { } from "@ui5-language-assistant/test-utils"; import { generate } from "@ui5-language-assistant/semantic-model"; import { UI5SemanticModel } from "@ui5-language-assistant/semantic-model-types"; -import { getNodeDocumentation } from "../src/documentation"; +import { + getNodeDetail, + getNodeDocumentation, + getUI5NodeName, +} from "../src/documentation"; +import { dir as tempDir } from "tmp-promise"; + +import { Position } from "vscode-languageserver"; +import { TextDocument } from "vscode-languageserver-textdocument"; +import { getSemanticModel } from "../src/ui5-model"; describe("The @ui5-language-assistant/language-server function", () => { let ui5SemanticModel: UI5SemanticModel; @@ -89,5 +98,83 @@ describe("The @ui5-language-assistant/language-server fun const result = getNodeDocumentation(ui5Enum, ui5SemanticModel); expect(result.value).to.include("Experimental."); }); + + it("get the UI5 node name with a model", async () => { + const xmlSnippet = ` + + + + + + `; + const { document, position } = getXmlSnippet(xmlSnippet); + + const result = await getUI5NodeName( + document.offsetAt(position), + document.getText(), + ui5SemanticModel + ); + if (result) { + expect(getNodeDetail(result)).to.equal("sap.m.Input"); + } else { + expect(result).to.equal("sap.m.Input"); + } + }); + + it("get the UI5 node name without a model", async () => { + const xmlSnippet = ` + + + + + + `; + + const cachePath = await tempDir(); + const ui5Model = await getSemanticModel( + cachePath.path, + "SAPUI5", + undefined, + true + ); + + const { document, position } = getXmlSnippet(xmlSnippet); + + const result = await getUI5NodeName( + document.offsetAt(position), + document.getText(), + undefined, + cachePath.path, + "SAPUI5", + ui5Model.version + ); + if (result) expect(getNodeDetail(result)).to.equal("sap.m.Input"); + else { + expect(result).to.equal("sap.m.Input"); + } + }); }); + + function getXmlSnippet( + xmlSnippet: string + ): { document: TextDocument; position: Position } { + const xmlText = xmlSnippet.replace("⇶", ""); + const offset = xmlSnippet.indexOf("Input"); + const document: TextDocument = createTextDocument("xml", xmlText); + const position: Position = document.positionAt(offset); + return { document, position }; + } + + function createTextDocument( + languageId: string, + content: string + ): TextDocument { + return TextDocument.create("uri", languageId, 0, content); + } }); diff --git a/packages/language-server/test/hover-spec.ts b/packages/language-server/test/hover-spec.ts index 6f9d3d8ec..447613cf6 100644 --- a/packages/language-server/test/hover-spec.ts +++ b/packages/language-server/test/hover-spec.ts @@ -28,7 +28,7 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { }); context("hover on attribute key", () => { - it("will get hover content UI5 property", () => { + it("will get hover content UI5 property", async () => { const xmlSnippet = ` @@ -36,7 +36,7 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { `; - const response = getHoverItem(xmlSnippet, ui5SemanticModel); + const response = await getHoverItem(xmlSnippet, ui5SemanticModel); expectExists(response, "Hover item"); assertMarkup(response.contents); expect(response.contents.value).to.include( @@ -44,13 +44,13 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { ); }); - it("will get hover content UI5 association", () => { + it("will get hover content UI5 association", async () => { const xmlSnippet = ` `; - const response = getHoverItem(xmlSnippet, ui5SemanticModel); + const response = await getHoverItem(xmlSnippet, ui5SemanticModel); expectExists(response, "Hover item"); assertMarkup(response.contents); expect(response.contents.value).to.include( @@ -58,12 +58,12 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { ); }); - it("will get hover content UI5 event", () => { + it("will get hover content UI5 event", async () => { const xmlSnippet = ` `; - const response = getHoverItem(xmlSnippet, ui5SemanticModel); + const response = await getHoverItem(xmlSnippet, ui5SemanticModel); expectExists(response, "Hover item"); assertMarkup(response.contents); expect(response.contents.value).to.include( @@ -71,7 +71,7 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { ); }); - it("will get hover content UI5 property - incorrect property", () => { + it("will get hover content UI5 property - incorrect property", async () => { const xmlSnippet = ` @@ -79,13 +79,13 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { `; - const response = getHoverItem(xmlSnippet, ui5SemanticModel); + const response = await getHoverItem(xmlSnippet, ui5SemanticModel); expect(response).to.not.exist; }); }); - context("hover on attribute value", () => { - it("will get hover content UI5 enum", () => { + context("hover on attribute value", async () => { + it("will get hover content UI5 enum", async () => { const xmlSnippet = ` @@ -93,7 +93,7 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { `; - const response = getHoverItem(xmlSnippet, ui5SemanticModel); + const response = await getHoverItem(xmlSnippet, ui5SemanticModel); expectExists(response, "Hover item"); assertMarkup(response.contents); expect(response.contents.value).to.include( @@ -101,7 +101,7 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { ); }); - it("will get hover content UI5 namespace in xmlns attribute", () => { + it("will get hover content UI5 namespace in xmlns attribute", async () => { const xmlSnippet = ` @@ -109,7 +109,7 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { `; - const response = getHoverItem(xmlSnippet, ui5SemanticModel); + const response = await getHoverItem(xmlSnippet, ui5SemanticModel); expectExists(response, "Hover item"); assertMarkup(response.contents); expect(response.contents.value).to.include( @@ -119,7 +119,7 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { }); context("hover on element open tag name", () => { - it("will get hover content UI5 class", () => { + it("will get hover content UI5 class", async () => { const xmlSnippet = ` @@ -127,7 +127,7 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { `; - const response = getHoverItem(xmlSnippet, ui5SemanticModel); + const response = await getHoverItem(xmlSnippet, ui5SemanticModel); expectExists(response, "Hover item"); assertMarkup(response.contents); expect(response.contents.value).to.include( @@ -135,7 +135,7 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { ); }); - it("will get hover content UI5 Aggregation", () => { + it("will get hover content UI5 Aggregation", async () => { const xmlSnippet = ` @@ -143,7 +143,7 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { `; - const response = getHoverItem(xmlSnippet, ui5SemanticModel); + const response = await getHoverItem(xmlSnippet, ui5SemanticModel); expectExists(response, "Hover item"); assertMarkup(response.contents); expect(response.contents.value).to.include("Child Controls of the view"); @@ -151,7 +151,7 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { }); context("hover on element close tag name", () => { - it("will get hover content UI5 class", () => { + it("will get hover content UI5 class", async () => { const xmlSnippet = ` @@ -159,7 +159,7 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { `; - const response = getHoverItem(xmlSnippet, ui5SemanticModel); + const response = await getHoverItem(xmlSnippet, ui5SemanticModel); expectExists(response, "Hover item"); assertMarkup(response.contents); expect(response.contents.value).to.include( @@ -167,7 +167,7 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { ); }); - it("will get hover content UI5 Aggregation", () => { + it("will get hover content UI5 Aggregation", async () => { const xmlSnippet = ` @@ -175,20 +175,20 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { `; - const response = getHoverItem(xmlSnippet, ui5SemanticModel); + const response = await getHoverItem(xmlSnippet, ui5SemanticModel); expectExists(response, "Hover item"); assertMarkup(response.contents); expect(response.contents.value).to.include("Child Controls of the view"); }); }); - it("will get hover content - open tag is different from close tag", () => { + it("will get hover content - open tag is different from close tag", async () => { const xmlSnippet = ` `; - const response = getHoverItem(xmlSnippet, ui5SemanticModel); + const response = await getHoverItem(xmlSnippet, ui5SemanticModel); expectExists(response, "Hover item"); assertMarkup(response.contents); expect(response.contents.value).to.include( @@ -196,7 +196,7 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { ); }); - it("will get undefined hover content", () => { + it("will get undefined hover content", async () => { const xmlSnippet = ` @@ -204,15 +204,15 @@ describe("the UI5 language assistant Hover Tooltip Service", () => { `; - const response = getHoverItem(xmlSnippet, ui5SemanticModel); + const response = await getHoverItem(xmlSnippet, ui5SemanticModel); expect(response).to.not.exist; }); }); -export function getHoverItem( +export async function getHoverItem( xmlSnippet: string, ui5SemanticModel: UI5SemanticModel -): Hover | undefined { +): Promise { const { document, position } = getXmlSnippet(xmlSnippet); const uri: TextDocumentIdentifier = { uri: "uri" }; const textDocPositionParams: TextDocumentPositionParams = { @@ -220,7 +220,7 @@ export function getHoverItem( position: position, }; - const hoverItem = getHoverResponse( + const hoverItem = await getHoverResponse( ui5SemanticModel, textDocPositionParams, document diff --git a/packages/logic-utils/package.json b/packages/logic-utils/package.json index ae9a2fadc..8950fddc7 100644 --- a/packages/logic-utils/package.json +++ b/packages/logic-utils/package.json @@ -27,7 +27,7 @@ "scripts": { "ci": "npm-run-all clean compile lint coverage", "clean": "rimraf ./lib ./coverage ./nyc_output", - "compile": "yarn run clean && tsc -p .", + "compile": "tsc -p .", "compile:watch": "tsc -p . --watch", "lint": "eslint . --ext .ts --max-warnings=0 --ignore-path=../../.gitignore", "test": "mocha", diff --git a/packages/semantic-model-types/package.json b/packages/semantic-model-types/package.json index f399b2c55..14b64e185 100644 --- a/packages/semantic-model-types/package.json +++ b/packages/semantic-model-types/package.json @@ -14,7 +14,7 @@ "scripts": { "ci": "npm-run-all clean compile lint coverage", "clean": "echo \"nothing to clean\"", - "compile": "yarn run clean && tsc -p .", + "compile": "tsc -p .", "compile:watch": "tsc -p . --watch", "lint": "eslint . --ext .ts --max-warnings=0 --ignore-path=../../.gitignore", "test": "echo nothing to test", diff --git a/packages/semantic-model/package.json b/packages/semantic-model/package.json index 085737b00..1786d5700 100644 --- a/packages/semantic-model/package.json +++ b/packages/semantic-model/package.json @@ -30,7 +30,7 @@ "scripts": { "ci": "npm-run-all clean generate-json-api compile lint coverage", "clean": "rimraf ./lib ./coverage ./nyc_output", - "compile": "yarn run clean && tsc -p .", + "compile": "tsc -p .", "compile:watch": "tsc -p . --watch", "lint": "eslint . --ext .ts --max-warnings=0 --ignore-path=../../.gitignore", "test": "mocha", diff --git a/packages/settings/package.json b/packages/settings/package.json index 04ca4c10b..3b07da302 100644 --- a/packages/settings/package.json +++ b/packages/settings/package.json @@ -20,7 +20,7 @@ "scripts": { "ci": "npm-run-all clean compile lint coverage", "clean": "rimraf ./lib ./coverage ./nyc_output", - "compile": "yarn run clean && tsc -p .", + "compile": "tsc -p .", "compile:watch": "tsc -p . --watch", "lint": "eslint . --ext .ts --max-warnings=0 --ignore-path=../../.gitignore", "test": "mocha", diff --git a/packages/user-facing-text/package.json b/packages/user-facing-text/package.json index 62e3e1667..33748ce2b 100644 --- a/packages/user-facing-text/package.json +++ b/packages/user-facing-text/package.json @@ -20,7 +20,7 @@ "scripts": { "ci": "npm-run-all clean compile lint coverage", "clean": "rimraf ./lib ./coverage ./nyc_output", - "compile": "yarn run clean && tsc -p .", + "compile": "tsc -p .", "compile:watch": "tsc -p . --watch", "lint": "eslint . --ext .ts --max-warnings=0 --ignore-path=../../.gitignore", "test": "mocha", diff --git a/packages/vscode-ui5-language-assistant/package.json b/packages/vscode-ui5-language-assistant/package.json index 2f5ee44bd..70dbe2598 100644 --- a/packages/vscode-ui5-language-assistant/package.json +++ b/packages/vscode-ui5-language-assistant/package.json @@ -20,6 +20,20 @@ "*" ], "contributes": { + "commands": [ + { + "command": "UI5LanguageAssistant.command.webView", + "title": "UI5-Language-Assistant: View API Reference" + } + ], + "keybindings": [ + { + "command": "UI5LanguageAssistant.command.webView", + "key": "F12", + "mac": "F12", + "when": "resourceLangId == xml" + } + ], "jsonValidation": [ { "fileMatch": [ @@ -82,7 +96,7 @@ "scripts": { "ci": "npm-run-all clean compile lint coverage:* bundle package", "clean": "rimraf ./dist ./lib ./coverage ./nyc_output *.vsix NOTICE LICENSE", - "compile": "yarn run clean && tsc -p .", + "compile": "tsc -p .", "compile:watch": "tsc -p . --watch", "lint": "eslint . --ext .ts --max-warnings=0 --ignore-path=../../.gitignore", "test:integration": "node lib/test/run-integration-tests.js", diff --git a/packages/vscode-ui5-language-assistant/resources/logo_ui5.png b/packages/vscode-ui5-language-assistant/resources/logo_ui5.png new file mode 100644 index 000000000..627798ea1 Binary files /dev/null and b/packages/vscode-ui5-language-assistant/resources/logo_ui5.png differ diff --git a/packages/vscode-ui5-language-assistant/src/constants.ts b/packages/vscode-ui5-language-assistant/src/constants.ts index 799dfcb08..ee96afa3f 100644 --- a/packages/vscode-ui5-language-assistant/src/constants.ts +++ b/packages/vscode-ui5-language-assistant/src/constants.ts @@ -1,2 +1,4 @@ export const COMMAND_OPEN_DEMOKIT = "UI5LanguageAssistant.command.openDemokit"; +export const COMMAND_OPEN_WEBVIEW = "UI5LanguageAssistant.command.webView"; export const LOGGING_LEVEL_CONFIG_PROP = "UI5LanguageAssistant.logging.level"; +// export const REPLACE_GO_TO_DEFINITION = "editor.action.goToDeclaration"; diff --git a/packages/vscode-ui5-language-assistant/src/extension.ts b/packages/vscode-ui5-language-assistant/src/extension.ts index 52b85cfb8..be0fe3d62 100644 --- a/packages/vscode-ui5-language-assistant/src/extension.ts +++ b/packages/vscode-ui5-language-assistant/src/extension.ts @@ -1,5 +1,6 @@ /* istanbul ignore file */ import { resolve } from "path"; +import { URL } from "url"; import { readFileSync } from "fs"; import { workspace, @@ -10,6 +11,8 @@ import { commands, env, Uri, + WebviewPanel, + ViewColumn, } from "vscode"; import { LanguageClient, @@ -21,27 +24,45 @@ import { LogLevel } from "@vscode-logging/types"; import { SERVER_PATH, ServerInitializationOptions, + getNodeName, } from "@ui5-language-assistant/language-server"; -import { COMMAND_OPEN_DEMOKIT, LOGGING_LEVEL_CONFIG_PROP } from "./constants"; - -type UI5Model = { url: string; framework: string; version: string }; +import { + COMMAND_OPEN_DEMOKIT, + COMMAND_OPEN_WEBVIEW, + LOGGING_LEVEL_CONFIG_PROP, +} from "./constants"; + +type UI5Model = { + cachePath: string; + url: string; + framework: string; + version: string; +}; let client: LanguageClient; let statusBarItem: StatusBarItem; let currentModel: UI5Model | undefined; - +let currentPanel: WebviewPanel | undefined; +let extContext: ExtensionContext | undefined; export async function activate(context: ExtensionContext): Promise { // create the LanguageClient (+Server) client = createLanguageClient(context); - + extContext = context; // create the StatusBarItem which displays the used UI5 version statusBarItem = createStatusBarItem(context); - + createWebView(context); // show/hide and update the status bar client.onReady().then(() => { - client.onNotification("UI5LanguageAssistant/ui5Model", (model: UI5Model) => - updateCurrentModel(model) + client.onNotification( + "UI5LanguageAssistant/ui5Model", + (model: UI5Model) => { + updateCurrentModel(model); + } ); + + client.onNotification("UI5LanguageAssistant/ui5Definition", (params) => { + showWebView(params.url); + }); }); window.onDidChangeActiveTextEditor(() => { updateCurrentModel(undefined); @@ -52,7 +73,7 @@ export async function activate(context: ExtensionContext): Promise { function createLanguageClient(context: ExtensionContext): LanguageClient { const debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] }; - + console.log(SERVER_PATH); const serverOptions: ServerOptions = { run: { module: SERVER_PATH, transport: TransportKind.ipc }, debug: { @@ -110,6 +131,12 @@ function createStatusBarItem(context: ExtensionContext): StatusBarItem { return statusBarItem; } +function createWebView(context: ExtensionContext) { + context.subscriptions.push( + commands.registerCommand(COMMAND_OPEN_WEBVIEW, showWebView) + ); +} + function updateCurrentModel(model: UI5Model | undefined) { currentModel = model; if (statusBarItem) { @@ -125,6 +152,97 @@ function updateCurrentModel(model: UI5Model | undefined) { } } +async function showWebView(url?: string | undefined) { + const columnToShowIn = + window.activeTextEditor && window.activeTextEditor.viewColumn + ? window.activeTextEditor.viewColumn + 1 + : ViewColumn.One; + const apiRefUrl = url || currentModel?.url; + if (!apiRefUrl) + throw Error("UI5-Language-Assistant: Model not initialised, try again"); + const document = window.activeTextEditor?.document; + let name: string | undefined; + if ( + currentModel?.framework && + currentModel.version && + window.activeTextEditor?.selection.active && + document + ) { + name = await getNodeName( + document.getText(), + document.offsetAt(window.activeTextEditor?.selection.active), + currentModel.cachePath, + currentModel.framework, + currentModel.version + ).catch((Error) => { + console.log(Error); + return undefined; + }); + } + const ui5Url = `${new URL(apiRefUrl).href}${name ? "#/api/" + name : ""}`; + + const createWebView = () => { + currentPanel = window.createWebviewPanel( + "ui5APIRef", + "UI5 API Reference", + columnToShowIn, + { + enableScripts: true, + } + ); + currentPanel.iconPath = Uri.file( + resolve(__dirname, "..", "..", "resources", "logo_ui5.png") + ); + currentPanel.webview.html = getWebviewContent(ui5Url); + + // Reset when the current panel is closed + currentPanel.onDidDispose( + () => { + currentPanel = undefined; + }, + null, + extContext?.subscriptions + ); + }; + if (currentPanel) { + // If the panel is disposed, then create a new one. + try { + currentPanel.webview.postMessage({ url: ui5Url }); + + currentPanel.reveal(columnToShowIn); + } catch (error) { + currentPanel = undefined; + // Otherwise, create a new panel. + createWebView(); + } + } else { + createWebView(); + } +} + +function getWebviewContent(ui5Url: string) { + //Check if we are in a tag + + return ` + + + + + + + + + + `; +} + export function deactivate(): Thenable | undefined { if (!client) { return undefined; diff --git a/packages/xml-views-completion/package.json b/packages/xml-views-completion/package.json index 5de09fd79..32d90c4a6 100644 --- a/packages/xml-views-completion/package.json +++ b/packages/xml-views-completion/package.json @@ -35,7 +35,7 @@ "scripts": { "ci": "npm-run-all clean compile lint coverage", "clean": "rimraf ./lib ./coverage ./nyc_output", - "compile": "yarn run clean && tsc -p .", + "compile": "tsc -p .", "compile:watch": "tsc -p . --watch", "lint": "eslint . --ext .ts --max-warnings=0 --ignore-path=../../.gitignore", "test": "mocha", diff --git a/packages/xml-views-quick-fix/package.json b/packages/xml-views-quick-fix/package.json index 833ed0a76..84ba7b66d 100644 --- a/packages/xml-views-quick-fix/package.json +++ b/packages/xml-views-quick-fix/package.json @@ -33,7 +33,7 @@ "scripts": { "ci": "npm-run-all clean compile lint coverage", "clean": "rimraf ./lib ./coverage ./nyc_output", - "compile": "yarn run clean && tsc -p .", + "compile": "tsc -p .", "compile:watch": "tsc -p . --watch", "lint": "eslint . --ext .ts --max-warnings=0 --ignore-path=../../.gitignore", "test": "mocha", diff --git a/packages/xml-views-tooltip/package.json b/packages/xml-views-tooltip/package.json index 53cd6be51..0987db75a 100644 --- a/packages/xml-views-tooltip/package.json +++ b/packages/xml-views-tooltip/package.json @@ -35,7 +35,7 @@ "scripts": { "ci": "npm-run-all clean compile lint coverage", "clean": "rimraf ./lib ./coverage ./nyc_output", - "compile": "yarn run clean && tsc -p .", + "compile": "tsc -p .", "compile:watch": "tsc -p . --watch", "lint": "eslint . --ext .ts --max-warnings=0 --ignore-path=../../.gitignore", "test": "mocha", diff --git a/packages/xml-views-validation/package.json b/packages/xml-views-validation/package.json index d5aa37b59..046a0d4ed 100644 --- a/packages/xml-views-validation/package.json +++ b/packages/xml-views-validation/package.json @@ -36,7 +36,7 @@ "scripts": { "ci": "npm-run-all clean compile lint coverage", "clean": "rimraf ./lib ./coverage ./nyc_output", - "compile": "yarn run clean && tsc -p .", + "compile": "tsc -p .", "compile:watch": "tsc -p . --watch", "lint": "eslint . --ext .ts --max-warnings=0 --ignore-path=../../.gitignore", "test": "mocha", diff --git a/test-packages/test-utils/package.json b/test-packages/test-utils/package.json index 49a168a1f..45f214d13 100644 --- a/test-packages/test-utils/package.json +++ b/test-packages/test-utils/package.json @@ -23,7 +23,7 @@ "scripts": { "ci": "npm-run-all clean compile lint", "clean": "rimraf ./lib ./coverage ./nyc_output ./.model-cache", - "compile": "yarn run clean && tsc -p .", + "compile": "tsc -p .", "compile:watch": "tsc -p . --watch", "lint": "eslint . --ext .ts --max-warnings=0 --ignore-path=../../.gitignore" } diff --git a/turbo.json b/turbo.json new file mode 100644 index 000000000..092bcf8d3 --- /dev/null +++ b/turbo.json @@ -0,0 +1,56 @@ +{ + "$schema": "https://turborepo.org/schema.json", + "pipeline": { + "build": { + "dependsOn": ["^generate-json-api"], + "outputs": ["lib/**"] + }, + + "ci": { + "dependsOn": ["^package"], + "outputs": ["dist/**", "lib/**"] + }, + "bundle": { + "dependsOn": ["compile"], + "outputs": ["lib/**"] + }, + "package": { + "dependsOn": ["^bundle"], + "outputs": ["*.vsix"] + }, + "generate-json-api": { + "inputs": ["resources/sap-ui-library-api.json"], + "outputs": ["src"] + }, + "compile": { + "dependsOn": ["clean"], + "outputs": ["lib/**"] + }, + "clean": { + "inputs": ["**/src/**/*.d.ts", "**/src/**/*.ts", "**/test/**/*.ts"], + "outputs": [] + }, + "pretest": { + "dependsOn": ["^compile"], + "inputs": ["**/src/**/*.d.ts", "**/src/**/*.ts", "**/test/**/*.ts"], + "outputs": [] + }, + "coverage": { + "dependsOn": ["^coverage"], + "outputs": ["coverage"], + "inputs": ["**/src/**/*.d.ts", "**/src/**/*.ts", "**/test/**/*.ts"] + }, + "format:validate": { + "outputs": [] + }, + "//#format:fix": { + "outputs": [] + }, + "lint": { + "outputs": [] + }, + "dev": { + "cache": false + } + } +} diff --git a/yarn.lock b/yarn.lock index 8d71ac438..97462ce55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8720,6 +8720,96 @@ tunnel@0.0.4: resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.4.tgz#2d3785a158c174c9a16dc2c046ec5fc5f1742213" integrity sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM= +turbo-android-arm64@1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/turbo-android-arm64/-/turbo-android-arm64-1.4.6.tgz#2127110335a86a50856852c2728eb75f7f61b77b" + integrity sha512-YxSlHc64CF5J7yNUMiLBHkeLyzrpe75Oy7tivWb3z7ySG44BXPikk4HDJZPh0T1ELvukDwuPKkvDukJ2oCLJpA== + +turbo-darwin-64@1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.4.6.tgz#8b3d930ed0d0b8c358d87ed2347381496f4283dd" + integrity sha512-f6uto7LLpjwZ6iZSF+8uaDpuiTji6xmnWDxNuW23DBE8iv5mxehHd+6Ys851uKDRrPb3QdCu9ctyigKTAla5Vg== + +turbo-darwin-arm64@1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-1.4.6.tgz#7f045cbfbb1d6ac18ea28122b9a6a5fdc629488a" + integrity sha512-o9C6e5XyuMHQwE0fEhUxfpXxvNr2QXXWX8nxIjygxeF19AqKbk/s08vZBOEmXV6/gx/pRhZ1S2nf0PIUjKBD/Q== + +turbo-freebsd-64@1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/turbo-freebsd-64/-/turbo-freebsd-64-1.4.6.tgz#b16c5617f2e818a709627351f1e14d1fd8dcf0e7" + integrity sha512-Gg9VOUo6McXYKGevcYjGUSmMryZyZggvpdPh7Dw3QTcT8Tsy6OBtq6WnJ2O4kFDsMigyKtEOJPceD9vDMZt3yQ== + +turbo-freebsd-arm64@1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/turbo-freebsd-arm64/-/turbo-freebsd-arm64-1.4.6.tgz#462b8ba68cccac93d169c80cf458d221c662a770" + integrity sha512-W7VrcneWFN1QENKt5cpAPSsf9ArYBBAm3VtPBZEO5tX8kuahGlah1SKdKJXrRxYOY82wyNxDagS/rHpBlrAAzw== + +turbo-linux-32@1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/turbo-linux-32/-/turbo-linux-32-1.4.6.tgz#0a0859be0941e4bcdc4bff81b97ee36f02cc1ffd" + integrity sha512-76j/zsui6mWPX8pZVMGgF8eiKHPmKuGa2lo0A/Ja0HUvdYCOGUfHsWJGVVIeYbuEp3jsKyVt7OnMDeH9CqO6bg== + +turbo-linux-64@1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-1.4.6.tgz#0a7d76fab78d7850c26d9d6b372c40ffca9835f8" + integrity sha512-z4A37Xm7lZyO9ddtGnvQHWMrsAKX6vFBxdbtb9MY76VRblo7lWSuk4LwCeM+T+ZDJ9LBFiF7aD/diRShlLx9jA== + +turbo-linux-arm64@1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-1.4.6.tgz#c66d3c6917ccbdb34cd7ce37c900613f4d690ebc" + integrity sha512-FW1jmOpZfOoVVvml338N0MPnYjiMyYWTaMb4T+IosgGYymcUE3xJjfXJcqfU/9/uKTyY8zG0qr9/5rw2kpMS2Q== + +turbo-linux-arm@1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/turbo-linux-arm/-/turbo-linux-arm-1.4.6.tgz#9a9d73a722bdd6acb40276d0616c155168a32172" + integrity sha512-Uh/V3oaAdhyZW6FKPpKihAxQo3EbvLaVNnzzkBmBnvHRkqoDJHhpuG72V7nn8pzxVbJ1++NEVjvbc2kmKFvGjg== + +turbo-linux-mips64le@1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/turbo-linux-mips64le/-/turbo-linux-mips64le-1.4.6.tgz#eb74c333c16ef03aa30dcb83fcc29d58218656e4" + integrity sha512-iWaL3Pwj52BH3T2M8nXScmbSnq4+x47MYK7lJMG7FsZGAIoT5ToO1Wt1iX3GRHTcnIZYm/kCfJ1ptK/NCossLA== + +turbo-linux-ppc64le@1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/turbo-linux-ppc64le/-/turbo-linux-ppc64le-1.4.6.tgz#74597f4c30fe73c92ef8912e4bf25ccbe7c7ec7e" + integrity sha512-Af/KlUmpiORDyELxT7byXNWl3fefErGQMJfeqXEtAdhs8OCKQWuU+lchcZbiBZYNpL+lZoa3PAmP9Fpx7R4plA== + +turbo-windows-32@1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/turbo-windows-32/-/turbo-windows-32-1.4.6.tgz#df1f3c25fea0bbccf7c5b44111ddbcd57f6fe547" + integrity sha512-NBd+XPlRSaR//lVN13Q9DOqK3CbowSvafIyGsO4jfvMsGTdyNDL6AYtFsvTKW91/G7ZhATmSEkPn2pZRuhP/DA== + +turbo-windows-64@1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-1.4.6.tgz#6a7d8897bb60234b6bc4b5d013adb00fac6f2beb" + integrity sha512-86AbmG+CjzVTpn4RGtwU2CYy4zSyAc9bIQ4pDGLIpCJg6JlD11duaiMJh0SCU/HCqWLJjWDI4qD+f9WNbgPsyQ== + +turbo-windows-arm64@1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-1.4.6.tgz#4c80528c6670ef50129053ad8279c832190b7234" + integrity sha512-V+pWcqhTtmQQ3ew8qEjYtUwzyW6tO1RgvP+6OKzItYzTnMTr1Fe42Q21V+tqRNxuNfFDKsgVJdk2p5wB87bvyQ== + +turbo@^1.4.5: + version "1.4.6" + resolved "https://registry.yarnpkg.com/turbo/-/turbo-1.4.6.tgz#c97c23cf898380bedabd04c5a91ab4eb9829bcdc" + integrity sha512-FKtBXlOJ7YjSK22yj4sJLCtDcHFElypt7xw9cZN7Wyv9x4XBrTmh5KP6RmcGnRR1/GJlTNwD2AY2T9QTPnHh+g== + optionalDependencies: + turbo-android-arm64 "1.4.6" + turbo-darwin-64 "1.4.6" + turbo-darwin-arm64 "1.4.6" + turbo-freebsd-64 "1.4.6" + turbo-freebsd-arm64 "1.4.6" + turbo-linux-32 "1.4.6" + turbo-linux-64 "1.4.6" + turbo-linux-arm "1.4.6" + turbo-linux-arm64 "1.4.6" + turbo-linux-mips64le "1.4.6" + turbo-linux-ppc64le "1.4.6" + turbo-windows-32 "1.4.6" + turbo-windows-64 "1.4.6" + turbo-windows-arm64 "1.4.6" + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"