Skip to content

Commit 762afa7

Browse files
authored
Merge pull request #476 from krassowski/backend-status
Add new status "no server extension"
2 parents 0a0d5c9 + 48637de commit 762afa7

File tree

9 files changed

+156
-9
lines changed

9 files changed

+156
-9
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- features
66

77
- make the extension work with `jupyterlab-classic` - experimental, not all features are functional yet ([#465])
8+
- new status "Server extension missing" and a dialog with advice was added to help users with atypical configurations ([#476])
89

910
- bug fixes:
1011

@@ -16,6 +17,7 @@
1617
[#449]: https://github.com/krassowski/jupyterlab-lsp/pull/449
1718
[#465]: https://github.com/krassowski/jupyterlab-lsp/pull/465
1819
[#474]: https://github.com/krassowski/jupyterlab-lsp/pull/474
20+
[#476]: https://github.com/krassowski/jupyterlab-lsp/pull/476
1921
[#478]: https://github.com/krassowski/jupyterlab-lsp/pull/478
2022

2123
### `jupyter-lsp 1.0.1` (unreleased)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
*** Settings ***
2+
Suite Setup Setup Server and Browser server_extension_enabled=${False}
3+
Resource ../Keywords.robot
4+
5+
*** Variables ***
6+
${STATUSBAR} css:div.lsp-statusbar-item
7+
${POPOVER} css:.lsp-popover
8+
9+
*** Test Cases ***
10+
Handles Server Extension Failure
11+
Setup Notebook Python Python.ipynb wait=${False}
12+
Element Should Contain ${STATUSBAR} Server extension missing
13+
Click Element ${STATUSBAR}
14+
Wait For Dialog
15+
Accept Default Dialog Option
16+
Page Should Not Contain Element ${POPOVER}
17+
[Teardown] Clean Up After Working With File Python.ipynb

atest/Keywords.robot

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@ Library ./config.py
1111

1212
*** Keywords ***
1313
Setup Server and Browser
14+
[Arguments] ${server_extension_enabled}=${True}
1415
Initialize Global Variables
15-
Create Notebok Server Config
16+
Create Notebok Server Config ${server_extension_enabled}
1617
Initialize User Settings
18+
${disable_global_config} = Set Variable If ${server_extension_enabled} != ${True} '1' ${EMPTY}
1719
${server} = Start Process jupyter-lab
1820
... cwd=${NOTEBOOK DIR}
1921
... stdout=${LAB LOG}
2022
... stderr=STDOUT
2123
... env:HOME=${HOME}
24+
... env:JUPYTER_NO_CONFIG=${disable_global_config}
2225
Set Global Variable ${SERVER} ${server}
2326
Open JupyterLab
2427
Read Page Config
@@ -34,6 +37,7 @@ Initialize Global Variables
3437
Set Screenshot Directory ${SCREENSHOTS DIR}
3538

3639
Create Notebok Server Config
40+
[Arguments] ${server_extension_enabled}=${True}
3741
[Documentation] Copies in notebook server config file and updates accordingly
3842
${conf} = Set Variable ${NOTEBOOK DIR}${/}${NBSERVER CONF}
3943
${extra_node_roots} = Create List ${ROOT}
@@ -47,9 +51,20 @@ Create Notebok Server Config
4751
... token=${TOKEN}
4852
... user_settings_dir=${SETTINGS DIR}
4953
... workspaces_dir=${WORKSPACES DIR}
54+
# should be automatically enabled, so do not enable manually:
55+
Run Keyword Unless
56+
... ${server_extension_enabled}
57+
... Set Server Extension State ${conf} enabled=${server_extension_enabled}
5058
Update Jupyter Config ${conf} LanguageServerManager
5159
... extra_node_roots=@{extra_node_roots}
5260

61+
Set Server Extension State
62+
[Arguments] ${conf} ${enabled}=${True}
63+
${extension_state} = Create Dictionary enabled=${enabled}
64+
${extensions} = Create Dictionary jupyter_lsp=${extension_state}
65+
Update Jupyter Config ${conf} LabApp
66+
... jpserver_extensions=${extensions}
67+
5368
Read Page Config
5469
${script} = Get Element Attribute id:jupyter-config-data innerHTML
5570
${config} = Evaluate __import__("json").loads(r"""${script}""")
@@ -249,14 +264,16 @@ Clean Up After Working With File
249264
Lab Log Should Not Contain Known Error Messages
250265

251266
Setup Notebook
252-
[Arguments] ${Language} ${file} ${isolated}=${True}
267+
[Arguments] ${Language} ${file} ${isolated}=${True} ${wait}=${True}
253268
Set Tags language:${Language.lower()}
254269
Run Keyword If ${isolated} Set Screenshot Directory ${SCREENSHOTS DIR}${/}notebook${/}${TEST NAME.replace(' ', '_')}
255270
Copy File examples${/}${file} ${NOTEBOOK DIR}${/}${file}
256271
Run Keyword If ${isolated} Try to Close All Tabs
257272
Open ${file} in ${MENU NOTEBOOK}
258273
Capture Page Screenshot 00-notebook-opened.png
259-
Wait Until Fully Initialized
274+
Run Keyword If
275+
... ${wait}
276+
... Wait Until Fully Initialized
260277
Capture Page Screenshot 01-notebook-initialized.png
261278

262279
Open Diagnostics Panel
@@ -392,3 +409,7 @@ Measure Cursor Position
392409
Wait Until Page Contains Element ${CM CURSORS}
393410
${position} = Wait Until Keyword Succeeds 20 x 0.05s Get Vertical Position ${CM CURSOR}
394411
[Return] ${position}
412+
413+
Switch To Tab
414+
[Arguments] ${file}
415+
Click Element ${JLAB XP DOCK TAB}\[contains(., '${file}')]

packages/jupyterlab-lsp/src/components/statusbar.tsx

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ import { DocumentLocator } from './utils';
3535
import { INotebookModel, NotebookPanel } from '@jupyterlab/notebook';
3636
import { LanguageServerManager } from '../manager';
3737
import { codeCheckIcon, codeClockIcon, codeWarningIcon } from './icons';
38+
import { Dialog, showDialog } from '@jupyterlab/apputils';
39+
import { SERVER_EXTENSION_404 } from '../errors';
40+
import okButton = Dialog.okButton;
3841

3942
interface IServerStatusProps {
4043
server: SCHEMA.LanguageServerSession;
@@ -317,11 +320,19 @@ export class LSPStatus extends VDomRenderer<LSPStatus.Model> {
317320
if (this._popup) {
318321
this._popup.dispose();
319322
}
320-
this._popup = showPopup({
321-
body: new LSPPopup(this.model),
322-
anchor: this,
323-
align: 'left'
324-
});
323+
if (this.model.status.status == 'no_server_extension') {
324+
showDialog({
325+
title: 'LSP server extension not found',
326+
body: SERVER_EXTENSION_404,
327+
buttons: [okButton()]
328+
}).catch(console.warn);
329+
} else {
330+
this._popup = showPopup({
331+
body: new LSPPopup(this.model),
332+
anchor: this,
333+
align: 'left'
334+
});
335+
}
325336
};
326337
}
327338

@@ -364,6 +375,7 @@ export class StatusButtonExtension
364375
}
365376

366377
type StatusCode =
378+
| 'no_server_extension'
367379
| 'waiting'
368380
| 'initializing'
369381
| 'initialized'
@@ -389,6 +401,7 @@ type StatusMap = Record<StatusCode, string>;
389401
type StatusIconClass = Record<StatusCode, string>;
390402

391403
const classByStatus: StatusIconClass = {
404+
no_server_extension: 'error',
392405
waiting: 'inactive',
393406
initialized: 'ready',
394407
initializing: 'preparing',
@@ -397,6 +410,7 @@ const classByStatus: StatusIconClass = {
397410
};
398411

399412
const iconByStatus: Record<StatusCode, LabIcon> = {
413+
no_server_extension: codeWarningIcon,
400414
waiting: codeClockIcon,
401415
initialized: codeCheckIcon,
402416
initializing: codeClockIcon,
@@ -405,6 +419,7 @@ const iconByStatus: Record<StatusCode, LabIcon> = {
405419
};
406420

407421
const shortMessageByStatus: StatusMap = {
422+
no_server_extension: 'Server extension missing',
408423
waiting: 'Waiting...',
409424
initialized: 'Fully initialized',
410425
initialized_but_some_missing: 'Initialized (additional servers needed)',
@@ -569,7 +584,9 @@ export namespace LSPStatus {
569584
});
570585

571586
let status: StatusCode;
572-
if (detected_documents.size === 0) {
587+
if (this.language_server_manager.statusCode === 404) {
588+
status = 'no_server_extension';
589+
} else if (detected_documents.size === 0) {
573590
status = 'waiting';
574591
} else if (initialized_documents.size === detected_documents.size) {
575592
status = 'initialized';
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React from 'react';
2+
3+
function external_link(url: string, label: string) {
4+
return (
5+
<a
6+
href={url}
7+
target="_blank"
8+
rel="noopener noreferrer"
9+
className="lsp-external-link"
10+
>
11+
{label}
12+
</a>
13+
);
14+
}
15+
16+
const migration_guide_url =
17+
'https://jupyter-server.readthedocs.io/en/latest/operators/migrate-from-nbserver.html';
18+
const jupyter_hub_migration_url =
19+
'https://github.com/jupyterhub/jupyterhub/pull/3329/files';
20+
21+
export const SERVER_EXTENSION_404 = (
22+
<div>
23+
<p>
24+
It appears that the required Jupyter server extension (
25+
<code>jupyter-lsp</code>) is not installed (or not enabled) in this
26+
environment.
27+
</p>
28+
<h3>What are the likely reasons?</h3>
29+
<ul>
30+
<li>
31+
It might be that you just installed it and need to restart JupyterLab to
32+
make it available on startup.
33+
</li>
34+
<li>
35+
Alternatively, you may be using an older <code>notebook</code> server
36+
instead of the new <code>jupyter_server</code>; please refer to the{' '}
37+
{external_link(migration_guide_url, 'migration guide')} or if you are a
38+
JupyterHub user please ensure that you start the JupyterLab using{' '}
39+
<code>jupyter_server</code> and not the old <code>notebook</code>, as
40+
documented in{' '}
41+
{external_link(jupyter_hub_migration_url, 'this pull request')}.
42+
</li>
43+
<li>
44+
There may be schema errors or language server errors preventing the
45+
extension from loading - please check the logs of JupyterLab (in the
46+
console where you execute <code>jupyter lab</code>
47+
</li>
48+
</ul>
49+
<h3>How do I check if the extension is installed?</h3>
50+
<p>
51+
Please ensure that <code>jupyter server extension list</code> includes
52+
jupyter-lsp and that it is enabled. If it is enabled please try to restart
53+
JupyterLab.
54+
</p>
55+
</div>
56+
);

packages/jupyterlab-lsp/src/manager.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,16 @@ export class LanguageServerManager implements ILanguageServerManager {
1414
protected _sessions: TSessionMap = new Map();
1515
private _settings: ServerConnection.ISettings;
1616
private _baseUrl: string;
17+
private _statusCode: number;
18+
private _retries: number;
19+
private _retriesInterval: number;
1720

1821
constructor(options: ILanguageServerManager.IOptions) {
1922
this._settings = options.settings || ServerConnection.makeSettings();
2023
this._baseUrl = options.baseUrl || PageConfig.getBaseUrl();
24+
this._retries = options.retries || 2;
25+
this._retriesInterval = options.retriesInterval || 10000;
26+
this._statusCode = null;
2127
this.fetchSessions().catch(console.warn);
2228
}
2329

@@ -45,15 +51,25 @@ export class LanguageServerManager implements ILanguageServerManager {
4551
return null;
4652
}
4753

54+
get statusCode(): number {
55+
return this._statusCode;
56+
}
57+
4858
async fetchSessions() {
4959
let response = await ServerConnection.makeRequest(
5060
this.statusUrl,
5161
{ method: 'GET' },
5262
this._settings
5363
);
5464

65+
this._statusCode = response.status;
66+
5567
if (!response.ok) {
5668
console.error('Could not fetch sessions', response);
69+
if (this._retries > 0) {
70+
this._retries -= 1;
71+
setTimeout(this.fetchSessions.bind(this), this._retriesInterval);
72+
}
5773
return;
5874
}
5975

packages/jupyterlab-lsp/src/tokens.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export interface ILanguageServerManager {
5757
): TLanguageServerId;
5858
fetchSessions(): Promise<void>;
5959
statusUrl: string;
60+
statusCode: number;
6061
}
6162

6263
export interface ILanguageServerConfiguration {
@@ -73,6 +74,15 @@ export namespace ILanguageServerManager {
7374
export interface IOptions {
7475
settings?: ServerConnection.ISettings;
7576
baseUrl?: string;
77+
/**
78+
* Number of connection retries to fetch the sessions.
79+
* Default 2.
80+
*/
81+
retries?: number;
82+
/**
83+
* The interval for retries, default 10 seconds.
84+
*/
85+
retriesInterval?: number;
7686
}
7787
export interface IGetServerIdOptions {
7888
language?: TLanguageId;

packages/jupyterlab-lsp/style/index.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@
1313
transform: scaleX(-1);
1414
transform-origin: center center;
1515
}
16+
17+
.lsp-external-link {
18+
color: var(--jp-content-link-color);
19+
}

packages/jupyterlab-lsp/style/statusbar.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ h5 {
106106
fill: var(--jp-warn-color0);
107107
}
108108

109+
.lsp-status-icon.error svg g {
110+
fill: var(--jp-error-color0);
111+
}
112+
109113
.lsp-status-icon.inactive svg g {
110114
fill: var(--jp-layout-color3);
111115
}

0 commit comments

Comments
 (0)