Skip to content

Commit eb834a1

Browse files
feat: Inline autocompletion (#5084)
* Inline ghost-text autocompletion Added optional ghost-text preview to popup-based autocompletion (disabled by default) New external inline-autocompletion widget which supports ghost-text only autocompletion * Autocomplete bugfixes Added the inline autocompletion to the kitchen sink demo Added kitchen sink demo option to enable inline preview for popup-based autocomplete Inline completion and command bar tooltip are changed to be editor scoped * Update src/ext/inline_autocomplete.js Co-authored-by: André Oliveira <[email protected]> * Fix styling and add cross-editor tests * Fix for popup display positioning for autocompletion * Add popup display unit tests --------- Co-authored-by: André Oliveira <[email protected]>
1 parent a5cc588 commit eb834a1

18 files changed

+2195
-163
lines changed

ace.d.ts

+106
Original file line numberDiff line numberDiff line change
@@ -924,6 +924,72 @@ export namespace Ace {
924924
prefix: string,
925925
callback: CompleterCallback): void;
926926
}
927+
928+
export class AceInline {
929+
show(editor: Editor, completion: Completion, prefix: string): void;
930+
isOpen(): void;
931+
hide(): void;
932+
destroy(): void;
933+
}
934+
935+
interface CompletionOptions {
936+
matches?: Completion[];
937+
}
938+
939+
type CompletionProviderOptions = {
940+
exactMatch?: boolean;
941+
ignoreCaption?: boolean;
942+
}
943+
944+
type CompletionRecord = {
945+
all: Completion[];
946+
filtered: Completion[];
947+
filterText: string;
948+
} | CompletionProviderOptions
949+
950+
type GatherCompletionRecord = {
951+
prefix: string;
952+
matches: Completion[];
953+
finished: boolean;
954+
}
955+
956+
type CompletionCallbackFunction = (err: Error | undefined, data: GatherCompletionRecord) => void;
957+
type CompletionProviderCallback = (err: Error | undefined, completions: CompletionRecord, finished: boolean) => void;
958+
959+
export class CompletionProvider {
960+
insertByIndex(editor: Editor, index: number, options: CompletionProviderOptions): boolean;
961+
insertMatch(editor: Editor, data: Completion, options: CompletionProviderOptions): boolean;
962+
completions: CompletionRecord;
963+
gatherCompletions(editor: Editor, callback: CompletionCallbackFunction): boolean;
964+
provideCompletions(editor: Editor, options: CompletionProviderOptions, callback: CompletionProviderCallback): void;
965+
detach(): void;
966+
}
967+
968+
export class Autocomplete {
969+
constructor();
970+
autoInsert?: boolean;
971+
autoSelect?: boolean;
972+
exactMatch?: boolean;
973+
inlineEnabled?: boolean;
974+
getPopup(): AcePopup;
975+
showPopup(editor: Editor, options: CompletionOptions): void;
976+
detach(): void;
977+
destroy(): void;
978+
}
979+
980+
type AcePopupNavigation = "up" | "down" | "start" | "end";
981+
982+
export class AcePopup {
983+
constructor(parentNode: HTMLElement);
984+
setData(list: Completion[], filterText: string): void;
985+
getData(row: number): Completion;
986+
getRow(): number;
987+
getRow(line: number): void;
988+
hide(): void;
989+
show(pos: Point, lineHeight: number, topdownOnly: boolean): void;
990+
tryShow(pos: Point, lineHeight: number, anchor: "top" | "bottom" | undefined, forceShow?: boolean): boolean;
991+
goTo(where: AcePopupNavigation): void;
992+
}
927993
}
928994

929995

