@microsoft/compose-language-service
Advanced tools
Comparing version 0.0.1-alpha to 0.0.2-alpha
@@ -1,3 +0,10 @@ | ||
## 0.0.1 - 20 September 2021 | ||
## 0.0.2-alpha - 29 October 2021 | ||
### Added | ||
* Significantly more completions have been added | ||
### Removed | ||
* Removed signature help for ports, in favor of completions instead | ||
## 0.0.1-alpha - 20 September 2021 | ||
### Added | ||
* Initial release! | ||
@@ -4,0 +11,0 @@ * Hyperlinks to Docker Hub for images |
@@ -5,7 +5,15 @@ /*!-------------------------------------------------------------------------------------------- | ||
*--------------------------------------------------------------------------------------------*/ | ||
import { ProtocolNotificationType, ProtocolRequestType } from 'vscode-languageserver-protocol'; | ||
import { TextDocumentIdentifier } from 'vscode-languageserver-types'; | ||
declare type LF = 1; | ||
declare type CRLF = 2; | ||
export declare type EndOfLine = LF | CRLF; | ||
import { ClientCapabilities, NotificationType, RequestType } from 'vscode-languageserver-protocol'; | ||
import { TextDocumentParams } from '../service/ExtendedParams'; | ||
export declare type ComposeLanguageClientCapabilities = ClientCapabilities & { | ||
experimental?: { | ||
documentSettings?: { | ||
request: boolean; | ||
notify: boolean; | ||
}; | ||
}; | ||
}; | ||
export declare const LF = 1; | ||
export declare const CRLF = 2; | ||
declare type EndOfLine = typeof LF | typeof CRLF; | ||
export interface DocumentSettings { | ||
@@ -15,12 +23,12 @@ tabSize: number; | ||
} | ||
export interface DocumentSettingsParams { | ||
textDocument: TextDocumentIdentifier; | ||
export declare type DocumentSettingsParams = TextDocumentParams; | ||
export declare namespace DocumentSettingsRequest { | ||
const method: "$/textDocument/documentSettings"; | ||
const type: RequestType<TextDocumentParams, DocumentSettings | null, never>; | ||
} | ||
export interface DocumentSettingsClientCapabilities { | ||
request: boolean; | ||
notify: boolean; | ||
export declare type DocumentSettingsNotificationParams = DocumentSettingsParams & DocumentSettings; | ||
export declare namespace DocumentSettingsNotification { | ||
const method: "$/textDocument/documentSettings/didChange"; | ||
const type: NotificationType<DocumentSettingsNotificationParams>; | ||
} | ||
export declare type DocumentSettingsNotificationParams = DocumentSettingsParams & DocumentSettings; | ||
export declare const DocumentSettingsRequestType: ProtocolRequestType<DocumentSettingsParams, DocumentSettings, never, never, never>; | ||
export declare const DocumentSettingsNotificationType: ProtocolNotificationType<DocumentSettingsNotificationParams, never>; | ||
export {}; |
@@ -7,6 +7,21 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.DocumentSettingsNotificationType = exports.DocumentSettingsRequestType = void 0; | ||
exports.DocumentSettingsNotification = exports.DocumentSettingsRequest = exports.CRLF = exports.LF = void 0; | ||
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol"); | ||
exports.DocumentSettingsRequestType = new vscode_languageserver_protocol_1.ProtocolRequestType('$/textDocument/documentSettings'); | ||
exports.DocumentSettingsNotificationType = new vscode_languageserver_protocol_1.ProtocolNotificationType('$/textDocument/documentSettings/didChange'); | ||
// TODO: can we get these from @types/vscode instead? It seems there's some type conflict between `Thenable<T>` from @types/vscode and vscode-jsonrpc preventing @types/vscode from working nicely | ||
exports.LF = 1; | ||
exports.CRLF = 2; | ||
// Use the same syntax as the LSP | ||
// eslint-disable-next-line @typescript-eslint/no-namespace | ||
var DocumentSettingsRequest; | ||
(function (DocumentSettingsRequest) { | ||
DocumentSettingsRequest.method = '$/textDocument/documentSettings'; | ||
DocumentSettingsRequest.type = new vscode_languageserver_protocol_1.RequestType(DocumentSettingsRequest.method); | ||
})(DocumentSettingsRequest = exports.DocumentSettingsRequest || (exports.DocumentSettingsRequest = {})); | ||
// Use the same syntax as the LSP | ||
// eslint-disable-next-line @typescript-eslint/no-namespace | ||
var DocumentSettingsNotification; | ||
(function (DocumentSettingsNotification) { | ||
DocumentSettingsNotification.method = '$/textDocument/documentSettings/didChange'; | ||
DocumentSettingsNotification.type = new vscode_languageserver_protocol_1.NotificationType(DocumentSettingsNotification.method); | ||
})(DocumentSettingsNotification = exports.DocumentSettingsNotification || (exports.DocumentSettingsNotification = {})); | ||
//# sourceMappingURL=DocumentSettings.js.map |
@@ -0,0 +0,0 @@ /*!-------------------------------------------------------------------------------------------- |
@@ -0,0 +0,0 @@ "use strict"; |
@@ -5,13 +5,43 @@ /*!-------------------------------------------------------------------------------------------- | ||
*--------------------------------------------------------------------------------------------*/ | ||
import { Position, TextDocumentsConfiguration } from 'vscode-languageserver'; | ||
import { Position, TextDocumentIdentifier, TextDocumentsConfiguration } from 'vscode-languageserver'; | ||
import { TextDocument } from 'vscode-languageserver-textdocument'; | ||
import { CST, Document as YamlDocument } from 'yaml'; | ||
import { DocumentSettings } from '../client/DocumentSettings'; | ||
import { ExtendedPositionParams, PositionInfo } from './ExtendedParams'; | ||
import { Lazy } from './utils/Lazy'; | ||
export declare class ComposeDocument { | ||
readonly textDocument: TextDocument; | ||
#private; | ||
readonly fullCst: Lazy<CST.Token[]>; | ||
readonly documentCst: Lazy<CST.Document>; | ||
readonly yamlDocument: Lazy<YamlDocument<unknown>>; | ||
private documentSettings; | ||
get textDocument(): TextDocument; | ||
get id(): TextDocumentIdentifier; | ||
private constructor(); | ||
private update; | ||
/** | ||
* Gets the text of a line at a given line number or position, including the line break (`\n` or `\r\n`) at the end if present | ||
* @param line The line number or `Position` | ||
* @returns The line text | ||
*/ | ||
lineAt(line: Position | number): string; | ||
/** | ||
* Gets settings from the document. If already populated, that will be returned. If supported, the info will be requested from the client. | ||
* Otherwise, the settings will be heuristically guessed based on document contents. | ||
* Note: The client is also directed to notify the server if the settings change, via the `DocumentSettingsNotification` notification. | ||
* @returns The document settings (tab size, line endings, etc.) | ||
*/ | ||
getSettings(): Promise<DocumentSettings>; | ||
/** | ||
* Updates the settings (tab size, line endings, etc.) for the document. This is meant to be called by the server upon receiving a `DocumentSettingsNotification`. | ||
* @param params The new settings for the document | ||
*/ | ||
updateSettings(params: DocumentSettings): void; | ||
/** | ||
* Gets information about the position, including the tab depth and a logical path describing where in the YAML tree the position is | ||
* @param params The `ExtendedPositionParams` for the position being queried | ||
* @returns A `PositionInfo` object with tab depth and logical path | ||
* @example If the position is in a service foo's `image` key, the logical path would be `/services/foo/image`. | ||
*/ | ||
getPositionInfo(params: ExtendedPositionParams): Promise<PositionInfo>; | ||
static DocumentManagerConfig: TextDocumentsConfiguration<ComposeDocument>; | ||
@@ -21,2 +51,14 @@ private buildFullCst; | ||
private buildYamlDocument; | ||
private guessDocumentSettings; | ||
/** | ||
* This method is responsible for determining the part of the logical path for the line where the cursor is, given the | ||
* cursor's position. | ||
* This is similar to the body of `getPositionInfo` but differs in a key way--the position is on this line, so the | ||
* character position can fundamentally affect what the logical path is | ||
* @param params Parameters including position | ||
* @param tabSize The tab size (needed to determine indent depth) | ||
* @returns The part(s) of the logical path at the current line | ||
*/ | ||
private getFirstLinePositionInfo; | ||
} | ||
export declare const KeyValueRegex: RegExp; |
@@ -6,7 +6,21 @@ "use strict"; | ||
*--------------------------------------------------------------------------------------------*/ | ||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
if (kind === "m") throw new TypeError("Private method is not writable"); | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); | ||
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; | ||
}; | ||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); | ||
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); | ||
}; | ||
var _ComposeDocument_textDocument; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.ComposeDocument = void 0; | ||
exports.KeyValueRegex = exports.ComposeDocument = void 0; | ||
const vscode_languageserver_1 = require("vscode-languageserver"); | ||
const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument"); | ||
const yaml_1 = require("yaml"); | ||
const DocumentSettings_1 = require("../client/DocumentSettings"); | ||
const ActionContext_1 = require("./utils/ActionContext"); | ||
const Lazy_1 = require("./utils/Lazy"); | ||
@@ -21,8 +35,29 @@ const EmptyDocumentCST = { | ||
class ComposeDocument { | ||
constructor(textDocument) { | ||
this.textDocument = textDocument; | ||
constructor(doc) { | ||
this.fullCst = new Lazy_1.Lazy(() => this.buildFullCst()); | ||
this.documentCst = new Lazy_1.Lazy(() => this.buildDocumentCst()); | ||
this.yamlDocument = new Lazy_1.Lazy(() => this.buildYamlDocument()); | ||
/* private */ _ComposeDocument_textDocument.set(this, void 0); | ||
__classPrivateFieldSet(this, _ComposeDocument_textDocument, doc, "f"); | ||
} | ||
get textDocument() { | ||
return __classPrivateFieldGet(this, _ComposeDocument_textDocument, "f"); | ||
} | ||
get id() { | ||
return { | ||
uri: this.textDocument.uri, | ||
}; | ||
} | ||
update(doc) { | ||
__classPrivateFieldSet(this, _ComposeDocument_textDocument, doc, "f"); | ||
this.yamlDocument.clear(); | ||
this.documentCst.clear(); | ||
this.fullCst.clear(); | ||
return this; | ||
} | ||
/** | ||
* Gets the text of a line at a given line number or position, including the line break (`\n` or `\r\n`) at the end if present | ||
* @param line The line number or `Position` | ||
* @returns The line text | ||
*/ | ||
lineAt(line) { | ||
@@ -37,2 +72,76 @@ // Flatten to a position at the start of the line | ||
} | ||
/** | ||
* Gets settings from the document. If already populated, that will be returned. If supported, the info will be requested from the client. | ||
* Otherwise, the settings will be heuristically guessed based on document contents. | ||
* Note: The client is also directed to notify the server if the settings change, via the `DocumentSettingsNotification` notification. | ||
* @returns The document settings (tab size, line endings, etc.) | ||
*/ | ||
async getSettings() { | ||
var _a, _b, _c; | ||
// First, try asking the client, if the capability is present | ||
if (!this.documentSettings) { | ||
const ctx = (0, ActionContext_1.getCurrentContext)(); | ||
if ((_c = (_b = (_a = ctx.clientCapabilities) === null || _a === void 0 ? void 0 : _a.experimental) === null || _b === void 0 ? void 0 : _b.documentSettings) === null || _c === void 0 ? void 0 : _c.request) { | ||
const result = await ctx.connection.sendRequest(DocumentSettings_1.DocumentSettingsRequest.type, { textDocument: this.id }); | ||
if (result) { | ||
this.documentSettings = result; | ||
} | ||
} | ||
} | ||
// If the capability is not present, or the above didn't get a result for some reason, try heuristically guessing | ||
if (!this.documentSettings) { | ||
this.documentSettings = this.guessDocumentSettings(); | ||
} | ||
return this.documentSettings; | ||
} | ||
/** | ||
* Updates the settings (tab size, line endings, etc.) for the document. This is meant to be called by the server upon receiving a `DocumentSettingsNotification`. | ||
* @param params The new settings for the document | ||
*/ | ||
updateSettings(params) { | ||
this.documentSettings = params; | ||
} | ||
/** | ||
* Gets information about the position, including the tab depth and a logical path describing where in the YAML tree the position is | ||
* @param params The `ExtendedPositionParams` for the position being queried | ||
* @returns A `PositionInfo` object with tab depth and logical path | ||
* @example If the position is in a service foo's `image` key, the logical path would be `/services/foo/image`. | ||
*/ | ||
async getPositionInfo(params) { | ||
const { tabSize } = await this.getSettings(); | ||
const partialPositionInfo = this.getFirstLinePositionInfo(params, tabSize); | ||
const fullPathParts = [...partialPositionInfo.pathParts]; | ||
// We no longer need to consider position, since the cursor is inherently below this point | ||
// So now we just scroll upward, ignoring any lines with equal or more indentation than the first line (where the cursor is) | ||
let currentIndentDepth = partialPositionInfo.cursorIndentDepth; | ||
for (let i = params.position.line - 1; i >= 0 && currentIndentDepth > 0; i--) { | ||
const currentLine = this.lineAt(i); | ||
let indentDepth = MaximumLineLength; | ||
let result; | ||
/* eslint-disable @typescript-eslint/no-non-null-assertion */ | ||
if ((result = ItemValueRegex.exec(currentLine))) { | ||
indentDepth = result.groups['indent'].length / tabSize; | ||
if (indentDepth < currentIndentDepth) { | ||
// If this line is an ItemValue and less indented, then add `<item>` to the path | ||
currentIndentDepth = indentDepth; | ||
fullPathParts.unshift(Item); | ||
} | ||
} | ||
else if ((result = exports.KeyValueRegex.exec(currentLine))) { | ||
indentDepth = result.groups['indent'].length / tabSize; | ||
if (indentDepth < currentIndentDepth || | ||
(indentDepth === currentIndentDepth && fullPathParts[0] === Item)) // YAML is too permissive and allows for items to have the same indentation as their parent key, so need to account for that | ||
{ | ||
// If this line is a KeyValue (which also includes keys alone) and less indented, add that key to the path | ||
currentIndentDepth = indentDepth; | ||
fullPathParts.unshift(result.groups['keyName']); | ||
} | ||
} | ||
/* eslint-enable @typescript-eslint/no-non-null-assertion */ | ||
} | ||
return { | ||
path: '/' + fullPathParts.join('/'), | ||
indentDepth: partialPositionInfo.cursorIndentDepth, // Indent depth is determined entirely from `getFirstLinePositionInfo` | ||
}; | ||
} | ||
buildFullCst() { | ||
@@ -51,3 +160,2 @@ return Array.from(new yaml_1.Parser().parse(this.textDocument.getText())); | ||
if (!(0, yaml_1.isDocument)(yamlDocument)) { | ||
// TODO: empty documents are a normal thing but will not have a YamlDocument, that should be handled differently than erroring | ||
throw new vscode_languageserver_1.ResponseError(vscode_languageserver_1.ErrorCodes.ParseError, 'Malformed YAML document'); | ||
@@ -57,8 +165,164 @@ } | ||
} | ||
guessDocumentSettings() { | ||
var _a; | ||
const documentText = this.textDocument.getText(); | ||
// For line endings, see if there are any \r | ||
const eol = /\r/ig.test(documentText) ? DocumentSettings_1.CRLF : DocumentSettings_1.LF; | ||
// For tab size, look for the first key with nonzero indentation. If none found, assume 2. | ||
let tabSize = 2; | ||
const indentedKeyLineMatch = /^(?<indentation>[ ]+)(?<key>[.\w-]+:\s*)$/im.exec(documentText); | ||
if ((_a = indentedKeyLineMatch === null || indentedKeyLineMatch === void 0 ? void 0 : indentedKeyLineMatch.groups) === null || _a === void 0 ? void 0 : _a['indentation']) { | ||
tabSize = indentedKeyLineMatch.groups['indentation'].length; | ||
} | ||
return { | ||
eol, | ||
tabSize, | ||
}; | ||
} | ||
/** | ||
* This method is responsible for determining the part of the logical path for the line where the cursor is, given the | ||
* cursor's position. | ||
* This is similar to the body of `getPositionInfo` but differs in a key way--the position is on this line, so the | ||
* character position can fundamentally affect what the logical path is | ||
* @param params Parameters including position | ||
* @param tabSize The tab size (needed to determine indent depth) | ||
* @returns The part(s) of the logical path at the current line | ||
*/ | ||
getFirstLinePositionInfo(params, tabSize) { | ||
const currentLine = this.lineAt(params.position); | ||
const pathParts = []; | ||
let cursorIndentDepth = 0; | ||
let result; | ||
/* eslint-disable @typescript-eslint/no-non-null-assertion */ | ||
if ((result = ItemKeyValueRegex.exec(currentLine))) { | ||
// First, see if it's an ItemKeyValue, i.e. ` - foo: bar` | ||
const itemSepPosition = currentLine.indexOf(result.groups['itemInd']); | ||
const keySepPosition = currentLine.indexOf(result.groups['keyInd'], itemSepPosition); | ||
const indentLength = result.groups['indent'].length; | ||
const keyName = result.groups['keyName']; | ||
cursorIndentDepth = indentLength / tabSize; | ||
if (params.position.character > keySepPosition) { | ||
// If the position is after the key separator, we're in the value | ||
pathParts.unshift(Value); | ||
pathParts.unshift(keyName); | ||
pathParts.unshift(Item); | ||
} | ||
else if (params.position.character === keySepPosition) { | ||
// If the position is at the key separator, we're on the separator | ||
pathParts.unshift(Sep); | ||
pathParts.unshift(keyName); | ||
pathParts.unshift(Item); | ||
} | ||
else if (params.position.character > itemSepPosition) { | ||
// Otherwise if the position is after the item separator, we're in the key | ||
pathParts.unshift(keyName); | ||
pathParts.unshift(Item); | ||
} | ||
else if (params.position.character === itemSepPosition) { | ||
// If we're at the item separator, we're at the item separator (of course) | ||
pathParts.unshift(Sep); | ||
pathParts.unshift(Item); | ||
} | ||
else if (params.position.character < indentLength) { | ||
// Otherwise if we're somewhere within the indentation, we're not at a "position" within this line, but we do need to consider the indent depth | ||
cursorIndentDepth = params.position.character / tabSize; | ||
} | ||
} | ||
else if ((result = exports.KeyValueRegex.exec(currentLine))) { | ||
// Next, check if it's a standard KeyValue, i.e. ` foo: bar` | ||
const keySepPosition = currentLine.indexOf(result.groups['keyInd']); | ||
const indentLength = result.groups['indent'].length; | ||
const keyName = result.groups['keyName']; | ||
cursorIndentDepth = indentLength / tabSize; | ||
if (params.position.character > keySepPosition) { | ||
// If the position is after the key separator, we're in the value | ||
pathParts.unshift(Value); | ||
pathParts.unshift(keyName); | ||
} | ||
else if (params.position.character === keySepPosition) { | ||
// If the position is at the key separator, we're on the separator | ||
pathParts.unshift(Sep); | ||
pathParts.unshift(keyName); | ||
} | ||
else if (params.position.character > indentLength) { | ||
// If the position is after the indent, we're in the key | ||
pathParts.unshift(keyName); | ||
} | ||
else if (params.position.character < indentLength) { | ||
// Otherwise if we're somewhere within the indentation, we're not at a "position" within this line, but we do need to consider the indent depth | ||
cursorIndentDepth = params.position.character / tabSize; | ||
} | ||
} | ||
else if ((result = ItemValueRegex.exec(currentLine))) { | ||
// Next, check if it's an ItemValue, i.e. ` - foo` | ||
const itemSepPosition = currentLine.indexOf(result.groups['itemInd']); | ||
const indentLength = result.groups['indent'].length; | ||
cursorIndentDepth = indentLength / tabSize; | ||
if (params.position.character > itemSepPosition) { | ||
// If the position is after the item separator, we're in the value | ||
pathParts.unshift(Value); | ||
pathParts.unshift(Item); | ||
} | ||
else if (params.position.character === itemSepPosition) { | ||
// If we're at the item separator, we're at the item separator (of course) | ||
pathParts.unshift(Sep); | ||
pathParts.unshift(Item); | ||
} | ||
else if (params.position.character < indentLength) { | ||
// Otherwise if we're somewhere within the indentation, we're not at a "position" within this line, but we do need to consider the indent depth | ||
cursorIndentDepth = params.position.character / tabSize; | ||
} | ||
} | ||
else if ((result = ValueRegex.exec(currentLine))) { | ||
// Next, check if it's a value alone, i.e. ` foo` | ||
const indentLength = result.groups['indent'].length; | ||
cursorIndentDepth = indentLength / tabSize; | ||
if (params.position.character > indentLength) { | ||
// If the position is after the indent, we're in the value | ||
pathParts.unshift(Value); | ||
} | ||
else if (params.position.character < indentLength) { | ||
// Otherwise if we're somewhere within the indentation, we're not at a "position" within this line, but we do need to consider the indent depth | ||
cursorIndentDepth = params.position.character / tabSize; | ||
} | ||
} | ||
else if ((result = WhitespaceRegex.exec(currentLine))) { | ||
// Last, check if it's whitespace only | ||
const indentLength = result.groups['indent'].length; | ||
cursorIndentDepth = indentLength / tabSize; | ||
if (params.position.character < indentLength) { | ||
// Simply need to determine the indent depth | ||
cursorIndentDepth = params.position.character / tabSize; | ||
} | ||
} | ||
/* eslint-enable @typescript-eslint/no-non-null-assertion */ | ||
return { | ||
pathParts, | ||
cursorIndentDepth, | ||
}; | ||
} | ||
} | ||
exports.ComposeDocument = ComposeDocument; | ||
_ComposeDocument_textDocument = new WeakMap(); | ||
ComposeDocument.DocumentManagerConfig = { | ||
create: (uri, languageId, version, content) => new ComposeDocument(vscode_languageserver_textdocument_1.TextDocument.create(uri, languageId, version, content)), | ||
update: (document, changes, version) => new ComposeDocument(vscode_languageserver_textdocument_1.TextDocument.update(document.textDocument, changes, version)), | ||
update: (document, changes, version) => document.update(vscode_languageserver_textdocument_1.TextDocument.update(document.textDocument, changes, version)), | ||
}; | ||
// IMPORTANT: For all of these regular expressions, the groups present *and* their names are very important. | ||
// Removal or alteration would break `getPositionInfo` and `getFirstLinePositionInfo` | ||
// A regex for matching a standard key/value line, i.e. `key: value` | ||
// Exported for use by KeyHoverProvider | ||
exports.KeyValueRegex = /^(?<indent> *)(?<key>(?<keyName>[.\w-]+)(?<keyInd>(?<keySep>:)\s+))(?<value>.*)$/im; | ||
// A regex for matching an item/value line, i.e. `- value` | ||
const ItemValueRegex = /^(?<indent> *)(?<itemInd>(?<itemSep>-) *)(?<value>.*)$/im; | ||
// A regex for matching an item/key/value line, i.e. `- key: value`. This will be the top line of a flow map. | ||
const ItemKeyValueRegex = /^(?<indent> *)(?<itemInd>(?<itemSep>-) *)(?<key>(?<keyName>[.\w-]+)(?<keyInd>(?<keySep>:)\s+))(?<value>.*)$/im; | ||
// A regex for matching any value line | ||
const ValueRegex = /^(?<indent> *)(?<value>\S+)$/im; | ||
// A regex for matching a whitespace-only line | ||
const WhitespaceRegex = /^(?<indent> *)$/im; | ||
// Constants for marking non-key parts of a logical path | ||
const Value = '<value>'; | ||
const Item = '<item>'; | ||
const Sep = '<sep>'; | ||
//# sourceMappingURL=ComposeDocument.js.map |
@@ -11,8 +11,10 @@ /*!-------------------------------------------------------------------------------------------- | ||
private readonly subscriptions; | ||
private readonly telemetryAggregator; | ||
constructor(connection: Connection, clientParams: InitializeParams); | ||
dispose(): void; | ||
get capabilities(): ServerCapabilities; | ||
private onDidChangeDocumentSettings; | ||
private createLspHandler; | ||
private createDocumentManagerHandler; | ||
private static flattenError; | ||
private callWithTelemetryAndErrorHandling; | ||
} |
@@ -9,2 +9,4 @@ "use strict"; | ||
const vscode_languageserver_1 = require("vscode-languageserver"); | ||
const DocumentSettings_1 = require("../client/DocumentSettings"); | ||
const TelemetryEvent_1 = require("../client/TelemetryEvent"); | ||
const ComposeDocument_1 = require("./ComposeDocument"); | ||
@@ -16,6 +18,7 @@ const MultiCompletionProvider_1 = require("./providers/completion/MultiCompletionProvider"); | ||
const KeyHoverProvider_1 = require("./providers/KeyHoverProvider"); | ||
const MultiSignatureHelpProvider_1 = require("./providers/signatureHelp/MultiSignatureHelpProvider"); | ||
const ActionContext_1 = require("./utils/ActionContext"); | ||
const TelemetryAggregator_1 = require("./utils/telemetry/TelemetryAggregator"); | ||
class ComposeLanguageService { | ||
// TODO: telemetry! Aggregation! | ||
constructor(connection, clientParams) { | ||
var _a; | ||
this.connection = connection; | ||
@@ -30,7 +33,10 @@ this.clientParams = clientParams; | ||
this.createLspHandler(this.connection.onHover, new KeyHoverProvider_1.KeyHoverProvider()); | ||
this.createLspHandler(this.connection.onSignatureHelp, new MultiSignatureHelpProvider_1.MultiSignatureHelpProvider()); | ||
this.createLspHandler(this.connection.onDocumentLinks, new ImageLinkProvider_1.ImageLinkProvider()); | ||
this.createLspHandler(this.connection.onDocumentFormatting, new DocumentFormattingProvider_1.DocumentFormattingProvider()); | ||
// Hook up one additional notification handler | ||
this.connection.onNotification(DocumentSettings_1.DocumentSettingsNotification.method, (params) => this.onDidChangeDocumentSettings(params)); | ||
// Start the document listener | ||
this.documentManager.listen(this.connection); | ||
// Start the telemetry aggregator | ||
this.subscriptions.push(this.telemetryAggregator = new TelemetryAggregator_1.TelemetryAggregator(this.connection, (_a = clientParams.initializationOptions) === null || _a === void 0 ? void 0 : _a.telemetryAggregationInterval)); | ||
} | ||
@@ -56,6 +62,2 @@ dispose() { | ||
hoverProvider: true, | ||
signatureHelpProvider: { | ||
triggerCharacters: ['-', ':'], | ||
retriggerCharacters: ['\n'], | ||
}, | ||
documentLinkProvider: { | ||
@@ -65,13 +67,2 @@ resolveProvider: false, | ||
documentFormattingProvider: true, | ||
// semanticTokensProvider: { | ||
// full: { | ||
// delta: false, | ||
// }, | ||
// legend: { | ||
// tokenTypes: [ | ||
// SemanticTokenTypes.variable, | ||
// ], | ||
// tokenModifiers: [], | ||
// }, | ||
// }, | ||
workspace: { | ||
@@ -84,5 +75,12 @@ workspaceFolders: { | ||
} | ||
onDidChangeDocumentSettings(params) { | ||
// TODO: Telemetrize this? | ||
const composeDoc = this.documentManager.get(params.textDocument.uri); | ||
if (composeDoc) { | ||
composeDoc.updateSettings(params); | ||
} | ||
} | ||
createLspHandler(event, handler) { | ||
event(async (params, token, workDoneProgress, resultProgress) => { | ||
try { | ||
return await this.callWithTelemetryAndErrorHandling(handler.constructor.name, async () => { | ||
const doc = this.documentManager.get(params.textDocument.uri); | ||
@@ -95,10 +93,5 @@ if (!doc) { | ||
document: doc, | ||
clientCapabilities: this.clientParams.capabilities, | ||
connection: this.connection, | ||
}; | ||
return await Promise.resolve(handler.on(extendedParams, token, workDoneProgress, resultProgress)); | ||
} | ||
catch (error) { | ||
return ComposeLanguageService.flattenError(error); | ||
} | ||
}); | ||
}); | ||
@@ -108,25 +101,49 @@ } | ||
event(async (params) => { | ||
try { | ||
return await this.callWithTelemetryAndErrorHandling(handler.name, async () => { | ||
const extendedParams = { | ||
...params, | ||
document: params.document, | ||
clientCapabilities: this.clientParams.capabilities, | ||
connection: this.connection, | ||
textDocument: params.document.id, | ||
}; | ||
return await Promise.resolve(handler(extendedParams)); | ||
} | ||
catch (error) { | ||
return ComposeLanguageService.flattenError(error); | ||
} | ||
}); | ||
}, this, this.subscriptions); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
static flattenError(error) { | ||
if (error instanceof vscode_languageserver_1.ResponseError) { | ||
return error; | ||
async callWithTelemetryAndErrorHandling(callbackId, callback) { | ||
const actionContext = { | ||
clientCapabilities: this.clientParams.capabilities, | ||
connection: this.connection, | ||
telemetry: (0, TelemetryEvent_1.initEvent)(callbackId), | ||
}; | ||
const startTime = process.hrtime.bigint(); | ||
try { | ||
return await (0, ActionContext_1.runWithContext)(actionContext, callback); | ||
} | ||
else if (error instanceof Error) { | ||
return new vscode_languageserver_1.ResponseError(vscode_languageserver_1.ErrorCodes.UnknownErrorCode, error.message, error); | ||
catch (error) { | ||
let responseError; | ||
let stack; | ||
if (error instanceof vscode_languageserver_1.ResponseError) { | ||
responseError = error; | ||
stack = error.stack; | ||
} | ||
else if (error instanceof Error) { | ||
responseError = new vscode_languageserver_1.ResponseError(vscode_languageserver_1.ErrorCodes.UnknownErrorCode, error.message, error); | ||
stack = error.stack; | ||
} | ||
else { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
responseError = new vscode_languageserver_1.ResponseError(vscode_languageserver_1.ErrorCodes.InternalError, error.toString ? error.toString() : 'Unknown error'); | ||
} | ||
actionContext.telemetry.properties.result = 'Failed'; | ||
actionContext.telemetry.properties.error = responseError.code.toString(); | ||
actionContext.telemetry.properties.errorMessage = responseError.message; | ||
actionContext.telemetry.properties.stack = stack; | ||
return responseError; | ||
} | ||
return new vscode_languageserver_1.ResponseError(vscode_languageserver_1.ErrorCodes.InternalError, error.toString()); | ||
finally { | ||
const endTime = process.hrtime.bigint(); | ||
const elapsedMicroseconds = Number((endTime - startTime) / BigInt(1000)); | ||
actionContext.telemetry.measurements.duration = elapsedMicroseconds; | ||
// The aggregator will internally handle suppressing / etc. | ||
this.telemetryAggregator.logEvent(actionContext.telemetry); | ||
} | ||
} | ||
@@ -133,0 +150,0 @@ } |
@@ -5,14 +5,18 @@ /*!-------------------------------------------------------------------------------------------- | ||
*--------------------------------------------------------------------------------------------*/ | ||
import { ClientCapabilities, Connection, Position } from 'vscode-languageserver'; | ||
import { CompletionParams, TextDocumentIdentifier, TextDocumentPositionParams } from 'vscode-languageserver'; | ||
import { ComposeDocument } from './ComposeDocument'; | ||
import { ExtendedPosition } from './ExtendedPosition'; | ||
import { Lazy } from './utils/Lazy'; | ||
export interface ExtendedParams { | ||
export interface TextDocumentParams { | ||
textDocument: TextDocumentIdentifier; | ||
} | ||
export interface ExtendedParams extends TextDocumentParams { | ||
document: ComposeDocument; | ||
clientCapabilities: ClientCapabilities; | ||
connection: Connection; | ||
} | ||
export interface ExtendedPositionParams extends ExtendedParams { | ||
position: Position; | ||
extendedPosition: Lazy<ExtendedPosition>; | ||
export interface ExtendedPositionParams extends ExtendedParams, TextDocumentPositionParams { | ||
} | ||
export interface PositionInfo { | ||
path: string; | ||
indentDepth: number; | ||
} | ||
export interface ExtendedCompletionParams extends CompletionParams, ExtendedPositionParams { | ||
positionInfo: PositionInfo; | ||
} |
@@ -0,0 +0,0 @@ "use strict"; |
@@ -5,4 +5,4 @@ /*!-------------------------------------------------------------------------------------------- | ||
*--------------------------------------------------------------------------------------------*/ | ||
import { CompletionItem, CompletionParams } from 'vscode-languageserver'; | ||
import { ExtendedParams } from '../../ExtendedParams'; | ||
import { CompletionItem } from 'vscode-languageserver'; | ||
import { ExtendedCompletionParams } from '../../ExtendedParams'; | ||
interface ExtendedCompletionItem extends CompletionItem { | ||
@@ -13,10 +13,13 @@ /** | ||
matcher?: RegExp; | ||
/** | ||
* The insertion text does not need to be the same as the label | ||
*/ | ||
insertionText: string; | ||
} | ||
export declare class CompletionCollection extends Array<ExtendedCompletionItem> { | ||
getActiveCompletionItems(params: CompletionParams & ExtendedParams): CompletionItem[]; | ||
readonly name: string; | ||
private readonly locationRequirements; | ||
constructor(name: string, locationRequirements: CompletionLocationRequirements, ...items: ExtendedCompletionItem[]); | ||
getActiveCompletionItems(params: ExtendedCompletionParams): CompletionItem[] | undefined; | ||
} | ||
interface CompletionLocationRequirements { | ||
logicalPaths?: RegExp[]; | ||
indentationDepth?: number; | ||
} | ||
export {}; |
@@ -8,23 +8,17 @@ "use strict"; | ||
exports.CompletionCollection = void 0; | ||
const vscode_languageserver_1 = require("vscode-languageserver"); | ||
class CompletionCollection extends Array { | ||
constructor(name, locationRequirements, ...items) { | ||
super(...items); | ||
this.name = name; | ||
this.locationRequirements = locationRequirements; | ||
} | ||
getActiveCompletionItems(params) { | ||
var _a, _b; | ||
const results = []; | ||
for (const m of this) { | ||
const match = (_a = m.matcher) === null || _a === void 0 ? void 0 : _a.exec(params.document.lineAt(params.position)); | ||
if (match || !m.matcher) { | ||
const ci = vscode_languageserver_1.CompletionItem.create(m.label); | ||
ci.insertTextFormat = (_b = m.insertTextFormat) !== null && _b !== void 0 ? _b : vscode_languageserver_1.InsertTextFormat.Snippet; | ||
ci.textEdit = vscode_languageserver_1.TextEdit.insert(params.position, m.insertionText); | ||
// Copy additional properties | ||
// TODO: this doesn't copy everything; what else should be added? | ||
ci.detail = m.detail; | ||
ci.documentation = m.documentation; | ||
ci.commitCharacters = m.commitCharacters; | ||
ci.filterText = m.filterText; | ||
results.push(ci); | ||
} | ||
if (this.locationRequirements.logicalPaths !== undefined && !this.locationRequirements.logicalPaths.some(p => { var _a; return p.test((_a = params.positionInfo.path) !== null && _a !== void 0 ? _a : ''); })) { | ||
return undefined; | ||
} | ||
return results; | ||
if (this.locationRequirements.indentationDepth !== undefined && this.locationRequirements.indentationDepth !== params.positionInfo.indentDepth) { | ||
return undefined; | ||
} | ||
const line = params.document.lineAt(params.position); | ||
return this.filter(eci => !eci.matcher || eci.matcher.test(line)); | ||
} | ||
@@ -31,0 +25,0 @@ } |
@@ -6,4 +6,4 @@ /*!-------------------------------------------------------------------------------------------- | ||
import { CancellationToken, CompletionItem, CompletionParams, WorkDoneProgressReporter } from 'vscode-languageserver'; | ||
import { ExtendedParams } from '../../ExtendedParams'; | ||
import { MultiProviderBase } from '../MultiProviderBase'; | ||
import { ExtendedParams, ExtendedPositionParams } from '../../ExtendedParams'; | ||
import { ProviderBase } from '../ProviderBase'; | ||
/** | ||
@@ -14,6 +14,6 @@ * Completions are one of the more involved features so we will split up the code, with this multi-provider calling each of them | ||
*/ | ||
export declare class MultiCompletionProvider extends MultiProviderBase<CompletionParams & ExtendedParams, CompletionItem[], never> { | ||
export declare class MultiCompletionProvider extends ProviderBase<CompletionParams & ExtendedParams, CompletionItem[] | undefined, never, never> { | ||
private readonly completionCollections; | ||
constructor(); | ||
on(params: CompletionParams & ExtendedParams, token: CancellationToken, workDoneProgress: WorkDoneProgressReporter): CompletionItem[] | undefined; | ||
protected reduce(subresults: (CompletionItem[] | undefined)[]): CompletionItem[] | undefined; | ||
on(params: CompletionParams & ExtendedPositionParams, token: CancellationToken, workDoneProgress: WorkDoneProgressReporter): Promise<CompletionItem[] | undefined>; | ||
} |
@@ -8,4 +8,7 @@ "use strict"; | ||
exports.MultiCompletionProvider = void 0; | ||
const MultiProviderBase_1 = require("../MultiProviderBase"); | ||
const VolumesCompletionProvider_1 = require("./VolumesCompletionProvider"); | ||
const ActionContext_1 = require("../../utils/ActionContext"); | ||
const ProviderBase_1 = require("../ProviderBase"); | ||
const RootCompletionCollection_1 = require("./RootCompletionCollection"); | ||
const ServiceCompletionCollection_1 = require("./ServiceCompletionCollection"); | ||
const VolumesCompletionCollection_1 = require("./VolumesCompletionCollection"); | ||
/** | ||
@@ -16,25 +19,33 @@ * Completions are one of the more involved features so we will split up the code, with this multi-provider calling each of them | ||
*/ | ||
class MultiCompletionProvider extends MultiProviderBase_1.MultiProviderBase { | ||
class MultiCompletionProvider extends ProviderBase_1.ProviderBase { | ||
constructor() { | ||
super(); | ||
this.register(new VolumesCompletionProvider_1.VolumesCompletionProvider()); | ||
this.completionCollections = [ | ||
RootCompletionCollection_1.RootCompletionCollection, | ||
ServiceCompletionCollection_1.ServiceCompletionCollection, | ||
VolumesCompletionCollection_1.VolumesCompletionCollection, | ||
]; | ||
} | ||
on(params, token, workDoneProgress) { | ||
var _a; | ||
if (!((_a = params.clientCapabilities.textDocument) === null || _a === void 0 ? void 0 : _a.completion)) { | ||
return undefined; | ||
} | ||
return super.on(params, token, workDoneProgress); | ||
} | ||
reduce(subresults) { | ||
async on(params, token, workDoneProgress) { | ||
const ctx = (0, ActionContext_1.getCurrentContext)(); | ||
const extendedParams = { | ||
...params, | ||
positionInfo: await params.document.getPositionInfo(params), | ||
}; | ||
const results = []; | ||
for (const s of subresults) { | ||
if (s) { | ||
results.push(...s); | ||
const respondingCollections = []; | ||
for (const collection of this.completionCollections) { | ||
// Within each loop we'll check for cancellation | ||
if (token.isCancellationRequested) { | ||
return undefined; | ||
} | ||
const subresults = collection.getActiveCompletionItems(extendedParams); | ||
if (subresults === null || subresults === void 0 ? void 0 : subresults.length) { | ||
respondingCollections.push(collection.name); | ||
results.push(...subresults); | ||
} | ||
// The set of collections that answer will be attached as a property | ||
ctx.telemetry.properties.respondingCollections = respondingCollections.sort().join(','); | ||
} | ||
if (results.length) { | ||
return results; | ||
} | ||
return undefined; | ||
return results.length > 0 ? results : undefined; | ||
} | ||
@@ -41,0 +52,0 @@ } |
@@ -0,0 +0,0 @@ /*!-------------------------------------------------------------------------------------------- |
@@ -9,2 +9,3 @@ "use strict"; | ||
const vscode_languageserver_1 = require("vscode-languageserver"); | ||
const ActionContext_1 = require("../utils/ActionContext"); | ||
const debounce_1 = require("../utils/debounce"); | ||
@@ -18,5 +19,7 @@ const yamlRangeToLspRange_1 = require("../utils/yamlRangeToLspRange"); | ||
var _a; | ||
if (!((_a = params.clientCapabilities.textDocument) === null || _a === void 0 ? void 0 : _a.publishDiagnostics)) { | ||
const ctx = (0, ActionContext_1.getCurrentContext)(); | ||
if (!((_a = ctx.clientCapabilities.textDocument) === null || _a === void 0 ? void 0 : _a.publishDiagnostics)) { | ||
return; | ||
} | ||
ctx.telemetry.suppressAll = true; // Diagnostics is async and telemetry won't really work | ||
(0, debounce_1.debounce)(DiagnosticDelay, { uri: params.document.textDocument.uri, callId: 'diagnostics' }, () => { | ||
@@ -27,3 +30,3 @@ const diagnostics = []; | ||
} | ||
params.connection.sendDiagnostics({ | ||
ctx.connection.sendDiagnostics({ | ||
uri: params.document.textDocument.uri, | ||
@@ -30,0 +33,0 @@ diagnostics: diagnostics, |
@@ -0,0 +0,0 @@ /*!-------------------------------------------------------------------------------------------- |
@@ -12,6 +12,2 @@ "use strict"; | ||
on(params, token) { | ||
var _a; | ||
if (!((_a = params.clientCapabilities.textDocument) === null || _a === void 0 ? void 0 : _a.formatting)) { | ||
return undefined; | ||
} | ||
if (params.document.yamlDocument.value.errors.length) { | ||
@@ -18,0 +14,0 @@ // Won't return formatting info unless the document is syntactically correct |
@@ -0,0 +0,0 @@ /*!-------------------------------------------------------------------------------------------- |
@@ -10,14 +10,13 @@ "use strict"; | ||
const yaml_1 = require("yaml"); | ||
const ActionContext_1 = require("../utils/ActionContext"); | ||
const yamlRangeToLspRange_1 = require("../utils/yamlRangeToLspRange"); | ||
const ProviderBase_1 = require("./ProviderBase"); | ||
const dockerHubImageRegex = /^(?<imageName>[\w.-]+)(?<tag>:[\w.-]+)?$/i; | ||
const dockerHubNamespacedImageRegex = /^(?<namespace>[a-z0-9]+)\/(?<imageName>[\w.-]+)(?<tag>:[\w.-]+)?$/i; | ||
const mcrImageRegex = /^mcr.microsoft.com\/(?<namespace>([a-z0-9]+\/)+)(?<imageName>[\w.-]+)(?<tag>:[\w.-]+)?$/i; | ||
const dockerHubImageRegex = /^(?<imageName>[.\w-]+)(?<tag>:[.\w-]+)?$/i; | ||
const dockerHubNamespacedImageRegex = /^(?<namespace>[a-z0-9]+)\/(?<imageName>[.\w-]+)(?<tag>:[.\w-]+)?$/i; | ||
const mcrImageRegex = /^mcr.microsoft.com\/(?<namespace>([a-z0-9]+\/)+)(?<imageName>[.\w-]+)(?<tag>:[.\w-]+)?$/i; | ||
class ImageLinkProvider extends ProviderBase_1.ProviderBase { | ||
on(params, token) { | ||
var _a; | ||
if (!((_a = params.clientCapabilities.textDocument) === null || _a === void 0 ? void 0 : _a.documentLink)) { | ||
return undefined; | ||
} | ||
const ctx = (0, ActionContext_1.getCurrentContext)(); | ||
const results = []; | ||
const imageTypes = new Set(); | ||
const serviceMap = params.document.yamlDocument.value.getIn(['services']); | ||
@@ -34,5 +33,6 @@ if ((0, yaml_1.isMap)(serviceMap)) { | ||
if (!hasBuild && (0, yaml_1.isScalar)(image) && typeof image.value === 'string') { | ||
const link = ImageLinkProvider.getLinkForImage(image.value); | ||
const quoteOffset = (image.type === yaml_1.Scalar.QUOTE_SINGLE || image.type === yaml_1.Scalar.QUOTE_DOUBLE) ? 1 : 0; // Offset if the scalar is quoted | ||
const link = ImageLinkProvider.getLinkForImage(image.value, imageTypes); | ||
if (link && image.range) { | ||
results.push(vscode_languageserver_1.DocumentLink.create((0, yamlRangeToLspRange_1.yamlRangeToLspRange)(params.document.textDocument, [image.range[0] + link.start, image.range[0] + link.start + link.length]), link.uri)); | ||
results.push(vscode_languageserver_1.DocumentLink.create((0, yamlRangeToLspRange_1.yamlRangeToLspRange)(params.document.textDocument, [quoteOffset + image.range[0] + link.start, quoteOffset + image.range[0] + link.start + link.length]), link.uri)); | ||
} | ||
@@ -43,5 +43,6 @@ } | ||
} | ||
ctx.telemetry.properties.imageTypes = Array.from(imageTypes.values()).sort().join(','); | ||
return results; | ||
} | ||
static getLinkForImage(image) { | ||
static getLinkForImage(image, imageTypes) { | ||
var _a, _b, _c, _d, _e, _f; | ||
@@ -53,5 +54,6 @@ let match; | ||
(imageName = (_a = match.groups) === null || _a === void 0 ? void 0 : _a['imageName'])) { | ||
imageTypes.add('dockerHub'); | ||
return { | ||
uri: `https://hub.docker.com/_/${imageName}`, | ||
start: 0, | ||
start: match.index, | ||
length: imageName.length | ||
@@ -63,5 +65,6 @@ }; | ||
(imageName = (_c = match.groups) === null || _c === void 0 ? void 0 : _c['imageName'])) { | ||
imageTypes.add('dockerHubNamespaced'); | ||
return { | ||
uri: `https://hub.docker.com/r/${namespace}/${imageName}`, | ||
start: 0, | ||
start: match.index, | ||
length: namespace.length + 1 + imageName.length // 1 is the length of the '/' after namespace | ||
@@ -73,5 +76,6 @@ }; | ||
(imageName = (_f = match.groups) === null || _f === void 0 ? void 0 : _f['imageName'])) { | ||
imageTypes.add('mcr'); | ||
return { | ||
uri: `https://hub.docker.com/_/microsoft-${namespace.replace('/', '-')}-${imageName}`, | ||
start: 0, | ||
start: match.index, | ||
length: 18 + namespace.length + 1 + imageName.length // 18 is the length of 'mcr.microsoft.com/', 1 is the length of the '/' after namespace | ||
@@ -78,0 +82,0 @@ }; |
@@ -9,3 +9,3 @@ /*!-------------------------------------------------------------------------------------------- | ||
export declare class KeyHoverProvider extends ProviderBase<HoverParams & ExtendedParams, Hover | undefined, never, never> { | ||
on(params: HoverParams & ExtendedParams, token: CancellationToken): Hover | undefined; | ||
on(params: HoverParams & ExtendedParams, token: CancellationToken): Promise<Hover | undefined>; | ||
} |
@@ -9,18 +9,21 @@ "use strict"; | ||
const vscode_languageserver_1 = require("vscode-languageserver"); | ||
const yaml_1 = require("yaml"); | ||
const ExtendedPosition_1 = require("../ExtendedPosition"); | ||
const yamlRangeToLspRange_1 = require("../utils/yamlRangeToLspRange"); | ||
const ComposeDocument_1 = require("../ComposeDocument"); | ||
const ActionContext_1 = require("../utils/ActionContext"); | ||
const ProviderBase_1 = require("./ProviderBase"); | ||
class KeyHoverProvider extends ProviderBase_1.ProviderBase { | ||
on(params, token) { | ||
var _a; | ||
if (!((_a = params.clientCapabilities.textDocument) === null || _a === void 0 ? void 0 : _a.hover)) { | ||
return undefined; | ||
} | ||
const contentFormat = params.clientCapabilities.textDocument.hover.contentFormat; | ||
async on(params, token) { | ||
var _a, _b, _c; | ||
const ctx = (0, ActionContext_1.getCurrentContext)(); | ||
ctx.telemetry.groupingStrategy = 'eventName'; // The below `hoverMatch` property that is attached will be lossy, but that's not serious; at global scales it will still be representative of usage | ||
const contentFormat = (_b = (_a = ctx.clientCapabilities.textDocument) === null || _a === void 0 ? void 0 : _a.hover) === null || _b === void 0 ? void 0 : _b.contentFormat; | ||
const preferMarkdown = (contentFormat === null || contentFormat === void 0 ? void 0 : contentFormat.length) ? (contentFormat === null || contentFormat === void 0 ? void 0 : contentFormat[0]) === vscode_languageserver_1.MarkupKind.Markdown : false; | ||
const extendedPosition = ExtendedPosition_1.ExtendedPosition.build(params.document, params.position); | ||
if (extendedPosition.itemType === 'key' && yaml_1.CST.isScalar(extendedPosition.parent.key)) { | ||
const keyInfo = ComposeKeyInfo.find((k) => k.pathRegex.test(extendedPosition.logicalPath)); | ||
if (keyInfo) { | ||
const positionInfo = await params.document.getPositionInfo(params); | ||
const keyInfo = ComposeKeyInfo.find((k) => k.pathRegex.test(positionInfo.path)); | ||
if (keyInfo) { | ||
const line = params.document.lineAt(params.position); | ||
const match = ComposeDocument_1.KeyValueRegex.exec(line); | ||
const keyName = (_c = match === null || match === void 0 ? void 0 : match.groups) === null || _c === void 0 ? void 0 : _c['keyName']; | ||
if (keyName) { | ||
const keyIndex = line.indexOf(keyName); | ||
ctx.telemetry.properties.hoverMatch = keyInfo.pathRegex.source; | ||
return { | ||
@@ -31,3 +34,3 @@ contents: { | ||
}, | ||
range: (0, yamlRangeToLspRange_1.yamlRangeToLspRange)(params.document.textDocument, [extendedPosition.parent.key.offset, extendedPosition.parent.key.offset + extendedPosition.parent.key.source.length]), | ||
range: vscode_languageserver_1.Range.create(vscode_languageserver_1.Position.create(params.position.line, keyIndex), vscode_languageserver_1.Position.create(params.position.line, keyIndex + keyName.length)), | ||
}; | ||
@@ -50,3 +53,3 @@ } | ||
{ | ||
pathRegex: /^\/networks\/[\w-]+\/driver$/i, | ||
pathRegex: /^\/networks\/[.\w-]+\/driver$/i, | ||
plaintextContents: 'The driver used for this network', | ||
@@ -63,87 +66,87 @@ }, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/build$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/build$/i, | ||
plaintextContents: 'The context used for building the image', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/build\/args$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/build\/args$/i, | ||
plaintextContents: 'Arguments used during the image build process', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/build\/context$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/build\/context$/i, | ||
plaintextContents: 'The context used for building the image', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/build\/dockerfile$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/build\/dockerfile$/i, | ||
plaintextContents: 'The Dockerfile used for building the image', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/command$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/command$/i, | ||
plaintextContents: 'The command that will be run in the container', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/container_name$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/container_name$/i, | ||
plaintextContents: 'The name that will be given to the container', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/depends_on$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/depends_on$/i, | ||
plaintextContents: 'Other services that this service depends on, which will be started before this one', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/entrypoint$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/entrypoint$/i, | ||
plaintextContents: 'The entrypoint to the application in the container', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/env_file$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/env_file$/i, | ||
plaintextContents: 'Files containing environment variables that will be included', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/environment$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/environment$/i, | ||
plaintextContents: 'Environment variables that will be included', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/expose$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/expose$/i, | ||
plaintextContents: 'Ports exposed to the other services but not to the host machine', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/healthcheck$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/healthcheck$/i, | ||
plaintextContents: 'A command for checking if the container is healthy', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/image$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/image$/i, | ||
plaintextContents: 'The image that will be pulled for the service. If `build` is specified, the built image will be given this tag.', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/labels$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/labels$/i, | ||
plaintextContents: 'Labels that will be given to the container', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/logging$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/logging$/i, | ||
plaintextContents: 'Settings for logging for this service', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/networks$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/networks$/i, | ||
plaintextContents: 'The service will be included in these networks, allowing it to reach other containers on the same network', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/ports$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/ports$/i, | ||
plaintextContents: 'Ports that will be exposed to the host', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/profiles$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/profiles$/i, | ||
plaintextContents: 'Profiles that this service is a part of. When the profile is started, this service will be started.', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/secrets$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/secrets$/i, | ||
plaintextContents: 'Secrets the service will have access to', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/user$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/user$/i, | ||
plaintextContents: 'The username under which the app in the container will be started', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/volumes$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/volumes$/i, | ||
plaintextContents: 'Named volumes and paths on the host mapped to paths in the container', | ||
}, | ||
{ | ||
pathRegex: /^\/services\/[\w-]+\/working_dir$/i, | ||
pathRegex: /^\/services\/[.\w-]+\/working_dir$/i, | ||
plaintextContents: 'The working directory in which the entrypoint or command will be run', | ||
@@ -160,3 +163,3 @@ }, | ||
{ | ||
pathRegex: /^\/volumes\/[\w-]+\/driver$/i, | ||
pathRegex: /^\/volumes\/[.\w-]+\/driver$/i, | ||
plaintextContents: 'The driver used for this volume', | ||
@@ -163,0 +166,0 @@ }, |
@@ -0,0 +0,0 @@ /*!-------------------------------------------------------------------------------------------- |
@@ -0,0 +0,0 @@ "use strict"; |
@@ -0,0 +0,0 @@ /*!-------------------------------------------------------------------------------------------- |
@@ -0,0 +0,0 @@ "use strict"; |
@@ -7,2 +7,3 @@ export declare class Lazy<T> { | ||
hasValue(): boolean; | ||
clear(): void; | ||
} |
@@ -23,3 +23,3 @@ "use strict"; | ||
this.valueFactory = valueFactory; | ||
_Lazy_value.set(this, void 0); | ||
/* private */ _Lazy_value.set(this, void 0); | ||
} | ||
@@ -35,2 +35,5 @@ get value() { | ||
} | ||
clear() { | ||
__classPrivateFieldSet(this, _Lazy_value, undefined, "f"); | ||
} | ||
} | ||
@@ -37,0 +40,0 @@ exports.Lazy = Lazy; |
@@ -0,0 +0,0 @@ /*!-------------------------------------------------------------------------------------------- |
@@ -0,0 +0,0 @@ "use strict"; |
{ | ||
"name": "@microsoft/compose-language-service", | ||
"author": "Microsoft Corporation", | ||
"version": "0.0.1-alpha", | ||
"version": "0.0.2-alpha", | ||
"publisher": "ms-azuretools", | ||
@@ -34,7 +34,7 @@ "description": "Language service for Docker Compose documents", | ||
"@types/node": "14.x", | ||
"@typescript-eslint/eslint-plugin": "^4.31.0", | ||
"@typescript-eslint/parser": "^4.31.0", | ||
"@typescript-eslint/eslint-plugin": "^5.2.0", | ||
"@typescript-eslint/parser": "^5.2.0", | ||
"chai": "^4.3.0", | ||
"chai-as-promised": "^7.1.1", | ||
"eslint": "^7.19.0", | ||
"eslint": "^8.1.0", | ||
"mocha": "^9.1.1", | ||
@@ -41,0 +41,0 @@ "typescript": "^4.4.2" |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
163510
1819
75
1
1