"use strict"; /* -------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ Object.defineProperty(exports, "__esModule", { value: true }); exports.DidSaveTextDocumentFeature = exports.WillSaveWaitUntilFeature = exports.WillSaveFeature = exports.DidChangeTextDocumentFeature = exports.DidCloseTextDocumentFeature = exports.DidOpenTextDocumentFeature = void 0; const vscode_1 = require("vscode"); const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol"); const features_1 = require("./features"); const UUID = require("./utils/uuid"); class DidOpenTextDocumentFeature extends features_1.TextDocumentEventFeature { constructor(client, syncedDocuments) { super(client, vscode_1.workspace.onDidOpenTextDocument, vscode_languageserver_protocol_1.DidOpenTextDocumentNotification.type, () => client.middleware.didOpen, (textDocument) => client.code2ProtocolConverter.asOpenTextDocumentParams(textDocument), (data) => data, features_1.TextDocumentEventFeature.textDocumentFilter); this._syncedDocuments = syncedDocuments; } get openDocuments() { return this._syncedDocuments.values(); } fillClientCapabilities(capabilities) { (0, features_1.ensure)((0, features_1.ensure)(capabilities, 'textDocument'), 'synchronization').dynamicRegistration = true; } initialize(capabilities, documentSelector) { const textDocumentSyncOptions = capabilities.resolvedTextDocumentSync; if (documentSelector && textDocumentSyncOptions && textDocumentSyncOptions.openClose) { this.register({ id: UUID.generateUuid(), registerOptions: { documentSelector: documentSelector } }); } } get registrationType() { return vscode_languageserver_protocol_1.DidOpenTextDocumentNotification.type; } register(data) { super.register(data); if (!data.registerOptions.documentSelector) { return; } const documentSelector = this._client.protocol2CodeConverter.asDocumentSelector(data.registerOptions.documentSelector); vscode_1.workspace.textDocuments.forEach((textDocument) => { const uri = textDocument.uri.toString(); if (this._syncedDocuments.has(uri)) { return; } if (vscode_1.languages.match(documentSelector, textDocument) > 0 && !this._client.hasDedicatedTextSynchronizationFeature(textDocument)) { const middleware = this._client.middleware; const didOpen = (textDocument) => { return this._client.sendNotification(this._type, this._createParams(textDocument)); }; (middleware.didOpen ? middleware.didOpen(textDocument, didOpen) : didOpen(textDocument)).catch((error) => { this._client.error(`Sending document notification ${this._type.method} failed`, error); }); this._syncedDocuments.set(uri, textDocument); } }); } getTextDocument(data) { return data; } notificationSent(textDocument, type, params) { this._syncedDocuments.set(textDocument.uri.toString(), textDocument); super.notificationSent(textDocument, type, params); } } exports.DidOpenTextDocumentFeature = DidOpenTextDocumentFeature; class DidCloseTextDocumentFeature extends features_1.TextDocumentEventFeature { constructor(client, syncedDocuments, pendingTextDocumentChanges) { super(client, vscode_1.workspace.onDidCloseTextDocument, vscode_languageserver_protocol_1.DidCloseTextDocumentNotification.type, () => client.middleware.didClose, (textDocument) => client.code2ProtocolConverter.asCloseTextDocumentParams(textDocument), (data) => data, features_1.TextDocumentEventFeature.textDocumentFilter); this._syncedDocuments = syncedDocuments; this._pendingTextDocumentChanges = pendingTextDocumentChanges; } get registrationType() { return vscode_languageserver_protocol_1.DidCloseTextDocumentNotification.type; } fillClientCapabilities(capabilities) { (0, features_1.ensure)((0, features_1.ensure)(capabilities, 'textDocument'), 'synchronization').dynamicRegistration = true; } initialize(capabilities, documentSelector) { let textDocumentSyncOptions = capabilities.resolvedTextDocumentSync; if (documentSelector && textDocumentSyncOptions && textDocumentSyncOptions.openClose) { this.register({ id: UUID.generateUuid(), registerOptions: { documentSelector: documentSelector } }); } } async callback(data) { await super.callback(data); this._pendingTextDocumentChanges.delete(data.uri.toString()); } getTextDocument(data) { return data; } notificationSent(textDocument, type, params) { this._syncedDocuments.delete(textDocument.uri.toString()); super.notificationSent(textDocument, type, params); } unregister(id) { const selector = this._selectors.get(id); // The super call removed the selector from the map // of selectors. super.unregister(id); const selectors = this._selectors.values(); this._syncedDocuments.forEach((textDocument) => { if (vscode_1.languages.match(selector, textDocument) > 0 && !this._selectorFilter(selectors, textDocument) && !this._client.hasDedicatedTextSynchronizationFeature(textDocument)) { let middleware = this._client.middleware; let didClose = (textDocument) => { return this._client.sendNotification(this._type, this._createParams(textDocument)); }; this._syncedDocuments.delete(textDocument.uri.toString()); (middleware.didClose ? middleware.didClose(textDocument, didClose) : didClose(textDocument)).catch((error) => { this._client.error(`Sending document notification ${this._type.method} failed`, error); }); } }); } } exports.DidCloseTextDocumentFeature = DidCloseTextDocumentFeature; class DidChangeTextDocumentFeature extends features_1.DynamicDocumentFeature { constructor(client, pendingTextDocumentChanges) { super(client); this._changeData = new Map(); this._onNotificationSent = new vscode_1.EventEmitter(); this._onPendingChangeAdded = new vscode_1.EventEmitter(); this._pendingTextDocumentChanges = pendingTextDocumentChanges; this._syncKind = vscode_languageserver_protocol_1.TextDocumentSyncKind.None; } get onNotificationSent() { return this._onNotificationSent.event; } get onPendingChangeAdded() { return this._onPendingChangeAdded.event; } get syncKind() { return this._syncKind; } get registrationType() { return vscode_languageserver_protocol_1.DidChangeTextDocumentNotification.type; } fillClientCapabilities(capabilities) { (0, features_1.ensure)((0, features_1.ensure)(capabilities, 'textDocument'), 'synchronization').dynamicRegistration = true; } initialize(capabilities, documentSelector) { let textDocumentSyncOptions = capabilities.resolvedTextDocumentSync; if (documentSelector && textDocumentSyncOptions && textDocumentSyncOptions.change !== undefined && textDocumentSyncOptions.change !== vscode_languageserver_protocol_1.TextDocumentSyncKind.None) { this.register({ id: UUID.generateUuid(), registerOptions: Object.assign({}, { documentSelector: documentSelector }, { syncKind: textDocumentSyncOptions.change }) }); } } register(data) { if (!data.registerOptions.documentSelector) { return; } if (!this._listener) { this._listener = vscode_1.workspace.onDidChangeTextDocument(this.callback, this); } this._changeData.set(data.id, { syncKind: data.registerOptions.syncKind, documentSelector: this._client.protocol2CodeConverter.asDocumentSelector(data.registerOptions.documentSelector), }); this.updateSyncKind(data.registerOptions.syncKind); } *getDocumentSelectors() { for (const data of this._changeData.values()) { yield data.documentSelector; } } async callback(event) { // Text document changes are send for dirty changes as well. We don't // have dirty / un-dirty events in the LSP so we ignore content changes // with length zero. if (event.contentChanges.length === 0) { return; } // We need to capture the URI and version here since they might change on the text document // until we reach did `didChange` call since the middleware support async execution. const uri = event.document.uri; const version = event.document.version; const promises = []; for (const changeData of this._changeData.values()) { if (vscode_1.languages.match(changeData.documentSelector, event.document) > 0 && !this._client.hasDedicatedTextSynchronizationFeature(event.document)) { const middleware = this._client.middleware; if (changeData.syncKind === vscode_languageserver_protocol_1.TextDocumentSyncKind.Incremental) { const didChange = async (event) => { const params = this._client.code2ProtocolConverter.asChangeTextDocumentParams(event, uri, version); await this._client.sendNotification(vscode_languageserver_protocol_1.DidChangeTextDocumentNotification.type, params); this.notificationSent(event.document, vscode_languageserver_protocol_1.DidChangeTextDocumentNotification.type, params); }; promises.push(middleware.didChange ? middleware.didChange(event, event => didChange(event)) : didChange(event)); } else if (changeData.syncKind === vscode_languageserver_protocol_1.TextDocumentSyncKind.Full) { const didChange = async (event) => { const eventUri = event.document.uri.toString(); this._pendingTextDocumentChanges.set(eventUri, event.document); this._onPendingChangeAdded.fire(); }; promises.push(middleware.didChange ? middleware.didChange(event, event => didChange(event)) : didChange(event)); } } } return Promise.all(promises).then(undefined, (error) => { this._client.error(`Sending document notification ${vscode_languageserver_protocol_1.DidChangeTextDocumentNotification.type.method} failed`, error); throw error; }); } notificationSent(textDocument, type, params) { this._onNotificationSent.fire({ textDocument, type, params }); } unregister(id) { this._changeData.delete(id); if (this._changeData.size === 0) { if (this._listener) { this._listener.dispose(); this._listener = undefined; } this._syncKind = vscode_languageserver_protocol_1.TextDocumentSyncKind.None; } else { this._syncKind = vscode_languageserver_protocol_1.TextDocumentSyncKind.None; for (const changeData of this._changeData.values()) { this.updateSyncKind(changeData.syncKind); if (this._syncKind === vscode_languageserver_protocol_1.TextDocumentSyncKind.Full) { break; } } } } clear() { this._pendingTextDocumentChanges.clear(); this._changeData.clear(); this._syncKind = vscode_languageserver_protocol_1.TextDocumentSyncKind.None; if (this._listener) { this._listener.dispose(); this._listener = undefined; } } getPendingDocumentChanges(excludes) { if (this._pendingTextDocumentChanges.size === 0) { return []; } let result; if (excludes.size === 0) { result = Array.from(this._pendingTextDocumentChanges.values()); this._pendingTextDocumentChanges.clear(); } else { result = []; for (const entry of this._pendingTextDocumentChanges) { if (!excludes.has(entry[0])) { result.push(entry[1]); this._pendingTextDocumentChanges.delete(entry[0]); } } } return result; } getProvider(document) { for (const changeData of this._changeData.values()) { if (vscode_1.languages.match(changeData.documentSelector, document) > 0) { return { send: (event) => { return this.callback(event); } }; } } return undefined; } updateSyncKind(syncKind) { if (this._syncKind === vscode_languageserver_protocol_1.TextDocumentSyncKind.Full) { return; } switch (syncKind) { case vscode_languageserver_protocol_1.TextDocumentSyncKind.Full: this._syncKind = syncKind; break; case vscode_languageserver_protocol_1.TextDocumentSyncKind.Incremental: if (this._syncKind === vscode_languageserver_protocol_1.TextDocumentSyncKind.None) { this._syncKind = vscode_languageserver_protocol_1.TextDocumentSyncKind.Incremental; } break; } } } exports.DidChangeTextDocumentFeature = DidChangeTextDocumentFeature; class WillSaveFeature extends features_1.TextDocumentEventFeature { constructor(client) { super(client, vscode_1.workspace.onWillSaveTextDocument, vscode_languageserver_protocol_1.WillSaveTextDocumentNotification.type, () => client.middleware.willSave, (willSaveEvent) => client.code2ProtocolConverter.asWillSaveTextDocumentParams(willSaveEvent), (event) => event.document, (selectors, willSaveEvent) => features_1.TextDocumentEventFeature.textDocumentFilter(selectors, willSaveEvent.document)); } get registrationType() { return vscode_languageserver_protocol_1.WillSaveTextDocumentNotification.type; } fillClientCapabilities(capabilities) { let value = (0, features_1.ensure)((0, features_1.ensure)(capabilities, 'textDocument'), 'synchronization'); value.willSave = true; } initialize(capabilities, documentSelector) { let textDocumentSyncOptions = capabilities.resolvedTextDocumentSync; if (documentSelector && textDocumentSyncOptions && textDocumentSyncOptions.willSave) { this.register({ id: UUID.generateUuid(), registerOptions: { documentSelector: documentSelector } }); } } getTextDocument(data) { return data.document; } } exports.WillSaveFeature = WillSaveFeature; class WillSaveWaitUntilFeature extends features_1.DynamicDocumentFeature { constructor(client) { super(client); this._selectors = new Map(); } getDocumentSelectors() { return this._selectors.values(); } get registrationType() { return vscode_languageserver_protocol_1.WillSaveTextDocumentWaitUntilRequest.type; } fillClientCapabilities(capabilities) { let value = (0, features_1.ensure)((0, features_1.ensure)(capabilities, 'textDocument'), 'synchronization'); value.willSaveWaitUntil = true; } initialize(capabilities, documentSelector) { let textDocumentSyncOptions = capabilities.resolvedTextDocumentSync; if (documentSelector && textDocumentSyncOptions && textDocumentSyncOptions.willSaveWaitUntil) { this.register({ id: UUID.generateUuid(), registerOptions: { documentSelector: documentSelector } }); } } register(data) { if (!data.registerOptions.documentSelector) { return; } if (!this._listener) { this._listener = vscode_1.workspace.onWillSaveTextDocument(this.callback, this); } this._selectors.set(data.id, this._client.protocol2CodeConverter.asDocumentSelector(data.registerOptions.documentSelector)); } callback(event) { if (features_1.TextDocumentEventFeature.textDocumentFilter(this._selectors.values(), event.document) && !this._client.hasDedicatedTextSynchronizationFeature(event.document)) { let middleware = this._client.middleware; let willSaveWaitUntil = (event) => { return this._client.sendRequest(vscode_languageserver_protocol_1.WillSaveTextDocumentWaitUntilRequest.type, this._client.code2ProtocolConverter.asWillSaveTextDocumentParams(event)).then(async (edits) => { let vEdits = await this._client.protocol2CodeConverter.asTextEdits(edits); return vEdits === undefined ? [] : vEdits; }); }; event.waitUntil(middleware.willSaveWaitUntil ? middleware.willSaveWaitUntil(event, willSaveWaitUntil) : willSaveWaitUntil(event)); } } unregister(id) { this._selectors.delete(id); if (this._selectors.size === 0 && this._listener) { this._listener.dispose(); this._listener = undefined; } } clear() { this._selectors.clear(); if (this._listener) { this._listener.dispose(); this._listener = undefined; } } } exports.WillSaveWaitUntilFeature = WillSaveWaitUntilFeature; class DidSaveTextDocumentFeature extends features_1.TextDocumentEventFeature { constructor(client) { super(client, vscode_1.workspace.onDidSaveTextDocument, vscode_languageserver_protocol_1.DidSaveTextDocumentNotification.type, () => client.middleware.didSave, (textDocument) => client.code2ProtocolConverter.asSaveTextDocumentParams(textDocument, this._includeText), (data) => data, features_1.TextDocumentEventFeature.textDocumentFilter); this._includeText = false; } get registrationType() { return vscode_languageserver_protocol_1.DidSaveTextDocumentNotification.type; } fillClientCapabilities(capabilities) { (0, features_1.ensure)((0, features_1.ensure)(capabilities, 'textDocument'), 'synchronization').didSave = true; } initialize(capabilities, documentSelector) { const textDocumentSyncOptions = capabilities.resolvedTextDocumentSync; if (documentSelector && textDocumentSyncOptions && textDocumentSyncOptions.save) { const saveOptions = typeof textDocumentSyncOptions.save === 'boolean' ? { includeText: false } : { includeText: !!textDocumentSyncOptions.save.includeText }; this.register({ id: UUID.generateUuid(), registerOptions: Object.assign({}, { documentSelector: documentSelector }, saveOptions) }); } } register(data) { this._includeText = !!data.registerOptions.includeText; super.register(data); } getTextDocument(data) { return data; } } exports.DidSaveTextDocumentFeature = DidSaveTextDocumentFeature;