-
-
Notifications
You must be signed in to change notification settings - Fork 553
/
complete.js
143 lines (127 loc) Β· 4.45 KB
/
complete.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// @flow
import { Observable } from "rxjs/Observable";
import { pluck, first, map, timeout } from "rxjs/operators";
import { createMessage, childOf, ofMessageType } from "@nteract/messaging";
import { js_idx_to_char_idx, char_idx_to_js_idx } from "./surrogate";
import type { EditorChange, CMI } from "../types";
import type { Channels } from "@nteract/types/channels";
// Hint picker
export const pick = (cm: any, handle: { pick: () => void }) => handle.pick();
export function formChangeObject(cm: CMI, change: EditorChange) {
return {
cm,
change
};
}
// ipykernel may return experimental completion in the metadata field,
// experiment with these. We use codemirror ability to take a rendering function
// on a per completion basis (we can't give a global one :-( to render not only
// the text, but the type as well.
// as this is not documented in CM the DOM structure of the completer will be
//
// <ul class="CodeMirror-hints" >
// <li class="CodeMirror-hint"></li>
// <li class="CodeMirror-hint CodeMirror-hint-active"></li>
// <li class="CodeMirror-hint"></li>
// <li class="CodeMirror-hint"></li>
// </ul>
// with each <li/> passed as the first argument of render.
const _expand_experimental_completions = (editor, matches, cursor) => ({
to: cursor,
from: cursor,
list: matches.map(completion => ({
text: completion.text,
to: editor.posFromIndex(completion.end),
from: editor.posFromIndex(completion.start),
type: completion.type,
render: (elt, data, completion) => {
const span = document.createElement("span");
const text = document.createTextNode(completion.text);
span.className += "completion-type completion-type-" + completion.type;
span.setAttribute("title", completion.type);
elt.appendChild(span);
elt.appendChild(text);
}
}))
});
// duplicate of default codemirror rendering logic for completions,
// except if the completion have a metadata._experimental key, dispatch to a new
// completer for these new values.
export const expand_completions = (editor: any) => (results: any) => {
if ((results.metadata || {})._jupyter_types_experimental != undefined) {
try {
return _expand_experimental_completions(
editor,
results.metadata._jupyter_types_experimental,
editor.getCursor()
);
} catch (e) {
console.error("Exprimental completion failed :", e);
}
}
let start = results.cursor_start;
let end = results.cursor_end;
if (end === null) {
// adapted message spec replies don't have cursor position info,
// interpret end=null as current position,
// and negative start relative to that
end = editor.indexFromPos(editor.getCursor());
if (start === null) {
start = end;
} else if (start < 0) {
start = end + start;
}
} else {
// handle surrogate pairs
// HACK: This seems susceptible to timing issues, we could verify changes in
// what's in the editor, as we'll be able to correlate across events
// Suggestions and background in https://github.com/nteract/nteract/pull/1840#discussion_r133380430
const text = editor.getValue();
end = char_idx_to_js_idx(end, text);
start = char_idx_to_js_idx(start, text);
}
return {
list: results.matches.map(match => ({
text: match,
render: (elt, data, current) =>
elt.appendChild(document.createTextNode(current.text))
})),
from: editor.posFromIndex(start),
to: editor.posFromIndex(end)
};
};
export function codeCompleteObservable(
channels: Channels,
editor: CMI,
message: Object
) {
const completion$ = channels.pipe(
childOf(message),
ofMessageType("complete_reply"),
pluck("content"),
first(),
map(expand_completions(editor)),
timeout(2000)
); // 2s
// On subscription, send the message
return Observable.create(observer => {
const subscription = completion$.subscribe(observer);
channels.next(message);
return subscription;
});
}
export const completionRequest = (code: string, cursorPos: number) =>
createMessage("complete_request", {
content: {
code,
cursor_pos: cursorPos
}
});
export function codeComplete(channels: Channels, editor: any) {
const cursor = editor.getCursor();
let cursorPos = editor.indexFromPos(cursor);
const code = editor.getValue();
cursorPos = js_idx_to_char_idx(cursorPos, code);
const message = completionRequest(code, cursorPos);
return codeCompleteObservable(channels, editor, message);
}