Skip to content

Commit f7ddb22

Browse files
authored
Merge pull request #162 from krassowski/fix/completer
Fix various bugs in the completer feature
2 parents 1c70b3b + c1bb8ff commit f7ddb22

File tree

5 files changed

+183
-14
lines changed

5 files changed

+183
-14
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
[#159](https://github.com/krassowski/jupyterlab-lsp/pull/159)
1010
)
1111

12+
- bugfixes
13+
14+
- fixed various small bugs in the completer (
15+
[#162](https://github.com/krassowski/jupyterlab-lsp/pull/162)
16+
)
17+
1218
## `lsp-ws-connection 0.3.1`
1319

1420
- added `sendSaved()` method (textDocument/didSave) (

atest/05_Features/Completion.robot

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,55 @@ Autocompletes If Only One Option
5858
User Can Select Lowercase After Starting Uppercase
5959
[Tags] language:python
6060
Setup Notebook Python Completion.ipynb
61-
Enter Cell Editor 4 line=1
61+
# `from time import Tim<tab>` → `from time import time`
62+
Wait Until Fully Initialized
63+
Enter Cell Editor 5 line=1
6264
Trigger Completer
6365
Completer Should Suggest time
6466
Press Keys None ENTER
65-
Wait Until Keyword Succeeds 40x 0.5s Cell Editor Should Equal 4 from time import time
67+
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
69+
70+
Mid Token Completions Do Not Overwrite
71+
[Tags] language:python
72+
Setup Notebook Python Completion.ipynb
73+
# `disp<tab>data` → `display_table<cursor>data`
74+
Place Cursor In Cell Editor At 9 line=1 character=4
75+
Capture Page Screenshot 01-cursor-placed.png
76+
Wait Until Fully Initialized
77+
Press Keys None TAB
78+
Capture Page Screenshot 02-completed.png
79+
Wait Until Keyword Succeeds 40x 0.5s Cell Editor Should Equal 9 display_tabledata
80+
# `disp<tab>lay` → `display_table<cursor>`
81+
Place Cursor In Cell Editor At 11 line=1 character=4
82+
Press Keys None TAB
83+
Wait Until Keyword Succeeds 40x 0.5s Cell Editor Should Equal 11 display_table
84+
[Teardown] Clean Up After Working With File Completion.ipynb
85+
86+
Completion Works For Tokens Separated By Space
87+
[Tags] language:python
88+
Setup Notebook Python Completion.ipynb
89+
# `from statistics <tab>` → `from statistics import<cursor>`
90+
Enter Cell Editor 13 line=1
91+
Wait Until Fully Initialized
92+
Trigger Completer
93+
Completer Should Suggest import
94+
Press Keys None ENTER
95+
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
97+
98+
Kernel And LSP Completions Merge Prefix Conflicts Are Resolved
99+
[Documentation] Reconciliate Python kernel returning prefixed completions and LSP (pyls) not-prefixed ones
100+
[Tags] language:python
101+
# For more details see: https://github.com/krassowski/jupyterlab-lsp/issues/30#issuecomment-576003987
102+
# `import os.pat<tab>` → `import os.pathsep`
103+
Setup Notebook Python Completion.ipynb
104+
Enter Cell Editor 15 line=1
105+
Wait Until Fully Initialized
106+
Trigger Completer
107+
Completer Should Suggest pathsep
108+
Select Completer Suggestion pathsep
109+
Wait Until Keyword Succeeds 40x 0.5s Cell Editor Should Equal 15 import os.pathsep
66110
[Teardown] Clean Up After Working With File Completion.ipynb
67111

68112
Triggers Completer On Dot
@@ -79,14 +123,20 @@ Triggers Completer On Dot
79123
*** Keywords ***
80124
Get Cell Editor Content
81125
[Arguments] ${cell_nr}
82-
${content} Execute JavaScript return document.querySelector('.jp-CodeCell:nth-child(${cell_nr}) .CodeMirror').CodeMirror.getValue()
126+
${content} Execute JavaScript return document.querySelector('.jp-Cell:nth-child(${cell_nr}) .CodeMirror').CodeMirror.getValue()
83127
[Return] ${content}
84128

85129
Cell Editor Should Equal
86130
[Arguments] ${cell} ${value}
87131
${content} = Get Cell Editor Content ${cell}
88132
Should Be Equal ${content} ${value}
89133

134+
Select Completer Suggestion
135+
[Arguments] ${text}
136+
${suggestion} = Set Variable css:.jp-Completer-item[data-value="${text}"]
137+
Mouse Over ${suggestion}
138+
Click Element ${suggestion} code
139+
90140
Completer Should Suggest
91141
[Arguments] ${text}
92142
Page Should Contain Element ${COMPLETER_BOX} .jp-Completer-item[data-value="${text}"]

atest/Keywords.robot

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,13 @@ Gently Reset Workspace
198198

199199
Enter Cell Editor
200200
[Arguments] ${cell_nr} ${line}=1
201-
Click Element css:.jp-CodeCell:nth-child(${cell_nr}) .CodeMirror-line:nth-child(${line})
202-
Wait Until Page Contains Element css:.jp-CodeCell:nth-child(${cell_nr}) .CodeMirror-focused
201+
Click Element css:.jp-Cell:nth-child(${cell_nr}) .CodeMirror-line:nth-child(${line})
202+
Wait Until Page Contains Element css:.jp-Cell:nth-child(${cell_nr}) .CodeMirror-focused
203+
204+
Place Cursor In Cell Editor At
205+
[Arguments] ${cell_nr} ${line} ${character}
206+
Enter Cell Editor ${cell_nr} ${line}
207+
Execute JavaScript return document.querySelector('.jp-Cell:nth-child(${cell_nr}) .CodeMirror').CodeMirror.setCursor({line: ${line} - 1, ch: ${character}})
203208

204209
Wait Until Fully Initialized
205210
Wait Until Element Contains ${STATUSBAR} Fully initialized timeout=35s

atest/examples/Completion.ipynb

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@
2828
"list."
2929
]
3030
},
31+
{
32+
"cell_type": "markdown",
33+
"metadata": {},
34+
"source": [
35+
"`from time import Tim<tab>` → `from time import time`"
36+
]
37+
},
3138
{
3239
"cell_type": "code",
3340
"execution_count": null,
@@ -36,6 +43,87 @@
3643
"source": [
3744
"from time import Tim"
3845
]
46+
},
47+
{
48+
"cell_type": "markdown",
49+
"metadata": {},
50+
"source": [
51+
"Setup (not a test case)"
52+
]
53+
},
54+
{
55+
"cell_type": "code",
56+
"execution_count": null,
57+
"metadata": {},
58+
"outputs": [],
59+
"source": [
60+
"def display_table():\n",
61+
" pass"
62+
]
63+
},
64+
{
65+
"cell_type": "markdown",
66+
"metadata": {},
67+
"source": [
68+
"`disp<tab>data` → `display_table<cursor>data`"
69+
]
70+
},
71+
{
72+
"cell_type": "code",
73+
"execution_count": null,
74+
"metadata": {},
75+
"outputs": [],
76+
"source": [
77+
"dispdata"
78+
]
79+
},
80+
{
81+
"cell_type": "markdown",
82+
"metadata": {},
83+
"source": [
84+
"`disp<tab>lay` → `display_table<cursor>`"
85+
]
86+
},
87+
{
88+
"cell_type": "code",
89+
"execution_count": null,
90+
"metadata": {},
91+
"outputs": [],
92+
"source": [
93+
"display"
94+
]
95+
},
96+
{
97+
"cell_type": "markdown",
98+
"metadata": {},
99+
"source": [
100+
"`from statistics <tab>` → `from statistics import<cursor>`"
101+
]
102+
},
103+
{
104+
"cell_type": "code",
105+
"execution_count": null,
106+
"metadata": {},
107+
"outputs": [],
108+
"source": [
109+
"from statistics "
110+
]
111+
},
112+
{
113+
"cell_type": "markdown",
114+
"metadata": {},
115+
"source": [
116+
"`import os.pat<tab>` → select `pathsep` → `import os.pathsep` (tests whether kernel prefixes are removed)"
117+
]
118+
},
119+
{
120+
"cell_type": "code",
121+
"execution_count": null,
122+
"metadata": {},
123+
"outputs": [],
124+
"source": [
125+
"import os.pat"
126+
]
39127
}
40128
],
41129
"metadata": {

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

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ export class LSPConnector extends DataConnector<
111111
const start = editor.getPositionAt(token.offset);
112112
const end = editor.getPositionAt(token.offset + token.value.length);
113113

114+
let position_in_token = cursor.column - start.column - 1;
114115
const typed_character = token.value[cursor.column - start.column - 1];
115116

116117
let start_in_root = this.transform_from_editor_to_root(start);
@@ -150,7 +151,8 @@ export class LSPConnector extends DataConnector<
150151
virtual_start,
151152
virtual_end,
152153
virtual_cursor,
153-
document
154+
document,
155+
position_in_token
154156
)
155157
]).then(([kernel, lsp]) =>
156158
this.merge_replies(kernel, lsp, this._editor)
@@ -163,7 +165,8 @@ export class LSPConnector extends DataConnector<
163165
virtual_start,
164166
virtual_end,
165167
virtual_cursor,
166-
document
168+
document,
169+
position_in_token
167170
).catch(e => {
168171
console.warn('LSP: hint failed', e);
169172
return this.fallback_connector.fetch(request);
@@ -180,7 +183,8 @@ export class LSPConnector extends DataConnector<
180183
start: IVirtualPosition,
181184
end: IVirtualPosition,
182185
cursor: IVirtualPosition,
183-
document: VirtualDocument
186+
document: VirtualDocument,
187+
position_in_token: number
184188
): Promise<CompletionHandler.IReply> {
185189
let connection = this._connections.get(document.id_path);
186190

@@ -190,7 +194,7 @@ export class LSPConnector extends DataConnector<
190194
// to the matches...
191195
// Suggested in https://github.com/jupyterlab/jupyterlab/issues/7044, TODO PR
192196

193-
console.log(token);
197+
console.log('[LSP][Completer] Token:', token);
194198

195199
let completion_items: lsProtocol.CompletionItem[] = [];
196200
await connection
@@ -208,6 +212,8 @@ export class LSPConnector extends DataConnector<
208212
completion_items = items || [];
209213
});
210214

