diff --git a/README.md b/README.md index f690ee2..9f226ad 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ * Hovering over a node shows description *if available* 5. Document outlining: * Shows a complete document outline of all nodes in the document +6. Go to definition for Templates + * Referenced templates can be resolved to a local file (if it exists) ## Developer Support diff --git a/language-server/src/server.ts b/language-server/src/server.ts index 3c4bfcb..c373831 100644 --- a/language-server/src/server.ts +++ b/language-server/src/server.ts @@ -9,7 +9,7 @@ import { createConnection, Connection, TextDocuments, InitializeParams, InitializeResult, NotificationType, RequestType, - DocumentFormattingRequest, Disposable, ProposedFeatures, CompletionList, TextDocumentSyncKind, ClientCapabilities + DocumentFormattingRequest, Disposable, ProposedFeatures, CompletionList, TextDocumentSyncKind, ClientCapabilities, DefinitionParams, } from "vscode-languageserver/node"; import { TextDocument } from 'vscode-languageserver-textdocument'; @@ -87,7 +87,7 @@ let workspaceRoot: URI; connection.onInitialize((params: InitializeParams): InitializeResult => { capabilities = params.capabilities; workspaceFolders = params["workspaceFolders"]; - workspaceRoot = URI.parse(params.rootPath); + workspaceRoot = URI.parse(params.rootUri, true); function hasClientCapability(...keys: string[]) { let c = params.capabilities; @@ -104,6 +104,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { textDocumentSync: TextDocumentSyncKind.Full, completionProvider: { resolveProvider: true }, hoverProvider: true, + definitionProvider: true, documentSymbolProvider: true, documentFormattingProvider: false } @@ -495,4 +496,15 @@ connection.onDocumentFormatting(formatParams => { return customLanguageService.doFormat(document, formatParams.options, customTags); }); +connection.onDefinition((definitionParams: DefinitionParams) => { + let document = documents.get(definitionParams.textDocument.uri); + + if(!document){ + return; + } + + let jsonDocument = parseYAML(document.getText()); + return customLanguageService.doDefinition(document, definitionParams.position, jsonDocument, workspaceRoot); +}) + connection.listen(); diff --git a/language-service/src/services/yamlDefinition.ts b/language-service/src/services/yamlDefinition.ts new file mode 100644 index 0000000..4eba5a8 --- /dev/null +++ b/language-service/src/services/yamlDefinition.ts @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as path from "path"; +import { PromiseConstructor, Thenable } from "vscode-json-languageservice"; +import { TextDocument, Position, Definition, Location, Range } from "vscode-languageserver-types"; +import { URI, Utils } from "vscode-uri"; + +import { StringASTNode } from "../parser/jsonParser"; +import { YAMLDocument } from "../parser/yamlParser"; + +export class YAMLDefinition { + + private promise: PromiseConstructor; + + constructor(promiseConstructor: PromiseConstructor) { + this.promise = promiseConstructor || Promise; + } + + public doDefinition(document: TextDocument, position: Position, yamlDocument: YAMLDocument, workspaceRoot: URI): Thenable { + const offset = document.offsetAt(position); + + const jsonDocument = yamlDocument.documents.length > 0 ? yamlDocument.documents[0] : null; + if(jsonDocument === null){ + return this.promise.resolve(void 0); + } + + const node = jsonDocument.getNodeFromOffset(offset); + + // can only jump to definition for template declaration, which means: + // * we must be on a string node that is acting as a value (vs a key) + // * the key (location) must be "template" + // + // In other words... + // - template: my_cool_template.yml + // ^^^^^^^^^^^^^^^^^^^^ this part + if (!(node instanceof StringASTNode) || node.location !== 'template' || node.isKey) { + return this.promise.resolve(void 0); + } + + let [location, resource] = node + .value + .split('@'); + + // cannot jump to external resources + if (resource && resource !== 'self') { + return this.promise.resolve(void 0); + } + + // Azure Pipelines accepts both forward and back slashes as path separators, + // even when running on non-Windows. + // To make things easier, normalize all path separators into this platform's path separator. + // That way, vscode-uri will operate on the separators as expected. + location = location + .replaceAll(path.posix.sep, path.sep) + .replaceAll(path.win32.sep, path.sep); + + // determine if abs path (from root) or relative path + // NOTE: Location.create takes in a string, even though the parameter is called 'uri'. + // So create an actual URI, then .toString() it and skip the unnecessary encoding. + let definitionUri = ''; + if (location.startsWith(path.sep)) { + // Substring to strip the leading separator. + definitionUri = Utils.joinPath(workspaceRoot, location.substring(1)).toString(true); + } + else { + definitionUri = Utils.resolvePath( + Utils.dirname(URI.parse(document.uri, true)), + location + ).toString(true); + } + + const definition = Location.create(definitionUri, Range.create(0, 0, 0, 0)); + + return this.promise.resolve(definition); + } +} diff --git a/language-service/src/yamlLanguageService.ts b/language-service/src/yamlLanguageService.ts index 7906cd3..85314c8 100644 --- a/language-service/src/yamlLanguageService.ts +++ b/language-service/src/yamlLanguageService.ts @@ -5,12 +5,14 @@ *--------------------------------------------------------------------------------------------*/ import { JSONSchemaService, CustomSchemaProvider } from './services/jsonSchemaService' import { TextDocument, Position, CompletionList, FormattingOptions, Diagnostic, - CompletionItem, TextEdit, Hover, SymbolInformation + CompletionItem, TextEdit, Hover, SymbolInformation, Definition } from 'vscode-languageserver-types'; +import { URI } from "vscode-uri"; import { JSONSchema } from './jsonSchema'; import { YAMLDocumentSymbols } from './services/documentSymbols'; import { YAMLCompletion } from "./services/yamlCompletion"; import { YAMLHover } from "./services/yamlHover"; +import { YAMLDefinition } from "./services/yamlDefinition"; import { YAMLValidation } from "./services/yamlValidation"; import { format } from './services/yamlFormatter'; import { JSONWorkerContribution } from './jsonContributions'; @@ -99,6 +101,7 @@ export interface LanguageService { doComplete(document: TextDocument, position: Position, yamlDocument: YAMLDocument): Thenable; doValidation(document: TextDocument, yamlDocument: YAMLDocument): Thenable; doHover(document: TextDocument, position: Position, doc: YAMLDocument): Thenable; + doDefinition(document: TextDocument, position: Position, doc: YAMLDocument, workspaceRoot: URI): Thenable; findDocumentSymbols(document: TextDocument, doc: YAMLDocument): SymbolInformation[]; doResolve(completionItem: CompletionItem): Thenable; resetSchema(uri: string): boolean; @@ -120,6 +123,7 @@ export function getLanguageService( let completer = new YAMLCompletion(schemaService, contributions, promise); let hover = new YAMLHover(schemaService, contributions, promise); + let definition = new YAMLDefinition(promise); let yamlDocumentSymbols = new YAMLDocumentSymbols(); let yamlValidation = new YAMLValidation(schemaService, promise); let yamlTraversal = new YAMLTraversal(promise); @@ -140,6 +144,7 @@ export function getLanguageService( doResolve: completer.doResolve.bind(completer), doValidation: yamlValidation.doValidation.bind(yamlValidation), doHover: hover.doHover.bind(hover), + doDefinition: definition.doDefinition.bind(definition), findDocumentSymbols: yamlDocumentSymbols.findDocumentSymbols.bind(yamlDocumentSymbols), resetSchema: (uri: string) => schemaService.onResourceChange(uri), doFormat: format, diff --git a/language-service/tsconfig.json b/language-service/tsconfig.json index 543fb0c..67253da 100644 --- a/language-service/tsconfig.json +++ b/language-service/tsconfig.json @@ -6,7 +6,10 @@ "moduleResolution": "node", "sourceMap": true, "declaration": true, - "lib" : [ "es2015", "dom" ], + "lib": [ + "es2021", + "dom" + ], "outDir": "./lib", "noUnusedLocals": true },