55.9 KB、1393行 生 Blame 履歴
1// TextEditor - A syntax highlighting text editor for Dear ImGui.
2// Copyright (c) 2024-2026 Johan A. Goossens. All rights reserved.
3//
4// This work is licensed under the terms of the MIT license.
5// For a copy, see <https://opensource.org/licenses/MIT>.
6
7
8#pragma once
9
10
11//
12// Include files
13//
14
15#include <algorithm>
16#include <array>
17#include <chrono>
18#include <functional>
19#include <iterator>
20#include <memory>
21#include <string>
22#include <string_view>
23#include <unordered_map>
24#include <unordered_set>
25#include <vector>
26
27#include "imgui.h"
28
29
30//
31// TextEditor
32//
33
34class IMGUI_API TextEditor {
35public:
36 // constructor
37 TextEditor();
38
39 //
40 // Below is the public API
41 // Public member functions start with an uppercase character to be consistent with Dear ImGui
42 //
43
44 // access editor options
45 inline void SetTabSize(int value) {
46 // this must be called before text is loaded/edited
47 if (document.isEmpty() && transactions.empty()) {
48 document.setTabSize(std::max(1, std::min(8, value)));
49 }
50 }
51
52 inline int GetTabSize() const { return document.getTabSize(); }
53 inline void SetInsertSpacesOnTabs(bool value) { document.setInsertSpacesOnTabs(value); }
54 inline bool IsInsertSpacesOnTabs() const { return document.isInsertSpacesOnTabs(); }
55 inline void SetLineSpacing(float value) { lineSpacing = std::max(1.0f, std::min(2.0f, value)); }
56 inline float GetLineSpacing() const { return lineSpacing; }
57 inline void SetReadOnlyEnabled(bool value) { readOnly = value; }
58 inline bool IsReadOnlyEnabled() const { return readOnly; }
59 inline void SetAutoIndentEnabled(bool value) { autoIndent = value; }
60 inline bool IsAutoIndentEnabled() const { return autoIndent; }
61 inline void SetShowWhitespacesEnabled(bool value) { showSpaces = value; showTabs = value; }
62 inline bool IsShowWhitespacesEnabled() const { return showSpaces && showTabs; }
63 inline void SetShowSpacesEnabled(bool value) { showSpaces = value; }
64 inline bool IsShowSpacesEnabled() const { return showSpaces; }
65 inline void SetShowTabsEnabled(bool value) { showTabs = value; }
66 inline bool IsShowTabsEnabled() const { return showTabs; }
67 inline void SetShowLineNumbersEnabled(bool value) { showLineNumbers = value; }
68 inline bool IsShowLineNumbersEnabled() const { return showLineNumbers; }
69 inline void SetShowScrollbarMiniMapEnabled(bool value) { showScrollbarMiniMap = value; }
70 inline bool IsShowScrollbarMiniMapEnabled() const { return showScrollbarMiniMap; }
71 inline void SetShowPanScrollIndicatorEnabled(bool value) { showPanScrollIndicator = value; }
72 inline bool IsShowPanScrollIndicatorEnabled() const { return showPanScrollIndicator; }
73 inline void SetShowMatchingBrackets(bool value) { showMatchingBrackets = value; showMatchingBracketsChanged = true; }
74 inline bool IsShowingMatchingBrackets() const { return showMatchingBrackets; }
75 inline void SetCompletePairedGlyphs(bool value) { completePairedGlyphs = value; }
76 inline bool IsCompletingPairedGlyphs() const { return completePairedGlyphs; }
77 inline void SetOverwriteEnabled(bool value) { overwrite = value; }
78 inline bool IsOverwriteEnabled() const { return overwrite; }
79 inline void SetMiddleMousePanMode() { panMode = true; }
80 inline void SetMiddleMouseScrollMode() { panMode = false; }
81 inline bool IsMiddleMousePanMode() const { return panMode; }
82
83 // access text (using UTF-8 encoded strings)
84 // (see note below on cursor and scroll manipulation after setting new text)
85 inline void SetText(const std::string_view& text) { setText(text); }
86 inline std::string GetText() const { return document.getText(); }
87 inline std::string GetCursorText(size_t cursor) const { return getCursorText(cursor); }
88
89 inline std::string GetLineText(int line) const {
90 return (line < 0 || line > static_cast<int>(document.size())) ? "" : document.getLineText(line);
91 }
92
93 inline std::string GetSectionText(int startLine, int startColumn, int endLine, int endColumn) const {
94 return document.getSectionText(
95 document.normalizeCoordinate(Coordinate(startLine, startColumn)),
96 document.normalizeCoordinate(Coordinate(endLine, endColumn)));
97 }
98
99 inline void ReplaceSectionText(int startLine, int startColumn, int endLine, int endColumn, const std::string_view& text) {
100 return replaceSectionText(
101 document.normalizeCoordinate(Coordinate(startLine, startColumn)),
102 document.normalizeCoordinate(Coordinate(endLine, endColumn)),
103 text
104 );
105 }
106
107 inline void ClearText() { SetText(""); }
108
109 inline bool IsEmpty() const { return document.isEmpty(); }
110 inline int GetLineCount() const { return document.lineCount(); }
111
112 // render the text editor in a Dear ImGui context
113 inline void Render(const char* title, const ImVec2& size=ImVec2(), bool border=false) { render(title, size, border); }
114
115 // programmatically set focus on the editor
116 inline void SetFocus() { focusOnEditor = true; }
117
118 // clipboard actions
119 inline void Cut() { if (!readOnly) cut(); }
120 inline void Copy() const { copy(); }
121 inline void Paste() { if (!readOnly) paste(); }
122 inline void Undo() { if (!readOnly) undo(); }
123 inline void Redo() { if (!readOnly) redo(); }
124 inline bool CanUndo() const { return !readOnly && transactions.canUndo(); };
125 inline bool CanRedo() const { return !readOnly && transactions.canRedo(); };
126 inline size_t GetUndoIndex() const { return transactions.getUndoIndex(); };
127
128 // manipulate cursors and selections (line numbers are zero-based)
129 inline void SetCursor(int line, int column) { moveTo(document.normalizeCoordinate(Coordinate(line, column)), false); }
130 inline void SelectAll() { selectAll(); }
131 inline void SelectLine(int line) { if (line >= 0 && line < document.lineCount()) selectLine(line); }
132 inline void SelectLines(int start, int end) { if (start >= 0 && end < document.lineCount() && start <= end) selectLines(start, end); }
133 inline void SelectRegion(int startLine, int startColumn, int endLine, int endColumn) { selectRegion(startLine, startColumn, endLine, endColumn); }
134 inline void SelectToBrackets(bool includeBrackets=true) { selectToBrackets(includeBrackets); }
135 inline void GrowSelectionsToCurlyBrackets() { growSelectionsToCurlyBrackets(); }
136 inline void ShrinkSelectionsToCurlyBrackets() { shrinkSelectionsToCurlyBrackets(); }
137 inline void AddNextOccurrence() { addNextOccurrence(); }
138 inline void SelectAllOccurrences() { selectAllOccurrences(); }
139 inline bool AnyCursorHasSelection() const { return cursors.anyHasSelection(); }
140 inline bool AllCursorsHaveSelection() const { return cursors.allHaveSelection(); }
141 inline bool CurrentCursorHasSelection() const { return cursors.currentCursorHasSelection(); }
142 inline void ClearCursors() { cursors.clearAll(); }
143
144 // get cursor positions (the meaning of main and current is explained in README.md)
145 inline size_t GetNumberOfCursors() const { return cursors.size(); }
146 inline void GetCursor(int& line, int& column, size_t cursor) const { return getCursor(line, column, cursor); }
147 inline void GetCursor(int& startLine, int& startColumn, int& endLine, int& endColumn, size_t cursor) const { return getCursor(startLine, startColumn, endLine, endColumn, cursor); }
148 inline void GetMainCursor(int& line, int& column) const { return getCursor(line, column, cursors.getMainIndex()); }
149 inline void GetCurrentCursor(int& line, int& column) const { return getCursor(line, column, cursors.getCurrentIndex()); }
150
151 // alternative API for cursor and selection position using lightweight out struct (line and column are zero-based)
152 struct CursorPosition {
153 CursorPosition() = default;
154 CursorPosition(int l, int c) : line(l), column(c) {}
155 int line = 0;
156 int column = 0;
157 };
158
159 struct CursorSelection {
160 CursorSelection() = default;
161 CursorSelection(CursorPosition s, CursorPosition e) : start(s), end(e) {}
162 CursorPosition start;
163 CursorPosition end;
164 };
165
166 inline CursorPosition GetMainCursorPosition() const { CursorPosition p; getCursor(p.line, p.column, cursors.getMainIndex()); return p; }
167 inline CursorPosition GetCurrentCursorPosition() const { CursorPosition p; getCursor(p.line, p.column, cursors.getCurrentIndex()); return p; }
168 inline CursorPosition GetCursorPosition(size_t cursor) const { CursorPosition p; getCursor(p.line, p.column, cursor); return p; }
169 inline CursorSelection GetCursorSelection(size_t cursor) const { CursorSelection s; getCursor(s.start.line, s.start.column, s.end.line, s.end.column, cursor); return s; }
170 inline CursorSelection GetMainCursorSelection() const { return GetCursorSelection(cursors.getMainIndex()); }
171
172 // get the word at a screen position
173 std::string GetWordAtScreenPos(const ImVec2& screenPos) const;
174
175 // scrolling support
176 enum class Scroll {
177 alignTop,
178 alignMiddle,
179 alignBottom
180 };
181
182 inline void ScrollToLine(int line, Scroll alignment) { scrollToLine(line, alignment); }
183 inline int GetFirstVisibleLine() const { return firstVisibleLine; }
184 inline int GetLastVisibleLine() const { return lastVisibleLine; }
185 inline int GetFirstVisibleColumn() const { return firstVisibleColumn; }
186 inline int GetLastVisibleColumn() const { return lastVisibleColumn; }
187
188 inline float GetLineHeight() const { return glyphSize.y; }
189 inline float GetGlyphWidth() const { return glyphSize.x; }
190
191 // note on setting cursor and scrolling
192 //
193 // calling SetCursor or ScrollToLine has no effect until the next call to Render
194 // this is because we can only do layout calculations when we are in a Dear ImGui drawing context
195 // as a result, SetCursor or ScrollToLine just mark the request and let Render execute it
196 //
197 // the order of the calls is therefore important as they can interfere with each other
198 // so if you call SetText, SetCursor and/or ScrollToLine before Render, the order should be:
199 //
200 // * call SetText first as it resets the entire editor state including cursors and scrolling
201 // * then call SetCursor as it sets the cursor and requests that we make the cursor visible (i.e. scroll to it)
202 // * then call ScrollToLine to mark the exact scroll location (it cancels the possible SetCursor scroll request)
203 // * call Render to properly update the entire state
204 //
205 // this works on opening the editor as well as later
206
207 // find/replace support
208 inline void SelectFirstOccurrenceOf(const std::string_view& text, bool caseSensitive=true, bool wholeWord=false) { selectFirstOccurrenceOf(text, caseSensitive, wholeWord); }
209 inline void SelectNextOccurrenceOf(const std::string_view& text, bool caseSensitive=true, bool wholeWord=false) { selectNextOccurrenceOf(text, caseSensitive, wholeWord); }
210 inline void SelectAllOccurrencesOf(const std::string_view& text, bool caseSensitive=true, bool wholeWord=false) { selectAllOccurrencesOf(text, caseSensitive, wholeWord); }
211 inline void ReplaceTextInCurrentCursor(const std::string_view& text) { if (!readOnly) replaceTextInCurrentCursor(text); }
212 inline void ReplaceTextInAllCursors(const std::string_view& text) { if (!readOnly) replaceTextInAllCursors(text); }
213
214 inline void OpenFindReplaceWindow() { openFindReplace(); }
215 inline void CloseFindReplaceWindow() { closeFindReplace(); }
216 inline void SetFindButtonLabel(const std::string_view& label) { findButtonLabel = label; }
217 inline void SetFindAllButtonLabel(const std::string_view& label) { findAllButtonLabel = label; }
218 inline void SetReplaceButtonLabel(const std::string_view& label) { replaceButtonLabel = label; }
219 inline void SetReplaceAllButtonLabel(const std::string_view& label) { replaceAllButtonLabel = label; }
220 inline bool HasFindString() const { return findText.size(); }
221 inline void FindNext() { findNext(); }
222 inline void FindAll() { findAll(); }
223
224 // access markers (line numbers are zero-based)
225 inline void AddMarker(int line, ImU32 lineNumberColor, ImU32 textColor, const std::string_view& lineNumberTooltip, const std::string_view& textTooltip) { addMarker(line, lineNumberColor, textColor, lineNumberTooltip, textTooltip); }
226 inline void ClearMarkers() { clearMarkers(); }
227 inline bool HasMarkers() const { return markers.size() != 0; }
228
229 // specify a change callback (called when changes are made (including undo/redo))
230 // the delay parameter specifies a time in miliseconds that the editor will wait for before calling
231 // which helps in case you don't need to track every keystroke
232 // passing nullptr deactivates the callback
233 inline void SetChangeCallback(std::function<void()> callback, int delay=0) {
234 delayedChangeCallback = callback;
235 delayedChangeDelay = std::chrono::milliseconds{delay};
236 }
237
238 // detailed change report passed to callback below
239 // this callback is different from the one above as is reports every change (not just a summary) and is very detailed
240 // the insert flag states whether the change was an insert (true) or a delete (false)
241 // in case of an overwrite, there will be two actions (first a delete and then an insert)
242 // the start parameters refer to the insert point or the start of the delete
243 // the end parameters refer to the end of the inserted text or the end of the deleted text
244 // the text parameter contains the inserted or deleted text
245 // line, column and index values are zero-based
246 struct Change {
247 bool insert;
248 int startLine;
249 int startColumn;
250 int startIndex;
251 int endLine;
252 int endColumn;
253 int endIndex;
254 std::string text;
255 };
256
257 // specify a transaction callback (live document changes in great detail)
258 // it provides a list of changes made to the document in a single transaction (in the right order)
259 // be carefull with this callback as it gets very verbose (called on every keystroke, delete, cut, paste, undo and redo)
260 // passing nullptr deactivates the callback
261 inline void SetTransactionCallback(std::function<void(std::vector<Change>&)> callback) { transactionCallback = callback; }
262
263 // line-based callbacks (line numbers are zero-based)
264 // insertor callback is called when for each line inserted and the result is used as the new line specific user data
265 // deletor callback is called for each line deleted (line specific user data is passed to callback)
266 // setting either callback to nullptr will deactivate that callback
267 inline void SetInsertor(std::function<void*(int line)> callback) { document.setInsertor(callback); }
268 inline void SetDeletor(std::function<void(int line, void* data)> callback) { document.setDeletor(callback); }
269
270 // line-based user data (line numbers are zero-based)
271 // allowing integrators to associate external data with select lines or all lines
272 // user data is an opaque void* that must be managed externally
273 // user data is also passed to the decorator callback (see below)
274 // user data is attached to a line and additions/insertions/deletions don't effect this
275 // if a line with user data is removed, it won't come back on a redo
276 // the deletor callback (if specified) is called when a line is deleted (see above)
277 inline void SetUserData(int line, void* data) { document.setUserData(line, data); }
278 inline void* GetUserData(int line) const { return document.getUserData(line); }
279 inline void IterateUserData(std::function<void(int line, void* data)> callback) const { document.iterateUserData(callback); }
280
281 // line-based decoration
282 struct Decorator {
283 int line; // zero-based
284 float width;
285 float height;
286 ImVec2 glyphSize;
287 void* userData;
288 };
289
290 // positive width is number of pixels, negative with is number of glyphs
291 inline void SetLineDecorator(float width, std::function<void(Decorator& decorator)> callback) {
292 decoratorWidth = width;
293 decoratorCallback = callback;
294 }
295
296 inline void ClearLineDecorator() { SetLineDecorator(0.0f, nullptr); }
297 inline bool HasLineDecorator() const { return decoratorWidth != 0.0f && decoratorCallback != nullptr; }
298
299 // setup context menu callbacks (these are called when a user right clicks line numbers or somewhere in the text)
300 // the editor sets up the popup menus, the callback has to populate them
301 inline void SetLineNumberContextMenuCallback(std::function<void(int line)> callback) { lineNumberContextMenuCallback = callback; }
302 inline void ClearLineNumberContextMenuCallback() { SetLineNumberContextMenuCallback(nullptr); }
303 inline bool HasLineNumberContextMenuCallback() const { return lineNumberContextMenuCallback != nullptr; }
304
305 inline void SetTextContextMenuCallback(std::function<void(int line, int column)> callback) { textContextMenuCallback = callback; }
306 inline void ClearTextContextMenuCallback() { SetTextContextMenuCallback(nullptr); }
307 inline bool HasTextContextMenuCallback() const { return textContextMenuCallback != nullptr; }
308
309 // useful functions to work on selections
310 // NOTE: functions provided to FilterSelections or FilterLines should accept and return UTF-8 encoded strings
311 inline void IndentLines() { if (!readOnly) indentLines(); }
312 inline void DeindentLines() { if (!readOnly) deindentLines(); }
313 inline void MoveUpLines() { if (!readOnly) moveUpLines(); }
314 inline void MoveDownLines() { if (!readOnly) moveDownLines(); }
315 inline void ToggleComments() { if (!readOnly && language) toggleComments(); }
316 inline void FilterSelections(std::function<std::string(std::string_view)> filter) { if (!readOnly) filterSelections(filter); }
317 inline void SelectionToLowerCase() { if (!readOnly) selectionToLowerCase(); }
318 inline void SelectionToUpperCase() { if (!readOnly) selectionToUpperCase(); }
319
320 // useful functions to work on entire text
321 inline void StripTrailingWhitespaces() { if (!readOnly) stripTrailingWhitespaces(); }
322 inline void FilterLines(std::function<std::string(std::string_view)> filter) { if (!readOnly) filterLines(filter); }
323 inline void TabsToSpaces() { if (!readOnly) tabsToSpaces(); }
324 inline void SpacesToTabs() { if (!readOnly) spacesToTabs(); }
325
326 // color palette support
327 enum class Color : char {
328 text,
329 keyword,
330 declaration,
331 number,
332 string,
333 punctuation,
334 preprocessor,
335 identifier,
336 knownIdentifier,
337 comment,
338 background,
339 cursor,
340 selection,
341 whitespace,
342 matchingBracketBackground,
343 matchingBracketActive,
344 matchingBracketLevel1,
345 matchingBracketLevel2,
346 matchingBracketLevel3,
347 matchingBracketError,
348 lineNumber,
349 currentLineNumber,
350 count
351 };
352
353 class Palette : public std::array<ImU32, static_cast<size_t>(Color::count)> {
354 public:
355 inline ImU32 get(Color color) const { return at(static_cast<size_t>(color)); }
356 };
357
358 inline void SetPalette(const Palette& newPalette) { paletteBase = newPalette; paletteAlpha = -1.0f; }
359 inline const Palette& GetPalette() const { return paletteBase; }
360 static inline void SetDefaultPalette(const Palette& aValue) { defaultPalette = aValue; }
361 static inline Palette& GetDefaultPalette() { return defaultPalette; }
362
363 static const Palette& GetDarkPalette();
364 static const Palette& GetLightPalette();
365
366 // a single colored character (a glyph)
367 class Glyph {
368 public:
369 // constructors
370 Glyph() = default;
371 Glyph(ImWchar cp) : codepoint(cp) {}
372 Glyph(ImWchar cp, Color col) : codepoint(cp), color(col) {}
373
374 // properties
375 ImWchar codepoint = 0;
376 Color color = Color::text;
377 };
378
379 // iterator used in language-specific tokenizers
380 // this iterator points to unicode codepoints
381 class Iterator {
382 public:
383 // constructors
384 Iterator() = default;
385 Iterator(Glyph* g) : glyph(g) {}
386
387 using iterator_category = std::forward_iterator_tag;
388 using difference_type = std::ptrdiff_t;
389 using value_type = ImWchar;
390 using pointer = ImWchar*;
391 using reference = ImWchar&;
392
393 inline reference operator*() const { return glyph->codepoint; }
394 inline pointer operator->() const { return &(glyph->codepoint); }
395 inline Iterator& operator++() { glyph++; return *this; }
396 inline Iterator operator++(int) { Iterator tmp = *this; glyph++; return tmp; }
397 inline size_t operator-(const Iterator& a) { return glyph - a.glyph; }
398 inline friend bool operator==(const Iterator& a, const Iterator& b) { return a.glyph == b.glyph; };
399 inline friend bool operator!=(const Iterator& a, const Iterator& b) { return !(a.glyph == b.glyph); };
400 inline friend bool operator<(const Iterator& a, const Iterator& b) { return a.glyph < b.glyph; };
401 inline friend bool operator<=(const Iterator& a, const Iterator& b) { return a.glyph <= b.glyph; };
402 inline friend bool operator>(const Iterator& a, const Iterator& b) { return a.glyph > b.glyph; };
403 inline friend bool operator>=(const Iterator& a, const Iterator& b) { return a.glyph >= b.glyph; };
404
405 private:
406 // properties
407 Glyph* glyph;
408 };
409
410 // language support
411 class Language {
412 public:
413 // name of the language
414 std::string name;
415
416 // flag to describe if keywords and identifiers are case sensitive (which is the default)
417 bool caseSensitive = true;
418
419 // the character that starts a preprocessor directive (can be 0 if language doesn't have this feature)
420 ImWchar preprocess = 0;
421
422 // a character sequence that start a single line comment (can be blank if language doesn't have this feature)
423 std::string singleLineComment;
424
425 // an alternate single line comment character sequence (can be blank if language doesn't have this feature)
426 std::string singleLineCommentAlt;
427
428 // the start and end character sequence for multiline comments (can be blank language doesn't have this feature)
429 std::string commentStart;
430 std::string commentEnd;
431
432 // flags specifying whether language supports single quoted ['] and/or double quoted [""] strings
433 bool hasSingleQuotedStrings = false;
434 bool hasDoubleQuotedStrings = false;
435
436 // other character sequences that start and end strings (can be blank if language doesn't have this feature)
437 std::string otherStringStart;
438 std::string otherStringEnd;
439
440 // alternate character sequences that start and end strings (can be blank if language doesn't have this feature)
441 std::string otherStringAltStart;
442 std::string otherStringAltEnd;
443
444 // character inside string used to escape the next character (can be 0 if language doesn't have this feature)
445 ImWchar stringEscape = 0;
446
447 // set of keywords, declarations, identifiers used in the language (can be blank if language doesn't have these features)
448 // if language is not case sensitive, all entries should be in lower case
449 std::unordered_set<std::string> keywords;
450 std::unordered_set<std::string> declarations;
451 std::unordered_set<std::string> identifiers;
452
453 // function to determine if specified character in considered punctuation
454 std::function<bool(ImWchar)> isPunctuation;
455
456 // functions to tokenize identifiers and numbers (can be nullptr if language doesn't have this feature)
457 // start and end refer to the characters being tokenized
458 // functions should return an iterator to the character after the detected token
459 // returning start means no token was found
460 std::function<Iterator(Iterator start, Iterator end)> getIdentifier;
461 std::function<Iterator(Iterator start, Iterator end)> getNumber;
462
463 // function to implement custom tokenizer
464 // if a token is found, function should return an iterator to the character after the token and set the color
465 std::function<Iterator(Iterator start, Iterator end, Color& color)> customTokenizer;
466
467 // predefined language definitions
468 static const Language* C();
469 static const Language* Cpp();
470 static const Language* Cs();
471 static const Language* AngelScript();
472 static const Language* Lua();
473 static const Language* Python();
474 static const Language* Glsl();
475 static const Language* Hlsl();
476 static const Language* Json();
477 static const Language* Markdown();
478 static const Language* Sql();
479 };
480
481 inline void SetLanguage(const Language* l) { language = l; languageChanged = true; }
482 inline const Language* GetLanguage() const { return language; };
483 inline bool HasLanguage() const { return language != nullptr; }
484 inline std::string GetLanguageName() const { return language == nullptr ? "None" : language->name; }
485
486 // iterate through identifiers detected by the colorizer (based on current language)
487 inline void IterateIdentifiers(std::function<void(const std::string& identifier)> callback) const { document.iterateIdentifiers(callback); }
488
489 // autocomplete state (acts as API between editor and outer application)
490 class AutoCompleteState {
491 public:
492 // current context (strings = UTF-8, columns = Nth visible column and indices = Nth codepoint)
493 // to understand the difference between column and index, think like a tab :-)
494 std::string searchTerm;
495 size_t line;
496 size_t searchTermStartColumn;
497 size_t searchTermStartIndex;
498 size_t searchTermEndColumn;
499 size_t searchTermEndIndex;
500
501 bool inIdentifier;
502 bool inNumber;
503 bool inComment;
504 bool inString;
505
506 // currently selected language (could be nullptr if no language is selected)
507 const Language* language;
508
509 // optional opaque void* provided by app when autocomplete was setup
510 void* userData;
511
512 // auto complete suggestions te be provided by app callback (the app is responsible for sorting)
513
514 // the editor does not automatically include language specific keywords or identifiers in the suggestion list
515 // this is left to the application so it can be context specific in case a language server is used
516 // a pointer to the current language definition is provided so callbacks have easy access
517 std::vector<std::string> suggestions;
518
519 // set this to true if you are building the suggestion list asynchronously and provide it later
520 // this way autocomplete is not cancelled if the suggestion list is empty and the user hits tab or enter
521 bool suggestionsPromise = false;
522 };
523
524 // autocomplete configuration (defaults are like Visual Studio Code)
525 class AutoCompleteConfig {
526 public:
527 // specifies whether typing by the user triggers autocomplete
528 bool triggerOnTyping = true;
529
530 // specifies whether the specified shortcut triggers autocomplete
531 bool triggerOnShortcut = true;
532
533 // specifies whether typing (or shortcut) in comments or strings triggers autocomplete
534 bool triggerInComments = false;
535 bool triggerInStrings = false;
536
537 // manual trigger key sequence (default is Ctrl+space on all platforms, even MacOS)
538 // remember Dear ImGui reverses Ctrl and Command on MacOS
539#if __APPLE__
540 ImGuiKeyChord triggerShortcut = ImGuiMod_Super | ImGuiKey_Space;
541#else
542 ImGuiKeyChord triggerShortcut = ImGuiMod_Ctrl | ImGuiKey_Space;
543#endif
544
545 // see if single suggestions are automatically inserted
546 // this only works when triggered manually
547 bool autoInsertSingleSuggestions = false;
548
549 // delay in milliseconds between autocomplete trigger and suggestions popup
550 std::chrono::milliseconds triggerDelay{200};
551
552 // text label used when no suggestions are available (this allows for internationalization)
553 std::string noSuggestionsLabel = "No suggestions";
554
555 // called when autocomplete is configured, active and the editor needs an updated suggestions list
556 // callback must populate and order suggestions in state object
557 // suggestion list is not cleared by editor between callbacks
558 // callback is called during the rendering loop (so don't take too long)
559
560 // if it takes too long, applications should do search in separate thread and
561 // use API to report results (see SetAutoCompleteSuggestions)
562 // callback should set suggestionsPromise to true in this case
563 std::function<void(AutoCompleteState&)> callback;
564
565 // opaque void* that must be managed externally but passed to callback
566 void* userData = nullptr;
567 };
568
569 // configure and activate autocomplete (passing nullptr deactivates it)
570 inline void SetAutoCompleteConfig(const AutoCompleteConfig* config) { autocomplete.setConfig(config); }
571
572 // provide autocomplete suggestions asynchronously (in case a callback takes to long and lookup is handled in a separate thread/process)
573 // this call is not threadsafe and must be called from the rendering thread (you must synchronize with your lookup thread yourself)
574 inline void SetAutoCompleteSuggestions(const std::vector<std::string>& suggestions) { autocomplete.setSuggestions(suggestions); }
575
576 // utility class to support some autocomplete implementations
577 // this is not used by default but can be used in autocomplete callbacks (see example app)
578 class Trie {
579 public:
580 // constructor
581 Trie() { clear(); }
582
583 // clear word tree
584 inline void clear() { root = std::make_unique<Node>(); }
585
586 // insert word (UTF-8 encoded) into tree
587 void insert(const std::string_view& word);
588
589 // populate list of suggestions based on provided search term (which is UTF-8 encoded)
590 // limit is maximum number of suggestions returned after they are sorted by relevance
591 // maxSkippedLetters is a the largest number of letters that can be skipped to find the next match
592 // this allows for missing letters (out of order letters are not taken into account)
593 void findSuggestions(std::vector<std::string>& suggestions, const std::string_view& searchTerm, size_t limit=20, size_t maxSkippedLetters=2);
594
595 private:
596 // definition of single node in the word graph
597 struct Node {
598 std::unordered_map<ImWchar, std::unique_ptr<Node>> children;
599 std::string word;
600 };
601
602 // the root node
603 std::unique_ptr<Node> root;
604
605 // maximum number of letters that can be skipped skip in matching algorithm
606 size_t maxSkip;
607
608 // search term as codepoint vector
609 std::vector<ImWchar> searchCodepoints;
610
611 // possible autocomplete candidates
612 struct Candidate {
613 Candidate(const Node* n, size_t c) : node(n), cost(c) {}
614 bool operator<(const Candidate& rhs) const { return cost < rhs.cost; }
615 bool operator==(const Candidate& rhs) const { return node == rhs.node; }
616 const Node* node;
617 size_t cost;
618 };
619
620 std::vector<Candidate> candidates;
621
622 // utility functions
623 void evaluateNode(const Node* node, size_t index, size_t cost, size_t skip);
624 void addCandidates(const Node* node, size_t cost);
625 };
626
627 // support functions for unicode codepoints
628 class CodePoint {
629 public:
630 static std::string_view::const_iterator skipBOM(std::string_view::const_iterator i, std::string_view::const_iterator end);
631 static std::string_view::const_iterator read(std::string_view::const_iterator i, std::string_view::const_iterator end, ImWchar* codepoint);
632 static size_t write(char* i, ImWchar codepoint); // must point to buffer of 4 characters (returns number of characters written)
633 static bool isLetter(ImWchar codepoint);
634 static bool isNumber(ImWchar codepoint);
635 static bool isWord(ImWchar codepoint);
636 static bool isWhiteSpace(ImWchar codepoint);
637 static bool isXidStart(ImWchar codepoint);
638 static bool isXidContinue(ImWchar codepoint);
639 static bool isLower(ImWchar codepoint);
640 static bool isUpper(ImWchar codepoint);
641 static ImWchar toUpper(ImWchar codepoint);
642 static ImWchar toLower(ImWchar codepoint);
643
644 static constexpr ImWchar singleQuote = '\'';
645 static constexpr ImWchar doubleQuote = '"';
646 static constexpr ImWchar openCurlyBracket = '{';
647 static constexpr ImWchar closeCurlyBracket = '}';
648 static constexpr ImWchar openSquareBracket = '[';
649 static constexpr ImWchar closeSquareBracket = ']';
650 static constexpr ImWchar openParenthesis = '(';
651 static constexpr ImWchar closeParenthesis = ')';
652
653 static inline bool isPairOpener(ImWchar ch) {
654 return
655 ch == openCurlyBracket ||
656 ch == openSquareBracket ||
657 ch == openParenthesis ||
658 ch == singleQuote ||
659 ch == doubleQuote;
660 }
661
662 static inline bool isPairCloser(ImWchar ch) {
663 return
664 ch == closeCurlyBracket ||
665 ch == closeSquareBracket ||
666 ch == closeParenthesis ||
667 ch == singleQuote ||
668 ch == doubleQuote;
669 }
670
671 static inline ImWchar toPairCloser(ImWchar ch) {
672 return
673 (ch == openCurlyBracket) ? closeCurlyBracket :
674 (ch == openSquareBracket) ? closeSquareBracket :
675 (ch == openParenthesis) ? closeParenthesis:
676 ch;
677 }
678
679 static inline ImWchar toPairOpener(ImWchar ch) {
680 return
681 (ch == closeCurlyBracket) ? openCurlyBracket :
682 (ch == closeSquareBracket) ? openSquareBracket :
683 (ch == closeParenthesis) ? openParenthesis:
684 ch;
685 }
686
687 static inline bool isMatchingPair(ImWchar open, ImWchar close) {
688 return isPairOpener(open) && close == toPairCloser(open);
689 }
690
691 static inline bool isBracketOpener(ImWchar ch) {
692 return
693 ch == openCurlyBracket ||
694 ch == openSquareBracket ||
695 ch == openParenthesis;
696 }
697
698 static inline bool isBracketCloser(ImWchar ch) {
699 return
700 ch == closeCurlyBracket ||
701 ch == closeSquareBracket ||
702 ch == closeParenthesis;
703 }
704
705 static inline bool isMatchingBrackets(ImWchar open, ImWchar close) {
706 return isBracketOpener(open) && close == toPairCloser(open);
707 }
708 };
709
710 // set the current ImGui context
711 // this is ONLY necessary if you are compiling this widget as a DLL (which is NOT recommended)
712 // it sets the global variable GImGui, which is not shared across DLL boundaries
713 // see GImGui documentation in imgui.cpp for more details
714 static inline void SetImGuiContext(ImGuiContext* ctx) { ImGui::SetCurrentContext(ctx); }
715
716protected:
717 //
718 // below is the private API
719 // private members (function and variables) start with a lowercase character
720 // private class names start with a lowercase character
721 //
722
723 class Coordinate {
724 // represent a character coordinate from the user's point of view, i. e. consider an uniform grid
725 // on the screen as it is rendered, and each cell has its own coordinate, starting from 0
726 //
727 // tabs are counted as [1..tabsize] count spaces, depending on how many spaces are necessary to
728 // reach the next tab stop
729 //
730 // for example, coordinate (1, 5) represents the character 'B' in a line "\tABC", when tabsize = 4,
731 // because it is rendered as " ABC" on the screen
732
733 public:
734 Coordinate() = default;
735 Coordinate(int l, int c) : line(l), column(c) {}
736
737 inline bool operator ==(const Coordinate& o) const { return line == o.line && column == o.column; }
738 inline bool operator !=(const Coordinate& o) const { return line != o.line || column != o.column; }
739 inline bool operator <(const Coordinate& o) const { return line != o.line ? line < o.line : column < o.column; }
740 inline bool operator >(const Coordinate& o) const { return line != o.line ? line > o.line : column > o.column; }
741 inline bool operator <=(const Coordinate& o) const { return line != o.line ? line < o.line : column <= o.column; }
742 inline bool operator >=(const Coordinate& o) const { return line != o.line ? line > o.line : column >= o.column; }
743
744 inline Coordinate operator -(const Coordinate& o) const { return Coordinate(line - o.line, column - o.column); }
745 inline Coordinate operator +(const Coordinate& o) const { return Coordinate(line + o.line, column + o.column); }
746
747 static inline Coordinate invalid() { static Coordinate invalid(-1, -1); return invalid; }
748 inline bool isValid() const { return line >= 0 && column >= 0; }
749
750 int line = 0;
751 int column = 0;
752 };
753
754 // a single cursor
755 class Cursor {
756 public:
757 // constructors
758 Cursor() = default;
759 Cursor(Coordinate coordinate) : start(coordinate), end(coordinate) {}
760 Cursor(Coordinate s, Coordinate e) : start(s), end(e) {}
761
762 // update the cursor
763 inline void update(Coordinate coordinate) { end = coordinate; updated = true; }
764 inline void update(Coordinate s, Coordinate e) { start = s; end = e; updated = true; }
765 inline void update(Coordinate coordinate, bool keep) { if (keep) update(coordinate); else update(coordinate, coordinate); updated = true; }
766
767 // adjust cursor for insert/delete operations
768 // (these functions assume that insert or delete points are before the cursor)
769 void adjustForInsert(Coordinate insertStart, Coordinate insertEnd);
770 void adjustForDelete(Coordinate deleteStart, Coordinate deleteEnd);
771
772 // access cursor properties
773 inline Coordinate getInteractiveStart() const { return start; }
774 inline Coordinate getInteractiveEnd() const { return end; }
775 inline Coordinate getSelectionStart() const { return start < end ? start : end; }
776 inline Coordinate getSelectionEnd() const { return start > end ? start : end; }
777 inline bool hasSelection() const { return start != end; }
778
779 inline void resetToStart() { update(getSelectionStart(), getSelectionStart()); }
780 inline void resetToEnd() { update(getSelectionEnd(), getSelectionEnd()); }
781
782 inline void setMain(bool value) { main = value; }
783 inline bool isMain() const { return main; }
784
785 inline void setCurrent(bool value) { current = value; }
786 inline bool isCurrent() const { return current; }
787
788 inline void setUpdated(bool value) { updated = value; }
789 inline bool isUpdated() const { return updated; }
790
791 private:
792 // helper functions
793 Coordinate adjustCoordinateForInsert(Coordinate coordinate, Coordinate insertStart, Coordinate insertEnd);
794 Coordinate adjustCoordinateForDelete(Coordinate coordinate, Coordinate deleteStart, Coordinate deleteEnd);
795
796 // properties
797 Coordinate start{0, 0};
798 Coordinate end{0, 0};
799 bool main = false;
800 bool current = true;
801 bool updated = true;
802 };
803
804 // the current list of cursors
805 class Cursors : public std::vector<Cursor> {
806 public:
807 // constructor
808 Cursors() { clearAll(); }
809
810 // reset the cursors
811 void reset();
812
813 // erase all cursors and specify a new one
814 inline void setCursor(Coordinate coordinate) { setCursor(coordinate, coordinate); }
815 void setCursor(Coordinate start, Coordinate end);
816
817 // add a cursor to the list
818 inline void addCursor(Coordinate c) { addCursor(c, c); }
819 void addCursor(Coordinate cursorStart, Coordinate cursorEnd);
820
821 // update the current cursor (the one last added)
822 inline void updateCurrentCursor(Coordinate coordinate) { at(current).update(coordinate); }
823 inline void updateCurrentCursor(Coordinate start, Coordinate end) { at(current).update(start, end); }
824 inline void updateCurrentCursor(Coordinate coordinate, bool keep) { at(current).update(coordinate, keep); }
825
826 // check cursor status
827 inline bool hasMultiple() const { return size() > 1; }
828 bool anyHasSelection() const;
829 bool allHaveSelection() const;
830 inline bool mainCursorHasSelection() const { return at(main).hasSelection(); }
831 inline bool currentCursorHasSelection() const { return at(current).hasSelection(); }
832 inline bool mainHasUpdate() const { return at(main).isUpdated(); }
833 bool anyHasUpdate() const;
834
835 // clear the selections and create the default cursor
836 void clearAll();
837
838 // clear all additional cursors
839 void clearAdditional(bool reset=false);
840
841 // clear all updated flags
842 void clearUpdated();
843
844 // get main/current cursor
845 inline Cursor& getMain() { return at(main); }
846 inline size_t getMainIndex() const { return main; }
847 inline Cursor& getCurrent() { return at(current); }
848 inline size_t getCurrentIndex() const { return current; }
849 inline iterator getMainAsIterator() { return begin() + main; }
850 inline iterator getCurrentAsIterator() { return begin() + current; }
851
852 // update cursors
853 void update();
854
855 // adjust cursors for insert/delete operations
856 // (these functions assume that insert or delete points are before the cursor)
857 void adjustForInsert(iterator start, Coordinate insertStart, Coordinate insertEnd);
858 void adjustForDelete(iterator start, Coordinate deleteStart, Coordinate deleteEnd);
859
860 private:
861 size_t main = 0;
862 size_t current = 0;
863 } cursors;
864
865 // the list of text markers
866 class Marker {
867 public:
868 Marker(ImU32 lc, ImU32 tc, const std::string_view& lt, const std::string_view& tt) :
869 lineNumberColor(lc), textColor(tc), lineNumberTooltip(lt), textTooltip(tt) {}
870
871 ImU32 lineNumberColor;
872 ImU32 textColor;
873 std::string lineNumberTooltip;
874 std::string textTooltip;
875 };
876
877 std::vector<Marker> markers;
878
879 // tokenizer state
880 enum class State : char {
881 inText,
882 inComment,
883 inSingleQuotedString,
884 inDoubleQuotedString,
885 inOtherString,
886 inOtherStringAlt
887 };
888
889 // a single line in a document
890 class Line : public std::vector<Glyph> {
891 public:
892 // state at start of line
893 State state = State::inText;
894
895 // marker reference (0 means no marker for this line)
896 size_t marker = 0;
897
898 // width of this line (in visible columns)
899 int maxColumn = 0;
900
901 // do we need to (re)colorize this line
902 bool colorize = true;
903
904 // user data associated with this line
905 void* userData = nullptr;
906 };
907
908 // the document being edited (Lines of Glyphs)
909 class Document : public std::vector<Line> {
910 public:
911 // constructor
912 Document() { emplace_back(); }
913
914 // access document's tab size and processing options
915 inline void setTabSize(int value) { tabSize = value; }
916 inline int getTabSize() const { return tabSize; }
917 inline void setInsertSpacesOnTabs(bool value) { insertSpacesOnTabs = value; }
918 inline bool isInsertSpacesOnTabs() const { return insertSpacesOnTabs; }
919
920 // manipulate document text (strings should be UTF-8 encoded)
921 void setText(const std::string_view& text);
922 void setText(const std::vector<std::string_view>& text);
923 Coordinate insertText(Coordinate start, const std::string_view& text);
924 void deleteText(Coordinate start, Coordinate end);
925
926 // access document text (strings are UTF-8 encoded)
927 std::string getText() const;
928 std::string getLineText(int line) const;
929 std::string getSectionText(Coordinate start, Coordinate end) const;
930 ImWchar getCodePoint(Coordinate location) const;
931
932 // get line or color state
933 inline State getLineState(int line) const { return at(line).state; }
934 Color getColor(Coordinate location) const;
935
936 // see if document is empty
937 inline bool isEmpty() const { return size() == 1 && at(0).size() == 0; }
938
939 // get number of lines (as an int)
940 inline int lineCount() const { return static_cast<int>(size()); }
941
942 // update maximum column numbers for this document and the specified lines
943 void updateMaximumColumn(int first, int last);
944 inline int getMaxColumn() const { return maxColumn; }
945
946 // translate visible column to line index (and visa versa)
947 size_t getIndex(const Line& line, int column) const;
948 inline size_t getIndex(Coordinate coordinate) const { return getIndex(at(coordinate.line), coordinate.column); }
949 int getColumn(const Line& line, size_t index) const;
950 inline int getColumn(int line, size_t index) const { return getColumn(at(line), index); }
951
952 // coordinate operations in context of document
953 Coordinate getUp(Coordinate from, int lines=1) const;
954 Coordinate getDown(Coordinate from, int lines=1) const;
955 Coordinate getLeft(Coordinate from, bool wordMode=false) const;
956 Coordinate getRight(Coordinate from, bool wordMode=false) const;
957 Coordinate getTop() const;
958 Coordinate getBottom() const;
959 Coordinate getStartOfLine(Coordinate from) const;
960 Coordinate getEndOfLine(Coordinate from) const;
961 inline Coordinate getNextLine(Coordinate from) const { return getRight(getEndOfLine(from)); }
962
963 // search in document
964 Coordinate findWordStart(Coordinate from, bool wordOnly=false) const;
965 Coordinate findWordEnd(Coordinate from, bool wordOnly=false) const;
966 bool findText(Coordinate from, const std::string_view& text, bool caseSensitive, bool wholeWord, Coordinate& start, Coordinate& end) const;
967
968 // see if document was updated this frame (can only be called once)
969 inline bool isUpdated() { auto result = updated; updated = false; return result; }
970 inline void resetUpdated() { updated = false; }
971
972 // line-based callbacks
973 inline void setInsertor(std::function<void*(int line)> callback) { insertor = callback; }
974 inline void setDeletor(std::function<void(int line, void* data)> callback) { deletor = callback; }
975
976 // access line user data
977 void setUserData(int line, void* data);
978 void* getUserData(int line) const;
979 void iterateUserData(std::function<void(int line, void* data)> callback) const;
980
981 // iterate through document to find identifiers
982 void iterateIdentifiers(std::function<void(const std::string& identifier)> callback) const;
983
984 // utility functions
985 bool isWholeWord(Coordinate start, Coordinate end) const;
986 inline bool isEndOfLine(Coordinate from) const { return getIndex(from) == at(from.line).size(); }
987 inline bool isLastLine(int line) const { return line == lineCount() - 1; }
988 Coordinate findPreviousNonWhiteSpace(Coordinate from, bool includeEndOfLine=true) const;
989 Coordinate findNextNonWhiteSpace(Coordinate from, bool includeEndOfLine=true) const;
990 Coordinate normalizeCoordinate(Coordinate coordinate) const;
991 void normalizeCoordinate(float line, float column, Coordinate& glyphCoordinate, Coordinate& cursorCoordinate) const;
992
993 private:
994 int tabSize = 4;
995 bool insertSpacesOnTabs = false;
996 int maxColumn = 0;
997 bool updated = false;
998
999 std::function<void*(int)> insertor;
1000 std::function<void(int, void*)> deletor;
1001
1002 void appendLine();
1003 void insertLine(int line);
1004 void deleteLines(int start, int end);
1005 void clearDocument();
1006 } document;
1007
1008 // single action to be performed on the document as part of a larger transaction
1009 class Action {
1010 public:
1011 // action types
1012 enum class Type : char {
1013 insertText,
1014 deleteText
1015 };
1016
1017 // constructors
1018 Action() = default;
1019 Action(Type t, Coordinate s, Coordinate e, const std::string_view& txt) : type(t), start(s), end(e), text(txt) {}
1020
1021 // properties
1022 Type type;
1023 Coordinate start;
1024 Coordinate end;
1025 std::string text;
1026 };
1027
1028 // a collection of actions for a complete transaction
1029 class Transaction : public std::vector<Action> {
1030 public:
1031 // access state before/after transactions
1032 inline void setBeforeState(const Cursors& cursors) { before = cursors; }
1033 inline const Cursors& getBeforeState() const { return before; }
1034 inline void setAfterState(const Cursors& cursors) { after = cursors; }
1035 inline const Cursors& getAfterState() const { return after; }
1036
1037 // add actions by type
1038 void addInsert(Coordinate start, Coordinate end, std::string_view text) { emplace_back(Action::Type::insertText, start, end, text); };
1039 void addDelete(Coordinate start, Coordinate end, std::string_view text) { emplace_back(Action::Type::deleteText, start, end, text); };
1040
1041 // get number of actions
1042 inline int actions() const { return static_cast<int>(size()); }
1043
1044 private:
1045 // properties
1046 Cursors before;
1047 Cursors after;
1048 };
1049
1050 // transaction list to support do/undo/redo
1051 class Transactions : public std::vector<std::shared_ptr<Transaction>> {
1052 public:
1053 // reset the transactions
1054 void reset();
1055
1056 // create a new transaction
1057 static inline std::shared_ptr<Transaction> create() { return std::make_shared<Transaction>(); }
1058
1059 // add a transaction to the list, execute it and make it undoable
1060 void add(std::shared_ptr<Transaction> transaction);
1061
1062 // undo the last transaction
1063 void undo(Document& document, Cursors& cursors);
1064
1065 // redo the last undone transaction;
1066 void redo(Document& document, Cursors& cursors);
1067
1068 // get status information
1069 inline size_t getUndoIndex() const { return undoIndex; }
1070 inline bool canUndo() const { return undoIndex > 0; }
1071 inline bool canRedo() const { return undoIndex < size(); }
1072 inline size_t getVersion() const { return version; }
1073
1074 private:
1075 size_t undoIndex = 0;
1076 size_t version = 0;
1077 } transactions;
1078
1079 // text colorizer (handles language tokenizing)
1080 class Colorizer {
1081 public:
1082 // update colors in entire document
1083 void updateEntireDocument(Document& document, const Language* language);
1084
1085 // update colors in changed lines in specified document
1086 void updateChangedLines(Document& document, const Language* language);
1087
1088 private:
1089 // update color in a single line
1090 State update(Line& line, const Language* language);
1091
1092 // see if string matches part of line
1093 bool matches(Line::iterator start, Line::iterator end, const std::string_view& text);
1094
1095 // set color for specified range of glyphs
1096 inline void setColor(Line::iterator start, Line::iterator end, Color color) { while (start < end) (start++)->color = color; }
1097 } colorizer;
1098
1099 // details about bracketed text
1100 class BracketPair {
1101 public:
1102 BracketPair(ImWchar sc, Coordinate s, ImWchar ec, Coordinate e, int l) : startChar(sc), start(s), endChar(ec), end(e), level(l) {}
1103 ImWchar startChar;
1104 Coordinate start;
1105 ImWchar endChar;
1106 Coordinate end;
1107 int level;
1108
1109 inline bool isAfter(Coordinate location) const { return start > location; }
1110 inline bool isAround(Coordinate location) const { return start < location && end >= location; }
1111 };
1112
1113 // class responsible for matching brackets
1114 class Bracketeer : public std::vector<BracketPair> {
1115 public:
1116 // reset the bracketeer
1117 void reset();
1118
1119 // update the list of bracket pairs in the document and colorize the relevant glyphs
1120 void update(Document& document);
1121
1122 // find relevant brackets
1123 iterator getEnclosingBrackets(Coordinate location);
1124 iterator getEnclosingCurlyBrackets(Coordinate first, Coordinate last);
1125 iterator getInnerCurlyBrackets(Coordinate first, Coordinate last);
1126
1127 // utility functions
1128 static inline bool isBracketCandidate(Glyph& glyph) {
1129 return glyph.color == Color::punctuation ||
1130 glyph.color == Color::matchingBracketLevel1 ||
1131 glyph.color == Color::matchingBracketLevel2 ||
1132 glyph.color == Color::matchingBracketLevel3 ||
1133 glyph.color == Color::matchingBracketError;
1134 }
1135 } bracketeer;
1136
1137 // autocomplete class
1138 class Autocomplete {
1139 public:
1140 // set the autocomplete configuration
1141 void setConfig(const AutoCompleteConfig* c);
1142
1143 // request autocomplete mode based on triggers (and if allowed by current state)
1144 // return value indicates whether autocomplete was initiated
1145 bool startTyping(Cursors& cursors);
1146 bool startShortcut(Cursors& cursors);
1147
1148 // cancel autocomplete mode (if required)
1149 void cancel();
1150
1151 // update autocomplete state and render (if required)
1152 bool render(Document& document, Cursors& cursors, const Language* language, float textOffset, ImVec2 glyphSize);
1153
1154 // specify a new set of suggestions
1155 void setSuggestions(const std::vector<std::string>& suggestions);
1156
1157 // get information
1158 inline bool isActive() const { return active; }
1159 inline bool hasSuggestions() const { return state.suggestions.size() > 0 || state.suggestionsPromise; }
1160 bool isSpecialKeyPressed() const;
1161 inline ImGuiKeyChord getTriggerShortcut() const { return configuration.triggerShortcut; }
1162 inline Coordinate getStart() const { return startLocation; }
1163 inline std::string getReplacement() { return currentSelection < state.suggestions.size() ? state.suggestions[currentSelection] : ""; }
1164
1165 private:
1166 // properties
1167 bool configured = false;
1168 bool active = false;
1169 bool requestActivation = false;
1170 bool requestDeactivation = false;
1171 Coordinate currentLocation;
1172 Coordinate startLocation;
1173 std::chrono::system_clock::time_point activationTime;
1174 AutoCompleteConfig configuration;
1175 AutoCompleteState state;
1176 bool triggeredManually = false;
1177 size_t currentSelection = 0;
1178 static constexpr float suggestionWidth = 250.0f;
1179
1180 // support functions
1181 void start(Cursors& cursors);
1182 void updateState(Document& document, const Language* language);
1183 void refreshSuggestions();
1184 } autocomplete;
1185
1186 // access the editor's text
1187 void setText(const std::string_view& text);
1188
1189 // render (parts of) the text editor
1190 void render(const char* title, const ImVec2& size, bool border);
1191 void renderSelections();
1192 void renderMarkers();
1193 void renderMatchingBrackets();
1194 void renderText();
1195 void renderCursors();
1196 void renderMargin();
1197 void renderLineNumbers();
1198 void renderDecorations();
1199 void renderScrollbarMiniMap();
1200 void renderPanScrollIndicator();
1201 void renderFindReplace(ImVec2 pos, float width);
1202
1203 // keyboard and mouse interactions
1204 void handleKeyboardInputs();
1205 void handleMouseInteractions();
1206
1207 // manipulate selections/cursors
1208 void selectAll();
1209 void selectLine(int line);
1210 void selectLines(int startLine, int endLine);
1211 void selectRegion(int startLine, int startColumn, int endLine, int endColumn);
1212 void selectToBrackets(bool includeBrackets);
1213 void growSelectionsToCurlyBrackets();
1214 void shrinkSelectionsToCurlyBrackets();
1215
1216 void cut();
1217 void copy() const;
1218 void paste();
1219 void undo();
1220 void redo();
1221
1222 // access cursor locations
1223 void getCursor(int& line, int& column, size_t cursor) const;
1224 void getCursor(int& startLine, int& startColumn, int& endLine, int& endColumn, size_t cursor) const;
1225 std::string getCursorText(size_t cursor) const;
1226
1227 // scrolling support
1228 void makeCursorVisible();
1229 void scrollToLine(int line, Scroll alignment);
1230
1231 // find/replace support
1232 void selectFirstOccurrenceOf(const std::string_view& text, bool caseSensitive, bool wholeWord);
1233 void selectNextOccurrenceOf(const std::string_view& text, bool caseSensitive, bool wholeWord);
1234 void selectAllOccurrencesOf(const std::string_view& text, bool caseSensitive, bool wholeWord);
1235 void addNextOccurrence();
1236 void selectAllOccurrences();
1237
1238 void replaceTextInCurrentCursor(const std::string_view& text);
1239 void replaceTextInAllCursors(const std::string_view& text);
1240 void replaceSectionText(const Coordinate& start, const Coordinate& end, const std::string_view& text);
1241
1242 void openFindReplace();
1243 void closeFindReplace();
1244 void find();
1245 void findNext();
1246 void findAll();
1247 void replace();
1248 void replaceAll();
1249
1250 // marker support
1251 void addMarker(int line, ImU32 lineNumberColor, ImU32 textColor, const std::string_view& lineNumberTooltip, const std::string_view& textTooltip);
1252 void clearMarkers();
1253
1254 // cursor/selection functions
1255 void moveUp(int lines, bool select);
1256 void moveDown(int lines, bool select);
1257 void moveLeft(bool select, bool wordMode);
1258 void moveRight(bool select, bool wordMode);
1259 void moveToTop(bool select);
1260 void moveToBottom(bool select);
1261 void moveToStartOfLine(bool select);
1262 void moveToEndOfLine(bool select);
1263 void moveTo(Coordinate coordinate, bool select);
1264
1265 // add/delete characters
1266 void handleCharacter(ImWchar character);
1267 void handleBackspace(bool wordMode);
1268 void handleDelete(bool wordMode);
1269
1270 // add/delete lines
1271 void removeSelectedLines();
1272 void insertLineAbove();
1273 void insertLineBelow();
1274
1275 // transform selected lines
1276 void indentLines();
1277 void deindentLines();
1278 void moveUpLines();
1279 void moveDownLines();
1280 void toggleComments();
1281
1282 // transform selections (filter function should accept and return UTF-8 encoded strings)
1283 void filterSelections(std::function<std::string(std::string_view)> filter);
1284 void selectionToLowerCase();
1285 void selectionToUpperCase();
1286
1287 // transform entire document (filter function should accept and return UTF-8 encoded strings)
1288 void stripTrailingWhitespaces();
1289 void filterLines(std::function<std::string(std::string_view)> filter);
1290 void tabsToSpaces();
1291 void spacesToTabs();
1292
1293 // transaction functions
1294 // note that strings must be UTF-8 encoded
1295 std::shared_ptr<Transaction> startTransaction(bool cancelsAutoComplete=true);
1296 bool endTransaction(std::shared_ptr<Transaction> transaction);
1297
1298 void insertTextIntoAllCursors(std::shared_ptr<Transaction> transaction, const std::string_view& text);
1299 void deleteTextFromAllCursors(std::shared_ptr<Transaction> transaction);
1300 void autoIndentAllCursors(std::shared_ptr<Transaction> transaction);
1301 Coordinate insertText(std::shared_ptr<Transaction> transaction, Coordinate start, const std::string_view& text);
1302 void deleteText(std::shared_ptr<Transaction> transaction, Coordinate start, Coordinate end);
1303
1304 // editor options
1305 float lineSpacing = 1.0f;
1306 bool readOnly = false;
1307 bool autoIndent = true;
1308 bool showSpaces = true;
1309 bool showTabs = true;
1310 bool showLineNumbers = true;
1311 bool showScrollbarMiniMap = true;
1312 bool showMatchingBrackets = true;
1313 bool completePairedGlyphs = true;
1314 bool overwrite = false;
1315
1316 // rendering context
1317 ImFont* font;
1318 float fontSize;
1319 ImVec2 glyphSize;
1320 ImVec2 lastRenderOrigin;
1321 float lineNumberLeftOffset;
1322 float lineNumberRightOffset;
1323 float decorationOffset;
1324 float textOffset;
1325 float visibleHeight;
1326 int visibleLines;
1327 int firstVisibleLine;
1328 int lastVisibleLine;
1329 float visibleWidth;
1330 int visibleColumns;
1331 int firstVisibleColumn;
1332 int lastVisibleColumn;
1333 float verticalScrollBarSize;
1334 float horizontalScrollBarSize;
1335 float cursorAnimationTimer = 0.0f;
1336 bool ensureCursorIsVisible = false;
1337 int scrollToLineNumber = -1;
1338 Scroll scrollToAlignment = Scroll::alignMiddle;
1339 bool showMatchingBracketsChanged = false;
1340 bool languageChanged = false;
1341
1342 float decoratorWidth = 0.0f;
1343 std::function<void(Decorator&)> decoratorCallback;
1344
1345 std::function<void(int line)> lineNumberContextMenuCallback;
1346 std::function<void(int line, int column)> textContextMenuCallback;
1347 int contextMenuLine = 0;
1348 int contextMenuColumn = 0;
1349
1350 static constexpr int leftMargin = 1; // margins are expressed in glyphs
1351 static constexpr int decorationMargin = 1;
1352 static constexpr int textMargin = 2;
1353 static constexpr int cursorWidth = 1;
1354
1355 // find and replace context
1356 std::string findButtonLabel = "Find";
1357 std::string findAllButtonLabel = "Find All";
1358 std::string replaceButtonLabel = "Replace";
1359 std::string replaceAllButtonLabel = "Replace All";
1360 bool findReplaceVisible = false;
1361 bool focusOnEditor = true;
1362 bool focusOnFind = false;
1363 bool findCancelledAutocomplete = false;
1364 std::string findText;
1365 std::string replaceText;
1366 bool caseSensitiveFind = false;
1367 bool wholeWordFind = false;
1368
1369 // interaction context
1370 float lastClickTime = -1.0f;
1371 ImWchar completePairCloser = 0;
1372 Coordinate completePairLocation;
1373 bool panMode = true;
1374 bool panning = false;
1375 bool scrolling = false;
1376 ImVec2 scrollStart;
1377 bool showPanScrollIndicator = true;
1378 std::function<void()> delayedChangeCallback;
1379 std::chrono::milliseconds delayedChangeDelay;
1380 std::chrono::system_clock::time_point delayedChangeReportTime;
1381 bool delayedChangeDetected = false;
1382 std::function<void(std::vector<Change>&)> transactionCallback;
1383
1384 // color palette support
1385 void updatePalette();
1386 static Palette defaultPalette;
1387 Palette paletteBase;
1388 Palette palette;
1389 float paletteAlpha;
1390
1391 // language support
1392 const Language* language = nullptr;
1393};