Skip to content

Commit fcf7b65

Browse files
authored
Merge pull request #165 from bollwyvl/add-one-socket-per-lang
Refactor LSPConnection, ConnectionManager
2 parents 3ca4f0a + 3d45458 commit fcf7b65

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1464
-861
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,4 @@ junit.xml
114114
coverage/
115115
.vscode/
116116
_schema.d.ts
117+
.virtual_documents/

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
# CHANGELOG
22

3+
## `@krassowski/jupyterlab-lsp 0.8.0` (unreleased)
4+
5+
- features
6+
7+
- opens a maximum of one WebSocket per language server ([#165][])
8+
- lazy-loads language server protocol machinery ([#165][])
9+
- waits much longer for slow-starting language servers ([#165][])
10+
- cleans up documents, handlers, events, and signals more aggressively ([#165][])
11+
- ignores malformed diagnostic ranges, enabling markdown support ([#165][])
12+
- passes tests on Python 3.8 on Windows ([#165][])
13+
14+
## `lsp-ws-connection 0.4.0` (unreleased)
15+
16+
- breaking changes
17+
18+
- no longer assumes one document per connection ([#165][])
19+
- requires documents be opened explicitly ([#165][])
20+
- use of the `eventEmitter` pattern mostly deprecated in favor of `Promise`s
21+
([#165][])
22+
23+
[#165]: https://github.com/krassowski/jupyterlab-lsp/pull/165
24+
325
## `jupyter-lsp 0.7.0`
426

527
- bugfixes

atest/01_Editor.robot

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ ${CM CURSORS} css:.CodeMirror-cursors:not([style='visibility: hidden'])
1111

1212
*** Test Cases ***
1313
Bash
14-
[Documentation] TODO: figure out why the first server is extra flaky
15-
Wait Until Keyword Succeeds 6x 5s Editor Shows Features for Language Bash example.sh Diagnostics=Failed to parse expression
16-
... Jump to Definition=fib
14+
Editor Shows Features for Language Bash example.sh Diagnostics=Failed to parse expression Jump to Definition=fib
1715

1816
CSS
1917
${def} = Set Variable xpath:(//span[contains(@class, 'cm-variable-2')][contains(text(), '--some-var')])[last()]
@@ -67,7 +65,7 @@ Editor Shows Features for Language
6765
Set Tags language:${Language.lower()}
6866
Set Screenshot Directory ${OUTPUT DIR}${/}screenshots${/}editor${/}${Language.lower()}
6967
Copy File examples${/}${file} ${OUTPUT DIR}${/}home${/}${file}
70-
Lab Command Close All Tabs
68+
Try to Close All Tabs
7169
Open ${file} in ${MENU EDITOR}
7270
Capture Page Screenshot 00-opened.png
7371
FOR ${f} IN @{features}

atest/03_Notebook.robot

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
*** Settings ***
22
Suite Setup Setup Suite For Screenshots notebook
3+
Test Setup Try to Close All Tabs
34
Resource Keywords.robot
45

56
*** Test Cases ***
67
Python
7-
[Setup] Gently Reset Workspace
88
Setup Notebook Python Python.ipynb
99
Capture Page Screenshot 01-python.png
1010
${diagnostic} = Set Variable W291 trailing whitespace (pycodestyle)
@@ -13,11 +13,11 @@ Python
1313
Clean Up After Working With File Python.ipynb
1414

1515
Foregin Extractors
16-
[Setup] Gently Reset Workspace
1716
Setup Notebook Python Foreign extractors.ipynb
18-
@{diagnostics} = Create List Failed to parse expression undefined name 'valid' (pyflakes) Trailing whitespace is superfluous. (lintr)
17+
# if mypy and pyflakes will fight over `(N|n)ame 'valid'`, just hope for the best
18+
@{diagnostics} = Create List Failed to parse expression ame 'valid' Trailing whitespace is superfluous. (lintr)
1919
FOR ${diagnostic} IN @{diagnostics}
20-
Wait Until Page Contains Element css:.cm-lsp-diagnostic[title="${diagnostic}"] timeout=35s
20+
Wait Until Page Contains Element css:.cm-lsp-diagnostic[title*\="${diagnostic}"] timeout=35s
2121
Capture Page Screenshot 0x-${diagnostic}.png
2222
END
2323
Clean Up After Working With File Foreign Extractors.ipynb

atest/05_Features/Completion.robot

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
*** Settings ***
22
Suite Setup Setup Suite For Screenshots completion
3+
Test Setup Setup Notebook Python Completion.ipynb
4+
Test Teardown Clean Up After Working With File Completion.ipynb
35
Force Tags feature:completion
46
Resource ../Keywords.robot
57

@@ -9,9 +11,6 @@ ${COMPLETER_BOX} css:.jp-Completer.jp-HoverBox
911
*** Test Cases ***
1012
Works With Kernel Running
1113
[Documentation] The suggestions from kernel and LSP should get integrated.
12-
[Tags] language:python
13-
Setup Notebook Python Completion.ipynb
14-
Wait Until Fully Initialized
1514
Enter Cell Editor 1 line=2
1615
Capture Page Screenshot 01-entered-cell.png
1716
Trigger Completer
@@ -26,12 +25,8 @@ Works With Kernel Running
2625
Capture Page Screenshot 03-completion-confirmed.png
2726
${content} = Get Cell Editor Content 1
2827
Should Contain ${content} TabError
29-
[Teardown] Clean Up After Working With File Completion.ipynb
3028

3129
Works When Kernel Is Shut Down
32-
[Tags] language:python
33-
Setup Notebook Python Completion.ipynb
34-
Wait Until Fully Initialized
3530
Lab Command Shut Down All Kernels…
3631
Capture Page Screenshot 01-shutting-kernels.png
3732
Accept Default Dialog Option
@@ -43,82 +38,58 @@ Works When Kernel Is Shut Down
4338
Completer Should Suggest test
4439
# this comes from kernel:
4540
Completer Should Not Suggest %%timeit
46-
[Teardown] Clean Up After Working With File Completion.ipynb
4741

4842
Autocompletes If Only One Option
49-
[Tags] language:python
50-
Setup Notebook Python Completion.ipynb
5143
Enter Cell Editor 3 line=1
5244
Press Keys None cle
5345
Wait Until Fully Initialized
5446
Press Keys None TAB
5547
Wait Until Keyword Succeeds 40x 0.5s Cell Editor Should Equal 3 list.clear
56-
[Teardown] Clean Up After Working With File Completion.ipynb
5748

5849
User Can Select Lowercase After Starting Uppercase
59-
[Tags] language:python
60-
Setup Notebook Python Completion.ipynb
6150
# `from time import Tim<tab>` → `from time import time`
62-
Wait Until Fully Initialized
6351
Enter Cell Editor 5 line=1
6452
Trigger Completer
6553
Completer Should Suggest time
6654
Press Keys None ENTER
6755
Wait Until Keyword Succeeds 40x 0.5s Cell Editor Should Equal 5 from time import time
68-
[Teardown] Clean Up After Working With File Completion.ipynb
6956

7057
Mid Token Completions Do Not Overwrite
71-
[Tags] language:python
72-
Setup Notebook Python Completion.ipynb
7358
# `disp<tab>data` → `display_table<cursor>data`
7459
Place Cursor In Cell Editor At 9 line=1 character=4
7560
Capture Page Screenshot 01-cursor-placed.png
76-
Wait Until Fully Initialized
7761
Press Keys None TAB
7862
Capture Page Screenshot 02-completed.png
7963
Wait Until Keyword Succeeds 40x 0.5s Cell Editor Should Equal 9 display_tabledata
8064
# `disp<tab>lay` → `display_table<cursor>`
8165
Place Cursor In Cell Editor At 11 line=1 character=4
8266
Press Keys None TAB
8367
Wait Until Keyword Succeeds 40x 0.5s Cell Editor Should Equal 11 display_table
84-
[Teardown] Clean Up After Working With File Completion.ipynb
8568

8669
Completion Works For Tokens Separated By Space
87-
[Tags] language:python
88-
Setup Notebook Python Completion.ipynb
8970
# `from statistics <tab>` → `from statistics import<cursor>`
9071
Enter Cell Editor 13 line=1
91-
Wait Until Fully Initialized
9272
Trigger Completer
9373
Completer Should Suggest import
9474
Press Keys None ENTER
9575
Wait Until Keyword Succeeds 40x 0.5s Cell Editor Should Equal 13 from statistics import
96-
[Teardown] Clean Up After Working With File Completion.ipynb
9776

9877
Kernel And LSP Completions Merge Prefix Conflicts Are Resolved
9978
[Documentation] Reconciliate Python kernel returning prefixed completions and LSP (pyls) not-prefixed ones
100-
[Tags] language:python
10179
# For more details see: https://github.com/krassowski/jupyterlab-lsp/issues/30#issuecomment-576003987
10280
# `import os.pat<tab>` → `import os.pathsep`
103-
Setup Notebook Python Completion.ipynb
10481
Enter Cell Editor 15 line=1
105-
Wait Until Fully Initialized
10682
Trigger Completer
10783
Completer Should Suggest pathsep
10884
Select Completer Suggestion pathsep
10985
Wait Until Keyword Succeeds 40x 0.5s Cell Editor Should Equal 15 import os.pathsep
110-
[Teardown] Clean Up After Working With File Completion.ipynb
11186

11287
Triggers Completer On Dot
113-
[Tags] language:python
114-
Setup Notebook Python Completion.ipynb
11588
Enter Cell Editor 2 line=1
116-
Wait Until Fully Initialized
11789
Press Keys None .
11890
Wait Until Keyword Succeeds 10x 0.5s Cell Editor Should Equal 2 list.
11991
Wait Until Page Contains Element ${COMPLETER_BOX} timeout=35s
12092
Completer Should Suggest append
121-
[Teardown] Clean Up After Working With File Completion.ipynb
12293

12394
*** Keywords ***
12495
Get Cell Editor Content
@@ -139,11 +110,12 @@ Select Completer Suggestion
139110

140111
Completer Should Suggest
141112
[Arguments] ${text}
142-
Page Should Contain Element ${COMPLETER_BOX} .jp-Completer-item[data-value="${text}"]
113+
Wait Until Page Contains Element ${COMPLETER_BOX} .jp-Completer-item[data-value="${text}"]
114+
Capture Page Screenshot ${text.replace(' ', '_')}.png
143115

144116
Completer Should Not Suggest
145117
[Arguments] ${text}
146-
Page Should Not Contain Element ${COMPLETER_BOX} .jp-Completer-item[data-value="${text}"]
118+
Wait Until Page Does Not Contain Element ${COMPLETER_BOX} .jp-Completer-item[data-value="${text}"]
147119

148120
Trigger Completer
149121
Press Keys None TAB

atest/05_Features/Signature.robot

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ ${SIGNATURE_BOX} css:.lsp-signature-help
99
*** Test Cases ***
1010
Triggers Signature Help After A Keystroke
1111
Setup Notebook Python Signature.ipynb
12-
Wait Until Fully Initialized
1312
Enter Cell Editor 1 line=6
1413
Capture Page Screenshot 01-entered-cell.png
1514
Press Keys None (

atest/Keywords.robot

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,17 @@ Open JupyterLab
6666
Close JupyterLab
6767
Close All Browsers
6868

69-
Reset Application State
69+
Close All Tabs
70+
Accept Default Dialog Option
7071
Lab Command Close All Tabs
7172
Accept Default Dialog Option
73+
74+
Try to Close All Tabs
75+
Wait Until Keyword Succeeds 5x 50ms Close All Tabs
76+
77+
Reset Application State
78+
Try to Close All Tabs
79+
Accept Default Dialog Option
7280
Ensure All Kernels Are Shut Down
7381
Lab Command Reset Application State
7482
Wait For Splash
@@ -172,11 +180,13 @@ Clean Up After Working With File
172180
Setup Notebook
173181
[Arguments] ${Language} ${file}
174182
Set Tags language:${Language.lower()}
175-
Set Screenshot Directory ${OUTPUT DIR}${/}screenshots${/}notebook${/}${file.lower()}
183+
Set Screenshot Directory ${OUTPUT DIR}${/}screenshots${/}notebook${/}${TEST NAME.replace(' ', '_')}
176184
Copy File examples${/}${file} ${OUTPUT DIR}${/}home${/}${file}
177-
Lab Command Close All Tabs
185+
Try to Close All Tabs
178186
Open ${file} in ${MENU NOTEBOOK}
179187
Capture Page Screenshot 00-opened.png
188+
Wait Until Fully Initialized
189+
Capture Page Screenshot 01-initialized.png
180190

181191
Open Diagnostics Panel
182192
Lab Command Show Diagnostics Panel
@@ -194,7 +204,7 @@ Wait For Dialog
194204
Wait Until Page Contains Element ${DIALOG WINDOW} timeout=180s
195205

196206
Gently Reset Workspace
197-
Lab Command Close All Tabs
207+
Try to Close All Tabs
198208

199209
Enter Cell Editor
200210
[Arguments] ${cell_nr} ${line}=1
@@ -207,7 +217,7 @@ Place Cursor In Cell Editor At
207217
Execute JavaScript return document.querySelector('.jp-Cell:nth-child(${cell_nr}) .CodeMirror').CodeMirror.setCursor({line: ${line} - 1, ch: ${character}})
208218

209219
Wait Until Fully Initialized
210-
Wait Until Element Contains ${STATUSBAR} Fully initialized timeout=35s
220+
Wait Until Element Contains ${STATUSBAR} Fully initialized timeout=60s
211221

212222
Open Context Menu Over
213223
[Arguments] ${sel}

azure-pipelines.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ variables:
2222
THIRD_PARTY_LABEXTENSIONS: >-
2323
@krassowski/jupyterlab_go_to_definition
2424
25+
LINKED_EXTENSIONS: >-
26+
packages/lsp-ws-connection
27+
2528
jobs:
2629
- template: ci/job.lint.yml
2730
- template: ci/job.test.yml

ci/job.test.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ parameters:
2020
spec: '>=3.8,<3.9.0a0'
2121
lab: '>=1.2.4,<1.3.0a0'
2222
env_update: conda env update -n jupyterlab-lsp --file env-test.yml --quiet
23+
lab_link: jupyter labextension link --no-build $(LINKED_EXTENSIONS)
2324
lab_ext: jupyter labextension install --no-build $(THIRD_PARTY_LABEXTENSIONS) $(FIRST_PARTY_LABEXTENSIONS)
2425

2526
jobs:
@@ -79,6 +80,9 @@ jobs:
7980
- script: ${{ platform.activate }} jupyterlab-lsp && python scripts/utest.py --test-run-title="Pytest ${{ platform.name }}${{ python.name }}"
8081
displayName: run python tests
8182

83+
- script: ${{ platform.activate }} jupyterlab-lsp && ${{ parameters.lab_link }} || ${{ parameters.lab_link }} || ${{ parameters.lab_link }}
84+
displayName: install support packages
85+
8286
- script: ${{ platform.activate }} jupyterlab-lsp && ${{ parameters.lab_ext }} || ${{ parameters.lab_ext }} || ${{ parameters.lab_ext }}
8387
displayName: install labextensions
8488

packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
11
import * as CodeMirror from 'codemirror';
22
import { until_ready } from '../../utils';
3-
import { CodeMirrorHandler, VirtualEditor } from '../../virtual/editor';
3+
import { VirtualEditor } from '../../virtual/editor';
44
import { VirtualDocument } from '../../virtual/document';
55
import { IRootPosition } from '../../positioning';
66
import { ILSPFeature } from './feature';
77
import { IJupyterLabComponentsManager } from '../jupyterlab/jl_adapter';
88

99
export class CodeMirrorAdapter {
1010
features: Map<string, ILSPFeature>;
11+
isDisposed = false;
1112

1213
private last_change: CodeMirror.EditorChange;
13-
private doc_change_handler: CodeMirrorHandler;
1414

1515
constructor(
1616
protected editor: VirtualEditor,
1717
protected virtual_document: VirtualDocument,
1818
protected jupyterlab_components: IJupyterLabComponentsManager,
1919
features = new Array<ILSPFeature>()
2020
) {
21-
this.doc_change_handler = this.saveChange.bind(this);
22-
this.editor.on('change', this.doc_change_handler);
21+
this.editor.on('change', this.saveChange);
2322

2423
this.features = new Map();
2524

@@ -38,19 +37,28 @@ export class CodeMirrorAdapter {
3837

3938
public async updateAfterChange() {
4039
this.jupyterlab_components.remove_tooltip();
41-
await until_ready(() => this.last_change != null, 30, 22).catch(() => {
42-
this.invalidateLastChange();
43-
throw Error(
40+
41+
try {
42+
await until_ready(() => this.last_change != null, 30, 22);
43+
} catch (err) {
44+
console.log(
4445
'No change obtained from CodeMirror editor within the expected time of 0.66s'
4546
);
46-
});
47+
return;
48+
}
49+
4750
let change: CodeMirror.EditorChange = this.last_change;
4851

52+
let root_position: IRootPosition;
53+
4954
try {
50-
const root_position = this.editor
51-
.getDoc()
52-
.getCursor('end') as IRootPosition;
55+
root_position = this.editor.getDoc().getCursor('end') as IRootPosition;
56+
} catch (err) {
57+
console.log('LSP: Root positon not found');
58+
return;
59+
}
5360

61+
try {
5462
let document = this.editor.document_at_root_position(root_position);
5563

5664
if (this.virtual_document !== document) {
@@ -77,14 +85,27 @@ export class CodeMirrorAdapter {
7785
this.last_change = null;
7886
}
7987

80-
public saveChange(doc: CodeMirror.Doc, change: CodeMirror.EditorChange) {
88+
public saveChange = (
89+
doc: CodeMirror.Doc,
90+
change: CodeMirror.EditorChange
91+
) => {
8192
this.last_change = change;
82-
}
93+
};
8394

84-
public remove() {
95+
public dispose() {
96+
if (this.isDisposed) {
97+
return;
98+
}
8599
for (let feature of this.features.values()) {
86100
feature.remove();
87101
}
88-
this.editor.off('change', this.doc_change_handler);
102+
this.features.clear();
103+
this.editor.off('change', this.saveChange);
104+
105+
// just to be sure
106+
this.editor = null;
107+
108+
// actually disposed
109+
this.isDisposed = true;
89110
}
90111
}

0 commit comments

Comments
 (0)