6.4 KB、176行 表示 生 Blame 履歴
1# Autocomplete
2
3The text editor provides an optional autocomplete framework. Once configured, the editor
4takes care of activation events (triggering), state tracking, visualization and insertion
5of suggestions with full undo/redo. The application is responsible for providing the list
6of suggestions through a callback or the API. This allows simple implementations to provide
7suggestions in realtime and allows other implementations to do things asynchronously like
8reaching out to a language server. Autocomplete can't be triggered when multiple cursors
9are active as this causes a mess. Try it in Visual Studio Code if you want to see what I mean.
10
11![Autocomplete](autocomplete.png)
12
13To activate the feature, the app must provide a configuration like:
14
15```c++
16TextEditor::AutoCompleteConfig config;
17
18config.callback = [this](TextEditor::AutoCompleteState& state) {
19 ....
20};
21
22editor.SetAutoCompleteConfig(&config);
23```
24
25Deactivation can be achieved by passing **nullptr** to **SetAutoCompleteConfig**.
26The **TextEditor::AutoCompleteConfig** class contains all the configuration options
27and is defined as (please note defaults):
28
29```c++
30class AutoCompleteConfig {
31public:
32 // specifies whether typing by the user triggers autocomplete
33 bool triggerOnTyping = true;
34
35 // specifies whether the specified shortcut triggers autocomplete
36 bool triggerOnShortcut = true;
37
38 // specifies whether typing (or shortcut) in comments or strings triggers autocomplete
39 bool triggerInComments = false;
40 bool triggerInStrings = false;
41
42 // manual trigger key sequence (default is Ctrl+space on all platforms, even MacOS)
43 // remember Dear ImGui reverses Ctrl and Command on MacOS
44#if __APPLE__
45 ImGuiKeyChord triggerShortcut = ImGuiMod_Super | ImGuiKey_Space;
46#else
47 ImGuiKeyChord triggerShortcut = ImGuiMod_Ctrl | ImGuiKey_Space;
48#endif
49
50 // see if single suggestions are automatically inserted
51 // this only works when triggered manually
52 bool autoInsertSingleSuggestions = false;
53
54 // delay in milliseconds between autocomplete trigger and suggestions popup
55 std::chrono::milliseconds triggerDelay{200};
56
57 // text label used when no suggestions are available (this allows for internationalization)
58 std::string noSuggestionsLabel = "No suggestions";
59
60 // called when autocomplete is configured, active and the editor needs an updated suggestions list
61 // callback must populate and order suggestions in state object
62 // suggestion list is not cleared by editor between callbacks
63 // callback is called during the rendering process (so don't take too long)
64
65 // if it takes too long, applications should do search in separate thread and
66 // use API to report results (see SetAutoCompleteSuggestions)
67 // callback should set suggestionsPromise to true in this case
68 std::function<void(AutoCompleteState&)> callback;
69
70 // optional opaque void* that must be managed externally but passed to callback
71 void* userData = nullptr;
72};
73```
74
75When the callback is activated, a **TextEditor::AutoCompleteState** object is passed
76informing the app about the context and providing space to return suggestions.
77It is defined as:
78
79```c++
80class AutoCompleteState {
81public:
82 // current context (strings = UTF-8, columns = Nth visible column and indices = Nth codepoint)
83 // to understand the difference between column and index, think like a tab :-)
84 std::string searchTerm;
85 size_t line;
86 size_t searchTermStartColumn;
87 size_t searchTermStartIndex;
88 size_t searchTermEndColumn;
89 size_t searchTermEndIndex;
90
91 bool inIdentifier;
92 bool inNumber;
93 bool inComment;
94 bool inString;
95
96 // currently selected language (could be nullptr if no language is selected)
97 const Language* language;
98
99 // opaque void* provided by app when autocomplete was setup
100 void* userData;
101
102 // auto complete suggestions te be provided by app callback (the app is responsible for sorting)
103
104 // the editor does not automatically include language specific keywords or identifiers in the suggestion list
105 // this is left to the application so it can be context specific in case a language server is used
106 // a pointer to the current language definition is provided so callbacks have easy access
107 std::vector<std::string> suggestions;
108
109 // set this to true if you are building the suggestion list asynchronously and provide it later
110 // this way autocomplete is not cancelled if the suggestion list is empty and the user hits tab or enter
111 bool suggestionsPromise = false;
112};
113```
114
115The editor also comes with a Trie class that implements fuzzy searching and
116the example app shows how it can be used. Given that this is a primitive,
117poor-man's solution, more sophisticated solutions probably required external
118language engines/services that are beyond the scope of this editor. With the
119provided API however, connections to external capabilities can be established
120and context-sensitive suggestions can be provided based on the most advanced
121algorithms (even good-old AI slop :-).
122
123Below is a quick snippet that shows how to use the Trie class to implement
124a poor-man's autocomplete without using a language server. This snippet
125was taken from the [example application](../example/) so can see it in
126context.
127
128```c++
129void Editor::setAutocompleteMode(bool flag) {
130 // see we are turning autocomplete on or off
131 if (flag) {
132 // rebuild word list
133 buildAutocompleteTrie();
134
135 // setup autocomplete by submitting a new configuration
136 TextEditor::AutoCompleteConfig config;
137
138 config.callback = [this](TextEditor::AutoCompleteState& state) {
139 trie.findSuggestions(state.suggestions, state.searchTerm);
140 };
141
142 editor.SetAutoCompleteConfig(&config);
143
144 // enable change tracking
145 // we don't track every keystroke, callbacks can be delayed up to 3000 milliseconds
146 // if you want live tracking, change 3000 to 0 (performance hit will be minimal for small documents)
147 editor.SetChangeCallback([this]() {
148 buildAutocompleteTrie();
149 }, 3000);
150
151 } else {
152 // disable autocomplete and change tracking
153 editor.SetAutoCompleteConfig(nullptr);
154 editor.SetChangeCallback(nullptr);
155 }
156}
157
158void Editor::buildAutocompleteTrie() {
159 // empty list first
160 trie.clear();
161
162 // add language words (if required)
163 auto language = editor.GetLanguage();
164
165 if (language) {
166 for (auto& word : language->keywords) { trie.insert(word); }
167 for (auto& word : language->declarations) { trie.insert(word); }
168 for (auto& word : language->identifiers) { trie.insert(word); }
169 }
170
171 // add all identifiers in current document
172 editor.IterateIdentifiers([this](const std::string& identifier) {
173 trie.insert(identifier);
174 });
175}
176```