forked from santaclose/ImGuiColorTextEdit
-
Notifications
You must be signed in to change notification settings - Fork 40
Expand file tree
/
Copy pathTextEditor.h
More file actions
1862 lines (1545 loc) · 75.8 KB
/
Copy pathTextEditor.h
File metadata and controls
1862 lines (1545 loc) · 75.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// TextEditor - A syntax highlighting text editor for Dear ImGui.
// Copyright (c) 2024-2026 Johan A. Goossens. All rights reserved.
//
// This work is licensed under the terms of the MIT license.
// For a copy, see <https://opensource.org/licenses/MIT>.
#pragma once
//
// Include files
//
#include <algorithm>
#include <array>
#include <chrono>
#include <functional>
#include <iterator>
#include <limits>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_set>
#include <vector>
#include "imgui.h"
//
// TextEditor
//
class IMGUI_API TextEditor {
public:
// constructor
TextEditor();
//
// Below is the public API
// Public member functions start with an uppercase character to be consistent with Dear ImGui
//
struct DocPos {
// represents the logical position of a glyph in a document expressed as a line number and glyph index (both zero-based)
DocPos() = default;
DocPos(size_t line, size_t index) : line(line), index(index) {}
inline bool operator ==(const DocPos& rhs) const { return line == rhs.line && index == rhs.index; }
inline bool operator !=(const DocPos& rhs) const { return line != rhs.line || index != rhs.index; }
inline bool operator <(const DocPos& rhs) const { return line != rhs.line ? line < rhs.line : index < rhs.index; }
inline bool operator >(const DocPos& rhs) const { return line != rhs.line ? line > rhs.line : index > rhs.index; }
inline bool operator <=(const DocPos& rhs) const { return line != rhs.line ? line < rhs.line : index <= rhs.index; }
inline bool operator >=(const DocPos& rhs) const { return line != rhs.line ? line > rhs.line : index >= rhs.index; }
inline DocPos operator -(const DocPos& rhs) const { return DocPos(line - rhs.line, index - rhs.index); }
inline DocPos operator +(const DocPos& rhs) const { return DocPos(line + rhs.line, index + rhs.index); }
size_t line = 0;
size_t index = 0;
};
struct DocSelection {
// represents a range of glyphs from a starting position to an end position
DocSelection() = default;
DocSelection(DocPos start, DocPos end) : start(start), end(end) {}
DocPos start;
DocPos end;
};
struct VisPos {
// represents the visual position of a glyph expressed as a visible row and column number (both zero-based)
// this is not necessarily the same as the document position
// tabs create a horizontal offset between index and column
// word-wrapping creates a vertical offset between line and row
VisPos() = default;
VisPos(size_t row, size_t column) : row(row), column(column) {}
inline bool operator ==(const VisPos& rhs) const { return row == rhs.row && column == rhs.column; }
inline bool operator !=(const VisPos& rhs) const { return row != rhs.row || column != rhs.column; }
inline bool operator <(const VisPos& rhs) const { return row != rhs.row ? row < rhs.row : column < rhs.column; }
inline bool operator >(const VisPos& rhs) const { return row != rhs.row ? row > rhs.row : column > rhs.column; }
inline bool operator <=(const VisPos& rhs) const { return row != rhs.row ? row < rhs.row : column <= rhs.column; }
inline bool operator >=(const VisPos& rhs) const { return row != rhs.row ? row > rhs.row : column >= rhs.column; }
inline VisPos operator -(const VisPos& rhs) const { return VisPos(row - rhs.row, column - rhs.column); }
inline VisPos operator +(const VisPos& rhs) const { return VisPos(row + rhs.row, column + rhs.column); }
size_t row = 0;
size_t column = 0;
};
// access editor's configuration options
inline void SetTabSize(size_t value) { config.tabSize = value; }
inline size_t GetTabSize() const { return config.tabSize; }
inline void SetInsertSpacesOnTabs(bool value) { config.insertSpacesOnTabs = value; }
inline bool IsInsertSpacesOnTabs() const { return config.insertSpacesOnTabs; }
inline void SetLineSpacing(float value) { config.lineSpacing = std::max(1.0f, std::min(2.0f, value)); }
inline float GetLineSpacing() const { return config.lineSpacing; }
inline void SetWordWrapEnabled(bool value) { config.wordWrap = value; }
inline bool IsWordWrapEnabled() const { return config.wordWrap; }
inline void SetReadOnlyEnabled(bool value) { config.readOnly = value; }
inline bool IsReadOnlyEnabled() const { return config.readOnly; }
inline void SetCaretsVisible(bool value) { config.caretsVisible = value; }
inline bool IsCaretsVisible() const { return config.caretsVisible; }
inline void SetAutoIndentEnabled(bool value) { config.autoIndent = value; }
inline bool IsAutoIndentEnabled() const { return config.autoIndent; }
inline void SetShowWhitespacesEnabled(bool value) { config.showSpaces = value; config.showTabs = value; }
inline bool IsShowWhitespacesEnabled() const { return config.showSpaces && config.showTabs; }
inline void SetShowSpacesEnabled(bool value) { config.showSpaces = value; }
inline bool IsShowSpacesEnabled() const { return config.showSpaces; }
inline void SetShowTabsEnabled(bool value) { config.showTabs = value; }
inline bool IsShowTabsEnabled() const { return config.showTabs; }
inline void SetShowLineNumbersEnabled(bool value) { config.showLineNumbers = value; }
inline bool IsShowLineNumbersEnabled() const { return config.showLineNumbers; }
inline void SetShowMiniMapEnabled(bool value) { config.showMiniMap = value; }
inline bool IsShowMiniMapEnabled() const { return config.showMiniMap; }
inline void SetMiniMapColumns(size_t value) { config.miniMapColumns = value; }
inline size_t GetMiniMapColumns() const { return config.miniMapColumns; }
inline void SetShowScrollbarMiniMapEnabled(bool value) { config.showScrollbarMiniMap = value; }
inline bool IsShowScrollbarMiniMapEnabled() const { return config.showScrollbarMiniMap; }
inline void SetShowPanScrollIndicatorEnabled(bool value) { config.showPanScrollIndicator = value; }
inline bool IsShowPanScrollIndicatorEnabled() const { return config.showPanScrollIndicator; }
inline void SetShowMatchingBrackets(bool value) { config.showMatchingBrackets = value; if (!value) { config.lineFolding = false; } }
inline bool IsShowingMatchingBrackets() const { return config.showMatchingBrackets; }
inline void SetCompletePairedGlyphs(bool value) { config.completePairedGlyphs = value; }
inline bool IsCompletingPairedGlyphs() const { return config.completePairedGlyphs; }
inline void SetLineFoldingEnabled(bool value) { config.lineFolding = value; if (value) { config.showMatchingBrackets = true; } }
inline bool IsLineFoldingEnabled() const { return config.lineFolding; }
inline void SetOverwriteEnabled(bool value) { config.overwrite = value; }
inline bool IsOverwriteEnabled() const { return config.overwrite; }
inline void SetMiddleMousePanMode() { config.panMode = true; }
inline void SetMiddleMouseScrollMode() { config.panMode = false; }
inline bool IsMiddleMousePanMode() const { return config.panMode; }
inline void SetLineNumberLeftMargin(size_t value) { config.leftMargin = value; } // margins are expressed in glyphs
inline size_t GetLineNumberLeftMargin() const { return config.leftMargin; }
inline void SetDecorationLeftMargin(size_t value) { config.decorationMargin = value; }
inline size_t GetDecorationLeftMargin() const { return config.decorationMargin; }
inline void SetTextLeftMargin(size_t value) { config.textMargin = value; }
inline size_t GetTextLeftMargin() const { return config.textMargin; }
// access text (using UTF-8 encoded strings)
// (see note below on cursor and scroll manipulation after setting new text)
inline void SetText(const std::string_view& text) { setText(text); }
inline void SetText(const std::vector<std::string_view>& lines) { setText(lines); }
inline std::string GetText() const { return document.getText(); }
inline std::string GetCursorText(size_t cursor) const { return cursor < cursors.size() ? document.getSectionText(cursors[cursor].getSelectionStart(), cursors[cursor].getSelectionEnd()) : ""; }
inline std::string GetLineText(size_t line) const { return line < document.size() ? document.getLineText(line) : ""; }
inline std::string GetSectionText(DocPos start, DocPos end) const { return document.getSectionText(normalizePos(start), normalizePos(end)); }
inline std::string GetSectionText(const DocSelection& selection) const { return GetSectionText(selection.start, selection.end); }
inline void ReplaceSectionText(DocPos start, DocPos end, const std::string_view& text) { replaceSectionText(normalizePos(start), normalizePos(end), text); }
inline void ReplaceSectionText(const DocSelection& selection, const std::string_view& text) { ReplaceSectionText(selection.start, selection.end, text); }
inline void ClearText() { setText(""); }
inline bool IsEmpty() const { return document.isEmpty(); }
inline size_t GetLineCount() const { return document.size(); }
// render the text editor in a Dear ImGui context
// note: if you overwrite windowFlags to for instance add ImGuiWindowFlags_NoSavedSettings
// ensure you keep the default as they are required for the editor
// - ImGuiWindowFlags_NoMove to ensure mouse drag event are passed to the editor
// - ImGuiWindowFlags_HorizontalScrollbar to ensure a horizontal scrollbar is rendered when required
inline bool Render(const char* title, const ImVec2& size=ImVec2(), ImGuiChildFlags childFlags=0, ImGuiWindowFlags windowFlags=ImGuiWindowFlags_NoMove | ImGuiWindowFlags_HorizontalScrollbar) { return render(title, size, childFlags, windowFlags); }
// programmatically set focus on the editor
inline void SetFocus() { focusOnEditor = true; }
// clipboard actions
inline void Cut() { if (!config.readOnly) cut(); }
inline void Copy() const { copy(); }
inline void Paste() { if (!config.readOnly) paste(); }
inline void Undo() { if (!config.readOnly) undo(); }
inline void Redo() { if (!config.readOnly) redo(); }
inline bool CanUndo() const { return !config.readOnly && transactions.canUndo(); };
inline bool CanRedo() const { return !config.readOnly && transactions.canRedo(); };
inline size_t GetUndoIndex() const { return transactions.getUndoIndex(); };
// manipulate cursors and selections (line numbers are zero-based)
inline void SelectAll() { selectAll(); }
inline void SelectLine(size_t line) { selectLine(normalizeLine(line)); }
inline void SelectLines(size_t start, size_t end) { if (end < document.size() && start <= end) { selectLines(start, end); }}
inline void SelectRegion(DocPos start, DocPos end) { selectRegion(normalizePos(start), normalizePos(end)); }
inline void SelectToBrackets(bool includeBrackets=true) { selectToBrackets(includeBrackets); }
inline void GrowSelections() { growSelections(); }
inline void ShrinkSelections() { shrinkSelections(); }
inline void AddNextOccurrence(bool wholeWord=false) { addNextOccurrence(wholeWord); }
inline void SelectAllOccurrences(bool wholeWord=false) { selectAllOccurrences(wholeWord); }
inline bool AnyCursorHasSelection() const { return cursors.anyHasSelection(); }
inline bool AllCursorsHaveSelection() const { return cursors.allHaveSelection(); }
inline bool CursorHasSelection(size_t cursor) const { return cursors.cursorHasSelection(cursor); }
inline bool MainCursorHasSelection() const { return cursors.mainCursorHasSelection(); }
inline bool CurrentCursorHasSelection() const { return cursors.currentCursorHasSelection(); }
inline void ClearCursors() { cursors.clearAll(); }
// get cursor positions (the meaning of main and current is explained in README.md)
inline size_t GetNumberOfCursors() const { return cursors.size(); }
inline DocPos GetCursorPosition(size_t cursor) const { return getCursorPosition(cursor); }
inline DocPos GetMainCursorPosition() const { return getCursorPosition(cursors.getMainIndex()); }
inline DocPos GetCurrentCursorPosition() const { return getCursorPosition(cursors.getCurrentIndex()); }
inline DocSelection GetCursorSelection(size_t cursor) const { return getCursorSelection(cursor); }
inline DocSelection GetMainCursorSelection() const { return getCursorSelection(cursors.getMainIndex()); }
inline DocSelection GetCurrentCursorSelection() const { return getCursorSelection(cursors.getCurrentIndex()); }
// get information at mouse position (e.g. from ImGui::GetMousePos())
inline bool IsMousePosOverGlyph(const ImVec2& mousePos) const { return isMousePosOverGlyph(mousePos); }
inline bool IsMousePosOverTextArea(const ImVec2& mousePos) const { return isMousePosOverTextArea(mousePos); }
inline DocPos GetDocPosAtMousePos(const ImVec2& mousePos) const {return getDocPosAtMousePos(mousePos); }
inline std::string GetWordAtMousePos(const ImVec2& mousePos) const { return getWordAtMousePos(mousePos); }
// scrolling support
enum class Scroll {
alignTop,
alignMiddle,
alignBottom
};
inline void ScrollToLine(size_t line, Scroll alignment) { scrollToLine(normalizeLine(line), alignment); }
inline size_t GetFirstVisibleRow() const { return firstVisibleRow; }
inline size_t GetLastVisibleRow() const { return lastVisibleRow; }
inline size_t GetFirstVisibleColumn() const { return firstVisibleColumn; }
inline size_t GetLastVisibleColumn() const { return lastVisibleColumn; }
// specify a new cursor position and scroll to it (if required)
// if the new position is currently in a folded region, it will be automatically unfolded
inline void SetCursor(DocPos pos) { setCursor(normalizePos(pos)); }
// note on setting scrolling and cursor position
//
// calling SetCursor or ScrollToLine has no effect until the next call to Render
// this is because we can only do layout calculations when we are in a Dear ImGui drawing context
// as a result, SetCursor or ScrollToLine just mark the request and let Render execute it
//
// the order of the calls is therefore important as they can interfere with each other
// so if you call SetText, SetCursor and/or ScrollToLine before Render, the order should be:
//
// * call SetText first as it resets the entire editor state including cursors and scrolling
// * then call SetCursor as it sets the cursor and requests that we make the cursor visible (i.e. scroll to it)
// * then call ScrollToLine to mark the exact scroll location (it cancels the possible SetCursor scroll request)
// * call Render to properly update the entire state
//
// this works while opening the editor as well as later
// get glyph size in pixels
inline float GetLineHeight() const { return glyphSize.y; }
inline float GetGlyphWidth() const { return glyphSize.x; }
// coordinate transformation
inline VisPos DocPos2VisPos(DocPos pos) const { return docPos2VisPos(normalizePos(pos)); }
inline DocPos VisPos2DocPos(VisPos pos) const { return visPos2DocPos(normalizePos(pos)); }
// see if a specified document location is visible (not folded and currently on screen)
inline bool IsDocPosVisible(DocPos pos) const { return isDocPosVisible(normalizePos(pos)); }
// see if a visual position covers a glyph
inline bool IsVisPosOverGlyph(VisPos pos) const { return typeSetter.isVisPosOverGlyph(normalizePos(pos)); }
// find start or end of word from provided position
inline DocPos FindWordStart(DocPos pos, bool wholeWord=false) const { return document.findWordStart(normalizePos(pos), wholeWord); }
inline DocPos FindWordEnd(DocPos pos, bool wholeWord=false) const { return document.findWordEnd(normalizePos(pos), wholeWord); }
// find/replace support
inline void SelectFirstOccurrenceOf(const std::string_view& text, bool caseSensitive=true, bool wholeWord=false) { selectFirstOccurrenceOf(text, caseSensitive, wholeWord); }
inline void SelectNextOccurrenceOf(const std::string_view& text, bool caseSensitive=true, bool wholeWord=false) { selectNextOccurrenceOf(text, caseSensitive, wholeWord); }
inline void SelectAllOccurrencesOf(const std::string_view& text, bool caseSensitive=true, bool wholeWord=false) { selectAllOccurrencesOf(text, caseSensitive, wholeWord); }
inline void ReplaceTextInCurrentCursor(const std::string_view& text) { if (!config.readOnly) replaceTextInCurrentCursor(text); }
inline void ReplaceTextInAllCursors(const std::string_view& text) { if (!config.readOnly) replaceTextInAllCursors(text); }
inline void OpenFindReplaceWindow() { openFindReplace(); }
inline void CloseFindReplaceWindow() { closeFindReplace(); }
inline void SetFindButtonLabel(const std::string_view& label) { findButtonLabel = label; }
inline void SetFindAllButtonLabel(const std::string_view& label) { findAllButtonLabel = label; }
inline void SetReplaceButtonLabel(const std::string_view& label) { replaceButtonLabel = label; }
inline void SetReplaceAllButtonLabel(const std::string_view& label) { replaceAllButtonLabel = label; }
inline bool HasFindString() const { return findText.size(); }
inline void FindNext() { findNext(); }
inline void FindAll() { findAll(); }
// access markers (line numbers are zero-based)
// markers are attached to lines and are not effected by inserts or deletes before
// if a line with a marker is deleted, undo doesn't restore it
inline void AddMarker(size_t line, ImU32 lineNumberColor, ImU32 textColor, const std::string_view& lineNumberTooltip, const std::string_view& textTooltip) { addMarker(normalizeLine(line), lineNumberColor, textColor, lineNumberTooltip, textTooltip); }
inline void ClearMarkers() { clearMarkers(); }
inline bool HasMarkers() const { return markers.size() != 0; }
// access squiggly underlines
// squigglies are attached to glyphs and are not effected by inserts or deletes before
// if a glyph with a squiggle is deleted, undo doesn't restore it
inline void AddSquiggle(DocPos start, DocPos end, size_t type, ImU32 color, const std::string_view& tooltip = std::string_view()) { addSquiggle(normalizePos(start), normalizePos(end), type, color, tooltip); }
inline void ClearSquiggles(DocPos start, DocPos end) { clearSquiggles(normalizePos(start), normalizePos(end)); }
inline void ClearSquiggles(size_t type) { clearSquiggles(type); }
inline void ClearSquiggles() { clearSquiggles(); }
inline bool HasSquiggles() const { return squiggles.size() != 0; }
// specify a change callback (called when changes are made (including undo/redo))
// the delay parameter specifies a time in miliseconds that the editor will wait for before calling
// which helps in case you don't need to track every keystroke
// passing nullptr for callback deactivates the feature
inline void SetChangeCallback(std::function<void()> callback, int delay=0) {
delayedChangeCallback = callback;
delayedChangeDelay = std::chrono::milliseconds(delay);
}
// detailed change report passed to callback below
// this callback is different from the one above as it reports every change (not just a summary) and is very detailed
// the insert flag states whether the change was an insert (true) or a delete (false)
// in case of an overwrite, there will be two actions (first a delete and then an insert)
// the start parameters refer to the insert point or the start of the delete
// the end parameters refer to the end of the inserted text or the end of the deleted text
// the text parameter contains the inserted or deleted text
// line and index values are zero-based
struct Change {
bool insert;
DocPos start;
DocPos end;
std::string text;
};
// specify a transaction callback (live document changes in great detail)
// it provides a list of changes made to the document in a single transaction (in the right order)
// be carefull with this callback as it gets very verbose (called on every keystroke, delete, cut, paste, undo and redo)
// passing nullptr deactivates the callback
inline void SetTransactionCallback(std::function<void(const std::vector<Change>&)> callback) { transactions.setCallback(callback); }
// line-based callbacks (line numbers are zero-based)
// insertor callback is called when for each line inserted and the result is used as the new line specific user data
// deletor callback is called for each line deleted (line specific user data is passed to callback)
// setting either callback to nullptr will deactivate that callback
inline void SetInsertor(std::function<void*(size_t line)> callback) { document.setInsertor(callback); }
inline void SetDeletor(std::function<void(size_t line, void* data)> callback) { document.setDeletor(callback); }
// line-based user data (line numbers are zero-based)
// allowing integrators to associate external data with select lines or all lines
// user data is an opaque void* that must be managed externally
// user data is also passed to the decorator and popup callbacks (see below)
// user data is attached to a line and insertions/deletions don't effect this
// if a line with user data is removed, it won't come back on a redo
// the deletor callback (if specified) is called when a line is deleted (see above)
inline void SetUserData(size_t line, void* data) { document.setUserData(normalizeLine(line), data); }
inline void* GetUserData(size_t line) const { return document.getUserData(normalizeLine(line)); }
inline void IterateUserData(std::function<void(size_t line, void* data)> callback) const { document.iterateUserData(callback); }
// line-based decoration
struct Decorator {
size_t line; // zero-based
float width;
float height;
ImVec2 glyphSize;
void* userData;
};
// setup a line decorator (width is number of glyphs)
inline void SetLineDecorator(size_t width, std::function<void(Decorator& decorator)> callback) {
decoratorWidth = width;
decoratorCallback = callback;
}
inline void ClearLineDecorator() { SetLineDecorator(0, nullptr); }
inline bool HasLineDecorator() const { return decoratorWidth != 0 && decoratorCallback != nullptr; }
// custom text cursor (caret) rendering
struct CustomCaret {
// draw list to submit rendering commands to
ImDrawList* drawList;
// top left corner of glyph where cursor is (in screen coordinates)
// can be used directly to submit drawing commands
ImVec2 glyphPos;
// visible size of glyph
ImVec2 glyphSize;
// flag indicating if cursor is visible (based on configuration and standard blinking algorithm)
// this can be ignored if the custom caret has its own animation algorithm
bool caretVisible;
// color of cursor caret as per the current palette
// this can also be ignored if custom caret has its own palette or animation
ImU32 caretColor;
// index of the cursor being rendered (in case additional cursor information is required)
size_t cursorIndex;
};
inline void SetCustomCaretRenderer(std::function<void(const CustomCaret& data)> callback) { customCaretCallback = callback; }
inline void ClearCustomCaretRenderer() { customCaretCallback = nullptr; }
inline bool HasCustomCaretRenderer() const { return customCaretCallback != nullptr; }
// custom line number renderer
struct CustomLineNumber {
// draw list to submit rendering commands to
ImDrawList* drawList;
// top left corner of line number box
// can be used directly to submit drawing commands
ImVec2 pos;
// visible size of line number box in pixels
ImVec2 size;
// width of line number box in glyphs (this is variable)
// the editor calculates the number of digits required for the highest line number
size_t digits;
// line number to be rendered (zero-based)
size_t lineNumber;
// line number for current cursor (zero-based)
size_t cursorLineNumber;
// line number color from current palette
// this can be ignored if custom renderer has its own palette or animation
ImU32 color;
};
inline void SetCustomLineNumberRenderer(std::function<void(const CustomLineNumber& data)> callback) { customLineNumberCallback = callback; }
inline void ClearCustomLineNumberRenderer() { customLineNumberCallback = nullptr; }
inline bool HasCustomLineNumberRenderer() const { return customLineNumberCallback != nullptr; }
// setup right click or hover callbacks
// the editor sets up a popup menu in the right location
// the callback has to populate it
// context callbacks activate on a right click
// hover callbacks are just based on position (no mouse buttons required)
struct PopupData {
DocPos pos;
void* userData;
};
inline void SetLineNumberContextMenuCallback(std::function<void(PopupData& data)> callback) { lineNumberContextMenuCallback = callback; }
inline void ClearLineNumberContextMenuCallback() { SetLineNumberContextMenuCallback(nullptr); }
inline bool HasLineNumberContextMenuCallback() const { return lineNumberContextMenuCallback != nullptr; }
inline void SetTextContextMenuCallback(std::function<void(PopupData& data)> callback) { textContextMenuCallback = callback; }
inline void ClearTextContextMenuCallback() { SetTextContextMenuCallback(nullptr); }
inline bool HasTextContextMenuCallback() const { return textContextMenuCallback != nullptr; }
inline void SetTextHoverCallback(std::function<void(PopupData& data)> callback) { textHoverCallback = callback; }
inline void ClearTextHoverCallback() { SetTextHoverCallback(nullptr); }
inline bool HasTextHoverCallback() const { return textHoverCallback != nullptr; }
// line folding support (only works when line folding is activated)
inline void FoldAroundLine(size_t line) { if (config.lineFolding) { lineFold.foldAroundLine(document, normalizeLine(line)); } }
inline void UnfoldAroundLine(size_t line) { if (config.lineFolding) { lineFold.unfoldAroundLine(document, normalizeLine(line)); } }
inline void ToggleAtLine(size_t line) { if (config.lineFolding) { lineFold.toggleAtLine(document, normalizeLine(line)); } }
inline void UnfoldAll() { if (config.lineFolding) { lineFold.unfoldAll(document); } }
inline bool IsLineFoldable(size_t line) const { return isLineFoldable(normalizeLine(line)); }
inline bool IsLineFolded(size_t line) const { return isLineFolded(normalizeLine(line)); }
inline bool IsLineVisible(size_t line) const { return isLineVisible(normalizeLine(line)); }
inline bool IsLineHidden(size_t line) const { return isLineHidden(normalizeLine(line)); }
// useful functions to work on selections
// NOTE: functions provided to FilterSelections or FilterLines should accept and return UTF-8 encoded strings
inline void IndentLines() { if (!config.readOnly) indentLines(); }
inline void DeindentLines() { if (!config.readOnly) deindentLines(); }
inline void MoveUpLines() { if (!config.readOnly) moveUpLines(); }
inline void MoveDownLines() { if (!config.readOnly) moveDownLines(); }
inline void ToggleComments() { if (!config.readOnly && config.language) toggleComments(); }
inline void FilterSelections(std::function<std::string(std::string_view)> filter) { if (!config.readOnly) filterSelections(filter); }
inline void SelectionToLowerCase() { if (!config.readOnly) selectionToLowerCase(); }
inline void SelectionToUpperCase() { if (!config.readOnly) selectionToUpperCase(); }
// useful functions to work on entire text
inline void StripTrailingWhitespaces() { if (!config.readOnly) stripTrailingWhitespaces(); }
inline void FilterLines(std::function<std::string(std::string_view)> filter) { if (!config.readOnly) filterLines(filter); }
inline void TabsToSpaces() { if (!config.readOnly) tabsToSpaces(); }
inline void SpacesToTabs() { if (!config.readOnly) spacesToTabs(); }
// color palette support
enum class Color : char {
text,
keyword,
declaration,
number,
string,
punctuation,
preprocessor,
identifier,
knownIdentifier,
comment,
background,
cursor,
selection,
whitespace,
matchingBracketBackground,
matchingBracketActive,
matchingBracketLevel1,
matchingBracketLevel2,
matchingBracketLevel3,
matchingBracketError,
lineNumber,
currentLineNumber,
count
};
struct Palette : public std::array<ImU32, static_cast<size_t>(Color::count)> {
inline ImU32 get(Color color) const { return at(static_cast<size_t>(color)); }
};
inline void SetPalette(const Palette& newPalette) { paletteBase = newPalette; paletteAlpha = -1.0f; }
inline const Palette& GetPalette() const { return paletteBase; }
static inline void SetDefaultPalette(const Palette& aValue) { defaultPalette = aValue; }
static inline Palette& GetDefaultPalette() { return defaultPalette; }
static const Palette& GetDarkPalette();
static const Palette& GetLightPalette();
// line break options
enum class BreakOption : char {
mustBreak,
allowBreak,
noBreak,
undefined
};
// a single colored character (a glyph)
struct Glyph {
// constructors
Glyph() = default;
Glyph(ImWchar cp) : codepoint(cp) {}
Glyph(ImWchar cp, Color col) : codepoint(cp), color(col) {}
// unicode codepoint for this glyph
ImWchar codepoint = 0;
// color for this glyph if a language is specified
// maintained by the Colorizer and the Bracketeer overlays
Color color = Color::text;
// maintained by the TypeSetter overlay
BreakOption breakOption = BreakOption::undefined;
// squiggle reference
size_t squiggle = 0;
};
// iterator used in language-specific tokenizers
// this iterator points to unicode codepoints
class Iterator {
public:
// constructors
Iterator() = default;
Iterator(Glyph* g) : glyph(g) {}
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = ImWchar;
using pointer = ImWchar*;
using reference = ImWchar&;
inline reference operator*() const { return glyph->codepoint; }
inline pointer operator->() const { return &(glyph->codepoint); }
inline Iterator& operator++() { glyph++; return *this; }
inline Iterator operator++(int) { Iterator tmp = *this; glyph++; return tmp; }
inline size_t operator-(const Iterator& a) { return static_cast<size_t>(glyph - a.glyph); }
inline friend bool operator==(const Iterator& a, const Iterator& b) { return a.glyph == b.glyph; };
inline friend bool operator!=(const Iterator& a, const Iterator& b) { return !(a.glyph == b.glyph); };
inline friend bool operator<(const Iterator& a, const Iterator& b) { return a.glyph < b.glyph; };
inline friend bool operator<=(const Iterator& a, const Iterator& b) { return a.glyph <= b.glyph; };
inline friend bool operator>(const Iterator& a, const Iterator& b) { return a.glyph > b.glyph; };
inline friend bool operator>=(const Iterator& a, const Iterator& b) { return a.glyph >= b.glyph; };
private:
// properties
Glyph* glyph;
};
// language support
struct Language {
// name of the language
std::string name;
// flag to describe if keywords and identifiers are case sensitive (which is the default)
bool caseSensitive = true;
// the character that starts a preprocessor directive (can be 0 if language doesn't have this feature)
ImWchar preprocess = 0;
// a character sequence that start a single line comment (can be blank if language doesn't have this feature)
std::string singleLineComment;
// an alternate single line comment character sequence (can be blank if language doesn't have this feature)
std::string singleLineCommentAlt;
// the start and end character sequence for multiline comments (can be blank language doesn't have this feature)
std::string commentStart;
std::string commentEnd;
// functions to help tokenize multilevel, multiline comments (can be nullptr if language doesn't have this feature)
// start and end refer to the characters being tokenized
// functions should return an iterator to the character after the detected token and set level
// returning start means no token was found
std::function<Iterator(Iterator start, Iterator end, size_t& level)> commentLevelStart;
std::function<Iterator(Iterator start, Iterator end, size_t& level)> commentLevelEnd;
// flags specifying whether language supports single quoted ['] and/or double quoted [""] strings
bool hasSingleQuotedStrings = false;
bool hasDoubleQuotedStrings = false;
// other character sequences that start and end strings (can be blank if language doesn't have this feature)
std::string otherStringStart;
std::string otherStringEnd;
// alternate character sequences that start and end strings (can be blank if language doesn't have this feature)
std::string otherStringAltStart;
std::string otherStringAltEnd;
// character inside string used to escape the next character (can be 0 if language doesn't have this feature)
ImWchar stringEscape = 0;
// functions to help tokenize multilevel, multiline strings (can be nullptr if language doesn't have this feature)
// start and end refer to the characters being tokenized
// functions should return an iterator to the character after the detected token and set level
// returning start means no token was found
std::function<Iterator(Iterator start, Iterator end, size_t& level)> stringLevelStart;
std::function<Iterator(Iterator start, Iterator end, size_t& level)> stringLevelEnd;
// does the language use indentation for blocks (e.g Python)
bool indentationForBlocks = false;
// set of keywords, declarations, identifiers used in the language (can be blank if language doesn't have these features)
// if language is not case sensitive, all entries should be in lower case
// guidance for these categories
// 1. these categories refer to different colors in the color palette
// 2. keywords are typically used to highlight control/reserved words in a language (palette color keyword)
// 3. declarations are used in strongly typed languages to highlight builtin types or the keywords to create a type (palette color declaration)
// 4. identifiers are used to color the language predefined variables (palette color knownIdentifier) differently from the user variables (palette color identifier)
std::unordered_set<std::string> keywords;
std::unordered_set<std::string> declarations;
std::unordered_set<std::string> identifiers;
// function to determine if specified character in considered punctuation
std::function<bool(ImWchar)> isPunctuation;
// functions to tokenize identifiers and numbers (can be nullptr if language doesn't have this feature)
// start and end refer to the characters being tokenized
// functions should return an iterator to the character after the detected token
// returning start means no token was found
std::function<Iterator(Iterator start, Iterator end)> getIdentifier;
std::function<Iterator(Iterator start, Iterator end)> getNumber;
// function to implement custom tokenizer (can be nullptr if language doesn't have this feature)
// if a token is found, function should return an iterator to the character after the token and set the color
std::function<Iterator(Iterator start, Iterator end, Color& color)> customTokenizer;
// predefined language definitions
static const Language* C();
static const Language* Cpp();
static const Language* Cs();
static const Language* AngelScript();
static const Language* Lua();
static const Language* Python();
static const Language* Glsl();
static const Language* Hlsl();
static const Language* Json();
static const Language* Markdown();
static const Language* Sql();
};
void SetLanguage(const Language* language);
inline const Language* GetLanguage() const { return config.language; };
inline bool HasLanguage() const { return config.language != nullptr; }
inline std::string GetLanguageName() const { return config.language == nullptr ? "None" : config.language->name; }
inline void SetLanguageChangeCallback(std::function<void()> callback) { languageChangeCallback = callback; }
// iterate through identifiers detected by the colorizer (based on current language)
inline void IterateIdentifiers(std::function<void(const std::string& identifier)> callback) const { document.iterateIdentifiers(callback); }
// autocomplete state (acts as API between editor and outer application)
struct AutoCompleteState {
// current context
std::string searchTerm;
DocPos searchTermStart;
DocPos searchTermEnd;
bool inIdentifier;
bool inNumber;
bool inComment;
bool inString;
// currently selected language (could be nullptr if no language is selected)
const Language* language;
// optional opaque void* provided by app when autocomplete was setup
void* userData;
// auto complete suggestions te be provided by app callback (the app is responsible for sorting)
// the editor does not automatically include language specific keywords or identifiers in the suggestion list
// this is left to the application so it can be context specific in case a language server is used
// a pointer to the current language definition is provided so callbacks have easy access
std::vector<std::string> suggestions;
// set this to true if you are building the suggestion list asynchronously and provide it later
// this way autocomplete is not cancelled if the suggestion list is empty and the user hits tab or enter
bool suggestionsPromise = false;
};
// autocomplete configuration (defaults are like Visual Studio Code)
struct AutoCompleteConfig {
// specifies whether typing by the user triggers autocomplete
bool triggerOnTyping = true;
// specifies whether the specified shortcut triggers autocomplete
bool triggerOnShortcut = true;
// specifies whether typing (or shortcut) in comments or strings triggers autocomplete
bool triggerInComments = false;
bool triggerInStrings = false;
// manual trigger key sequence (default is Ctrl+space on all platforms, even MacOS)
// remember Dear ImGui reverses Ctrl and Command on MacOS
#if __APPLE__
ImGuiKeyChord triggerShortcut = ImGuiMod_Super | ImGuiKey_Space;
#else
ImGuiKeyChord triggerShortcut = ImGuiMod_Ctrl | ImGuiKey_Space;
#endif
// see if single suggestions are automatically inserted
// this only works when triggered manually
bool autoInsertSingleSuggestions = false;
// delay in milliseconds between autocomplete trigger and suggestions popup
std::chrono::milliseconds triggerDelay{200};
// text label used when no suggestions are available (this allows for internationalization)
std::string noSuggestionsLabel = "No suggestions";
// width of suggestion popup expressed in number of glyphs
size_t suggestionWidth = 30;
// called when autocomplete is configured, active and the editor needs an updated suggestions list
// callback must populate and order suggestions in state object
// suggestion list is not cleared by editor between callbacks
// callback is called during the rendering loop (so don't take too long)
// if it takes too long, applications should do search in separate thread and
// use API to report results (see SetAutoCompleteSuggestions)
// callback should set suggestionsPromise to true in this case
std::function<void(AutoCompleteState&)> callback;
// opaque void* that must be managed externally but passed to callback
void* userData = nullptr;
};
// configure and activate autocomplete (passing nullptr deactivates it)
inline void SetAutoCompleteConfig(const AutoCompleteConfig* autoCompleteConfig) { autocomplete.setConfig(autoCompleteConfig); }
// provide autocomplete suggestions asynchronously (in case a callback takes to long and lookup is handled in a separate thread/process)
// this call is not threadsafe and must be called from the rendering thread (you must synchronize with your lookup thread yourself)
inline void SetAutoCompleteSuggestions(const std::vector<std::string>& suggestions) { autocomplete.setSuggestions(suggestions); }
// support functions for unicode codepoints
struct CodePoint {
static std::string_view::const_iterator skipBOM(std::string_view::const_iterator i, std::string_view::const_iterator end);
static std::string_view::const_iterator read(std::string_view::const_iterator i, std::string_view::const_iterator end, ImWchar* codepoint);
static size_t write(char* i, ImWchar codepoint); // must point to buffer of 4 characters (returns number of characters written)
static bool isLetter(ImWchar codepoint);
static bool isNumber(ImWchar codepoint);
static bool isWord(ImWchar codepoint);
static bool isWhiteSpace(ImWchar codepoint);
static bool isXidStart(ImWchar codepoint);
static bool isXidContinue(ImWchar codepoint);
static bool isLower(ImWchar codepoint);
static bool isUpper(ImWchar codepoint);
static bool isEastAsian(ImWchar codepoint);
static ImWchar toUpper(ImWchar codepoint);
static ImWchar toLower(ImWchar codepoint);
static constexpr ImWchar singleQuote = '\'';
static constexpr ImWchar doubleQuote = '"';
static constexpr ImWchar openCurlyBracket = '{';
static constexpr ImWchar closeCurlyBracket = '}';
static constexpr ImWchar openSquareBracket = '[';
static constexpr ImWchar closeSquareBracket = ']';
static constexpr ImWchar openParenthesis = '(';
static constexpr ImWchar closeParenthesis = ')';
static inline bool isPairOpener(ImWchar ch) {
return
ch == openCurlyBracket ||
ch == openSquareBracket ||
ch == openParenthesis ||
ch == singleQuote ||
ch == doubleQuote;
}
static inline bool isPairCloser(ImWchar ch) {
return
ch == closeCurlyBracket ||
ch == closeSquareBracket ||
ch == closeParenthesis ||
ch == singleQuote ||
ch == doubleQuote;
}
static inline ImWchar toPairCloser(ImWchar ch) {
return
(ch == openCurlyBracket) ? closeCurlyBracket :
(ch == openSquareBracket) ? closeSquareBracket :
(ch == openParenthesis) ? closeParenthesis:
ch;
}
static inline ImWchar toPairOpener(ImWchar ch) {
return
(ch == closeCurlyBracket) ? openCurlyBracket :
(ch == closeSquareBracket) ? openSquareBracket :
(ch == closeParenthesis) ? openParenthesis:
ch;
}
static inline bool isMatchingPair(ImWchar open, ImWchar close) {
return isPairOpener(open) && close == toPairCloser(open);
}
static inline bool isBracketOpener(ImWchar ch) {
return
ch == openCurlyBracket ||
ch == openSquareBracket ||
ch == openParenthesis;
}
static inline bool isBracketCloser(ImWchar ch) {
return
ch == closeCurlyBracket ||
ch == closeSquareBracket ||
ch == closeParenthesis;
}
static inline bool isMatchingBrackets(ImWchar open, ImWchar close) {
return isBracketOpener(open) && close == toPairCloser(open);
}
};
// configuration for line break algorithm used when word wrap is active
struct LineBreakConfig {
// wrap mode (false = simple mode, true = unicode line break mode)
bool useUnicodeAnnex14 = false;
// simple mode options (strings of UTF-8 encoded glyphs)
std::string breakAfter = " \t{[(";
std::string breakBefore = ".";
// unicode line breaking options
// based on the unicode standard annex #14 which identifies break
// opportunities expressed as rules which can be (de)activated below
// see https://www.unicode.org/reports/tr14 for details
bool lb2 = true;
bool lb3 = true;
bool lb4 = true;
bool lb5 = true;
bool lb6 = true;
bool lb7 = true;
bool lb8 = true;
bool lb8a = true;
bool lb9 = true;
bool lb10 = true;
bool lb11 = true;
bool lb12 = true;
bool lb12a = true;
bool lb13 = true;
bool lb14 = true;
bool lb15a = true;
bool lb15b = true;
bool lb15c = true;
bool lb15d = true;
bool lb16 = true;
bool lb17 = true;
bool lb18 = true;
bool lb19 = true;
bool lb19a = true;
bool lb20 = true;
bool lb20a = true;
bool lb21 = true;
bool lb21a = true;
bool lb21b = true;
bool lb22 = true;
bool lb23 = true;
bool lb23a = true;
bool lb24 = true;
bool lb25 = true;
bool lb26 = true;
bool lb27 = true;
bool lb28 = true;
bool lb28a = true;
bool lb29 = true;
bool lb30 = true;
bool lb30a = true;
bool lb30b = true;
};
// set the line break configuration
inline void SetLineBreakConfig(LineBreakConfig& newConfig) { typeSetter.setLineBreakConfig(newConfig); }
// set the current ImGui context
//
// this is ONLY necessary if you are compiling this widget as a DLL (which is NOT recommended)
// it sets the global variable GImGui, which is not shared across DLL boundaries
// see GImGui documentation in imgui.cpp for more details
static inline void SetImGuiContext(ImGuiContext* ctx) { ImGui::SetCurrentContext(ctx); }
protected:
//
// below is the private API
// private members (functions and variables) start with a lowercase character
// private type names start with a uppercase character
//
// everybody needs a friend
friend class TextDiff;
// editor configuration
struct Config {
// options
size_t tabSize = 4;
bool insertSpacesOnTabs = false;
float lineSpacing = 1.0f;
bool wordWrap = false;
bool readOnly = false;
bool caretsVisible = true;
bool autoIndent = true;
bool showSpaces = true;
bool showTabs = true;
bool showLineNumbers = true;
bool showMiniMap = false;
size_t miniMapColumns = 0;
bool showScrollbarMiniMap = true;
bool showMatchingBrackets = true;
bool completePairedGlyphs = true;
bool lineFolding = false;
bool overwrite = false;
bool panMode = true;
bool showPanScrollIndicator = true;
size_t leftMargin = 1; // margins are expressed in number of glyphs
size_t decorationMargin = 1;
size_t textMargin = 2;
// language support
const Language* language = nullptr;
// word wrap limits
size_t wordWrapColumns = 0;
} config;
// colorizer/tokenizer state
enum class LineState : char {
inText,
inComment,
inCommentLevel1,
inCommentLevel2,
inCommentLevel3,
inCommentLevel4,
inCommentLevel5,
inCommentLevel6,
inCommentLevel7,
inSingleQuotedString,
inDoubleQuotedString,
inOtherString,
inOtherStringAlt,
inStringLevel0,
inStringLevel1,
inStringLevel2,
inStringLevel3,
inStringLevel4,
inStringLevel5,
inStringLevel6,
inStringLevel7
};
static inline bool lineStateInComment(LineState state) { return state >= LineState::inComment && state <= LineState::inCommentLevel7; }
static inline bool lineStateInString(LineState state) { return state >= LineState::inSingleQuotedString && state <= LineState::inStringLevel7; }
static inline bool lineStateInStringLevel(LineState state) { return state >= LineState::inStringLevel0 && state <= LineState::inStringLevel7; }
static inline LineState commentLevelToLineState(size_t level) { return static_cast<LineState>(static_cast<int>(LineState::inComment) + level); }
static inline LineState stringLevelToLineState(size_t level) { return static_cast<LineState>(static_cast<int>(LineState::inStringLevel0) + level); }
static constexpr size_t maxCommentLevel = static_cast<int>(LineState::inCommentLevel7) - static_cast<int>(LineState::inComment);
static constexpr size_t maxStringLevel = static_cast<int>(LineState::inStringLevel7) - static_cast<int>(LineState::inStringLevel0);
// line folding state
enum class FoldingState : char {
foldable,
folded,
visible,
hidden
};
// information for wrapped lines
struct LineSection {
LineSection() = default;
LineSection(size_t startIndex, size_t endIndex, size_t columns, size_t indent) :
startIndex(startIndex),endIndex(endIndex), columns(columns), indent(indent) {}
size_t startIndex;
size_t endIndex;
size_t columns;
size_t indent;