Skip to content

Commit bf6589b

Browse files
authored
Merge pull request #146 from krassowski/tests/feature/completion
Add initial tests for completion feature
2 parents ee693bd + 500cb08 commit bf6589b

File tree

7 files changed

+167
-18
lines changed

7 files changed

+167
-18
lines changed

CHANGELOG.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
[#129](https://github.com/krassowski/jupyterlab-lsp/pull/129)
1717
)
1818
- added a widget panel with diagnostics (inspections), allowing to
19-
sort and explore diagnostics, and to go-to the respective location
20-
in code (on click); accessible from the context menu (
19+
sort and explore diagnostics, and to go to the respective location
20+
in code (with a click); accessible from the context menu (
2121
[#127](https://github.com/krassowski/jupyterlab-lsp/pull/127)
2222
)
2323
- all commands are now accessible from the command palette (
@@ -37,6 +37,9 @@
3737
- fixed LSP of R in Python (`%%R` magic cell from rpy2) (
3838
[#144](https://github.com/krassowski/jupyterlab-lsp/pull/144)
3939
)
40+
- completion now work properly when the kernel is shut down (
41+
[#146](https://github.com/krassowski/jupyterlab-lsp/pull/146)
42+
)
4043

4144
## `lsp-ws-connection 0.3.0`
4245

atest/05_Features/Completion.robot

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
*** Settings ***
2+
Suite Setup Setup Suite For Screenshots completion
3+
Resource ../Keywords.robot
4+
5+
*** Variables ***
6+
${STATUSBAR} css:div.lsp-statusbar-item
7+
${COMPLETER_BOX} css:.jp-Completer.jp-HoverBox
8+
9+
*** Test Cases ***
10+
Works With Kernel Running
11+
[Documentation] The suggestions from kernel and LSP should get integrated.
12+
Setup Notebook Python Completion.ipynb
13+
Wait Until Element Contains ${STATUSBAR} Fully initialized timeout=20s
14+
Enter Cell Editor 1 line=2
15+
Capture Page Screenshot 01-entered-cell.png
16+
Trigger Completer
17+
Capture Page Screenshot 02-completions-shown.png
18+
# lowercase and uppercase suggestions:
19+
Completer Should Suggest TabError
20+
# this comes from LSP:
21+
Completer Should Suggest test
22+
# this comes from kernel
23+
Completer Should Suggest %%timeit
24+
Press Keys None ENTER
25+
Capture Page Screenshot 03-completion-confirmed.png
26+
${content} = Get Cell Editor Content 1
27+
Should Contain ${content} TabError
28+
[Teardown] Clean Up After Working With File Completion.ipynb
29+
30+
Works When Kernel Is Shut Down
31+
Setup Notebook Python Completion.ipynb
32+
Lab Command Shut Down All Kernels…
33+
Capture Page Screenshot 01-shutting-kernels.png
34+
Accept Default Dialog Option
35+
Capture Page Screenshot 02-kernels-shut.png
36+
Enter Cell Editor 1 line=2
37+
Trigger Completer
38+
Capture Page Screenshot 03-completions-shown.png
39+
# this comes from LSP:
40+
Completer Should Suggest test
41+
# this comes from kernel:
42+
Completer Should Not Suggest %%timeit
43+
[Teardown] Clean Up After Working With File Completion.ipynb
44+
45+
Autocompletes If Only One Option
46+
Setup Notebook Python Completion.ipynb
47+
Enter Cell Editor 3 line=1
48+
Press Keys None cle
49+
Press Keys None TAB
50+
Wait Until Keyword Succeeds 20x 0.5s Cell Editor Should Equal 3 list.clear
51+
[Teardown] Clean Up After Working With File Completion.ipynb
52+
53+
*** Keywords ***
54+
Enter Cell Editor
55+
[Arguments] ${cell_nr} ${line}=1
56+
Click Element css:.jp-CodeCell:nth-child(${cell_nr}) .CodeMirror-line:nth-child(${line})
57+
Wait Until Page Contains Element css:.jp-CodeCell:nth-child(${cell_nr}) .CodeMirror-focused
58+
59+
Get Cell Editor Content
60+
[Arguments] ${cell_nr}
61+
${content} Execute JavaScript return document.querySelector('.jp-CodeCell:nth-child(${cell_nr}) .CodeMirror').CodeMirror.getValue()
62+
[Return] ${content}
63+
64+
Cell Editor Should Equal
65+
[Arguments] ${cell} ${value}
66+
${content} = Get Cell Editor Content ${cell}
67+
Should Be Equal ${content} ${value}
68+
69+
Completer Should Suggest
70+
[Arguments] ${text}
71+
Page Should Contain Element ${COMPLETER_BOX} .jp-Completer-item[data-value="${text}"]
72+
73+
Completer Should Not Suggest
74+
[Arguments] ${text}
75+
Page Should Not Contain Element ${COMPLETER_BOX} .jp-Completer-item[data-value="${text}"]
76+
77+
Trigger Completer
78+
Press Keys None TAB
79+
Wait Until Page Contains Element ${COMPLETER_BOX} timeout=6s

atest/examples/Completion.ipynb

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"metadata": {},
7+
"outputs": [],
8+
"source": [
9+
"test = 1 \n",
10+
"t"
11+
]
12+
},
13+
{
14+
"cell_type": "code",
15+
"execution_count": null,
16+
"metadata": {},
17+
"outputs": [],
18+
"source": [
19+
"list"
20+
]
21+
},
22+
{
23+
"cell_type": "code",
24+
"execution_count": null,
25+
"metadata": {},
26+
"outputs": [],
27+
"source": [
28+
"list."
29+
]
30+
}
31+
],
32+
"metadata": {
33+
"kernelspec": {
34+
"display_name": "Python 3",
35+
"language": "python",
36+
"name": "python3"
37+
},
38+
"language_info": {
39+
"codemirror_mode": {
40+
"name": "ipython",
41+
"version": 3
42+
},
43+
"file_extension": ".py",
44+
"mimetype": "text/x-python",
45+
"name": "python",
46+
"nbconvert_exporter": "python",
47+
"pygments_lexer": "ipython3",
48+
"version": "3.8.0"
49+
}
50+
},
51+
"nbformat": 4,
52+
"nbformat_minor": 4
53+
}

packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ export class LSPConnector extends DataConnector<
6767
this.options = options;
6868
}
6969

70+
protected get _has_kernel(): boolean {
71+
return this.options.session.kernel !== null;
72+
}
73+
7074
protected get _kernel_language(): string {
7175
return this.options.session.kernel.info.language_info.name;
7276
}
@@ -127,6 +131,7 @@ export class LSPConnector extends DataConnector<
127131

128132
try {
129133
if (
134+
this._has_kernel &&
130135
this._kernel_connector &&
131136
// TODO: this would be awesome if we could connect to rpy2 for R suggestions in Python,
132137
// but this is not the job of this extension; nevertheless its better to keep this in
@@ -157,10 +162,11 @@ export class LSPConnector extends DataConnector<
157162
virtual_cursor,
158163
document
159164
).catch(e => {
160-
console.log(e);
165+
console.warn('LSP: hint failed', e);
161166
return this.fallback_connector.fetch(request);
162167
});
163168
} catch (e) {
169+
console.warn('LSP: kernel completions failed', e);
164170
return this.fallback_connector.fetch(request);
165171
}
166172
}

packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ export abstract class JupyterLabWidgetAdapter
210210

211211
await this.virtual_editor.update_documents().then(() => {
212212
// refresh the document on the LSP server
213-
this.document_changed(virtual_document);
213+
this.document_changed(virtual_document, true);
214214
console.log(
215215
'LSP: virtual document(s) for',
216216
this.document_path,
@@ -229,7 +229,7 @@ export abstract class JupyterLabWidgetAdapter
229229
});
230230
}
231231

232-
document_changed(virtual_document: VirtualDocument) {
232+
document_changed(virtual_document: VirtualDocument, is_init = false) {
233233
// TODO only send the difference, using connection.sendSelectiveChange()
234234
let connection = this.connection_manager.connections.get(
235235
virtual_document.id_path
@@ -251,18 +251,22 @@ export abstract class JupyterLabWidgetAdapter
251251
'has changed sending update'
252252
);
253253
connection.sendFullTextChange(virtual_document.value);
254-
// guarantee that the virtual editor won't perform an update of the virtual documents while
255-
// the changes are recorded...
256-
// TODO this is not ideal - why it solves the problem of some errors,
257-
// it introduces an unnecessary delay. A better way could be to invalidate some of the updates when a new one comes in.
258-
// but maybe not every one (then the outdated state could be kept for too long fo a user who writes very quickly)
259-
// also we would not want to invalidate the updates for the purpose of autocompletion (the trigger characters)
260-
this.virtual_editor
261-
.with_update_lock(async () => {
262-
await adapter.updateAfterChange();
263-
})
264-
.then()
265-
.catch(console.warn);
254+
// the first change (initial) is not propagated to features,
255+
// as it has no associated CodeMirrorChange object
256+
if (!is_init) {
257+
// guarantee that the virtual editor won't perform an update of the virtual documents while
258+
// the changes are recorded...
259+
// TODO this is not ideal - why it solves the problem of some errors,
260+
// it introduces an unnecessary delay. A better way could be to invalidate some of the updates when a new one comes in.
261+
// but maybe not every one (then the outdated state could be kept for too long fo a user who writes very quickly)
262+
// also we would not want to invalidate the updates for the purpose of autocompletion (the trigger characters)
263+
this.virtual_editor
264+
.with_update_lock(async () => {
265+
await adapter.updateAfterChange();
266+
})
267+
.then()
268+
.catch(console.warn);
269+
}
266270
}
267271

268272
private async connect_adapter(

packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter {
4747
.catch(console.warn);
4848

4949
this.widget.context.session.kernelChanged.connect((_session, change) => {
50+
if (!change.newValue) {
51+
console.warn('LSP: kernel was shut down');
52+
return;
53+
}
5054
change.newValue.ready
5155
.then(async spec => {
5256
console.log(

packages/jupyterlab-lsp/src/virtual/editor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export abstract class VirtualEditor implements CodeMirror.Editor {
102102
* @param fn - the callback to execute in update lock
103103
*/
104104
public async with_update_lock(fn: Function) {
105-
await until_ready(() => this.can_update(), 10, 10).then(() => {
105+
await until_ready(() => this.can_update(), 12, 10).then(() => {
106106
try {
107107
this.update_lock = true;
108108
fn();

0 commit comments

Comments
 (0)