// Part of ImGui Bundle - MIT License - Copyright (c) 2022-2026 Pascal Thomet - https://github.com/pthom/imgui_bundle #include "imgui.h" #include "immapp/immapp.h" #include "ImGuiColorTextEdit/TextEditor.h" #include "ImGuiColorTextEdit/TextDiff.h" #include "demo_utils/api_demos.h" #include #include #include #include #include // ============================================================================ // Source display helper: shows the code of a demo function in a collapsible section // ============================================================================ namespace { std::string gCppContent; // loaded once std::string gPythonContent; // loaded once void LoadFileContents() { if (gCppContent.empty()) gCppContent = ReadCppCode("demo_text_edit"); if (gPythonContent.empty()) gPythonContent = ReadPythonCode("demo_text_edit"); } // Extract source between a separator comment and the next one. // For C++: searches for "void funcName()" preceded by "// ====" // For Python: searches for "def pyFuncName(" preceded by "# ====" std::string ExtractFunctionSource(const std::string& content, const std::string& needle, const std::string& separator) { auto pos = content.find(needle); if (pos == std::string::npos) return "// Source not found for " + needle; auto commentStart = content.rfind(separator, pos); if (commentStart != std::string::npos) pos = commentStart; auto endPos = content.find(separator, pos + needle.size()); if (endPos == std::string::npos) endPos = content.size(); return content.substr(pos, endPos - pos); } // Show a "Show source" checkbox with C++/Python toggle. // When checked, displays the function's source in a read-only editor (light theme). // cppFuncName: e.g. "DemoBasicEditor", pyFuncName: e.g. "demo_basic_editor" void ShowSourceToggle(const char* cppFuncName, const char* pyFuncName) { static std::map showFlags; static std::map langFlags; // 0 = C++, 1 = Python static std::map sourceEditors; std::string key(cppFuncName); bool& show = showFlags[key]; int& lang = langFlags[key]; ImGui::Checkbox("Show source", &show); if (show) { ImGui::SameLine(); ImGui::RadioButton("C++", &lang, 0); ImGui::SameLine(); ImGui::RadioButton("Python", &lang, 1); ImGui::SeparatorText("Source"); LoadFileContents(); // Build editor key per language to keep separate editors std::string editorKey = key + (lang == 0 ? "_cpp" : "_py"); if (sourceEditors.find(editorKey) == sourceEditors.end()) { auto& ed = sourceEditors[editorKey]; if (lang == 0) ed.SetText(ExtractFunctionSource(gCppContent, std::string("void ") + cppFuncName + "()", "// ====")); else ed.SetText(ExtractFunctionSource(gPythonContent, std::string("def ") + pyFuncName + "(", "# ====")); ed.SetLanguage(lang == 0 ? TextEditor::Language::Cpp() : TextEditor::Language::Python()); ed.SetPalette(TextEditor::GetLightPalette()); ed.SetReadOnlyEnabled(true); ed.SetCaretsVisible(false); } auto codeFont = ImGuiMd::GetCodeFont(); ImGui::PushFont(codeFont.font, codeFont.size); std::string id = std::string("##src_") + editorKey; sourceEditors[editorKey].Render(id.c_str(), ImVec2(-1, ImGui::GetTextLineHeight() * 15), false); ImGui::PopFont(); ImGui::SeparatorText("Demo"); } } } // anonymous namespace // ============================================================================ // Tab 1: Basic Editor // Demonstrates: text loading, language selection, palette switching // ============================================================================ void DemoBasicEditor() { ShowSourceToggle("DemoBasicEditor", "demo_basic_editor"); static bool initialized = false; static TextEditor editor; if (!initialized) { LoadFileContents(); editor.SetText(gCppContent); editor.SetLanguage(TextEditor::Language::Cpp()); initialized = true; } // Palette buttons if (ImGui::SmallButton("Dark")) editor.SetPalette(TextEditor::GetDarkPalette()); ImGui::SameLine(); if (ImGui::SmallButton("Light")) editor.SetPalette(TextEditor::GetLightPalette()); // Language selection ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::CalcTextSize("AngelScript__").x); static int langIdx = 1; // default: C++ const char* langNames[] = { "None", "C++", "C", "Python", "GLSL", "HLSL", "Lua", "SQL", "AngelScript", "C#", "JSON", "Markdown" }; if (ImGui::Combo("Language", &langIdx, langNames, IM_ARRAYSIZE(langNames))) { const TextEditor::Language* langs[] = { nullptr, TextEditor::Language::Cpp(), TextEditor::Language::C(), TextEditor::Language::Python(), TextEditor::Language::Glsl(), TextEditor::Language::Hlsl(), TextEditor::Language::Lua(), TextEditor::Language::Sql(), TextEditor::Language::AngelScript(), TextEditor::Language::Cs(), TextEditor::Language::Json(), TextEditor::Language::Markdown() }; editor.SetLanguage(langs[langIdx]); } // Cursor position display (doc coords: row != line once word-wrap is on) ImGui::SameLine(); auto pos = editor.GetMainCursorPosition(); ImGui::Text("Line: %zu Col: %zu (doc)", pos.line + 1, pos.index + 1); // Second row: editor view/behavior toggles bool wrap = editor.IsWordWrapEnabled(); if (ImGui::Checkbox("Word Wrap", &wrap)) editor.SetWordWrapEnabled(wrap); ImGui::SameLine(); bool readOnly = editor.IsReadOnlyEnabled(); if (ImGui::Checkbox("Read Only", &readOnly)) editor.SetReadOnlyEnabled(readOnly); ImGui::SameLine(); bool folding = editor.IsLineFoldingEnabled(); if (ImGui::Checkbox("Line Folding", &folding)) editor.SetLineFoldingEnabled(folding); ImGui::SameLine(); ImGui::BeginDisabled(!folding); if (ImGui::SmallButton("Unfold All")) editor.UnfoldAll(); ImGui::EndDisabled(); ImGui::SameLine(); bool miniMap = editor.IsShowMiniMapEnabled(); if (ImGui::Checkbox("Show Mini Map", &miniMap)) editor.SetShowMiniMapEnabled(miniMap); ImGui::TextDisabled("(folding uses brackets for C/C++, indentation for Python)"); // Render editor: we shall use a monospace font auto codeFont = ImGuiMd::GetCodeFont(); ImGui::PushFont(codeFont.font, codeFont.size); editor.Render("##basic"); ImGui::PopFont(); } // ============================================================================ // Tab 2: Change Callback // Demonstrates: detecting edits via SetChangeCallback // ============================================================================ void DemoChangeCallback() { ShowSourceToggle("DemoChangeCallback", "demo_change_callback"); static bool initialized = false; static TextEditor editor; static int changeCount = 0; static std::string lastChangeTime; if (!initialized) { editor.SetText("Edit this text to see the change callback in action.\n\nTry typing, deleting, or pasting.\n"); editor.SetLanguage(TextEditor::Language::Cpp()); editor.SetChangeCallback([&]() { changeCount++; lastChangeTime = "just now"; }, 200); // 200ms debounce initialized = true; } ImGui::Text("Change count: %d", changeCount); if (!lastChangeTime.empty()) { ImGui::SameLine(); ImGui::Text(" (last change: %s)", lastChangeTime.c_str()); } auto codeFont = ImGuiMd::GetCodeFont(); ImGui::PushFont(codeFont.font, codeFont.size); editor.Render("##changes"); ImGui::PopFont(); } // ============================================================================ // Tab 3: Filters // Demonstrates: FilterSelections to transform selected text // ============================================================================ void DemoFilters() { ShowSourceToggle("DemoFilters", "demo_filters"); static bool initialized = false; static TextEditor editor; if (!initialized) { editor.SetText( "Select some text below, then click a filter button.\n" "\n" "Hello World\n" "the quick brown fox jumps over the lazy dog\n" "SOME UPPERCASE TEXT\n" "mixed Case Text Here\n" ); initialized = true; } ImGui::Text("Select text, then apply a filter:"); ImGui::SameLine(); if (ImGui::SmallButton("UPPER")) { editor.FilterSelections([](std::string_view text) -> std::string { std::string result(text); std::transform(result.begin(), result.end(), result.begin(), ::toupper); return result; }); } ImGui::SameLine(); if (ImGui::SmallButton("lower")) { editor.FilterSelections([](std::string_view text) -> std::string { std::string result(text); std::transform(result.begin(), result.end(), result.begin(), ::tolower); return result; }); } ImGui::SameLine(); if (ImGui::SmallButton("Strip trailing spaces")) editor.StripTrailingWhitespaces(); auto codeFont = ImGuiMd::GetCodeFont(); ImGui::PushFont(codeFont.font, codeFont.size); editor.Render("##filters"); ImGui::PopFont(); } // ============================================================================ // Tab 4: Annotations & Interactivity // Demonstrates four ways to annotate or react inside an editor: // - Line decorator (custom-drawn gutter): red circle on breakpoint lines // - Line markers (colored gutter + tooltips): error & warning markers // - Context menus on line numbers and on text (right-click) // - Hover callback in text: live popup showing the word under the cursor // ============================================================================ void DemoDecoratorsAndContextMenus() { ShowSourceToggle("DemoDecoratorsAndContextMenus", "demo_decorators_and_context_menus"); static bool initialized = false; static TextEditor editor; static std::set breakpoints; static std::string lastAction; // Hover callback kept here so the "Hover hints" checkbox below can // re-install it after a clear. Body assigned in the init block. static std::function textHover; if (!initialized) { editor.SetText( "#include \n" "#include \n" "#include \n" "\n" "// Compute summary statistics for a vector of numbers.\n" "double stats(const std::vector& values) {\n" " if (values.empty())\n" " return 0.0;\n" " double total = 0.0;\n" " for (double v : values) total += v;\n" " double mean = total / values.size();\n" " int unused = 42;\n" " return mean;\n" "}\n" "\n" "void greet(const std::string& name) {\n" " std::cout << \"Hello \" << name << std::endl;\n" "}\n" "\n" "int divide(int a, int b) {\n" " return a / b;\n" "}\n" "\n" "int main() {\n" " auto data = std::vector{1, 2, 3, 4, 5};\n" " double m = stats(data);\n" " std::cout << \"mean=\" << m << std::endl;\n" " greet(\"world\");\n" " std::cout << divide(10, 0) << std::endl;\n" " return 0;\n" "}\n" ); editor.SetLanguage(TextEditor::Language::Cpp()); // Markers: persistent colored bands in the gutter / text background, // with built-in tooltips on hover (no callback needed for the tooltip). // Spread across the file so the scrollbar mini map has visible ticks. // Line numbers are zero-based. ImU32 warningColor = IM_COL32(180, 140, 0, 255); ImU32 warningBg = IM_COL32(180, 140, 0, 48); ImU32 errorColor = IM_COL32(220, 50, 50, 255); ImU32 errorBg = IM_COL32(220, 50, 50, 56); editor.AddMarker(2, warningColor, warningBg, "warning", "Included but never used: "); editor.AddMarker(11, warningColor, warningBg, "warning", "Unused variable: 'unused'"); editor.AddMarker(20, errorColor, errorBg, "error", "Unchecked division: b may be zero"); editor.AddMarker(27, errorColor, errorBg, "error", "Division by zero at runtime: divide(10, 0)"); // Decorator: draw a red circle on lines that have a breakpoint editor.SetLineDecorator(-2.0f, [](TextEditor::Decorator& decorator) { if (breakpoints.count(decorator.line)) { auto cursorPos = ImGui::GetCursorScreenPos(); ImVec2 center( cursorPos.x + decorator.glyphSize.x * 0.5f, cursorPos.y + decorator.glyphSize.y * 0.5f ); float radius = decorator.glyphSize.y * 0.35f; ImGui::GetWindowDrawList()->AddCircleFilled(center, radius, IM_COL32(255, 60, 60, 255)); } }); // Right-click on line numbers: toggle breakpoint editor.SetLineNumberContextMenuCallback([](TextEditor::PopupData& data) { size_t line = data.pos.line; bool has = breakpoints.count(line) > 0; std::string label = (has ? "Remove breakpoint" : "Set breakpoint"); label += " (line " + std::to_string(line + 1) + ")"; if (ImGui::MenuItem(label.c_str())) { if (has) breakpoints.erase(line); else breakpoints.insert(line); } }); // Right-click in the text: different context menu editor.SetTextContextMenuCallback([](TextEditor::PopupData& data) { size_t line = data.pos.line; size_t column = data.pos.index; if (ImGui::MenuItem("Go to definition")) lastAction = "Go to definition at " + std::to_string(line + 1) + ":" + std::to_string(column + 1); if (ImGui::MenuItem("Find references")) lastAction = "Find references at " + std::to_string(line + 1) + ":" + std::to_string(column + 1); }); // Hover in text: live popup with the word under the mouse // (think IDE quick-info / type-on-hover). textHover = [](TextEditor::PopupData& data) { std::string word = editor.GetWordAtMousePos(ImGui::GetMousePos()); ImGui::Text("Hovered: line %zu, col %zu", data.pos.line + 1, data.pos.index + 1); if (!word.empty()) { ImGui::Text("Word: %s", word.c_str()); } }; editor.SetTextHoverCallback(textHover); initialized = true; } ImGui::TextWrapped( "Right-click line numbers (or F9) to toggle breakpoints. " "Right-click in text for a menu. Hover text for live info. " "Hover the colored gutter bands for marker tooltips." ); if (!lastAction.empty()) ImGui::TextColored(ImVec4(0.5f, 0.8f, 1.0f, 1.0f), "%s", lastAction.c_str()); ImGui::BeginDisabled(!editor.HasMarkers()); if (ImGui::SmallButton("Clear markers")) editor.ClearMarkers(); ImGui::EndDisabled(); ImGui::SameLine(); bool miniMap = editor.IsShowMiniMapEnabled(); if (ImGui::Checkbox("Show Mini Map", &miniMap)) editor.SetShowMiniMapEnabled(miniMap); ImGui::SameLine(); bool hoverOn = editor.HasTextHoverCallback(); if (ImGui::Checkbox("Hover hints", &hoverOn)) { if (hoverOn) editor.SetTextHoverCallback(textHover); else editor.ClearTextHoverCallback(); } ImGui::SameLine(); bool scrollbarMini = editor.IsShowScrollbarMiniMapEnabled(); if (ImGui::Checkbox("Show Scrollbar Mini Map", &scrollbarMini)) editor.SetShowScrollbarMiniMapEnabled(scrollbarMini); ImGui::SameLine(); ImGui::TextDisabled("(ticks in the scrollbar at marker / selection lines)"); ImGui::NewLine(); ImGui::NewLine(); auto codeFont = ImGuiMd::GetCodeFont(); ImGui::PushFont(codeFont.font, codeFont.size); editor.Render("##decorators_ctx", ImVec2(-1, ImGui::GetTextLineHeight() * 20)); ImGui::PopFont(); // F9: toggle breakpoint on current line if (ImGui::Shortcut(ImGuiKey_F9)) { size_t line = editor.GetMainCursorPosition().line; if (breakpoints.count(line)) breakpoints.erase(line); else breakpoints.insert(line); } } // ============================================================================ // Tab 6: Text Diff // Demonstrates: side-by-side text comparison with TextDiff // ============================================================================ void DemoTextDiff() { ShowSourceToggle("DemoTextDiff", "demo_text_diff"); static bool initialized = false; static TextDiff diff; static bool sideBySide = false; if (!initialized) { // The long comment line on the second row exists so that toggling // the Word Wrap checkbox produces a visible effect on the diff. std::string left = "#include \n" "// Module that says hello. A tiny example used in the ImGui Bundle TextDiff demo to show how line-by-line comparison works.\n" "\n" "void foo() {\n" " std::cout << \"Hello\" << std::endl;\n" " return 0;\n" "}\n"; std::string right = "#include \n" "// Module that says hello to anyone by name. A tiny example used in the ImGui Bundle TextDiff demo to show how line-by-line comparison and word-wrap work together.\n" "#include \n" "\n" "void foo() {\n" " std::string name = \"World\";\n" " std::cout << \"Hello, \" << name << std::endl;\n" " return 0;\n" "}\n"; diff.SetText(left, right); diff.SetLanguage(TextEditor::Language::Cpp()); initialized = true; } if (ImGui::Checkbox("Side by side", &sideBySide)) diff.SetSideBySideMode(sideBySide); ImGui::SameLine(); bool wrap = diff.IsWordWrapEnabled(); if (ImGui::Checkbox("Word Wrap", &wrap)) diff.SetWordWrapEnabled(wrap); auto codeFont = ImGuiMd::GetCodeFont(); ImGui::PushFont(codeFont.font, codeFont.size); diff.Render("##diff"); ImGui::PopFont(); } // ============================================================================ // Tab 7: Editor and menus // Demonstrates how menus can be added to supplement the editor // ============================================================================ #if __APPLE__ #define SHORTCUT "Cmd-" #else #define SHORTCUT "Ctrl-" #endif void DemoEditorWithMenus() { ShowSourceToggle("DemoEditorWithMenus", "demo_editor_with_menus"); static bool initialized = false; static TextEditor editor; if (!initialized) { editor.SetText( "#include \n" "\n" "void foo() {\n" " std::cout << \"Hello\" << std::endl;\n" " int x = 42;\n" " float pi = 3.14159f;\n" " return x;\n" "}\n" ); editor.SetLanguage(TextEditor::Language::Cpp()); initialized = true; } ImGui::BeginChild("editor_with_menus", ImVec2(0, 0), 0, ImGuiWindowFlags_MenuBar); // create menubar if (ImGui::BeginMenuBar()) { // if (ImGui::BeginMenu("File")) { // if (ImGui::MenuItem("New", SHORTCUT "N")) { newFile(); } // if (ImGui::MenuItem("Open...", SHORTCUT "O")) { openFile(); } // ImGui::Separator(); // // if (ImGui::MenuItem("Save", SHORTCUT "S", nullptr, isSavable())) { saveFile(); } // if (ImGui::MenuItem("Save As...")) { showSaveFileAs(); } // ImGui::EndMenu(); // } if (ImGui::BeginMenu("Edit")) { if (ImGui::MenuItem("Undo", " " SHORTCUT "Z", nullptr, editor.CanUndo())) { editor.Undo(); } #if __APPLE__ if (ImGui::MenuItem("Redo", "^" SHORTCUT "Z", nullptr, editor.CanRedo())) { editor.Redo(); } #else if (ImGui::MenuItem("Redo", " " SHORTCUT "Y", nullptr, editor.CanRedo())) { editor.Redo(); } #endif ImGui::Separator(); if (ImGui::MenuItem("Cut", " " SHORTCUT "X", nullptr, editor.AnyCursorHasSelection())) { editor.Cut(); } if (ImGui::MenuItem("Copy", " " SHORTCUT "C", nullptr, editor.AnyCursorHasSelection())) { editor.Copy(); } if (ImGui::MenuItem("Paste", " " SHORTCUT "V", nullptr, ImGui::GetClipboardText() != nullptr)) { editor.Paste(); } ImGui::Separator(); bool flag; flag = editor.IsInsertSpacesOnTabs(); if (ImGui::MenuItem("Insert Spaces on Tabs", nullptr, &flag)) { editor.SetInsertSpacesOnTabs(flag); }; if (ImGui::MenuItem("Tabs To Spaces")) { editor.TabsToSpaces(); } if (ImGui::MenuItem("Spaces To Tabs", nullptr, nullptr, !editor.IsInsertSpacesOnTabs())) { editor.SpacesToTabs(); } if (ImGui::MenuItem("Strip Trailing Whitespaces")) { editor.StripTrailingWhitespaces(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Selection")) { if (ImGui::MenuItem("Select All", " " SHORTCUT "A", nullptr, !editor.IsEmpty())) { editor.SelectAll(); } ImGui::Separator(); if (ImGui::MenuItem("Indent Line(s)", " " SHORTCUT "]", nullptr, !editor.IsEmpty())) { editor.IndentLines(); } if (ImGui::MenuItem("Deindent Line(s)", " " SHORTCUT "[", nullptr, !editor.IsEmpty())) { editor.DeindentLines(); } if (ImGui::MenuItem("Move Line(s) Up", "Alt-Up", nullptr, !editor.IsEmpty())) { editor.MoveUpLines(); } if (ImGui::MenuItem("Move Line(s) Down", "Alt-Down", nullptr, !editor.IsEmpty())) { editor.MoveDownLines(); } if (ImGui::MenuItem("Toggle Comments", " " SHORTCUT "/", nullptr, editor.HasLanguage())) { editor.ToggleComments(); } ImGui::Separator(); if (ImGui::MenuItem("To Uppercase", nullptr, nullptr, editor.AnyCursorHasSelection())) { editor.SelectionToUpperCase(); } if (ImGui::MenuItem("To Lowercase", nullptr, nullptr, editor.AnyCursorHasSelection())) { editor.SelectionToLowerCase(); } ImGui::Separator(); if (ImGui::MenuItem("Add Next Occurrence", " " SHORTCUT "D", nullptr, editor.CurrentCursorHasSelection())) { editor.AddNextOccurrence(); } if (ImGui::MenuItem("Select All Occurrences", "^" SHORTCUT "D", nullptr, editor.CurrentCursorHasSelection())) { editor.SelectAllOccurrences(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("View")) { bool flag; flag = editor.IsShowWhitespacesEnabled(); if (ImGui::MenuItem("Show Whitespaces", nullptr, &flag)) { editor.SetShowWhitespacesEnabled(flag); }; flag = editor.IsShowSpacesEnabled(); if (ImGui::MenuItem("Show Spaces", nullptr, &flag)) { editor.SetShowSpacesEnabled(flag); }; flag = editor.IsShowTabsEnabled(); if (ImGui::MenuItem("Show Tabs", nullptr, &flag)) { editor.SetShowTabsEnabled(flag); }; flag = editor.IsShowLineNumbersEnabled(); if (ImGui::MenuItem("Show Line Numbers", nullptr, &flag)) { editor.SetShowLineNumbersEnabled(flag); }; flag = editor.IsShowingMatchingBrackets(); if (ImGui::MenuItem("Show Matching Brackets", nullptr, &flag)) { editor.SetShowMatchingBrackets(flag); }; flag = editor.IsCompletingPairedGlyphs(); if (ImGui::MenuItem("Complete Matching Glyphs", nullptr, &flag)) { editor.SetCompletePairedGlyphs(flag); }; flag = editor.IsShowPanScrollIndicatorEnabled(); if (ImGui::MenuItem("Show Pan/Scroll Indicator", nullptr, &flag)) { editor.SetShowPanScrollIndicatorEnabled(flag); }; flag = editor.IsMiddleMousePanMode(); if (ImGui::MenuItem("Middle Mouse Pan Mode", nullptr, &flag)) { if (flag) editor.SetMiddleMousePanMode(); else editor.SetMiddleMouseScrollMode(); }; ImGui::Separator(); flag = editor.IsWordWrapEnabled(); if (ImGui::MenuItem("Word Wrap", nullptr, &flag)) { editor.SetWordWrapEnabled(flag); }; flag = editor.IsLineFoldingEnabled(); if (ImGui::MenuItem("Line Folding", nullptr, &flag)) { editor.SetLineFoldingEnabled(flag); }; flag = editor.IsShowMiniMapEnabled(); if (ImGui::MenuItem("Show Mini Map", nullptr, &flag)) { editor.SetShowMiniMapEnabled(flag); }; flag = editor.IsShowScrollbarMiniMapEnabled(); if (ImGui::MenuItem("Show Scrollbar Mini Map", nullptr, &flag)) { editor.SetShowScrollbarMiniMapEnabled(flag); }; ImGui::EndMenu(); } if (ImGui::BeginMenu("Find")) { if (ImGui::MenuItem("Find", " " SHORTCUT "F")) { editor.OpenFindReplaceWindow(); } if (ImGui::MenuItem("Find Next", " " SHORTCUT "G", nullptr, editor.HasFindString())) { editor.FindNext(); } // if (ImGui::MenuItem("Find All", "Shift " SHORTCUT "F", nullptr, editor.HasFindString())) { editor.FindAll(); } ImGui::Separator(); ImGui::EndMenu(); } ImGui::EndMenuBar(); } auto codeFont = ImGuiMd::GetCodeFont(); ImGui::PushFont(codeFont.font, codeFont.size); editor.Render("##editor_menus"); ImGui::PopFont(); ImGui::EndChild(); } // ============================================================================ // Tab 7: Multi-Cursor // Demonstrates: multiple simultaneous cursors and selections. // - Alt-click (Mac) / Ctrl-click (Windows, Linux) adds a cursor // - Select a word, then Ctrl-D (Cmd-D on Mac) adds a cursor at the next match // A live status panel lists every cursor (position + selected text) using // GetNumberOfCursors / GetCursorSelection / GetCursorText. // ============================================================================ void DemoMultiCursor() { ShowSourceToggle("DemoMultiCursor", "demo_multi_cursor"); static bool initialized = false; static TextEditor editor; if (!initialized) { editor.SetText( "// Multi-cursor playground.\n" "// - Alt-click (Mac) / Ctrl-click (Windows, Linux) to add a cursor\n" "// at the click location.\n" "// - Hold the same modifier and drag to add a cursor and extend its selection\n" "// - Double-click a word to select it, then Ctrl-D (or Command-D on Mac)\n" "// to add a cursor at the next occurrence of the same word.\n" "\n" "int value = 1;\n" "value = value + 1;\n" "value = value * value;\n" "value = value - 1;\n" "std::cout << value << value << value << std::endl;\n" ); editor.SetLanguage(TextEditor::Language::Cpp()); initialized = true; } // Quick-action buttons. AddNextOccurrence / SelectAllOccurrences require // the current cursor to have a selection (a word to match). { ImGui::BeginDisabled(!editor.CurrentCursorHasSelection()); if (ImGui::SmallButton("Add Next Occurrence")) editor.AddNextOccurrence(); ImGui::SameLine(); if (ImGui::SmallButton("Select All Occurrences")) editor.SelectAllOccurrences(); ImGui::EndDisabled(); ImGui::SameLine(); ImGui::BeginDisabled(editor.GetNumberOfCursors() <= 1); if (ImGui::SmallButton("Clear Extra Cursors")) editor.ClearCursors(); // leaves a single cursor at the main location ImGui::EndDisabled(); } // Editor (constrained height so the cursor info panel below stays visible) { auto codeFont = ImGuiMd::GetCodeFont(); ImGui::PushFont(codeFont.font, codeFont.size); float editorHeight = ImGui::GetTextLineHeight() * 20.0f; editor.Render("##multi_cursor", ImVec2(0, editorHeight)); ImGui::PopFont(); } // Live cursor status panel { size_t nCursors = editor.GetNumberOfCursors(); ImGui::Text("Cursors: %zu", nCursors); for (size_t i = 0; i < nCursors; ++i) { auto sel = editor.GetCursorSelection(i); std::string text = editor.GetCursorText(i); if (text.empty()) { ImGui::BulletText("[%zu] L:%zu C:%zu", i, sel.start.line + 1, sel.start.index + 1); } else { ImGui::BulletText("[%zu] L:%zu C:%zu -> L:%zu C:%zu selected: '%s'", i, sel.start.line + 1, sel.start.index + 1, sel.end.line + 1, sel.end.index + 1, text.c_str()); } } } } // ============================================================================ // Main demo function // ============================================================================ void demo_text_edit() { ImGuiMd::Render(R"( # ImGuiColorTextEdit [ImGuiColorTextEdit](https://github.com/goossens/ImGuiColorTextEdit) is a syntax highlighting text editor for Dear ImGui (originally by BalazsJako, rewritten by Johan A. Goossens) )"); if (ImGui::BeginTabBar("##TextEditorDemos")) { if (ImGui::BeginTabItem("Basic Editor")) { DemoBasicEditor(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Change Callback")) { DemoChangeCallback(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Annotations & Interactivity")) { DemoDecoratorsAndContextMenus(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Filters")) { DemoFilters(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Text Diff")) { DemoTextDiff(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Multi-Cursor")) { DemoMultiCursor(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Editor with Menus")) { DemoEditorWithMenus(); ImGui::EndTabItem(); } ImGui::EndTabBar(); } }