Skip to content

Commit 9c4bc52

Browse files
committed
context-aware hinter code
1 parent a79c2f1 commit 9c4bc52

File tree

2 files changed

+76
-25
lines changed

2 files changed

+76
-25
lines changed

client/modules/IDE/components/Editor/index.jsx

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ import { EditorContainer, EditorHolder } from './MobileEditor';
7373
import { FolderIcon } from '../../../../common/icons';
7474
import IconButton from '../../../../common/IconButton';
7575

76+
import contextAwareHinter from '../contextAwareHinter';
77+
7678
emmet(CodeMirror);
7779

7880
window.JSHINT = JSHINT;
@@ -462,22 +464,27 @@ class Editor extends React.Component {
462464
// JavaScript
463465
CodeMirror.showHint(
464466
_cm,
465-
() => {
466-
const c = _cm.getCursor();
467-
const token = _cm.getTokenAt(c);
468-
469-
const hints = this.hinter
470-
.search(token.string)
471-
.filter((h) => h.item.text[0] === token.string[0]);
472-
473-
return {
474-
list: hints,
475-
from: CodeMirror.Pos(c.line, token.start),
476-
to: CodeMirror.Pos(c.line, c.ch)
477-
};
478-
},
467+
(cm, options) => contextAwareHinter(cm, { hinter: this.hinter }),
479468
hintOptions
480469
);
470+
// CodeMirror.showHint(
471+
// _cm,
472+
// () => {
473+
// const c = _cm.getCursor();
474+
// const token = _cm.getTokenAt(c);
475+
476+
// const hints = this.hinter
477+
// .search(token.string)
478+
// .filter((h) => h.item.text[0] === token.string[0]);
479+
480+
// return {
481+
// list: hints,
482+
// from: CodeMirror.Pos(c.line, token.start),
483+
// to: CodeMirror.Pos(c.line, c.ch)
484+
// };
485+
// },
486+
// hintOptions
487+
// );
481488
} else if (_cm.options.mode === 'css') {
482489
// CSS
483490
CodeMirror.showHint(_cm, CodeMirror.hint.css, hintOptions);

client/modules/IDE/components/show-hint.js

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77

88
// declare global: DOMRect
99

10+
// The first function (mod) is a wrapper to support different JavaScript environments.
11+
// The second function (inside) contains the actual implementation.
12+
import parseCode from './parseCode';
13+
import CodeMirror from 'codemirror';
14+
1015
(function (mod) {
1116
if (typeof exports == 'object' && typeof module == 'object')
1217
// CommonJS
@@ -16,6 +21,8 @@
1621
define(['codemirror'], mod);
1722
// Plain browser env
1823
else mod(CodeMirror);
24+
25+
// This part uptill here makes the code compatible with multiple JavaScript environments, so it can run in different places
1926
})(function (CodeMirror) {
2027
'use strict';
2128

@@ -25,55 +32,67 @@
2532
// This is the old interface, kept around for now to stay
2633
// backwards-compatible.
2734
CodeMirror.showHint = function (cm, getHints, options) {
28-
if (!getHints) return cm.showHint(options);
35+
console.log('showhint was called: ', getHints, options);
36+
if (!getHints) return cm.showHint(options); // if not getHints function passed, it assumes youre using the newer interface
37+
// restructured options to call the new c.showHint() method
2938
if (options && options.async) getHints.async = true;
3039
var newOpts = { hint: getHints };
3140
if (options) for (var prop in options) newOpts[prop] = options[prop];
41+
console.log('newopts: ', newOpts);
42+
const context = parseCode(cm);
43+
console.log('Cursor context =', context);
3244
return cm.showHint(newOpts);
3345
};
3446

47+
// this adds a method called showHint to every cm editor instance (editor.showHint())
3548
CodeMirror.defineExtension('showHint', function (options) {
3649
options = parseOptions(this, this.getCursor('start'), options);
3750
var selections = this.listSelections();
51+
console.log('selections are: ', selections);
3852
if (selections.length > 1) return;
3953
// By default, don't allow completion when something is selected.
4054
// A hint function can have a `supportsSelection` property to
4155
// indicate that it can handle selections.
4256
if (this.somethingSelected()) {
4357
if (!options.hint.supportsSelection) return;
4458
// Don't try with cross-line selections
59+
// if selection spans multiple lines, bail out
4560
for (var i = 0; i < selections.length; i++)
4661
if (selections[i].head.line != selections[i].anchor.line) return;
4762
}
4863

49-
if (this.state.completionActive) this.state.completionActive.close();
64+
if (this.state.completionActive) this.state.completionActive.close(); // close an already active autocomplete session if active
65+
// create a new completion object and saves it to this.state.completionActive
5066
var completion = (this.state.completionActive = new Completion(
5167
this,
5268
options
5369
));
54-
if (!completion.options.hint) return;
70+
if (!completion.options.hint) return; // safety check to ensure hint is valid
5571

56-
CodeMirror.signal(this, 'startCompletion', this);
72+
CodeMirror.signal(this, 'startCompletion', this); // emits a signal; fires a startCompletion event on editor instance
5773
completion.update(true);
5874
});
5975

6076
CodeMirror.defineExtension('closeHint', function () {
6177
if (this.state.completionActive) this.state.completionActive.close();
6278
});
6379

80+
// defines a constructor function
6481
function Completion(cm, options) {
6582
this.cm = cm;
6683
this.options = options;
67-
this.widget = null;
84+
this.widget = null; // will hold a reference to the dropdown menu that shows suggestions
6885
this.debounce = 0;
6986
this.tick = 0;
70-
this.startPos = this.cm.getCursor('start');
87+
this.startPos = this.cm.getCursor('start'); // startPos is a {line,ch} object used to remember where hinting started
88+
// startLen is the len of the line minus length of any selected text
7189
this.startLen =
7290
this.cm.getLine(this.startPos.line).length -
7391
this.cm.getSelection().length;
7492

7593
if (this.options.updateOnCursorActivity) {
76-
var self = this;
94+
var self = this; // stores ref to this as self so it can be accessed inside the nested function
95+
// adds an event listener to the editor; called when the cursor moves
7796
cm.on(
7897
'cursorActivity',
7998
(this.activityFunc = function () {
@@ -95,12 +114,14 @@
95114
if (!this.active()) return;
96115
this.cm.state.completionActive = null;
97116
this.tick = null;
117+
// removes the current activity listener
98118
if (this.options.updateOnCursorActivity) {
99119
this.cm.off('cursorActivity', this.activityFunc);
100120
}
101-
121+
// signals and removes the widget
102122
if (this.widget && this.data) CodeMirror.signal(this.data, 'close');
103123
if (this.widget) this.widget.close();
124+
// emits a completition end event
104125
CodeMirror.signal(this.cm, 'endCompletion', this.cm);
105126
},
106127

@@ -109,6 +130,7 @@
109130
},
110131

111132
pick: function (data, i) {
133+
// selects an item from the suggestion list
112134
var completion = data.list[i],
113135
self = this;
114136
this.cm.operation(function () {
@@ -120,15 +142,18 @@
120142
completion.to || data.to,
121143
'complete'
122144
);
145+
// signals that a hint was picked and scrolls to it
123146
CodeMirror.signal(data, 'pick', completion);
124147
self.cm.scrollIntoView();
125148
});
149+
// closes widget if closeOnPick is enabled
126150
if (this.options.closeOnPick) {
127151
this.close();
128152
}
129153
},
130154

131155
cursorActivity: function () {
156+
// if a debounce is scheduled, cancel it to avoid outdated updates
132157
if (this.debounce) {
133158
cancelAnimationFrame(this.debounce);
134159
this.debounce = 0;
@@ -149,6 +174,7 @@
149174
!pos.ch ||
150175
this.options.closeCharacters.test(line.charAt(pos.ch - 1))
151176
) {
177+
console.log('this.close called');
152178
this.close();
153179
} else {
154180
var self = this;
@@ -192,6 +218,7 @@
192218
function parseOptions(cm, pos, options) {
193219
var editor = cm.options.hintOptions;
194220
var out = {};
221+
// copies all default hint settings into out
195222
for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
196223
if (editor)
197224
for (var prop in editor)
@@ -200,14 +227,16 @@
200227
for (var prop in options)
201228
if (options[prop] !== undefined) out[prop] = options[prop];
202229
if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos);
230+
console.log('out is ', out);
203231
return out;
204232
}
205-
233+
// extracts the visible text from a completion entry
206234
function getText(completion) {
207235
if (typeof completion === 'string') return completion;
208236
else return completion.item.text;
209237
}
210238

239+
// builds a key mapping object to define keyboard behavior for autocomplete
211240
function buildKeyMap(completion, handle) {
212241
var baseMap = {
213242
Up: function () {
@@ -232,7 +261,7 @@
232261
Tab: handle.pick,
233262
Esc: handle.close
234263
};
235-
264+
// checks if the user is on macOS and adds shortcuts accordingly
236265
var mac = /Mac/.test(navigator.platform);
237266

238267
if (mac) {
@@ -244,6 +273,7 @@
244273
};
245274
}
246275

276+
// user defined custom key bindings
247277
var custom = completion.options.customKeys;
248278
var ourMap = custom ? {} : baseMap;
249279
function addBinding(key, val) {
@@ -257,6 +287,7 @@
257287
else bound = val;
258288
ourMap[key] = bound;
259289
}
290+
// apply all custom key bindings and extraKeys
260291
if (custom)
261292
for (var key in custom)
262293
if (custom.hasOwnProperty(key)) addBinding(key, custom[key]);
@@ -267,15 +298,20 @@
267298
return ourMap;
268299
}
269300

301+
// hintsElement is the parent for hints and el is the clicked element within that container
270302
function getHintElement(hintsElement, el) {
303+
console.log('el is ', el);
271304
while (el && el != hintsElement) {
272-
if (el.nodeName.toUpperCase() === 'LI' && el.parentNode == hintsElement)
305+
if (el.nodeName.toUpperCase() === 'LI' && el.parentNode == hintsElement) {
306+
console.log('new el is ', el);
273307
return el;
308+
}
274309
el = el.parentNode;
275310
}
276311
}
277312

278313
function displayHint(name, type, p5) {
314+
console.log('name is', name, type, p5);
279315
return `<p class="${type}-item">\
280316
<span class="${type}-name hint-name">${name}</span>\
281317
<span class="hint-hidden">, </span>\
@@ -293,7 +329,9 @@ ${
293329
}
294330

295331
function getInlineHintSuggestion(focus, tokenLength) {
332+
console.log('the focus is: ', focus, tokenLength);
296333
const suggestionItem = focus.item;
334+
// builds the remainder of the suggestion excluding what user already typed
297335
const baseCompletion = `<span class="inline-hinter-suggestion">${suggestionItem.text.slice(
298336
tokenLength
299337
)}</span>`;
@@ -310,6 +348,7 @@ ${
310348
);
311349
}
312350

351+
// clears existing inline hint (like the part is suggested)
313352
function removeInlineHint(cm) {
314353
if (cm.state.inlineHint) {
315354
cm.state.inlineHint.clear();
@@ -341,7 +380,12 @@ ${
341380
}
342381
}
343382

383+
// defines the autocomplete dropdown ui
384+
// completition = the autocomplete context having cm and options
385+
// data = object with the list of suggestions
344386
function Widget(completion, data) {
387+
console.log('widget completetition= ', completion);
388+
console.log('widget data= ', data);
345389
this.id = 'cm-complete-' + Math.floor(Math.random(1e6));
346390
this.completion = completion;
347391
this.data = data;

0 commit comments

Comments
 (0)