atom-languageclient
Version:
Integrate Language Servers with Atom
951 lines • 136 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LanguageClientConnection = void 0;
const cp = require("child_process");
const rpc = require("vscode-jsonrpc");
const rpcNode = require("vscode-jsonrpc/node");
const path = require("path");
const convert_js_1 = require("./convert.js");
const apply_edit_adapter_1 = require("./adapters/apply-edit-adapter");
const autocomplete_adapter_1 = require("./adapters/autocomplete-adapter");
const CallHierarchyAdapter = require("./adapters/call-hierarchy-adapter");
const code_action_adapter_1 = require("./adapters/code-action-adapter");
const code_format_adapter_1 = require("./adapters/code-format-adapter");
const code_highlight_adapter_1 = require("./adapters/code-highlight-adapter");
const datatip_adapter_1 = require("./adapters/datatip-adapter");
const definition_adapter_1 = require("./adapters/definition-adapter");
const document_sync_adapter_1 = require("./adapters/document-sync-adapter");
const find_references_adapter_1 = require("./adapters/find-references-adapter");
const linter_push_v2_adapter_1 = require("./adapters/linter-push-v2-adapter");
const logging_console_adapter_1 = require("./adapters/logging-console-adapter");
const notifications_adapter_1 = require("./adapters/notifications-adapter");
const outline_view_adapter_1 = require("./adapters/outline-view-adapter");
const rename_adapter_1 = require("./adapters/rename-adapter");
const signature_help_adapter_1 = require("./adapters/signature-help-adapter");
const ShowDocumentAdapter = require("./adapters/show-document-adapter");
const Utils = require("./utils");
const languageclient_1 = require("./languageclient");
Object.defineProperty(exports, "LanguageClientConnection", { enumerable: true, get: function () { return languageclient_1.LanguageClientConnection; } });
const logger_1 = require("./logger");
const server_manager_js_1 = require("./server-manager.js");
const atom_1 = require("atom");
const path_1 = require("path");
/**
* Public: AutoLanguageClient provides a simple way to have all the supported Atom-IDE services wired up entirely for
* you by just subclassing it and implementing at least
*
* - `startServerProcess`
* - `getGrammarScopes`
* - `getLanguageName`
* - `getServerName`
*/
class AutoLanguageClient {
constructor() {
this._isDeactivating = false;
this._serverAdapters = new WeakMap();
this.processStdErr = "";
/**
* If this is set to `true` (the default value), the servers will shut down gracefully. If it is set to `false`, the
* servers will be killed without awaiting shutdown response.
*/
this.shutdownGracefully = true;
this.reportBusyWhile = (title, f) => __awaiter(this, void 0, void 0, function* () {
if (this.busySignalService) {
return this.busySignalService.reportBusyWhile(title, f);
}
else {
return this.reportBusyWhileDefault(title, f);
}
});
this.reportBusyWhileDefault = (title, f) => __awaiter(this, void 0, void 0, function* () {
this.logger.info(`[Started] ${title}`);
let res;
try {
res = yield f();
}
finally {
this.logger.info(`[Finished] ${title}`);
}
return res;
});
}
// You must implement these so we know how to deal with your language and server
// -------------------------------------------------------------------------
/** Return an array of the grammar scopes you handle, e.g. [ 'source.js' ] */
getGrammarScopes() {
throw Error("Must implement getGrammarScopes when extending AutoLanguageClient");
}
/** Return the name of the language you support, e.g. 'JavaScript' */
getLanguageName() {
throw Error("Must implement getLanguageName when extending AutoLanguageClient");
}
/** Return the name of your server, e.g. 'Eclipse JDT' */
getServerName() {
throw Error("Must implement getServerName when extending AutoLanguageClient");
}
/** Start your server process */
startServerProcess(_projectPath) {
throw Error("Must override startServerProcess to start language server process when extending AutoLanguageClient");
}
// You might want to override these for different behavior
// ---------------------------------------------------------------------------
/** (Optional) Determine whether we should start a server for a given editor if we don't have one yet */
shouldStartForEditor(editor) {
return this.getGrammarScopes().includes(editor.getGrammar().scopeName);
}
/** (Optional) Return the parameters used to initialize a client - you may want to extend capabilities */
getInitializeParams(projectPath, lsProcess) {
const rootUri = convert_js_1.default.pathToUri(projectPath);
return {
processId: lsProcess.pid !== undefined ? lsProcess.pid : null,
rootPath: projectPath,
rootUri,
locale: atom.config.get("atom-i18n.locale") || "en",
workspaceFolders: [{ uri: rootUri, name: path_1.basename(projectPath) }],
// The capabilities supported.
// TODO the capabilities set to false/undefined are TODO. See {ls.ServerCapabilities} for a full list.
capabilities: {
workspace: {
applyEdit: true,
configuration: false,
workspaceEdit: {
documentChanges: true,
normalizesLineEndings: false,
changeAnnotationSupport: undefined,
resourceOperations: ["create", "rename", "delete"],
},
workspaceFolders: true,
didChangeConfiguration: {
dynamicRegistration: false,
},
didChangeWatchedFiles: {
dynamicRegistration: false,
},
// BLOCKED: on atom/symbols-view
symbol: {
dynamicRegistration: false,
},
executeCommand: {
dynamicRegistration: false,
},
semanticTokens: undefined,
codeLens: undefined,
fileOperations: {
// BLOCKED: on tree-view not providing hooks for "before file/dir created"
willCreate: false,
// BLOCKED: on tree-view not providing hooks for "before file/dir renamed"
willRename: false,
// BLOCKED: on tree-view not providing hooks for "before file/dir deleted"
willDelete: false,
},
},
textDocument: {
synchronization: {
dynamicRegistration: false,
willSave: true,
willSaveWaitUntil: true,
didSave: true,
},
completion: {
dynamicRegistration: false,
completionItem: {
snippetSupport: true,
commitCharactersSupport: false,
documentationFormat: [],
deprecatedSupport: false,
preselectSupport: false,
tagSupport: {
valueSet: [],
},
insertReplaceSupport: false,
resolveSupport: {
properties: [],
},
insertTextModeSupport: {
valueSet: [],
},
},
completionItemKind: {
valueSet: [],
},
contextSupport: true,
},
hover: {
dynamicRegistration: false,
},
signatureHelp: {
dynamicRegistration: false,
},
declaration: undefined,
references: {
dynamicRegistration: false,
},
documentHighlight: {
dynamicRegistration: false,
},
documentSymbol: {
dynamicRegistration: false,
hierarchicalDocumentSymbolSupport: true,
},
formatting: {
dynamicRegistration: false,
},
rangeFormatting: {
dynamicRegistration: false,
},
onTypeFormatting: {
dynamicRegistration: false,
},
definition: {
dynamicRegistration: false,
},
codeAction: {
dynamicRegistration: false,
codeActionLiteralSupport: {
codeActionKind: {
valueSet: [""], // TODO explicitly support more?
},
},
},
codeLens: {
dynamicRegistration: false,
},
documentLink: {
dynamicRegistration: false,
},
rename: {
dynamicRegistration: false,
},
moniker: {
dynamicRegistration: false,
},
publishDiagnostics: {
relatedInformation: true,
tagSupport: {
// BLOCKED: on steelbrain/linter supporting ways of denoting useless code and deprecated symbols
valueSet: [],
},
versionSupport: false,
codeDescriptionSupport: true,
dataSupport: true,
},
callHierarchy: {
dynamicRegistration: false,
},
implementation: undefined,
typeDefinition: undefined,
colorProvider: undefined,
foldingRange: undefined,
selectionRange: undefined,
linkedEditingRange: undefined,
semanticTokens: undefined,
},
general: {
regularExpressions: undefined,
markdown: undefined,
},
window: {
workDoneProgress: false,
showMessage: undefined,
showDocument: { support: true },
},
experimental: {},
},
};
}
/** (Optional) Early wire-up of listeners before initialize method is sent */
preInitialization(_connection) { }
/** (Optional) Late wire-up of listeners after initialize method has been sent */
postInitialization(_server) { }
/** (Optional) Determine whether to use ipc, stdio or socket to connect to the server */
getConnectionType() {
return this.socket != null ? "socket" : "stdio";
}
/** (Optional) Return the name of your root configuration key */
getRootConfigurationKey() {
return "";
}
/** (Optional) Transform the configuration object before it is sent to the server */
mapConfigurationObject(configuration) {
return configuration;
}
/**
* (Optional) Determines the `languageId` string used for `textDocument/didOpen` notification. The default is to use
* the grammar name.
*
* You can override this like this:
*
* class MyLanguageClient extends AutoLanguageClient {
* getLanguageIdFromEditor(editor: TextEditor) {
* if (editor.getGrammar().scopeName === "source.myLanguage") {
* return "myCustumLanguageId"
* }
* return super.getLanguageIdFromEditor(editor)
* }
* }
*
* @param editor A {TextEditor} which is opened.
* @returns A {string} of `languageId` used for `textDocument/didOpen` notification.
*/
getLanguageIdFromEditor(editor) {
return editor.getGrammar().name;
}
// Helper methods that are useful for implementors
// ---------------------------------------------------------------------------
/** Gets a LanguageClientConnection for a given TextEditor */
getConnectionForEditor(editor) {
return __awaiter(this, void 0, void 0, function* () {
const server = yield this._serverManager.getServer(editor);
return server ? server.connection : null;
});
}
/** Restart all active language servers for this language client in the workspace */
restartAllServers() {
return __awaiter(this, void 0, void 0, function* () {
yield this._serverManager.restartAllServers();
});
}
// Default implementation of the rest of the AutoLanguageClient
// ---------------------------------------------------------------------------
/** Activate does very little for perf reasons - hooks in via ServerManager for later 'activation' */
activate() {
this._disposable = new atom_1.CompositeDisposable();
this.name = `${this.getLanguageName()} (${this.getServerName()})`;
this.logger = this.getLogger();
this._serverManager = new server_manager_js_1.ServerManager((p) => this.startServer(p), this.logger, (e) => this.shouldStartForEditor(e), (filepath) => this.filterChangeWatchedFiles(filepath), this.reportBusyWhile, this.getServerName(), (textEditor) => this.determineProjectPath(textEditor), this.shutdownGracefully);
this._serverManager.startListening();
process.on("exit", () => this.exitCleanup.bind(this));
}
exitCleanup() {
this._serverManager.terminate();
}
/** Deactivate disposes the resources we're using */
deactivate() {
return __awaiter(this, void 0, void 0, function* () {
this._isDeactivating = true;
this._disposable.dispose();
this._serverManager.stopListening();
yield this._serverManager.stopAllServers();
});
}
/**
* Spawn a general language server. Use this inside the `startServerProcess` override if the language server is a
* general executable. Also see the `spawnChildNode` method. If the name is provided as the first argument, it checks
* `bin/platform-arch/exeName` by default, and if doesn't exists uses the exe on PATH. For example on Windows x64, by
* passing `serve-d`, `bin/win32-x64/exeName.exe` is spawned by default.
*
* @param exe The `name` or `path` of the executable
* @param args Args passed to spawn the exe. Defaults to `[]`.
* @param options: Child process spawn options. Defaults to `{}`.
* @param rootPath The path of the folder of the exe file. Defaults to `join("bin", `${process.platform}-${process.arch} `)`.
* @param exeExtention The extention of the exe file. Defaults to `process.platform === "win32" ? ".exe" : ""`
*/
spawn(exe, args = [], options = {}, rootPath = Utils.rootPathDefault, exeExtention = Utils.exeExtentionDefault) {
this.logger.debug(`starting "${exe} ${args.join(" ")}"`);
return cp.spawn(Utils.getExePath(exe, rootPath, exeExtention), args, options);
}
/**
* Spawn a language server using Atom's Nodejs process Use this inside the `startServerProcess` override if the
* language server is a JavaScript file. Also see the `spawn` method
*/
spawnChildNode(args, options = {}) {
this.logger.debug(`starting child Node "${args.join(" ")}"`);
options.env = options.env || Object.create(process.env);
if (options.env) {
options.env.ELECTRON_RUN_AS_NODE = "1";
options.env.ELECTRON_NO_ATTACH_CONSOLE = "1";
}
return cp.spawn(process.execPath, args, options);
}
/** LSP logging is only set for warnings & errors by default unless you turn on the core.debugLSP setting */
getLogger() {
const filter = atom.config.get("core.debugLSP")
? logger_1.FilteredLogger.DeveloperLevelFilter
: logger_1.FilteredLogger.UserLevelFilter;
return new logger_1.FilteredLogger(new logger_1.ConsoleLogger(this.name), filter);
}
/** Starts the server by starting the process, then initializing the language server and starting adapters */
startServer(projectPath) {
return __awaiter(this, void 0, void 0, function* () {
const lsProcess = yield this.reportBusyWhile(`Starting ${this.getServerName()} for ${path.basename(projectPath)}`, () => __awaiter(this, void 0, void 0, function* () { return this.startServerProcess(projectPath); }));
this.captureServerErrors(lsProcess, projectPath);
const connection = new languageclient_1.LanguageClientConnection(this.createRpcConnection(lsProcess), this.logger);
this.preInitialization(connection);
const initializeParams = this.getInitializeParams(projectPath, lsProcess);
const initialization = connection.initialize(initializeParams);
this.reportBusyWhile(`${this.getServerName()} initializing for ${path.basename(projectPath)}`, () => initialization);
const initializeResponse = yield initialization;
const newServer = {
projectPath,
process: lsProcess,
connection,
capabilities: initializeResponse.capabilities,
disposable: new atom_1.CompositeDisposable(),
additionalPaths: new Set(),
};
this.postInitialization(newServer);
connection.initialized();
connection.on("close", () => {
if (!this._isDeactivating) {
this._serverManager.stopServer(newServer);
if (!this._serverManager.hasServerReachedRestartLimit(newServer)) {
this.logger.debug(`Restarting language server for project '${newServer.projectPath}'`);
this._serverManager.startServer(projectPath);
}
else {
this.logger.warn(`Language server has exceeded auto-restart limit for project '${newServer.projectPath}'`);
atom.notifications.addError(`The ${this.name} language server has exited and exceeded the restart limit for project '${newServer.projectPath}'`);
}
}
});
const configurationKey = this.getRootConfigurationKey();
if (configurationKey) {
newServer.disposable.add(atom.config.observe(configurationKey, (config) => {
const mappedConfig = this.mapConfigurationObject(config || {});
if (mappedConfig) {
connection.didChangeConfiguration({
settings: mappedConfig,
});
}
}));
}
this.startExclusiveAdapters(newServer);
return newServer;
});
}
captureServerErrors(lsProcess, projectPath) {
var _a, _b;
lsProcess.on("error", (err) => this.onSpawnError(err));
lsProcess.on("close", (code, signal) => this.onSpawnClose(code, signal));
lsProcess.on("disconnect", () => this.onSpawnDisconnect());
lsProcess.on("exit", (code, signal) => this.onSpawnExit(code, signal));
(_a = lsProcess.stderr) === null || _a === void 0 ? void 0 : _a.setEncoding("utf8");
(_b = lsProcess.stderr) === null || _b === void 0 ? void 0 : _b.on("data", (chunk) => this.onSpawnStdErrData(chunk, projectPath));
}
/**
* The function called whenever the spawned server `error`s. Extend (call super.onSpawnError) or override this if you
* need custom error handling
*/
onSpawnError(err) {
atom.notifications.addError(`${this.getServerName()} language server for ${this.getLanguageName()} unable to start`, {
dismissable: true,
description: err.toString(),
});
}
/**
* The function called whenever the spawned server `close`s. Extend (call super.onSpawnClose) or override this if you
* need custom close handling
*/
onSpawnClose(code, signal) {
if (code !== 0 && signal === null) {
atom.notifications.addError(`${this.getServerName()} language server for ${this.getLanguageName()} was closed with code: ${code}.`);
}
}
/**
* The function called whenever the spawned server `disconnect`s. Extend (call super.onSpawnDisconnect) or override
* this if you need custom disconnect handling
*/
onSpawnDisconnect() {
this.logger.debug(`${this.getServerName()} language server for ${this.getLanguageName()} got disconnected.`);
}
/**
* The function called whenever the spawned server `exit`s. Extend (call super.onSpawnExit) or override this if you
* need custom exit handling
*/
onSpawnExit(code, signal) {
this.logger.debug(`exit: code ${code} signal ${signal}`);
}
/** (Optional) Finds the project path. If there is a custom logic for finding projects override this method. */
determineProjectPath(textEditor) {
const filePath = textEditor.getPath();
// TODO can filePath be null
if (filePath === null || filePath === undefined) {
return null;
}
const projectPath = this._serverManager.getNormalizedProjectPaths().find((d) => filePath.startsWith(d));
if (projectPath !== undefined) {
return projectPath;
}
const serverWithClaim = this._serverManager
.getActiveServers()
.find((server) => { var _a; return (_a = server.additionalPaths) === null || _a === void 0 ? void 0 : _a.has(path.dirname(filePath)); });
if (serverWithClaim !== undefined) {
return server_manager_js_1.normalizePath(serverWithClaim.projectPath);
}
return null;
}
/**
* The function called whenever the spawned server returns `data` in `stderr` Extend (call super.onSpawnStdErrData) or
* override this if you need custom stderr data handling
*/
onSpawnStdErrData(chunk, projectPath) {
const errorString = chunk.toString();
this.handleServerStderr(errorString, projectPath);
// Keep the last 5 lines for packages to use in messages
this.processStdErr = (this.processStdErr + errorString).split("\n").slice(-5).join("\n");
}
/** Creates the RPC connection which can be ipc, socket or stdio */
createRpcConnection(lsProcess) {
let reader;
let writer;
const connectionType = this.getConnectionType();
switch (connectionType) {
case "ipc":
reader = new rpcNode.IPCMessageReader(lsProcess);
writer = new rpcNode.IPCMessageWriter(lsProcess);
break;
case "socket":
reader = new rpcNode.SocketMessageReader(this.socket);
writer = new rpcNode.SocketMessageWriter(this.socket);
break;
case "stdio":
if (lsProcess.stdin !== null && lsProcess.stdout !== null) {
reader = new rpcNode.StreamMessageReader(lsProcess.stdout);
writer = new rpcNode.StreamMessageWriter(lsProcess.stdin);
}
else {
this.logger.error(`The language server process for ${this.getLanguageName()} does not have a valid stdin and stdout`);
return Utils.assertUnreachable("stdio");
}
break;
default:
return Utils.assertUnreachable(connectionType);
}
return rpc.createMessageConnection(reader, writer, {
log: (..._args) => { },
warn: (..._args) => { },
info: (..._args) => { },
error: (...args) => {
this.logger.error(args);
},
});
}
/** Start adapters that are not shared between servers */
startExclusiveAdapters(server) {
apply_edit_adapter_1.default.attach(server.connection);
notifications_adapter_1.default.attach(server.connection, this.name, server.projectPath);
if (document_sync_adapter_1.default.canAdapt(server.capabilities)) {
const docSyncAdapter = new document_sync_adapter_1.default(server.connection, (editor) => this.shouldSyncForEditor(editor, server.projectPath), server.capabilities.textDocumentSync, this.reportBusyWhile, (editor) => this.getLanguageIdFromEditor(editor));
server.disposable.add(docSyncAdapter);
}
const linterPushV2 = new linter_push_v2_adapter_1.default(server.connection);
if (this._linterDelegate != null) {
linterPushV2.attach(this._linterDelegate);
}
server.disposable.add(linterPushV2);
const loggingConsole = new logging_console_adapter_1.default(server.connection);
if (this._consoleDelegate != null) {
loggingConsole.attach(this._consoleDelegate({ id: this.name, name: this.getLanguageName() }));
}
server.disposable.add(loggingConsole);
let signatureHelpAdapter;
if (signature_help_adapter_1.default.canAdapt(server.capabilities)) {
signatureHelpAdapter = new signature_help_adapter_1.default(server, this.getGrammarScopes());
if (this._signatureHelpRegistry != null) {
signatureHelpAdapter.attach(this._signatureHelpRegistry);
}
server.disposable.add(signatureHelpAdapter);
}
this._serverAdapters.set(server, {
linterPushV2,
loggingConsole,
signatureHelpAdapter,
});
ShowDocumentAdapter.attach(server.connection);
server.connection.onWorkspaceFolders(() => this._serverManager.getWorkspaceFolders());
}
shouldSyncForEditor(editor, projectPath) {
return this.isFileInProject(editor, projectPath) && this.shouldStartForEditor(editor);
}
isFileInProject(editor, projectPath) {
return (editor.getPath() || "").startsWith(projectPath);
}
// Autocomplete+ via LS completion---------------------------------------
/**
* A method to override to return an array of grammar scopes that should not be used for autocompletion.
*
* Usually that's used for disabling autocomplete inside comments,
*
* @example If the grammar scopes are [ '.source.js' ], `getAutocompleteDisabledScopes` may return [ '.source.js .comment' ].
*/
getAutocompleteDisabledScopes() {
return [];
}
provideAutocomplete() {
return {
selector: this.getGrammarScopes()
.map((g) => autocomplete_adapter_1.grammarScopeToAutoCompleteSelector(g))
.join(", "),
disableForSelector: this.getAutocompleteDisabledScopes()
.map((g) => autocomplete_adapter_1.grammarScopeToAutoCompleteSelector(g))
.join(", "),
inclusionPriority: 1,
suggestionPriority: 2,
excludeLowerPriority: false,
filterSuggestions: true,
getSuggestions: this.getSuggestions.bind(this),
onDidInsertSuggestion: (event) => {
autocomplete_adapter_1.default.applyAdditionalTextEdits(event);
this.onDidInsertSuggestion(event);
},
getSuggestionDetailsOnSelect: this.getSuggestionDetailsOnSelect.bind(this),
};
}
getSuggestions(request) {
return __awaiter(this, void 0, void 0, function* () {
const server = yield this._serverManager.getServer(request.editor);
if (server == null || !autocomplete_adapter_1.default.canAdapt(server.capabilities)) {
return [];
}
this.autoComplete = this.autoComplete || new autocomplete_adapter_1.default();
this._lastAutocompleteRequest = request;
return this.autoComplete.getSuggestions(server, request, this.onDidConvertAutocomplete, atom.config.get("autocomplete-plus.minimumWordLength"));
});
}
getSuggestionDetailsOnSelect(suggestion) {
return __awaiter(this, void 0, void 0, function* () {
const request = this._lastAutocompleteRequest;
if (request == null) {
return null;
}
const server = yield this._serverManager.getServer(request.editor);
if (server == null || !autocomplete_adapter_1.default.canResolve(server.capabilities) || this.autoComplete == null) {
return null;
}
return this.autoComplete.completeSuggestion(server, suggestion, request, this.onDidConvertAutocomplete);
});
}
onDidConvertAutocomplete(_completionItem, _suggestion, _request) { }
onDidInsertSuggestion(_arg) { }
// Definitions via LS documentHighlight and gotoDefinition------------
provideDefinitions() {
return {
name: this.name,
priority: 20,
grammarScopes: this.getGrammarScopes(),
wordRegExp: null,
getDefinition: this.getDefinition.bind(this),
};
}
getDefinition(editor, point) {
return __awaiter(this, void 0, void 0, function* () {
const server = yield this._serverManager.getServer(editor);
if (server == null || !definition_adapter_1.default.canAdapt(server.capabilities)) {
return null;
}
this.definitions = this.definitions || new definition_adapter_1.default();
const query = yield this.definitions.getDefinition(server.connection, server.capabilities, this.getLanguageName(), editor, point);
if (query !== null && server.additionalPaths !== undefined) {
// populate additionalPaths based on definitions
// Indicates that the language server can support LSP functionality for out of project files indicated by `textDocument/definition` responses.
for (const def of query.definitions) {
server_manager_js_1.considerAdditionalPath(server, path.dirname(def.path));
}
}
return query;
});
}
// Outline View via LS documentSymbol---------------------------------
provideOutlines() {
return {
name: this.name,
grammarScopes: this.getGrammarScopes(),
priority: 1,
getOutline: this.getOutline.bind(this),
};
}
getOutline(editor) {
return __awaiter(this, void 0, void 0, function* () {
const server = yield this._serverManager.getServer(editor);
if (server == null || !outline_view_adapter_1.default.canAdapt(server.capabilities)) {
return null;
}
this.outlineView = this.outlineView || new outline_view_adapter_1.default();
return this.outlineView.getOutline(server.connection, editor);
});
}
// Call Hierarchy View via LS callHierarchy---------------------------------
provideCallHierarchy() {
return {
name: this.name,
grammarScopes: this.getGrammarScopes(),
priority: 1,
getIncomingCallHierarchy: this.getIncomingCallHierarchy.bind(this),
getOutgoingCallHierarchy: this.getOutgoingCallHierarchy.bind(this),
};
}
getIncomingCallHierarchy(editor, point) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const server = yield this._serverManager.getServer(editor);
if (server === null || !CallHierarchyAdapter.canAdapt(server.capabilities)) {
return null;
}
this.callHierarchy = (_a = this.callHierarchy) !== null && _a !== void 0 ? _a : CallHierarchyAdapter;
return this.callHierarchy.getCallHierarchy(server.connection, editor, point, "incoming");
});
}
getOutgoingCallHierarchy(editor, point) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const server = yield this._serverManager.getServer(editor);
if (server === null || !CallHierarchyAdapter.canAdapt(server.capabilities)) {
return null;
}
this.callHierarchy = (_a = this.callHierarchy) !== null && _a !== void 0 ? _a : CallHierarchyAdapter;
return this.callHierarchy.getCallHierarchy(server.connection, editor, point, "outgoing");
});
}
// Linter push v2 API via LS publishDiagnostics
consumeLinterV2(registerIndie) {
this._linterDelegate = registerIndie({ name: this.name });
if (this._linterDelegate == null) {
return;
}
for (const server of this._serverManager.getActiveServers()) {
const linterPushV2 = this.getServerAdapter(server, "linterPushV2");
if (linterPushV2 != null) {
linterPushV2.attach(this._linterDelegate);
}
}
}
// Find References via LS findReferences------------------------------
provideFindReferences() {
return {
isEditorSupported: (editor) => this.getGrammarScopes().includes(editor.getGrammar().scopeName),
findReferences: this.getReferences.bind(this),
};
}
getReferences(editor, point) {
return __awaiter(this, void 0, void 0, function* () {
const server = yield this._serverManager.getServer(editor);
if (server == null || !find_references_adapter_1.default.canAdapt(server.capabilities)) {
return null;
}
this.findReferences = this.findReferences || new find_references_adapter_1.default();
return this.findReferences.getReferences(server.connection, editor, point, server.projectPath);
});
}
// Datatip via LS textDocument/hover----------------------------------
consumeDatatip(service) {
this._disposable.add(service.addProvider({
providerName: this.name,
priority: 1,
grammarScopes: this.getGrammarScopes(),
validForScope: (scopeName) => {
return this.getGrammarScopes().includes(scopeName);
},
datatip: this.getDatatip.bind(this),
}));
}
getDatatip(editor, point) {
return __awaiter(this, void 0, void 0, function* () {
const server = yield this._serverManager.getServer(editor);
if (server == null || !datatip_adapter_1.default.canAdapt(server.capabilities)) {
return null;
}
this.datatip = this.datatip || new datatip_adapter_1.default();
return this.datatip.getDatatip(server.connection, editor, point);
});
}
// Console via LS logging---------------------------------------------
consumeConsole(createConsole) {
this._consoleDelegate = createConsole;
for (const server of this._serverManager.getActiveServers()) {
const loggingConsole = this.getServerAdapter(server, "loggingConsole");
if (loggingConsole) {
loggingConsole.attach(this._consoleDelegate({ id: this.name, name: this.getLanguageName() }));
}
}
// No way of detaching from client connections today
return new atom_1.Disposable(() => { });
}
// Code Format via LS formatDocument & formatDocumentRange------------
provideCodeFormat() {
return {
grammarScopes: this.getGrammarScopes(),
priority: 1,
formatCode: this.getCodeFormat.bind(this),
};
}
getCodeFormat(editor, range) {
return __awaiter(this, void 0, void 0, function* () {
const server = yield this._serverManager.getServer(editor);
if (server == null || !code_format_adapter_1.default.canAdapt(server.capabilities)) {
return [];
}
return code_format_adapter_1.default.format(server.connection, server.capabilities, editor, range);
});
}
provideRangeCodeFormat() {
return {
grammarScopes: this.getGrammarScopes(),
priority: 1,
formatCode: this.getRangeCodeFormat.bind(this),
};
}
getRangeCodeFormat(editor, range) {
return __awaiter(this, void 0, void 0, function* () {
const server = yield this._serverManager.getServer(editor);
if (server == null || !server.capabilities.documentRangeFormattingProvider) {
return [];
}
return code_format_adapter_1.default.formatRange(server.connection, editor, range);
});
}
provideFileCodeFormat() {
return {
grammarScopes: this.getGrammarScopes(),
priority: 1,
formatEntireFile: this.getFileCodeFormat.bind(this),
};
}
provideOnSaveCodeFormat() {
return {
grammarScopes: this.getGrammarScopes(),
priority: 1,
formatOnSave: this.getFileCodeFormat.bind(this),
};
}
getFileCodeFormat(editor) {
return __awaiter(this, void 0, void 0, function* () {
const server = yield this._serverManager.getServer(editor);
if (server == null || !server.capabilities.documentFormattingProvider) {
return [];
}
return code_format_adapter_1.default.formatDocument(server.connection, editor);
});
}
provideOnTypeCodeFormat() {
return {
grammarScopes: this.getGrammarScopes(),
priority: 1,
formatAtPosition: this.getOnTypeCodeFormat.bind(this),
};
}
getOnTypeCodeFormat(editor, point, character) {
return __awaiter(this, void 0, void 0, function* () {
const server = yield this._serverManager.getServer(editor);
if (server == null || !server.capabilities.documentOnTypeFormattingProvider) {
return [];
}
return code_format_adapter_1.default.formatOnType(server.connection, editor, point, character);
});
}
provideCodeHighlight() {
return {
grammarScopes: this.getGrammarScopes(),
priority: 1,
highlight: (editor, position) => {
return this.getCodeHighlight(editor, position);
},
};
}
getCodeHighlight(editor, position) {
return __awaiter(this, void 0, void 0, function* () {
const server = yield this._serverManager.getServer(editor);
if (server == null || !code_highlight_adapter_1.default.canAdapt(server.capabilities)) {
return null;
}
return code_highlight_adapter_1.default.highlight(server.connection, server.capabilities, editor, position);
});
}
provideCodeActions() {
return {
grammarScopes: this.getGrammarScopes(),
priority: 1,
getCodeActions: (editor, range, diagnostics) => {
return this.getCodeActions(editor, range, diagnostics);
},
};
}
getCodeActions(editor, range, diagnostics) {
return __awaiter(this, void 0, void 0, function* () {
const server = yield this._serverManager.getServer(editor);
if (server == null || !code_action_adapter_1.default.canAdapt(server.capabilities)) {
return null;
}
return code_action_adapter_1.default.getCodeActions(server.connection, server.capabilities, this.getServerAdapter(server, "linterPushV2"), editor, range, diagnostics, this.filterCodeActions.bind(this), this.onApplyCodeActions.bind(this));
});
}
/** Optionally filter code action before they're displayed */
filterCodeActions(actions) {
return actions;
}
/**
* Optionally handle a code action before default handling. Return `false` to prevent default handling, `true` to
* continue with default handling.
*/
onApplyCodeActions(_action) {
return __awaiter(this, void 0, void 0, function* () {
return true;
});
}
provideRefactor() {
return {
grammarScopes: this.getGrammarScopes(),
priority: 1,
rename: this.getRename.bind(this),
};
}
getRename(editor, position, newName) {
return __awaiter(this, void 0, void 0, function* () {
const server = yield this._serverManager.getServer(editor);
if (server == null || !rename_adapter_1.default.canAdapt(server.capabilities)) {
return null;
}
return rename_adapter_1.default.getRename(server.connection, editor, position, newName);
});
}
consumeSignatureHelp(registry) {
this._signatureHelpRegistry = registry;
for (const server of this._serverManager.getActiveServers()) {
const signatureHelpAdapter = this.getServerAdapter(server, "signatureHelpAdapter");
if (signatureHelpAdapter != null) {
signatureHelpAdapter.attach(registry);
}
}
return new atom_1.Disposable(() => {
this._signatureHelpRegistry = undefined;
});
}
consumeBusySignal(service) {
this.busySignalService = service;
return new atom_1.Disposable(() => delete this.busySignalService);
}
/**
* `didChangeWatchedFiles` message filtering, override for custom logic.
*
* @param filePath Path of a file that has changed in the project path
* @returns `false` => message will not be sent to the language server
*/
filterChangeWatchedFiles(_filePath) {
return true;
}
/**
* Called on language server stderr output.
*
* @param stderr A chunk of stderr from a language server instance
*/
handleServerStderr(stderr, _projectPath) {
stderr
.split("\n")
.filter((l) => l)
.forEach((line) => this.logger.warn(`stderr ${line}`));
}
getServerAdapter(server, adapter) {
const adapters = this._serverAdapters.get(server);
return adapters && adapters[adapter];
}
}
exports.default = AutoLanguageClient;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0by1sYW5ndWFnZWNsaWVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL2xpYi9hdXRvLWxhbmd1YWdlY2xpZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7OztBQUFBLG9DQUFtQztBQUVuQyxzQ0FBcUM7QUFDckMsK0NBQThDO0FBQzlDLDZCQUE0QjtBQUc1Qiw2Q0FBa0M7QUFDbEMsc0VBQTREO0FBQzVELDBFQUF5RztBQUN6RywwRUFBeUU7QUFDekUsd0VBQThEO0FBQzlELHdFQUE4RDtBQUM5RCw4RUFBb0U7QUFDcEUsZ0VBQXVEO0FBQ3ZELHNFQUE2RDtBQUM3RCw0RUFBa0U7QUFDbEUsZ0ZBQXNFO0FBQ3RFLDhFQUFtRTtBQUNuRSxnRkFBc0U7QUFDdEUsNEVBQW1FO0FBQ25FLDBFQUFnRTtBQUNoRSw4REFBcUQ7QUFDckQsOEVBQW9FO0FBQ3BFLHdFQUF1RTtBQUN2RSxpQ0FBZ0M7QUFFaEMscURBQTJEO0FBYXBDLHlHQWJkLHlDQUF3QixPQWFjO0FBWi9DLHFDQUFnRTtBQUNoRSwyREFNNEI7QUFDNUIsK0JBQWdGO0FBRWhGLCtCQUErQjtBQVcvQjs7Ozs7Ozs7R0FRRztBQUNILE1BQXFCLGtCQUFrQjtJQUF2QztRQU9VLG9CQUFlLEdBQVksS0FBSyxDQUFBO1FBQ2hDLG9CQUFlLEdBQUcsSUFBSSxPQUFPLEVBQWdDLENBQUE7UUFLM0Qsa0JBQWEsR0FBVyxFQUFFLENBQUE7UUE2K0JwQzs7O1dBR0c7UUFDTyx1QkFBa0IsR0FBWSxJQUFJLENBQUE7UUFzQmxDLG9CQUFlLEdBQTBCLENBQU8sS0FBSyxFQUFFLENBQUMsRUFBRSxFQUFFO1lBQ3BFLElBQUksSUFBSSxDQUFDLGlCQUFpQixFQUFFO2dCQUMxQixPQUFPLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxlQUFlLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFBO2FBQ3hEO2lCQUFNO2dCQUNMLE9BQU8sSUFBSSxDQUFDLHNCQUFzQixDQUFDLEtBQUssRUFBRSxDQUFDLENBQUMsQ0FBQTthQUM3QztRQUNILENBQUMsQ0FBQSxDQUFBO1FBRVMsMkJBQXNCLEdBQTBCLENBQU8sS0FBSyxFQUFFLENBQUMsRUFBRSxFQUFFO1lBQzNFLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLGFBQWEsS0FBSyxFQUFFLENBQUMsQ0FBQTtZQUN0QyxJQUFJLEdBQUcsQ0FBQTtZQUNQLElBQUk7Z0JBQ0YsR0FBRyxHQUFHLE1BQU0sQ0FBQyxFQUFFLENBQUE7YUFDaEI7b0JBQVM7Z0JBQ1IsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsY0FBYyxLQUFLLEVBQUUsQ0FBQyxDQUFBO2FBQ3hDO1lBQ0QsT0FBTyxHQUFHLENBQUE7UUFDWixDQUFDLENBQUEsQ0FBQTtJQUNILENBQUM7SUE1Z0NDLGdGQUFnRjtJQUNoRiw0RUFBNEU7SUFFNUUsNkVBQTZFO0lBQ25FLGdCQUFnQjtRQUN4QixNQUFNLEtBQUssQ0FBQyxtRUFBbUUsQ0FBQyxDQUFBO0lBQ2xGLENBQUM7SUFFRCxxRUFBcUU7SUFDM0QsZUFBZTtRQUN2QixNQUFNLEtBQUssQ0FBQyxrRUFBa0UsQ0FBQyxDQUFBO0lBQ2pGLENBQUM7SUFFRCx5REFBeUQ7SUFDL0MsYUFBYTtRQUNyQixNQUFNLEtBQUssQ0FBQyxnRUFBZ0UsQ0FBQyxDQUFBO0lBQy9FLENBQUM7SUFFRCxnQ0FBZ0M7SUFDdEIsa0JBQWtCLENBQUMsWUFBb0I7UUFDL0MsTUFBTSxLQUFLLENBQUMscUdBQXFHLENBQUMsQ0FBQTtJQUNwSCxDQUFDO0lBRUQsMERBQTBEO0lBQzFELDhFQUE4RTtJQUU5RSx3R0FBd0c7SUFDOUYsb0JBQW9CLENBQUMsTUFBa0I7UUFDL0MsT0FBTyxJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLFVBQVUsRUFBRSxDQUFDLFNBQVMsQ0FBQyxDQUFBO0lBQ3hFLENBQUM7SUFFRCx5R0FBeUc7SUFDL0YsbUJBQW1CLENBQUMsV0FBbUIsRUFBRSxTQUFnQztRQUNqRixNQUFNLE9BQU8sR0FBRyxvQkFBTyxDQUFDLFNBQVMsQ0FBQyxXQUFXLENBQUMsQ0FBQTtRQUM5QyxPQUFPO1lBQ0wsU0FBUyxFQUFFLFNBQVMsQ0FBQyxHQUFHLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxJQUFJO1lBQzdELFFBQVEsRUFBRSxXQUFXO1lBQ3JCLE9BQU87WUFDUCxNQUFNLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsa0JBQWtCLENBQUMsSUFBSSxJQUFJO1lBQ25ELGdCQUFnQixFQUFFLENBQUMsRUFBRSxHQUFHLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxlQUFRLENBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQztZQUNqRSw4QkFBOEI7WUFDOUIsc0dBQXNHO1lBQ3RHLFlBQVksRUFBRTtnQkFDWixTQUFTLEVBQUU7b0JBQ1QsU0FBUyxFQUFFLElBQUk7b0JBQ2YsYUFBYSxFQUFFLEtBQUs7b0JBQ3BCLGFBQWEsRUFBRTt3QkFDYixlQUFlLEVBQUUsSUFBSTt3QkFDckIscUJBQXFCLEVBQUUsS0FBSzt3QkFDNUIsdUJBQXVCLEVBQUUsU0FBUzt3QkFDbEMsa0JBQWtCLEVBQUUsQ0FBQyxRQUFRLEVBQUUsUUFBUSxFQUFFLFFBQVEsQ0FBQztxQkFDbkQ7b0JBQ0QsZ0JBQWdCLEVBQUUsSUFBSTtvQkFDdEIsc0JBQXNCLEVBQUU7d0JBQ3RCLG1CQUFtQixFQUFFLEtBQUs7cUJBQzNCO29CQUNELHFCQUFxQixFQUFFO3dCQUNyQixtQkFBbUIsRUFBRSxLQUFLO3FCQUMzQjtvQkFDRCxnQ0FBZ0M7b0JBQ2hDLE1BQU0sRUFBRTt3QkFDTixtQkFBbUIsRUFBRSxLQUFLO3FCQUMzQjtvQkFDRCxjQUFjLEVBQUU7d0JBQ2QsbUJBQW1CLEVBQUUsS0FBSztxQkFDM0I7b0JBQ0QsY0FBYyxFQUFFLFNBQVM7b0JBQ3pCLFFBQVEsRUFBRSxTQUFTO29CQUNuQixjQUFjLEVBQUU7d0JBQ2QsMEVBQTBFO3dCQUMxRSxVQUFVLEVBQUUsS0FBSzt3QkFDakIsMEVBQTBFO3dCQUMxRSxVQUFVLEVBQUUsS0FBSzt3QkFDakIsMEVBQTBFO3dCQUMxRSxVQUFVLEVBQUUsS0FBSztxQkFDbEI7aUJBQ0Y7Z0JBQ0QsWUFBWSxFQUFFO29CQUNaLGVBQWUsRUFBRTt3QkFDZixtQkFBbUIsRUFBRSxLQUFLO3dCQUMxQixRQUFRLEVBQUUsSUFBSTt3QkFDZCxpQkFBaUIsRUFBRSxJQUFJO3dCQUN2QixPQUFPLEVBQUUsSUFBSTtxQkFDZDtvQkFDRCxVQUFVLEVBQUU7d0JBQ1YsbUJBQW1CLEVBQUUsS0FBSzt3QkFDMUIsY0FBYyxFQUFFOzRCQUNkLGNBQWMsRUFBRSxJQUFJOzRCQUNwQix1QkFBdUIsRUFBRSxLQUFLOzRCQUM5QixtQkFBbUIsRUFBRSxFQUFFOzRCQUN2QixpQkFBaUIsRUFBRSxLQUFLOzRCQUN4QixnQkFBZ0IsRUFBRSxLQUFLOzRCQUN2QixVQUFVLEVBQUU7Z0NBQ1YsUUFBUSxFQUFFLEVBQUU7NkJBQ2I7NEJBQ0Qsb0JBQW9CLEVBQUUsS0FBSzs0QkFDM0IsY0FBYyxFQUFFO2dDQUNkLFVBQVUsRUFBRSxFQUFFOzZCQUNmOzRCQUNELHFCQUFxQixFQUFFO2dDQUNyQixRQUFRLEVBQUUsRUFBRTs2QkFDYjt5QkFDRjt3QkFDRCxrQkFBa0IsRUFBRTs0QkFDbEIsUUFBUSxFQUFFLEVBQUU7eUJBQ2I7d0JBQ0QsY0FBYyxFQUFFLElBQUk7cUJBQ3JCO29CQUNELEtBQUssRUFBRTt3QkFDTCxtQkFBbUIsRUFBRSxLQUFLO3FCQUMzQjtvQkFDRCxhQUFhLEVBQUU7d0JBQ2IsbUJBQW1CLEVBQUUsS0FBSztxQkFDM0I7b0JBQ0QsV0FBVyxFQUFFLFNBQVM7b0JBQ3RCLFVBQVUsRUFBRTt3QkFDVixtQkFBbUIsRUFBRSxLQUFLO3FCQUMzQjtvQkFDRCxpQkFBaUIsRUFBRTt3QkFDakIsbUJBQW1CLEVBQUUsS0FBSztxQkFDM0I7b0JBQ0QsY0FBYyxFQUFFO3dCQUNkLG1CQUFtQixFQUFFLEtBQUs7d0JBQzFCLGlDQUFpQyxFQUFFLElBQUk7cUJBQ3hDO29CQUNELFVBQVUsRUFBRTt3QkFDVixtQkFBbUIsRUFBRSxLQUFLO3FCQUMzQjtvQkFDRCxlQUFlLEVBQUU7d0JBQ2YsbUJBQW1CLEVBQUUsS0FBSztxQkFDM0I7b0JBQ0QsZ0JBQWdCLEVBQUU7d0JBQ2hCLG1CQUFtQixFQUFFLEtBQUs7cUJBQzNCO29CQUNELFVBQVUsRUFBRTt3QkFDVixtQkFBbUIsRUFBRSxLQUFLO3FCQUMzQjtvQkFDRCxVQUFVLEVBQUU7d0JBQ1YsbUJBQW1CLEVBQUUsS0FBSzt3QkFDMUIsd0JBQXdCLEVBQUU7NEJBQ3hCLGNBQWMsRUFBRTtnQ0FDZCxRQUFRLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxnQ0FBZ0M7NkJBQ2pEO3lCQUNGO3FCQUNGO29CQUNELFFBQVEsRUFBRTt3QkFDUixtQkFBbUIsRUFBRSxLQUFLO3FCQUMzQjtvQkFDRCxZQUFZLEVBQUU7d0JBQ1osbUJBQW1CLEVBQUUsS0FBSztxQkFDM0I7b0JBQ0QsTUFBTSxFQUFFO3dCQUNOLG1CQUFtQixFQUFFLEtBQUs7cUJBQzNCO29CQUNELE9BQU8sRUFBRTt3QkFDUCxtQkFBbUIsRUFBRSxLQUFLO3FCQUMzQjtvQkFDRCxrQkFBa0IsRUFBRTt3QkFDbEIsa0JBQWtCLEVBQUUsSUFBSTt3QkFDeEIsVUFBVSxFQUFFOzRCQUNWLGdHQUFnRzs0QkFDaEcsUUFBUSxFQUFFLEVBQUU7eUJBQ2I7d0JBQ0QsY0FBYyxFQUFFLEtBQUs7d0JBQ3JCLHNCQUFzQixFQUFFL