19.3 KB、735行 生 Blame 履歴
1// TextEditor - A syntax highlighting text editor for 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//
9// Include files
10//
11
12#include <cstdio>
13#include <exception>
14#include <fstream>
15
16#include "imgui.h"
17#include "ImGuiFileDialog.h"
18
19#include "editor.h"
20
21
22//
23// Constants
24//
25
26#if __APPLE__
27#define SHORTCUT "Cmd-"
28#else
29#define SHORTCUT "Ctrl-"
30#endif
31
32static const char* demo =
33"// Demo C++ Code\n"
34"\n"
35"#include <iostream>\n"
36"#include <random>\n"
37"#include <vector>\n"
38"\n"
39"int main(int, char**) {\n"
40" std::random_device rd;\n"
41" std::mt19937 gen(rd());\n"
42" std::uniform_int_distribution<> distrib(0, 1000);\n"
43" std::vector<int> numbers;\n"
44"\n"
45" for (auto i = 0; i < 100; i++) {\n"
46" numbers.emplace_back(distrib(gen));\n"
47" }\n"
48"\n"
49" for (auto n : numbers) {\n"
50" std::cout << n << std::endl;\n"
51" }\n"
52"\n"
53" return 0;\n"
54"}\n";
55
56
57//
58// Editor::Editor
59//
60
61Editor::Editor() {
62 // setup text editor with demo text
63 originalText = demo;
64 editor.SetText(demo);
65 editor.SetLanguage(TextEditor::Language::Cpp());
66 version = editor.GetUndoIndex();
67 filename = "untitled";
68}
69
70
71//
72// Editor::newFile
73//
74
75void Editor::newFile() {
76 if (isDirty()) {
77 showConfirmClose([this]() {
78 originalText.clear();
79 editor.SetText("");
80 version = editor.GetUndoIndex();
81 filename = "untitled";
82 buildAutocompleteTrie();
83 });
84
85 } else {
86 originalText.clear();
87 editor.SetText("");
88 version = editor.GetUndoIndex();
89 filename = "untitled";
90 buildAutocompleteTrie();
91 }
92}
93
94
95//
96// Editor::openFile
97//
98
99void Editor::openFile() {
100 if (isDirty()) {
101 showConfirmClose([this]() {
102 showFileOpen();
103 });
104
105 } else {
106 showFileOpen();
107 }
108}
109
110void Editor::openFile(const std::string& path) {
111 try {
112 std::ifstream stream(path.c_str());
113 std::string text;
114
115 stream.seekg(0, std::ios::end);
116 text.reserve(stream.tellg());
117 stream.seekg(0, std::ios::beg);
118
119 text.assign((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());
120 stream.close();
121
122 originalText = text;
123 editor.SetText(text);
124 version = editor.GetUndoIndex();
125 filename = path;
126
127 buildAutocompleteTrie();
128
129 } catch (std::exception& e) {
130 showError(e.what());
131 }
132}
133
134
135//
136// Editor::saveFile
137//
138
139void Editor::saveFile() {
140 try {
141 editor.StripTrailingWhitespaces();
142 std::ofstream stream(filename.c_str());
143 stream << editor.GetText();
144 stream.close();
145 version = editor.GetUndoIndex();
146
147 } catch (std::exception& e) {
148 showError(e.what());
149 }
150}
151
152
153//
154// Editor::render
155//
156
157void Editor::render() {
158 // create the outer window
159 ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f));
160 ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size);
161 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
162 ImGui::Begin("MainWindow", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_MenuBar);
163
164 // add a menubar
165 renderMenuBar();
166
167 // render the text editor widget
168 auto area = ImGui::GetContentRegionAvail();
169 auto& style = ImGui::GetStyle();
170 auto statusBarHeight = ImGui::GetFrameHeight() + 2.0f * style.WindowPadding.y;
171 auto editorSize = ImVec2(0.0f, area.y - style.ItemSpacing.y - statusBarHeight);
172 ImGui::PushFont(nullptr, fontSize);
173 editor.Render("TextEditor", editorSize);
174 ImGui::PopFont();
175
176 // render a statusbar
177 ImGui::Spacing();
178 renderStatusBar();
179
180 if (state ==State::diff) {
181 renderDiff();
182
183 } else if (state == State::openFile) {
184 renderFileOpen();
185
186 } else if (state == State::saveFileAs) {
187 renderSaveAs();
188
189 } else if (state == State::confirmClose) {
190 renderConfirmClose();
191
192 } else if (state == State::confirmQuit) {
193 renderConfirmQuit();
194
195 } else if (state == State::confirmError) {
196 renderConfirmError();
197 }
198
199 ImGui::End();
200 ImGui::PopStyleVar();
201}
202
203
204//
205// Editor::tryToQuit
206//
207
208void Editor::tryToQuit() {
209 if (isDirty()) {
210 showConfirmQuit();
211
212 } else {
213 done = true;
214 }
215}
216
217
218//
219// Editor::renderMenuBar
220//
221
222void Editor::renderMenuBar() {
223 // create menubar
224 if (ImGui::BeginMenuBar()) {
225 if (ImGui::BeginMenu("File")) {
226 if (ImGui::MenuItem("New", SHORTCUT "N")) { newFile(); }
227 if (ImGui::MenuItem("Open...", SHORTCUT "O")) { openFile(); }
228 ImGui::Separator();
229
230 if (ImGui::MenuItem("Save", SHORTCUT "S", nullptr, isSavable())) { saveFile(); }
231 if (ImGui::MenuItem("Save As...")) { showSaveFileAs(); }
232 ImGui::EndMenu();
233 }
234
235 if (ImGui::BeginMenu("Edit")) {
236 if (ImGui::MenuItem("Undo", " " SHORTCUT "Z", nullptr, editor.CanUndo())) { editor.Undo(); }
237#if __APPLE__
238 if (ImGui::MenuItem("Redo", "^" SHORTCUT "Z", nullptr, editor.CanRedo())) { editor.Redo(); }
239#else
240 if (ImGui::MenuItem("Redo", " " SHORTCUT "Y", nullptr, editor.CanRedo())) { editor.Redo(); }
241#endif
242
243 ImGui::Separator();
244 if (ImGui::MenuItem("Cut", " " SHORTCUT "X", nullptr, editor.AnyCursorHasSelection())) { editor.Cut(); }
245 if (ImGui::MenuItem("Copy", " " SHORTCUT "C", nullptr, editor.AnyCursorHasSelection())) { editor.Copy(); }
246 if (ImGui::MenuItem("Paste", " " SHORTCUT "V", nullptr, ImGui::GetClipboardText() != nullptr)) { editor.Paste(); }
247
248 ImGui::Separator();
249 bool flag;
250 flag = editor.IsInsertSpacesOnTabs(); if (ImGui::MenuItem("Insert Spaces on Tabs", nullptr, &flag)) { editor.SetInsertSpacesOnTabs(flag); };
251
252 if (ImGui::MenuItem("Tabs To Spaces")) { editor.TabsToSpaces(); }
253 if (ImGui::MenuItem("Spaces To Tabs", nullptr, nullptr, !editor.IsInsertSpacesOnTabs())) { editor.SpacesToTabs(); }
254 if (ImGui::MenuItem("Strip Trailing Whitespaces")) { editor.StripTrailingWhitespaces(); }
255
256 ImGui::EndMenu();
257 }
258
259 if (ImGui::BeginMenu("Selection")) {
260 if (ImGui::MenuItem("Select All", " " SHORTCUT "A", nullptr, !editor.IsEmpty())) { editor.SelectAll(); }
261 ImGui::Separator();
262
263 if (ImGui::MenuItem("Indent Line(s)", " " SHORTCUT "]", nullptr, !editor.IsEmpty())) { editor.IndentLines(); }
264 if (ImGui::MenuItem("Deindent Line(s)", " " SHORTCUT "[", nullptr, !editor.IsEmpty())) { editor.DeindentLines(); }
265 if (ImGui::MenuItem("Move Line(s) Up", nullptr, nullptr, !editor.IsEmpty())) { editor.MoveUpLines(); }
266 if (ImGui::MenuItem("Move Line(s) Down", nullptr, nullptr, !editor.IsEmpty())) { editor.MoveDownLines(); }
267 if (ImGui::MenuItem("Toggle Comments", " " SHORTCUT "/", nullptr, editor.HasLanguage())) { editor.ToggleComments(); }
268 ImGui::Separator();
269
270 if (ImGui::MenuItem("To Uppercase", nullptr, nullptr, editor.AnyCursorHasSelection())) { editor.SelectionToUpperCase(); }
271 if (ImGui::MenuItem("To Lowercase", nullptr, nullptr, editor.AnyCursorHasSelection())) { editor.SelectionToLowerCase(); }
272 ImGui::Separator();
273
274 if (ImGui::MenuItem("Add Next Occurrence", " " SHORTCUT "D", nullptr, editor.CurrentCursorHasSelection())) { editor.AddNextOccurrence(); }
275 if (ImGui::MenuItem("Select All Occurrences", "^" SHORTCUT "D", nullptr, editor.CurrentCursorHasSelection())) { editor.SelectAllOccurrences(); }
276
277 ImGui::EndMenu();
278 }
279
280 if (ImGui::BeginMenu("View")) {
281 if (ImGui::MenuItem("Zoom In", " " SHORTCUT "+")) { increaseFontSIze(); }
282 if (ImGui::MenuItem("Zoom Out", " " SHORTCUT "-")) { decreaseFontSIze(); }
283 ImGui::Separator();
284
285 bool flag;
286 if (ImGui::MenuItem("Autocomplete", nullptr, &autocomplete)) { setAutocompleteMode(autocomplete); }
287 flag = editor.IsShowWhitespacesEnabled(); if (ImGui::MenuItem("Show Whitespaces", nullptr, &flag)) { editor.SetShowWhitespacesEnabled(flag); };
288 flag = editor.IsShowSpacesEnabled(); if (ImGui::MenuItem("Show Spaces", nullptr, &flag)) { editor.SetShowSpacesEnabled(flag); };
289 flag = editor.IsShowTabsEnabled(); if (ImGui::MenuItem("Show Tabs", nullptr, &flag)) { editor.SetShowTabsEnabled(flag); };
290 flag = editor.IsShowLineNumbersEnabled(); if (ImGui::MenuItem("Show Line Numbers", nullptr, &flag)) { editor.SetShowLineNumbersEnabled(flag); };
291 flag = editor.IsShowingMatchingBrackets(); if (ImGui::MenuItem("Show Matching Brackets", nullptr, &flag)) { editor.SetShowMatchingBrackets(flag); };
292 flag = editor.IsCompletingPairedGlyphs(); if (ImGui::MenuItem("Complete Matching Glyphs", nullptr, &flag)) { editor.SetCompletePairedGlyphs(flag); };
293 flag = editor.IsShowPanScrollIndicatorEnabled(); if (ImGui::MenuItem("Show Pan/Scroll Indicator", nullptr, &flag)) { editor.SetShowPanScrollIndicatorEnabled(flag); };
294 flag = editor.IsMiddleMousePanMode(); if (ImGui::MenuItem("Middle Mouse Pan Mode", nullptr, &flag)) { if (flag) editor.SetMiddleMousePanMode(); else editor.SetMiddleMouseScrollMode(); };
295
296 ImGui::Separator();
297 if (ImGui::MenuItem("Show Diff", " " SHORTCUT "I")) { showDiff(); }
298
299 ImGui::EndMenu();
300 }
301
302 if (ImGui::BeginMenu("Find")) {
303 if (ImGui::MenuItem("Find", " " SHORTCUT "F")) { editor.OpenFindReplaceWindow(); }
304 if (ImGui::MenuItem("Find Next", " " SHORTCUT "G", nullptr, editor.HasFindString())) { editor.FindNext(); }
305 if (ImGui::MenuItem("Find All", "^" SHORTCUT "G", nullptr, editor.HasFindString())) { editor.FindAll(); }
306 ImGui::Separator();
307 ImGui::EndMenu();
308 }
309
310 ImGui::EndMenuBar();
311 }
312
313 // handle keyboard shortcuts
314 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::GetIO().WantCaptureKeyboard) {
315 if (ImGui::IsKeyDown(ImGuiMod_Ctrl)) {
316 if (ImGui::IsKeyPressed(ImGuiKey_N)) { newFile(); }
317 else if (ImGui::IsKeyPressed(ImGuiKey_O)) { openFile(); }
318 else if (ImGui::IsKeyPressed(ImGuiKey_S)) { if (filename == "untitled") { showSaveFileAs(); } else { saveFile(); } }
319 else if (ImGui::IsKeyPressed(ImGuiKey_I)) { showDiff(); }
320 else if (ImGui::IsKeyPressed(ImGuiKey_Equal)) { increaseFontSIze(); }
321 else if (ImGui::IsKeyPressed(ImGuiKey_Minus)) { decreaseFontSIze(); }
322 }
323 }
324}
325
326
327//
328// Editor::renderStatusBar
329//
330
331void Editor::renderStatusBar() {
332 // language support
333 static const char* languages[] = {"C", "C++", "Cs", "AngelScript", "Lua", "Python", "GLSL", "HLSL", "JSON", "Markdown", "SQL"};
334
335 static const TextEditor::Language* definitions[] = {
336 TextEditor::Language::C(),
337 TextEditor::Language::Cpp(),
338 TextEditor::Language::Cs(),
339 TextEditor::Language::AngelScript(),
340 TextEditor::Language::Lua(),
341 TextEditor::Language::Python(),
342 TextEditor::Language::Glsl(),
343 TextEditor::Language::Hlsl(),
344 TextEditor::Language::Json(),
345 TextEditor::Language::Markdown(),
346 TextEditor::Language::Sql()
347 };
348
349 std::string language = editor.GetLanguageName();
350
351 // create a statusbar window
352 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.15f, 0.15f, 0.15f, 1.0f));
353 ImGui::BeginChild("StatusBar", ImVec2(0.0f, 0.0f), ImGuiChildFlags_Borders);
354 ImGui::SetNextItemWidth(120.0f);
355
356 // allow user to select language for colorizing
357 if (ImGui::BeginCombo("##LanguageSelector", language.c_str())) {
358 for (int n = 0; n < IM_ARRAYSIZE(languages); n++) {
359 bool selected = (language == languages[n]);
360
361 if (ImGui::Selectable(languages[n], selected)) {
362 editor.SetLanguage(definitions[n]);
363 buildAutocompleteTrie();
364 }
365
366 if (selected) {
367 ImGui::SetItemDefaultFocus();
368 }
369 }
370
371 ImGui::EndCombo();
372 }
373
374 // determine horizontal gap so the rest is right aligned
375 ImGui::SameLine(0.0f, 0.0f);
376 ImGui::AlignTextToFramePadding();
377
378 int line;
379 int column;
380 int tabSize = editor.GetTabSize();
381 float glyphWidth = ImGui::CalcTextSize("#").x;
382 editor.GetCurrentCursor(line, column);
383
384 // determine status message
385 char status[256];
386
387 auto statusSize = std::snprintf(
388 status,
389 sizeof(status),
390 "Ln %d, Col %d Tab Size: %d File: %s",
391 line + 1,
392 column + 1,
393 tabSize,
394 filename.c_str()
395 );
396
397 auto size = glyphWidth * (statusSize + 3);
398 auto width = ImGui::GetContentRegionAvail().x;
399
400 ImGui::SameLine(0.0f, width - size);
401 ImGui::TextUnformatted(status);
402
403 // render "text dirty" indicator
404 ImGui::SameLine(0.0f, glyphWidth * 1.0f);
405 auto drawlist = ImGui::GetWindowDrawList();
406 auto pos = ImGui::GetCursorScreenPos();
407 auto offset = ImGui::GetFrameHeight() * 0.5f;
408 auto radius = offset * 0.6f;
409 auto color = isDirty() ? IM_COL32(164, 0, 0, 255) : IM_COL32(164, 164, 164, 255);
410 drawlist->AddCircleFilled(ImVec2(pos.x + offset, pos.y + offset), radius, color);
411
412 ImGui::EndChild();
413 ImGui::PopStyleColor();
414}
415
416
417//
418// Editor::showDiff
419//
420
421void Editor::showDiff() {
422 diff.SetLanguage(editor.GetLanguage());
423 diff.SetText(originalText, editor.GetText());
424 state = State::diff;
425}
426
427
428//
429// Editor::showFileOpen
430//
431
432void Editor::showFileOpen() {
433 // open a file selector dialog
434 IGFD::FileDialogConfig config;
435 config.countSelectionMax = 1;
436
437 config.flags =
438 ImGuiFileDialogFlags_Modal |
439 ImGuiFileDialogFlags_DontShowHiddenFiles |
440 ImGuiFileDialogFlags_ReadOnlyFileNameField;
441
442 ImGuiFileDialog::Instance()->OpenDialog("file-open", "Select File to Open...", ".*", config);
443 state = State::openFile;
444}
445
446
447//
448// Editor::showSaveFileAs
449//
450
451void Editor::showSaveFileAs() {
452 IGFD::FileDialogConfig config;
453 config.countSelectionMax = 1;
454
455 config.flags =
456 ImGuiFileDialogFlags_Modal |
457 ImGuiFileDialogFlags_DontShowHiddenFiles |
458 ImGuiFileDialogFlags_ConfirmOverwrite;
459
460 ImGuiFileDialog::Instance()->OpenDialog("file-saveas", "Save File as...", "*", config);
461 state = State::saveFileAs;
462}
463
464
465//
466// Editor::showConfirmClose
467//
468
469void Editor::showConfirmClose(std::function<void()> callback) {
470 onConfirmClose = callback;
471 state = State::confirmClose;
472}
473
474
475//
476// Editor::showConfirmQuit
477//
478
479void Editor::showConfirmQuit() {
480 state = State::confirmQuit;
481}
482
483
484//
485// Editor::showError
486//
487
488void Editor::showError(const std::string &message) {
489 errorMessage = message;
490 state = State::confirmError;
491}
492
493
494//
495// Editor::renderDiff
496//
497
498void Editor::renderDiff() {
499 ImGui::OpenPopup("Changes since Opening File##diff");
500 auto viewport = ImGui::GetMainViewport();
501 ImVec2 center = viewport->GetCenter();
502 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
503
504 if (ImGui::BeginPopupModal("Changes since Opening File##diff", NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
505 diff.Render("diff", viewport->Size * 0.8f, true);
506
507 ImGui::Separator();
508 static constexpr float buttonWidth = 80.0f;
509 auto buttonOffset = ImGui::GetContentRegionAvail().x - buttonWidth;
510 bool sideBySide = diff.GetSideBySideMode();
511
512 if (ImGui::Checkbox("Show side-by-side", &sideBySide)) {
513 diff.SetSideBySideMode(sideBySide);
514 }
515
516 ImGui::SameLine();
517 ImGui::Indent(buttonOffset);
518
519 if (ImGui::Button("OK", ImVec2(buttonWidth, 0.0f)) || ImGui::IsKeyPressed(ImGuiKey_Escape, false)) {
520 state = State::edit;
521 ImGui::CloseCurrentPopup();
522 }
523
524 ImGui::EndPopup();
525 }
526}
527
528
529//
530// Editor::renderFileOpen
531//
532
533void Editor::renderFileOpen() {
534 // handle file open dialog
535 ImVec2 maxSize = ImGui::GetMainViewport()->Size;
536 ImVec2 minSize = maxSize * 0.5f;
537 auto dialog = ImGuiFileDialog::Instance();
538
539 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
540 ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
541
542 if (dialog->Display("file-open", ImGuiWindowFlags_NoCollapse, minSize, maxSize)) {
543 // open selected file (if required)
544 if (dialog->IsOk()) {
545 openFile(dialog->GetFilePathName());
546 state = State::edit;
547 }
548
549 // close dialog
550 dialog->Close();
551 }
552}
553
554
555//
556// Editor::renderSaveAs
557//
558
559void Editor::renderSaveAs() {
560 // handle saveas dialog
561 ImVec2 maxSize = ImGui::GetMainViewport()->Size;
562 ImVec2 minSize = maxSize * 0.5f;
563 auto dialog = ImGuiFileDialog::Instance();
564
565 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
566 ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
567
568 if (dialog->Display("file-saveas", ImGuiWindowFlags_NoCollapse, minSize, maxSize)) {
569 // open selected file if required
570 if (dialog->IsOk()) {
571 filename = dialog->GetFilePathName();
572 saveFile();
573 state = State::edit;
574
575 } else {
576 state = State::edit;
577 }
578
579 // close dialog
580 dialog->Close();
581 }
582}
583
584
585//
586// Editor::renderConfirmClose
587//
588
589void Editor::renderConfirmClose() {
590 ImGui::OpenPopup("Confirm Close");
591 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
592 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
593
594 if (ImGui::BeginPopupModal("Confirm Close", NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
595 ImGui::Text("This file has changed!\nDo you really want to delete it?\n\n");
596 ImGui::Separator();
597
598 static constexpr float buttonWidth = 80.0f;
599 ImGui::Indent(ImGui::GetContentRegionAvail().x - buttonWidth * 2.0f - ImGui::GetStyle().ItemSpacing.x);
600
601 if (ImGui::Button("OK", ImVec2(buttonWidth, 0.0f))) {
602 state = State::edit;
603 onConfirmClose();
604 ImGui::CloseCurrentPopup();
605 }
606
607 ImGui::SameLine();
608
609 if (ImGui::Button("Cancel", ImVec2(buttonWidth, 0.0f)) || ImGui::IsKeyPressed(ImGuiKey_Escape, false)) {
610 state = State::edit;
611 ImGui::CloseCurrentPopup();
612 }
613
614 ImGui::EndPopup();
615 }
616}
617
618
619//
620// Editor::renderConfirmQuit
621//
622
623void Editor::renderConfirmQuit() {
624 ImGui::OpenPopup("Quit Editor?");
625 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
626 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
627
628 if (ImGui::BeginPopupModal("Quit Editor?", NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
629 ImGui::Text("Your text has changed and is not saved!\nDo you really want to quit?\n\n");
630 ImGui::Separator();
631
632 static constexpr float buttonWidth = 80.0f;
633 ImGui::Indent(ImGui::GetContentRegionAvail().x - buttonWidth * 2.0f - ImGui::GetStyle().ItemSpacing.x);
634
635 if (ImGui::Button("OK", ImVec2(buttonWidth, 0.0f))) {
636 done = true;
637 state = State::edit;
638 ImGui::CloseCurrentPopup();
639 }
640
641 ImGui::SameLine();
642
643 if (ImGui::Button("Cancel", ImVec2(buttonWidth, 0.0f)) || ImGui::IsKeyPressed(ImGuiKey_Escape, false)) {
644 state = State::edit;
645 ImGui::CloseCurrentPopup();
646 }
647
648 ImGui::EndPopup();
649 }
650}
651
652
653//
654// Editor::renderConfirmError
655//
656
657void Editor::renderConfirmError() {
658 ImGui::OpenPopup("Error");
659 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
660 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
661
662 if (ImGui::BeginPopupModal("Error", NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
663 ImGui::Text("%s\n", errorMessage.c_str());
664 ImGui::Separator();
665
666 static constexpr float buttonWidth = 80.0f;
667 ImGui::Indent(ImGui::GetContentRegionAvail().x - buttonWidth);
668
669 if (ImGui::Button("OK", ImVec2(buttonWidth, 0.0f)) || ImGui::IsKeyPressed(ImGuiKey_Escape, false)) {
670 errorMessage.clear();
671 state = State::edit;
672 ImGui::CloseCurrentPopup();
673 }
674
675 ImGui::EndPopup();
676 }
677}
678
679
680//
681// Editor::setAutocompleteMode
682//
683
684void Editor::setAutocompleteMode(bool flag) {
685 // see we are turning autocomplete on or off
686 if (flag) {
687 // rebuild word list
688 buildAutocompleteTrie();
689
690 // setup autocomplete by submitting a new configuration
691 TextEditor::AutoCompleteConfig config;
692
693 config.callback = [this](TextEditor::AutoCompleteState& state) {
694 trie.findSuggestions(state.suggestions, state.searchTerm);
695 };
696
697 editor.SetAutoCompleteConfig(&config);
698
699 // enable change tracking
700 // we don't track every keystroke, callbacks can be delayed up to 3000 milliseconds
701 // if you want live tracking, change the 3000 to 0 (performance hit will be minimal for small documents)
702 editor.SetChangeCallback([this]() {
703 buildAutocompleteTrie();
704 }, 3000);
705
706 } else {
707 // disable autocomplete and change tracking
708 editor.SetAutoCompleteConfig(nullptr);
709 editor.SetChangeCallback(nullptr);
710 }
711}
712
713
714//
715// Editor::buildAutocompleteTrie
716//
717
718void Editor::buildAutocompleteTrie() {
719 // empty list first
720 trie.clear();
721
722 // add language words (if required)
723 auto language = editor.GetLanguage();
724
725 if (language) {
726 for (auto& word : language->keywords) { trie.insert(word); }
727 for (auto& word : language->declarations) { trie.insert(word); }
728 for (auto& word : language->identifiers) { trie.insert(word); }
729 }
730
731 // add all identifiers in current document
732 editor.IterateIdentifiers([this](const std::string& identifier) {
733 trie.insert(identifier);
734 });
735}