Skip to content

Commit 8d56c84

Browse files
authoredMay 10, 2023
feat: Inline autocomplete tooltip UX redesign (#5149)
Changed UX of the inline tooltip for inline autocomplete.
1 parent 5f2e7d1 commit 8d56c84

9 files changed

+1632
-455
lines changed
 

‎ace.d.ts

+14-9
Original file line numberDiff line numberDiff line change
@@ -1101,39 +1101,44 @@ export const Range: {
11011101

11021102
type InlineAutocompleteAction = "prev" | "next" | "first" | "last";
11031103

1104-
type TooltipCommandEnabledFunction = (editor: Ace.Editor) => boolean;
1104+
type TooltipCommandFunction<T> = (editor: Ace.Editor) => T;
11051105

11061106
interface TooltipCommand extends Ace.Command {
1107-
enabled: TooltipCommandEnabledFunction | boolean,
1108-
position?: number;
1107+
enabled: TooltipCommandFunction<boolean> | boolean,
1108+
getValue?: TooltipCommandFunction<any>,
1109+
type: "button" | "text" | "checkbox"
1110+
iconCssClass: string,
1111+
cssClass: string
11091112
}
11101113

11111114
export class InlineAutocomplete {
11121115
constructor();
11131116
getInlineRenderer(): Ace.AceInline;
1114-
getInlineTooltip(): InlineTooltip;
1117+
getInlineTooltip(): CommandBarTooltip;
11151118
getCompletionProvider(): Ace.CompletionProvider;
11161119
show(editor: Ace.Editor): void;
11171120
isOpen(): boolean;
11181121
detach(): void;
11191122
destroy(): void;
11201123
goTo(action: InlineAutocompleteAction): void;
11211124
tooltipEnabled: boolean;
1122-
commands: Record<string, TooltipCommand>
1125+
commands: Record<string, Ace.Command>
11231126
getIndex(): number;
11241127
setIndex(value: number): void;
11251128
getLength(): number;
11261129
getData(index?: number): Ace.Completion | undefined;
11271130
updateCompletions(options: Ace.CompletionOptions): void;
11281131
}
11291132

1130-
export class InlineTooltip {
1133+
export class CommandBarTooltip {
11311134
constructor(parentElement: HTMLElement);
1132-
setCommands(commands: Record<string, TooltipCommand>): void;
1133-
show(editor: Ace.Editor): void;
1135+
registerCommand(id: string, command: TooltipCommand): void;
1136+
attach(editor: Ace.Editor): void;
11341137
updatePosition(): void;
1135-
updateButtons(force?: boolean): void;
1138+
update(): void;
11361139
isShown(): boolean;
1140+
getAlwaysShow(): boolean;
1141+
setAlwaysShow(alwaysShow: boolean): void;
11371142
detach(): void;
11381143
destroy(): void;
11391144
}

‎src/autocomplete.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,12 @@ class CompletionProvider {
560560
if (!this.completions)
561561
return false;
562562
if (this.completions.filterText) {
563-
var ranges = editor.selection.getAllRanges();
563+
var ranges;
564+
if (editor.selection.getAllRanges) {
565+
ranges = editor.selection.getAllRanges();
566+
} else {
567+
ranges = [editor.getSelectionRange()];
568+
}
564569
for (var i = 0, range; range = ranges[i]; i++) {
565570
range.start.column -= this.completions.filterText.length;
566571
editor.session.remove(range);

‎src/ext/command_bar.js

+635
Large diffs are not rendered by default.

‎src/ext/command_bar_test.js

+694
Large diffs are not rendered by default.

‎src/ext/inline_autocomplete.js

+72-253
Large diffs are not rendered by default.

‎src/ext/inline_autocomplete_test.js

+206-17
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,21 @@ if (typeof process !== "undefined") {
99
var Editor = require("../editor").Editor;
1010
var EditSession = require("../edit_session").EditSession;
1111
var InlineAutocomplete = require("./inline_autocomplete").InlineAutocomplete;
12+
const { Autocomplete } = require("../autocomplete");
1213
var assert = require("../test/assertions");
14+
var BUTTON_CLASS_NAME = require("./command_bar").BUTTON_CLASS_NAME;
1315
var type = require("../test/user").type;
1416
var VirtualRenderer = require("../virtual_renderer").VirtualRenderer;
1517

1618
var editor;
1719
var autocomplete;
20+
var inlineTooltip;
21+
var wrapperEl;
22+
23+
function simulateClick(node) {
24+
node.dispatchEvent(new window.CustomEvent("click", { bubbles: true }));
25+
}
26+
1827

1928
var getAllLines = function() {
2029
var text = Array.from(editor.renderer.$textLayer.element.childNodes).map(function (node) {
@@ -61,22 +70,36 @@ var mockCompleter = {
6170
}
6271
};
6372

73+
var setupInlineTooltip = function() {
74+
inlineTooltip = autocomplete.getInlineTooltip();
75+
inlineTooltip.setAlwaysShow(true);
76+
// Workaround: non-standard width and height hints for mock dom (mock dom does not work well with flex elements)
77+
// When running in the browser, these are ignored
78+
inlineTooltip.tooltip.getElement().style.widthHint = 150;
79+
inlineTooltip.tooltip.getElement().style.heightHint = editor.renderer.lineHeight * 2;
80+
inlineTooltip.moreOptions.getElement().style.widthHint = 150;
81+
inlineTooltip.moreOptions.getElement().style.heightHint = editor.renderer.lineHeight * 2;
82+
};
83+
6484
module.exports = {
6585
setUp: function(done) {
66-
var el = document.createElement("div");
67-
el.style.left = "20px";
68-
el.style.top = "30px";
69-
el.style.width = "500px";
70-
el.style.height = "500px";
71-
document.body.appendChild(el);
72-
var renderer = new VirtualRenderer(el);
86+
wrapperEl = document.createElement("div");
87+
wrapperEl.style.position = "fixed";
88+
wrapperEl.style.left = "400px";
89+
wrapperEl.style.top = "30px";
90+
wrapperEl.style.width = "500px";
91+
wrapperEl.style.height = "500px";
92+
document.body.appendChild(wrapperEl);
93+
var renderer = new VirtualRenderer(wrapperEl);
7394
var session = new EditSession("");
7495
editor = new Editor(renderer, session);
96+
editor.setOption("enableInlineAutocompletion", true);
7597
editor.execCommand("insertstring", "f");
7698
editor.getSelection().moveCursorFileEnd();
7799
editor.renderer.$loop._flush();
78100
editor.completers = [mockCompleter];
79101
autocomplete = InlineAutocomplete.for(editor);
102+
setupInlineTooltip();
80103
editor.focus();
81104
done();
82105
},
@@ -88,17 +111,48 @@ module.exports = {
88111
assert.strictEqual(getAllLines(), "foo");
89112
done();
90113
},
91-
"test: autocomplete tooltip is shown according to the selected option": function(done) {
92-
assert.equal(autocomplete.inlineTooltip, null);
114+
"test: autocomplete start keybinding works": function(done) {
115+
type("Alt-C");
116+
assert.strictEqual(autocomplete.isOpen(), true);
117+
assert.strictEqual(autocomplete.getIndex(), 0);
118+
assert.strictEqual(autocomplete.getData().value, "foo");
119+
editor.renderer.$loop._flush();
120+
assert.strictEqual(getAllLines(), "foo");
121+
autocomplete.detach();
93122

123+
editor.setOption("enableInlineAutocompletion", false);
124+
125+
type("Alt-C");
126+
assert.strictEqual(autocomplete.isOpen(), false);
127+
assert.strictEqual(autocomplete.getIndex(), -1);
128+
editor.renderer.$loop._flush();
129+
assert.strictEqual(getAllLines(), "f");
130+
131+
done();
132+
},
133+
"test: replaces different autocomplete implementation for the editor when opened": function(done) {
134+
var completer = Autocomplete.for(editor);
135+
completer.showPopup(editor, {});
136+
assert.strictEqual(editor.completer, completer);
137+
assert.strictEqual(autocomplete.isOpen(), false);
138+
139+
autocomplete = InlineAutocomplete.for(editor);
140+
autocomplete.show(editor);
141+
assert.strictEqual(editor.completer.isOpen(), true);
142+
editor.renderer.$loop._flush();
143+
assert.strictEqual(getAllLines(), "foo");
144+
145+
done();
146+
},
147+
"test: autocomplete tooltip is shown according to the selected option": function(done) {
94148
autocomplete.show(editor);
95-
assert.strictEqual(autocomplete.inlineTooltip.isShown(), true);
149+
assert.strictEqual(inlineTooltip.isShown(), true);
96150

97151
autocomplete.detach();
98-
assert.strictEqual(autocomplete.inlineTooltip.isShown(), false);
152+
assert.strictEqual(inlineTooltip.isShown(), false);
99153
done();
100154
},
101-
"test: autocomplete navigation works": function(done) {
155+
"test: autocomplete keyboard navigation works": function(done) {
102156
autocomplete.show(editor);
103157
editor.renderer.$loop._flush();
104158
assert.strictEqual(autocomplete.getIndex(), 0);
@@ -118,6 +172,69 @@ module.exports = {
118172
assert.equal(getAllLines(), "foo");
119173
done();
120174
},
175+
"test: autocomplete tooltip navigation works": function(done) {
176+
autocomplete.show(editor);
177+
assert.strictEqual(autocomplete.getInlineTooltip().isShown(), true);
178+
179+
editor.renderer.$loop._flush();
180+
assert.strictEqual(autocomplete.getIndex(), 0);
181+
assert.strictEqual(autocomplete.getData().value, "foo");
182+
assert.strictEqual(getAllLines(), "foo");
183+
184+
var buttonElements = Array.from(document.querySelectorAll("." + BUTTON_CLASS_NAME));
185+
186+
var prevButton = buttonElements[0];
187+
var nextButton = buttonElements[2];
188+
189+
simulateClick(prevButton);
190+
191+
editor.renderer.$loop._flush();
192+
assert.strictEqual(autocomplete.getIndex(), 5);
193+
assert.strictEqual(autocomplete.getData().value, "fundraiser");
194+
assert.strictEqual(getAllLines(), "fundraiser");
195+
196+
simulateClick(nextButton);
197+
198+
editor.renderer.$loop._flush();
199+
assert.strictEqual(autocomplete.getIndex(), 0);
200+
assert.strictEqual(autocomplete.getData().value, "foo");
201+
assert.strictEqual(getAllLines(), "foo");
202+
203+
simulateClick(nextButton);
204+
205+
editor.renderer.$loop._flush();
206+
assert.strictEqual(autocomplete.getIndex(), 1);
207+
assert.strictEqual(autocomplete.getData().value, "foobar");
208+
assert.strictEqual(getAllLines(), "foobar");
209+
210+
autocomplete.setIndex(5);
211+
editor.renderer.$loop._flush();
212+
assert.strictEqual(autocomplete.getData().value, "fundraiser");
213+
assert.equal(getAllLines(), "fundraiser");
214+
215+
simulateClick(nextButton);
216+
217+
editor.renderer.$loop._flush();
218+
assert.strictEqual(autocomplete.getIndex(), 0);
219+
assert.strictEqual(autocomplete.getData().value, "foo");
220+
assert.strictEqual(getAllLines(), "foo");
221+
222+
simulateClick(prevButton);
223+
224+
editor.renderer.$loop._flush();
225+
assert.strictEqual(autocomplete.getIndex(), 5);
226+
assert.strictEqual(autocomplete.getData().value, "fundraiser");
227+
assert.strictEqual(getAllLines(), "fundraiser");
228+
229+
simulateClick(prevButton);
230+
231+
editor.renderer.$loop._flush();
232+
assert.strictEqual(autocomplete.getIndex(), 4);
233+
assert.strictEqual(autocomplete.getData().value, "function");
234+
assert.strictEqual(getAllLines(), "function");
235+
236+
done();
237+
},
121238
"test: verify goTo commands": function(done) {
122239
autocomplete.show(editor);
123240
autocomplete.setIndex(1);
@@ -145,9 +262,14 @@ module.exports = {
145262

146263
autocomplete.goTo("next");
147264
editor.renderer.$loop._flush();
148-
assert.strictEqual(autocomplete.getIndex(), 5);
265+
assert.strictEqual(autocomplete.getIndex(), 0);
266+
assert.strictEqual(autocomplete.getData().value, "foo");
267+
assert.strictEqual(getAllLines(), "foo");
268+
269+
autocomplete.setIndex(5);
270+
editor.renderer.$loop._flush();
149271
assert.strictEqual(autocomplete.getData().value, "fundraiser");
150-
assert.strictEqual(getAllLines(), "fundraiser");
272+
assert.equal(getAllLines(), "fundraiser");
151273

152274
autocomplete.goTo("first");
153275
editor.renderer.$loop._flush();
@@ -157,9 +279,9 @@ module.exports = {
157279

158280
autocomplete.goTo("prev");
159281
editor.renderer.$loop._flush();
160-
assert.strictEqual(autocomplete.getIndex(), 0);
161-
assert.strictEqual(autocomplete.getData().value, "foo");
162-
assert.strictEqual(getAllLines(), "foo");
282+
assert.strictEqual(autocomplete.getIndex(), 5);
283+
assert.strictEqual(autocomplete.getData().value, "fundraiser");
284+
assert.strictEqual(getAllLines(), "fundraiser");
163285
done();
164286
},
165287
"test: set index to negative value hides suggestions": function(done) {
@@ -204,6 +326,29 @@ module.exports = {
204326
assert.strictEqual(getAllLines(), "foo");
205327
done();
206328
},
329+
"test: autocomplete can be accepted via tooltip": function(done) {
330+
autocomplete.show(editor);
331+
editor.renderer.$loop._flush();
332+
assert.strictEqual(autocomplete.isOpen(), true);
333+
assert.equal(autocomplete.inlineTooltip.isShown(), true);
334+
assert.ok(document.querySelectorAll(".ace_ghost_text").length > 0);
335+
assert.strictEqual(getAllLines(), "foo");
336+
337+
var buttonElements = Array.from(document.querySelectorAll("." + BUTTON_CLASS_NAME));
338+
var acceptButton = buttonElements[3];
339+
340+
simulateClick(acceptButton);
341+
342+
editor.renderer.$loop._flush();
343+
assert.equal(autocomplete.inlineCompleter, null);
344+
assert.equal(autocomplete.inlineTooltip.isShown(), false);
345+
assert.strictEqual(autocomplete.isOpen(), false);
346+
assert.equal(editor.renderer.$ghostText, null);
347+
assert.equal(editor.renderer.$ghostTextWidget, null);
348+
assert.strictEqual(document.querySelectorAll(".ace_ghost_text").length, 0);
349+
assert.strictEqual(getAllLines(), "foo");
350+
done();
351+
},
207352
"test: incremental typing filters results": function(done) {
208353
autocomplete.show(editor);
209354
editor.renderer.$loop._flush();
@@ -232,6 +377,48 @@ module.exports = {
232377

233378
done();
234379
},
380+
"test: tooltip stays open on incremental typing": function(done) {
381+
autocomplete.show(editor);
382+
assert.strictEqual(inlineTooltip.isShown(), true);
383+
editor.renderer.$loop._flush();
384+
assert.strictEqual(autocomplete.isOpen(), true);
385+
assert.equal(getAllLines(), "foo");
386+
typeAndChange("u", "n");
387+
editor.renderer.$loop._flush();
388+
assert.strictEqual(autocomplete.isOpen(), true);
389+
assert.strictEqual(inlineTooltip.isShown(), true);
390+
done();
391+
},
392+
"test: can toggle tooltip display mode via tooltip button": function(done) {
393+
autocomplete.show(editor);
394+
assert.strictEqual(inlineTooltip.isShown(), true);
395+
396+
var buttonElements = Array.from(document.querySelectorAll("." + BUTTON_CLASS_NAME));
397+
var moreOptionsButton = buttonElements[4];
398+
var showTooltipToggle = buttonElements[5];
399+
var showTooltipToggleCheckMark = showTooltipToggle.firstChild;
400+
401+
assert.strictEqual(showTooltipToggle.ariaChecked.toString(), "true");
402+
assert.strictEqual(showTooltipToggleCheckMark.classList.contains("ace_checkmark"), true);
403+
assert.strictEqual(inlineTooltip.getAlwaysShow(), true);
404+
assert.strictEqual(inlineTooltip.isMoreOptionsShown(), false);
405+
406+
simulateClick(moreOptionsButton);
407+
408+
assert.strictEqual(inlineTooltip.isShown(), true);
409+
assert.strictEqual(showTooltipToggle.ariaChecked.toString(), "true");
410+
assert.strictEqual(showTooltipToggleCheckMark.classList.contains("ace_checkmark"), true);
411+
assert.strictEqual(inlineTooltip.getAlwaysShow(), true);
412+
assert.strictEqual(inlineTooltip.isMoreOptionsShown(), true);
413+
414+
simulateClick(showTooltipToggle);
415+
416+
assert.strictEqual(showTooltipToggle.ariaChecked.toString(), "false");
417+
assert.strictEqual(showTooltipToggleCheckMark.classList.contains("ace_checkmark"), false);
418+
assert.strictEqual(inlineTooltip.getAlwaysShow(), false);
419+
assert.strictEqual(inlineTooltip.isMoreOptionsShown(), false);
420+
done();
421+
},
235422
"test: verify detach": function(done) {
236423
autocomplete.show(editor);
237424
editor.renderer.$loop._flush();
@@ -274,9 +461,11 @@ module.exports = {
274461
editor.renderer.$loop._flush();
275462
done();
276463
},
464+
277465
tearDown: function() {
278466
autocomplete.destroy();
279467
editor.destroy();
468+
wrapperEl.parentElement.removeChild(wrapperEl);
280469
}
281470
};
282471

‎src/ext/inline_autocomplete_tooltip_test.js

-174
This file was deleted.

‎src/test/all_browser.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ var testNames = [
2525
"ace/editor_navigation_test",
2626
"ace/editor_text_edit_test",
2727
"ace/editor_commands_test",
28+
"ace/ext/command_bar_test",
2829
"ace/ext/hardwrap_test",
2930
"ace/ext/inline_autocomplete_test",
30-
"ace/ext/inline_autocomplete_tooltip_test",
3131
"ace/ext/static_highlight_test",
3232
"ace/ext/whitespace_test",
3333
"ace/ext/error_marker_test",

‎src/test/mockdom.js

+4
Original file line numberDiff line numberDiff line change
@@ -375,11 +375,15 @@ function Node(name) {
375375

376376
if (this.style.width)
377377
width = parseCssLength(this.style.width || "100%", rect.width);
378+
else if (this.style.widthHint)
379+
width = this.style.widthHint;
378380
else
379381
width = rect.width - right - left;
380382

381383
if (this.style.height)
382384
height = parseCssLength(this.style.height || "100%", rect.height);
385+
else if (this.style.heightHint)
386+
height = this.style.heightHint;
383387
else
384388
height = rect.height - top - bottom;
385389

0 commit comments

Comments
 (0)
Please sign in to comment.