From 7556bd1c9c214ac77786ac130cfaf6585641a29e Mon Sep 17 00:00:00 2001 From: mkslanc Date: Mon, 13 Jan 2025 12:15:35 +0400 Subject: [PATCH] separate session language provider from provider file --- packages/ace-linters/src/language-provider.ts | 425 +---------------- .../src/session-language-provider.ts | 429 ++++++++++++++++++ 2 files changed, 435 insertions(+), 419 deletions(-) create mode 100644 packages/ace-linters/src/session-language-provider.ts diff --git a/packages/ace-linters/src/language-provider.ts b/packages/ace-linters/src/language-provider.ts index 12d74f6..63173af 100644 --- a/packages/ace-linters/src/language-provider.ts +++ b/packages/ace-linters/src/language-provider.ts @@ -1,18 +1,16 @@ import {Ace} from "ace-code"; import "./types/ace-extension"; -import {FormattingOptions} from "vscode-languageserver-protocol"; import {CommonConverter} from "./type-converters/common-converters"; -import {ComboDocumentIdentifier, IMessageController} from "./types/message-controller-interface"; +import {IMessageController} from "./types/message-controller-interface"; import {MessageController} from "./message-controller"; import { - fromAceDelta, fromAnnotations, fromDocumentHighlights, + fromAnnotations, fromPoint, fromRange, fromSignatureHelp, - toAnnotations, toCompletionItem, - toCompletions, toMarkerGroupItem, - toRange, toResolvedCompletion, + toCompletions, + toResolvedCompletion, toTooltip } from "./type-converters/lsp/lsp-converters"; import * as lsp from "vscode-languageserver-protocol"; @@ -21,7 +19,7 @@ import showdown from "showdown"; import {createWorker} from "./cdn-worker"; import {SignatureTooltip} from "./components/signature-tooltip"; import { - AceRangeData, CodeActionsByService, + CodeActionsByService, ProviderOptions, ServiceFeatures, ServiceOptions, @@ -29,19 +27,14 @@ import { SupportedServices, Tooltip } from "./types/language-service"; -import {MarkerGroup} from "./ace/marker_group"; import {AceRange} from "./ace/range-singleton"; import {HoverTooltip} from "./ace/hover-tooltip"; -import { - DecodedSemanticTokens, DecodedToken, - mergeTokens, OriginalSemanticTokens, - parseSemanticTokens -} from "./type-converters/lsp/semantic-tokens"; import {LightbulbWidget} from "./components/lightbulb"; import {AceVirtualRenderer} from "./ace/renderer-singleton"; import {AceEditor} from "./ace/editor-singleton"; import {setStyles} from "./misc/styles"; import {convertToUri} from "./utils"; +import {SessionLanguageProvider} from "./session-language-provider"; export class LanguageProvider { activeEditor: Ace.Editor; @@ -509,409 +502,3 @@ export class LanguageProvider { } } } - -class SessionLanguageProvider { - session: Ace.EditSession; - documentUri: string; - private $messageController: IMessageController; - private $deltaQueue: Ace.Delta[] | null; - private $isConnected = false; - private $options?: ServiceOptions; - private $filePath: string; - private $isFilePathRequired = false; - private $servicesCapabilities?: { [serviceName: string]: lsp.ServerCapabilities }; - private $requestsQueue: Function[] = []; - - state: { - occurrenceMarkers: MarkerGroup | null, - diagnosticMarkers: MarkerGroup | null - } = { - occurrenceMarkers: null, - diagnosticMarkers: null - } - - private extensions = { - "typescript": "ts", - "javascript": "js" - } - editor: Ace.Editor; - - private semanticTokensLegend?: lsp.SemanticTokensLegend; - private $provider: LanguageProvider; - - private $predefinedTokens: DecodedToken[] = []; - private $firstStaleLine: undefined | number; - - /** - * Constructs a new instance of the `SessionLanguageProvider` class. - * - * @param provider - The `LanguageProvider` instance. - * @param session - The Ace editor session. - * @param editor - The Ace editor instance. - * @param messageController - The `IMessageController` instance for handling messages. - */ - constructor(provider: LanguageProvider, session: Ace.EditSession, editor: Ace.Editor, messageController: IMessageController) { - this.$provider = provider; - this.$messageController = messageController; - this.session = session; - this.editor = editor; - this.$isFilePathRequired = provider.requireFilePath; - - session.doc.version = 1; - session.doc.on("change", this.$changeListener, true); - this.addSemanticTokenSupport(session); //TODO: ? - session.on("changeMode", this.$changeMode); - if (this.$provider.options.functionality!.semanticTokens) { - session.on("changeScrollTop", () => this.getSemanticTokens()); - } - - this.$init(); - } - - enqueueIfNotConnected(callback: () => void) { - if (!this.$isConnected) { - this.$requestsQueue.push(callback); - } else { - callback(); - } - } - - get comboDocumentIdentifier(): ComboDocumentIdentifier { - return { - documentUri: this.documentUri, - sessionId: this.session["id"] - }; - } - - /** - * @param filePath - */ - setFilePath(filePath: string) { - this.enqueueIfNotConnected(() => { - this.session.doc.version++; - if (this.$filePath !== undefined)//TODO change file path - return; - this.$filePath = filePath; - const previousComboId = this.comboDocumentIdentifier; - this.initDocumentUri(true); - this.$messageController.renameDocument(previousComboId, this.comboDocumentIdentifier.documentUri, this.session.doc.version); - }) - }; - - private $init() { - if (this.$isFilePathRequired && this.$filePath === undefined) - return; - this.initDocumentUri(); - this.$messageController.init(this.comboDocumentIdentifier, this.session.doc, this.$mode, this.$options, this.$connected); - } - - addSemanticTokenSupport(session: Ace.EditSession) { - let bgTokenizer = session.bgTokenizer; - session.setSemanticTokens = (tokens: DecodedSemanticTokens | undefined) => { - /*const hasPreviousTokens = bgTokenizer?.semanticTokens?.tokens && bgTokenizer?.semanticTokens?.tokens.length > 0; - const hasNewTokens = tokens && tokens.tokens.length > 0; - - if (hasPreviousTokens || hasNewTokens) { - let startRow = Number.MAX_SAFE_INTEGER; - if (hasPreviousTokens) { - startRow = Math.min(startRow, bgTokenizer!.semanticTokens!.tokens[0].row); - } - if (hasNewTokens) { - startRow = Math.min(startRow, tokens.tokens[0].row); - } else { - startRow = 0; - } - bgTokenizer.currentLine = startRow; - bgTokenizer.lines = bgTokenizer.lines.slice(0, startRow - 1); - if (startRow === 0) { - bgTokenizer.lines[0] = null; - } - } else { - bgTokenizer.currentLine = 0; - bgTokenizer.lines = []; - }*/ - bgTokenizer.currentLine = 0; - bgTokenizer.lines = []; - bgTokenizer.semanticTokens = tokens; - } - - bgTokenizer.$tokenizeRow = (row: number) => { - var line = bgTokenizer.doc.getLine(row); - var state = bgTokenizer.states[row - 1]; - var data = bgTokenizer.tokenizer.getLineTokens(line, state, row); - - if (bgTokenizer.states[row] + "" !== data.state + "") { - bgTokenizer.states[row] = data.state; - bgTokenizer.lines[row + 1] = null; - if (bgTokenizer.currentLine > row + 1) - bgTokenizer.currentLine = row + 1; - } else if (bgTokenizer.currentLine == row) { - bgTokenizer.currentLine = row + 1; - } - - if (bgTokenizer.semanticTokens) { - let decodedTokens = bgTokenizer.semanticTokens.getByRow(row); - if (decodedTokens && decodedTokens.length > 0) { - data.tokens = mergeTokens(data.tokens, decodedTokens); - } - } - - return bgTokenizer.lines[row] = data.tokens; - } - } - - private $connected = (capabilities: { [serviceName: string]: lsp.ServerCapabilities }) => { - this.$isConnected = true; - - this.setServerCapabilities(capabilities); - - this.$requestsQueue.forEach((requestCallback) => requestCallback()); - this.$requestsQueue = []; - - if (this.$deltaQueue) - this.$sendDeltaQueue(); - if (this.$options) - this.setOptions(this.$options); - } - - private $changeMode = () => { - this.enqueueIfNotConnected(() => { - this.$deltaQueue = []; - - this.session.clearAnnotations(); - if (this.state.diagnosticMarkers) { - this.state.diagnosticMarkers.setMarkers([]); - } - - this.session.setSemanticTokens(undefined); //clear all semantic tokens - let newVersion = this.session.doc.version++; - this.$messageController.changeMode(this.comboDocumentIdentifier, this.session.getValue(), newVersion, this.$mode, this.setServerCapabilities); - }); - }; - - setServerCapabilities = (capabilities: { [serviceName: string]: lsp.ServerCapabilities }) => { - if (!capabilities) - return; - this.$servicesCapabilities = {...capabilities}; - - let hasTriggerChars = Object.values(capabilities).some((capability) => capability?.completionProvider?.triggerCharacters); - - if (hasTriggerChars) { - let completer = this.editor.completers.find((completer) => completer.id === "lspCompleters"); - if (completer) { - let allTriggerCharacters: string[] = []; - Object.values(capabilities).forEach((capability) => { - if (capability?.completionProvider?.triggerCharacters) { - allTriggerCharacters.push(...capability.completionProvider.triggerCharacters); - } - }); - - allTriggerCharacters = [...new Set(allTriggerCharacters)]; - - completer.triggerCharacters = allTriggerCharacters; - } - } - - let hasSemanticTokensProvider = Object.values(capabilities).some((capability) => { - if (capability?.semanticTokensProvider) { - this.semanticTokensLegend = capability.semanticTokensProvider.legend; - return true; - } - }); - if (hasSemanticTokensProvider) { - this.getSemanticTokens(); - } - //TODO: we should restrict range formatting if any of services is only has full format capabilities - //or we shoudl use service with full format capability instead of range one's - } - - private initDocumentUri(isRename = false) { - let filePath = this.$filePath ?? this.session["id"] + "." + this.$extension; - if (isRename) { - delete this.$provider.$urisToSessionsIds[this.documentUri]; - } - this.documentUri = convertToUri(filePath); - this.$provider.$urisToSessionsIds[this.documentUri] = this.session["id"]; - } - - private get $extension() { - let mode = this.$mode.replace("ace/mode/", ""); - return this.extensions[mode] ?? mode; - } - - private get $mode(): string { - return this.session["$modeId"]; - } - - private get $format(): FormattingOptions { - return { - tabSize: this.session.getTabSize(), - insertSpaces: this.session.getUseSoftTabs() - } - } - - private $changeListener = (delta: Ace.Delta) => { - this.session.doc.version++; - if (!this.$deltaQueue) { - this.$deltaQueue = []; - setTimeout(() => this.$sendDeltaQueue(() => { - this.getSemanticTokens(); - }), 0); - } - this.$deltaQueue.push(delta); - } - - $sendDeltaQueue = (callback?) => { - let deltas = this.$deltaQueue; - if (!deltas) return callback && callback(); - this.$deltaQueue = null; - if (deltas.length) - this.$messageController.change(this.comboDocumentIdentifier, deltas.map((delta) => - fromAceDelta(delta, this.session.doc.getNewLineCharacter())), this.session.doc, callback); - }; - - $showAnnotations = (diagnostics: lsp.Diagnostic[]) => { - if (!diagnostics) { - return; - } - - let annotations = toAnnotations(diagnostics); - this.session.clearAnnotations(); - if (annotations && annotations.length > 0) { - this.session.setAnnotations(annotations); - } - if (!this.state.diagnosticMarkers) { - this.state.diagnosticMarkers = new MarkerGroup(this.session); - } - //TODO: show unused option - this.setPredefinedTokens(diagnostics); - this.state.diagnosticMarkers.setMarkers(diagnostics?.map((el) => toMarkerGroupItem(CommonConverter.toRange(toRange(el.range)), "language_highlight_error", el.message))); - } - - setPredefinedTokens(diagnostics: lsp.Diagnostic[]) { - this.$predefinedTokens = []; - - diagnostics.forEach((el) => { - if (el.tags && el.tags.length > 0) { - if (!this.$firstStaleLine || el.range.start.line < this.$firstStaleLine) { - this.$firstStaleLine = el.range.start.line; - } - this.$predefinedTokens.push({ - row: el.range.start.line, - startColumn: el.range.start.character, - length: el.range.end.character - el.range.start.character, - type: el.tags[0] === lsp.DiagnosticTag.Deprecated ? "highlight_deprecated": "highlight_unnecessary" - }); - } - }); - - if (!this.$provider.options.functionality!.semanticTokens) { - this.$applySemanticTokens(undefined); - } - } - - setOptions(options: OptionsType) { - if (!this.$isConnected) { - this.$options = options; - return; - } - this.$messageController.changeOptions(this.comboDocumentIdentifier, options); - } - - validate = () => { - this.$messageController.doValidation(this.comboDocumentIdentifier, this.$showAnnotations); - } - - format = () => { - let selectionRanges = this.session.getSelection().getAllRanges(); - let $format = this.$format; - let aceRangeDatas = selectionRanges as AceRangeData[]; - if (!selectionRanges || selectionRanges[0].isEmpty()) { - let row = this.session.getLength(); - let column = this.session.getLine(row).length - 1; - aceRangeDatas = - [{ - start: { - row: 0, column: 0 - }, - end: { - row: row, column: column - } - }]; - } - for (let range of aceRangeDatas) { - this.$messageController.format(this.comboDocumentIdentifier, fromRange(range), $format, this.applyEdits); - } - } - - applyEdits = (edits: lsp.TextEdit[]) => { - edits ??= []; - for (let edit of edits.reverse()) { - this.session.replace(toRange(edit.range), edit.newText); - } - } - - getSemanticTokens() { - //TODO: disable this when unused var false - /*if (!this.$provider.options.functionality!.semanticTokens) - return;*/ - //TODO: improve this - let lastRow = this.editor.renderer.getLastVisibleRow(); - let visibleRange: AceRangeData = { - start: { - row: this.editor.renderer.getFirstVisibleRow(), - column: 0 - }, - end: { - row: lastRow + 1, - column: this.session.getLine(lastRow).length - } - } - if (this.$provider.options.functionality!.semanticTokens) { - this.$messageController.getSemanticTokens(this.comboDocumentIdentifier, fromRange(visibleRange), this.$applySemanticTokens); - } else { - this.$applySemanticTokens(undefined); - } - } - - $applySemanticTokens = (tokens: lsp.SemanticTokens | null | undefined) => { - if (!tokens && this.$predefinedTokens.length == 0) { - this.session.setSemanticTokens(undefined); - this.$runTokenizer(); - return; - } - let originalTokens: OriginalSemanticTokens | undefined; - if (tokens) { - originalTokens = { - tokens: tokens.data, - tokenTypes: this.semanticTokensLegend!.tokenTypes, - tokenModifiersLegend: this.semanticTokensLegend!.tokenModifiers - } - } - let decodedTokens = parseSemanticTokens(originalTokens, this.$predefinedTokens); - - this.session.setSemanticTokens(decodedTokens); - this.$runTokenizer(); - } - - $runTokenizer() { - let bgTokenizer = this.session.bgTokenizer; - //@ts-ignore - bgTokenizer.running = setTimeout(() => { - bgTokenizer.$worker(); - }, 20); - } - - $applyDocumentHighlight = (documentHighlights: lsp.DocumentHighlight[]) => { - if (!this.state.occurrenceMarkers) { - this.state.occurrenceMarkers = new MarkerGroup(this.session); - } - if (documentHighlights) { //some servers return null, which contradicts spec - this.state.occurrenceMarkers.setMarkers(fromDocumentHighlights(documentHighlights)); - } - }; - - closeDocument(callback?) { - this.$messageController.closeDocument(this.comboDocumentIdentifier, callback); - } -} diff --git a/packages/ace-linters/src/session-language-provider.ts b/packages/ace-linters/src/session-language-provider.ts new file mode 100644 index 0000000..3e6dcdd --- /dev/null +++ b/packages/ace-linters/src/session-language-provider.ts @@ -0,0 +1,429 @@ +import {Ace} from "ace-code"; +import {ComboDocumentIdentifier, IMessageController} from "./types/message-controller-interface"; +import {AceRangeData, ServiceOptions} from "./types/language-service"; +import * as lsp from "vscode-languageserver-protocol"; +import {MarkerGroup} from "./ace/marker_group"; +import { + DecodedSemanticTokens, + DecodedToken, + mergeTokens, + OriginalSemanticTokens, parseSemanticTokens +} from "./type-converters/lsp/semantic-tokens"; +import {convertToUri} from "./utils"; +import {FormattingOptions} from "vscode-languageserver-protocol"; +import { + fromAceDelta, + fromDocumentHighlights, + fromRange, + toAnnotations, + toMarkerGroupItem, + toRange +} from "./type-converters/lsp/lsp-converters"; +import {CommonConverter} from "./type-converters/common-converters"; +import {LanguageProvider} from "./language-provider"; + +export class SessionLanguageProvider { + session: Ace.EditSession; + documentUri: string; + private $messageController: IMessageController; + private $deltaQueue: Ace.Delta[] | null; + private $isConnected = false; + private $options?: ServiceOptions; + private $filePath: string; + private $isFilePathRequired = false; + private $servicesCapabilities?: { [serviceName: string]: lsp.ServerCapabilities }; + private $requestsQueue: Function[] = []; + + state: { + occurrenceMarkers: MarkerGroup | null, + diagnosticMarkers: MarkerGroup | null + } = { + occurrenceMarkers: null, + diagnosticMarkers: null + } + + private extensions = { + "typescript": "ts", + "javascript": "js" + } + editor: Ace.Editor; + + private semanticTokensLegend?: lsp.SemanticTokensLegend; + private $provider: LanguageProvider; + + private $predefinedTokens: DecodedToken[] = []; + private $firstStaleLine: undefined | number; + + /** + * Constructs a new instance of the `SessionLanguageProvider` class. + * + * @param provider - The `LanguageProvider` instance. + * @param session - The Ace editor session. + * @param editor - The Ace editor instance. + * @param messageController - The `IMessageController` instance for handling messages. + */ + constructor(provider: LanguageProvider, session: Ace.EditSession, editor: Ace.Editor, messageController: IMessageController) { + this.$provider = provider; + this.$messageController = messageController; + this.session = session; + this.editor = editor; + this.$isFilePathRequired = provider.requireFilePath; + + session.doc.version = 1; + session.doc.on("change", this.$changeListener, true); + this.addSemanticTokenSupport(session); //TODO: ? + session.on("changeMode", this.$changeMode); + if (this.$provider.options.functionality!.semanticTokens) { + session.on("changeScrollTop", () => this.getSemanticTokens()); + } + + this.$init(); + } + + enqueueIfNotConnected(callback: () => void) { + if (!this.$isConnected) { + this.$requestsQueue.push(callback); + } else { + callback(); + } + } + + get comboDocumentIdentifier(): ComboDocumentIdentifier { + return { + documentUri: this.documentUri, + sessionId: this.session["id"] + }; + } + + /** + * @param filePath + */ + setFilePath(filePath: string) { + this.enqueueIfNotConnected(() => { + this.session.doc.version++; + if (this.$filePath !== undefined)//TODO change file path + return; + this.$filePath = filePath; + const previousComboId = this.comboDocumentIdentifier; + this.initDocumentUri(true); + this.$messageController.renameDocument(previousComboId, this.comboDocumentIdentifier.documentUri, this.session.doc.version); + }) + }; + + private $init() { + if (this.$isFilePathRequired && this.$filePath === undefined) + return; + this.initDocumentUri(); + this.$messageController.init(this.comboDocumentIdentifier, this.session.doc, this.$mode, this.$options, this.$connected); + } + + addSemanticTokenSupport(session: Ace.EditSession) { + let bgTokenizer = session.bgTokenizer; + session.setSemanticTokens = (tokens: DecodedSemanticTokens | undefined) => { + /*const hasPreviousTokens = bgTokenizer?.semanticTokens?.tokens && bgTokenizer?.semanticTokens?.tokens.length > 0; + const hasNewTokens = tokens && tokens.tokens.length > 0; + + if (hasPreviousTokens || hasNewTokens) { + let startRow = Number.MAX_SAFE_INTEGER; + if (hasPreviousTokens) { + startRow = Math.min(startRow, bgTokenizer!.semanticTokens!.tokens[0].row); + } + if (hasNewTokens) { + startRow = Math.min(startRow, tokens.tokens[0].row); + } else { + startRow = 0; + } + bgTokenizer.currentLine = startRow; + bgTokenizer.lines = bgTokenizer.lines.slice(0, startRow - 1); + if (startRow === 0) { + bgTokenizer.lines[0] = null; + } + } else { + bgTokenizer.currentLine = 0; + bgTokenizer.lines = []; + }*/ + bgTokenizer.currentLine = 0; + bgTokenizer.lines = []; + bgTokenizer.semanticTokens = tokens; + } + + bgTokenizer.$tokenizeRow = (row: number) => { + var line = bgTokenizer.doc.getLine(row); + var state = bgTokenizer.states[row - 1]; + var data = bgTokenizer.tokenizer.getLineTokens(line, state, row); + + if (bgTokenizer.states[row] + "" !== data.state + "") { + bgTokenizer.states[row] = data.state; + bgTokenizer.lines[row + 1] = null; + if (bgTokenizer.currentLine > row + 1) + bgTokenizer.currentLine = row + 1; + } else if (bgTokenizer.currentLine == row) { + bgTokenizer.currentLine = row + 1; + } + + if (bgTokenizer.semanticTokens) { + let decodedTokens = bgTokenizer.semanticTokens.getByRow(row); + if (decodedTokens && decodedTokens.length > 0) { + data.tokens = mergeTokens(data.tokens, decodedTokens); + } + } + + return bgTokenizer.lines[row] = data.tokens; + } + } + + private $connected = (capabilities: { [serviceName: string]: lsp.ServerCapabilities }) => { + this.$isConnected = true; + + this.setServerCapabilities(capabilities); + + this.$requestsQueue.forEach((requestCallback) => requestCallback()); + this.$requestsQueue = []; + + if (this.$deltaQueue) + this.$sendDeltaQueue(); + if (this.$options) + this.setOptions(this.$options); + } + + private $changeMode = () => { + this.enqueueIfNotConnected(() => { + this.$deltaQueue = []; + + this.session.clearAnnotations(); + if (this.state.diagnosticMarkers) { + this.state.diagnosticMarkers.setMarkers([]); + } + + this.session.setSemanticTokens(undefined); //clear all semantic tokens + let newVersion = this.session.doc.version++; + this.$messageController.changeMode(this.comboDocumentIdentifier, this.session.getValue(), newVersion, this.$mode, this.setServerCapabilities); + }); + }; + + setServerCapabilities = (capabilities: { [serviceName: string]: lsp.ServerCapabilities }) => { + if (!capabilities) + return; + this.$servicesCapabilities = {...capabilities}; + + let hasTriggerChars = Object.values(capabilities).some((capability) => capability?.completionProvider?.triggerCharacters); + + if (hasTriggerChars) { + let completer = this.editor.completers.find((completer) => completer.id === "lspCompleters"); + if (completer) { + let allTriggerCharacters: string[] = []; + Object.values(capabilities).forEach((capability) => { + if (capability?.completionProvider?.triggerCharacters) { + allTriggerCharacters.push(...capability.completionProvider.triggerCharacters); + } + }); + + allTriggerCharacters = [...new Set(allTriggerCharacters)]; + + completer.triggerCharacters = allTriggerCharacters; + } + } + + let hasSemanticTokensProvider = Object.values(capabilities).some((capability) => { + if (capability?.semanticTokensProvider) { + this.semanticTokensLegend = capability.semanticTokensProvider.legend; + return true; + } + }); + if (hasSemanticTokensProvider) { + this.getSemanticTokens(); + } + //TODO: we should restrict range formatting if any of services is only has full format capabilities + //or we shoudl use service with full format capability instead of range one's + } + + private initDocumentUri(isRename = false) { + let filePath = this.$filePath ?? this.session["id"] + "." + this.$extension; + if (isRename) { + delete this.$provider.$urisToSessionsIds[this.documentUri]; + } + this.documentUri = convertToUri(filePath); + this.$provider.$urisToSessionsIds[this.documentUri] = this.session["id"]; + } + + private get $extension() { + let mode = this.$mode.replace("ace/mode/", ""); + return this.extensions[mode] ?? mode; + } + + private get $mode(): string { + return this.session["$modeId"]; + } + + private get $format(): FormattingOptions { + return { + tabSize: this.session.getTabSize(), + insertSpaces: this.session.getUseSoftTabs() + } + } + + private $changeListener = (delta: Ace.Delta) => { + this.session.doc.version++; + if (!this.$deltaQueue) { + this.$deltaQueue = []; + setTimeout(() => this.$sendDeltaQueue(() => { + this.getSemanticTokens(); + }), 0); + } + this.$deltaQueue.push(delta); + } + + $sendDeltaQueue = (callback?) => { + let deltas = this.$deltaQueue; + if (!deltas) return callback && callback(); + this.$deltaQueue = null; + if (deltas.length) + this.$messageController.change(this.comboDocumentIdentifier, deltas.map((delta) => + fromAceDelta(delta, this.session.doc.getNewLineCharacter())), this.session.doc, callback); + }; + + $showAnnotations = (diagnostics: lsp.Diagnostic[]) => { + if (!diagnostics) { + return; + } + + let annotations = toAnnotations(diagnostics); + this.session.clearAnnotations(); + if (annotations && annotations.length > 0) { + this.session.setAnnotations(annotations); + } + if (!this.state.diagnosticMarkers) { + this.state.diagnosticMarkers = new MarkerGroup(this.session); + } + //TODO: show unused option + this.setPredefinedTokens(diagnostics); + this.state.diagnosticMarkers.setMarkers(diagnostics?.map((el) => toMarkerGroupItem(CommonConverter.toRange(toRange(el.range)), "language_highlight_error", el.message))); + } + + setPredefinedTokens(diagnostics: lsp.Diagnostic[]) { + this.$predefinedTokens = []; + + diagnostics.forEach((el) => { + if (el.tags && el.tags.length > 0) { + if (!this.$firstStaleLine || el.range.start.line < this.$firstStaleLine) { + this.$firstStaleLine = el.range.start.line; + } + this.$predefinedTokens.push({ + row: el.range.start.line, + startColumn: el.range.start.character, + length: el.range.end.character - el.range.start.character, + type: el.tags[0] === lsp.DiagnosticTag.Deprecated ? "highlight_deprecated": "highlight_unnecessary" + }); + } + }); + + if (!this.$provider.options.functionality!.semanticTokens) { + this.$applySemanticTokens(undefined); + } + } + + setOptions(options: OptionsType) { + if (!this.$isConnected) { + this.$options = options; + return; + } + this.$messageController.changeOptions(this.comboDocumentIdentifier, options); + } + + validate = () => { + this.$messageController.doValidation(this.comboDocumentIdentifier, this.$showAnnotations); + } + + format = () => { + let selectionRanges = this.session.getSelection().getAllRanges(); + let $format = this.$format; + let aceRangeDatas = selectionRanges as AceRangeData[]; + if (!selectionRanges || selectionRanges[0].isEmpty()) { + let row = this.session.getLength(); + let column = this.session.getLine(row).length - 1; + aceRangeDatas = + [{ + start: { + row: 0, column: 0 + }, + end: { + row: row, column: column + } + }]; + } + for (let range of aceRangeDatas) { + this.$messageController.format(this.comboDocumentIdentifier, fromRange(range), $format, this.applyEdits); + } + } + + applyEdits = (edits: lsp.TextEdit[]) => { + edits ??= []; + for (let edit of edits.reverse()) { + this.session.replace(toRange(edit.range), edit.newText); + } + } + + getSemanticTokens() { + //TODO: disable this when unused var false + /*if (!this.$provider.options.functionality!.semanticTokens) + return;*/ + //TODO: improve this + let lastRow = this.editor.renderer.getLastVisibleRow(); + let visibleRange: AceRangeData = { + start: { + row: this.editor.renderer.getFirstVisibleRow(), + column: 0 + }, + end: { + row: lastRow + 1, + column: this.session.getLine(lastRow).length + } + } + if (this.$provider.options.functionality!.semanticTokens) { + this.$messageController.getSemanticTokens(this.comboDocumentIdentifier, fromRange(visibleRange), this.$applySemanticTokens); + } else { + this.$applySemanticTokens(undefined); + } + } + + $applySemanticTokens = (tokens: lsp.SemanticTokens | null | undefined) => { + if (!tokens && this.$predefinedTokens.length == 0) { + this.session.setSemanticTokens(undefined); + this.$runTokenizer(); + return; + } + let originalTokens: OriginalSemanticTokens | undefined; + if (tokens) { + originalTokens = { + tokens: tokens.data, + tokenTypes: this.semanticTokensLegend!.tokenTypes, + tokenModifiersLegend: this.semanticTokensLegend!.tokenModifiers + } + } + let decodedTokens = parseSemanticTokens(originalTokens, this.$predefinedTokens); + + this.session.setSemanticTokens(decodedTokens); + this.$runTokenizer(); + } + + $runTokenizer() { + let bgTokenizer = this.session.bgTokenizer; + //@ts-ignore + bgTokenizer.running = setTimeout(() => { + bgTokenizer.$worker(); + }, 20); + } + + $applyDocumentHighlight = (documentHighlights: lsp.DocumentHighlight[]) => { + if (!this.state.occurrenceMarkers) { + this.state.occurrenceMarkers = new MarkerGroup(this.session); + } + if (documentHighlights) { //some servers return null, which contradicts spec + this.state.occurrenceMarkers.setMarkers(fromDocumentHighlights(documentHighlights)); + } + }; + + closeDocument(callback?) { + this.$messageController.closeDocument(this.comboDocumentIdentifier, callback); + } +} \ No newline at end of file