@jupyterlab/lsp
Version:
248 lines • 7.95 kB
JavaScript
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
import { ServerConnection } from '@jupyterlab/services';
import { Signal } from '@lumino/signaling';
import { ILanguageServerManager } from './tokens';
import { PromiseDelegate } from '@lumino/coreutils';
export class LanguageServerManager {
constructor(options) {
/**
* map of language server sessions.
*/
this._sessions = new Map();
/**
* Map of language server specs.
*/
this._specs = new Map();
/**
* Set of emitted warning message, message in this set will not be warned again.
*/
this._warningsEmitted = new Set();
/**
* A promise resolved when this server manager is ready.
*/
this._ready = new PromiseDelegate();
/**
* Signal emitted when a language server session is changed
*/
this._sessionsChanged = new Signal(this);
this._isDisposed = false;
/**
* Check if the manager is enabled or disabled
*/
this._enabled = true;
this._settings = options.settings || ServerConnection.makeSettings();
this._baseUrl = options.baseUrl || PageConfig.getBaseUrl();
this._retries = options.retries || 2;
this._retriesInterval = options.retriesInterval || 10000;
this._statusCode = -1;
this._configuration = {};
this.fetchSessions().catch(e => console.log(e));
}
/**
* Check if the manager is enabled or disabled
*/
get isEnabled() {
return this._enabled;
}
/**
* Check if the manager is disposed.
*/
get isDisposed() {
return this._isDisposed;
}
/**
* Get server connection settings.
*/
get settings() {
return this._settings;
}
/**
* Get the language server specs.
*/
get specs() {
return this._specs;
}
/**
* Get the status end point.
*/
get statusUrl() {
return URLExt.join(this._baseUrl, ILanguageServerManager.URL_NS, 'status');
}
/**
* Signal emitted when a language server session is changed
*/
get sessionsChanged() {
return this._sessionsChanged;
}
/**
* Get the map of language server sessions.
*/
get sessions() {
return this._sessions;
}
/**
* A promise resolved when this server manager is ready.
*/
get ready() {
return this._ready.promise;
}
/**
* Get the status code of server's responses.
*/
get statusCode() {
return this._statusCode;
}
/**
* Enable the language server services
*/
async enable() {
this._enabled = true;
await this.fetchSessions();
}
/**
* Disable the language server services
*/
disable() {
this._enabled = false;
this._sessions = new Map();
this._sessionsChanged.emit(void 0);
}
/**
* Dispose the manager.
*/
dispose() {
if (this._isDisposed) {
return;
}
this._isDisposed = true;
Signal.clearData(this);
}
/**
* Update the language server configuration.
*/
setConfiguration(configuration) {
this._configuration = configuration;
}
/**
* Get matching language server for input language option.
*/
getMatchingServers(options) {
if (!options.language) {
console.error('Cannot match server by language: language not available; ensure that kernel and specs provide language and MIME type');
return [];
}
const matchingSessionsKeys = [];
for (const [key, session] of this._sessions.entries()) {
if (this.isMatchingSpec(options, session.spec)) {
matchingSessionsKeys.push(key);
}
}
return matchingSessionsKeys.sort(this.compareRanks.bind(this));
}
/**
* Get matching language server spec for input language option.
*/
getMatchingSpecs(options) {
const result = new Map();
for (const [key, specification] of this._specs.entries()) {
if (this.isMatchingSpec(options, specification)) {
result.set(key, specification);
}
}
return result;
}
/**
* Fetch the server session list from the status endpoint. The server
* manager is ready once this method finishes.
*/
async fetchSessions() {
if (!this._enabled) {
return;
}
let response = await ServerConnection.makeRequest(this.statusUrl, { method: 'GET' }, this._settings);
this._statusCode = response.status;
if (!response.ok) {
if (this._retries > 0) {
this._retries -= 1;
setTimeout(this.fetchSessions.bind(this), this._retriesInterval);
}
else {
this._ready.resolve(undefined);
console.log('Missing jupyter_lsp server extension, skipping.');
}
return;
}
let sessions;
try {
const data = await response.json();
sessions = data.sessions;
try {
this.version = data.version;
this._specs = new Map(Object.entries(data.specs));
}
catch (err) {
console.warn(err);
}
}
catch (err) {
console.warn(err);
this._ready.resolve(undefined);
return;
}
for (let key of Object.keys(sessions)) {
let id = key;
if (this._sessions.has(id)) {
Object.assign(this._sessions.get(id) || {}, sessions[key]);
}
else {
this._sessions.set(id, sessions[key]);
}
}
const oldKeys = this._sessions.keys();
for (const oldKey in oldKeys) {
if (!sessions[oldKey]) {
let oldId = oldKey;
this._sessions.delete(oldId);
}
}
this._sessionsChanged.emit(void 0);
this._ready.resolve(undefined);
}
/**
* Check if input language option maths the language server spec.
*/
isMatchingSpec(options, spec) {
// most things speak language
// if language is not known, it is guessed based on MIME type earlier
// so some language should be available by now (which can be not so obvious, e.g. "plain" for txt documents)
const lowerCaseLanguage = options.language.toLocaleLowerCase();
return spec.languages.some((language) => language.toLocaleLowerCase() == lowerCaseLanguage);
}
/**
* Helper function to warn a message only once.
*/
warnOnce(arg) {
if (!this._warningsEmitted.has(arg)) {
this._warningsEmitted.add(arg);
console.warn(arg);
}
}
/**
* Compare the rank of two servers with the same language.
*/
compareRanks(a, b) {
var _a, _b, _c, _d;
const DEFAULT_RANK = 50;
const aRank = (_b = (_a = this._configuration[a]) === null || _a === void 0 ? void 0 : _a.rank) !== null && _b !== void 0 ? _b : DEFAULT_RANK;
const bRank = (_d = (_c = this._configuration[b]) === null || _c === void 0 ? void 0 : _c.rank) !== null && _d !== void 0 ? _d : DEFAULT_RANK;
if (aRank == bRank) {
this.warnOnce(`Two matching servers: ${a} and ${b} have the same rank; choose which one to use by changing the rank in Advanced Settings Editor`);
return a.localeCompare(b);
}
// higher rank = higher in the list (descending order)
return bRank - aRank;
}
}
//# sourceMappingURL=manager.js.map