demo_text_edit.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. // Part of ImGui Bundle - MIT License - Copyright (c) 2022-2026 Pascal Thomet - https://github.com/pthom/imgui_bundle
  2. #include "imgui.h"
  3. #include "immapp/immapp.h"
  4. #include "ImGuiColorTextEdit/TextEditor.h"
  5. #include "ImGuiColorTextEdit/TextDiff.h"
  6. #include "demo_utils/api_demos.h"
  7. #include <string>
  8. #include <set>
  9. #include <map>
  10. #include <algorithm>
  11. #include <cctype>
  12. // ============================================================================
  13. // Source display helper: shows the code of a demo function in a collapsible section
  14. // ============================================================================
  15. namespace
  16. {
  17. std::string gCppContent; // loaded once
  18. std::string gPythonContent; // loaded once
  19. void LoadFileContents()
  20. {
  21. if (gCppContent.empty())
  22. gCppContent = ReadCppCode("demo_text_edit");
  23. if (gPythonContent.empty())
  24. gPythonContent = ReadPythonCode("demo_text_edit");
  25. }
  26. // Extract source between a separator comment and the next one.
  27. // For C++: searches for "void funcName()" preceded by "// ===="
  28. // For Python: searches for "def pyFuncName(" preceded by "# ===="
  29. std::string ExtractFunctionSource(const std::string& content, const std::string& needle, const std::string& separator)
  30. {
  31. auto pos = content.find(needle);
  32. if (pos == std::string::npos) return "// Source not found for " + needle;
  33. auto commentStart = content.rfind(separator, pos);
  34. if (commentStart != std::string::npos)
  35. pos = commentStart;
  36. auto endPos = content.find(separator, pos + needle.size());
  37. if (endPos == std::string::npos)
  38. endPos = content.size();
  39. return content.substr(pos, endPos - pos);
  40. }
  41. // Show a "Show source" checkbox with C++/Python toggle.
  42. // When checked, displays the function's source in a read-only editor (light theme).
  43. // cppFuncName: e.g. "DemoBasicEditor", pyFuncName: e.g. "demo_basic_editor"
  44. void ShowSourceToggle(const char* cppFuncName, const char* pyFuncName)
  45. {
  46. static std::map<std::string, bool> showFlags;
  47. static std::map<std::string, int> langFlags; // 0 = C++, 1 = Python
  48. static std::map<std::string, TextEditor> sourceEditors;
  49. std::string key(cppFuncName);
  50. bool& show = showFlags[key];
  51. int& lang = langFlags[key];
  52. ImGui::Checkbox("Show source", &show);
  53. if (show)
  54. {
  55. ImGui::SameLine();
  56. ImGui::RadioButton("C++", &lang, 0);
  57. ImGui::SameLine();
  58. ImGui::RadioButton("Python", &lang, 1);
  59. ImGui::SeparatorText("Source");
  60. LoadFileContents();
  61. // Build editor key per language to keep separate editors
  62. std::string editorKey = key + (lang == 0 ? "_cpp" : "_py");
  63. if (sourceEditors.find(editorKey) == sourceEditors.end())
  64. {
  65. auto& ed = sourceEditors[editorKey];
  66. if (lang == 0)
  67. ed.SetText(ExtractFunctionSource(gCppContent, std::string("void ") + cppFuncName + "()", "// ===="));
  68. else
  69. ed.SetText(ExtractFunctionSource(gPythonContent, std::string("def ") + pyFuncName + "(", "# ===="));
  70. ed.SetLanguage(lang == 0 ? TextEditor::Language::Cpp() : TextEditor::Language::Python());
  71. ed.SetPalette(TextEditor::GetLightPalette());
  72. ed.SetReadOnlyEnabled(true);
  73. ed.SetCaretsVisible(false);
  74. }
  75. auto codeFont = ImGuiMd::GetCodeFont();
  76. ImGui::PushFont(codeFont.font, codeFont.size);
  77. std::string id = std::string("##src_") + editorKey;
  78. sourceEditors[editorKey].Render(id.c_str(), ImVec2(-1, ImGui::GetTextLineHeight() * 15), false);
  79. ImGui::PopFont();
  80. ImGui::SeparatorText("Demo");
  81. }
  82. }
  83. } // anonymous namespace
  84. // ============================================================================
  85. // Tab 1: Basic Editor
  86. // Demonstrates: text loading, language selection, palette switching
  87. // ============================================================================
  88. void DemoBasicEditor()
  89. {
  90. ShowSourceToggle("DemoBasicEditor", "demo_basic_editor");
  91. static bool initialized = false;
  92. static TextEditor editor;
  93. if (!initialized)
  94. {
  95. LoadFileContents();
  96. editor.SetText(gCppContent);
  97. editor.SetLanguage(TextEditor::Language::Cpp());
  98. initialized = true;
  99. }
  100. // Palette buttons
  101. if (ImGui::SmallButton("Dark"))
  102. editor.SetPalette(TextEditor::GetDarkPalette());
  103. ImGui::SameLine();
  104. if (ImGui::SmallButton("Light"))
  105. editor.SetPalette(TextEditor::GetLightPalette());
  106. // Language selection
  107. ImGui::SameLine();
  108. ImGui::SetNextItemWidth(ImGui::CalcTextSize("AngelScript__").x);
  109. static int langIdx = 1; // default: C++
  110. const char* langNames[] = { "None", "C++", "C", "Python", "GLSL", "HLSL", "Lua", "SQL", "AngelScript", "C#", "JSON", "Markdown" };
  111. if (ImGui::Combo("Language", &langIdx, langNames, IM_ARRAYSIZE(langNames)))
  112. {
  113. const TextEditor::Language* langs[] = {
  114. nullptr,
  115. TextEditor::Language::Cpp(), TextEditor::Language::C(),
  116. TextEditor::Language::Python(), TextEditor::Language::Glsl(),
  117. TextEditor::Language::Hlsl(), TextEditor::Language::Lua(),
  118. TextEditor::Language::Sql(), TextEditor::Language::AngelScript(),
  119. TextEditor::Language::Cs(), TextEditor::Language::Json(),
  120. TextEditor::Language::Markdown()
  121. };
  122. editor.SetLanguage(langs[langIdx]);
  123. }
  124. // Cursor position display (doc coords: row != line once word-wrap is on)
  125. ImGui::SameLine();
  126. auto pos = editor.GetMainCursorPosition();
  127. ImGui::Text("Line: %zu Col: %zu (doc)", pos.line + 1, pos.index + 1);
  128. // Second row: editor view/behavior toggles
  129. bool wrap = editor.IsWordWrapEnabled();
  130. if (ImGui::Checkbox("Word Wrap", &wrap))
  131. editor.SetWordWrapEnabled(wrap);
  132. ImGui::SameLine();
  133. bool readOnly = editor.IsReadOnlyEnabled();
  134. if (ImGui::Checkbox("Read Only", &readOnly))
  135. editor.SetReadOnlyEnabled(readOnly);
  136. ImGui::SameLine();
  137. bool folding = editor.IsLineFoldingEnabled();
  138. if (ImGui::Checkbox("Line Folding", &folding))
  139. editor.SetLineFoldingEnabled(folding);
  140. ImGui::SameLine();
  141. ImGui::BeginDisabled(!folding);
  142. if (ImGui::SmallButton("Unfold All"))
  143. editor.UnfoldAll();
  144. ImGui::EndDisabled();
  145. ImGui::SameLine();
  146. bool miniMap = editor.IsShowMiniMapEnabled();
  147. if (ImGui::Checkbox("Show Mini Map", &miniMap))
  148. editor.SetShowMiniMapEnabled(miniMap);
  149. ImGui::TextDisabled("(folding uses brackets for C/C++, indentation for Python)");
  150. // Render editor: we shall use a monospace font
  151. auto codeFont = ImGuiMd::GetCodeFont();
  152. ImGui::PushFont(codeFont.font, codeFont.size);
  153. editor.Render("##basic");
  154. ImGui::PopFont();
  155. }
  156. // ============================================================================
  157. // Tab 2: Change Callback
  158. // Demonstrates: detecting edits via SetChangeCallback
  159. // ============================================================================
  160. void DemoChangeCallback()
  161. {
  162. ShowSourceToggle("DemoChangeCallback", "demo_change_callback");
  163. static bool initialized = false;
  164. static TextEditor editor;
  165. static int changeCount = 0;
  166. static std::string lastChangeTime;
  167. if (!initialized)
  168. {
  169. editor.SetText("Edit this text to see the change callback in action.\n\nTry typing, deleting, or pasting.\n");
  170. editor.SetLanguage(TextEditor::Language::Cpp());
  171. editor.SetChangeCallback([&]()
  172. {
  173. changeCount++;
  174. lastChangeTime = "just now";
  175. }, 200); // 200ms debounce
  176. initialized = true;
  177. }
  178. ImGui::Text("Change count: %d", changeCount);
  179. if (!lastChangeTime.empty())
  180. {
  181. ImGui::SameLine();
  182. ImGui::Text(" (last change: %s)", lastChangeTime.c_str());
  183. }
  184. auto codeFont = ImGuiMd::GetCodeFont();
  185. ImGui::PushFont(codeFont.font, codeFont.size);
  186. editor.Render("##changes");
  187. ImGui::PopFont();
  188. }
  189. // ============================================================================
  190. // Tab 3: Filters
  191. // Demonstrates: FilterSelections to transform selected text
  192. // ============================================================================
  193. void DemoFilters()
  194. {
  195. ShowSourceToggle("DemoFilters", "demo_filters");
  196. static bool initialized = false;
  197. static TextEditor editor;
  198. if (!initialized)
  199. {
  200. editor.SetText(
  201. "Select some text below, then click a filter button.\n"
  202. "\n"
  203. "Hello World\n"
  204. "the quick brown fox jumps over the lazy dog\n"
  205. "SOME UPPERCASE TEXT\n"
  206. "mixed Case Text Here\n"
  207. );
  208. initialized = true;
  209. }
  210. ImGui::Text("Select text, then apply a filter:");
  211. ImGui::SameLine();
  212. if (ImGui::SmallButton("UPPER"))
  213. {
  214. editor.FilterSelections([](std::string_view text) -> std::string {
  215. std::string result(text);
  216. std::transform(result.begin(), result.end(), result.begin(), ::toupper);
  217. return result;
  218. });
  219. }
  220. ImGui::SameLine();
  221. if (ImGui::SmallButton("lower"))
  222. {
  223. editor.FilterSelections([](std::string_view text) -> std::string {
  224. std::string result(text);
  225. std::transform(result.begin(), result.end(), result.begin(), ::tolower);
  226. return result;
  227. });
  228. }
  229. ImGui::SameLine();
  230. if (ImGui::SmallButton("Strip trailing spaces"))
  231. editor.StripTrailingWhitespaces();
  232. auto codeFont = ImGuiMd::GetCodeFont();
  233. ImGui::PushFont(codeFont.font, codeFont.size);
  234. editor.Render("##filters");
  235. ImGui::PopFont();
  236. }
  237. // ============================================================================
  238. // Tab 4: Annotations & Interactivity
  239. // Demonstrates four ways to annotate or react inside an editor:
  240. // - Line decorator (custom-drawn gutter): red circle on breakpoint lines
  241. // - Line markers (colored gutter + tooltips): error & warning markers
  242. // - Context menus on line numbers and on text (right-click)
  243. // - Hover callback in text: live popup showing the word under the cursor
  244. // ============================================================================
  245. void DemoDecoratorsAndContextMenus()
  246. {
  247. ShowSourceToggle("DemoDecoratorsAndContextMenus", "demo_decorators_and_context_menus");
  248. static bool initialized = false;
  249. static TextEditor editor;
  250. static std::set<size_t> breakpoints;
  251. static std::string lastAction;
  252. // Hover callback kept here so the "Hover hints" checkbox below can
  253. // re-install it after a clear. Body assigned in the init block.
  254. static std::function<void(TextEditor::PopupData&)> textHover;
  255. if (!initialized)
  256. {
  257. editor.SetText(
  258. "#include <iostream>\n"
  259. "#include <vector>\n"
  260. "#include <map>\n"
  261. "\n"
  262. "// Compute summary statistics for a vector of numbers.\n"
  263. "double stats(const std::vector<double>& values) {\n"
  264. " if (values.empty())\n"
  265. " return 0.0;\n"
  266. " double total = 0.0;\n"
  267. " for (double v : values) total += v;\n"
  268. " double mean = total / values.size();\n"
  269. " int unused = 42;\n"
  270. " return mean;\n"
  271. "}\n"
  272. "\n"
  273. "void greet(const std::string& name) {\n"
  274. " std::cout << \"Hello \" << name << std::endl;\n"
  275. "}\n"
  276. "\n"
  277. "int divide(int a, int b) {\n"
  278. " return a / b;\n"
  279. "}\n"
  280. "\n"
  281. "int main() {\n"
  282. " auto data = std::vector<double>{1, 2, 3, 4, 5};\n"
  283. " double m = stats(data);\n"
  284. " std::cout << \"mean=\" << m << std::endl;\n"
  285. " greet(\"world\");\n"
  286. " std::cout << divide(10, 0) << std::endl;\n"
  287. " return 0;\n"
  288. "}\n"
  289. );
  290. editor.SetLanguage(TextEditor::Language::Cpp());
  291. // Markers: persistent colored bands in the gutter / text background,
  292. // with built-in tooltips on hover (no callback needed for the tooltip).
  293. // Spread across the file so the scrollbar mini map has visible ticks.
  294. // Line numbers are zero-based.
  295. ImU32 warningColor = IM_COL32(180, 140, 0, 255);
  296. ImU32 warningBg = IM_COL32(180, 140, 0, 48);
  297. ImU32 errorColor = IM_COL32(220, 50, 50, 255);
  298. ImU32 errorBg = IM_COL32(220, 50, 50, 56);
  299. editor.AddMarker(2, warningColor, warningBg, "warning", "Included but never used: <map>");
  300. editor.AddMarker(11, warningColor, warningBg, "warning", "Unused variable: 'unused'");
  301. editor.AddMarker(20, errorColor, errorBg, "error", "Unchecked division: b may be zero");
  302. editor.AddMarker(27, errorColor, errorBg, "error", "Division by zero at runtime: divide(10, 0)");
  303. // Decorator: draw a red circle on lines that have a breakpoint
  304. editor.SetLineDecorator(-2.0f, [](TextEditor::Decorator& decorator) {
  305. if (breakpoints.count(decorator.line))
  306. {
  307. auto cursorPos = ImGui::GetCursorScreenPos();
  308. ImVec2 center(
  309. cursorPos.x + decorator.glyphSize.x * 0.5f,
  310. cursorPos.y + decorator.glyphSize.y * 0.5f
  311. );
  312. float radius = decorator.glyphSize.y * 0.35f;
  313. ImGui::GetWindowDrawList()->AddCircleFilled(center, radius, IM_COL32(255, 60, 60, 255));
  314. }
  315. });
  316. // Right-click on line numbers: toggle breakpoint
  317. editor.SetLineNumberContextMenuCallback([](TextEditor::PopupData& data) {
  318. size_t line = data.pos.line;
  319. bool has = breakpoints.count(line) > 0;
  320. std::string label = (has ? "Remove breakpoint" : "Set breakpoint");
  321. label += " (line " + std::to_string(line + 1) + ")";
  322. if (ImGui::MenuItem(label.c_str()))
  323. {
  324. if (has)
  325. breakpoints.erase(line);
  326. else
  327. breakpoints.insert(line);
  328. }
  329. });
  330. // Right-click in the text: different context menu
  331. editor.SetTextContextMenuCallback([](TextEditor::PopupData& data) {
  332. size_t line = data.pos.line;
  333. size_t column = data.pos.index;
  334. if (ImGui::MenuItem("Go to definition"))
  335. lastAction = "Go to definition at " + std::to_string(line + 1) + ":" + std::to_string(column + 1);
  336. if (ImGui::MenuItem("Find references"))
  337. lastAction = "Find references at " + std::to_string(line + 1) + ":" + std::to_string(column + 1);
  338. });
  339. // Hover in text: live popup with the word under the mouse
  340. // (think IDE quick-info / type-on-hover).
  341. textHover = [](TextEditor::PopupData& data) {
  342. std::string word = editor.GetWordAtMousePos(ImGui::GetMousePos());
  343. ImGui::Text("Hovered: line %zu, col %zu", data.pos.line + 1, data.pos.index + 1);
  344. if (!word.empty())
  345. {
  346. ImGui::Text("Word: %s", word.c_str());
  347. }
  348. };
  349. editor.SetTextHoverCallback(textHover);
  350. initialized = true;
  351. }
  352. ImGui::TextWrapped(
  353. "Right-click line numbers (or F9) to toggle breakpoints. "
  354. "Right-click in text for a menu. Hover text for live info. "
  355. "Hover the colored gutter bands for marker tooltips."
  356. );
  357. if (!lastAction.empty())
  358. ImGui::TextColored(ImVec4(0.5f, 0.8f, 1.0f, 1.0f), "%s", lastAction.c_str());
  359. ImGui::BeginDisabled(!editor.HasMarkers());
  360. if (ImGui::SmallButton("Clear markers"))
  361. editor.ClearMarkers();
  362. ImGui::EndDisabled();
  363. ImGui::SameLine();
  364. bool miniMap = editor.IsShowMiniMapEnabled();
  365. if (ImGui::Checkbox("Show Mini Map", &miniMap))
  366. editor.SetShowMiniMapEnabled(miniMap);
  367. ImGui::SameLine();
  368. bool hoverOn = editor.HasTextHoverCallback();
  369. if (ImGui::Checkbox("Hover hints", &hoverOn))
  370. {
  371. if (hoverOn)
  372. editor.SetTextHoverCallback(textHover);
  373. else
  374. editor.ClearTextHoverCallback();
  375. }
  376. ImGui::SameLine();
  377. bool scrollbarMini = editor.IsShowScrollbarMiniMapEnabled();
  378. if (ImGui::Checkbox("Show Scrollbar Mini Map", &scrollbarMini))
  379. editor.SetShowScrollbarMiniMapEnabled(scrollbarMini);
  380. ImGui::SameLine();
  381. ImGui::TextDisabled("(ticks in the scrollbar at marker / selection lines)");
  382. ImGui::NewLine();
  383. ImGui::NewLine();
  384. auto codeFont = ImGuiMd::GetCodeFont();
  385. ImGui::PushFont(codeFont.font, codeFont.size);
  386. editor.Render("##decorators_ctx", ImVec2(-1, ImGui::GetTextLineHeight() * 20));
  387. ImGui::PopFont();
  388. // F9: toggle breakpoint on current line
  389. if (ImGui::Shortcut(ImGuiKey_F9))
  390. {
  391. size_t line = editor.GetMainCursorPosition().line;
  392. if (breakpoints.count(line))
  393. breakpoints.erase(line);
  394. else
  395. breakpoints.insert(line);
  396. }
  397. }
  398. // ============================================================================
  399. // Tab 6: Text Diff
  400. // Demonstrates: side-by-side text comparison with TextDiff
  401. // ============================================================================
  402. void DemoTextDiff()
  403. {
  404. ShowSourceToggle("DemoTextDiff", "demo_text_diff");
  405. static bool initialized = false;
  406. static TextDiff diff;
  407. static bool sideBySide = false;
  408. if (!initialized)
  409. {
  410. // The long comment line on the second row exists so that toggling
  411. // the Word Wrap checkbox produces a visible effect on the diff.
  412. std::string left =
  413. "#include <iostream>\n"
  414. "// Module that says hello. A tiny example used in the ImGui Bundle TextDiff demo to show how line-by-line comparison works.\n"
  415. "\n"
  416. "void foo() {\n"
  417. " std::cout << \"Hello\" << std::endl;\n"
  418. " return 0;\n"
  419. "}\n";
  420. std::string right =
  421. "#include <iostream>\n"
  422. "// 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"
  423. "#include <string>\n"
  424. "\n"
  425. "void foo() {\n"
  426. " std::string name = \"World\";\n"
  427. " std::cout << \"Hello, \" << name << std::endl;\n"
  428. " return 0;\n"
  429. "}\n";
  430. diff.SetText(left, right);
  431. diff.SetLanguage(TextEditor::Language::Cpp());
  432. initialized = true;
  433. }
  434. if (ImGui::Checkbox("Side by side", &sideBySide))
  435. diff.SetSideBySideMode(sideBySide);
  436. ImGui::SameLine();
  437. bool wrap = diff.IsWordWrapEnabled();
  438. if (ImGui::Checkbox("Word Wrap", &wrap))
  439. diff.SetWordWrapEnabled(wrap);
  440. auto codeFont = ImGuiMd::GetCodeFont();
  441. ImGui::PushFont(codeFont.font, codeFont.size);
  442. diff.Render("##diff");
  443. ImGui::PopFont();
  444. }
  445. // ============================================================================
  446. // Tab 7: Editor and menus
  447. // Demonstrates how menus can be added to supplement the editor
  448. // ============================================================================
  449. #if __APPLE__
  450. #define SHORTCUT "Cmd-"
  451. #else
  452. #define SHORTCUT "Ctrl-"
  453. #endif
  454. void DemoEditorWithMenus()
  455. {
  456. ShowSourceToggle("DemoEditorWithMenus", "demo_editor_with_menus");
  457. static bool initialized = false;
  458. static TextEditor editor;
  459. if (!initialized)
  460. {
  461. editor.SetText(
  462. "#include <iostream>\n"
  463. "\n"
  464. "void foo() {\n"
  465. " std::cout << \"Hello\" << std::endl;\n"
  466. " int x = 42;\n"
  467. " float pi = 3.14159f;\n"
  468. " return x;\n"
  469. "}\n"
  470. );
  471. editor.SetLanguage(TextEditor::Language::Cpp());
  472. initialized = true;
  473. }
  474. ImGui::BeginChild("editor_with_menus", ImVec2(0, 0), 0, ImGuiWindowFlags_MenuBar);
  475. // create menubar
  476. if (ImGui::BeginMenuBar()) {
  477. // if (ImGui::BeginMenu("File")) {
  478. // if (ImGui::MenuItem("New", SHORTCUT "N")) { newFile(); }
  479. // if (ImGui::MenuItem("Open...", SHORTCUT "O")) { openFile(); }
  480. // ImGui::Separator();
  481. //
  482. // if (ImGui::MenuItem("Save", SHORTCUT "S", nullptr, isSavable())) { saveFile(); }
  483. // if (ImGui::MenuItem("Save As...")) { showSaveFileAs(); }
  484. // ImGui::EndMenu();
  485. // }
  486. if (ImGui::BeginMenu("Edit")) {
  487. if (ImGui::MenuItem("Undo", " " SHORTCUT "Z", nullptr, editor.CanUndo())) { editor.Undo(); }
  488. #if __APPLE__
  489. if (ImGui::MenuItem("Redo", "^" SHORTCUT "Z", nullptr, editor.CanRedo())) { editor.Redo(); }
  490. #else
  491. if (ImGui::MenuItem("Redo", " " SHORTCUT "Y", nullptr, editor.CanRedo())) { editor.Redo(); }
  492. #endif
  493. ImGui::Separator();
  494. if (ImGui::MenuItem("Cut", " " SHORTCUT "X", nullptr, editor.AnyCursorHasSelection())) { editor.Cut(); }
  495. if (ImGui::MenuItem("Copy", " " SHORTCUT "C", nullptr, editor.AnyCursorHasSelection())) { editor.Copy(); }
  496. if (ImGui::MenuItem("Paste", " " SHORTCUT "V", nullptr, ImGui::GetClipboardText() != nullptr)) { editor.Paste(); }
  497. ImGui::Separator();
  498. bool flag;
  499. flag = editor.IsInsertSpacesOnTabs(); if (ImGui::MenuItem("Insert Spaces on Tabs", nullptr, &flag)) { editor.SetInsertSpacesOnTabs(flag); };
  500. if (ImGui::MenuItem("Tabs To Spaces")) { editor.TabsToSpaces(); }
  501. if (ImGui::MenuItem("Spaces To Tabs", nullptr, nullptr, !editor.IsInsertSpacesOnTabs())) { editor.SpacesToTabs(); }
  502. if (ImGui::MenuItem("Strip Trailing Whitespaces")) { editor.StripTrailingWhitespaces(); }
  503. ImGui::EndMenu();
  504. }
  505. if (ImGui::BeginMenu("Selection")) {
  506. if (ImGui::MenuItem("Select All", " " SHORTCUT "A", nullptr, !editor.IsEmpty())) { editor.SelectAll(); }
  507. ImGui::Separator();
  508. if (ImGui::MenuItem("Indent Line(s)", " " SHORTCUT "]", nullptr, !editor.IsEmpty())) { editor.IndentLines(); }
  509. if (ImGui::MenuItem("Deindent Line(s)", " " SHORTCUT "[", nullptr, !editor.IsEmpty())) { editor.DeindentLines(); }
  510. if (ImGui::MenuItem("Move Line(s) Up", "Alt-Up", nullptr, !editor.IsEmpty())) { editor.MoveUpLines(); }
  511. if (ImGui::MenuItem("Move Line(s) Down", "Alt-Down", nullptr, !editor.IsEmpty())) { editor.MoveDownLines(); }
  512. if (ImGui::MenuItem("Toggle Comments", " " SHORTCUT "/", nullptr, editor.HasLanguage())) { editor.ToggleComments(); }
  513. ImGui::Separator();
  514. if (ImGui::MenuItem("To Uppercase", nullptr, nullptr, editor.AnyCursorHasSelection())) { editor.SelectionToUpperCase(); }
  515. if (ImGui::MenuItem("To Lowercase", nullptr, nullptr, editor.AnyCursorHasSelection())) { editor.SelectionToLowerCase(); }
  516. ImGui::Separator();
  517. if (ImGui::MenuItem("Add Next Occurrence", " " SHORTCUT "D", nullptr, editor.CurrentCursorHasSelection())) { editor.AddNextOccurrence(); }
  518. if (ImGui::MenuItem("Select All Occurrences", "^" SHORTCUT "D", nullptr, editor.CurrentCursorHasSelection())) { editor.SelectAllOccurrences(); }
  519. ImGui::EndMenu();
  520. }
  521. if (ImGui::BeginMenu("View")) {
  522. bool flag;
  523. flag = editor.IsShowWhitespacesEnabled(); if (ImGui::MenuItem("Show Whitespaces", nullptr, &flag)) { editor.SetShowWhitespacesEnabled(flag); };
  524. flag = editor.IsShowSpacesEnabled(); if (ImGui::MenuItem("Show Spaces", nullptr, &flag)) { editor.SetShowSpacesEnabled(flag); };
  525. flag = editor.IsShowTabsEnabled(); if (ImGui::MenuItem("Show Tabs", nullptr, &flag)) { editor.SetShowTabsEnabled(flag); };
  526. flag = editor.IsShowLineNumbersEnabled(); if (ImGui::MenuItem("Show Line Numbers", nullptr, &flag)) { editor.SetShowLineNumbersEnabled(flag); };
  527. flag = editor.IsShowingMatchingBrackets(); if (ImGui::MenuItem("Show Matching Brackets", nullptr, &flag)) { editor.SetShowMatchingBrackets(flag); };
  528. flag = editor.IsCompletingPairedGlyphs(); if (ImGui::MenuItem("Complete Matching Glyphs", nullptr, &flag)) { editor.SetCompletePairedGlyphs(flag); };
  529. flag = editor.IsShowPanScrollIndicatorEnabled(); if (ImGui::MenuItem("Show Pan/Scroll Indicator", nullptr, &flag)) { editor.SetShowPanScrollIndicatorEnabled(flag); };
  530. flag = editor.IsMiddleMousePanMode(); if (ImGui::MenuItem("Middle Mouse Pan Mode", nullptr, &flag)) { if (flag) editor.SetMiddleMousePanMode(); else editor.SetMiddleMouseScrollMode(); };
  531. ImGui::Separator();
  532. flag = editor.IsWordWrapEnabled(); if (ImGui::MenuItem("Word Wrap", nullptr, &flag)) { editor.SetWordWrapEnabled(flag); };
  533. flag = editor.IsLineFoldingEnabled(); if (ImGui::MenuItem("Line Folding", nullptr, &flag)) { editor.SetLineFoldingEnabled(flag); };
  534. flag = editor.IsShowMiniMapEnabled(); if (ImGui::MenuItem("Show Mini Map", nullptr, &flag)) { editor.SetShowMiniMapEnabled(flag); };
  535. flag = editor.IsShowScrollbarMiniMapEnabled(); if (ImGui::MenuItem("Show Scrollbar Mini Map", nullptr, &flag)) { editor.SetShowScrollbarMiniMapEnabled(flag); };
  536. ImGui::EndMenu();
  537. }
  538. if (ImGui::BeginMenu("Find")) {
  539. if (ImGui::MenuItem("Find", " " SHORTCUT "F")) { editor.OpenFindReplaceWindow(); }
  540. if (ImGui::MenuItem("Find Next", " " SHORTCUT "G", nullptr, editor.HasFindString())) { editor.FindNext(); }
  541. // if (ImGui::MenuItem("Find All", "Shift " SHORTCUT "F", nullptr, editor.HasFindString())) { editor.FindAll(); }
  542. ImGui::Separator();
  543. ImGui::EndMenu();
  544. }
  545. ImGui::EndMenuBar();
  546. }
  547. auto codeFont = ImGuiMd::GetCodeFont();
  548. ImGui::PushFont(codeFont.font, codeFont.size);
  549. editor.Render("##editor_menus");
  550. ImGui::PopFont();
  551. ImGui::EndChild();
  552. }
  553. // ============================================================================
  554. // Tab 7: Multi-Cursor
  555. // Demonstrates: multiple simultaneous cursors and selections.
  556. // - Alt-click (Mac) / Ctrl-click (Windows, Linux) adds a cursor
  557. // - Select a word, then Ctrl-D (Cmd-D on Mac) adds a cursor at the next match
  558. // A live status panel lists every cursor (position + selected text) using
  559. // GetNumberOfCursors / GetCursorSelection / GetCursorText.
  560. // ============================================================================
  561. void DemoMultiCursor()
  562. {
  563. ShowSourceToggle("DemoMultiCursor", "demo_multi_cursor");
  564. static bool initialized = false;
  565. static TextEditor editor;
  566. if (!initialized)
  567. {
  568. editor.SetText(
  569. "// Multi-cursor playground.\n"
  570. "// - Alt-click (Mac) / Ctrl-click (Windows, Linux) to add a cursor\n"
  571. "// at the click location.\n"
  572. "// - Hold the same modifier and drag to add a cursor and extend its selection\n"
  573. "// - Double-click a word to select it, then Ctrl-D (or Command-D on Mac)\n"
  574. "// to add a cursor at the next occurrence of the same word.\n"
  575. "\n"
  576. "int value = 1;\n"
  577. "value = value + 1;\n"
  578. "value = value * value;\n"
  579. "value = value - 1;\n"
  580. "std::cout << value << value << value << std::endl;\n"
  581. );
  582. editor.SetLanguage(TextEditor::Language::Cpp());
  583. initialized = true;
  584. }
  585. // Quick-action buttons. AddNextOccurrence / SelectAllOccurrences require
  586. // the current cursor to have a selection (a word to match).
  587. {
  588. ImGui::BeginDisabled(!editor.CurrentCursorHasSelection());
  589. if (ImGui::SmallButton("Add Next Occurrence"))
  590. editor.AddNextOccurrence();
  591. ImGui::SameLine();
  592. if (ImGui::SmallButton("Select All Occurrences"))
  593. editor.SelectAllOccurrences();
  594. ImGui::EndDisabled();
  595. ImGui::SameLine();
  596. ImGui::BeginDisabled(editor.GetNumberOfCursors() <= 1);
  597. if (ImGui::SmallButton("Clear Extra Cursors"))
  598. editor.ClearCursors(); // leaves a single cursor at the main location
  599. ImGui::EndDisabled();
  600. }
  601. // Editor (constrained height so the cursor info panel below stays visible)
  602. {
  603. auto codeFont = ImGuiMd::GetCodeFont();
  604. ImGui::PushFont(codeFont.font, codeFont.size);
  605. float editorHeight = ImGui::GetTextLineHeight() * 20.0f;
  606. editor.Render("##multi_cursor", ImVec2(0, editorHeight));
  607. ImGui::PopFont();
  608. }
  609. // Live cursor status panel
  610. {
  611. size_t nCursors = editor.GetNumberOfCursors();
  612. ImGui::Text("Cursors: %zu", nCursors);
  613. for (size_t i = 0; i < nCursors; ++i)
  614. {
  615. auto sel = editor.GetCursorSelection(i);
  616. std::string text = editor.GetCursorText(i);
  617. if (text.empty())
  618. {
  619. ImGui::BulletText("[%zu] L:%zu C:%zu",
  620. i, sel.start.line + 1, sel.start.index + 1);
  621. }
  622. else
  623. {
  624. ImGui::BulletText("[%zu] L:%zu C:%zu -> L:%zu C:%zu selected: '%s'",
  625. i,
  626. sel.start.line + 1, sel.start.index + 1,
  627. sel.end.line + 1, sel.end.index + 1,
  628. text.c_str());
  629. }
  630. }
  631. }
  632. }
  633. // ============================================================================
  634. // Main demo function
  635. // ============================================================================
  636. void demo_text_edit()
  637. {
  638. ImGuiMd::Render(R"(
  639. # ImGuiColorTextEdit
  640. [ImGuiColorTextEdit](https://github.com/goossens/ImGuiColorTextEdit) is a syntax highlighting text editor for Dear ImGui (originally by BalazsJako, rewritten by Johan A. Goossens)
  641. )");
  642. if (ImGui::BeginTabBar("##TextEditorDemos"))
  643. {
  644. if (ImGui::BeginTabItem("Basic Editor"))
  645. {
  646. DemoBasicEditor();
  647. ImGui::EndTabItem();
  648. }
  649. if (ImGui::BeginTabItem("Change Callback"))
  650. {
  651. DemoChangeCallback();
  652. ImGui::EndTabItem();
  653. }
  654. if (ImGui::BeginTabItem("Annotations & Interactivity"))
  655. {
  656. DemoDecoratorsAndContextMenus();
  657. ImGui::EndTabItem();
  658. }
  659. if (ImGui::BeginTabItem("Filters"))
  660. {
  661. DemoFilters();
  662. ImGui::EndTabItem();
  663. }
  664. if (ImGui::BeginTabItem("Text Diff"))
  665. {
  666. DemoTextDiff();
  667. ImGui::EndTabItem();
  668. }
  669. if (ImGui::BeginTabItem("Multi-Cursor"))
  670. {
  671. DemoMultiCursor();
  672. ImGui::EndTabItem();
  673. }
  674. if (ImGui::BeginTabItem("Editor with Menus"))
  675. {
  676. DemoEditorWithMenus();
  677. ImGui::EndTabItem();
  678. }
  679. ImGui::EndTabBar();
  680. }
  681. }