#
tokens: 32101/50000 1/104 files (page 5/10)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 5 of 10. Use http://codebase.md/moisnx/arc?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .clang-format
├── .config
│   └── arceditor
│       ├── config.yaml
│       ├── keybinds.conf
│       └── themes
│           ├── catppuccin-mocha.theme
│           ├── cyberpunk-neon.theme
│           ├── default.theme
│           ├── dracula.theme
│           ├── github_dark.theme
│           ├── gruvbox_dark.theme
│           ├── gruvbox_light.theme
│           ├── high_constrast_dark.theme
│           ├── monokai.theme
│           ├── onedark.theme
│           ├── solarized_dark.theme
│           ├── solarized_light.theme
│           ├── tokyo_night.theme
│           └── vscode_light.theme
├── .github
│   └── assets
│       └── screenshot.gif
├── .gitignore
├── .gitmessage
├── .gitmodules
├── build.md
├── CMakeLists.txt
├── deps
│   └── tree-sitter-markdown
│       ├── .editorconfig
│       ├── .gitattributes
│       ├── .github
│       │   ├── screenshot.png
│       │   └── workflows
│       │       ├── ci.yml
│       │       ├── publish.yml
│       │       └── release.yml
│       ├── .gitignore
│       ├── binding.gyp
│       ├── bindings
│       │   ├── go
│       │   │   ├── binding_test.go
│       │   │   ├── markdown_inline.go
│       │   │   └── markdown.go
│       │   ├── node
│       │   │   ├── binding_test.js
│       │   │   ├── binding.cc
│       │   │   ├── index.d.ts
│       │   │   ├── index.js
│       │   │   └── inline.js
│       │   ├── python
│       │   │   ├── tests
│       │   │   │   └── test_binding.py
│       │   │   └── tree_sitter_markdown
│       │   │       ├── __init__.py
│       │   │       ├── __init__.pyi
│       │   │       ├── binding.c
│       │   │       └── py.typed
│       │   ├── rust
│       │   │   ├── benchmark.rs
│       │   │   ├── build.rs
│       │   │   ├── lib.rs
│       │   │   └── parser.rs
│       │   └── swift
│       │       ├── .gitignore
│       │       └── TreeSitterMarkdownTests
│       │           └── TreeSitterMarkdownTests.swift
│       ├── Cargo.toml
│       ├── CMakeLists.txt
│       ├── common
│       │   ├── common.js
│       │   ├── common.mak
│       │   └── html_entities.json
│       ├── CONTRIBUTING.md
│       ├── go.mod
│       ├── LICENSE
│       ├── Makefile
│       ├── package-lock.json
│       ├── package.json
│       ├── Package.resolved
│       ├── Package.swift
│       ├── pyproject.toml
│       ├── README.md
│       ├── scripts
│       │   ├── build.js
│       │   └── test.js
│       ├── setup.py
│       ├── tree-sitter-markdown
│       │   ├── bindings
│       │   │   ├── c
│       │   │   │   ├── tree-sitter-markdown.h
│       │   │   │   └── tree-sitter-markdown.pc.in
│       │   │   └── swift
│       │   │       └── TreeSitterMarkdown
│       │   │           └── markdown.h
│       │   ├── CMakeLists.txt
│       │   ├── grammar.js
│       │   ├── Makefile
│       │   ├── package.json
│       │   ├── queries
│       │   │   ├── highlights.scm
│       │   │   └── injections.scm
│       │   ├── src
│       │   │   ├── grammar.json
│       │   │   ├── node-types.json
│       │   │   ├── parser.c
│       │   │   ├── scanner.c
│       │   │   └── tree_sitter
│       │   │       ├── alloc.h
│       │   │       ├── array.h
│       │   │       └── parser.h
│       │   └── test
│       │       └── corpus
│       │           ├── extension_minus_metadata.txt
│       │           ├── extension_pipe_table.txt
│       │           ├── extension_plus_metadata.txt
│       │           ├── extension_task_list.txt
│       │           ├── failing.txt
│       │           ├── issues.txt
│       │           └── spec.txt
│       ├── tree-sitter-markdown-inline
│       │   ├── bindings
│       │   │   ├── c
│       │   │   │   ├── tree-sitter-markdown-inline.h
│       │   │   │   └── tree-sitter-markdown-inline.pc.in
│       │   │   └── swift
│       │   │       └── TreeSitterMarkdownInline
│       │   │           └── markdown_inline.h
│       │   ├── CMakeLists.txt
│       │   ├── grammar.js
│       │   ├── Makefile
│       │   ├── package.json
│       │   ├── queries
│       │   │   ├── highlights.scm
│       │   │   └── injections.scm
│       │   ├── src
│       │   │   ├── grammar.json
│       │   │   ├── node-types.json
│       │   │   ├── parser.c
│       │   │   ├── scanner.c
│       │   │   └── tree_sitter
│       │   │       ├── alloc.h
│       │   │       ├── array.h
│       │   │       └── parser.h
│       │   └── test
│       │       └── corpus
│       │           ├── extension_latex.txt
│       │           ├── extension_strikethrough.txt
│       │           ├── extension_wikilink.txt
│       │           ├── failing.txt
│       │           ├── issues.txt
│       │           ├── spec.txt
│       │           └── tags.txt
│       └── tree-sitter.json
├── LICENSE
├── Makefile
├── quickstart.md
├── README.md
├── src
│   ├── core
│   │   ├── buffer.cpp
│   │   ├── buffer.h
│   │   ├── config_manager.cpp
│   │   ├── config_manager.h
│   │   ├── editor_delta.h
│   │   ├── editor_validation.h
│   │   ├── editor.cpp
│   │   └── editor.h
│   ├── features
│   │   ├── markdown_state.h
│   │   ├── syntax_config_loader.cpp
│   │   ├── syntax_config_loader.h
│   │   ├── syntax_highlighter.cpp
│   │   └── syntax_highlighter.h
│   ├── main.cpp
│   └── ui
│       ├── input_handler.cpp
│       ├── input_handler.h
│       ├── renderer.cpp
│       ├── renderer.h
│       ├── style_manager.cpp
│       └── style_manager.h
└── treesitter
    ├── languages.yaml
    └── queries
        ├── _javascript
        │   ├── highlights.scm
        │   ├── locals.scm
        │   └── tags.scm
        ├── _jsx
        │   ├── highlights.scm
        │   ├── indents.scm
        │   └── textobjects.scm
        ├── _typescript
        │   ├── highlights.scm
        │   ├── indents.scm
        │   ├── locals.scm
        │   ├── tags.scm
        │   └── textobjects.scm
        ├── bash
        │   ├── highlights.scm
        │   ├── indents.scm
        │   ├── injections.scm
        │   ├── rainbows.scm
        │   ├── tags.scm
        │   └── textobjects.scm
        ├── c
        │   ├── highlights.scm
        │   ├── indents.scm
        │   ├── injections.scm
        │   ├── locals.scm
        │   ├── rainbows.scm
        │   ├── tags.scm
        │   └── textobjects.scm
        ├── cpp
        │   ├── highlights.scm
        │   ├── indents.scm
        │   ├── injections.scm
        │   ├── rainbows.scm
        │   ├── tags.scm
        │   └── textobjects.scm
        ├── css
        │   ├── highlights.scm
        │   ├── indents.scm
        │   ├── injections.scm
        │   └── rainbows.scm
        ├── ecma
        │   ├── highlights.scm
        │   ├── indents.scm
        │   ├── injections.scm
        │   ├── locals.scm
        │   ├── rainbows.scm
        │   ├── README.md
        │   └── textobjects.scm
        ├── go
        │   ├── highlights.scm
        │   ├── indents.scm
        │   ├── injections.scm
        │   ├── locals.scm
        │   ├── rainbows.scm
        │   ├── tags.scm
        │   └── textobjects.scm
        ├── javascript
        │   ├── highlights.scm
        │   ├── indents.scm
        │   ├── injections.scm
        │   ├── locals.scm
        │   ├── rainbows.scm
        │   ├── tags.scm
        │   └── textobjects.scm
        ├── markdown
        │   ├── highlights.scm
        │   ├── injections.scm
        │   └── tags.scm
        ├── markdown.inline
        │   ├── highlights.scm
        │   └── injections.scm
        ├── python
        │   ├── highlights.scm
        │   ├── indents.scm
        │   ├── injections.scm
        │   ├── locals.scm
        │   ├── rainbows.scm
        │   ├── tags.scm
        │   └── textobjects.scm
        ├── rust
        │   ├── highlights.scm
        │   ├── indents.scm
        │   ├── injections.scm
        │   ├── locals.scm
        │   ├── rainbows.scm
        │   ├── tags.scm
        │   └── textobjects.scm
        ├── toml
        │   ├── highlights.scm
        │   ├── injections.scm
        │   ├── rainbows.scm
        │   └── textobjects.scm
        ├── tsx
        │   ├── highlights.scm
        │   ├── indents.scm
        │   ├── injections.scm
        │   ├── locals.scm
        │   ├── rainbows.scm
        │   ├── tags.scm
        │   └── textobjects.scm
        ├── typescript
        │   ├── highlights.scm
        │   ├── indents.scm
        │   ├── injections.scm
        │   ├── locals.scm
        │   ├── rainbows.scm
        │   ├── tags.scm
        │   └── textobjects.scm
        ├── yaml
        │   ├── highlights.scm
        │   ├── indents.scm
        │   ├── injections.scm
        │   ├── rainbows.scm
        │   └── textobjects.scm
        └── zig
            ├── highlights.scm
            ├── indents.scm
            ├── injections.scm
            └── textobjects.scm