215+
let prefix = token.value.slice(0, position_in_token + 1);
216+
211217
let matches: Array<string> = [];
212218
const types: Array<IItemType> = [];
213219
let all_non_prefixed = true;
@@ -219,10 +225,22 @@ export class LSPConnector extends DataConnector<
219225
// kind: 3
220226
// label: "mean(data)"
221227
// sortText: "amean"
228+
229+
// TODO: add support for match.textEdit
222230
let text = match.insertText ? match.insertText : match.label;
223231

224-
if (text.toLowerCase().startsWith(token.value.toLowerCase())) {
232+
if (text.toLowerCase().startsWith(prefix.toLowerCase())) {
225233
all_non_prefixed = false;
234+
if (prefix !== token.value) {
235+
if (text.toLowerCase().startsWith(token.value.toLowerCase())) {
236+
// given a completion insert text "display_table" and two test cases:
237+
// disp<tab>data → display_table<cursor>data
238+
// disp<tab>lay → display_table<cursor>
239+
// we have to adjust the prefix for the latter (otherwise we would get display_table<cursor>lay),
240+
// as we are constrained NOT to replace after the prefix (which would be "disp" otherwise)
241+
prefix = token.value;
242+
}
243+
}
226244
}
227245

228246
matches.push(text);
@@ -243,7 +261,7 @@ export class LSPConnector extends DataConnector<
243261
// text = token.value + text;
244262
// but it did not work for "from statistics <tab>" and lead to "from statisticsimport" (no space)
245263
start: token.offset + (all_non_prefixed ? 1 : 0),
246-
end: token.offset + token.value.length,
264+
end: token.offset + prefix.length,
247265
matches: matches,
248266
metadata: {
249267
_jupyter_types_experimental: types
@@ -266,7 +284,7 @@ export class LSPConnector extends DataConnector<
266284
} else if (lsp.matches.length === 0) {
267285
return kernel;
268286
}
269-
console.log('merging LSP and kernel completions:', lsp, kernel);
287+
console.log('[LSP][Completer] Merging completions:', lsp, kernel);
270288

271289
// Populate the result with a copy of the lsp matches.
272290
const matches = lsp.matches.slice();
@@ -285,8 +303,10 @@ export class LSPConnector extends DataConnector<
285303
if (lsp.start > kernel.start) {
286304
const cursor = editor.getCursorPosition();
287305
const line = editor.getLine(cursor.line);
288-
prefix = line.substring(cursor.column - 1, cursor.column);
289-
console.log('will remove prefix from kernel response:', prefix);
306+
prefix = line.substring(kernel.start, lsp.start);
307+
console.log('[LSP][Completer] Removing kernel prefix: ', prefix);
308+
} else if (lsp.start < kernel.start) {
309+
console.warn('[LSP][Completer] Kernel start > LSP start');
290310
}
291311

292312
let remove_prefix = (value: string) => {

0 commit comments

Comments
 (0)