Skip to content

Commit

Permalink
feat: Inline autocompletion (#5084)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
azmkercso and andredcoliveira authored Mar 17, 2023
1 parent a5cc588 commit eb834a1
Show file tree
Hide file tree
Showing 18 changed files with 2,195 additions and 163 deletions.
106 changes: 106 additions & 0 deletions ace.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,72 @@ export namespace Ace {
prefix: string,
callback: CompleterCallback): void;
}

export class AceInline {
show(editor: Editor, completion: Completion, prefix: string): void;
isOpen(): void;
hide(): void;
destroy(): void;
}

interface CompletionOptions {
matches?: Completion[];
}

type CompletionProviderOptions = {
exactMatch?: boolean;
ignoreCaption?: boolean;
}

type CompletionRecord = {
all: Completion[];
filtered: Completion[];
filterText: string;
} | CompletionProviderOptions

type GatherCompletionRecord = {
prefix: string;
matches: Completion[];
finished: boolean;
}

type CompletionCallbackFunction = (err: Error | undefined, data: GatherCompletionRecord) => void;
type CompletionProviderCallback = (err: Error | undefined, completions: CompletionRecord, finished: boolean) => void;

export class CompletionProvider {
insertByIndex(editor: Editor, index: number, options: CompletionProviderOptions): boolean;
insertMatch(editor: Editor, data: Completion, options: CompletionProviderOptions): boolean;
completions: CompletionRecord;
gatherCompletions(editor: Editor, callback: CompletionCallbackFunction): boolean;
provideCompletions(editor: Editor, options: CompletionProviderOptions, callback: CompletionProviderCallback): void;
detach(): void;
}

export class Autocomplete {
constructor();
autoInsert?: boolean;
autoSelect?: boolean;
exactMatch?: boolean;
inlineEnabled?: boolean;
getPopup(): AcePopup;
showPopup(editor: Editor, options: CompletionOptions): void;
detach(): void;
destroy(): void;
}

type AcePopupNavigation = "up" | "down" | "start" | "end";

export class AcePopup {
constructor(parentNode: HTMLElement);
setData(list: Completion[], filterText: string): void;
getData(row: number): Completion;
getRow(): number;
getRow(line: number): void;
hide(): void;
show(pos: Point, lineHeight: number, topdownOnly: boolean): void;
tryShow(pos: Point, lineHeight: number, anchor: "top" | "bottom" | undefined, forceShow?: boolean): boolean;
goTo(where: AcePopupNavigation): void;
}
}


Expand All @@ -946,3 +1012,43 @@ export const Range: {
fromPoints(start: Ace.Point, end: Ace.Point): Ace.Range;
comparePoints(p1: Ace.Point, p2: Ace.Point): number;
};


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

type TooltipCommandEnabledFunction = (editor: Ace.Editor) => boolean;

interface TooltipCommand extends Ace.Command {
enabled: TooltipCommandEnabledFunction | boolean,
position?: number;
}

export class InlineAutocomplete {
constructor();
getInlineRenderer(): Ace.AceInline;
getInlineTooltip(): InlineTooltip;
getCompletionProvider(): Ace.CompletionProvider;
show(editor: Ace.Editor): void;
isOpen(): boolean;
detach(): void;
destroy(): void;
goTo(action: InlineAutocompleteAction): void;
tooltipEnabled: boolean;
commands: Record<string, TooltipCommand>
getIndex(): number;
setIndex(value: number): void;
getLength(): number;
getData(index?: number): Ace.Completion | undefined;
updateCompletions(options: Ace.CompletionOptions): void;
}

export class InlineTooltip {
constructor(parentElement: HTMLElement);
setCommands(commands: Record<string, TooltipCommand>): void;
show(editor: Ace.Editor): void;
updatePosition(): void;
updateButtons(force?: boolean): void;
isShown(): boolean;
detach(): void;
destroy(): void;
}
48 changes: 48 additions & 0 deletions demo/inline_autocompletion.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ACE Inline Autocompletion demo</title>
<style type="text/css" media="screen">
body {
overflow: hidden;
}

#editor {
margin: 0;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
</style>
</head>
<body>

<pre id="editor"></pre>

<script src="kitchen-sink/require.js"></script>
<script>
// setup paths
require.config({paths: { "ace" : "../src"}});
// load ace and extensions
require(["ace/ace", "ace/mode/javascript", "ace/ext/language_tools", "ace/ext/inline_autocomplete"], function(ace, codeLens) {
var editor = ace.edit("editor");
editor.session.setMode("ace/mode/javascript");
editor.setTheme("ace/theme/tomorrow");
// enable inline autocompletion
editor.setOptions({
enableBasicAutocompletion: false,
enableInlineAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: false
});

window.editor = editor;
});
</script>

<script src="./show_own_source.js"></script>
</body>
</html>
27 changes: 27 additions & 0 deletions demo/kitchen-sink/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,8 @@ doclist.pickDocument = function(name) {
var OptionPanel = require("ace/ext/options").OptionPanel;
var optionsPanel = new OptionPanel(env.editor);

var originalAutocompleteCommand = null;

optionsPanel.add({
Main: {
Document: {
Expand Down Expand Up @@ -368,6 +370,29 @@ optionsPanel.add({
path: "showTokenInfo",
position: 2000
},
"Inline preview for autocomplete": {
path: "inlineEnabledForAutocomplete",
position: 2000,
onchange: function(value) {
var Autocomplete = require("ace/autocomplete").Autocomplete;
if (value && !originalAutocompleteCommand) {
originalAutocompleteCommand = Autocomplete.startCommand.exec;
Autocomplete.startCommand.exec = function(editor) {
var autocomplete = Autocomplete.for(editor);
autocomplete.inlineEnabled = true;
originalAutocompleteCommand(...arguments);
}
} else if (!value) {
var autocomplete = Autocomplete.for(editor);
autocomplete.destroy();
Autocomplete.startCommand.exec = originalAutocompleteCommand;
originalAutocompleteCommand = null;
}
},
getValue: function() {
return !!originalAutocompleteCommand;
}
},
"Show Textarea Position": devUtil.textPositionDebugger,
"Text Input Debugger": devUtil.textInputDebugger,
}
Expand Down Expand Up @@ -468,8 +493,10 @@ optionsPanelContainer.insertBefore(
);

require("ace/ext/language_tools");
require("ace/ext/inline_autocomplete");
env.editor.setOptions({
enableBasicAutocompletion: true,
enableInlineAutocompletion: true,
enableSnippets: true
});

Expand Down
Loading

0 comments on commit eb834a1

Please sign in to comment.