```

# Files

--------------------------------------------------------------------------------
/src/core/editor.cpp:
--------------------------------------------------------------------------------

```cpp
   1 | #include "editor.h"
   2 | // #include "src/ui/colors.h"
   3 | #include "src/core/config_manager.h"
   4 | #include "src/ui/style_manager.h"
   5 | #include <algorithm>
   6 | #include <cstdlib>
   7 | #include <cstring>
   8 | #include <fstream>
   9 | #include <iostream>
  10 | #ifdef _WIN32
  11 | #include <curses.h>
  12 | #else
  13 | #include <ncurses.h>
  14 | #endif
  15 | #include <iostream>
  16 | #include <sstream>
  17 | #include <string>
  18 | #include <utility>
  19 | 
  20 | // Windows-specific mouse codes
  21 | #ifndef BUTTON4_PRESSED
  22 | #define BUTTON4_PRESSED 0x00200000L
  23 | #endif
  24 | #ifndef BUTTON5_PRESSED
  25 | #define BUTTON5_PRESSED 0x00100000L
  26 | #endif
  27 | 
  28 | // =================================================================
  29 | // Constructor
  30 | // =================================================================
  31 | 
  32 | Editor::Editor(SyntaxHighlighter *highlighter) : syntaxHighlighter(highlighter)
  33 | {
  34 |   tabSize = ConfigManager::getTabSize();
  35 | }
  36 | 
  37 | EditorSnapshot Editor::captureSnapshot() const
  38 | {
  39 |   EditorSnapshot snap;
  40 |   snap.lineCount = buffer.getLineCount();
  41 |   snap.cursorLine = cursorLine;
  42 |   snap.cursorCol = cursorCol;
  43 |   snap.viewportTop = viewportTop;
  44 |   snap.viewportLeft = viewportLeft;
  45 |   snap.bufferSize = buffer.size();
  46 | 
  47 |   if (snap.lineCount > 0)
  48 |   {
  49 |     snap.firstLine = buffer.getLine(0);
  50 |     snap.lastLine = buffer.getLine(snap.lineCount - 1);
  51 |     if (cursorLine < snap.lineCount)
  52 |     {
  53 |       snap.cursorLineContent = buffer.getLine(cursorLine);
  54 |     }
  55 |   }
  56 | 
  57 |   return snap;
  58 | }
  59 | 
  60 | ValidationResult Editor::validateState(const std::string &context) const
  61 | {
  62 |   // Check buffer is not empty
  63 |   if (buffer.getLineCount() == 0)
  64 |   {
  65 |     return ValidationResult("Buffer has 0 lines at: " + context);
  66 |   }
  67 | 
  68 |   // Check cursor line bounds
  69 |   if (cursorLine < 0 || cursorLine >= buffer.getLineCount())
  70 |   {
  71 |     std::ostringstream oss;
  72 |     oss << "Cursor line " << cursorLine << " out of bounds [0, "
  73 |         << buffer.getLineCount() - 1 << "] at: " << context;
  74 |     return ValidationResult(oss.str());
  75 |   }
  76 | 
  77 |   // Check cursor column bounds
  78 |   std::string line = buffer.getLine(cursorLine);
  79 |   if (cursorCol < 0 || cursorCol > static_cast<int>(line.length()))
  80 |   {
  81 |     std::ostringstream oss;
  82 |     oss << "Cursor col " << cursorCol << " out of bounds [0, " << line.length()
  83 |         << "] at: " << context;
  84 |     return ValidationResult(oss.str());
  85 |   }
  86 | 
  87 |   // Check viewport bounds
  88 |   if (viewportTop < 0)
  89 |   {
  90 |     return ValidationResult("Viewport top negative at: " + context);
  91 |   }
  92 | 
  93 |   if (viewportLeft < 0)
  94 |   {
  95 |     return ValidationResult("Viewport left negative at: " + context);
  96 |   }
  97 | 
  98 |   // Check viewport can contain cursor
  99 |   if (cursorLine < viewportTop)
 100 |   {
 101 |     std::ostringstream oss;
 102 |     oss << "Cursor line " << cursorLine << " above viewport " << viewportTop
 103 |         << " at: " + context;
 104 |     return ValidationResult(oss.str());
 105 |   }
 106 | 
 107 |   return ValidationResult(); // All valid
 108 | }
 109 | 
 110 | // Compare two snapshots and report differences
 111 | std::string Editor::compareSnapshots(const EditorSnapshot &before,
 112 |                                      const EditorSnapshot &after) const
 113 | {
 114 |   std::ostringstream oss;
 115 | 
 116 |   if (before.lineCount != after.lineCount)
 117 |   {
 118 |     oss << "LineCount: " << before.lineCount << " -> " << after.lineCount
 119 |         << "\n";
 120 |   }
 121 |   if (before.cursorLine != after.cursorLine)
 122 |   {
 123 |     oss << "CursorLine: " << before.cursorLine << " -> " << after.cursorLine
 124 |         << "\n";
 125 |   }
 126 |   if (before.cursorCol != after.cursorCol)
 127 |   {
 128 |     oss << "CursorCol: " << before.cursorCol << " -> " << after.cursorCol
 129 |         << "\n";
 130 |   }
 131 |   if (before.bufferSize != after.bufferSize)
 132 |   {
 133 |     oss << "BufferSize: " << before.bufferSize << " -> " << after.bufferSize
 134 |         << "\n";
 135 |   }
 136 |   if (before.cursorLineContent != after.cursorLineContent)
 137 |   {
 138 |     oss << "CursorLine content changed\n";
 139 |     oss << "  Before: '" << before.cursorLineContent << "'\n";
 140 |     oss << "  After:  '" << after.cursorLineContent << "'\n";
 141 |   }
 142 | 
 143 |   return oss.str();
 144 | }
 145 | 
 146 | void Editor::reloadConfig()
 147 | {
 148 |   tabSize = ConfigManager::getTabSize();
 149 |   // Trigger redisplay to reflect changes
 150 | }
 151 | 
 152 | // =================================================================
 153 | // Mode Management
 154 | // =================================================================
 155 | 
 156 | // =================================================================
 157 | // Private Helper Methods (from original code)
 158 | // =================================================================
 159 | 
 160 | std::string Editor::expandTabs(const std::string &line, int tabSize)
 161 | {
 162 |   std::string result;
 163 |   for (char c : line)
 164 |   {
 165 |     if (c == '\t')
 166 |     {
 167 |       int spacesToAdd = tabSize - (result.length() % tabSize);
 168 |       result.append(spacesToAdd, ' ');
 169 |     }
 170 |     else if (c >= 32 && c <= 126)
 171 |     {
 172 |       result += c;
 173 |     }
 174 |     else
 175 |     {
 176 |       result += ' ';
 177 |     }
 178 |   }
 179 |   return result;
 180 | }
 181 | 
 182 | std::string Editor::getFileExtension()
 183 | {
 184 |   if (filename.empty())
 185 |     return "";
 186 | 
 187 |   size_t dot = filename.find_last_of(".");
 188 |   if (dot == std::string::npos)
 189 |     return "";
 190 | 
 191 |   std::string ext = filename.substr(dot + 1);
 192 |   std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
 193 | 
 194 |   return ext;
 195 | }
 196 | 
 197 | bool Editor::isPositionSelected(int line, int col)
 198 | {
 199 |   if (!hasSelection && !isSelecting)
 200 |     return false;
 201 | 
 202 |   int startL = selectionStartLine;
 203 |   int startC = selectionStartCol;
 204 |   int endL = selectionEndLine;
 205 |   int endC = selectionEndCol;
 206 | 
 207 |   if (startL > endL || (startL == endL && startC > endC))
 208 |   {
 209 |     std::swap(startL, endL);
 210 |     std::swap(startC, endC);
 211 |   }
 212 | 
 213 |   if (line < startL || line > endL)
 214 |     return false;
 215 | 
 216 |   if (startL == endL)
 217 |   {
 218 |     return col >= startC && col < endC;
 219 |   }
 220 |   else if (line == startL)
 221 |   {
 222 |     return col >= startC;
 223 |   }
 224 |   else if (line == endL)
 225 |   {
 226 |     return col < endC;
 227 |   }
 228 |   else
 229 |   {
 230 |     return true;
 231 |   }
 232 | }
 233 | 
 234 | void Editor::positionCursor()
 235 | {
 236 |   int rows, cols;
 237 |   getmaxyx(stdscr, rows, cols);
 238 | 
 239 |   int screenRow = cursorLine - viewportTop;
 240 |   if (screenRow >= 0 && screenRow < viewportHeight)
 241 |   {
 242 |     bool show_line_numbers = ConfigManager::getLineNumbers();
 243 |     int lineNumWidth =
 244 |         show_line_numbers ? std::to_string(buffer.getLineCount()).length() : 0;
 245 |     int contentStartCol = show_line_numbers ? (lineNumWidth + 3) : 0;
 246 |     int screenCol = contentStartCol + cursorCol - viewportLeft;
 247 | 
 248 |     if (screenCol >= contentStartCol && screenCol < cols)
 249 |     {
 250 |       move(screenRow, screenCol);
 251 |     }
 252 |     else
 253 |     {
 254 |       move(screenRow, contentStartCol);
 255 |     }
 256 |   }
 257 |   // REMOVED: All #ifdef _WIN32 refresh() calls
 258 | }
 259 | 
 260 | bool Editor::mouseToFilePos(int mouseRow, int mouseCol, int &fileRow,
 261 |                             int &fileCol)
 262 | {
 263 |   int rows, cols;
 264 |   getmaxyx(stdscr, rows, cols);
 265 | 
 266 |   if (mouseRow >= rows - 1)
 267 |     return false;
 268 | 
 269 |   bool show_line_numbers = ConfigManager::getLineNumbers();
 270 |   int lineNumWidth =
 271 |       show_line_numbers ? std::to_string(buffer.getLineCount()).length() : 0;
 272 |   int contentStartCol = show_line_numbers ? (lineNumWidth + 3) : 0;
 273 | 
 274 |   if (mouseCol < contentStartCol)
 275 |   {
 276 |     mouseCol = contentStartCol;
 277 |   }
 278 | 
 279 |   fileRow = viewportTop + mouseRow;
 280 |   if (fileRow < 0)
 281 |     fileRow = 0;
 282 |   if (fileRow >= buffer.getLineCount())
 283 |     fileRow = buffer.getLineCount() - 1;
 284 | 
 285 |   fileCol = viewportLeft + (mouseCol - contentStartCol);
 286 |   if (fileCol < 0)
 287 |     fileCol = 0;
 288 | 
 289 |   return true;
 290 | }
 291 | 
 292 | void Editor::updateCursorAndViewport(int newLine, int newCol)
 293 | {
 294 |   cursorLine = newLine;
 295 | 
 296 |   int currentTabSize = ConfigManager::getTabSize();
 297 |   std::string expandedLine =
 298 |       expandTabs(buffer.getLine(cursorLine), currentTabSize);
 299 |   cursorCol = std::min(newCol, static_cast<int>(expandedLine.length()));
 300 | 
 301 |   if (cursorLine < viewportTop)
 302 |   {
 303 |     viewportTop = cursorLine;
 304 |   }
 305 |   else if (cursorLine >= viewportTop + viewportHeight)
 306 |   {
 307 |     viewportTop = cursorLine - viewportHeight + 1;
 308 |   }
 309 | 
 310 |   int rows, cols;
 311 |   getmaxyx(stdscr, rows, cols);
 312 |   bool show_line_numbers = ConfigManager::getLineNumbers();
 313 |   int lineNumWidth =
 314 |       show_line_numbers ? std::to_string(buffer.getLineCount()).length() : 0;
 315 |   int contentWidth = cols - (show_line_numbers ? (lineNumWidth + 3) : 0);
 316 | 
 317 |   if (cursorCol < viewportLeft)
 318 |   {
 319 |     viewportLeft = cursorCol;
 320 |   }
 321 |   else if (cursorCol >= viewportLeft + contentWidth)
 322 |   {
 323 |     viewportLeft = cursorCol - contentWidth + 1;
 324 |   }
 325 | }
 326 | 
 327 | // =================================================================
 328 | // Public API Methods
 329 | // =================================================================
 330 | 
 331 | void Editor::setSyntaxHighlighter(SyntaxHighlighter *highlighter)
 332 | {
 333 |   syntaxHighlighter = highlighter;
 334 | }
 335 | 
 336 | void Editor::display()
 337 | {
 338 |   // Validate state
 339 |   if (!validateEditorState())
 340 |   {
 341 |     validateCursorAndViewport();
 342 |     if (!validateEditorState())
 343 |       return;
 344 |   }
 345 | 
 346 |   int rows, cols;
 347 |   getmaxyx(stdscr, rows, cols);
 348 |   viewportHeight = rows - 1;
 349 | 
 350 |   bool show_line_numbers = ConfigManager::getLineNumbers();
 351 |   int lineNumWidth =
 352 |       show_line_numbers ? std::to_string(buffer.getLineCount()).length() : 0;
 353 |   int contentStartCol = show_line_numbers ? (lineNumWidth + 3) : 0;
 354 |   int contentWidth = cols - contentStartCol;
 355 | 
 356 |   int endLine = std::min(viewportTop + viewportHeight, buffer.getLineCount());
 357 | 
 358 |   // OPTIMIZATION: Pre-mark viewport lines for priority parsing
 359 |   if (syntaxHighlighter)
 360 |   {
 361 |     syntaxHighlighter->markViewportLines(viewportTop, endLine - 1);
 362 |   }
 363 | 
 364 |   // Pre-compute selection (unchanged)
 365 |   bool hasActiveSelection = (hasSelection || isSelecting);
 366 |   int sel_start_line = -1, sel_start_col = -1;
 367 |   int sel_end_line = -1, sel_end_col = -1;
 368 | 
 369 |   if (hasActiveSelection)
 370 |   {
 371 |     auto [start, end] = getNormalizedSelection();
 372 |     sel_start_line = start.first;
 373 |     sel_start_col = start.second;
 374 |     sel_end_line = end.first;
 375 |     sel_end_col = end.second;
 376 |   }
 377 | 
 378 |   int currentTabSize = ConfigManager::getTabSize();
 379 | 
 380 |   // OPTIMIZATION: Batch render - minimize attribute changes
 381 |   for (int i = viewportTop; i < endLine; i++)
 382 |   {
 383 |     int screenRow = i - viewportTop;
 384 |     bool isCurrentLine = (cursorLine == i);
 385 | 
 386 |     move(screenRow, 0);
 387 |     attrset(COLOR_PAIR(0));
 388 | 
 389 |     // Render line numbers
 390 |     if (show_line_numbers)
 391 |     {
 392 |       int ln_colorPair = isCurrentLine ? 3 : 2;
 393 |       attron(COLOR_PAIR(ln_colorPair));
 394 |       printw("%*d ", lineNumWidth, i + 1);
 395 |       attroff(COLOR_PAIR(ln_colorPair));
 396 | 
 397 |       attron(COLOR_PAIR(4));
 398 |       addch(' ');
 399 |       attroff(COLOR_PAIR(4));
 400 |       addch(' ');
 401 |     }
 402 | 
 403 |     // Get line content
 404 |     std::string expandedLine = expandTabs(buffer.getLine(i), currentTabSize);
 405 | 
 406 |     // OPTIMIZATION: Get highlighting spans (cached if available)
 407 |     std::vector<ColorSpan> currentLineSpans;
 408 |     if (syntaxHighlighter)
 409 |     {
 410 |       try
 411 |       {
 412 |         currentLineSpans =
 413 |             syntaxHighlighter->getHighlightSpans(expandedLine, i, buffer);
 414 |       }
 415 |       catch (...)
 416 |       {
 417 |         currentLineSpans.clear();
 418 |       }
 419 |     }
 420 | 
 421 |     // Render line content (unchanged logic, but faster due to cached spans)
 422 |     bool lineHasSelection =
 423 |         hasActiveSelection && i >= sel_start_line && i <= sel_end_line;
 424 |     int current_span_idx = 0;
 425 |     int num_spans = currentLineSpans.size();
 426 | 
 427 |     for (int screenCol = 0; screenCol < contentWidth; screenCol++)
 428 |     {
 429 |       int fileCol = viewportLeft + screenCol;
 430 |       bool charExists =
 431 |           (fileCol >= 0 && fileCol < static_cast<int>(expandedLine.length()));
 432 |       char ch = charExists ? expandedLine[fileCol] : ' ';
 433 | 
 434 |       if (charExists && (ch < 32 || ch > 126))
 435 |         ch = ' ';
 436 | 
 437 |       // Selection check
 438 |       bool isSelected = false;
 439 |       if (lineHasSelection && charExists)
 440 |       {
 441 |         if (sel_start_line == sel_end_line)
 442 |         {
 443 |           isSelected = (fileCol >= sel_start_col && fileCol < sel_end_col);
 444 |         }
 445 |         else if (i == sel_start_line)
 446 |         {
 447 |           isSelected = (fileCol >= sel_start_col);
 448 |         }
 449 |         else if (i == sel_end_line)
 450 |         {
 451 |           isSelected = (fileCol < sel_end_col);
 452 |         }
 453 |         else
 454 |         {
 455 |           isSelected = true;
 456 |         }
 457 |       }
 458 | 
 459 |       if (isSelected)
 460 |       {
 461 |         attron(COLOR_PAIR(14) | A_REVERSE);
 462 |         addch(ch);
 463 |         attroff(COLOR_PAIR(14) | A_REVERSE);
 464 |       }
 465 |       else
 466 |       {
 467 |         bool colorApplied = false;
 468 | 
 469 |         if (charExists && num_spans > 0)
 470 |         {
 471 |           while (current_span_idx < num_spans &&
 472 |                  currentLineSpans[current_span_idx].end <= fileCol)
 473 |           {
 474 |             current_span_idx++;
 475 |           }
 476 | 
 477 |           if (current_span_idx < num_spans)
 478 |           {
 479 |             const auto &span = currentLineSpans[current_span_idx];
 480 |             if (fileCol >= span.start && fileCol < span.end)
 481 |             {
 482 |               if (span.colorPair >= 0 && span.colorPair < COLOR_PAIRS)
 483 |               {
 484 |                 attron(COLOR_PAIR(span.colorPair));
 485 |                 if (span.attribute != 0)
 486 |                   attron(span.attribute);
 487 |                 addch(ch);
 488 |                 if (span.attribute != 0)
 489 |                   attroff(span.attribute);
 490 |                 attroff(COLOR_PAIR(span.colorPair));
 491 |                 colorApplied = true;
 492 |               }
 493 |             }
 494 |           }
 495 |         }
 496 | 
 497 |         if (!colorApplied)
 498 |         {
 499 |           attrset(COLOR_PAIR(0));
 500 |           addch(ch);
 501 |         }
 502 |       }
 503 |     }
 504 | 
 505 |     attrset(COLOR_PAIR(0));
 506 |     clrtoeol();
 507 |   }
 508 | 
 509 |   // Clear remaining lines
 510 |   attrset(COLOR_PAIR(0));
 511 |   for (int i = endLine - viewportTop; i < viewportHeight; i++)
 512 |   {
 513 |     move(i, 0);
 514 |     clrtoeol();
 515 |   }
 516 | 
 517 |   drawStatusBar();
 518 |   positionCursor();
 519 | }
 520 | 
 521 | void Editor::drawStatusBar()
 522 | {
 523 |   int rows, cols;
 524 |   getmaxyx(stdscr, rows, cols);
 525 |   int statusRow = rows - 1;
 526 | 
 527 |   move(statusRow, 0);
 528 |   attrset(COLOR_PAIR(STATUS_BAR));
 529 |   clrtoeol();
 530 | 
 531 |   move(statusRow, 0);
 532 |   attron(COLOR_PAIR(STATUS_BAR));
 533 | 
 534 |   // Show filename
 535 |   attron(COLOR_PAIR(STATUS_BAR_CYAN) | A_BOLD);
 536 |   if (filename.empty())
 537 |   {
 538 |     printw("[No Name]");
 539 |   }
 540 |   else
 541 |   {
 542 |     size_t lastSlash = filename.find_last_of("/\\");
 543 |     std::string displayName = (lastSlash != std::string::npos)
 544 |                                   ? filename.substr(lastSlash + 1)
 545 |                                   : filename;
 546 |     printw("%s", displayName.c_str());
 547 |   }
 548 |   attroff(COLOR_PAIR(STATUS_BAR_CYAN) | A_BOLD);
 549 | 
 550 |   // Show modified indicator
 551 |   if (isModified)
 552 |   {
 553 |     attron(COLOR_PAIR(STATUS_BAR_ACTIVE) | A_BOLD);
 554 |     printw(" [+]");
 555 |     attroff(COLOR_PAIR(STATUS_BAR_ACTIVE) | A_BOLD);
 556 |   }
 557 | 
 558 |   // Show file extension
 559 |   std::string ext = getFileExtension();
 560 |   if (!ext.empty())
 561 |   {
 562 |     attron(COLOR_PAIR(STATUS_BAR_ACTIVE));
 563 |     printw(" [%s]", ext.c_str());
 564 |     attroff(COLOR_PAIR(STATUS_BAR_ACTIVE));
 565 |   }
 566 | 
 567 |   // Right section with position info
 568 |   char rightSection[256];
 569 |   if (hasSelection)
 570 |   {
 571 |     auto [start, end] = getNormalizedSelection();
 572 |     int startL = start.first, startC = start.second;
 573 |     int endL = end.first, endC = end.second;
 574 | 
 575 |     if (startL == endL)
 576 |     {
 577 |       int selectionSize = endC - startC;
 578 |       snprintf(rightSection, sizeof(rightSection),
 579 |                "[%d chars] %d:%d %d/%d %d%% ", selectionSize, cursorLine + 1,
 580 |                cursorCol + 1, cursorLine + 1, buffer.getLineCount(),
 581 |                buffer.getLineCount() == 0
 582 |                    ? 0
 583 |                    : ((cursorLine + 1) * 100 / buffer.getLineCount()));
 584 |     }
 585 |     else
 586 |     {
 587 |       int lineCount = endL - startL + 1;
 588 |       snprintf(rightSection, sizeof(rightSection),
 589 |                "[%d lines] %d:%d %d/%d %d%% ", lineCount, cursorLine + 1,
 590 |                cursorCol + 1, cursorLine + 1, buffer.getLineCount(),
 591 |                buffer.getLineCount() == 0
 592 |                    ? 0
 593 |                    : ((cursorLine + 1) * 100 / buffer.getLineCount()));
 594 |     }
 595 |   }
 596 |   else
 597 |   {
 598 |     snprintf(rightSection, sizeof(rightSection), "%d:%d %d/%d %d%% ",
 599 |              cursorLine + 1, cursorCol + 1, cursorLine + 1,
 600 |              buffer.getLineCount(),
 601 |              buffer.getLineCount() == 0
 602 |                  ? 0
 603 |                  : ((cursorLine + 1) * 100 / buffer.getLineCount()));
 604 |   }
 605 | 
 606 |   int rightLen = strlen(rightSection);
 607 |   int currentPos = getcurx(stdscr);
 608 |   int rightStart = cols - rightLen;
 609 | 
 610 |   if (rightStart <= currentPos)
 611 |   {
 612 |     rightStart = currentPos + 2;
 613 |   }
 614 | 
 615 |   // Fill middle space
 616 |   attron(COLOR_PAIR(STATUS_BAR));
 617 |   for (int i = currentPos; i < rightStart && i < cols; i++)
 618 |   {
 619 |     move(statusRow, i);
 620 |     addch(' ');
 621 |   }
 622 | 
 623 |   // Right section
 624 |   if (rightStart < cols)
 625 |   {
 626 |     move(statusRow, rightStart);
 627 |     attron(COLOR_PAIR(STATUS_BAR_YELLOW) | A_BOLD);
 628 |     printw("%s", rightSection);
 629 |     attroff(COLOR_PAIR(STATUS_BAR_YELLOW) | A_BOLD);
 630 |   }
 631 | 
 632 |   attroff(COLOR_PAIR(STATUS_BAR));
 633 | }
 634 | 
 635 | void Editor::handleResize()
 636 | {
 637 |   int rows, cols;
 638 |   getmaxyx(stdscr, rows, cols);
 639 |   viewportHeight = rows - 1;
 640 | 
 641 |   if (cursorLine >= viewportTop + viewportHeight)
 642 |   {
 643 |     viewportTop = cursorLine - viewportHeight + 1;
 644 |   }
 645 |   if (viewportTop < 0)
 646 |   {
 647 |     viewportTop = 0;
 648 |   }
 649 |   clear();
 650 |   display();
 651 | 
 652 |   wnoutrefresh(stdscr); // Mark stdscr as ready
 653 |   doupdate();           // Execute the single, clean flush
 654 | }
 655 | 
 656 | void Editor::handleMouse(MEVENT &event)
 657 | {
 658 |   if (event.bstate & BUTTON1_PRESSED)
 659 |   {
 660 |     int fileRow, fileCol;
 661 |     if (mouseToFilePos(event.y, event.x, fileRow, fileCol))
 662 |     {
 663 |       // Start a new selection on mouse press
 664 |       clearSelection();
 665 |       isSelecting = true;
 666 |       selectionStartLine = fileRow;
 667 |       selectionStartCol = fileCol;
 668 |       selectionEndLine = fileRow;
 669 |       selectionEndCol = fileCol;
 670 |       updateCursorAndViewport(fileRow, fileCol);
 671 |     }
 672 |   }
 673 |   else if (event.bstate & BUTTON1_RELEASED)
 674 |   {
 675 |     if (isSelecting)
 676 |     {
 677 |       int fileRow, fileCol;
 678 |       if (mouseToFilePos(event.y, event.x, fileRow, fileCol))
 679 |       {
 680 |         selectionEndLine = fileRow;
 681 |         selectionEndCol = fileCol;
 682 |         // Only keep selection if it's not just a click (start != end)
 683 |         if (selectionStartLine != selectionEndLine ||
 684 |             selectionStartCol != selectionEndCol)
 685 |         {
 686 |           hasSelection = true;
 687 |         }
 688 |         else
 689 |         {
 690 |           // Just a click, no drag - clear selection
 691 |           clearSelection();
 692 |         }
 693 |         updateCursorAndViewport(fileRow, fileCol);
 694 |       }
 695 |       isSelecting = false;
 696 |     }
 697 |   }
 698 |   else if ((event.bstate & REPORT_MOUSE_POSITION) && isSelecting)
 699 |   {
 700 |     // Mouse drag - extend selection
 701 |     int fileRow, fileCol;
 702 |     if (mouseToFilePos(event.y, event.x, fileRow, fileCol))
 703 |     {
 704 |       selectionEndLine = fileRow;
 705 |       selectionEndCol = fileCol;
 706 |       updateCursorAndViewport(fileRow, fileCol);
 707 |     }
 708 |   }
 709 |   else if (event.bstate & BUTTON1_CLICKED)
 710 |   {
 711 |     // Single click - move cursor and clear selection
 712 |     int fileRow, fileCol;
 713 |     if (mouseToFilePos(event.y, event.x, fileRow, fileCol))
 714 |     {
 715 |       clearSelection();
 716 |       updateCursorAndViewport(fileRow, fileCol);
 717 |     }
 718 |   }
 719 |   else if (event.bstate & BUTTON4_PRESSED)
 720 |   {
 721 |     // Scroll up
 722 |     scrollUp();
 723 |   }
 724 |   else if (event.bstate & BUTTON5_PRESSED)
 725 |   {
 726 |     // Scroll down
 727 |     scrollDown();
 728 |   }
 729 | }
 730 | 
 731 | void Editor::clearSelection()
 732 | {
 733 |   hasSelection = false;
 734 |   isSelecting = false;
 735 |   selectionStartLine = 0;
 736 |   selectionStartCol = 0;
 737 |   selectionEndLine = 0;
 738 |   selectionEndCol = 0;
 739 | }
 740 | 
 741 | void Editor::moveCursorUp()
 742 | {
 743 |   if (cursorLine > 0)
 744 |   {
 745 |     cursorLine--;
 746 |     if (cursorLine < viewportTop)
 747 |     {
 748 |       viewportTop = cursorLine;
 749 |     }
 750 | 
 751 |     if (cursorCol > 0)
 752 |     {
 753 |       std::string line = buffer.getLine(cursorLine);
 754 |       int lineLen = static_cast<int>(line.length());
 755 |       if (cursorCol > lineLen)
 756 |       {
 757 |         std::string expandedLine = expandTabs(line, tabSize);
 758 |         cursorCol =
 759 |             std::min(cursorCol, static_cast<int>(expandedLine.length()));
 760 |       }
 761 |     }
 762 |   }
 763 |   // Note: Selection handling now done in InputHandler
 764 | }
 765 | 
 766 | void Editor::moveCursorDown()
 767 | {
 768 |   int maxLine = buffer.getLineCount() - 1;
 769 |   if (cursorLine < maxLine)
 770 |   {
 771 |     cursorLine++;
 772 |     if (cursorLine >= viewportTop + viewportHeight)
 773 |     {
 774 |       viewportTop = cursorLine - viewportHeight + 1;
 775 |     }
 776 | 
 777 |     if (cursorCol > 0)
 778 |     {
 779 |       std::string line = buffer.getLine(cursorLine);
 780 |       int lineLen = static_cast<int>(line.length());
 781 |       if (cursorCol > lineLen)
 782 |       {
 783 |         std::string expandedLine = expandTabs(line, tabSize);
 784 |         cursorCol =
 785 |             std::min(cursorCol, static_cast<int>(expandedLine.length()));
 786 |       }
 787 |     }
 788 |   }
 789 | }
 790 | 
 791 | void Editor::moveCursorLeft()
 792 | {
 793 |   if (cursorCol > 0)
 794 |   {
 795 |     cursorCol--;
 796 |     if (cursorCol < viewportLeft)
 797 |     {
 798 |       viewportLeft = cursorCol;
 799 |     }
 800 |   }
 801 |   else if (cursorLine > 0)
 802 |   {
 803 |     cursorLine--;
 804 |     int currentTabSize = ConfigManager::getTabSize();
 805 |     std::string expandedLine =
 806 |         expandTabs(buffer.getLine(cursorLine), currentTabSize);
 807 |     cursorCol = expandedLine.length();
 808 | 
 809 |     if (cursorLine < viewportTop)
 810 |     {
 811 |       viewportTop = cursorLine;
 812 |     }
 813 | 
 814 |     int rows, cols;
 815 |     getmaxyx(stdscr, rows, cols);
 816 |     bool show_line_numbers = ConfigManager::getLineNumbers();
 817 |     int lineNumWidth =
 818 |         show_line_numbers ? std::to_string(buffer.getLineCount()).length() : 0;
 819 |     int contentWidth = cols - (show_line_numbers ? (lineNumWidth + 3) : 0);
 820 | 
 821 |     if (contentWidth > 0 && cursorCol >= viewportLeft + contentWidth)
 822 |     {
 823 |       viewportLeft = cursorCol - contentWidth + 1;
 824 |       if (viewportLeft < 0)
 825 |         viewportLeft = 0;
 826 |     }
 827 |   }
 828 | }
 829 | 
 830 | void Editor::moveCursorRight()
 831 | {
 832 |   std::string line = buffer.getLine(cursorLine);
 833 | 
 834 |   if (cursorCol < static_cast<int>(line.length()))
 835 |   {
 836 |     if (line[cursorCol] != '\t')
 837 |     {
 838 |       cursorCol++;
 839 |     }
 840 |     else
 841 |     {
 842 |       int currentTabSize = ConfigManager::getTabSize();
 843 |       std::string expandedLine = expandTabs(line, currentTabSize);
 844 |       if (cursorCol < static_cast<int>(expandedLine.length()))
 845 |       {
 846 |         cursorCol++;
 847 |       }
 848 |     }
 849 | 
 850 |     int rows, cols;
 851 |     getmaxyx(stdscr, rows, cols);
 852 |     bool show_line_numbers = ConfigManager::getLineNumbers();
 853 |     int lineNumWidth =
 854 |         show_line_numbers ? std::to_string(buffer.getLineCount()).length() : 0;
 855 |     int contentWidth = cols - (show_line_numbers ? (lineNumWidth + 3) : 0);
 856 | 
 857 |     if (contentWidth > 0 && cursorCol >= viewportLeft + contentWidth)
 858 |     {
 859 |       viewportLeft = cursorCol - contentWidth + 1;
 860 |     }
 861 |   }
 862 |   else if (cursorLine < buffer.getLineCount() - 1)
 863 |   {
 864 |     cursorLine++;
 865 |     cursorCol = 0;
 866 | 
 867 |     if (cursorLine >= viewportTop + viewportHeight)
 868 |     {
 869 |       viewportTop = cursorLine - viewportHeight + 1;
 870 |     }
 871 | 
 872 |     viewportLeft = 0;
 873 |   }
 874 | }
 875 | 
 876 | void Editor::pageUp()
 877 | {
 878 |   for (int i = 0; i < 10; i++)
 879 |   {
 880 |     moveCursorUp();
 881 |   }
 882 | }
 883 | 
 884 | void Editor::pageDown()
 885 | {
 886 |   for (int i = 0; i < 10; i++)
 887 |   {
 888 |     moveCursorDown();
 889 |   }
 890 | }
 891 | 
 892 | void Editor::moveCursorToLineStart()
 893 | {
 894 |   cursorCol = 0;
 895 |   if (cursorCol < viewportLeft)
 896 |   {
 897 |     viewportLeft = 0;
 898 |   }
 899 | 
 900 |   // Selection handling is done in InputHandler, not here
 901 |   // This method just moves the cursor
 902 | }
 903 | 
 904 | void Editor::moveCursorToLineEnd()
 905 | {
 906 |   int currentTabSize = ConfigManager::getTabSize();
 907 |   std::string expandedLine =
 908 |       expandTabs(buffer.getLine(cursorLine), currentTabSize);
 909 |   cursorCol = static_cast<int>(expandedLine.length());
 910 | 
 911 |   int rows, cols;
 912 |   getmaxyx(stdscr, rows, cols);
 913 |   bool show_line_numbers = ConfigManager::getLineNumbers();
 914 |   int lineNumWidth =
 915 |       show_line_numbers ? std::to_string(buffer.getLineCount()).length() : 0;
 916 |   int contentWidth = cols - (show_line_numbers ? (lineNumWidth + 3) : 0);
 917 | 
 918 |   if (contentWidth > 0 && cursorCol >= viewportLeft + contentWidth)
 919 |   {
 920 |     viewportLeft = cursorCol - contentWidth + 1;
 921 |     if (viewportLeft < 0)
 922 |       viewportLeft = 0;
 923 |   }
 924 | 
 925 |   // Selection handling is done in InputHandler, not here
 926 |   // This method just moves the cursor
 927 | }
 928 | 
 929 | void Editor::scrollUp(int linesToScroll)
 930 | {
 931 |   viewportTop -= linesToScroll;
 932 |   if (viewportTop < 0)
 933 |     viewportTop = 0;
 934 | 
 935 |   if (cursorLine < viewportTop)
 936 |   {
 937 |     cursorLine = viewportTop;
 938 |     if (cursorLine < 0)
 939 |       cursorLine = 0;
 940 |     if (cursorLine >= buffer.getLineCount())
 941 |     {
 942 |       cursorLine = buffer.getLineCount() - 1;
 943 |     }
 944 | 
 945 |     std::string expandedLine = expandTabs(buffer.getLine(cursorLine), tabSize);
 946 |     cursorCol = std::min(cursorCol, static_cast<int>(expandedLine.length()));
 947 |   }
 948 | }
 949 | 
 950 | void Editor::scrollDown(int linesToScroll)
 951 | {
 952 |   int maxViewportTop = buffer.getLineCount() - viewportHeight;
 953 |   if (maxViewportTop < 0)
 954 |     maxViewportTop = 0;
 955 | 
 956 |   viewportTop += linesToScroll;
 957 |   if (viewportTop > maxViewportTop)
 958 |     viewportTop = maxViewportTop;
 959 |   if (viewportTop < 0)
 960 |     viewportTop = 0;
 961 | 
 962 |   if (cursorLine >= viewportTop + viewportHeight)
 963 |   {
 964 |     cursorLine = viewportTop + viewportHeight - 1;
 965 | 
 966 |     int maxLine = buffer.getLineCount() - 1;
 967 |     if (cursorLine > maxLine)
 968 |       cursorLine = maxLine;
 969 |     if (cursorLine < 0)
 970 |       cursorLine = 0;
 971 | 
 972 |     std::string expandedLine = expandTabs(buffer.getLine(cursorLine), tabSize);
 973 |     cursorCol = std::min(cursorCol, static_cast<int>(expandedLine.length()));
 974 |   }
 975 | }
 976 | 
 977 | void Editor::validateCursorAndViewport()
 978 | {
 979 |   if (buffer.getLineCount() == 0)
 980 |     return;
 981 | 
 982 |   int maxLine = buffer.getLineCount() - 1;
 983 |   if (cursorLine < 0)
 984 |     cursorLine = 0;
 985 |   if (cursorLine > maxLine)
 986 |     cursorLine = maxLine;
 987 | 
 988 |   std::string expandedLine = expandTabs(buffer.getLine(cursorLine), tabSize);
 989 |   if (cursorCol < 0)
 990 |     cursorCol = 0;
 991 |   if (cursorCol > static_cast<int>(expandedLine.length()))
 992 |   {
 993 |     cursorCol = static_cast<int>(expandedLine.length());
 994 |   }
 995 | 
 996 |   int maxViewportTop = buffer.getLineCount() - viewportHeight;
 997 |   if (maxViewportTop < 0)
 998 |     maxViewportTop = 0;
 999 | 
1000 |   if (viewportTop < 0)
1001 |     viewportTop = 0;
1002 |   if (viewportTop > maxViewportTop)
1003 |     viewportTop = maxViewportTop;
1004 |   if (viewportLeft < 0)
1005 |     viewportLeft = 0;
1006 | 
1007 |   if (cursorLine < viewportTop)
1008 |   {
1009 |     viewportTop = cursorLine;
1010 |   }
1011 |   else if (cursorLine >= viewportTop + viewportHeight)
1012 |   {
1013 |     viewportTop = cursorLine - viewportHeight + 1;
1014 |     if (viewportTop < 0)
1015 |       viewportTop = 0;
1016 |     if (viewportTop > maxViewportTop)
1017 |       viewportTop = maxViewportTop;
1018 |   }
1019 | }
1020 | 
1021 | // =================================================================
1022 | // File Operations
1023 | // =================================================================
1024 | 
1025 | void Editor::debugPrintState(const std::string &context)
1026 | {
1027 |   std::cerr << "=== EDITOR STATE DEBUG: " << context << " ===" << std::endl;
1028 |   std::cerr << "cursorLine: " << cursorLine << std::endl;
1029 |   std::cerr << "cursorCol: " << cursorCol << std::endl;
1030 |   std::cerr << "viewportTop: " << viewportTop << std::endl;
1031 |   std::cerr << "viewportLeft: " << viewportLeft << std::endl;
1032 |   std::cerr << "buffer.getLineCount(): " << buffer.getLineCount() << std::endl;
1033 |   std::cerr << "buffer.size(): " << buffer.size() << std::endl;
1034 |   std::cerr << "isModified: " << isModified << std::endl;
1035 |   // std::cerr << "currentMode: " << (int)currentMode << std::endl;
1036 | 
1037 |   if (cursorLine < buffer.getLineCount())
1038 |   {
1039 |     std::string currentLine = buffer.getLine(cursorLine);
1040 |     std::cerr << "currentLine length: " << currentLine.length() << std::endl;
1041 |     std::cerr << "currentLine content: '" << currentLine << "'" << std::endl;
1042 |   }
1043 |   else
1044 |   {
1045 |     std::cerr << "ERROR: cursorLine out of bounds!" << std::endl;
1046 |   }
1047 | 
1048 |   std::cerr << "hasSelection: " << hasSelection << std::endl;
1049 |   std::cerr << "isSelecting: " << isSelecting << std::endl;
1050 |   std::cerr << "undoStack.size(): " << undoStack.size() << std::endl;
1051 |   std::cerr << "redoStack.size(): " << redoStack.size() << std::endl;
1052 |   std::cerr << "=== END DEBUG ===" << std::endl;
1053 | }
1054 | 
1055 | bool Editor::validateEditorState()
1056 | {
1057 |   bool valid = true;
1058 | 
1059 |   if (cursorLine < 0 || cursorLine >= buffer.getLineCount())
1060 |   {
1061 |     std::cerr << "INVALID: cursorLine out of bounds: " << cursorLine
1062 |               << " (max: " << buffer.getLineCount() - 1 << ")" << std::endl;
1063 |     valid = false;
1064 |   }
1065 | 
1066 |   if (cursorCol < 0)
1067 |   {
1068 |     std::cerr << "INVALID: cursorCol negative: " << cursorCol << std::endl;
1069 |     valid = false;
1070 |   }
1071 | 
1072 |   if (cursorLine >= 0 && cursorLine < buffer.getLineCount())
1073 |   {
1074 |     std::string line = buffer.getLine(cursorLine);
1075 |     if (cursorCol > static_cast<int>(line.length()))
1076 |     {
1077 |       std::cerr << "INVALID: cursorCol past end of line: " << cursorCol
1078 |                 << " (line length: " << line.length() << ")" << std::endl;
1079 |       valid = false;
1080 |     }
1081 |   }
1082 | 
1083 |   if (viewportTop < 0)
1084 |   {
1085 |     std::cerr << "INVALID: viewportTop negative: " << viewportTop << std::endl;
1086 |     valid = false;
1087 |   }
1088 | 
1089 |   if (viewportLeft < 0)
1090 |   {
1091 |     std::cerr << "INVALID: viewportLeft negative: " << viewportLeft
1092 |               << std::endl;
1093 |     valid = false;
1094 |   }
1095 | 
1096 |   return valid;
1097 | }
1098 | 
1099 | bool Editor::loadFile(const std::string &fname)
1100 | {
1101 |   filename = fname;
1102 | 
1103 |   if (syntaxHighlighter)
1104 |   {
1105 |     std::string extension = getFileExtension();
1106 |     syntaxHighlighter->setLanguage(extension);
1107 |   }
1108 | 
1109 |   if (!buffer.loadFromFile(filename))
1110 |   {
1111 |     buffer.clear();
1112 |     buffer.insertLine(0, "");
1113 |     return false;
1114 |   }
1115 | 
1116 |   // Set language but DON'T parse yet - parsing happens on first display
1117 | 
1118 |   isModified = false;
1119 |   return true;
1120 | }
1121 | 
1122 | bool Editor::saveFile()
1123 | {
1124 |   if (filename.empty())
1125 |   {
1126 |     return false;
1127 |   }
1128 | 
1129 |   // Set flag to prevent saveState() during file operations
1130 |   isSaving = true;
1131 | 
1132 |   bool success = buffer.saveToFile(filename);
1133 | 
1134 |   if (success)
1135 |   {
1136 |     isModified = false;
1137 |   }
1138 | 
1139 |   isSaving = false; // Reset flag
1140 |   return success;
1141 | }
1142 | // =================================================================
1143 | // Text Editing Operations
1144 | // =================================================================
1145 | 
1146 | void Editor::insertChar(char ch)
1147 | {
1148 |   if (cursorLine < 0 || cursorLine >= buffer.getLineCount())
1149 |     return;
1150 | 
1151 |   if (useDeltaUndo_ && !isUndoRedoing)
1152 |   {
1153 |     EditDelta delta = createDeltaForInsertChar(ch);
1154 |     std::string line = buffer.getLine(cursorLine);
1155 |     if (cursorCol > static_cast<int>(line.length()))
1156 |       cursorCol = line.length();
1157 |     if (cursorCol < 0)
1158 |       cursorCol = 0;
1159 | 
1160 |     size_t byte_pos = buffer.lineColToPos(cursorLine, cursorCol);
1161 |     line.insert(cursorCol, 1, ch);
1162 |     buffer.replaceLine(cursorLine, line);
1163 |     cursorCol++;
1164 | 
1165 |     if (syntaxHighlighter && !isUndoRedoing)
1166 |     {
1167 |       syntaxHighlighter->updateTreeAfterEdit(
1168 |           buffer, byte_pos, 0, 1, cursorLine, cursorCol - 1, cursorLine,
1169 |           cursorCol - 1, cursorLine, cursorCol);
1170 | 
1171 |       // NEW: Always invalidate cache after edit
1172 |       syntaxHighlighter->invalidateLineCache(cursorLine);
1173 |     }
1174 | 
1175 |     // Update viewport
1176 |     int rows, cols;
1177 |     getmaxyx(stdscr, rows, cols);
1178 |     bool show_line_numbers = ConfigManager::getLineNumbers();
1179 |     int lineNumWidth =
1180 |         show_line_numbers ? std::to_string(buffer.getLineCount()).length() : 0;
1181 |     int contentWidth = cols - (show_line_numbers ? (lineNumWidth + 3) : 0);
1182 | 
1183 |     if (contentWidth > 0 && cursorCol >= viewportLeft + contentWidth)
1184 |     {
1185 |       viewportLeft = cursorCol - contentWidth + 1;
1186 |     }
1187 | 
1188 |     // Complete delta
1189 |     delta.postCursorLine = cursorLine;
1190 |     delta.postCursorCol = cursorCol;
1191 |     delta.postViewportTop = viewportTop;
1192 |     delta.postViewportLeft = viewportLeft;
1193 | 
1194 |     addDelta(delta);
1195 | 
1196 |     // FIX: Auto-commit on timeout OR boundary characters for immediate
1197 |     // highlighting
1198 |     auto now = std::chrono::steady_clock::now();
1199 |     auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
1200 |                        now - currentDeltaGroup_.timestamp)
1201 |                        .count();
1202 | 
1203 |     // Boundary characters that should trigger immediate commit
1204 |     bool is_boundary_char =
1205 |         (ch == '>' || ch == ')' || ch == '}' || ch == ']' || ch == ';' ||
1206 |          ch == ',' || ch == ' ' || ch == '\t' || ch == '<' || ch == '(' ||
1207 |          ch == '{' || ch == '[');
1208 | 
1209 |     if (elapsed > UNDO_GROUP_TIMEOUT_MS || is_boundary_char)
1210 |     {
1211 |       commitDeltaGroup();
1212 |       beginDeltaGroup();
1213 |     }
1214 | 
1215 |     markModified();
1216 |   }
1217 |   else if (!isUndoRedoing)
1218 |   {
1219 |     // OLD: Full-state undo (fallback)
1220 |     saveState();
1221 | 
1222 |     std::string line = buffer.getLine(cursorLine);
1223 |     if (cursorCol > static_cast<int>(line.length()))
1224 |       cursorCol = line.length();
1225 |     if (cursorCol < 0)
1226 |       cursorCol = 0;
1227 | 
1228 |     size_t byte_pos = buffer.lineColToPos(cursorLine, cursorCol);
1229 |     line.insert(cursorCol, 1, ch);
1230 |     buffer.replaceLine(cursorLine, line);
1231 | 
1232 |     if (syntaxHighlighter && !isUndoRedoing)
1233 |     {
1234 |       syntaxHighlighter->updateTreeAfterEdit(buffer, byte_pos, 0, 1, cursorLine,
1235 |                                              cursorCol, cursorLine, cursorCol,
1236 |                                              cursorLine, cursorCol + 1);
1237 |       syntaxHighlighter->invalidateLineRange(cursorLine, cursorLine);
1238 |     }
1239 | 
1240 |     cursorCol++;
1241 |     markModified();
1242 | 
1243 |     int rows, cols;
1244 |     getmaxyx(stdscr, rows, cols);
1245 |     int lineNumWidth = std::to_string(buffer.getLineCount()).length();
1246 |     int contentWidth = cols - lineNumWidth - 3;
1247 |     if (contentWidth > 0 && cursorCol >= viewportLeft + contentWidth)
1248 |     {
1249 |       viewportLeft = cursorCol - contentWidth + 1;
1250 |     }
1251 |   }
1252 | }
1253 | 
1254 | void Editor::insertNewline()
1255 | {
1256 |   if (useDeltaUndo_ && !isUndoRedoing)
1257 |   {
1258 |     EditorSnapshot before = captureSnapshot();
1259 |     EditDelta delta = createDeltaForNewline();
1260 | 
1261 |     size_t byte_pos = buffer.lineColToPos(cursorLine, cursorCol);
1262 | 
1263 |     // 1. MODIFY BUFFER FIRST
1264 |     splitLineAtCursor();
1265 |     cursorLine++;
1266 |     cursorCol = 0;
1267 | 
1268 |     // 2. THEN notify Tree-sitter AFTER buffer change
1269 |     if (syntaxHighlighter && !isUndoRedoing)
1270 |     {
1271 |       syntaxHighlighter->updateTreeAfterEdit(
1272 |           buffer, byte_pos, 0, 1,                  // Inserted 1 byte (newline)
1273 |           delta.preCursorLine, delta.preCursorCol, // OLD position
1274 |           delta.preCursorLine, delta.preCursorCol, cursorLine,
1275 |           0); // NEW position
1276 | 
1277 |       // Invalidate from split point onwards
1278 |       syntaxHighlighter->invalidateLineRange(cursorLine - 1,
1279 |                                              buffer.getLineCount() - 1);
1280 |     }
1281 | 
1282 |     if (cursorLine >= viewportTop + viewportHeight)
1283 |     {
1284 |       viewportTop = cursorLine - viewportHeight + 1;
1285 |     }
1286 |     viewportLeft = 0;
1287 | 
1288 |     // Complete delta
1289 |     delta.postCursorLine = cursorLine;
1290 |     delta.postCursorCol = cursorCol;
1291 |     delta.postViewportTop = viewportTop;
1292 |     delta.postViewportLeft = viewportLeft;
1293 | 
1294 |     ValidationResult valid = validateState("After insertNewline");
1295 |     if (valid)
1296 |     {
1297 |       addDelta(delta);
1298 |       // Newlines always commit the current group
1299 |       commitDeltaGroup();
1300 |       beginDeltaGroup();
1301 |     }
1302 |     else
1303 |     {
1304 |       std::cerr << "VALIDATION FAILED in insertNewline\n";
1305 |       std::cerr << valid.error << "\n";
1306 |     }
1307 | 
1308 |     markModified();
1309 |   }
1310 |   else if (!isUndoRedoing)
1311 |   {
1312 |     // OLD: Full-state undo
1313 |     saveState();
1314 | 
1315 |     size_t byte_pos = buffer.lineColToPos(cursorLine, cursorCol);
1316 | 
1317 |     splitLineAtCursor();
1318 |     cursorLine++;
1319 |     cursorCol = 0;
1320 | 
1321 |     if (syntaxHighlighter && !isUndoRedoing)
1322 |     {
1323 |       syntaxHighlighter->updateTreeAfterEdit(buffer, byte_pos, 0, 1,
1324 |                                              cursorLine - 1, 0, cursorLine - 1,
1325 |                                              0, cursorLine, 0);
1326 |       syntaxHighlighter->invalidateLineRange(cursorLine - 1,
1327 |                                              buffer.getLineCount() - 1);
1328 |     }
1329 | 
1330 |     if (cursorLine >= viewportTop + viewportHeight)
1331 |     {
1332 |       viewportTop = cursorLine - viewportHeight + 1;
1333 |     }
1334 |     viewportLeft = 0;
1335 |     markModified();
1336 |   }
1337 | }
1338 | 
1339 | void Editor::deleteChar()
1340 | {
1341 |   if (useDeltaUndo_ && !isUndoRedoing)
1342 |   {
1343 |     EditorSnapshot before = captureSnapshot();
1344 |     EditDelta delta = createDeltaForDeleteChar();
1345 |     std::string line = buffer.getLine(cursorLine);
1346 | 
1347 |     if (cursorCol < static_cast<int>(line.length()))
1348 |     {
1349 |       size_t byte_pos = buffer.lineColToPos(cursorLine, cursorCol);
1350 |       line.erase(cursorCol, 1);
1351 |       buffer.replaceLine(cursorLine, line);
1352 | 
1353 |       if (syntaxHighlighter && !isUndoRedoing)
1354 |       {
1355 |         syntaxHighlighter->updateTreeAfterEdit(
1356 |             buffer, byte_pos, 1, 0, cursorLine, cursorCol, cursorLine,
1357 |             cursorCol + 1, cursorLine, cursorCol);
1358 | 
1359 |         // NEW: Always invalidate cache after edit
1360 |         syntaxHighlighter->invalidateLineCache(cursorLine);
1361 |       }
1362 |     }
1363 |     else if (cursorLine < buffer.getLineCount() - 1)
1364 |     {
1365 |       size_t byte_pos = buffer.lineColToPos(cursorLine, line.length());
1366 |       std::string nextLine = buffer.getLine(cursorLine + 1);
1367 |       buffer.replaceLine(cursorLine, line + nextLine);
1368 |       buffer.deleteLine(cursorLine + 1);
1369 | 
1370 |       if (syntaxHighlighter && !isUndoRedoing)
1371 |       {
1372 |         syntaxHighlighter->updateTreeAfterEdit(
1373 |             buffer, byte_pos, 1, 0, cursorLine, (uint32_t)line.length(),
1374 |             cursorLine + 1, 0, cursorLine, (uint32_t)line.length());
1375 | 
1376 |         syntaxHighlighter->invalidateLineRange(cursorLine,
1377 |                                                buffer.getLineCount() - 1);
1378 |       }
1379 |     }
1380 | 
1381 |     delta.postCursorLine = cursorLine;
1382 |     delta.postCursorCol = cursorCol;
1383 |     delta.postViewportTop = viewportTop;
1384 |     delta.postViewportLeft = viewportLeft;
1385 | 
1386 |     ValidationResult valid = validateState("After deleteChar");
1387 |     if (valid)
1388 |     {
1389 |       addDelta(delta);
1390 |       if (delta.operation == EditDelta::JOIN_LINES)
1391 |       {
1392 |         commitDeltaGroup();
1393 |         beginDeltaGroup();
1394 |       }
1395 |     }
1396 |     else
1397 |     {
1398 |       std::cerr << "VALIDATION FAILED in deleteChar\n";
1399 |       std::cerr << valid.error << "\n";
1400 |     }
1401 | 
1402 |     markModified();
1403 |   }
1404 |   else if (!isUndoRedoing)
1405 |   {
1406 |     saveState();
1407 |     std::string line = buffer.getLine(cursorLine);
1408 | 
1409 |     if (cursorCol < static_cast<int>(line.length()))
1410 |     {
1411 |       size_t byte_pos = buffer.lineColToPos(cursorLine, cursorCol);
1412 |       line.erase(cursorCol, 1);
1413 |       buffer.replaceLine(cursorLine, line);
1414 | 
1415 |       if (syntaxHighlighter && !isUndoRedoing)
1416 |       {
1417 |         syntaxHighlighter->updateTreeAfterEdit(
1418 |             buffer, byte_pos, 1, 0, cursorLine, cursorCol, cursorLine,
1419 |             cursorCol + 1, cursorLine, cursorCol);
1420 |         // NEW: Always invalidate cache after edit
1421 |         syntaxHighlighter->invalidateLineCache(cursorLine);
1422 |       }
1423 | 
1424 |       markModified();
1425 |     }
1426 |     else if (cursorLine < buffer.getLineCount() - 1)
1427 |     {
1428 |       size_t byte_pos = buffer.lineColToPos(cursorLine, line.length());
1429 |       std::string nextLine = buffer.getLine(cursorLine + 1);
1430 |       buffer.replaceLine(cursorLine, line + nextLine);
1431 |       buffer.deleteLine(cursorLine + 1);
1432 | 
1433 |       if (syntaxHighlighter && !isUndoRedoing)
1434 |       {
1435 |         syntaxHighlighter->updateTreeAfterEdit(
1436 |             buffer, byte_pos, 1, 0, cursorLine, (uint32_t)line.length(),
1437 |             cursorLine + 1, 0, cursorLine, (uint32_t)line.length());
1438 |         syntaxHighlighter->invalidateLineRange(cursorLine,
1439 |                                                buffer.getLineCount() - 1);
1440 |       }
1441 | 
1442 |       markModified();
1443 |     }
1444 |   }
1445 | }
1446 | 
1447 | void Editor::backspace()
1448 | {
1449 |   if (useDeltaUndo_ && !isUndoRedoing)
1450 |   {
1451 |     EditorSnapshot before = captureSnapshot();
1452 |     EditDelta delta = createDeltaForBackspace();
1453 | 
1454 |     if (cursorCol > 0)
1455 |     {
1456 |       std::string line = buffer.getLine(cursorLine);
1457 |       size_t byte_pos = buffer.lineColToPos(cursorLine, cursorCol - 1);
1458 |       line.erase(cursorCol - 1, 1);
1459 |       buffer.replaceLine(cursorLine, line);
1460 |       cursorCol--;
1461 | 
1462 |       if (syntaxHighlighter && !isUndoRedoing)
1463 |       {
1464 |         syntaxHighlighter->updateTreeAfterEdit(
1465 |             buffer, byte_pos, 1, 0, cursorLine, cursorCol, cursorLine,
1466 |             cursorCol + 1, cursorLine, cursorCol);
1467 | 
1468 |         // NEW: Always invalidate cache after edit
1469 |         syntaxHighlighter->invalidateLineCache(cursorLine);
1470 |       }
1471 | 
1472 |       if (cursorCol < viewportLeft)
1473 |       {
1474 |         viewportLeft = cursorCol;
1475 |       }
1476 |     }
1477 |     else if (cursorLine > 0)
1478 |     {
1479 |       std::string currentLine = buffer.getLine(cursorLine);
1480 |       std::string prevLine = buffer.getLine(cursorLine - 1);
1481 |       size_t byte_pos = buffer.lineColToPos(cursorLine - 1, prevLine.length());
1482 | 
1483 |       int oldCursorLine = cursorLine;
1484 |       cursorCol = static_cast<int>(prevLine.length());
1485 |       cursorLine--;
1486 | 
1487 |       buffer.replaceLine(cursorLine, prevLine + currentLine);
1488 |       buffer.deleteLine(cursorLine + 1);
1489 | 
1490 |       if (syntaxHighlighter && !isUndoRedoing)
1491 |       {
1492 |         syntaxHighlighter->updateTreeAfterEdit(
1493 |             buffer, byte_pos, 1, 0, cursorLine, cursorCol, oldCursorLine, 0,
1494 |             cursorLine, cursorCol);
1495 | 
1496 |         syntaxHighlighter->invalidateLineRange(cursorLine,
1497 |                                                buffer.getLineCount() - 1);
1498 |       }
1499 |     }
1500 | 
1501 |     delta.postCursorLine = cursorLine;
1502 |     delta.postCursorCol = cursorCol;
1503 |     delta.postViewportTop = viewportTop;
1504 |     delta.postViewportLeft = viewportLeft;
1505 | 
1506 |     ValidationResult valid = validateState("After backspace");
1507 |     if (valid)
1508 |     {
1509 |       addDelta(delta);
1510 |       if (delta.operation == EditDelta::JOIN_LINES)
1511 |       {
1512 |         commitDeltaGroup();
1513 |         beginDeltaGroup();
1514 |       }
1515 |     }
1516 |     else
1517 |     {
1518 |       std::cerr << "VALIDATION FAILED in backspace\n";
1519 |       std::cerr << valid.error << "\n";
1520 |     }
1521 | 
1522 |     markModified();
1523 |   }
1524 |   else if (!isUndoRedoing)
1525 |   {
1526 |     saveState();
1527 | 
1528 |     if (cursorCol > 0)
1529 |     {
1530 |       size_t byte_pos = buffer.lineColToPos(cursorLine, cursorCol - 1);
1531 |       std::string line = buffer.getLine(cursorLine);
1532 |       line.erase(cursorCol - 1, 1);
1533 |       buffer.replaceLine(cursorLine, line);
1534 |       cursorCol--;
1535 | 
1536 |       if (syntaxHighlighter && !isUndoRedoing)
1537 |       {
1538 |         syntaxHighlighter->updateTreeAfterEdit(
1539 |             buffer, byte_pos, 1, 0, cursorLine, cursorCol, cursorLine,
1540 |             cursorCol + 1, cursorLine, cursorCol);
1541 |         // NEW: Always invalidate cache after edit
1542 |         syntaxHighlighter->invalidateLineCache(cursorLine);
1543 |       }
1544 | 
1545 |       if (cursorCol < viewportLeft)
1546 |       {
1547 |         viewportLeft = cursorCol;
1548 |       }
1549 | 
1550 |       markModified();
1551 |     }
1552 |     else if (cursorLine > 0)
1553 |     {
1554 |       std::string currentLine = buffer.getLine(cursorLine);
1555 |       std::string prevLine = buffer.getLine(cursorLine - 1);
1556 |       size_t byte_pos = buffer.lineColToPos(cursorLine - 1, prevLine.length());
1557 | 
1558 |       cursorCol = static_cast<int>(prevLine.length());
1559 |       cursorLine--;
1560 | 
1561 |       buffer.replaceLine(cursorLine, prevLine + currentLine);
1562 |       buffer.deleteLine(cursorLine + 1);
1563 | 
1564 |       if (syntaxHighlighter && !isUndoRedoing)
1565 |       {
1566 |         syntaxHighlighter->updateTreeAfterEdit(
1567 |             buffer, byte_pos, 1, 0, cursorLine, cursorCol, cursorLine + 1, 0,
1568 |             cursorLine, cursorCol);
1569 |         syntaxHighlighter->invalidateLineRange(cursorLine,
1570 |                                                buffer.getLineCount() - 1);
1571 |       }
1572 | 
1573 |       markModified();
1574 |     }
1575 |   }
1576 | }
1577 | 
1578 | void Editor::deleteLine()
1579 | {
1580 |   // SAVE STATE BEFORE MODIFICATION
1581 |   if (!isUndoRedoing)
1582 |   {
1583 |     saveState();
1584 |   }
1585 | 
1586 |   if (buffer.getLineCount() == 1)
1587 |   {
1588 |     std::string line = buffer.getLine(0);
1589 |     size_t byte_pos = 0;
1590 | 
1591 |     buffer.replaceLine(0, "");
1592 |     cursorCol = 0;
1593 | 
1594 |     if (syntaxHighlighter && !isUndoRedoing)
1595 |     {
1596 |       syntaxHighlighter->updateTreeAfterEdit(buffer, byte_pos, line.length(), 0,
1597 |                                              0, 0, 0, (uint32_t)line.length(),
1598 |                                              0, 0);
1599 |       syntaxHighlighter->invalidateLineRange(0, 0);
1600 |     }
1601 | 
1602 |     // buffer.replaceLine(0, "");
1603 |     // cursorCol = 0;
1604 |   }
1605 |   else
1606 |   {
1607 |     size_t byte_pos = buffer.lineColToPos(cursorLine, 0);
1608 |     std::string line = buffer.getLine(cursorLine);
1609 |     size_t line_length = line.length();
1610 | 
1611 |     bool has_newline = (cursorLine < buffer.getLineCount() - 1);
1612 |     size_t delete_bytes = line_length + (has_newline ? 1 : 0);
1613 | 
1614 |     buffer.deleteLine(cursorLine);
1615 | 
1616 |     if (syntaxHighlighter && !isUndoRedoing)
1617 |     {
1618 |       syntaxHighlighter->updateTreeAfterEdit(
1619 |           buffer, byte_pos, delete_bytes, 0, cursorLine, 0,
1620 |           cursorLine + (has_newline ? 1 : 0),
1621 |           has_newline ? 0 : (uint32_t)line_length, cursorLine, 0);
1622 |       syntaxHighlighter->invalidateLineRange(cursorLine,
1623 |                                              buffer.getLineCount() - 1);
1624 |     }
1625 | 
1626 |     if (cursorLine >= buffer.getLineCount())
1627 |     {
1628 |       cursorLine = buffer.getLineCount() - 1;
1629 |     }
1630 | 
1631 |     line = buffer.getLine(cursorLine);
1632 |     if (cursorCol > static_cast<int>(line.length()))
1633 |     {
1634 |       cursorCol = static_cast<int>(line.length());
1635 |     }
1636 |   }
1637 | 
1638 |   validateCursorAndViewport();
1639 |   markModified();
1640 | }
1641 | 
1642 | // =================================================================
1643 | // Undo/Redo System
1644 | // =================================================================
1645 | 
1646 | void Editor::deleteSelection()
1647 | {
1648 |   if (!hasSelection && !isSelecting)
1649 |     return;
1650 | 
1651 |   if (useDeltaUndo_ && !isUndoRedoing)
1652 |   {
1653 |     EditorSnapshot before = captureSnapshot();
1654 |     EditDelta delta = createDeltaForDeleteSelection();
1655 | 
1656 |     auto selection = getNormalizedSelection();
1657 |     int startLine = selection.first.first;
1658 |     int startCol = selection.first.second;
1659 |     int endLine = selection.second.first;
1660 |     int endCol = selection.second.second;
1661 | 
1662 |     size_t start_byte = buffer.lineColToPos(startLine, startCol);
1663 |     size_t end_byte = buffer.lineColToPos(endLine, endCol);
1664 |     size_t delete_bytes = end_byte - start_byte;
1665 | 
1666 |     // 1. MODIFY BUFFER FIRST
1667 |     if (startLine == endLine)
1668 |     {
1669 |       std::string line = buffer.getLine(startLine);
1670 |       line.erase(startCol, endCol - startCol);
1671 |       buffer.replaceLine(startLine, line);
1672 |     }
1673 |     else
1674 |     {
1675 |       std::string firstLine = buffer.getLine(startLine);
1676 |       std::string lastLine = buffer.getLine(endLine);
1677 | 
1678 |       std::string newLine =
1679 |           firstLine.substr(0, startCol) + lastLine.substr(endCol);
1680 |       buffer.replaceLine(startLine, newLine);
1681 | 
1682 |       for (int i = endLine; i > startLine; i--)
1683 |       {
1684 |         buffer.deleteLine(i);
1685 |       }
1686 |     }
1687 | 
1688 |     // 2. THEN notify Tree-sitter AFTER buffer change
1689 |     if (syntaxHighlighter && !isUndoRedoing)
1690 |     {
1691 |       syntaxHighlighter->updateTreeAfterEdit(
1692 |           buffer, start_byte, delete_bytes, 0, // Deleted bytes
1693 |           startLine, startCol, endLine, endCol, startLine, startCol);
1694 | 
1695 |       syntaxHighlighter->invalidateLineRange(startLine,
1696 |                                              buffer.getLineCount() - 1);
1697 |     }
1698 | 
1699 |     updateCursorAndViewport(startLine, startCol);
1700 |     clearSelection();
1701 | 
1702 |     // Complete delta
1703 |     delta.postCursorLine = cursorLine;
1704 |     delta.postCursorCol = cursorCol;
1705 |     delta.postViewportTop = viewportTop;
1706 |     delta.postViewportLeft = viewportLeft;
1707 | 
1708 |     ValidationResult valid = validateState("After deleteSelection");
1709 |     if (valid)
1710 |     {
1711 |       addDelta(delta);
1712 |       commitDeltaGroup();
1713 |       beginDeltaGroup();
1714 |     }
1715 |     else
1716 |     {
1717 |       std::cerr << "VALIDATION FAILED in deleteSelection\n";
1718 |       std::cerr << valid.error << "\n";
1719 |     }
1720 | 
1721 |     markModified();
1722 |   }
1723 |   else if (!isUndoRedoing)
1724 |   {
1725 |     // OLD: Full-state undo
1726 |     saveState();
1727 | 
1728 |     auto selection = getNormalizedSelection();
1729 |     int startLine = selection.first.first;
1730 |     int startCol = selection.first.second;
1731 |     int endLine = selection.second.first;
1732 |     int endCol = selection.second.second;
1733 | 
1734 |     size_t start_byte = buffer.lineColToPos(startLine, startCol);
1735 |     size_t end_byte = buffer.lineColToPos(endLine, endCol);
1736 |     size_t delete_bytes = end_byte - start_byte;
1737 | 
1738 |     if (startLine == endLine)
1739 |     {
1740 |       std::string line = buffer.getLine(startLine);
1741 |       line.erase(startCol, endCol - startCol);
1742 |       buffer.replaceLine(startLine, line);
1743 |     }
1744 |     else
1745 |     {
1746 |       std::string firstLine = buffer.getLine(startLine);
1747 |       std::string lastLine = buffer.getLine(endLine);
1748 | 
1749 |       std::string newLine =
1750 |           firstLine.substr(0, startCol) + lastLine.substr(endCol);
1751 |       buffer.replaceLine(startLine, newLine);
1752 | 
1753 |       for (int i = endLine; i > startLine; i--)
1754 |       {
1755 |         buffer.deleteLine(i);
1756 |       }
1757 |     }
1758 | 
1759 |     if (syntaxHighlighter && !isUndoRedoing)
1760 |     {
1761 |       syntaxHighlighter->updateTreeAfterEdit(buffer, start_byte, delete_bytes,
1762 |                                              0, startLine, startCol, endLine,
1763 |                                              endCol, startLine, startCol);
1764 |       syntaxHighlighter->invalidateLineRange(startLine,
1765 |                                              buffer.getLineCount() - 1);
1766 |     }
1767 | 
1768 |     updateCursorAndViewport(startLine, startCol);
1769 |     clearSelection();
1770 |     markModified();
1771 |   }
1772 | }
1773 | 
1774 | void Editor::undo()
1775 | {
1776 |   if (useDeltaUndo_)
1777 |   {
1778 |     // Commit any pending delta group first
1779 |     if (!currentDeltaGroup_.isEmpty())
1780 |     {
1781 |       commitDeltaGroup();
1782 |     }
1783 | 
1784 |     if (deltaUndoStack_.empty())
1785 |     {
1786 |       return;
1787 |     }
1788 | 
1789 | #ifdef DEBUG_DELTA_UNDO
1790 |     std::cerr << "\n=== UNDO START ===\n";
1791 |     EditorSnapshot beforeUndo = captureSnapshot();
1792 | #endif
1793 | 
1794 |     // Get the delta group to undo
1795 |     DeltaGroup group = deltaUndoStack_.top();
1796 |     deltaUndoStack_.pop();
1797 | 
1798 | #ifdef DEBUG_DELTA_UNDO
1799 |     std::cerr << "Undoing group:\n" << group.toString() << "\n";
1800 | #endif
1801 | 
1802 |     // Track affected line range for incremental highlighting
1803 |     int minAffectedLine = buffer.getLineCount();
1804 |     int maxAffectedLine = 0;
1805 | 
1806 |     // Apply deltas in REVERSE order
1807 |     for (auto it = group.deltas.rbegin(); it != group.deltas.rend(); ++it)
1808 |     {
1809 |       // Track which lines are affected
1810 |       minAffectedLine =
1811 |           std::min(minAffectedLine, std::min(it->startLine, it->preCursorLine));
1812 |       maxAffectedLine =
1813 |           std::max(maxAffectedLine, std::max(it->endLine, it->postCursorLine));
1814 | 
1815 |       applyDeltaReverse(*it);
1816 | 
1817 | #ifdef DEBUG_DELTA_UNDO
1818 |       ValidationResult valid = validateState("After undo delta");
1819 |       if (!valid)
1820 |       {
1821 |         std::cerr << "CRITICAL: Validation failed during undo!\n";
1822 |         std::cerr << valid.error << "\n";
1823 |       }
1824 | #endif
1825 |     }
1826 | 
1827 |     // Save to redo stack
1828 |     deltaRedoStack_.push(group);
1829 | 
1830 |     // FIXED: Incremental syntax update instead of full reparse
1831 |     if (syntaxHighlighter)
1832 |     {
1833 |       // Only invalidate affected line range, not entire cache
1834 |       syntaxHighlighter->invalidateLineRange(minAffectedLine,
1835 |                                              buffer.getLineCount() - 1);
1836 | 
1837 |       // Use viewport-only parsing for immediate visual update
1838 |       syntaxHighlighter->parseViewportOnly(buffer, viewportTop);
1839 | 
1840 |       // Schedule background full reparse (non-blocking)
1841 |       syntaxHighlighter->scheduleBackgroundParse(buffer);
1842 |     }
1843 | 
1844 |     isModified = true;
1845 | 
1846 | #ifdef DEBUG_DELTA_UNDO
1847 |     EditorSnapshot afterUndo = captureSnapshot();
1848 |     std::cerr << "Affected lines: " << minAffectedLine << " to "
1849 |               << maxAffectedLine << "\n";
1850 |     std::cerr << "=== UNDO END ===\n\n";
1851 | #endif
1852 |   }
1853 |   else
1854 |   {
1855 |     // OLD: Full-state undo (fallback)
1856 |     if (undoStack.empty())
1857 |       return;
1858 | 
1859 |     isUndoRedoing = true;
1860 |     redoStack.push(getCurrentState());
1861 |     EditorState state = undoStack.top();
1862 |     undoStack.pop();
1863 |     restoreState(state);
1864 | 
1865 |     if (syntaxHighlighter)
1866 |     {
1867 |       syntaxHighlighter->bufferChanged(buffer);
1868 |     }
1869 | 
1870 |     isModified = true;
1871 |     isUndoRedoing = false;
1872 |   }
1873 | }
1874 | 
1875 | void Editor::redo()
1876 | {
1877 |   if (useDeltaUndo_)
1878 |   {
1879 |     if (deltaRedoStack_.empty())
1880 |     {
1881 |       return;
1882 |     }
1883 | 
1884 | #ifdef DEBUG_DELTA_UNDO
1885 |     std::cerr << "\n=== REDO START ===\n";
1886 |     EditorSnapshot beforeRedo = captureSnapshot();
1887 | #endif
1888 | 
1889 |     // Get the delta group to redo
1890 |     DeltaGroup group = deltaRedoStack_.top();
1891 |     deltaRedoStack_.pop();
1892 | 
1893 | #ifdef DEBUG_DELTA_UNDO
1894 |     std::cerr << "Redoing group:\n" << group.toString() << "\n";
1895 | #endif
1896 | 
1897 |     // Track affected line range
1898 |     int minAffectedLine = buffer.getLineCount();
1899 |     int maxAffectedLine = 0;
1900 | 
1901 |     // Apply deltas in FORWARD order
1902 |     for (const auto &delta : group.deltas)
1903 |     {
1904 |       minAffectedLine = std::min(
1905 |           minAffectedLine, std::min(delta.startLine, delta.preCursorLine));
1906 |       maxAffectedLine = std::max(maxAffectedLine,
1907 |                                  std::max(delta.endLine, delta.postCursorLine));
1908 | 
1909 |       applyDeltaForward(delta);
1910 | 
1911 | #ifdef DEBUG_DELTA_UNDO
1912 |       ValidationResult valid = validateState("After redo delta");
1913 |       if (!valid)
1914 |       {
1915 |         std::cerr << "CRITICAL: Validation failed during redo!\n";
1916 |         std::cerr << valid.error << "\n";
1917 |       }
1918 | #endif
1919 |     }
1920 | 
1921 |     // Save to undo stack
1922 |     deltaUndoStack_.push(group);
1923 | 
1924 |     // FIXED: Incremental syntax update
1925 |     if (syntaxHighlighter)
1926 |     {
1927 |       syntaxHighlighter->invalidateLineRange(minAffectedLine,
1928 |                                              buffer.getLineCount() - 1);
1929 |       syntaxHighlighter->parseViewportOnly(buffer, viewportTop);
1930 |       syntaxHighlighter->scheduleBackgroundParse(buffer);
1931 |     }
1932 | 
1933 |     isModified = true;
1934 | 
1935 | #ifdef DEBUG_DELTA_UNDO
1936 |     EditorSnapshot afterRedo = captureSnapshot();
1937 |     std::cerr << "Affected lines: " << minAffectedLine << " to "
1938 |               << maxAffectedLine << "\n";
1939 |     std::cerr << "=== REDO END ===\n\n";
1940 | #endif
1941 |   }
1942 |   else
1943 |   {
1944 |     // OLD: Full-state redo (fallback)
1945 |     if (redoStack.empty())
1946 |       return;
1947 | 
1948 |     isUndoRedoing = true;
1949 |     undoStack.push(getCurrentState());
1950 |     EditorState state = redoStack.top();
1951 |     redoStack.pop();
1952 |     restoreState(state);
1953 | 
1954 |     if (syntaxHighlighter)
1955 |     {
1956 |       syntaxHighlighter->bufferChanged(buffer);
1957 |     }
1958 | 
1959 |     isModified = true;
1960 |     isUndoRedoing = false;
1961 |   }
1962 | }
1963 | 
1964 | EditorState Editor::getCurrentState()
1965 | {
1966 |   EditorState state;
1967 | 
1968 |   // Your existing serialization code
1969 |   std::ostringstream oss;
1970 |   for (int i = 0; i < buffer.getLineCount(); i++)
1971 |   {
1972 |     oss << buffer.getLine(i);
1973 |     if (i < buffer.getLineCount() - 1)
1974 |     {
1975 |       oss << "\n";
1976 |     }
1977 |   }
1978 |   state.content = oss.str();
1979 | 
1980 |   // Save cursor/viewport state regardless
1981 |   state.cursorLine = cursorLine;
1982 |   state.cursorCol = cursorCol;
1983 |   state.viewportTop = viewportTop;
1984 |   state.viewportLeft = viewportLeft;
1985 | 
1986 |   return state;
1987 | }
1988 | 
1989 | void Editor::restoreState(const EditorState &state)
1990 | {
1991 |   // Clear buffer and reload content
1992 |   buffer.clear();
1993 | 
1994 |   std::istringstream iss(state.content);
1995 |   std::string line;
1996 |   int lineNum = 0;
1997 | 
1998 |   while (std::getline(iss, line))
1999 |   {
2000 |     buffer.insertLine(lineNum++, line);
2001 |   }
2002 | 
2003 |   // If no lines were added, add empty line
2004 |   if (buffer.getLineCount() == 0)
2005 |   {
2006 |     buffer.insertLine(0, "");
2007 |   }
2008 | 
2009 |   // Restore cursor and viewport
2010 |   cursorLine = state.cursorLine;
2011 |   cursorCol = state.cursorCol;
2012 |   viewportTop = state.viewportTop;
2013 |   viewportLeft = state.viewportLeft;
2014 | 
2015 |   validateCursorAndViewport();
2016 | }
2017 | 
2018 | void Editor::limitUndoStack()
2019 | {
2020 |   while (undoStack.size() > MAX_UNDO_LEVELS)
2021 |   {
2022 |     // Remove oldest state (bottom of stack)
2023 |     std::stack<EditorState> temp;
2024 |     bool first = true;
2025 | 
2026 |     while (!undoStack.empty())
2027 |     {
2028 |       if (first)
2029 |       {
2030 |         first = false;
2031 |         undoStack.pop(); // Skip the oldest
2032 |       }
2033 |       else
2034 |       {
2035 |         temp.push(undoStack.top());
2036 |         undoStack.pop();
2037 |       }
2038 |     }
2039 | 
2040 |     // Restore stack in correct order
2041 |     while (!temp.empty())
2042 |     {
2043 |       undoStack.push(temp.top());
2044 |       temp.pop();
2045 |     }
2046 |   }
2047 | }
2048 | 
2049 | // =================================================================
2050 | // Internal Helpers
2051 | // =================================================================
2052 | 
2053 | void Editor::markModified() { isModified = true; }
2054 | 
2055 | void Editor::splitLineAtCursor()
2056 | {
2057 |   std::string line = buffer.getLine(cursorLine);
2058 |   std::string leftPart = line.substr(0, cursorCol);
2059 |   std::string rightPart = line.substr(cursorCol);
2060 | 
2061 |   buffer.replaceLine(cursorLine, leftPart);
2062 |   buffer.insertLine(cursorLine + 1, rightPart);
2063 | }
2064 | 
2065 | void Editor::joinLineWithNext()
2066 | {
2067 |   if (cursorLine < buffer.getLineCount() - 1)
2068 |   {
2069 |     std::string currentLine = buffer.getLine(cursorLine);
2070 |     std::string nextLine = buffer.getLine(cursorLine + 1);
2071 | 
2072 |     buffer.replaceLine(cursorLine, currentLine + nextLine);
2073 |     buffer.deleteLine(cursorLine + 1);
2074 |   }
2075 | }
2076 | 
2077 | std::pair<std::pair<int, int>, std::pair<int, int>>
2078 | Editor::getNormalizedSelection()
2079 | {
2080 |   int startLine = selectionStartLine;
2081 |   int startCol = selectionStartCol;
2082 |   int endLine = selectionEndLine;
2083 |   int endCol = selectionEndCol;
2084 | 
2085 |   // Always normalize so start < end
2086 |   if (startLine > endLine || (startLine == endLine && startCol > endCol))
2087 |   {
2088 |     std::swap(startLine, endLine);
2089 |     std::swap(startCol, endCol);
2090 |   }
2091 | 
2092 |   return {{startLine, startCol}, {endLine, endCol}};
2093 | }
2094 | std::string Editor::getSelectedText()
2095 | {
2096 |   if (!hasSelection && !isSelecting)
2097 |     return "";
2098 | 
2099 |   auto [start, end] = getNormalizedSelection();
2100 |   int startLine = start.first, startCol = start.second;
2101 |   int endLine = end.first, endCol = end.second;
2102 | 
2103 |   std::ostringstream result;
2104 | 
2105 |   if (startLine == endLine)
2106 |   {
2107 |     // Single line selection
2108 |     std::string line = buffer.getLine(startLine);
2109 |     result << line.substr(startCol, endCol - startCol);
2110 |   }
2111 |   else
2112 |   {
2113 |     // Multi-line selection
2114 |     for (int i = startLine; i <= endLine; i++)
2115 |     {
2116 |       std::string line = buffer.getLine(i);
2117 | 
2118 |       if (i == startLine)
2119 |       {
2120 |         result << line.substr(startCol);
2121 |       }
2122 |       else if (i == endLine)
2123 |       {
2124 |         result << line.substr(0, endCol);
2125 |       }
2126 |       else
2127 |       {
2128 |         result << line;
2129 |       }
2130 | 
2131 |       if (i < endLine)
2132 |       {
2133 |         result << "\n";
2134 |       }
2135 |     }
2136 |   }
2137 | 
2138 |   // CRITICAL FIX: Actually return the result!
2139 |   return result.str();
2140 | }
2141 | 
2142 | // Selection management
2143 | void Editor::startSelectionIfNeeded()
2144 | {
2145 |   if (!hasSelection && !isSelecting)
2146 |   {
2147 |     isSelecting = true;
2148 |     selectionStartLine = cursorLine;
2149 |     selectionStartCol = cursorCol;
2150 |     selectionEndLine = cursorLine;
2151 |     selectionEndCol = cursorCol;
2152 |   }
2153 | }
2154 | 
2155 | void Editor::updateSelectionEnd()
2156 | {
2157 |   if (isSelecting || hasSelection)
2158 |   {
2159 |     selectionEndLine = cursorLine;
2160 |     selectionEndCol = cursorCol;
2161 |     hasSelection = true;
2162 |   }
2163 | }
2164 | 
2165 | // Clipboard operations
2166 | void Editor::copySelection()
2167 | {
2168 |   if (!hasSelection && !isSelecting)
2169 |     return;
2170 | 
2171 |   clipboard = getSelectedText();
2172 | 
2173 |   // On Unix, also copy to system clipboard using xclip or xsel
2174 | #ifndef _WIN32
2175 |   FILE *pipe = popen("xclip -selection clipboard 2>/dev/null || xsel "
2176 |                      "--clipboard --input 2>/dev/null",
2177 |                      "w");
2178 |   if (pipe)
2179 |   {
2180 |     fwrite(clipboard.c_str(), 1, clipboard.length(), pipe);
2181 |     pclose(pipe);
2182 |   }
2183 | #endif
2184 | }
2185 | 
2186 | void Editor::cutSelection()
2187 | {
2188 |   if (!hasSelection && !isSelecting)
2189 |     return;
2190 | 
2191 |   copySelection();
2192 |   deleteSelection();
2193 | }
2194 | 
2195 | void Editor::pasteFromClipboard()
2196 | {
2197 |   // Try to get from system clipboard first
2198 | #ifndef _WIN32
2199 |   FILE *pipe = popen("xclip -selection clipboard -o 2>/dev/null || xsel "
2200 |                      "--clipboard --output 2>/dev/null",
2201 |                      "r");
2202 |   if (pipe)
2203 |   {
2204 |     char buffer_chars[4096];
2205 |     std::string result;
2206 |     while (fgets(buffer_chars, sizeof(buffer_chars), pipe))
2207 |     {
2208 |       result += buffer_chars;
2209 |     }
2210 |     pclose(pipe);
2211 | 
2212 |     if (!result.empty())
2213 |     {
2214 |       clipboard = result;
2215 |     }
2216 |   }
2217 | #endif
2218 | 
2219 |   if (clipboard.empty())
2220 |     return;
2221 | 
2222 |   // SAVE STATE BEFORE PASTE (single undo point for entire paste)
2223 |   if (!isUndoRedoing)
2224 |   {
2225 |     saveState();
2226 |   }
2227 | 
2228 |   // Delete selection if any
2229 |   if (hasSelection || isSelecting)
2230 |   {
2231 |     deleteSelection();
2232 |   }
2233 | 
2234 |   // Insert clipboard content character by character
2235 |   // Note: Each insertChar/insertNewline will NOT call saveState
2236 |   // because we already saved it above
2237 |   for (char ch : clipboard)
2238 |   {
2239 |     if (ch == '\n')
2240 |     {
2241 |       insertNewline();
2242 |     }
2243 |     else
2244 |     {
2245 |       insertChar(ch);
2246 |     }
2247 |   }
2248 | }
2249 | 
2250 | void Editor::selectAll()
2251 | {
2252 |   if (buffer.getLineCount() == 0)
2253 |     return;
2254 | 
2255 |   selectionStartLine = 0;
2256 |   selectionStartCol = 0;
2257 | 
2258 |   selectionEndLine = buffer.getLineCount() - 1;
2259 |   std::string lastLine = buffer.getLine(selectionEndLine);
2260 |   selectionEndCol = static_cast<int>(lastLine.length());
2261 | 
2262 |   hasSelection = true;
2263 |   isSelecting = false;
2264 | }
2265 | 
2266 | void Editor::initializeViewportHighlighting()
2267 | {
2268 |   if (syntaxHighlighter)
2269 |   {
2270 |     // Pre-parse viewport so first display() is instant
2271 |     syntaxHighlighter->parseViewportOnly(buffer, viewportTop);
2272 |   }
2273 | }
2274 | 
2275 | // Cursor
2276 | void Editor::setCursorMode()
2277 | {
2278 |   switch (currentMode)
2279 |   {
2280 |   case CursorMode::NORMAL:
2281 |     // Block cursor (solid block)
2282 |     printf("\033[2 q");
2283 |     fflush(stdout);
2284 |     break;
2285 |   case CursorMode::INSERT:
2286 |     // Vertical bar cursor (thin lifne like VSCode/modern editors)
2287 |     printf("\033[6 q");
2288 |     fflush(stdout);
2289 |     break;
2290 |   case CursorMode::VISUAL:
2291 |     // Underline cursor for visual mode
2292 |     printf("\033[4 q");
2293 |     fflush(stdout);
2294 |     break;
2295 |   default:
2296 |     printf("\033[6 q");
2297 |     fflush(stdout);
2298 |     break;
2299 |   }
2300 | }
2301 | 
2302 | // === Delta Group Management ===
2303 | 
2304 | void Editor::beginDeltaGroup()
2305 | {
2306 |   currentDeltaGroup_ = DeltaGroup();
2307 |   currentDeltaGroup_.initialLineCount = buffer.getLineCount();
2308 |   currentDeltaGroup_.initialBufferSize = buffer.size();
2309 |   currentDeltaGroup_.timestamp = std::chrono::steady_clock::now();
2310 | }
2311 | 
2312 | void Editor::addDelta(const EditDelta &delta)
2313 | {
2314 |   currentDeltaGroup_.addDelta(delta);
2315 | 
2316 | // DEBUG: Validate after every delta in debug builds
2317 | #ifdef DEBUG_DELTA_UNDO
2318 |   ValidationResult valid = validateState("After adding delta");
2319 |   if (!valid)
2320 |   {
2321 |     std::cerr << "VALIDATION FAILED after delta:\n";
2322 |     std::cerr << delta.toString() << "\n";
2323 |     std::cerr << "Error: " << valid.error << "\n";
2324 |     std::cerr << "Current state: " << captureSnapshot().toString() << "\n";
2325 |   }
2326 | #endif
2327 | }
2328 | 
2329 | void Editor::commitDeltaGroup()
2330 | {
2331 |   if (currentDeltaGroup_.isEmpty())
2332 |   {
2333 |     return;
2334 |   }
2335 | 
2336 |   // Validate before committing
2337 |   ValidationResult valid = validateState("Before committing delta group");
2338 |   if (!valid)
2339 |   {
2340 |     std::cerr << "WARNING: Invalid state before commit, discarding group\n";
2341 |     std::cerr << valid.error << "\n";
2342 |     currentDeltaGroup_ = DeltaGroup();
2343 |     return;
2344 |   }
2345 | 
2346 |   deltaUndoStack_.push(currentDeltaGroup_);
2347 | 
2348 |   // Clear redo stack on new edit
2349 |   while (!deltaRedoStack_.empty())
2350 |   {
2351 |     deltaRedoStack_.pop();
2352 |   }
2353 | 
2354 |   // Limit stack size
2355 |   while (deltaUndoStack_.size() > MAX_UNDO_LEVELS)
2356 |   {
2357 |     // Remove oldest (bottom of stack)
2358 |     std::stack<DeltaGroup> temp;
2359 |     bool first = true;
2360 |     while (!deltaUndoStack_.empty())
2361 |     {
2362 |       if (first)
2363 |       {
2364 |         first = false;
2365 |         deltaUndoStack_.pop(); // Discard oldest
2366 |       }
2367 |       else
2368 |       {
2369 |         temp.push(deltaUndoStack_.top());
2370 |         deltaUndoStack_.pop();
2371 |       }
2372 |     }
2373 |     while (!temp.empty())
2374 |     {
2375 |       deltaUndoStack_.push(temp.top());
2376 |       temp.pop();
2377 |     }
2378 |   }
2379 | 
2380 |   currentDeltaGroup_ = DeltaGroup();
2381 | }
2382 | 
2383 | // === Delta Creation for Each Operation ===
2384 | 
2385 | EditDelta Editor::createDeltaForInsertChar(char ch)
2386 | {
2387 |   EditDelta delta;
2388 |   delta.operation = EditDelta::INSERT_CHAR;
2389 | 
2390 |   // Capture state BEFORE edit
2391 |   delta.preCursorLine = cursorLine;
2392 |   delta.preCursorCol = cursorCol;
2393 |   delta.preViewportTop = viewportTop;
2394 |   delta.preViewportLeft = viewportLeft;
2395 | 
2396 |   delta.startLine = cursorLine;
2397 |   delta.startCol = cursorCol;
2398 |   delta.endLine = cursorLine;
2399 |   delta.endCol = cursorCol;
2400 | 
2401 |   // Content: what we're inserting
2402 |   delta.insertedContent = std::string(1, ch);
2403 |   delta.deletedContent = ""; // Nothing deleted
2404 | 
2405 |   // No structural change
2406 |   delta.lineCountDelta = 0;
2407 | 
2408 |   // Post-state will be filled after edit completes
2409 |   return delta;
2410 | }
2411 | 
2412 | EditDelta Editor::createDeltaForDeleteChar()
2413 | {
2414 |   EditDelta delta;
2415 |   delta.operation = EditDelta::DELETE_CHAR;
2416 | 
2417 |   // Capture state BEFORE deletion
2418 |   delta.preCursorLine = cursorLine;
2419 |   delta.preCursorCol = cursorCol;
2420 |   delta.preViewportTop = viewportTop;
2421 |   delta.preViewportLeft = viewportLeft;
2422 | 
2423 |   delta.startLine = cursorLine;
2424 |   delta.startCol = cursorCol;
2425 | 
2426 |   // Capture what we're about to delete
2427 |   std::string line = buffer.getLine(cursorLine);
2428 | 
2429 |   if (cursorCol < static_cast<int>(line.length()))
2430 |   {
2431 |     // Deleting a character on current line
2432 |     delta.deletedContent = std::string(1, line[cursorCol]);
2433 |     delta.endLine = cursorLine;
2434 |     delta.endCol = cursorCol + 1;
2435 |     delta.lineCountDelta = 0;
2436 |   }
2437 |   else if (cursorLine < buffer.getLineCount() - 1)
2438 |   {
2439 |     // Deleting newline - will join lines
2440 |     delta.operation = EditDelta::JOIN_LINES;
2441 |     delta.deletedContent = "\n";
2442 |     delta.endLine = cursorLine + 1;
2443 |     delta.endCol = 0;
2444 |     delta.lineCountDelta = -1;
2445 | 
2446 |     // Save line contents for reversal
2447 |     delta.firstLineBeforeJoin = line;
2448 |     delta.secondLineBeforeJoin = buffer.getLine(cursorLine + 1);
2449 |   }
2450 | 
2451 |   delta.insertedContent = ""; // Nothing inserted
2452 | 
2453 |   return delta;
2454 | }
2455 | 
2456 | EditDelta Editor::createDeltaForBackspace()
2457 | {
2458 |   EditDelta delta;
2459 |   delta.operation = EditDelta::DELETE_CHAR;
2460 | 
2461 |   // Capture state BEFORE deletion
2462 |   delta.preCursorLine = cursorLine;
2463 |   delta.preCursorCol = cursorCol;
2464 |   delta.preViewportTop = viewportTop;
2465 |   delta.preViewportLeft = viewportLeft;
2466 | 
2467 |   if (cursorCol > 0)
2468 |   {
2469 |     // Deleting character before cursor on same line
2470 |     std::string line = buffer.getLine(cursorLine);
2471 |     delta.deletedContent = std::string(1, line[cursorCol - 1]);
2472 | 
2473 |     delta.startLine = cursorLine;
2474 |     delta.startCol = cursorCol - 1;
2475 |     delta.endLine = cursorLine;
2476 |     delta.endCol = cursorCol;
2477 |     delta.lineCountDelta = 0;
2478 |   }
2479 |   else if (cursorLine > 0)
2480 |   {
2481 |     // Backspace at line start - join with previous line
2482 |     delta.operation = EditDelta::JOIN_LINES;
2483 |     delta.deletedContent = "\n";
2484 | 
2485 |     std::string prevLine = buffer.getLine(cursorLine - 1);
2486 |     std::string currLine = buffer.getLine(cursorLine);
2487 | 
2488 |     delta.startLine = cursorLine - 1;
2489 |     delta.startCol = prevLine.length();
2490 |     delta.endLine = cursorLine;
2491 |     delta.endCol = 0;
2492 |     delta.lineCountDelta = -1;
2493 | 
2494 |     // Save line contents for reversal
2495 |     delta.firstLineBeforeJoin = prevLine;
2496 |     delta.secondLineBeforeJoin = currLine;
2497 |   }
2498 | 
2499 |   delta.insertedContent = ""; // Nothing inserted
2500 | 
2501 |   return delta;
2502 | }
2503 | 
2504 | EditDelta Editor::createDeltaForNewline()
2505 | {
2506 |   EditDelta delta;
2507 |   delta.operation = EditDelta::SPLIT_LINE;
2508 | 
2509 |   // Capture state BEFORE split
2510 |   delta.preCursorLine = cursorLine;
2511 |   delta.preCursorCol = cursorCol;
2512 |   delta.preViewportTop = viewportTop;
2513 |   delta.preViewportLeft = viewportLeft;
2514 | 
2515 |   delta.startLine = cursorLine;
2516 |   delta.startCol = cursorCol;
2517 |   delta.endLine = cursorLine + 1; // New line will be created
2518 |   delta.endCol = 0;
2519 | 
2520 |   // Save the line content before split
2521 |   delta.lineBeforeSplit = buffer.getLine(cursorLine);
2522 | 
2523 |   delta.insertedContent = "\n";
2524 |   delta.deletedContent = "";
2525 |   delta.lineCountDelta = 1; // One new line
2526 | 
2527 |   return delta;
2528 | }
2529 | 
2530 | EditDelta Editor::createDeltaForDeleteSelection()
2531 | {
2532 |   EditDelta delta;
2533 |   delta.operation = EditDelta::DELETE_TEXT;
2534 | 
2535 |   // Capture state
2536 |   delta.preCursorLine = cursorLine;
2537 |   delta.preCursorCol = cursorCol;
2538 |   delta.preViewportTop = viewportTop;
2539 |   delta.preViewportLeft = viewportLeft;
2540 | 
2541 |   auto [start, end] = getNormalizedSelection();
2542 |   delta.startLine = start.first;
2543 |   delta.startCol = start.second;
2544 |   delta.endLine = end.first;
2545 |   delta.endCol = end.second;
2546 | 
2547 |   // Capture the deleted text
2548 |   delta.deletedContent = getSelectedText();
2549 |   delta.insertedContent = "";
2550 | 
2551 |   // Calculate line count change
2552 |   delta.lineCountDelta = -(end.first - start.first);
2553 | 
2554 |   return delta;
2555 | }
2556 | 
2557 | // === Memory Usage Stats ===
2558 | 
2559 | size_t Editor::getUndoMemoryUsage() const
2560 | {
2561 |   size_t total = 0;
2562 | 
2563 |   if (useDeltaUndo_)
2564 |   {
2565 |     // Count delta stack
2566 |     std::stack<DeltaGroup> temp = deltaUndoStack_;
2567 |     while (!temp.empty())
2568 |     {
2569 |       total += temp.top().getMemorySize();
2570 |       temp.pop();
2571 |     }
2572 |   }
2573 |   else
2574 |   {
2575 |     // Count old state stack (approximate)
2576 |     total = undoStack.size() * sizeof(EditorState);
2577 |     std::stack<EditorState> temp = undoStack;
2578 |     while (!temp.empty())
2579 |     {
2580 |       total += temp.top().content.capacity();
2581 |       temp.pop();
2582 |     }
2583 |   }
2584 | 
2585 |   return total;
2586 | }
2587 | 
2588 | size_t Editor::getRedoMemoryUsage() const
2589 | {
2590 |   size_t total = 0;
2591 | 
2592 |   if (useDeltaUndo_)
2593 |   {
2594 |     std::stack<DeltaGroup> temp = deltaRedoStack_;
2595 |     while (!temp.empty())
2596 |     {
2597 |       total += temp.top().getMemorySize();
2598 |       temp.pop();
2599 |     }
2600 |   }
2601 |   else
2602 |   {
2603 |     total = redoStack.size() * sizeof(EditorState);
2604 |     std::stack<EditorState> temp = redoStack;
2605 |     while (!temp.empty())
2606 |     {
2607 |       total += temp.top().content.capacity();
2608 |       temp.pop();
2609 |     }
2610 |   }
2611 | 
2612 |   return total;
2613 | }
2614 | 
2615 | void Editor::applyDeltaForward(const EditDelta &delta)
2616 | {
2617 |   isUndoRedoing = true;
2618 | 
2619 | #ifdef DEBUG_DELTA_UNDO
2620 |   std::cerr << "Applying delta forward: " << delta.toString() << "\n";
2621 | #endif
2622 | 
2623 |   // Restore cursor to PRE-edit position
2624 |   cursorLine = delta.preCursorLine;
2625 |   cursorCol = delta.preCursorCol;
2626 |   viewportTop = delta.preViewportTop;
2627 |   viewportLeft = delta.preViewportLeft;
2628 | 
2629 |   validateCursorAndViewport();
2630 | 
2631 |   // Notify Tree-sitter BEFORE applying changes
2632 |   // notifyTreeSitterEdit(delta, false); // false = forward (redo)
2633 | 
2634 |   switch (delta.operation)
2635 |   {
2636 |   case EditDelta::INSERT_CHAR:
2637 |   case EditDelta::INSERT_TEXT:
2638 |   {
2639 |     // Re-insert the text
2640 |     std::string line = buffer.getLine(cursorLine);
2641 |     line.insert(cursorCol, delta.insertedContent);
2642 |     buffer.replaceLine(cursorLine, line);
2643 |     cursorCol += delta.insertedContent.length();
2644 |     break;
2645 |   }
2646 | 
2647 |   case EditDelta::DELETE_CHAR:
2648 |   case EditDelta::DELETE_TEXT:
2649 |   {
2650 |     // Re-delete the text
2651 |     if (delta.startLine == delta.endLine)
2652 |     {
2653 |       std::string line = buffer.getLine(delta.startLine);
2654 |       line.erase(delta.startCol, delta.deletedContent.length());
2655 |       buffer.replaceLine(delta.startLine, line);
2656 |     }
2657 |     else
2658 |     {
2659 |       // Multi-line deletion
2660 |       std::string firstLine = buffer.getLine(delta.startLine);
2661 |       std::string lastLine = buffer.getLine(delta.endLine);
2662 |       std::string newLine =
2663 |           firstLine.substr(0, delta.startCol) + lastLine.substr(delta.endCol);
2664 |       buffer.replaceLine(delta.startLine, newLine);
2665 | 
2666 |       for (int i = delta.endLine; i > delta.startLine; i--)
2667 |       {
2668 |         buffer.deleteLine(i);
2669 |       }
2670 |     }
2671 |     break;
2672 |   }
2673 | 
2674 |   case EditDelta::SPLIT_LINE:
2675 |   {
2676 |     // Re-split the line
2677 |     std::string line = buffer.getLine(cursorLine);
2678 |     std::string leftPart = line.substr(0, cursorCol);
2679 |     std::string rightPart = line.substr(cursorCol);
2680 | 
2681 |     buffer.replaceLine(cursorLine, leftPart);
2682 |     buffer.insertLine(cursorLine + 1, rightPart);
2683 | 
2684 |     cursorLine++;
2685 |     cursorCol = 0;
2686 |     break;
2687 |   }
2688 | 
2689 |   case EditDelta::JOIN_LINES:
2690 |   {
2691 |     // Re-join the lines
2692 |     if (delta.startLine + 1 < buffer.getLineCount())
2693 |     {
2694 |       std::string firstLine = buffer.getLine(delta.startLine);
2695 |       std::string secondLine = buffer.getLine(delta.startLine + 1);
2696 |       buffer.replaceLine(delta.startLine, firstLine + secondLine);
2697 |       buffer.deleteLine(delta.startLine + 1);
2698 |     }
2699 |     break;
2700 |   }
2701 | 
2702 |   case EditDelta::REPLACE_LINE:
2703 |   {
2704 |     if (!delta.insertedContent.empty())
2705 |     {
2706 |       buffer.replaceLine(delta.startLine, delta.insertedContent);
2707 |     }
2708 |     break;
2709 |   }
2710 |   }
2711 | 
2712 |   // Restore POST-edit cursor position
2713 |   cursorLine = delta.postCursorLine;
2714 |   cursorCol = delta.postCursorCol;
2715 |   viewportTop = delta.postViewportTop;
2716 |   viewportLeft = delta.postViewportLeft;
2717 | 
2718 |   validateCursorAndViewport();
2719 |   buffer.invalidateLineIndex();
2720 | 
2721 |   // Invalidate only affected lines (not entire cache)
2722 |   if (syntaxHighlighter)
2723 |   {
2724 |     int startLine = std::min(delta.startLine, delta.preCursorLine);
2725 |     int endLine = std::max(delta.endLine, delta.postCursorLine);
2726 |     syntaxHighlighter->invalidateLineRange(startLine,
2727 |                                            buffer.getLineCount() - 1);
2728 |   }
2729 | 
2730 |   isUndoRedoing = false;
2731 | }
2732 | 
2733 | // === Apply Delta Reverse (for Undo) ===
2734 | 
2735 | void Editor::applyDeltaReverse(const EditDelta &delta)
2736 | {
2737 |   isUndoRedoing = true;
2738 | 
2739 | #ifdef DEBUG_DELTA_UNDO
2740 |   std::cerr << "Applying delta reverse: " << delta.toString() << "\n";
2741 | #endif
2742 | 
2743 |   // Restore cursor to POST-edit position
2744 |   cursorLine = delta.postCursorLine;
2745 |   cursorCol = delta.postCursorCol;
2746 |   viewportTop = delta.postViewportTop;
2747 |   viewportLeft = delta.postViewportLeft;
2748 | 
2749 |   validateCursorAndViewport();
2750 | 
2751 |   switch (delta.operation)
2752 |   {
2753 |   case EditDelta::INSERT_CHAR:
2754 |   case EditDelta::INSERT_TEXT:
2755 |   {
2756 |     // Reverse of insert is delete
2757 |     std::string line = buffer.getLine(delta.startLine);
2758 |     if (delta.startCol + delta.insertedContent.length() <= line.length())
2759 |     {
2760 |       line.erase(delta.startCol, delta.insertedContent.length());
2761 |       buffer.replaceLine(delta.startLine, line);
2762 |     }
2763 |     break;
2764 |   }
2765 | 
2766 |   case EditDelta::DELETE_CHAR:
2767 |   case EditDelta::DELETE_TEXT:
2768 |   {
2769 |     // FIXED: Proper multi-line restoration
2770 |     if (delta.startLine == delta.endLine)
2771 |     {
2772 |       // Single line restoration (simple case)
2773 |       std::string line = buffer.getLine(delta.startLine);
2774 |       line.insert(delta.startCol, delta.deletedContent);
2775 |       buffer.replaceLine(delta.startLine, line);
2776 |     }
2777 |     else
2778 |     {
2779 |       // Multi-line restoration (the bug was here!)
2780 | 
2781 |       // Step 1: Get the current line at startLine
2782 |       std::string currentLine = buffer.getLine(delta.startLine);
2783 | 
2784 |       // Step 2: Split current line at insertion point
2785 |       std::string beforeInsert = currentLine.substr(0, delta.startCol);
2786 |       std::string afterInsert = currentLine.substr(delta.startCol);
2787 | 
2788 |       // Step 3: Split deletedContent by ACTUAL newlines, preserving them
2789 |       std::vector<std::string> linesToRestore;
2790 |       size_t pos = 0;
2791 |       size_t nextNewline;
2792 | 
2793 |       while ((nextNewline = delta.deletedContent.find('\n', pos)) !=
2794 |              std::string::npos)
2795 |       {
2796 |         // Include everything up to (but not including) the newline
2797 |         linesToRestore.push_back(
2798 |             delta.deletedContent.substr(pos, nextNewline - pos));
2799 |         pos = nextNewline + 1;
2800 |       }
2801 | 
2802 |       // Add remaining content (after last newline)
2803 |       if (pos < delta.deletedContent.length())
2804 |       {
2805 |         linesToRestore.push_back(delta.deletedContent.substr(pos));
2806 |       }
2807 | 
2808 |       // Step 4: Reconstruct lines correctly
2809 |       if (!linesToRestore.empty())
2810 |       {
2811 |         // First line: beforeInsert + first restored line
2812 |         buffer.replaceLine(delta.startLine, beforeInsert + linesToRestore[0]);
2813 | 
2814 |         // Insert middle lines (if any)
2815 |         for (size_t i = 1; i < linesToRestore.size(); ++i)
2816 |         {
2817 |           buffer.insertLine(delta.startLine + i, linesToRestore[i]);
2818 |         }
2819 | 
2820 |         // Handle the content after insertion point
2821 |         if (linesToRestore.size() == 1)
2822 |         {
2823 |           // Single line case: append afterInsert to same line
2824 |           std::string finalLine = buffer.getLine(delta.startLine);
2825 |           buffer.replaceLine(delta.startLine, finalLine + afterInsert);
2826 |         }
2827 |         else
2828 |         {
2829 |           // Multi-line case: append afterInsert to last restored line
2830 |           int lastLineIdx = delta.startLine + linesToRestore.size() - 1;
2831 |           std::string lastLine = buffer.getLine(lastLineIdx);
2832 |           buffer.replaceLine(lastLineIdx, lastLine + afterInsert);
2833 |         }
2834 |       }
2835 |       else
2836 |       {
2837 |         // Edge case: deletedContent was empty (shouldn't happen, but be safe)
2838 |         std::cerr << "WARNING: Empty deletedContent in multi-line restore\n";
2839 |       }
2840 |     }
2841 |     break;
2842 |   }
2843 | 
2844 |   case EditDelta::SPLIT_LINE:
2845 |   {
2846 |     // Reverse of split is join
2847 |     if (!delta.lineBeforeSplit.empty())
2848 |     {
2849 |       if (delta.startLine + 1 < buffer.getLineCount())
2850 |       {
2851 |         buffer.replaceLine(delta.startLine, delta.lineBeforeSplit);
2852 |         buffer.deleteLine(delta.startLine + 1);
2853 |       }
2854 |     }
2855 |     break;
2856 |   }
2857 | 
2858 |   case EditDelta::JOIN_LINES:
2859 |   {
2860 |     // Reverse of join is split
2861 |     if (!delta.firstLineBeforeJoin.empty() &&
2862 |         !delta.secondLineBeforeJoin.empty())
2863 |     {
2864 |       buffer.replaceLine(delta.startLine, delta.firstLineBeforeJoin);
2865 |       buffer.insertLine(delta.startLine + 1, delta.secondLineBeforeJoin);
2866 |     }
2867 |     break;
2868 |   }
2869 | 
2870 |   case EditDelta::REPLACE_LINE:
2871 |   {
2872 |     // Reverse of replace is restore original
2873 |     if (!delta.deletedContent.empty())
2874 |     {
2875 |       buffer.replaceLine(delta.startLine, delta.deletedContent);
2876 |     }
2877 |     break;
2878 |   }
2879 |   }
2880 | 
2881 |   // Restore PRE-edit cursor position
2882 |   cursorLine = delta.preCursorLine;
2883 |   cursorCol = delta.preCursorCol;
2884 |   viewportTop = delta.preViewportTop;
2885 |   viewportLeft = delta.preViewportLeft;
2886 | 
2887 |   validateCursorAndViewport();
2888 |   buffer.invalidateLineIndex();
2889 | 
2890 |   isUndoRedoing = false;
2891 | }
2892 | 
2893 | // Fallback
2894 | void Editor::saveState()
2895 | {
2896 |   if (isSaving || isUndoRedoing)
2897 |     return;
2898 | 
2899 |   auto now = std::chrono::steady_clock::now();
2900 |   auto elapsed =
2901 |       std::chrono::duration_cast<std::chrono::milliseconds>(now - lastEditTime)
2902 |           .count();
2903 | 
2904 |   // Only save if enough time has passed since last edit
2905 |   if (elapsed > UNDO_GROUP_TIMEOUT_MS || undoStack.empty())
2906 |   {
2907 |     EditorState state = getCurrentState();
2908 |     undoStack.push(state);
2909 |     limitUndoStack();
2910 | 
2911 |     while (!redoStack.empty())
2912 |     {
2913 |       redoStack.pop();
2914 |     }
2915 |   }
2916 | 
2917 |   lastEditTime = now;
2918 | }
2919 | 
2920 | void Editor::optimizedLineInvalidation(int startLine, int endLine)
2921 | {
2922 |   if (!syntaxHighlighter)
2923 |   {
2924 |     return;
2925 |   }
2926 | 
2927 |   // Only invalidate if change is significant
2928 |   int changeSize = endLine - startLine + 1;
2929 | 
2930 |   if (changeSize > 100)
2931 |   {
2932 |     // Large change: full reparse (but async)
2933 |     syntaxHighlighter->clearAllCache();
2934 |     syntaxHighlighter->scheduleBackgroundParse(buffer);
2935 |   }
2936 |   else if (changeSize > 10)
2937 |   {
2938 |     // Medium change: invalidate range and reparse viewport
2939 |     syntaxHighlighter->invalidateLineRange(startLine,
2940 |                                            buffer.getLineCount() - 1);
2941 |     syntaxHighlighter->parseViewportOnly(buffer, viewportTop);
2942 |   }
2943 |   else
2944 |   {
2945 |     // Small change: just invalidate the affected lines
2946 |     syntaxHighlighter->invalidateLineRange(startLine, endLine);
2947 |   }
2948 | }
2949 | 
2950 | // ============================================================================
2951 | // FIX 4: Add Tree-sitter Edit Notification for Delta Operations
2952 | // ============================================================================
2953 | // Add this method to track Tree-sitter edits during delta apply:
2954 | 
2955 | void Editor::notifyTreeSitterEdit(const EditDelta &delta, bool isReverse)
2956 | {
2957 |   if (!syntaxHighlighter)
2958 |   {
2959 |     return;
2960 |   }
2961 | 
2962 |   // Calculate byte positions
2963 |   size_t start_byte = buffer.lineColToPos(delta.startLine, delta.startCol);
2964 | 
2965 |   if (isReverse)
2966 |   {
2967 |     // Undoing: reverse the original operation
2968 |     switch (delta.operation)
2969 |     {
2970 |     case EditDelta::INSERT_CHAR:
2971 |     case EditDelta::INSERT_TEXT:
2972 |     {
2973 |       // Was an insert, now delete
2974 |       size_t len = delta.insertedContent.length();
2975 |       syntaxHighlighter->notifyEdit(start_byte, 0, len, // Inserting back
2976 |                                     delta.startLine, delta.startCol,
2977 |                                     delta.startLine, delta.startCol,
2978 |                                     delta.postCursorLine, delta.postCursorCol);
2979 |       break;
2980 |     }
2981 | 
2982 |     case EditDelta::DELETE_CHAR:
2983 |     case EditDelta::DELETE_TEXT:
2984 |     {
2985 |       // Was a delete, now insert
2986 |       size_t len = delta.deletedContent.length();
2987 |       syntaxHighlighter->notifyEdit(start_byte, len, 0, // Deleting
2988 |                                     delta.startLine, delta.startCol,
2989 |                                     delta.endLine, delta.endCol,
2990 |                                     delta.startLine, delta.startCol);
2991 |       break;
2992 |     }
2993 | 
2994 |     case EditDelta::SPLIT_LINE:
2995 |     {
2996 |       // Was a split, now join
2997 |       syntaxHighlighter->notifyEdit(start_byte, 0, 1, // Remove newline
2998 |                                     delta.startLine, delta.startCol,
2999 |                                     delta.startLine, delta.startCol,
3000 |                                     delta.startLine + 1, 0);
3001 |       break;
3002 |     }
3003 | 
3004 |     case EditDelta::JOIN_LINES:
3005 |     {
3006 |       // Was a join, now split
3007 |       syntaxHighlighter->notifyEdit(start_byte, 1, 0, // Add newline
3008 |                                     delta.startLine, delta.startCol,
3009 |                                     delta.startLine + 1, 0, delta.startLine,
3010 |                                     delta.startCol);
3011 |       break;
3012 |     }
3013 |     }
3014 |   }
3015 |   else
3016 |   {
3017 |     // Redoing: apply the original operation
3018 |     switch (delta.operation)
3019 |     {
3020 |     case EditDelta::INSERT_CHAR:
3021 |     case EditDelta::INSERT_TEXT:
3022 |     {
3023 |       size_t len = delta.insertedContent.length();
3024 |       syntaxHighlighter->notifyEdit(start_byte, len, 0, delta.startLine,
3025 |                                     delta.startCol, delta.postCursorLine,
3026 |                                     delta.postCursorCol, delta.startLine,
3027 |                                     delta.startCol);
3028 |       break;
3029 |     }
3030 | 
3031 |     case EditDelta::DELETE_CHAR:
3032 |     case EditDelta::DELETE_TEXT:
3033 |     {
3034 |       size_t len = delta.deletedContent.length();
3035 |       syntaxHighlighter->notifyEdit(
3036 |           start_byte, 0, len, delta.startLine, delta.startCol, delta.startLine,
3037 |           delta.startCol, delta.endLine, delta.endCol);
3038 |       break;
3039 |     }
3040 | 
3041 |     case EditDelta::SPLIT_LINE:
3042 |     {
3043 |       syntaxHighlighter->notifyEdit(start_byte, 1, 0, delta.startLine,
3044 |                                     delta.startCol, delta.startLine + 1, 0,
3045 |                                     delta.startLine, delta.startCol);
3046 |       break;
3047 |     }
3048 | 
3049 |     case EditDelta::JOIN_LINES:
3050 |     {
3051 |       syntaxHighlighter->notifyEdit(start_byte, 0, 1, delta.startLine,
3052 |                                     delta.startCol, delta.startLine,
3053 |                                     delta.startCol, delta.startLine + 1, 0);
3054 |       break;
3055 |     }
3056 |     }
3057 |   }
3058 | }
```
Page 5/10FirstPrevNextLast