Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use language server implementation instead of language for URLs #199

Merged
merged 18 commits into from
Feb 22, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion atest/01_Editor.robot
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ CSS

Docker
${def} = Set Variable xpath://span[contains(@class, 'cm-string')][contains(text(), 'PLANET')]
Editor Shows Features for Language Docker Dockerfile Diagnostics=Instruction has no arguments Jump to Definition=${def} Rename=${def}
Wait Until Keyword Succeeds 3x 100ms Editor Shows Features for Language Docker Dockerfile Diagnostics=Instruction has no arguments Jump to Definition=${def} Rename=${def}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this "fixes" the problem, but the fact remains that you don't reliably get diagnostics back from a dockerfile on the first file opened until you change the file (then it seems to work every time). I tried sending more change events, but it didn't have an effect. I'm willing to deal with it if it makes the tests pass for now.


JS
${def} = Set Variable xpath:(//span[contains(@class, 'cm-variable')][contains(text(), 'fib')])[last()]
Expand Down
8 changes: 4 additions & 4 deletions docs/EXTENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ from jupyter_lsp import lsp_message_listener
def load_jupyter_server_extension(nbapp):

@lsp_message_listener("all")
async def my_listener(scope, message, languages, manager):
print("received a {} {} message about {}".format(
scope, message["method"], languages
async def my_listener(scope, message, language_server, manager):
print("received a {} {} message from {}".format(
scope, message["method"], language_server
))
```

Expand All @@ -95,5 +95,5 @@ def load_jupyter_server_extension(nbapp):
Fine-grained controls are available as part of the Python API. Pass these as
named arguments to `lsp_message_listener`.

- `languages`: a regular expression of languages
- `language_server`: a regular expression of language servers
- `method`: a regular expression of LSP JSON-RPC method names
26 changes: 26 additions & 0 deletions packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { ICellModel } from '@jupyterlab/cells';
import createNotebook = NBTestUtils.createNotebook;
import { CodeMirrorAdapter } from './cm_adapter';
import { VirtualDocument } from '../../virtual/document';
import { LanguageServerManager } from '../../manager';
import { DocumentConnectionManager } from '../../connection_manager';

interface IFeatureTestEnvironment {
host: HTMLElement;
Expand All @@ -30,6 +32,18 @@ interface IFeatureTestEnvironment {
dispose(): void;
}

export class MockLanguageServerManager extends LanguageServerManager {
async fetchSessions() {
this._sessions = new Map();
this._sessions.set('pyls', {
spec: {
languages: ['python']
}
} as any);
this._sessionsChanged.emit(void 0);
}
}

export abstract class FeatureTestEnvironment
implements IFeatureTestEnvironment {
host: HTMLElement;
Expand Down Expand Up @@ -129,6 +143,18 @@ export class FileEditorFeatureTestEnvironment extends FeatureTestEnvironment {
host: this.host,
model
});

const LANGSERVER_MANAGER = new MockLanguageServerManager({});
const CONNECTION_MANAGER = new DocumentConnectionManager({
language_server_manager: LANGSERVER_MANAGER
});

const DEBUG = false;

if (DEBUG) {
console.log(CONNECTION_MANAGER);
}

this.init();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { DefaultIconReact } from '@jupyterlab/ui-components';
import { JupyterLabWidgetAdapter } from '../jl_adapter';
import { collect_documents, VirtualDocument } from '../../../virtual/document';
import { LSPConnection } from '../../../connection';
import { PageConfig } from '@jupyterlab/coreutils';
import { DocumentConnectionManager } from '../../../connection_manager';
import { ILanguageServerManager } from '../../../tokens';

interface IServerStatusProps {
server: SCHEMA.LanguageServerSession;
Expand Down Expand Up @@ -313,32 +313,11 @@ export namespace LSPStatus {
*/
export class Model extends VDomModel {
server_extension_status: SCHEMA.ServersResponse = null;
language_server_manager: ILanguageServerManager;
private _connection_manager: DocumentConnectionManager;

constructor() {
super();

// PathExt.join skips on of the slashes in https://
let url = PageConfig.getBaseUrl() + 'lsp';
fetch(url)
.then(response => {
// TODO: retry a few times
if (!response.ok) {
throw new Error(response.statusText);
}
response
.json()
.then(
(data: SCHEMA.ServersResponse) =>
(this.server_extension_status = data)
)
.catch(console.warn);
})
.catch(console.error);
}

get available_servers(): Array<SCHEMA.LanguageServerSession> {
return this.server_extension_status.sessions;
return Array.from(this.language_server_manager.sessions.values());
}

get supported_languages(): Set<string> {
Expand Down
45 changes: 39 additions & 6 deletions packages/jupyterlab-lsp/src/connection_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { sleep, until_ready } from './utils';

// Name-only import so as to not trigger inclusion in main bundle
import * as ConnectionModuleType from './connection';
import { TLanguageServerId, ILanguageServerManager } from './tokens';

export interface IDocumentConnectionData {
virtual_document: VirtualDocument;
Expand Down Expand Up @@ -49,9 +50,10 @@ export class DocumentConnectionManager {
DocumentConnectionManager,
Map<VirtualDocument.id_path, VirtualDocument>
>;
language_server_manager: ILanguageServerManager;
private ignored_languages: Set<string>;

constructor() {
constructor(options: DocumentConnectionManager.IOptions) {
this.connections = new Map();
this.documents = new Map();
this.ignored_languages = new Set();
Expand All @@ -60,6 +62,8 @@ export class DocumentConnectionManager {
this.disconnected = new Signal(this);
this.closed = new Signal(this);
this.documents_changed = new Signal(this);
this.language_server_manager = options.language_server_manager;
Private.setLanguageServerManager(options.language_server_manager);
}

connect_document_signals(virtual_document: VirtualDocument) {
Expand Down Expand Up @@ -123,11 +127,16 @@ export class DocumentConnectionManager {
language
);

const language_server_id = this.language_server_manager.getServerId({
language
});

// lazily load 1) the underlying library (1.5mb) and/or 2) a live WebSocket-
// like connection: either already connected or potentiailly in the process
// of connecting.
const connection = await Private.connection(
language,
language_server_id,
uris,
this.on_new_connection
);
Expand Down Expand Up @@ -273,6 +282,10 @@ export class DocumentConnectionManager {
}

export namespace DocumentConnectionManager {
export interface IOptions {
language_server_manager: ILanguageServerManager;
}

export function solve_uris(
virtual_document: VirtualDocument,
language: string
Expand All @@ -285,11 +298,20 @@ export namespace DocumentConnectionManager {
? rootUri
: virtualDocumentsUri;

const language_server_id = Private.getLanguageServerManager().getServerId({
language
});

return {
base: baseUri,
document: URLExt.join(baseUri, virtual_document.uri),
server: URLExt.join('ws://jupyter-lsp', language),
socket: URLExt.join(wsBase, 'lsp', language)
socket: URLExt.join(
wsBase,
ILanguageServerManager.URL_NS,
'ws',
language_server_id
)
};
}

Expand All @@ -305,14 +327,25 @@ export namespace DocumentConnectionManager {
* Namespace primarily for language-keyed cache of LSPConnections
*/
namespace Private {
const _connections = new Map<string, LSPConnection>();
const _connections: Map<TLanguageServerId, LSPConnection> = new Map();
let _promise: Promise<typeof ConnectionModuleType>;
let _language_server_manager: ILanguageServerManager;

export function getLanguageServerManager() {
return _language_server_manager;
}
export function setLanguageServerManager(
language_server_manager: ILanguageServerManager
) {
_language_server_manager = language_server_manager;
}

/**
* Return (or create and initialize) the WebSocket associated with the language
*/
export async function connection(
language: string,
language_server_id: TLanguageServerId,
uris: DocumentConnectionManager.IURIs,
onCreate: (connection: LSPConnection) => void
): Promise<LSPConnection> {
Expand All @@ -325,7 +358,7 @@ namespace Private {
}

const { LSPConnection } = await _promise;
let connection = _connections.get(language);
let connection = _connections.get(language_server_id);

if (connection == null) {
const socket = new WebSocket(uris.socket);
Expand All @@ -336,12 +369,12 @@ namespace Private {
});
// TODO: remove remaining unbounded users of connection.on
connection.setMaxListeners(999);
_connections.set(language, connection);
_connections.set(language_server_id, connection);
connection.connect(socket);
onCreate(connection);
}

connection = _connections.get(language);
connection = _connections.get(language_server_id);

return connection;
}
Expand Down
8 changes: 7 additions & 1 deletion packages/jupyterlab-lsp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { FileEditor, IEditorTracker } from '@jupyterlab/fileeditor';
import { ISettingRegistry } from '@jupyterlab/coreutils';
import { IDocumentManager } from '@jupyterlab/docmanager';

import { LanguageServerManager } from './manager';

import { FileEditorJumper } from '@krassowski/jupyterlab_go_to_definition/lib/jumpers/fileeditor';
import { NotebookJumper } from '@krassowski/jupyterlab_go_to_definition/lib/jumpers/notebook';

Expand Down Expand Up @@ -73,9 +75,13 @@ const plugin: JupyterFrontEndPlugin<void> = {
labShell: ILabShell,
status_bar: IStatusBar
) => {
const connection_manager = new DocumentConnectionManager();
const language_server_manager = new LanguageServerManager({});
const connection_manager = new DocumentConnectionManager({
language_server_manager
});

const status_bar_item = new LSPStatus();
status_bar_item.model.language_server_manager = language_server_manager;
status_bar_item.model.connection_manager = connection_manager;

labShell.currentChanged.connect(() => {
Expand Down
86 changes: 86 additions & 0 deletions packages/jupyterlab-lsp/src/manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { Signal } from '@phosphor/signaling';

import { PageConfig, URLExt } from '@jupyterlab/coreutils';
import { ServerConnection } from '@jupyterlab/services';

import { ILanguageServerManager, TSessionMap } from './tokens';
import * as SCHEMA from './_schema';

export class LanguageServerManager implements ILanguageServerManager {
protected _sessionsChanged: Signal<ILanguageServerManager, void> = new Signal<
ILanguageServerManager,
void
>(this);
protected _sessions: TSessionMap = new Map();
private _settings: ServerConnection.ISettings;
private _baseUrl: string;

constructor(options: ILanguageServerManager.IOptions) {
this._settings = options.settings || ServerConnection.makeSettings();
this._baseUrl = options.baseUrl || PageConfig.getBaseUrl();
this.fetchSessions().catch(console.warn);
}

get statusUrl() {
return URLExt.join(this._baseUrl, ILanguageServerManager.URL_NS, 'status');
}

get sessionsChanged() {
return this._sessionsChanged;
}

get sessions(): TSessionMap {
return this._sessions;
}

getServerId(options: ILanguageServerManager.IGetServerIdOptions) {
// most things speak language
for (const [key, session] of this._sessions.entries()) {
if (options.language) {
if (session.spec.languages.indexOf(options.language) !== -1) {
return key;
}
}
}
return null;
}

async fetchSessions() {
let response = await ServerConnection.makeRequest(
this.statusUrl,
{ method: 'GET' },
this._settings
);

if (!response.ok) {
throw new Error(response.statusText);
}

let sessions: SCHEMA.Sessions;

try {
sessions = (await response.json()).sessions;
} catch (err) {
console.warn(err);
return;
}

for (const key of Object.keys(sessions)) {
if (this._sessions.has(key)) {
Object.assign(this._sessions.get(key), sessions[key]);
} else {
this._sessions.set(key, sessions[key]);
}
}

const oldKeys = this._sessions.keys();

for (const oldKey in oldKeys) {
if (!sessions[oldKey]) {
this._sessions.delete(oldKey);
}
}

this._sessionsChanged.emit(void 0);
}
}
31 changes: 31 additions & 0 deletions packages/jupyterlab-lsp/src/tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ISignal } from '@phosphor/signaling';
import { ServerConnection } from '@jupyterlab/services';

import * as SCHEMA from './_schema';

export type TLanguageServerId = string;
export type TLanguageId = string;

export type TSessionMap = Map<TLanguageServerId, SCHEMA.LanguageServerSession>;

export interface ILanguageServerManager {
sessionsChanged: ISignal<ILanguageServerManager, void>;
sessions: TSessionMap;
getServerId(
options: ILanguageServerManager.IGetServerIdOptions
): TLanguageServerId;
fetchSessions(): Promise<void>;
statusUrl: string;
}

export namespace ILanguageServerManager {
export const URL_NS = 'lsp';
export interface IOptions {
settings?: ServerConnection.ISettings;
baseUrl?: string;
}
export interface IGetServerIdOptions {
language?: TLanguageId;
mimeType?: string;
}
}
Loading