@@ -946,3 +1012,43 @@ export const Range: {
9461012
fromPoints(start: Ace.Point, end: Ace.Point): Ace.Range;
9471013
comparePoints(p1: Ace.Point, p2: Ace.Point): number;
9481014
};
1015+
1016+
1017+
type InlineAutocompleteAction = "prev" | "next" | "first" | "last";
1018+
1019+
type TooltipCommandEnabledFunction = (editor: Ace.Editor) => boolean;
1020+
1021+
interface TooltipCommand extends Ace.Command {
1022+
enabled: TooltipCommandEnabledFunction | boolean,
1023+
position?: number;
1024+
}
1025+
1026+
export class InlineAutocomplete {
1027+
constructor();
1028+
getInlineRenderer(): Ace.AceInline;
1029+
getInlineTooltip(): InlineTooltip;
1030+
getCompletionProvider(): Ace.CompletionProvider;
1031+
show(editor: Ace.Editor): void;
1032+
isOpen(): boolean;
1033+
detach(): void;
1034+
destroy(): void;
1035+
goTo(action: InlineAutocompleteAction): void;
1036+
tooltipEnabled: boolean;
1037+
commands: Record<string, TooltipCommand>
1038+
getIndex(): number;
1039+
setIndex(value: number): void;
1040+
getLength(): number;
1041+
getData(index?: number): Ace.Completion | undefined;
1042+
updateCompletions(options: Ace.CompletionOptions): void;
1043+
}
1044+
1045+
export class InlineTooltip {
1046+
constructor(parentElement: HTMLElement);
1047+
setCommands(commands: Record<string, TooltipCommand>): void;
1048+
show(editor: Ace.Editor): void;
1049+
updatePosition(): void;
1050+
updateButtons(force?: boolean): void;
1051+
isShown(): boolean;
1052+
detach(): void;
1053+
destroy(): void;
1054+
}

demo/inline_autocompletion.html

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>ACE Inline Autocompletion demo</title>
6+
<style type="text/css" media="screen">
7+
body {
8+
overflow: hidden;
9+
}
10+
11+
#editor {
12+
margin: 0;
13+
position: absolute;
14+
top: 0;
15+
bottom: 0;
16+
left: 0;
17+
right: 0;
18+
}
19+
</style>
20+
</head>
21+
<body>
22+
23+
<pre id="editor"></pre>
24+
25+
<script src="kitchen-sink/require.js"></script>
26+
<script>
27+
// setup paths
28+
require.config({paths: { "ace" : "../src"}});
29+
// load ace and extensions
30+
require(["ace/ace", "ace/mode/javascript", "ace/ext/language_tools", "ace/ext/inline_autocomplete"], function(ace, codeLens) {
31+
var editor = ace.edit("editor");
32+
editor.session.setMode("ace/mode/javascript");
33+
editor.setTheme("ace/theme/tomorrow");
34+
// enable inline autocompletion
35+
editor.setOptions({
36+
enableBasicAutocompletion: false,
37+
enableInlineAutocompletion: true,
38+
enableSnippets: true,
39+
enableLiveAutocompletion: false
40+
});
41+
42+
window.editor = editor;
43+
});
44+
</script>
45+
46+
<script src="./show_own_source.js"></script>
47+
</body>
48+
</html>

demo/kitchen-sink/demo.js

+27
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,8 @@ doclist.pickDocument = function(name) {
312312
var OptionPanel = require("ace/ext/options").OptionPanel;
313313
var optionsPanel = new OptionPanel(env.editor);
314314

315+
var originalAutocompleteCommand = null;
316+
315317
optionsPanel.add({
316318
Main: {
317319
Document: {
@@ -368,6 +370,29 @@ optionsPanel.add({
368370
path: "showTokenInfo",
369371
position: 2000
370372
},
373+
"Inline preview for autocomplete": {
374+
path: "inlineEnabledForAutocomplete",
375+
position: 2000,
376+
onchange: function(value) {
377+
var Autocomplete = require("ace/autocomplete").Autocomplete;
378+
if (value && !originalAutocompleteCommand) {
379+
originalAutocompleteCommand = Autocomplete.startCommand.exec;
380+
Autocomplete.startCommand.exec = function(editor) {
381+
var autocomplete = Autocomplete.for(editor);
382+
autocomplete.inlineEnabled = true;
383+
originalAutocompleteCommand(...arguments);
384+
}
385+
} else if (!value) {
386+
var autocomplete = Autocomplete.for(editor);
387+
autocomplete.destroy();
388+
Autocomplete.startCommand.exec = originalAutocompleteCommand;
389+
originalAutocompleteCommand = null;
390+
}
391+
},
392+
getValue: function() {
393+
return !!originalAutocompleteCommand;
394+
}
395+
},
371396
"Show Textarea Position": devUtil.textPositionDebugger,
372397
"Text Input Debugger": devUtil.textInputDebugger,
373398
}
@@ -468,8 +493,10 @@ optionsPanelContainer.insertBefore(
468493
);
469494

470495
require("ace/ext/language_tools");
496+
require("ace/ext/inline_autocomplete");
471497
env.editor.setOptions({
472498
enableBasicAutocompletion: true,
499+
enableInlineAutocompletion: true,
473500
enableSnippets: true
474501
});
475502

0 commit comments

Comments
 (0)