#
tokens: 46890/50000 12/104 files (page 2/10)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 2 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

--------------------------------------------------------------------------------
/build.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Building Arc Editor
  2 | 
  3 | Arc is a terminal-based text editor with syntax highlighting and live configuration reloading.
  4 | 
  5 | ## Prerequisites
  6 | 
  7 | ### All Platforms
  8 | 
  9 | - CMake 3.16 or higher
 10 | - C++20 compatible compiler
 11 | - Git (for cloning submodules/dependencies)
 12 | 
 13 | ### Platform-Specific Requirements
 14 | 
 15 | #### Linux/Ubuntu
 16 | 
 17 | ```bash
 18 | sudo apt update
 19 | sudo apt install build-essential cmake libncurses5-dev libncursesw5-dev libyaml-cpp-dev
 20 | ```
 21 | 
 22 | #### macOS
 23 | 
 24 | ```bash
 25 | brew install cmake ncurses yaml-cpp
 26 | ```
 27 | 
 28 | #### Windows
 29 | 
 30 | - Visual Studio 2019 or later (with C++ workload)
 31 | - [vcpkg](https://github.com/microsoft/vcpkg) package manager
 32 | 
 33 | ```powershell
 34 | # Install vcpkg if not already installed
 35 | git clone https://github.com/microsoft/vcpkg.git C:\tools\vcpkg
 36 | cd C:\tools\vcpkg
 37 | .\bootstrap-vcpkg.bat
 38 | .\vcpkg integrate install
 39 | 
 40 | # Install dependencies (static libraries recommended)
 41 | .\vcpkg install pdcurses:x64-windows-static
 42 | .\vcpkg install yaml-cpp:x64-windows-static
 43 | ```
 44 | 
 45 | ## Dependencies Structure
 46 | 
 47 | The project expects dependencies in the following structure:
 48 | 
 49 | ```
 50 | arc/
 51 | ├── deps/
 52 | │   ├── tree-sitter-core/       # Tree-sitter parser library
 53 | │   ├── tree-sitter-python/     # Python language parser
 54 | │   ├── tree-sitter-c/          # C language parser
 55 | │   ├── tree-sitter-cpp/        # C++ language parser
 56 | │   ├── tree-sitter-rust/       # Rust language parser
 57 | │   ├── tree-sitter-javascript/ # JavaScript language parser
 58 | │   ├── tree-sitter-typescript/ # TypeScript parsers
 59 | │   ├── tree-sitter-markdown/   # Markdown parser
 60 | │   └── efsw/                   # File system watcher library
 61 | ├── src/
 62 | ├── CMakeLists.txt
 63 | └── ...
 64 | ```
 65 | 
 66 | ### Cloning Dependencies
 67 | 
 68 | If you have submodules set up:
 69 | 
 70 | ```bash
 71 | git clone --recursive https://github.com/moisnx/arc.git
 72 | cd arc
 73 | ```
 74 | 
 75 | Or if you already cloned without `--recursive`:
 76 | 
 77 | ```bash
 78 | git submodule update --init --recursive
 79 | ```
 80 | 
 81 | ## Building
 82 | 
 83 | ### Linux/macOS
 84 | 
 85 | ```bash
 86 | # Create build directory
 87 | mkdir build && cd build
 88 | 
 89 | # Configure
 90 | cmake .. -DCMAKE_BUILD_TYPE=Release
 91 | 
 92 | # Build
 93 | make -j$(nproc)  # Linux
 94 | make -j$(sysctl -n hw.ncpu)  # macOS
 95 | 
 96 | # Run
 97 | ./arc
 98 | ```
 99 | 
100 | ### Windows (MSVC)
101 | 
102 | ```powershell
103 | # Create build directory
104 | mkdir build
105 | cd build
106 | 
107 | # Configure with vcpkg toolchain
108 | cmake .. -DCMAKE_TOOLCHAIN_FILE=C:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static
109 | 
110 | # Build
111 | cmake --build . --config Release
112 | 
113 | # Run
114 | .\Release\arc.exe
115 | ```
116 | 
117 | ### Windows (MinGW)
118 | 
119 | ```bash
120 | mkdir build && cd build
121 | cmake .. -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release
122 | mingw32-make -j4
123 | ./arc.exe
124 | ```
125 | 
126 | ## Build Options
127 | 
128 | ### CMake Options
129 | 
130 | ```bash
131 | # Disable Tree-sitter (fallback to basic syntax highlighting)
132 | cmake .. -DTREE_SITTER_ENABLED=OFF
133 | 
134 | # Debug build with symbols
135 | cmake .. -DCMAKE_BUILD_TYPE=Debug
136 | 
137 | # Specify custom dependency location
138 | cmake .. -DDEPS_DIR=/path/to/deps
139 | ```
140 | 
141 | ### Tree-sitter Language Support
142 | 
143 | The build system automatically discovers available Tree-sitter language parsers in the `deps/` directory. Supported languages:
144 | 
145 | - Python
146 | - C/C++
147 | - Rust
148 | - JavaScript/TypeScript/TSX
149 | - Markdown
150 | 
151 | If a parser is not found, it will be skipped and the build will continue.
152 | 
153 | ## Build Targets
154 | 
155 | ```bash
156 | # Build everything
157 | cmake --build .
158 | 
159 | # Build specific target
160 | cmake --build . --target arc
161 | 
162 | # Clean build
163 | cmake --build . --target clean
164 | 
165 | # Rebuild from scratch
166 | rm -rf build && mkdir build && cd build && cmake .. && cmake --build .
167 | ```
168 | 
169 | ## Troubleshooting
170 | 
171 | ### Linux: ncurses not found
172 | 
173 | ```bash
174 | sudo apt install libncurses5-dev libncursesw5-dev
175 | ```
176 | 
177 | ### macOS: yaml-cpp not found
178 | 
179 | ```bash
180 | brew install yaml-cpp
181 | ```
182 | 
183 | ### Windows: Missing DLLs at runtime
184 | 
185 | If using dynamic linking, copy required DLLs to the executable directory:
186 | 
187 | ```powershell
188 | # PDCurses
189 | copy C:\tools\vcpkg\installed\x64-windows\bin\pdcurses.dll .\Release\
190 | 
191 | # yaml-cpp
192 | copy C:\tools\vcpkg\installed\x64-windows\bin\yaml-cpp.dll .\Release\
193 | ```
194 | 
195 | **Better solution**: Use static libraries (`:x64-windows-static` triplet) to avoid DLL dependencies.
196 | 
197 | ### Windows: Runtime library mismatch errors
198 | 
199 | Ensure all dependencies use the same runtime library. The project uses static runtime (`/MT`) by default.
200 | 
201 | ### Tree-sitter parsers not detected (Windows)
202 | 
203 | Check that parser paths don't contain special characters. The build system should automatically handle Windows paths correctly.
204 | 
205 | ### Cursor positioning issues (Windows)
206 | 
207 | Known issue with PDCurses. This is a rendering difference between PDCurses and ncurses and will be addressed in a future update.
208 | 
209 | ## Installation
210 | 
211 | ```bash
212 | # Linux/macOS
213 | sudo cmake --install . --prefix /usr/local
214 | 
215 | # Or copy manually
216 | sudo cp arc /usr/local/bin/
217 | 
218 | # Windows - copy to a directory in your PATH
219 | copy Release\arc.exe C:\Windows\
220 | ```
221 | 
222 | ## Development Build
223 | 
224 | For faster iteration during development:
225 | 
226 | ```bash
227 | # Debug build with minimal optimization
228 | cmake .. -DCMAKE_BUILD_TYPE=Debug
229 | 
230 | # With verbose output
231 | cmake --build . --verbose
232 | 
233 | # Run directly from build directory
234 | ./arc  # or .\Debug\arc.exe on Windows
235 | ```
236 | 
237 | ## Cross-Compilation
238 | 
239 | ### Linux → Windows (MinGW)
240 | 
241 | ```bash
242 | sudo apt install mingw-w64
243 | mkdir build-windows && cd build-windows
244 | cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/mingw-w64-x86_64.cmake
245 | make
246 | ```
247 | 
248 | ## Performance Considerations
249 | 
250 | - **Release builds** are significantly faster than Debug builds
251 | - **Static linking** produces larger binaries but has no runtime dependencies
252 | - **Tree-sitter** adds ~5-10MB to binary size but provides superior syntax highlighting
253 | 
254 | ## CI/CD Integration
255 | 
256 | Example GitHub Actions workflow:
257 | 
258 | ```yaml
259 | name: Build
260 | 
261 | on: [push, pull_request]
262 | 
263 | jobs:
264 |   build:
265 |     strategy:
266 |       matrix:
267 |         os: [ubuntu-latest, macos-latest, windows-latest]
268 | 
269 |     runs-on: ${{ matrix.os }}
270 | 
271 |     steps:
272 |       - uses: actions/checkout@v3
273 |         with:
274 |           submodules: recursive
275 | 
276 |       - name: Install dependencies (Ubuntu)
277 |         if: matrix.os == 'ubuntu-latest'
278 |         run: sudo apt install libncurses-dev libyaml-cpp-dev
279 | 
280 |       - name: Install dependencies (macOS)
281 |         if: matrix.os == 'macos-latest'
282 |         run: brew install ncurses yaml-cpp
283 | 
284 |       - name: Setup vcpkg (Windows)
285 |         if: matrix.os == 'windows-latest'
286 |         uses: lukka/run-vcpkg@v11
287 | 
288 |       - name: Build
289 |         run: |
290 |           mkdir build && cd build
291 |           cmake .. -DCMAKE_BUILD_TYPE=Release
292 |           cmake --build . --config Release
293 | 
294 |       - name: Test
295 |         run: cd build && ctest
296 | ```
297 | 
298 | ## Getting Help
299 | 
300 | - Check [GitHub Issues](https://github.com/moisnx/arc/issues) for known problems
301 | - Read the [main README](README.md) for usage instructions
302 | - Join discussions in [GitHub Discussions](https://github.com/moisnx/arc/discussions)
303 | 
304 | ## License
305 | 
306 | See [LICENSE](LICENSE) file for details.
307 | 
```

--------------------------------------------------------------------------------
/deps/tree-sitter-markdown/tree-sitter-markdown-inline/test/corpus/failing.txt:
--------------------------------------------------------------------------------

```
  1 | ================================================================================
  2 | Example 355 - https://github.github.com/gfm/#example-355
  3 | :skip
  4 | ================================================================================
  5 | `<http://foo.bar.`baz>`
  6 | 
  7 | --------------------------------------------------------------------------------
  8 | 
  9 | (inline
 10 |   (code_span))
 11 | 
 12 | ================================================================================
 13 | Example 363 - https://github.github.com/gfm/#example-363
 14 | :skip
 15 | ================================================================================
 16 | *  a *
 17 | 
 18 | --------------------------------------------------------------------------------
 19 | 
 20 | (inline)
 21 | 
 22 | ================================================================================
 23 | Example 389 - https://github.github.com/gfm/#example-389
 24 | :skip
 25 | ================================================================================
 26 | a**"foo"**
 27 | 
 28 | --------------------------------------------------------------------------------
 29 | 
 30 | (inline)
 31 | 
 32 | ================================================================================
 33 | Example 394 - https://github.github.com/gfm/#example-394
 34 | :skip
 35 | ================================================================================
 36 | a__"foo"__
 37 | 
 38 | --------------------------------------------------------------------------------
 39 | 
 40 | (inline)
 41 | 
 42 | ================================================================================
 43 | Example 400 - https://github.github.com/gfm/#example-400
 44 | :skip
 45 | ================================================================================
 46 | **foo bar **
 47 | 
 48 | --------------------------------------------------------------------------------
 49 | 
 50 | (inline)
 51 | 
 52 | ================================================================================
 53 | Example 401 - https://github.github.com/gfm/#example-401
 54 | :skip
 55 | ================================================================================
 56 | **(**foo)
 57 | 
 58 | --------------------------------------------------------------------------------
 59 | 
 60 | (inline)
 61 | 
 62 | ================================================================================
 63 | Example 407 - https://github.github.com/gfm/#example-407
 64 | :skip
 65 | ================================================================================
 66 | __(__foo)
 67 | 
 68 | --------------------------------------------------------------------------------
 69 | 
 70 | (inline)
 71 | 
 72 | ================================================================================
 73 | Example 406 - https://github.github.com/gfm/#example-406
 74 | :skip
 75 | ================================================================================
 76 | __foo bar __
 77 | 
 78 | --------------------------------------------------------------------------------
 79 | 
 80 | (inline)
 81 | 
 82 | ================================================================================
 83 | Example 411 - https://github.github.com/gfm/#example-411
 84 | :skip
 85 | ================================================================================
 86 | __foo__bar__baz__
 87 | 
 88 | --------------------------------------------------------------------------------
 89 | 
 90 | (inline
 91 |   (strong_emphasis))
 92 | 
 93 | ================================================================================
 94 | Example 420 - https://github.github.com/gfm/#example-420
 95 | :skip
 96 | ================================================================================
 97 | *foo**bar**baz*
 98 | 
 99 | --------------------------------------------------------------------------------
100 | 
101 | (inline
102 |   (emphasis
103 |     (strong_emphasis)))
104 | 
105 | ================================================================================
106 | Example 421 - https://github.github.com/gfm/#example-421
107 | :skip
108 | ================================================================================
109 | *foo**bar*
110 | 
111 | --------------------------------------------------------------------------------
112 | 
113 | (inline
114 |   (emphasis))
115 | 
116 | ================================================================================
117 | Example 424 - https://github.github.com/gfm/#example-424
118 | :skip
119 | ================================================================================
120 | *foo**bar***
121 | 
122 | --------------------------------------------------------------------------------
123 | 
124 | (inline
125 |   (emphasis
126 |     (strong_emphasis)))
127 | 
128 | ================================================================================
129 | Example 438 - https://github.github.com/gfm/#example-438
130 | :skip
131 | ================================================================================
132 | **foo*bar*baz**
133 | 
134 | --------------------------------------------------------------------------------
135 | 
136 | (inline
137 |   (strong_emphasis
138 |     (emphasis))))
139 | 
140 | ================================================================================
141 | Example 524 - https://github.github.com/gfm/#example-524
142 | :skip
143 | ================================================================================
144 | [link *foo **bar** `#`*](/uri)
145 | 
146 | --------------------------------------------------------------------------------
147 | 
148 | (inline
149 |   (link_text
150 |     (emphasis
151 |       (emphasis_delimiter)
152 |       (strong_emphasis
153 |         (emphasis_delimiter)
154 |         (emphasis_delimiter)
155 |         (emphasis_delimiter)
156 |         (emphasis_delimiter))
157 |       (code_span
158 |         (code_span_delimiter)
159 |         (code_span_delimiter))
160 |       (emphasis_delimiter)))
161 |   (link_destination))
162 | 
163 | ================================================================================
164 | Example 538 - https://github.github.com/gfm/#example-538
165 | :skip
166 | ================================================================================
167 | [link *foo **bar** `#`*][ref]
168 | 
169 | [ref]: /uri
170 | 
171 | --------------------------------------------------------------------------------
172 | 
173 | (inline
174 |   (full_reference_link
175 |     (link_text
176 |       (emphasis
177 |         (strong_emphasis)
178 |         (code_span)))
179 |     (link_label)))
180 | (link_reference_definition
181 |   (link_label)
182 |   (link_destination))
183 | 
184 | ================================================================================
185 | Example 560 - https://github.github.com/gfm/#example-560
186 | :skip
187 | ================================================================================
188 | [
189 |  ]
190 | 
191 | [
192 |  ]: /uri
193 | 
194 | --------------------------------------------------------------------------------
195 | 
196 | (inline)
197 | 
198 | ================================================================================
199 | Example 588 - https://github.github.com/gfm/#example-588
200 | :skip
201 | ================================================================================
202 | ![foo](<url>)
203 | 
204 | --------------------------------------------------------------------------------
205 | 
206 | (inline
207 |   (image
208 |     (image_description)
209 |     (link_destination)))
210 | 
211 | ================================================================================
212 | Example 635 - https://github.github.com/gfm/#example-635
213 | :skip
214 | ================================================================================
215 | <a foo="bar" bam = 'baz <em>"</em>'
216 | _boolean zoop:33=zoop:33 />
217 | 
218 | --------------------------------------------------------------------------------
219 | 
220 | (inline
221 |   (html_tag))
222 | 
```

--------------------------------------------------------------------------------
/deps/tree-sitter-markdown/tree-sitter-markdown-inline/src/tree_sitter/parser.h:
--------------------------------------------------------------------------------

```
  1 | #ifndef TREE_SITTER_PARSER_H_
  2 | #define TREE_SITTER_PARSER_H_
  3 | 
  4 | #ifdef __cplusplus
  5 | extern "C" {
  6 | #endif
  7 | 
  8 | #include <stdbool.h>
  9 | #include <stdint.h>
 10 | #include <stdlib.h>
 11 | 
 12 | #define ts_builtin_sym_error ((TSSymbol)-1)
 13 | #define ts_builtin_sym_end 0
 14 | #define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
 15 | 
 16 | #ifndef TREE_SITTER_API_H_
 17 | typedef uint16_t TSStateId;
 18 | typedef uint16_t TSSymbol;
 19 | typedef uint16_t TSFieldId;
 20 | typedef struct TSLanguage TSLanguage;
 21 | typedef struct TSLanguageMetadata {
 22 |   uint8_t major_version;
 23 |   uint8_t minor_version;
 24 |   uint8_t patch_version;
 25 | } TSLanguageMetadata;
 26 | #endif
 27 | 
 28 | typedef struct {
 29 |   TSFieldId field_id;
 30 |   uint8_t child_index;
 31 |   bool inherited;
 32 | } TSFieldMapEntry;
 33 | 
 34 | // Used to index the field and supertype maps.
 35 | typedef struct {
 36 |   uint16_t index;
 37 |   uint16_t length;
 38 | } TSMapSlice;
 39 | 
 40 | typedef struct {
 41 |   bool visible;
 42 |   bool named;
 43 |   bool supertype;
 44 | } TSSymbolMetadata;
 45 | 
 46 | typedef struct TSLexer TSLexer;
 47 | 
 48 | struct TSLexer {
 49 |   int32_t lookahead;
 50 |   TSSymbol result_symbol;
 51 |   void (*advance)(TSLexer *, bool);
 52 |   void (*mark_end)(TSLexer *);
 53 |   uint32_t (*get_column)(TSLexer *);
 54 |   bool (*is_at_included_range_start)(const TSLexer *);
 55 |   bool (*eof)(const TSLexer *);
 56 |   void (*log)(const TSLexer *, const char *, ...);
 57 | };
 58 | 
 59 | typedef enum {
 60 |   TSParseActionTypeShift,
 61 |   TSParseActionTypeReduce,
 62 |   TSParseActionTypeAccept,
 63 |   TSParseActionTypeRecover,
 64 | } TSParseActionType;
 65 | 
 66 | typedef union {
 67 |   struct {
 68 |     uint8_t type;
 69 |     TSStateId state;
 70 |     bool extra;
 71 |     bool repetition;
 72 |   } shift;
 73 |   struct {
 74 |     uint8_t type;
 75 |     uint8_t child_count;
 76 |     TSSymbol symbol;
 77 |     int16_t dynamic_precedence;
 78 |     uint16_t production_id;
 79 |   } reduce;
 80 |   uint8_t type;
 81 | } TSParseAction;
 82 | 
 83 | typedef struct {
 84 |   uint16_t lex_state;
 85 |   uint16_t external_lex_state;
 86 | } TSLexMode;
 87 | 
 88 | typedef struct {
 89 |   uint16_t lex_state;
 90 |   uint16_t external_lex_state;
 91 |   uint16_t reserved_word_set_id;
 92 | } TSLexerMode;
 93 | 
 94 | typedef union {
 95 |   TSParseAction action;
 96 |   struct {
 97 |     uint8_t count;
 98 |     bool reusable;
 99 |   } entry;
100 | } TSParseActionEntry;
101 | 
102 | typedef struct {
103 |   int32_t start;
104 |   int32_t end;
105 | } TSCharacterRange;
106 | 
107 | struct TSLanguage {
108 |   uint32_t abi_version;
109 |   uint32_t symbol_count;
110 |   uint32_t alias_count;
111 |   uint32_t token_count;
112 |   uint32_t external_token_count;
113 |   uint32_t state_count;
114 |   uint32_t large_state_count;
115 |   uint32_t production_id_count;
116 |   uint32_t field_count;
117 |   uint16_t max_alias_sequence_length;
118 |   const uint16_t *parse_table;
119 |   const uint16_t *small_parse_table;
120 |   const uint32_t *small_parse_table_map;
121 |   const TSParseActionEntry *parse_actions;
122 |   const char * const *symbol_names;
123 |   const char * const *field_names;
124 |   const TSMapSlice *field_map_slices;
125 |   const TSFieldMapEntry *field_map_entries;
126 |   const TSSymbolMetadata *symbol_metadata;
127 |   const TSSymbol *public_symbol_map;
128 |   const uint16_t *alias_map;
129 |   const TSSymbol *alias_sequences;
130 |   const TSLexerMode *lex_modes;
131 |   bool (*lex_fn)(TSLexer *, TSStateId);
132 |   bool (*keyword_lex_fn)(TSLexer *, TSStateId);
133 |   TSSymbol keyword_capture_token;
134 |   struct {
135 |     const bool *states;
136 |     const TSSymbol *symbol_map;
137 |     void *(*create)(void);
138 |     void (*destroy)(void *);
139 |     bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
140 |     unsigned (*serialize)(void *, char *);
141 |     void (*deserialize)(void *, const char *, unsigned);
142 |   } external_scanner;
143 |   const TSStateId *primary_state_ids;
144 |   const char *name;
145 |   const TSSymbol *reserved_words;
146 |   uint16_t max_reserved_word_set_size;
147 |   uint32_t supertype_count;
148 |   const TSSymbol *supertype_symbols;
149 |   const TSMapSlice *supertype_map_slices;
150 |   const TSSymbol *supertype_map_entries;
151 |   TSLanguageMetadata metadata;
152 | };
153 | 
154 | static inline bool set_contains(const TSCharacterRange *ranges, uint32_t len, int32_t lookahead) {
155 |   uint32_t index = 0;
156 |   uint32_t size = len - index;
157 |   while (size > 1) {
158 |     uint32_t half_size = size / 2;
159 |     uint32_t mid_index = index + half_size;
160 |     const TSCharacterRange *range = &ranges[mid_index];
161 |     if (lookahead >= range->start && lookahead <= range->end) {
162 |       return true;
163 |     } else if (lookahead > range->end) {
164 |       index = mid_index;
165 |     }
166 |     size -= half_size;
167 |   }
168 |   const TSCharacterRange *range = &ranges[index];
169 |   return (lookahead >= range->start && lookahead <= range->end);
170 | }
171 | 
172 | /*
173 |  *  Lexer Macros
174 |  */
175 | 
176 | #ifdef _MSC_VER
177 | #define UNUSED __pragma(warning(suppress : 4101))
178 | #else
179 | #define UNUSED __attribute__((unused))
180 | #endif
181 | 
182 | #define START_LEXER()           \
183 |   bool result = false;          \
184 |   bool skip = false;            \
185 |   UNUSED                        \
186 |   bool eof = false;             \
187 |   int32_t lookahead;            \
188 |   goto start;                   \
189 |   next_state:                   \
190 |   lexer->advance(lexer, skip);  \
191 |   start:                        \
192 |   skip = false;                 \
193 |   lookahead = lexer->lookahead;
194 | 
195 | #define ADVANCE(state_value) \
196 |   {                          \
197 |     state = state_value;     \
198 |     goto next_state;         \
199 |   }
200 | 
201 | #define ADVANCE_MAP(...)                                              \
202 |   {                                                                   \
203 |     static const uint16_t map[] = { __VA_ARGS__ };                    \
204 |     for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) {  \
205 |       if (map[i] == lookahead) {                                      \
206 |         state = map[i + 1];                                           \
207 |         goto next_state;                                              \
208 |       }                                                               \
209 |     }                                                                 \
210 |   }
211 | 
212 | #define SKIP(state_value) \
213 |   {                       \
214 |     skip = true;          \
215 |     state = state_value;  \
216 |     goto next_state;      \
217 |   }
218 | 
219 | #define ACCEPT_TOKEN(symbol_value)     \
220 |   result = true;                       \
221 |   lexer->result_symbol = symbol_value; \
222 |   lexer->mark_end(lexer);
223 | 
224 | #define END_STATE() return result;
225 | 
226 | /*
227 |  *  Parse Table Macros
228 |  */
229 | 
230 | #define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT)
231 | 
232 | #define STATE(id) id
233 | 
234 | #define ACTIONS(id) id
235 | 
236 | #define SHIFT(state_value)            \
237 |   {{                                  \
238 |     .shift = {                        \
239 |       .type = TSParseActionTypeShift, \
240 |       .state = (state_value)          \
241 |     }                                 \
242 |   }}
243 | 
244 | #define SHIFT_REPEAT(state_value)     \
245 |   {{                                  \
246 |     .shift = {                        \
247 |       .type = TSParseActionTypeShift, \
248 |       .state = (state_value),         \
249 |       .repetition = true              \
250 |     }                                 \
251 |   }}
252 | 
253 | #define SHIFT_EXTRA()                 \
254 |   {{                                  \
255 |     .shift = {                        \
256 |       .type = TSParseActionTypeShift, \
257 |       .extra = true                   \
258 |     }                                 \
259 |   }}
260 | 
261 | #define REDUCE(symbol_name, children, precedence, prod_id) \
262 |   {{                                                       \
263 |     .reduce = {                                            \
264 |       .type = TSParseActionTypeReduce,                     \
265 |       .symbol = symbol_name,                               \
266 |       .child_count = children,                             \
267 |       .dynamic_precedence = precedence,                    \
268 |       .production_id = prod_id                             \
269 |     },                                                     \
270 |   }}
271 | 
272 | #define RECOVER()                    \
273 |   {{                                 \
274 |     .type = TSParseActionTypeRecover \
275 |   }}
276 | 
277 | #define ACCEPT_INPUT()              \
278 |   {{                                \
279 |     .type = TSParseActionTypeAccept \
280 |   }}
281 | 
282 | #ifdef __cplusplus
283 | }
284 | #endif
285 | 
286 | #endif  // TREE_SITTER_PARSER_H_
287 | 
```

--------------------------------------------------------------------------------
/deps/tree-sitter-markdown/tree-sitter-markdown/src/tree_sitter/parser.h:
--------------------------------------------------------------------------------

```
  1 | #ifndef TREE_SITTER_PARSER_H_
  2 | #define TREE_SITTER_PARSER_H_
  3 | 
  4 | #ifdef __cplusplus
  5 | extern "C" {
  6 | #endif
  7 | 
  8 | #include <stdbool.h>
  9 | #include <stdint.h>
 10 | #include <stdlib.h>
 11 | 
 12 | #define ts_builtin_sym_error ((TSSymbol)-1)
 13 | #define ts_builtin_sym_end 0
 14 | #define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
 15 | 
 16 | #ifndef TREE_SITTER_API_H_
 17 | typedef uint16_t TSStateId;
 18 | typedef uint16_t TSSymbol;
 19 | typedef uint16_t TSFieldId;
 20 | typedef struct TSLanguage TSLanguage;
 21 | typedef struct TSLanguageMetadata {
 22 |   uint8_t major_version;
 23 |   uint8_t minor_version;
 24 |   uint8_t patch_version;
 25 | } TSLanguageMetadata;
 26 | #endif
 27 | 
 28 | typedef struct {
 29 |   TSFieldId field_id;
 30 |   uint8_t child_index;
 31 |   bool inherited;
 32 | } TSFieldMapEntry;
 33 | 
 34 | // Used to index the field and supertype maps.
 35 | typedef struct {
 36 |   uint16_t index;
 37 |   uint16_t length;
 38 | } TSMapSlice;
 39 | 
 40 | typedef struct {
 41 |   bool visible;
 42 |   bool named;
 43 |   bool supertype;
 44 | } TSSymbolMetadata;
 45 | 
 46 | typedef struct TSLexer TSLexer;
 47 | 
 48 | struct TSLexer {
 49 |   int32_t lookahead;
 50 |   TSSymbol result_symbol;
 51 |   void (*advance)(TSLexer *, bool);
 52 |   void (*mark_end)(TSLexer *);
 53 |   uint32_t (*get_column)(TSLexer *);
 54 |   bool (*is_at_included_range_start)(const TSLexer *);
 55 |   bool (*eof)(const TSLexer *);
 56 |   void (*log)(const TSLexer *, const char *, ...);
 57 | };
 58 | 
 59 | typedef enum {
 60 |   TSParseActionTypeShift,
 61 |   TSParseActionTypeReduce,
 62 |   TSParseActionTypeAccept,
 63 |   TSParseActionTypeRecover,
 64 | } TSParseActionType;
 65 | 
 66 | typedef union {
 67 |   struct {
 68 |     uint8_t type;
 69 |     TSStateId state;
 70 |     bool extra;
 71 |     bool repetition;
 72 |   } shift;
 73 |   struct {
 74 |     uint8_t type;
 75 |     uint8_t child_count;
 76 |     TSSymbol symbol;
 77 |     int16_t dynamic_precedence;
 78 |     uint16_t production_id;
 79 |   } reduce;
 80 |   uint8_t type;
 81 | } TSParseAction;
 82 | 
 83 | typedef struct {
 84 |   uint16_t lex_state;
 85 |   uint16_t external_lex_state;
 86 | } TSLexMode;
 87 | 
 88 | typedef struct {
 89 |   uint16_t lex_state;
 90 |   uint16_t external_lex_state;
 91 |   uint16_t reserved_word_set_id;
 92 | } TSLexerMode;
 93 | 
 94 | typedef union {
 95 |   TSParseAction action;
 96 |   struct {
 97 |     uint8_t count;
 98 |     bool reusable;
 99 |   } entry;
100 | } TSParseActionEntry;
101 | 
102 | typedef struct {
103 |   int32_t start;
104 |   int32_t end;
105 | } TSCharacterRange;
106 | 
107 | struct TSLanguage {
108 |   uint32_t abi_version;
109 |   uint32_t symbol_count;
110 |   uint32_t alias_count;
111 |   uint32_t token_count;
112 |   uint32_t external_token_count;
113 |   uint32_t state_count;
114 |   uint32_t large_state_count;
115 |   uint32_t production_id_count;
116 |   uint32_t field_count;
117 |   uint16_t max_alias_sequence_length;
118 |   const uint16_t *parse_table;
119 |   const uint16_t *small_parse_table;
120 |   const uint32_t *small_parse_table_map;
121 |   const TSParseActionEntry *parse_actions;
122 |   const char * const *symbol_names;
123 |   const char * const *field_names;
124 |   const TSMapSlice *field_map_slices;
125 |   const TSFieldMapEntry *field_map_entries;
126 |   const TSSymbolMetadata *symbol_metadata;
127 |   const TSSymbol *public_symbol_map;
128 |   const uint16_t *alias_map;
129 |   const TSSymbol *alias_sequences;
130 |   const TSLexerMode *lex_modes;
131 |   bool (*lex_fn)(TSLexer *, TSStateId);
132 |   bool (*keyword_lex_fn)(TSLexer *, TSStateId);
133 |   TSSymbol keyword_capture_token;
134 |   struct {
135 |     const bool *states;
136 |     const TSSymbol *symbol_map;
137 |     void *(*create)(void);
138 |     void (*destroy)(void *);
139 |     bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
140 |     unsigned (*serialize)(void *, char *);
141 |     void (*deserialize)(void *, const char *, unsigned);
142 |   } external_scanner;
143 |   const TSStateId *primary_state_ids;
144 |   const char *name;
145 |   const TSSymbol *reserved_words;
146 |   uint16_t max_reserved_word_set_size;
147 |   uint32_t supertype_count;
148 |   const TSSymbol *supertype_symbols;
149 |   const TSMapSlice *supertype_map_slices;
150 |   const TSSymbol *supertype_map_entries;
151 |   TSLanguageMetadata metadata;
152 | };
153 | 
154 | static inline bool set_contains(const TSCharacterRange *ranges, uint32_t len, int32_t lookahead) {
155 |   uint32_t index = 0;
156 |   uint32_t size = len - index;
157 |   while (size > 1) {
158 |     uint32_t half_size = size / 2;
159 |     uint32_t mid_index = index + half_size;
160 |     const TSCharacterRange *range = &ranges[mid_index];
161 |     if (lookahead >= range->start && lookahead <= range->end) {
162 |       return true;
163 |     } else if (lookahead > range->end) {
164 |       index = mid_index;
165 |     }
166 |     size -= half_size;
167 |   }
168 |   const TSCharacterRange *range = &ranges[index];
169 |   return (lookahead >= range->start && lookahead <= range->end);
170 | }
171 | 
172 | /*
173 |  *  Lexer Macros
174 |  */
175 | 
176 | #ifdef _MSC_VER
177 | #define UNUSED __pragma(warning(suppress : 4101))
178 | #else
179 | #define UNUSED __attribute__((unused))
180 | #endif
181 | 
182 | #define START_LEXER()           \
183 |   bool result = false;          \
184 |   bool skip = false;            \
185 |   UNUSED                        \
186 |   bool eof = false;             \
187 |   int32_t lookahead;            \
188 |   goto start;                   \
189 |   next_state:                   \
190 |   lexer->advance(lexer, skip);  \
191 |   start:                        \
192 |   skip = false;                 \
193 |   lookahead = lexer->lookahead;
194 | 
195 | #define ADVANCE(state_value) \
196 |   {                          \
197 |     state = state_value;     \
198 |     goto next_state;         \
199 |   }
200 | 
201 | #define ADVANCE_MAP(...)                                              \
202 |   {                                                                   \
203 |     static const uint16_t map[] = { __VA_ARGS__ };                    \
204 |     for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) {  \
205 |       if (map[i] == lookahead) {                                      \
206 |         state = map[i + 1];                                           \
207 |         goto next_state;                                              \
208 |       }                                                               \
209 |     }                                                                 \
210 |   }
211 | 
212 | #define SKIP(state_value) \
213 |   {                       \
214 |     skip = true;          \
215 |     state = state_value;  \
216 |     goto next_state;      \
217 |   }
218 | 
219 | #define ACCEPT_TOKEN(symbol_value)     \
220 |   result = true;                       \
221 |   lexer->result_symbol = symbol_value; \
222 |   lexer->mark_end(lexer);
223 | 
224 | #define END_STATE() return result;
225 | 
226 | /*
227 |  *  Parse Table Macros
228 |  */
229 | 
230 | #define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT)
231 | 
232 | #define STATE(id) id
233 | 
234 | #define ACTIONS(id) id
235 | 
236 | #define SHIFT(state_value)            \
237 |   {{                                  \
238 |     .shift = {                        \
239 |       .type = TSParseActionTypeShift, \
240 |       .state = (state_value)          \
241 |     }                                 \
242 |   }}
243 | 
244 | #define SHIFT_REPEAT(state_value)     \
245 |   {{                                  \
246 |     .shift = {                        \
247 |       .type = TSParseActionTypeShift, \
248 |       .state = (state_value),         \
249 |       .repetition = true              \
250 |     }                                 \
251 |   }}
252 | 
253 | #define SHIFT_EXTRA()                 \
254 |   {{                                  \
255 |     .shift = {                        \
256 |       .type = TSParseActionTypeShift, \
257 |       .extra = true                   \
258 |     }                                 \
259 |   }}
260 | 
261 | #define REDUCE(symbol_name, children, precedence, prod_id) \
262 |   {{                                                       \
263 |     .reduce = {                                            \
264 |       .type = TSParseActionTypeReduce,                     \
265 |       .symbol = symbol_name,                               \
266 |       .child_count = children,                             \
267 |       .dynamic_precedence = precedence,                    \
268 |       .production_id = prod_id                             \
269 |     },                                                     \
270 |   }}
271 | 
272 | #define RECOVER()                    \
273 |   {{                                 \
274 |     .type = TSParseActionTypeRecover \
275 |   }}
276 | 
277 | #define ACCEPT_INPUT()              \
278 |   {{                                \
279 |     .type = TSParseActionTypeAccept \
280 |   }}
281 | 
282 | #ifdef __cplusplus
283 | }
284 | #endif
285 | 
286 | #endif  // TREE_SITTER_PARSER_H_
287 | 
```

--------------------------------------------------------------------------------
/src/features/syntax_config_loader.cpp:
--------------------------------------------------------------------------------

```cpp
  1 | // src/features/syntax_config_loader.cpp
  2 | #include "syntax_config_loader.h"
  3 | #include <algorithm>
  4 | #include <filesystem>
  5 | #include <fstream>
  6 | #include <iostream>
  7 | #include <sstream>
  8 | #include <yaml-cpp/yaml.h>
  9 | 
 10 | SyntaxConfigLoader::SyntaxConfigLoader() {}
 11 | 
 12 | bool SyntaxConfigLoader::loadFromRegistry(const std::string &registry_path)
 13 | {
 14 |   // std::cerr << "Loading language registry from: " << registry_path <<
 15 |   // std::endl;
 16 | 
 17 |   if (!std::filesystem::exists(registry_path))
 18 |   {
 19 |     std::cerr << "ERROR: Registry file does not exist: " << registry_path
 20 |               << std::endl;
 21 |     return false;
 22 |   }
 23 | 
 24 |   return parseRegistryFile(registry_path);
 25 | }
 26 | 
 27 | bool SyntaxConfigLoader::parseRegistryFile(const std::string &filepath)
 28 | {
 29 |   try
 30 |   {
 31 |     YAML::Node root = YAML::LoadFile(filepath);
 32 | 
 33 |     if (!root["languages"])
 34 |     {
 35 |       std::cerr << "ERROR: No 'languages' section in registry file"
 36 |                 << std::endl;
 37 |       return false;
 38 |     }
 39 | 
 40 |     YAML::Node languages = root["languages"];
 41 |     int loaded_count = 0;
 42 | 
 43 |     for (auto it = languages.begin(); it != languages.end(); ++it)
 44 |     {
 45 |       std::string lang_key = it->first.as<std::string>();
 46 |       YAML::Node lang_node = it->second;
 47 | 
 48 |       auto config = std::make_unique<LanguageConfig>();
 49 | 
 50 |       // Parse language configuration
 51 |       if (lang_node["name"])
 52 |       {
 53 |         config->name = lang_node["name"].as<std::string>();
 54 |       }
 55 |       else
 56 |       {
 57 |         config->name = lang_key; // Use key as fallback
 58 |       }
 59 | 
 60 |       // Ensure name is never empty
 61 |       if (config->name.empty())
 62 |       {
 63 |         std::cerr << "WARNING: Empty name for language '" << lang_key
 64 |                   << "', using key as name" << std::endl;
 65 |         config->name = lang_key;
 66 |       }
 67 | 
 68 |       if (lang_node["builtin"])
 69 |         config->builtin = lang_node["builtin"].as<bool>();
 70 |       else
 71 |         config->builtin = true; // Default to builtin
 72 | 
 73 |       if (lang_node["parser_name"])
 74 |         config->parser_name = lang_node["parser_name"].as<std::string>();
 75 | 
 76 |       if (lang_node["query_path"])
 77 |         config->query_file_path = lang_node["query_path"].as<std::string>();
 78 | 
 79 |       if (lang_node["queries"] && lang_node["queries"].IsSequence())
 80 |       {
 81 |         for (const auto &query_node : lang_node["queries"])
 82 |         {
 83 |           std::string query_path = query_node.as<std::string>();
 84 |           if (!query_path.empty())
 85 |           {
 86 |             config->queries.push_back(query_path);
 87 |           }
 88 |         }
 89 |       }
 90 | 
 91 |       // Parse extensions (CRITICAL: Must have at least one extension)
 92 |       if (lang_node["extensions"] && lang_node["extensions"].IsSequence())
 93 |       {
 94 |         for (const auto &ext_node : lang_node["extensions"])
 95 |         {
 96 |           std::string ext = ext_node.as<std::string>();
 97 |           if (!ext.empty())
 98 |           {
 99 |             config->extensions.push_back(ext);
100 |             // Map extension to language name
101 |             extension_to_language_[ext] = config->name;
102 |           }
103 |         }
104 | 
105 |         if (config->extensions.empty())
106 |         {
107 |           std::cerr << "WARNING: Language '" << lang_key
108 |                     << "' has no valid extensions, skipping" << std::endl;
109 |           continue; // Skip this language
110 |         }
111 |       }
112 |       else
113 |       {
114 |         std::cerr << "WARNING: Language '" << lang_key
115 |                   << "' has no extensions defined, skipping" << std::endl;
116 |         continue; // Skip this language
117 |       }
118 | 
119 |       // Store configuration
120 |       std::string stored_name = config->name; // Save for logging
121 |       language_configs_[config->name] = std::move(config);
122 |       loaded_count++;
123 | 
124 |       // std::cerr << "✓ Loaded: " << lang_key << " -> \"" << stored_name
125 |       //           << "\" with "
126 |       //           << language_configs_[stored_name]->extensions.size()
127 |       //           << " extensions" << std::endl;
128 |     }
129 | 
130 |     // std::cerr << "Successfully loaded " << loaded_count
131 |     //           << " languages from registry" << std::endl;
132 |     return loaded_count > 0;
133 |   }
134 |   catch (const YAML::BadFile &e)
135 |   {
136 |     std::cerr << "ERROR: Cannot open registry file: " << filepath << " - "
137 |               << e.what() << std::endl;
138 |     return false;
139 |   }
140 |   catch (const YAML::ParserException &e)
141 |   {
142 |     std::cerr << "ERROR: YAML parsing error in registry: " << filepath << ": "
143 |               << e.what() << std::endl;
144 |     return false;
145 |   }
146 |   catch (const std::exception &e)
147 |   {
148 |     std::cerr << "ERROR: Exception while parsing registry " << filepath << ": "
149 |               << e.what() << std::endl;
150 |     return false;
151 |   }
152 | }
153 | 
154 | bool SyntaxConfigLoader::loadLanguageConfig(const std::string &language_name,
155 |                                             const std::string &config_path)
156 | {
157 |   auto config = std::make_unique<LanguageConfig>();
158 | 
159 |   if (!parseYamlFile(config_path, *config))
160 |   {
161 |     std::cerr << "ERROR: Failed to parse YAML file: " << config_path
162 |               << std::endl;
163 |     return false;
164 |   }
165 | 
166 |   std::string actualLanguageName =
167 |       config->name.empty() ? language_name : config->name;
168 | 
169 |   for (const auto &ext : config->extensions)
170 |   {
171 |     extension_to_language_[ext] = actualLanguageName;
172 |   }
173 | 
174 |   language_configs_[actualLanguageName] = std::move(config);
175 |   return true;
176 | }
177 | 
178 | const LanguageConfig *
179 | SyntaxConfigLoader::getLanguageConfig(const std::string &language_name) const
180 | {
181 |   auto it = language_configs_.find(language_name);
182 |   return (it != language_configs_.end()) ? it->second.get() : nullptr;
183 | }
184 | 
185 | std::string
186 | SyntaxConfigLoader::getLanguageFromExtension(const std::string &extension) const
187 | {
188 |   auto it = extension_to_language_.find(extension);
189 |   std::string result =
190 |       (it != extension_to_language_.end()) ? it->second : "text";
191 |   return result;
192 | }
193 | 
194 | void SyntaxConfigLoader::debugCurrentState() const
195 | {
196 |   std::cerr << "\n=== LANGUAGE REGISTRY DEBUG STATE ===" << std::endl;
197 | 
198 |   for (const auto &pair : language_configs_)
199 |   {
200 |     std::cerr << "  Language: '" << pair.first << "'" << std::endl;
201 |     std::cerr << "    Config name: '" << pair.second->name << "'" << std::endl;
202 |     std::cerr << "    Builtin: " << (pair.second->builtin ? "yes" : "no")
203 |               << std::endl;
204 |     std::cerr << "    Extensions: ";
205 |     for (const auto &ext : pair.second->extensions)
206 |     {
207 |       std::cerr << "'" << ext << "' ";
208 |     }
209 |     std::cerr << std::endl;
210 |     std::cerr << "    Parser name: '" << pair.second->parser_name << "'"
211 |               << std::endl;
212 |     std::cerr << "    Query file: '" << pair.second->query_file_path << "'"
213 |               << std::endl;
214 |   }
215 | 
216 |   std::cerr << "\nExtension mappings: " << extension_to_language_.size()
217 |             << std::endl;
218 |   for (const auto &pair : extension_to_language_)
219 |   {
220 |     std::cerr << "  '" << pair.first << "' -> '" << pair.second << "'"
221 |               << std::endl;
222 |   }
223 | 
224 |   std::cerr << "=== END DEBUG STATE ===\n" << std::endl;
225 | }
226 | 
227 | bool SyntaxConfigLoader::loadAllLanguageConfigs(
228 |     const std::string &config_directory)
229 | {
230 |   // DEPRECATED: Check if registry.yaml exists first
231 |   std::string registry_path = "treesitter/languages.yaml";
232 | 
233 |   // std::cerr << "Registry path: " << registry_path << std::endl;
234 | 
235 |   if (std::filesystem::exists(registry_path))
236 |   {
237 |     // std::cerr << "Found registry.yaml, using unified registry loading"
238 |     //           << std::endl;
239 |     return loadFromRegistry(registry_path);
240 |   }
241 | 
242 |   // Legacy fallback: Load individual YAML files
243 |   std::cerr << "No registry.yaml found, falling back to individual file loading"
244 |             << std::endl;
245 | 
246 |   try
247 |   {
248 |     if (!std::filesystem::exists(config_directory))
249 |     {
250 |       std::cerr << "ERROR: Config directory does not exist!" << std::endl;
251 |       return false;
252 |     }
253 | 
254 |     if (!std::filesystem::is_directory(config_directory))
255 |     {
256 |       std::cerr << "ERROR: Path is not a directory!" << std::endl;
257 |       return false;
258 |     }
259 | 
260 |     int success_count = 0;
261 | 
262 |     for (const auto &entry :
263 |          std::filesystem::directory_iterator(config_directory))
264 |     {
265 |       if (entry.is_regular_file() && entry.path().extension() == ".yaml")
266 |       {
267 |         std::string language_name = entry.path().stem().string();
268 |         std::string config_path = entry.path().string();
269 | 
270 |         if (loadLanguageConfig(language_name, config_path))
271 |         {
272 |           success_count++;
273 |         }
274 |         else
275 |         {
276 |           std::cerr << "ERROR: Failed to load config for language: "
277 |                     << language_name << std::endl;
278 |           return false;
279 |         }
280 |       }
281 |     }
282 | 
283 |     return success_count > 0;
284 |   }
285 |   catch (const std::filesystem::filesystem_error &ex)
286 |   {
287 |     std::cerr << "Filesystem error: " << ex.what() << std::endl;
288 |     return false;
289 |   }
290 |   catch (const std::exception &ex)
291 |   {
292 |     std::cerr << "General error: " << ex.what() << std::endl;
293 |     return false;
294 |   }
295 | }
296 | 
297 | bool SyntaxConfigLoader::parseYamlFile(const std::string &filepath,
298 |                                        LanguageConfig &config)
299 | {
300 |   try
301 |   {
302 |     YAML::Node root = YAML::LoadFile(filepath);
303 | 
304 |     if (root["language_info"])
305 |     {
306 |       YAML::Node lang_info = root["language_info"];
307 | 
308 |       if (lang_info["name"])
309 |         config.name = lang_info["name"].as<std::string>();
310 | 
311 |       if (lang_info["extensions"])
312 |       {
313 |         for (const auto &ext : lang_info["extensions"])
314 |         {
315 |           config.extensions.push_back(ext.as<std::string>());
316 |         }
317 |       }
318 | 
319 |       if (lang_info["parser_name"])
320 |         config.parser_name = lang_info["parser_name"].as<std::string>();
321 | 
322 |       if (lang_info["query_file_path"])
323 |         config.query_file_path = lang_info["query_file_path"].as<std::string>();
324 | 
325 |       // Set builtin to true for legacy configs
326 |       config.builtin = true;
327 |     }
328 |     else
329 |     {
330 |       std::cerr << "ERROR: No language_info section found!" << std::endl;
331 |       return false;
332 |     }
333 | 
334 |     return true;
335 |   }
336 |   catch (const YAML::BadFile &e)
337 |   {
338 |     std::cerr << "ERROR: Cannot open file: " << filepath << " - " << e.what()
339 |               << std::endl;
340 |     return false;
341 |   }
342 |   catch (const YAML::ParserException &e)
343 |   {
344 |     std::cerr << "ERROR: YAML parsing error in file " << filepath << ": "
345 |               << e.what() << std::endl;
346 |     return false;
347 |   }
348 |   catch (const std::exception &e)
349 |   {
350 |     std::cerr << "ERROR: General exception while parsing " << filepath << ": "
351 |               << e.what() << std::endl;
352 |     return false;
353 |   }
354 | }
```

--------------------------------------------------------------------------------
/deps/tree-sitter-markdown/tree-sitter-markdown-inline/src/tree_sitter/array.h:
--------------------------------------------------------------------------------

```
  1 | #ifndef TREE_SITTER_ARRAY_H_
  2 | #define TREE_SITTER_ARRAY_H_
  3 | 
  4 | #ifdef __cplusplus
  5 | extern "C" {
  6 | #endif
  7 | 
  8 | #include "./alloc.h"
  9 | 
 10 | #include <assert.h>
 11 | #include <stdbool.h>
 12 | #include <stdint.h>
 13 | #include <stdlib.h>
 14 | #include <string.h>
 15 | 
 16 | #ifdef _MSC_VER
 17 | #pragma warning(push)
 18 | #pragma warning(disable : 4101)
 19 | #elif defined(__GNUC__) || defined(__clang__)
 20 | #pragma GCC diagnostic push
 21 | #pragma GCC diagnostic ignored "-Wunused-variable"
 22 | #endif
 23 | 
 24 | #define Array(T)       \
 25 |   struct {             \
 26 |     T *contents;       \
 27 |     uint32_t size;     \
 28 |     uint32_t capacity; \
 29 |   }
 30 | 
 31 | /// Initialize an array.
 32 | #define array_init(self) \
 33 |   ((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
 34 | 
 35 | /// Create an empty array.
 36 | #define array_new() \
 37 |   { NULL, 0, 0 }
 38 | 
 39 | /// Get a pointer to the element at a given `index` in the array.
 40 | #define array_get(self, _index) \
 41 |   (assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index])
 42 | 
 43 | /// Get a pointer to the first element in the array.
 44 | #define array_front(self) array_get(self, 0)
 45 | 
 46 | /// Get a pointer to the last element in the array.
 47 | #define array_back(self) array_get(self, (self)->size - 1)
 48 | 
 49 | /// Clear the array, setting its size to zero. Note that this does not free any
 50 | /// memory allocated for the array's contents.
 51 | #define array_clear(self) ((self)->size = 0)
 52 | 
 53 | /// Reserve `new_capacity` elements of space in the array. If `new_capacity` is
 54 | /// less than the array's current capacity, this function has no effect.
 55 | #define array_reserve(self, new_capacity) \
 56 |   _array__reserve((Array *)(self), array_elem_size(self), new_capacity)
 57 | 
 58 | /// Free any memory allocated for this array. Note that this does not free any
 59 | /// memory allocated for the array's contents.
 60 | #define array_delete(self) _array__delete((Array *)(self))
 61 | 
 62 | /// Push a new `element` onto the end of the array.
 63 | #define array_push(self, element)                            \
 64 |   (_array__grow((Array *)(self), 1, array_elem_size(self)), \
 65 |    (self)->contents[(self)->size++] = (element))
 66 | 
 67 | /// Increase the array's size by `count` elements.
 68 | /// New elements are zero-initialized.
 69 | #define array_grow_by(self, count) \
 70 |   do { \
 71 |     if ((count) == 0) break; \
 72 |     _array__grow((Array *)(self), count, array_elem_size(self)); \
 73 |     memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \
 74 |     (self)->size += (count); \
 75 |   } while (0)
 76 | 
 77 | /// Append all elements from one array to the end of another.
 78 | #define array_push_all(self, other)                                       \
 79 |   array_extend((self), (other)->size, (other)->contents)
 80 | 
 81 | /// Append `count` elements to the end of the array, reading their values from the
 82 | /// `contents` pointer.
 83 | #define array_extend(self, count, contents)                    \
 84 |   _array__splice(                                               \
 85 |     (Array *)(self), array_elem_size(self), (self)->size, \
 86 |     0, count,  contents                                        \
 87 |   )
 88 | 
 89 | /// Remove `old_count` elements from the array starting at the given `index`. At
 90 | /// the same index, insert `new_count` new elements, reading their values from the
 91 | /// `new_contents` pointer.
 92 | #define array_splice(self, _index, old_count, new_count, new_contents)  \
 93 |   _array__splice(                                                       \
 94 |     (Array *)(self), array_elem_size(self), _index,                \
 95 |     old_count, new_count, new_contents                                 \
 96 |   )
 97 | 
 98 | /// Insert one `element` into the array at the given `index`.
 99 | #define array_insert(self, _index, element) \
100 |   _array__splice((Array *)(self), array_elem_size(self), _index, 0, 1, &(element))
101 | 
102 | /// Remove one element from the array at the given `index`.
103 | #define array_erase(self, _index) \
104 |   _array__erase((Array *)(self), array_elem_size(self), _index)
105 | 
106 | /// Pop the last element off the array, returning the element by value.
107 | #define array_pop(self) ((self)->contents[--(self)->size])
108 | 
109 | /// Assign the contents of one array to another, reallocating if necessary.
110 | #define array_assign(self, other) \
111 |   _array__assign((Array *)(self), (const Array *)(other), array_elem_size(self))
112 | 
113 | /// Swap one array with another
114 | #define array_swap(self, other) \
115 |   _array__swap((Array *)(self), (Array *)(other))
116 | 
117 | /// Get the size of the array contents
118 | #define array_elem_size(self) (sizeof *(self)->contents)
119 | 
120 | /// Search a sorted array for a given `needle` value, using the given `compare`
121 | /// callback to determine the order.
122 | ///
123 | /// If an existing element is found to be equal to `needle`, then the `index`
124 | /// out-parameter is set to the existing value's index, and the `exists`
125 | /// out-parameter is set to true. Otherwise, `index` is set to an index where
126 | /// `needle` should be inserted in order to preserve the sorting, and `exists`
127 | /// is set to false.
128 | #define array_search_sorted_with(self, compare, needle, _index, _exists) \
129 |   _array__search_sorted(self, 0, compare, , needle, _index, _exists)
130 | 
131 | /// Search a sorted array for a given `needle` value, using integer comparisons
132 | /// of a given struct field (specified with a leading dot) to determine the order.
133 | ///
134 | /// See also `array_search_sorted_with`.
135 | #define array_search_sorted_by(self, field, needle, _index, _exists) \
136 |   _array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists)
137 | 
138 | /// Insert a given `value` into a sorted array, using the given `compare`
139 | /// callback to determine the order.
140 | #define array_insert_sorted_with(self, compare, value) \
141 |   do { \
142 |     unsigned _index, _exists; \
143 |     array_search_sorted_with(self, compare, &(value), &_index, &_exists); \
144 |     if (!_exists) array_insert(self, _index, value); \
145 |   } while (0)
146 | 
147 | /// Insert a given `value` into a sorted array, using integer comparisons of
148 | /// a given struct field (specified with a leading dot) to determine the order.
149 | ///
150 | /// See also `array_search_sorted_by`.
151 | #define array_insert_sorted_by(self, field, value) \
152 |   do { \
153 |     unsigned _index, _exists; \
154 |     array_search_sorted_by(self, field, (value) field, &_index, &_exists); \
155 |     if (!_exists) array_insert(self, _index, value); \
156 |   } while (0)
157 | 
158 | // Private
159 | 
160 | typedef Array(void) Array;
161 | 
162 | /// This is not what you're looking for, see `array_delete`.
163 | static inline void _array__delete(Array *self) {
164 |   if (self->contents) {
165 |     ts_free(self->contents);
166 |     self->contents = NULL;
167 |     self->size = 0;
168 |     self->capacity = 0;
169 |   }
170 | }
171 | 
172 | /// This is not what you're looking for, see `array_erase`.
173 | static inline void _array__erase(Array *self, size_t element_size,
174 |                                 uint32_t index) {
175 |   assert(index < self->size);
176 |   char *contents = (char *)self->contents;
177 |   memmove(contents + index * element_size, contents + (index + 1) * element_size,
178 |           (self->size - index - 1) * element_size);
179 |   self->size--;
180 | }
181 | 
182 | /// This is not what you're looking for, see `array_reserve`.
183 | static inline void _array__reserve(Array *self, size_t element_size, uint32_t new_capacity) {
184 |   if (new_capacity > self->capacity) {
185 |     if (self->contents) {
186 |       self->contents = ts_realloc(self->contents, new_capacity * element_size);
187 |     } else {
188 |       self->contents = ts_malloc(new_capacity * element_size);
189 |     }
190 |     self->capacity = new_capacity;
191 |   }
192 | }
193 | 
194 | /// This is not what you're looking for, see `array_assign`.
195 | static inline void _array__assign(Array *self, const Array *other, size_t element_size) {
196 |   _array__reserve(self, element_size, other->size);
197 |   self->size = other->size;
198 |   memcpy(self->contents, other->contents, self->size * element_size);
199 | }
200 | 
201 | /// This is not what you're looking for, see `array_swap`.
202 | static inline void _array__swap(Array *self, Array *other) {
203 |   Array swap = *other;
204 |   *other = *self;
205 |   *self = swap;
206 | }
207 | 
208 | /// This is not what you're looking for, see `array_push` or `array_grow_by`.
209 | static inline void _array__grow(Array *self, uint32_t count, size_t element_size) {
210 |   uint32_t new_size = self->size + count;
211 |   if (new_size > self->capacity) {
212 |     uint32_t new_capacity = self->capacity * 2;
213 |     if (new_capacity < 8) new_capacity = 8;
214 |     if (new_capacity < new_size) new_capacity = new_size;
215 |     _array__reserve(self, element_size, new_capacity);
216 |   }
217 | }
218 | 
219 | /// This is not what you're looking for, see `array_splice`.
220 | static inline void _array__splice(Array *self, size_t element_size,
221 |                                  uint32_t index, uint32_t old_count,
222 |                                  uint32_t new_count, const void *elements) {
223 |   uint32_t new_size = self->size + new_count - old_count;
224 |   uint32_t old_end = index + old_count;
225 |   uint32_t new_end = index + new_count;
226 |   assert(old_end <= self->size);
227 | 
228 |   _array__reserve(self, element_size, new_size);
229 | 
230 |   char *contents = (char *)self->contents;
231 |   if (self->size > old_end) {
232 |     memmove(
233 |       contents + new_end * element_size,
234 |       contents + old_end * element_size,
235 |       (self->size - old_end) * element_size
236 |     );
237 |   }
238 |   if (new_count > 0) {
239 |     if (elements) {
240 |       memcpy(
241 |         (contents + index * element_size),
242 |         elements,
243 |         new_count * element_size
244 |       );
245 |     } else {
246 |       memset(
247 |         (contents + index * element_size),
248 |         0,
249 |         new_count * element_size
250 |       );
251 |     }
252 |   }
253 |   self->size += new_count - old_count;
254 | }
255 | 
256 | /// A binary search routine, based on Rust's `std::slice::binary_search_by`.
257 | /// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`.
258 | #define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \
259 |   do { \
260 |     *(_index) = start; \
261 |     *(_exists) = false; \
262 |     uint32_t size = (self)->size - *(_index); \
263 |     if (size == 0) break; \
264 |     int comparison; \
265 |     while (size > 1) { \
266 |       uint32_t half_size = size / 2; \
267 |       uint32_t mid_index = *(_index) + half_size; \
268 |       comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \
269 |       if (comparison <= 0) *(_index) = mid_index; \
270 |       size -= half_size; \
271 |     } \
272 |     comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \
273 |     if (comparison == 0) *(_exists) = true; \
274 |     else if (comparison < 0) *(_index) += 1; \
275 |   } while (0)
276 | 
277 | /// Helper macro for the `_sorted_by` routines below. This takes the left (existing)
278 | /// parameter by reference in order to work with the generic sorting function above.
279 | #define _compare_int(a, b) ((int)*(a) - (int)(b))
280 | 
281 | #ifdef _MSC_VER
282 | #pragma warning(pop)
283 | #elif defined(__GNUC__) || defined(__clang__)
284 | #pragma GCC diagnostic pop
285 | #endif
286 | 
287 | #ifdef __cplusplus
288 | }
289 | #endif
290 | 
291 | #endif  // TREE_SITTER_ARRAY_H_
292 | 
```

--------------------------------------------------------------------------------
/deps/tree-sitter-markdown/tree-sitter-markdown/src/tree_sitter/array.h:
--------------------------------------------------------------------------------

```
  1 | #ifndef TREE_SITTER_ARRAY_H_
  2 | #define TREE_SITTER_ARRAY_H_
  3 | 
  4 | #ifdef __cplusplus
  5 | extern "C" {
  6 | #endif
  7 | 
  8 | #include "./alloc.h"
  9 | 
 10 | #include <assert.h>
 11 | #include <stdbool.h>
 12 | #include <stdint.h>
 13 | #include <stdlib.h>
 14 | #include <string.h>
 15 | 
 16 | #ifdef _MSC_VER
 17 | #pragma warning(push)
 18 | #pragma warning(disable : 4101)
 19 | #elif defined(__GNUC__) || defined(__clang__)
 20 | #pragma GCC diagnostic push
 21 | #pragma GCC diagnostic ignored "-Wunused-variable"
 22 | #endif
 23 | 
 24 | #define Array(T)       \
 25 |   struct {             \
 26 |     T *contents;       \
 27 |     uint32_t size;     \
 28 |     uint32_t capacity; \
 29 |   }
 30 | 
 31 | /// Initialize an array.
 32 | #define array_init(self) \
 33 |   ((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
 34 | 
 35 | /// Create an empty array.
 36 | #define array_new() \
 37 |   { NULL, 0, 0 }
 38 | 
 39 | /// Get a pointer to the element at a given `index` in the array.
 40 | #define array_get(self, _index) \
 41 |   (assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index])
 42 | 
 43 | /// Get a pointer to the first element in the array.
 44 | #define array_front(self) array_get(self, 0)
 45 | 
 46 | /// Get a pointer to the last element in the array.
 47 | #define array_back(self) array_get(self, (self)->size - 1)
 48 | 
 49 | /// Clear the array, setting its size to zero. Note that this does not free any
 50 | /// memory allocated for the array's contents.
 51 | #define array_clear(self) ((self)->size = 0)
 52 | 
 53 | /// Reserve `new_capacity` elements of space in the array. If `new_capacity` is
 54 | /// less than the array's current capacity, this function has no effect.
 55 | #define array_reserve(self, new_capacity) \
 56 |   _array__reserve((Array *)(self), array_elem_size(self), new_capacity)
 57 | 
 58 | /// Free any memory allocated for this array. Note that this does not free any
 59 | /// memory allocated for the array's contents.
 60 | #define array_delete(self) _array__delete((Array *)(self))
 61 | 
 62 | /// Push a new `element` onto the end of the array.
 63 | #define array_push(self, element)                            \
 64 |   (_array__grow((Array *)(self), 1, array_elem_size(self)), \
 65 |    (self)->contents[(self)->size++] = (element))
 66 | 
 67 | /// Increase the array's size by `count` elements.
 68 | /// New elements are zero-initialized.
 69 | #define array_grow_by(self, count) \
 70 |   do { \
 71 |     if ((count) == 0) break; \
 72 |     _array__grow((Array *)(self), count, array_elem_size(self)); \
 73 |     memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \
 74 |     (self)->size += (count); \
 75 |   } while (0)
 76 | 
 77 | /// Append all elements from one array to the end of another.
 78 | #define array_push_all(self, other)                                       \
 79 |   array_extend((self), (other)->size, (other)->contents)
 80 | 
 81 | /// Append `count` elements to the end of the array, reading their values from the
 82 | /// `contents` pointer.
 83 | #define array_extend(self, count, contents)                    \
 84 |   _array__splice(                                               \
 85 |     (Array *)(self), array_elem_size(self), (self)->size, \
 86 |     0, count,  contents                                        \
 87 |   )
 88 | 
 89 | /// Remove `old_count` elements from the array starting at the given `index`. At
 90 | /// the same index, insert `new_count` new elements, reading their values from the
 91 | /// `new_contents` pointer.
 92 | #define array_splice(self, _index, old_count, new_count, new_contents)  \
 93 |   _array__splice(                                                       \
 94 |     (Array *)(self), array_elem_size(self), _index,                \
 95 |     old_count, new_count, new_contents                                 \
 96 |   )
 97 | 
 98 | /// Insert one `element` into the array at the given `index`.
 99 | #define array_insert(self, _index, element) \
100 |   _array__splice((Array *)(self), array_elem_size(self), _index, 0, 1, &(element))
101 | 
102 | /// Remove one element from the array at the given `index`.
103 | #define array_erase(self, _index) \
104 |   _array__erase((Array *)(self), array_elem_size(self), _index)
105 | 
106 | /// Pop the last element off the array, returning the element by value.
107 | #define array_pop(self) ((self)->contents[--(self)->size])
108 | 
109 | /// Assign the contents of one array to another, reallocating if necessary.
110 | #define array_assign(self, other) \
111 |   _array__assign((Array *)(self), (const Array *)(other), array_elem_size(self))
112 | 
113 | /// Swap one array with another
114 | #define array_swap(self, other) \
115 |   _array__swap((Array *)(self), (Array *)(other))
116 | 
117 | /// Get the size of the array contents
118 | #define array_elem_size(self) (sizeof *(self)->contents)
119 | 
120 | /// Search a sorted array for a given `needle` value, using the given `compare`
121 | /// callback to determine the order.
122 | ///
123 | /// If an existing element is found to be equal to `needle`, then the `index`
124 | /// out-parameter is set to the existing value's index, and the `exists`
125 | /// out-parameter is set to true. Otherwise, `index` is set to an index where
126 | /// `needle` should be inserted in order to preserve the sorting, and `exists`
127 | /// is set to false.
128 | #define array_search_sorted_with(self, compare, needle, _index, _exists) \
129 |   _array__search_sorted(self, 0, compare, , needle, _index, _exists)
130 | 
131 | /// Search a sorted array for a given `needle` value, using integer comparisons
132 | /// of a given struct field (specified with a leading dot) to determine the order.
133 | ///
134 | /// See also `array_search_sorted_with`.
135 | #define array_search_sorted_by(self, field, needle, _index, _exists) \
136 |   _array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists)
137 | 
138 | /// Insert a given `value` into a sorted array, using the given `compare`
139 | /// callback to determine the order.
140 | #define array_insert_sorted_with(self, compare, value) \
141 |   do { \
142 |     unsigned _index, _exists; \
143 |     array_search_sorted_with(self, compare, &(value), &_index, &_exists); \
144 |     if (!_exists) array_insert(self, _index, value); \
145 |   } while (0)
146 | 
147 | /// Insert a given `value` into a sorted array, using integer comparisons of
148 | /// a given struct field (specified with a leading dot) to determine the order.
149 | ///
150 | /// See also `array_search_sorted_by`.
151 | #define array_insert_sorted_by(self, field, value) \
152 |   do { \
153 |     unsigned _index, _exists; \
154 |     array_search_sorted_by(self, field, (value) field, &_index, &_exists); \
155 |     if (!_exists) array_insert(self, _index, value); \
156 |   } while (0)
157 | 
158 | // Private
159 | 
160 | typedef Array(void) Array;
161 | 
162 | /// This is not what you're looking for, see `array_delete`.
163 | static inline void _array__delete(Array *self) {
164 |   if (self->contents) {
165 |     ts_free(self->contents);
166 |     self->contents = NULL;
167 |     self->size = 0;
168 |     self->capacity = 0;
169 |   }
170 | }
171 | 
172 | /// This is not what you're looking for, see `array_erase`.
173 | static inline void _array__erase(Array *self, size_t element_size,
174 |                                 uint32_t index) {
175 |   assert(index < self->size);
176 |   char *contents = (char *)self->contents;
177 |   memmove(contents + index * element_size, contents + (index + 1) * element_size,
178 |           (self->size - index - 1) * element_size);
179 |   self->size--;
180 | }
181 | 
182 | /// This is not what you're looking for, see `array_reserve`.
183 | static inline void _array__reserve(Array *self, size_t element_size, uint32_t new_capacity) {
184 |   if (new_capacity > self->capacity) {
185 |     if (self->contents) {
186 |       self->contents = ts_realloc(self->contents, new_capacity * element_size);
187 |     } else {
188 |       self->contents = ts_malloc(new_capacity * element_size);
189 |     }
190 |     self->capacity = new_capacity;
191 |   }
192 | }
193 | 
194 | /// This is not what you're looking for, see `array_assign`.
195 | static inline void _array__assign(Array *self, const Array *other, size_t element_size) {
196 |   _array__reserve(self, element_size, other->size);
197 |   self->size = other->size;
198 |   memcpy(self->contents, other->contents, self->size * element_size);
199 | }
200 | 
201 | /// This is not what you're looking for, see `array_swap`.
202 | static inline void _array__swap(Array *self, Array *other) {
203 |   Array swap = *other;
204 |   *other = *self;
205 |   *self = swap;
206 | }
207 | 
208 | /// This is not what you're looking for, see `array_push` or `array_grow_by`.
209 | static inline void _array__grow(Array *self, uint32_t count, size_t element_size) {
210 |   uint32_t new_size = self->size + count;
211 |   if (new_size > self->capacity) {
212 |     uint32_t new_capacity = self->capacity * 2;
213 |     if (new_capacity < 8) new_capacity = 8;
214 |     if (new_capacity < new_size) new_capacity = new_size;
215 |     _array__reserve(self, element_size, new_capacity);
216 |   }
217 | }
218 | 
219 | /// This is not what you're looking for, see `array_splice`.
220 | static inline void _array__splice(Array *self, size_t element_size,
221 |                                  uint32_t index, uint32_t old_count,
222 |                                  uint32_t new_count, const void *elements) {
223 |   uint32_t new_size = self->size + new_count - old_count;
224 |   uint32_t old_end = index + old_count;
225 |   uint32_t new_end = index + new_count;
226 |   assert(old_end <= self->size);
227 | 
228 |   _array__reserve(self, element_size, new_size);
229 | 
230 |   char *contents = (char *)self->contents;
231 |   if (self->size > old_end) {
232 |     memmove(
233 |       contents + new_end * element_size,
234 |       contents + old_end * element_size,
235 |       (self->size - old_end) * element_size
236 |     );
237 |   }
238 |   if (new_count > 0) {
239 |     if (elements) {
240 |       memcpy(
241 |         (contents + index * element_size),
242 |         elements,
243 |         new_count * element_size
244 |       );
245 |     } else {
246 |       memset(
247 |         (contents + index * element_size),
248 |         0,
249 |         new_count * element_size
250 |       );
251 |     }
252 |   }
253 |   self->size += new_count - old_count;
254 | }
255 | 
256 | /// A binary search routine, based on Rust's `std::slice::binary_search_by`.
257 | /// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`.
258 | #define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \
259 |   do { \
260 |     *(_index) = start; \
261 |     *(_exists) = false; \
262 |     uint32_t size = (self)->size - *(_index); \
263 |     if (size == 0) break; \
264 |     int comparison; \
265 |     while (size > 1) { \
266 |       uint32_t half_size = size / 2; \
267 |       uint32_t mid_index = *(_index) + half_size; \
268 |       comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \
269 |       if (comparison <= 0) *(_index) = mid_index; \
270 |       size -= half_size; \
271 |     } \
272 |     comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \
273 |     if (comparison == 0) *(_exists) = true; \
274 |     else if (comparison < 0) *(_index) += 1; \
275 |   } while (0)
276 | 
277 | /// Helper macro for the `_sorted_by` routines below. This takes the left (existing)
278 | /// parameter by reference in order to work with the generic sorting function above.
279 | #define _compare_int(a, b) ((int)*(a) - (int)(b))
280 | 
281 | #ifdef _MSC_VER
282 | #pragma warning(pop)
283 | #elif defined(__GNUC__) || defined(__clang__)
284 | #pragma GCC diagnostic pop
285 | #endif
286 | 
287 | #ifdef __cplusplus
288 | }
289 | #endif
290 | 
291 | #endif  // TREE_SITTER_ARRAY_H_
292 | 
```

--------------------------------------------------------------------------------
/src/ui/renderer.cpp:
--------------------------------------------------------------------------------

```cpp
  1 | #include "renderer.h"
  2 | #include "src/ui/style_manager.h"
  3 | #include <algorithm>
  4 | #include <cstring>
  5 | #include <iostream>
  6 | 
  7 | Renderer::Renderer() : tab_size_(4) {}
  8 | 
  9 | Renderer::~Renderer() { restoreDefaultCursor(); }
 10 | 
 11 | void Renderer::renderEditor(const EditorState &state, const GapBuffer &buffer,
 12 |                             const SyntaxHighlighter *highlighter)
 13 | {
 14 |   // Set background color for entire screen
 15 |   setDefaultColors();
 16 |   clear();
 17 | 
 18 |   // Render main content area
 19 |   renderContent(state, buffer, highlighter);
 20 | 
 21 |   // Render status bar
 22 |   renderStatusBar(state, buffer);
 23 | 
 24 |   // Position cursor
 25 |   positionCursor(state.cursor, state.viewport);
 26 | 
 27 |   // Update cursor style based on mode
 28 |   updateCursorStyle(state.mode);
 29 | 
 30 |   refresh();
 31 | }
 32 | 
 33 | void Renderer::renderContent(const EditorState &state, const GapBuffer &buffer,
 34 |                              const SyntaxHighlighter *highlighter)
 35 | {
 36 |   const ViewportInfo &viewport = state.viewport;
 37 | 
 38 |   int line_num_width = calculateLineNumberWidth(buffer.getLineCount());
 39 |   int end_line =
 40 |       std::min(viewport.top + viewport.height, buffer.getLineCount());
 41 | 
 42 |   // Pre-calculate syntax highlighting for visible lines
 43 |   std::vector<std::vector<ColorSpan>> line_spans(end_line - viewport.top);
 44 |   if (highlighter)
 45 |   {
 46 |     for (int i = viewport.top; i < end_line; i++)
 47 |     {
 48 |       try
 49 |       {
 50 |         std::string line = buffer.getLine(i);
 51 |         std::string expanded_line = expandTabs(line, tab_size_);
 52 |         line_spans[i - viewport.top] =
 53 |             highlighter->getHighlightSpans(expanded_line, i, buffer);
 54 |       }
 55 |       catch (const std::exception &e)
 56 |       {
 57 |         std::cerr << "Syntax highlighting error on line " << i << ": "
 58 |                   << e.what() << std::endl;
 59 |         line_spans[i - viewport.top].clear();
 60 |       }
 61 |     }
 62 |   }
 63 | 
 64 |   // Render each visible line
 65 |   for (int i = viewport.top; i < end_line; i++)
 66 |   {
 67 |     int screen_row = i - viewport.top;
 68 |     move(screen_row, 0);
 69 | 
 70 |     // Set background for entire line
 71 |     setDefaultColors();
 72 | 
 73 |     // Render line number
 74 |     bool is_current_line = (state.cursor.line == i);
 75 |     renderLineNumbers(i, i + 1, state.cursor.line, line_num_width,
 76 |                       viewport.top);
 77 | 
 78 |     // Render line content
 79 |     std::string line = buffer.getLine(i);
 80 |     std::string expanded_line = expandTabs(line, tab_size_);
 81 |     const std::vector<ColorSpan> &spans =
 82 |         highlighter ? line_spans[i - viewport.top] : std::vector<ColorSpan>();
 83 | 
 84 |     renderLine(expanded_line, i, spans, viewport, state);
 85 | 
 86 |     // Clear to end of line with proper background
 87 |     setDefaultColors();
 88 |     clrtoeol();
 89 |   }
 90 | 
 91 |   // Fill remaining screen with background color
 92 |   setDefaultColors();
 93 |   for (int i = end_line - viewport.top; i < viewport.height; i++)
 94 |   {
 95 |     move(i, 0);
 96 |     clrtoeol();
 97 |   }
 98 | }
 99 | 
100 | void Renderer::renderLine(const std::string &line, int line_number,
101 |                           const std::vector<ColorSpan> &spans,
102 |                           const ViewportInfo &viewport,
103 |                           const EditorState &state)
104 | {
105 |   int content_width = viewport.width - viewport.content_start_col;
106 | 
107 |   for (int screen_col = 0; screen_col < content_width; screen_col++)
108 |   {
109 |     int file_col = viewport.left + screen_col;
110 | 
111 |     bool char_exists =
112 |         (file_col >= 0 && file_col < static_cast<int>(line.length()));
113 |     char ch = char_exists ? line[file_col] : ' ';
114 | 
115 |     // Filter non-printable characters
116 |     if (char_exists && (ch < 32 || ch > 126))
117 |     {
118 |       ch = ' ';
119 |     }
120 | 
121 |     // Check for selection
122 |     bool is_selected = isPositionSelected(line_number, file_col, state);
123 | 
124 |     if (is_selected)
125 |     {
126 |       attron(COLOR_PAIR(SELECTION) | A_REVERSE);
127 |       addch(ch);
128 |       attroff(COLOR_PAIR(SELECTION) | A_REVERSE);
129 |     }
130 |     else
131 |     {
132 |       // Apply syntax highlighting
133 |       bool color_applied = false;
134 | 
135 |       if (char_exists && !spans.empty())
136 |       {
137 |         for (const auto &span : spans)
138 |         {
139 |           if (file_col >= span.start && file_col < span.end)
140 |           {
141 |             if (span.colorPair >= 0 && span.colorPair < COLOR_PAIRS)
142 |             {
143 |               applyColorSpan(span, ch);
144 |               color_applied = true;
145 |               break;
146 |             }
147 |           }
148 |         }
149 |       }
150 | 
151 |       if (!color_applied)
152 |       {
153 |         setDefaultColors();
154 |         addch(ch);
155 |       }
156 |     }
157 |   }
158 | }
159 | 
160 | void Renderer::renderStatusBar(const EditorState &state,
161 |                                const GapBuffer &buffer)
162 | {
163 |   int rows, cols;
164 |   getmaxyx(stdscr, rows, cols);
165 |   int status_row = rows - 1;
166 | 
167 |   move(status_row, 0);
168 | 
169 |   // Set status bar background
170 |   attrset(COLOR_PAIR(STATUS_BAR));
171 |   clrtoeol();
172 | 
173 |   move(status_row, 0);
174 | 
175 |   // Render mode
176 |   renderStatusMode(state.mode);
177 | 
178 |   // Render file info
179 |   renderStatusFile(state.filename, state.is_modified);
180 | 
181 |   // Render position info (right-aligned)
182 |   renderStatusPosition(state.cursor, buffer.getLineCount(), state.has_selection,
183 |                        0); // TODO: calculate selection size
184 | }
185 | 
186 | void Renderer::renderStatusMode(EditorMode mode)
187 | {
188 |   std::string mode_str;
189 |   int mode_color;
190 | 
191 |   switch (mode)
192 |   {
193 |   case EditorMode::NORMAL:
194 |     mode_str = " NORMAL ";
195 |     mode_color = STATUS_BAR;
196 |     break;
197 |   case EditorMode::INSERT:
198 |     mode_str = " INSERT ";
199 |     mode_color = STATUS_BAR_ACTIVE;
200 |     break;
201 |   case EditorMode::VISUAL:
202 |     mode_str = " VISUAL ";
203 |     mode_color = STATUS_BAR_ACTIVE;
204 |     break;
205 |   }
206 | 
207 |   attron(COLOR_PAIR(mode_color) | A_BOLD);
208 |   printw("%s", mode_str.c_str());
209 |   attroff(COLOR_PAIR(mode_color) | A_BOLD);
210 | 
211 |   attron(COLOR_PAIR(STATUS_BAR));
212 |   printw(" ");
213 | }
214 | 
215 | void Renderer::renderStatusFile(const std::string &filename, bool is_modified)
216 | {
217 |   attron(COLOR_PAIR(STATUS_BAR_CYAN) | A_BOLD);
218 |   if (filename.empty())
219 |   {
220 |     printw("[No Name]");
221 |   }
222 |   else
223 |   {
224 |     size_t last_slash = filename.find_last_of("/\\");
225 |     std::string display_name = (last_slash != std::string::npos)
226 |                                    ? filename.substr(last_slash + 1)
227 |                                    : filename;
228 |     printw("%s", display_name.c_str());
229 |   }
230 |   attroff(COLOR_PAIR(STATUS_BAR_CYAN) | A_BOLD);
231 | 
232 |   if (is_modified)
233 |   {
234 |     attron(COLOR_PAIR(STATUS_BAR_ACTIVE) | A_BOLD);
235 |     printw(" [+]");
236 |     attroff(COLOR_PAIR(STATUS_BAR_ACTIVE) | A_BOLD);
237 |   }
238 | }
239 | 
240 | void Renderer::renderStatusPosition(const CursorInfo &cursor, int total_lines,
241 |                                     bool has_selection, int selection_size)
242 | {
243 |   int rows, cols;
244 |   getmaxyx(stdscr, rows, cols);
245 |   int status_row = rows - 1;
246 | 
247 |   char position_info[256];
248 |   int percentage =
249 |       total_lines == 0 ? 0 : ((cursor.line + 1) * 100 / total_lines);
250 | 
251 |   if (has_selection)
252 |   {
253 |     snprintf(position_info, sizeof(position_info),
254 |              "[selection] %d:%d %d/%d %d%% ", cursor.line + 1, cursor.col + 1,
255 |              cursor.line + 1, total_lines, percentage);
256 |   }
257 |   else
258 |   {
259 |     snprintf(position_info, sizeof(position_info), "%d:%d %d/%d %d%% ",
260 |              cursor.line + 1, cursor.col + 1, cursor.line + 1, total_lines,
261 |              percentage);
262 |   }
263 | 
264 |   int info_len = strlen(position_info);
265 |   int current_pos = getcurx(stdscr);
266 |   int right_start = cols - info_len;
267 | 
268 |   if (right_start <= current_pos)
269 |   {
270 |     right_start = current_pos + 2;
271 |   }
272 | 
273 |   // Fill middle space
274 |   attron(COLOR_PAIR(STATUS_BAR));
275 |   for (int i = current_pos; i < right_start && i < cols; i++)
276 |   {
277 |     move(status_row, i);
278 |     addch(' ');
279 |   }
280 | 
281 |   // Render position info
282 |   if (right_start < cols)
283 |   {
284 |     move(status_row, right_start);
285 |     attron(COLOR_PAIR(STATUS_BAR_YELLOW) | A_BOLD);
286 |     printw("%s", position_info);
287 |     attroff(COLOR_PAIR(STATUS_BAR_YELLOW) | A_BOLD);
288 |   }
289 | }
290 | 
291 | void Renderer::renderLineNumbers(int start_line, int end_line, int current_line,
292 |                                  int line_num_width, int viewport_top)
293 | {
294 |   int line_index = start_line - viewport_top;
295 |   bool is_current = (start_line == current_line);
296 | 
297 |   int color_pair = is_current ? LINE_NUMBERS_ACTIVE : LINE_NUMBERS;
298 |   attron(COLOR_PAIR(color_pair));
299 |   printw("%*d ", line_num_width, start_line + 1);
300 |   attroff(COLOR_PAIR(color_pair));
301 | 
302 |   // Separator
303 |   attron(COLOR_PAIR(LINE_NUMBERS_DIM));
304 |   addch(' ');
305 |   attroff(COLOR_PAIR(LINE_NUMBERS_DIM));
306 |   addch(' ');
307 | }
308 | 
309 | void Renderer::positionCursor(const CursorInfo &cursor,
310 |                               const ViewportInfo &viewport)
311 | {
312 |   int screen_row = cursor.line - viewport.top;
313 |   if (screen_row >= 0 && screen_row < viewport.height)
314 |   {
315 |     int screen_col = viewport.content_start_col + cursor.col - viewport.left;
316 | 
317 |     int rows, cols;
318 |     getmaxyx(stdscr, rows, cols);
319 | 
320 |     if (screen_col >= viewport.content_start_col && screen_col < cols)
321 |     {
322 |       move(screen_row, screen_col);
323 |     }
324 |     else
325 |     {
326 |       move(screen_row, viewport.content_start_col);
327 |     }
328 |   }
329 | }
330 | 
331 | void Renderer::updateCursorStyle(EditorMode mode)
332 | {
333 |   switch (mode)
334 |   {
335 |   case EditorMode::NORMAL:
336 |     printf("\033[2 q"); // Block cursor
337 |     break;
338 |   case EditorMode::INSERT:
339 |     printf("\033[6 q"); // Vertical bar cursor
340 |     break;
341 |   case EditorMode::VISUAL:
342 |     printf("\033[4 q"); // Underline cursor
343 |     break;
344 |   }
345 |   fflush(stdout);
346 | }
347 | 
348 | void Renderer::restoreDefaultCursor()
349 | {
350 |   printf("\033[0 q");
351 |   fflush(stdout);
352 | }
353 | 
354 | void Renderer::clear() { ::clear(); }
355 | 
356 | void Renderer::refresh() { ::refresh(); }
357 | 
358 | void Renderer::handleResize()
359 | {
360 |   clear();
361 |   refresh();
362 | }
363 | 
364 | Renderer::ViewportInfo Renderer::calculateViewport() const
365 | {
366 |   ViewportInfo viewport;
367 |   int rows, cols;
368 |   getmaxyx(stdscr, rows, cols);
369 | 
370 |   viewport.height = rows - 1; // Reserve last row for status bar
371 |   viewport.width = cols;
372 | 
373 |   // Calculate content start column (after line numbers)
374 |   int max_lines = 1000; // Reasonable default, should be passed as parameter
375 |   int line_num_width = calculateLineNumberWidth(max_lines);
376 |   viewport.content_start_col = line_num_width + 3; // +3 for space and separator
377 | 
378 |   return viewport;
379 | }
380 | 
381 | int Renderer::calculateLineNumberWidth(int max_line) const
382 | {
383 |   if (max_line <= 0)
384 |     return 1;
385 | 
386 |   int width = 0;
387 |   int num = max_line;
388 |   while (num > 0)
389 |   {
390 |     width++;
391 |     num /= 10;
392 |   }
393 |   return std::max(width, 3); // Minimum width of 3
394 | }
395 | 
396 | bool Renderer::isPositionSelected(int line, int col,
397 |                                   const EditorState &state) const
398 | {
399 |   if (!state.has_selection)
400 |   {
401 |     return false;
402 |   }
403 | 
404 |   int start_line = state.selection_start_line;
405 |   int start_col = state.selection_start_col;
406 |   int end_line = state.selection_end_line;
407 |   int end_col = state.selection_end_col;
408 | 
409 |   // Normalize selection
410 |   if (start_line > end_line || (start_line == end_line && start_col > end_col))
411 |   {
412 |     std::swap(start_line, end_line);
413 |     std::swap(start_col, end_col);
414 |   }
415 | 
416 |   if (line < start_line || line > end_line)
417 |   {
418 |     return false;
419 |   }
420 | 
421 |   if (start_line == end_line)
422 |   {
423 |     return col >= start_col && col < end_col;
424 |   }
425 |   else if (line == start_line)
426 |   {
427 |     return col >= start_col;
428 |   }
429 |   else if (line == end_line)
430 |   {
431 |     return col < end_col;
432 |   }
433 |   else
434 |   {
435 |     return true;
436 |   }
437 | }
438 | 
439 | std::string Renderer::expandTabs(const std::string &line, int tab_size) const
440 | {
441 |   std::string result;
442 |   for (char c : line)
443 |   {
444 |     if (c == '\t')
445 |     {
446 |       int spaces_to_add = tab_size - (result.length() % tab_size);
447 |       result.append(spaces_to_add, ' ');
448 |     }
449 |     else if (c >= 32 && c <= 126)
450 |     {
451 |       result += c;
452 |     }
453 |     else
454 |     {
455 |       result += ' ';
456 |     }
457 |   }
458 |   return result;
459 | }
460 | 
461 | void Renderer::applyColorSpan(const ColorSpan &span, char ch)
462 | {
463 |   int attrs = COLOR_PAIR(span.colorPair);
464 |   if (span.attribute != 0)
465 |   {
466 |     attrs |= span.attribute;
467 |   }
468 | 
469 |   attron(attrs);
470 |   addch(ch);
471 |   attroff(attrs);
472 | }
473 | 
474 | void Renderer::setDefaultColors()
475 | {
476 |   attrset(COLOR_PAIR(0)); // Use default background
477 | }
```

--------------------------------------------------------------------------------
/src/core/buffer.cpp:
--------------------------------------------------------------------------------

```cpp
  1 | #include "buffer.h"
  2 | #include <algorithm>
  3 | #include <cstring>
  4 | #include <fstream>
  5 | #include <iostream>
  6 | #include <stdexcept>
  7 | #include <vector>
  8 | 
  9 | const size_t GapBuffer::DEFAULT_GAP_SIZE;
 10 | const size_t GapBuffer::MIN_GAP_SIZE;
 11 | GapBuffer::GapBuffer()
 12 |     : gapStart(0), gapSize(DEFAULT_GAP_SIZE), lineIndexDirty(true)
 13 | {
 14 |   buffer.resize(DEFAULT_GAP_SIZE);
 15 | }
 16 | 
 17 | GapBuffer::GapBuffer(const std::string &initialText) : GapBuffer()
 18 | {
 19 |   if (!initialText.empty())
 20 |   {
 21 |     insertText(0, initialText);
 22 |   }
 23 | }
 24 | 
 25 | bool GapBuffer::loadFromFile(const std::string &filename)
 26 | {
 27 |   // --- Step 1: Fast I/O (Read entire file into memory) ---
 28 |   std::ifstream file(filename, std::ios::binary | std::ios::ate);
 29 |   if (!file.is_open())
 30 |   {
 31 |     return false;
 32 |   }
 33 | 
 34 |   std::streamsize fileSize = file.tellg();
 35 |   file.seekg(0, std::ios::beg);
 36 | 
 37 |   // Handle empty file case
 38 |   if (fileSize <= 0)
 39 |   {
 40 |     clear();
 41 |     insertChar(0, '\n');
 42 |     return true;
 43 |   }
 44 | 
 45 |   std::vector<char> content(fileSize);
 46 |   if (!file.read(content.data(), fileSize))
 47 |   {
 48 |     return false;
 49 |   }
 50 |   file.close();
 51 | 
 52 |   // --- Step 2: Check if normalization is needed ---
 53 |   const char *src = content.data();
 54 |   bool has_carriage_returns = false;
 55 | 
 56 |   // OPTIMIZATION: Quick scan for \r (memchr is highly optimized)
 57 |   if (std::memchr(src, '\r', fileSize) != nullptr)
 58 |   {
 59 |     has_carriage_returns = true;
 60 |   }
 61 | 
 62 |   // --- Step 3: Clear and Initial Buffer Sizing ---
 63 |   clear();
 64 | 
 65 |   if (!has_carriage_returns)
 66 |   {
 67 |     // FAST PATH: No normalization needed, direct copy
 68 |     buffer.resize(fileSize + DEFAULT_GAP_SIZE);
 69 |     char *dest = buffer.data() + DEFAULT_GAP_SIZE;
 70 | 
 71 |     std::memcpy(dest, src, fileSize);
 72 | 
 73 |     buffer.resize(fileSize + DEFAULT_GAP_SIZE);
 74 |     gapStart = 0;
 75 |     gapSize = DEFAULT_GAP_SIZE;
 76 |     invalidateLineIndex();
 77 | 
 78 |     return true;
 79 |   }
 80 | 
 81 |   // SLOW PATH: Normalization required
 82 |   buffer.resize(fileSize + DEFAULT_GAP_SIZE);
 83 |   char *dest = buffer.data() + DEFAULT_GAP_SIZE;
 84 |   size_t actualTextSize = 0;
 85 | 
 86 |   for (std::streamsize i = 0; i < fileSize; ++i)
 87 |   {
 88 |     char c = src[i];
 89 | 
 90 |     if (c == '\r')
 91 |     {
 92 |       // Windows \r\n sequence: skip \r, write \n
 93 |       if (i + 1 < fileSize && src[i + 1] == '\n')
 94 |       {
 95 |         c = '\n';
 96 |         i++; // Skip next \n
 97 |       }
 98 |       else
 99 |       {
100 |         // Old Mac/Lone \r: Replace with \n
101 |         c = '\n';
102 |       }
103 |     }
104 | 
105 |     *dest++ = c;
106 |     actualTextSize++;
107 |   }
108 | 
109 |   // --- Step 4: Finalize Gap Buffer State ---
110 |   buffer.resize(actualTextSize + DEFAULT_GAP_SIZE);
111 |   gapStart = 0;
112 |   gapSize = DEFAULT_GAP_SIZE;
113 |   invalidateLineIndex();
114 | 
115 |   return true;
116 | }
117 | 
118 | bool GapBuffer::saveToFile(const std::string &filename) const
119 | {
120 |   std::ofstream file(filename);
121 |   if (!file.is_open())
122 |   {
123 |     return false;
124 |   }
125 | 
126 |   file << getText();
127 |   return file.good();
128 | }
129 | 
130 | void GapBuffer::loadFromString(const std::string &content)
131 | {
132 |   clear();
133 | 
134 |   if (content.empty())
135 |   {
136 |     insertChar(0, '\n');
137 |     return;
138 |   }
139 | 
140 |   // Resize buffer to hold content + gap
141 |   size_t content_size = content.length();
142 |   buffer.resize(content_size + DEFAULT_GAP_SIZE);
143 | 
144 |   // Copy content directly after gap
145 |   std::memcpy(buffer.data() + DEFAULT_GAP_SIZE, content.data(), content_size);
146 | 
147 |   gapStart = 0;
148 |   gapSize = DEFAULT_GAP_SIZE;
149 | 
150 |   // Mark index as dirty - will rebuild on first access
151 |   invalidateLineIndex();
152 | }
153 | 
154 | void GapBuffer::clear()
155 | {
156 |   buffer.clear();
157 |   buffer.resize(DEFAULT_GAP_SIZE);
158 |   gapStart = 0;
159 |   gapSize = DEFAULT_GAP_SIZE;
160 |   invalidateLineIndex();
161 | }
162 | 
163 | int GapBuffer::getLineCount() const
164 | {
165 |   if (lineIndexDirty)
166 |   {
167 |     rebuildLineIndex();
168 |   }
169 |   return static_cast<int>(lineIndex.size());
170 | }
171 | 
172 | std::string GapBuffer::getLine(int lineNum) const
173 | {
174 |   if (lineIndexDirty)
175 |   {
176 |     rebuildLineIndex();
177 |   }
178 | 
179 |   if (lineNum < 0 || lineNum >= static_cast<int>(lineIndex.size()))
180 |   {
181 |     return "";
182 |   }
183 | 
184 |   size_t lineStart = lineIndex[lineNum];
185 |   size_t lineEnd;
186 | 
187 |   if (lineNum + 1 < static_cast<int>(lineIndex.size()))
188 |   {
189 |     lineEnd = lineIndex[lineNum + 1] - 1; // -1 to exclude newline
190 |   }
191 |   else
192 |   {
193 |     lineEnd = textSize();
194 |   }
195 | 
196 |   if (lineEnd <= lineStart)
197 |   {
198 |     return "";
199 |   }
200 | 
201 |   std::string result;
202 |   result.reserve(lineEnd - lineStart);
203 | 
204 |   for (size_t pos = lineStart; pos < lineEnd; ++pos)
205 |   {
206 |     result += charAt(pos);
207 |   }
208 | 
209 |   return result;
210 | }
211 | 
212 | size_t GapBuffer::getLineLength(int lineNum) const
213 | {
214 |   if (lineIndexDirty)
215 |   {
216 |     rebuildLineIndex();
217 |   }
218 | 
219 |   if (lineNum < 0 || lineNum >= static_cast<int>(lineIndex.size()))
220 |   {
221 |     return 0;
222 |   }
223 | 
224 |   size_t lineStart = lineIndex[lineNum];
225 |   size_t lineEnd;
226 | 
227 |   if (lineNum + 1 < static_cast<int>(lineIndex.size()))
228 |   {
229 |     lineEnd = lineIndex[lineNum + 1] - 1; // -1 to exclude the newline
230 |   }
231 |   else
232 |   {
233 |     lineEnd = textSize();
234 |   }
235 | 
236 |   return (lineEnd > lineStart) ? lineEnd - lineStart : 0;
237 | }
238 | 
239 | bool GapBuffer::isEmpty() const { return textSize() == 0; }
240 | 
241 | size_t GapBuffer::lineColToPos(int line, int col) const
242 | {
243 |   if (lineIndexDirty)
244 |   {
245 |     rebuildLineIndex();
246 |   }
247 | 
248 |   if (line < 0 || line >= static_cast<int>(lineIndex.size()))
249 |   {
250 |     return textSize();
251 |   }
252 | 
253 |   size_t lineStart = lineIndex[line];
254 |   size_t maxCol = getLineLength(line);
255 | 
256 |   size_t actualCol = std::min(static_cast<size_t>(std::max(0, col)), maxCol);
257 |   return lineStart + actualCol;
258 | }
259 | 
260 | std::pair<int, int> GapBuffer::posToLineCol(size_t pos) const
261 | {
262 |   if (lineIndexDirty)
263 |   {
264 |     rebuildLineIndex();
265 |   }
266 | 
267 |   pos = std::min(pos, textSize());
268 | 
269 |   // Binary search to find the line
270 |   int line = 0;
271 |   for (int i = static_cast<int>(lineIndex.size()) - 1; i >= 0; --i)
272 |   {
273 |     if (pos >= lineIndex[i])
274 |     {
275 |       line = i;
276 |       break;
277 |     }
278 |   }
279 | 
280 |   int col = static_cast<int>(pos - lineIndex[line]);
281 |   return std::make_pair(line, col);
282 | }
283 | 
284 | void GapBuffer::insertChar(size_t pos, char c)
285 | {
286 |   pos = std::min(pos, textSize());
287 |   moveGapTo(pos);
288 | 
289 |   if (gapSize == 0)
290 |   {
291 |     expandGap();
292 |   }
293 | 
294 |   buffer[gapStart] = c;
295 |   gapStart++;
296 |   gapSize--;
297 | 
298 |   if (c == '\n')
299 |   {
300 |     invalidateLineIndex();
301 |   }
302 | }
303 | 
304 | void GapBuffer::insertText(size_t pos, const std::string &text)
305 | {
306 |   if (text.empty())
307 |     return;
308 | 
309 |   pos = std::min(pos, textSize());
310 |   moveGapTo(pos);
311 | 
312 |   if (gapSize < text.size())
313 |   {
314 |     expandGap(text.size());
315 |   }
316 | 
317 |   bool hasNewlines = false;
318 |   for (char c : text)
319 |   {
320 |     buffer[gapStart] = c;
321 |     gapStart++;
322 |     gapSize--;
323 | 
324 |     if (c == '\n')
325 |     {
326 |       hasNewlines = true;
327 |     }
328 |   }
329 | 
330 |   if (hasNewlines)
331 |   {
332 |     invalidateLineIndex();
333 |   }
334 | }
335 | 
336 | void GapBuffer::deleteChar(size_t pos) { deleteRange(pos, 1); }
337 | 
338 | void GapBuffer::deleteRange(size_t start, size_t length)
339 | {
340 |   if (length == 0)
341 |     return;
342 | 
343 |   start = std::min(start, textSize());
344 |   length = std::min(length, textSize() - start);
345 | 
346 |   if (length == 0)
347 |     return;
348 | 
349 |   // Check if we're deleting newlines
350 |   bool hasNewlines = false;
351 |   for (size_t i = start; i < start + length; ++i)
352 |   {
353 |     if (charAt(i) == '\n')
354 |     {
355 |       hasNewlines = true;
356 |       break;
357 |     }
358 |   }
359 | 
360 |   moveGapTo(start);
361 |   gapSize += length;
362 | 
363 |   if (hasNewlines)
364 |   {
365 |     invalidateLineIndex();
366 |   }
367 | }
368 | 
369 | void GapBuffer::insertLine(int lineNum, const std::string &line)
370 | {
371 |   size_t pos = lineColToPos(lineNum, 0);
372 |   size_t lineLength = line.length() + 1; // +1 for newline
373 | 
374 |   // Insert the text
375 |   insertText(pos, line + "\n");
376 | 
377 |   // OPTIMIZATION: Update line index incrementally instead of rebuilding
378 |   if (!lineIndexDirty && lineNum < static_cast<int>(lineIndex.size()))
379 |   {
380 |     // Insert new line offset at position lineNum + 1
381 |     lineIndex.insert(lineIndex.begin() + lineNum + 1, pos + lineLength);
382 | 
383 |     // Shift all subsequent offsets by the length of inserted content
384 |     for (size_t i = lineNum + 2; i < lineIndex.size(); ++i)
385 |     {
386 |       lineIndex[i] += lineLength;
387 |     }
388 |   }
389 |   else
390 |   {
391 |     // Fallback: mark index as dirty for rebuild
392 |     invalidateLineIndex();
393 |   }
394 | }
395 | 
396 | void GapBuffer::deleteLine(int lineNum)
397 | {
398 |   if (lineNum < 0 || lineNum >= getLineCount())
399 |     return;
400 | 
401 |   size_t lineStart = lineColToPos(lineNum, 0);
402 |   size_t lineLength = getLineLength(lineNum);
403 | 
404 |   // Include the newline character if it exists
405 |   bool has_newline = (lineNum < getLineCount() - 1);
406 |   if (has_newline)
407 |   {
408 |     lineLength++;
409 |   }
410 | 
411 |   // Delete the range
412 |   deleteRange(lineStart, lineLength);
413 | 
414 |   // OPTIMIZATION: Update line index incrementally
415 |   if (!lineIndexDirty && lineNum + 1 < static_cast<int>(lineIndex.size()))
416 |   {
417 |     // Remove line offset at position lineNum + 1
418 |     lineIndex.erase(lineIndex.begin() + lineNum + 1);
419 | 
420 |     // Shift all subsequent offsets back by the deleted length
421 |     for (size_t i = lineNum + 1; i < lineIndex.size(); ++i)
422 |     {
423 |       lineIndex[i] -= lineLength;
424 |     }
425 |   }
426 |   else
427 |   {
428 |     // Fallback: mark index as dirty for rebuild
429 |     invalidateLineIndex();
430 |   }
431 | }
432 | 
433 | void GapBuffer::replaceLine(int lineNum, const std::string &newLine)
434 | {
435 |   if (lineNum < 0 || lineNum >= getLineCount())
436 |     return;
437 | 
438 |   size_t lineStart = lineColToPos(lineNum, 0);
439 |   size_t oldLineLength = getLineLength(lineNum);
440 | 
441 |   // Check if the new line contains newlines
442 |   bool newLineHasNewlines = (newLine.find('\n') != std::string::npos);
443 | 
444 |   if (newLineHasNewlines)
445 |   {
446 |     // Complex case: line split - must rebuild index
447 |     moveGapTo(lineStart);
448 |     if (gapSize < newLine.length())
449 |     {
450 |       expandGap(newLine.length());
451 |     }
452 | 
453 |     gapSize += oldLineLength;
454 |     for (char c : newLine)
455 |     {
456 |       buffer[gapStart] = c;
457 |       gapStart++;
458 |       gapSize--;
459 |     }
460 | 
461 |     invalidateLineIndex();
462 |     return;
463 |   }
464 | 
465 |   // Simple case: Same line, just different text
466 |   moveGapTo(lineStart);
467 | 
468 |   if (gapSize < newLine.length())
469 |   {
470 |     expandGap(newLine.length());
471 |   }
472 | 
473 |   gapSize += oldLineLength;
474 | 
475 |   for (char c : newLine)
476 |   {
477 |     buffer[gapStart] = c;
478 |     gapStart++;
479 |     gapSize--;
480 |   }
481 | 
482 |   // CRITICAL OPTIMIZATION: Update line index incrementally
483 |   if (!lineIndexDirty && lineNum + 1 < static_cast<int>(lineIndex.size()))
484 |   {
485 |     // Adjust all subsequent line offsets by the length difference
486 |     int lengthDiff =
487 |         static_cast<int>(newLine.length()) - static_cast<int>(oldLineLength);
488 | 
489 |     if (lengthDiff != 0)
490 |     {
491 |       for (size_t i = lineNum + 1; i < lineIndex.size(); ++i)
492 |       {
493 |         lineIndex[i] += lengthDiff;
494 |       }
495 |     }
496 |     // Line index is now up-to-date, no need to invalidate!
497 |   }
498 |   else if (lineIndexDirty)
499 |   {
500 |     // Already dirty, no change needed
501 |   }
502 | }
503 | 
504 | // std::string GapBuffer::getText() const
505 | // {
506 | //   std::string result;
507 | //   result.reserve(textSize());
508 | 
509 | //   // Add text before gap
510 | //   for (size_t i = 0; i < gapStart; ++i)
511 | //   {
512 | //     result += buffer[i];
513 | //   }
514 | 
515 | //   // Add text after gap
516 | //   for (size_t i = gapEnd(); i < buffer.size(); ++i)
517 | //   {
518 | //     result += buffer[i];
519 | //   }
520 | 
521 | //   return result;
522 | // }
523 | 
524 | // In buffer.cpp, inside GapBuffer::getText()
525 | 
526 | std::string GapBuffer::getText() const
527 | {
528 |   // Build line index ONCE if needed (for future getLine() calls)
529 |   if (lineIndexDirty)
530 |   {
531 |     rebuildLineIndex();
532 |   }
533 | 
534 |   // The size of the final string *must* match textSize()
535 |   size_t text_size = textSize();
536 |   std::string result;
537 |   result.reserve(text_size);
538 | 
539 |   // Append text before gap
540 |   result.append(buffer.data(), gapStart);
541 | 
542 |   // Append text after gap
543 |   size_t afterGapLength = text_size - gapStart;
544 |   if (afterGapLength > 0 && gapEnd() < buffer.size())
545 |   {
546 |     result.append(buffer.data() + gapEnd(), afterGapLength);
547 |   }
548 | 
549 |   return result;
550 | }
551 | 
552 | std::string GapBuffer::getTextRange(size_t start, size_t length) const
553 | {
554 |   start = std::min(start, textSize());
555 |   length = std::min(length, textSize() - start);
556 | 
557 |   std::string result;
558 |   result.reserve(length);
559 | 
560 |   for (size_t i = 0; i < length; ++i)
561 |   {
562 |     result += charAt(start + i);
563 |   }
564 | 
565 |   return result;
566 | }
567 | 
568 | size_t GapBuffer::size() const { return textSize(); }
569 | 
570 | size_t GapBuffer::getBufferSize() const { return buffer.size(); }
571 | 
572 | // In buffer.cpp
573 | 
574 | void GapBuffer::moveGapTo(size_t pos)
575 | {
576 |   if (pos == gapStart)
577 |     return;
578 | 
579 |   // Get the base pointer once
580 |   char *base_ptr = buffer.data();
581 | 
582 |   if (pos < gapStart)
583 |   {
584 |     // Move gap left - move text from before new gap position to after gap
585 |     size_t moveSize = gapStart - pos;
586 |     size_t gapEndPos = gapEnd();
587 | 
588 |     // Move text from [pos] to [gapEndPos - moveSize]
589 |     // This moves the text segment that was at pos and pushes it to the end of
590 |     // the text segment after the gap
591 |     std::memmove(base_ptr + gapEndPos - moveSize, base_ptr + pos, moveSize);
592 | 
593 |     gapStart = pos;
594 |   }
595 |   else
596 |   {
597 |     // Move gap right - move text from after gap to before new gap position
598 |     size_t moveSize = pos - gapStart;
599 |     size_t gapEndPos = gapEnd();
600 | 
601 |     // Move text from [gapEndPos] to [gapStart]
602 |     // This pulls the text segment that was after the gap and fills the newly
603 |     // created gap space
604 |     std::memmove(base_ptr + gapStart, base_ptr + gapEndPos, moveSize);
605 | 
606 |     gapStart = pos;
607 |   }
608 | 
609 |   // IMPORTANT: The gap size must remain the same, but its position is now
610 |   // correct
611 | }
612 | 
613 | void GapBuffer::expandGap(size_t minSize)
614 | {
615 |   size_t newGapSize = std::max(minSize, std::max(gapSize * 2, MIN_GAP_SIZE));
616 |   size_t oldBufferSize = buffer.size();
617 |   size_t newBufferSize = oldBufferSize + newGapSize - gapSize;
618 | 
619 |   // Create new buffer
620 |   std::vector<char> newBuffer(newBufferSize);
621 | 
622 |   // Copy text before gap
623 |   std::copy(buffer.begin(), buffer.begin() + gapStart, newBuffer.begin());
624 | 
625 |   // Copy text after gap
626 |   size_t afterGapSize = oldBufferSize - gapEnd();
627 |   std::copy(buffer.begin() + gapEnd(), buffer.end(),
628 |             newBuffer.begin() + gapStart + newGapSize);
629 | 
630 |   buffer = std::move(newBuffer);
631 |   gapSize = newGapSize;
632 | }
633 | 
634 | void GapBuffer::rebuildLineIndex() const
635 | {
636 |   lineIndex.clear();
637 |   lineIndex.push_back(0); // First line always starts at position 0
638 | 
639 |   for (size_t pos = 0; pos < textSize(); ++pos)
640 |   {
641 |     if (charAt(pos) == '\n')
642 |     {
643 |       lineIndex.push_back(pos + 1);
644 |     }
645 |   }
646 | 
647 |   // If the buffer doesn't end with a newline, we still have the last line
648 |   // The line index is already correct as we added positions after each newline
649 | 
650 |   lineIndexDirty = false;
651 | }
652 | 
653 | void GapBuffer::invalidateLineIndex() { lineIndexDirty = true; }
654 | 
655 | char GapBuffer::charAt(size_t pos) const
656 | {
657 |   if (pos >= textSize())
658 |   {
659 |     return '\0';
660 |   }
661 | 
662 |   if (pos < gapStart)
663 |   {
664 |     return buffer[pos];
665 |   }
666 |   else
667 |   {
668 |     // Position is after gap, so add gap size to skip over it
669 |     return buffer[pos + gapSize];
670 |   }
671 | }
```

--------------------------------------------------------------------------------
/src/main.cpp:
--------------------------------------------------------------------------------

```cpp
  1 | // src/main.cpp
  2 | #include "src/core/config_manager.h"
  3 | #include "src/core/editor.h"
  4 | #include "src/features/syntax_highlighter.h"
  5 | #include "src/ui/input_handler.h"
  6 | #include "src/ui/style_manager.h"
  7 | #include <algorithm>
  8 | #include <cstdlib>
  9 | #include <filesystem>
 10 | #include <iostream>
 11 | #include <vector>
 12 | 
 13 | #ifdef _WIN32
 14 | #include <curses.h>
 15 | #else
 16 | #include <csignal>
 17 | #include <ncurses.h>
 18 | #include <termios.h>
 19 | #include <unistd.h>
 20 | #endif
 21 | 
 22 | // void disableXonXoff();
 23 | // void disableSignalHandling();
 24 | // void restoreSignalHandling();
 25 | bool initializeNcurses();
 26 | bool initializeThemes();
 27 | void setupMouse();
 28 | void cleanupMouse();
 29 | 
 30 | enum class BenchmarkMode
 31 | {
 32 |   NONE,
 33 |   STARTUP_ONLY,        // Just ncurses + theme init (your current --quit)
 34 |   STARTUP_INTERACTIVE, // Full init to first input ready (RECOMMENDED)
 35 |   FILE_LOAD,           // Measure file loading separately
 36 |   FULL_CYCLE           // Complete startup + single operation + exit
 37 | };
 38 | 
 39 | struct BenchmarkResult
 40 | {
 41 |   std::chrono::milliseconds init_time;
 42 |   std::chrono::milliseconds theme_time;
 43 |   std::chrono::milliseconds editor_creation_time;
 44 |   std::chrono::milliseconds file_load_time;
 45 |   std::chrono::milliseconds first_render_time;
 46 |   std::chrono::milliseconds syntax_highlight_time;
 47 |   std::chrono::milliseconds total_time;
 48 | 
 49 |   void print(std::ostream &os) const
 50 |   {
 51 |     os << "=== Benchmark Results ===" << std::endl;
 52 |     os << "Init (ncurses):        " << init_time.count() << "ms" << std::endl;
 53 |     os << "Theme load:            " << theme_time.count() << "ms" << std::endl;
 54 |     os << "Editor creation:       " << editor_creation_time.count() << "ms"
 55 |        << std::endl;
 56 |     os << "File load:             " << file_load_time.count() << "ms"
 57 |        << std::endl;
 58 |     os << "Syntax highlighting:   " << syntax_highlight_time.count() << "ms"
 59 |        << std::endl;
 60 |     os << "First render:          " << first_render_time.count() << "ms"
 61 |        << std::endl;
 62 |     os << "------------------------" << std::endl;
 63 |     os << "TOTAL (user-perceived): " << total_time.count() << "ms" << std::endl;
 64 |   }
 65 | };
 66 | 
 67 | BenchmarkResult runStartupInteractiveBenchmark(const std::string &filename,
 68 |                                                bool enable_syntax_highlighting)
 69 | {
 70 | 
 71 |   BenchmarkResult result{};
 72 |   auto start = std::chrono::high_resolution_clock::now();
 73 | 
 74 |   // Phase 1: Initialize ncurses
 75 |   //   disableXonXoff();
 76 |   // #ifndef _win32
 77 |   //   disableSignalHandling();
 78 |   // #endif
 79 |   if (!initializeNcurses())
 80 |   {
 81 |     throw std::runtime_error("ncurses init failed");
 82 |   }
 83 |   if (!initializeThemes())
 84 |   {
 85 |     endwin();
 86 |     throw std::runtime_error("theme init failed");
 87 |   }
 88 | 
 89 |   auto after_init = std::chrono::high_resolution_clock::now();
 90 |   result.init_time =
 91 |       std::chrono::duration_cast<std::chrono::milliseconds>(after_init - start);
 92 | 
 93 |   // Phase 2: Load theme
 94 |   std::string active_theme = ConfigManager::getActiveTheme();
 95 |   std::string theme_file = ConfigManager::getThemeFile(active_theme);
 96 |   if (!theme_file.empty())
 97 |   {
 98 |     g_style_manager.load_theme_from_file(theme_file);
 99 |   }
100 | 
101 |   auto after_theme = std::chrono::high_resolution_clock::now();
102 |   result.theme_time = std::chrono::duration_cast<std::chrono::milliseconds>(
103 |       after_theme - after_init);
104 | 
105 |   // Phase 3: Create syntax highlighter (if enabled)
106 |   SyntaxHighlighter syntaxHighlighter;
107 |   SyntaxHighlighter *highlighterPtr = nullptr;
108 | 
109 |   if (enable_syntax_highlighting)
110 |   {
111 |     std::string syntax_dir = ConfigManager::getSyntaxRulesDir();
112 |     if (syntaxHighlighter.initialize(syntax_dir))
113 |     {
114 |       highlighterPtr = &syntaxHighlighter;
115 |     }
116 |   }
117 | 
118 |   auto after_highlighter_init = std::chrono::high_resolution_clock::now();
119 | 
120 |   // Phase 4: Create editor and load file
121 |   Editor editor(highlighterPtr);
122 | 
123 |   auto after_editor_creation = std::chrono::high_resolution_clock::now();
124 |   result.editor_creation_time =
125 |       std::chrono::duration_cast<std::chrono::milliseconds>(
126 |           after_editor_creation - after_theme);
127 | 
128 |   if (!editor.loadFile(filename))
129 |   {
130 |     endwin();
131 |     throw std::runtime_error("Failed to load file");
132 |   }
133 | 
134 |   auto after_file_load = std::chrono::high_resolution_clock::now();
135 |   result.file_load_time = std::chrono::duration_cast<std::chrono::milliseconds>(
136 |       after_file_load - after_editor_creation);
137 | 
138 |   // Phase 5: Initialize syntax highlighting for viewport
139 |   if (highlighterPtr)
140 |   {
141 |     editor.initializeViewportHighlighting();
142 |   }
143 | 
144 |   auto after_syntax = std::chrono::high_resolution_clock::now();
145 |   result.syntax_highlight_time =
146 |       std::chrono::duration_cast<std::chrono::milliseconds>(after_syntax -
147 |                                                             after_file_load);
148 | 
149 |   // Phase 6: Render first display
150 |   editor.setCursorMode();
151 |   editor.display();
152 |   wnoutrefresh(stdscr);
153 | 
154 |   auto after_render = std::chrono::high_resolution_clock::now();
155 |   result.first_render_time =
156 |       std::chrono::duration_cast<std::chrono::milliseconds>(after_render -
157 |                                                             after_syntax);
158 | 
159 |   // This is the "interactive ready" point - user can now type
160 |   result.total_time = std::chrono::duration_cast<std::chrono::milliseconds>(
161 |       after_render - start);
162 | 
163 |   // Cleanup
164 |   endwin();
165 | 
166 |   return result;
167 | }
168 | 
169 | void flushInputQueue();
170 | 
171 | int main(int argc, char *argv[])
172 | {
173 |   if (argc < 2)
174 |   {
175 |     std::cerr << "Usage: " << argv[0] << " <filename> [options]" << std::endl;
176 |     std::cerr << "\nBenchmark options:" << std::endl;
177 |     std::cerr << "  --bench-startup          Benchmark startup to interactive"
178 |               << std::endl;
179 |     std::cerr
180 |         << "  --bench-startup-nosyntax Same but without syntax highlighting"
181 |         << std::endl;
182 |     std::cerr << "  --bench-file-only        Benchmark only file loading"
183 |               << std::endl;
184 |     return 1;
185 |   }
186 | 
187 |   // Parse command line arguments
188 |   std::vector<std::string> args(argv, argv + argc);
189 |   bool bench_quit = std::any_of(args.begin(), args.end(), [](const auto &arg)
190 |                                 { return arg == "--bench-quit"; });
191 |   bool force_no_highlighting =
192 |       std::any_of(args.begin(), args.end(),
193 |                   [](const auto &arg) { return arg == "--none"; });
194 |   bool quit_immediately =
195 |       std::any_of(args.begin(), args.end(),
196 |                   [](const auto &arg) { return arg == "--quit"; });
197 | 
198 |   //   Bechmark
199 |   bool bench_startup = std::any_of(args.begin(), args.end(), [](const auto &arg)
200 |                                    { return arg == "--bench-startup"; });
201 |   bool bench_startup_nosyntax =
202 |       std::any_of(args.begin(), args.end(), [](const auto &arg)
203 |                   { return arg == "--bench-startup-nosyntax"; });
204 | 
205 |   std::string filename = std::filesystem::absolute(argv[1]).string();
206 | 
207 |   // Initialize config
208 |   if (!ConfigManager::ensureConfigStructure())
209 |   {
210 |     std::cerr << "Warning: Failed to ensure config structure" << std::endl;
211 |   }
212 |   ConfigManager::copyProjectFilesToConfig();
213 |   ConfigManager::loadConfig();
214 | 
215 |   if (bench_startup || bench_startup_nosyntax)
216 |   {
217 |     try
218 |     {
219 |       BenchmarkResult result =
220 |           runStartupInteractiveBenchmark(filename, !bench_startup_nosyntax);
221 | 
222 |       result.print(std::cerr);
223 |       return 0;
224 |     }
225 |     catch (const std::exception &e)
226 |     {
227 |       std::cerr << "Benchmark failed: " << e.what() << std::endl;
228 |       return 1;
229 |     }
230 |   }
231 |   // BENCHMARK PATH: Load file and quit immediately
232 |   if (quit_immediately)
233 |   {
234 |     auto start = std::chrono::high_resolution_clock::now();
235 | 
236 |     // Initialize ncurses (users see this cost)
237 |     //     disableXonXoff();
238 |     // #ifndef _win32
239 |     //     disableSignalHandling();
240 |     // #endif
241 |     if (!initializeNcurses())
242 |       return 1;
243 |     if (!initializeThemes())
244 |       return 1;
245 | 
246 |     auto after_init = std::chrono::high_resolution_clock::now();
247 | 
248 |     // Load theme (users see this)
249 |     std::string active_theme = ConfigManager::getActiveTheme();
250 |     std::string theme_file = ConfigManager::getThemeFile(active_theme);
251 |     if (!theme_file.empty())
252 |     {
253 |       g_style_manager.load_theme_from_file(theme_file);
254 |     }
255 | 
256 |     auto after_theme = std::chrono::high_resolution_clock::now();
257 | 
258 |     // Render once (users see this)
259 |     // editor.display();
260 |     wnoutrefresh(stdscr);
261 |     doupdate();
262 |     auto after_render = std::chrono::high_resolution_clock::now();
263 | 
264 |     // Cleanup
265 |     endwin();
266 | 
267 |     // Print timings to stderr (won't interfere with hyperfine)
268 |     auto ms = [](auto s, auto e)
269 |     {
270 |       return std::chrono::duration_cast<std::chrono::milliseconds>(e - s)
271 |           .count();
272 |     };
273 | 
274 |     std::cerr << "Init: " << ms(start, after_init) << "ms, "
275 |               << "Theme: " << ms(after_init, after_theme) << "ms, "
276 |               << "Render: " << ms(after_theme, after_render) << "ms, "
277 |               << "Total: " << ms(start, after_render) << "ms" << std::endl;
278 | 
279 |     return 0;
280 |   }
281 | 
282 |   // Determine syntax mode (CLI args override config)
283 |   SyntaxMode syntax_mode =
284 |       force_no_highlighting ? SyntaxMode::NONE : ConfigManager::getSyntaxMode();
285 | 
286 |   // Create syntax highlighter
287 |   SyntaxHighlighter syntaxHighlighter;
288 |   SyntaxHighlighter *highlighterPtr = nullptr;
289 | 
290 |   if (syntax_mode != SyntaxMode::NONE)
291 |   {
292 |     std::string syntax_dir = ConfigManager::getSyntaxRulesDir();
293 |     if (syntaxHighlighter.initialize(syntax_dir))
294 |     {
295 |       highlighterPtr = &syntaxHighlighter;
296 |     }
297 |     else
298 |     {
299 |       std::cerr << "Warning: Syntax highlighter init failed" << std::endl;
300 |     }
301 |   }
302 | 
303 |   // Create editor
304 |   Editor editor(highlighterPtr);
305 |   editor.setDeltaUndoEnabled(true);
306 | 
307 |   // Initialize delta group (even if disabled)
308 |   editor.beginDeltaGroup();
309 |   if (!editor.loadFile(filename))
310 |   {
311 |     std::cerr << "Warning: Could not open file " << filename << std::endl;
312 |   }
313 | 
314 |   // Initialize ncurses
315 |   //   disableXonXoff();
316 |   // #ifndef _win32
317 |   //   disableSignalHandling();
318 |   // #endif
319 |   if (!initializeNcurses())
320 |   {
321 |     return 1;
322 |   }
323 | 
324 |   if (!initializeThemes())
325 |   {
326 |     endwin();
327 |     return 1;
328 |   }
329 | 
330 |   // Load theme
331 |   std::string active_theme = ConfigManager::getActiveTheme();
332 |   std::string theme_file = ConfigManager::getThemeFile(active_theme);
333 |   if (!theme_file.empty())
334 |   {
335 |     if (!g_style_manager.load_theme_from_file(theme_file))
336 |     {
337 |       std::cerr << "FATAL: Theme load failed" << std::endl;
338 |     }
339 |   }
340 | 
341 |   setupMouse();
342 | 
343 |   if (highlighterPtr)
344 |   {
345 |     editor.initializeViewportHighlighting();
346 |   }
347 |   // Start editor
348 |   InputHandler inputHandler(editor);
349 |   editor.setCursorMode();
350 |   editor.display();
351 |   wnoutrefresh(stdscr);
352 |   doupdate();
353 |   curs_set(1); // Ensure cursor is visible
354 | 
355 |   // Handle --quit for testing
356 |   if (quit_immediately)
357 |   {
358 |     cleanupMouse();
359 |     attrset(A_NORMAL);
360 |     curs_set(1);
361 |     endwin();
362 |     std::cerr << "First line: " << editor.getFirstLine() << std::endl;
363 |     return 0;
364 |   }
365 | 
366 |   if (highlighterPtr)
367 |   {
368 |     highlighterPtr->scheduleBackgroundParse(editor.getBuffer());
369 |   }
370 |   // Start config watching
371 |   if (!ConfigManager::startWatchingConfig())
372 |   {
373 |     std::cerr << "Warning: Config watching failed" << std::endl;
374 |   }
375 | 
376 |   // Main loop
377 |   int key;
378 |   bool running = true;
379 |   while (running)
380 |   {
381 |     if (ConfigManager::isReloadPending())
382 |     {
383 |       curs_set(0); // Hide cursor
384 |       editor.display();
385 |       wnoutrefresh(stdscr);
386 |       doupdate();
387 |       editor.positionCursor(); // Position AFTER flush
388 |       curs_set(1);             // Show cursor
389 |     }
390 | 
391 |     key = getch();
392 | 
393 |     // if (key == 'q' || key == 'Q')
394 |     // {
395 |     //   //   if (editor.getMode() == EditorMode::NORMAL)
396 |     //   //   {
397 |     //   running = false;
398 |     //   continue;
399 |     //   //   }
400 |     // }
401 | 
402 |     InputHandler::KeyResult result = inputHandler.handleKey(key);
403 | 
404 |     switch (result)
405 |     {
406 |     case InputHandler::KeyResult::QUIT:
407 |       running = false;
408 |       break;
409 |     case InputHandler::KeyResult::REDRAW:
410 |     case InputHandler::KeyResult::HANDLED:
411 |       curs_set(0); // Hide during render
412 |       editor.display();
413 |       wnoutrefresh(stdscr);
414 |       doupdate();
415 |       editor.positionCursor(); // Position AFTER flush
416 |       curs_set(1);             // Show immediately
417 |       break;
418 |     case InputHandler::KeyResult::NOT_HANDLED:
419 |       //   std::cerr << "Input not handled" << std::endl;
420 |       break;
421 |     }
422 |     // if (result != InputHandler::KeyResult::NOT_HANDLED)
423 |     // {
424 |     //   doupdate();
425 |     // }
426 |   }
427 | 
428 |   // Cleanup
429 |   cleanupMouse();
430 |   attrset(A_NORMAL);
431 |   curs_set(1);
432 |   endwin();
433 |   return 0;
434 | }
435 | 
436 | bool initializeNcurses()
437 | {
438 |   initscr();
439 |   //   cbreak();
440 |   raw();
441 |   keypad(stdscr, TRUE); // This MUST be set for arrow keys
442 |   noecho();
443 |   curs_set(1);
444 | 
445 | #ifdef _WIN32
446 |   // CRITICAL: Don't use timeout on Windows - causes ERR spam
447 |   nodelay(stdscr, FALSE); // Blocking mode
448 | 
449 |   PDC_set_blink(FALSE);
450 |   PDC_return_key_modifiers(TRUE);
451 |   PDC_save_key_modifiers(TRUE);
452 |   scrollok(stdscr, FALSE);
453 |   leaveok(stdscr, FALSE);
454 |   raw();
455 |   meta(stdscr, TRUE);
456 |   intrflush(stdscr, FALSE);
457 | #else
458 |   timeout(50);
459 | #endif
460 | 
461 |   if (!has_colors())
462 |   {
463 |     endwin();
464 |     std::cerr << "Error: Terminal does not support colors" << std::endl;
465 |     return false;
466 |   }
467 | 
468 |   if (start_color() == ERR)
469 |   {
470 |     endwin();
471 |     std::cerr << "Error: Could not initialize colors" << std::endl;
472 |     return false;
473 |   }
474 | 
475 |   if (use_default_colors() == ERR)
476 |   {
477 |     assume_default_colors(COLOR_WHITE, COLOR_BLACK);
478 |   }
479 | 
480 |   return true;
481 | }
482 | 
483 | bool initializeThemes()
484 | {
485 |   g_style_manager.initialize();
486 | 
487 |   // Register the callback for future config changes
488 |   ConfigManager::registerReloadCallback(
489 |       []()
490 |       {
491 |         // 1. Get current theme from the *newly loaded* config
492 |         std::string active_theme = ConfigManager::getActiveTheme();
493 |         std::string theme_file = ConfigManager::getThemeFile(active_theme);
494 | 
495 |         if (!theme_file.empty())
496 |         {
497 |           // 2. Load the theme
498 |           if (!g_style_manager.load_theme_from_file(theme_file))
499 |           {
500 |             std::cerr << "ERROR: Theme reload failed for: " << active_theme
501 |                       << std::endl;
502 |           }
503 |         }
504 |       });
505 | 
506 |   // NOTE: Initial theme load logic removed from here
507 |   return true;
508 | }
509 | 
510 | void flushInputQueue()
511 | {
512 |   nodelay(stdscr, TRUE); // Make getch() non-blocking
513 |   while (getch() != ERR)
514 |   {
515 |     // Drain all pending input
516 |   }
517 |   nodelay(stdscr, FALSE); // Restore blocking mode
518 |   timeout(50);            // Restore your timeout
519 | }
520 | 
521 | void setupMouse()
522 | {
523 |   mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL);
524 | #ifndef _WIN32
525 |   printf("\033[?1003h");
526 |   fflush(stdout);
527 | #endif
528 | }
529 | 
530 | void cleanupMouse()
531 | {
532 | #ifndef _WIN32
533 |   printf("\033[?1003l");
534 |   fflush(stdout);
535 | #endif
536 | }
```

--------------------------------------------------------------------------------
/deps/tree-sitter-markdown/tree-sitter-markdown-inline/src/node-types.json:
--------------------------------------------------------------------------------

```json
  1 | [
  2 |   {
  3 |     "type": "backslash_escape",
  4 |     "named": true,
  5 |     "fields": {}
  6 |   },
  7 |   {
  8 |     "type": "code_span",
  9 |     "named": true,
 10 |     "fields": {},
 11 |     "children": {
 12 |       "multiple": true,
 13 |       "required": true,
 14 |       "types": [
 15 |         {
 16 |           "type": "code_span_delimiter",
 17 |           "named": true
 18 |         }
 19 |       ]
 20 |     }
 21 |   },
 22 |   {
 23 |     "type": "collapsed_reference_link",
 24 |     "named": true,
 25 |     "fields": {},
 26 |     "children": {
 27 |       "multiple": false,
 28 |       "required": false,
 29 |       "types": [
 30 |         {
 31 |           "type": "link_text",
 32 |           "named": true
 33 |         }
 34 |       ]
 35 |     }
 36 |   },
 37 |   {
 38 |     "type": "emphasis",
 39 |     "named": true,
 40 |     "fields": {},
 41 |     "children": {
 42 |       "multiple": true,
 43 |       "required": true,
 44 |       "types": [
 45 |         {
 46 |           "type": "backslash_escape",
 47 |           "named": true
 48 |         },
 49 |         {
 50 |           "type": "code_span",
 51 |           "named": true
 52 |         },
 53 |         {
 54 |           "type": "collapsed_reference_link",
 55 |           "named": true
 56 |         },
 57 |         {
 58 |           "type": "email_autolink",
 59 |           "named": true
 60 |         },
 61 |         {
 62 |           "type": "emphasis",
 63 |           "named": true
 64 |         },
 65 |         {
 66 |           "type": "emphasis_delimiter",
 67 |           "named": true
 68 |         },
 69 |         {
 70 |           "type": "entity_reference",
 71 |           "named": true
 72 |         },
 73 |         {
 74 |           "type": "full_reference_link",
 75 |           "named": true
 76 |         },
 77 |         {
 78 |           "type": "hard_line_break",
 79 |           "named": true
 80 |         },
 81 |         {
 82 |           "type": "html_tag",
 83 |           "named": true
 84 |         },
 85 |         {
 86 |           "type": "image",
 87 |           "named": true
 88 |         },
 89 |         {
 90 |           "type": "inline_link",
 91 |           "named": true
 92 |         },
 93 |         {
 94 |           "type": "latex_block",
 95 |           "named": true
 96 |         },
 97 |         {
 98 |           "type": "numeric_character_reference",
 99 |           "named": true
100 |         },
101 |         {
102 |           "type": "shortcut_link",
103 |           "named": true
104 |         },
105 |         {
106 |           "type": "strikethrough",
107 |           "named": true
108 |         },
109 |         {
110 |           "type": "strong_emphasis",
111 |           "named": true
112 |         },
113 |         {
114 |           "type": "uri_autolink",
115 |           "named": true
116 |         }
117 |       ]
118 |     }
119 |   },
120 |   {
121 |     "type": "full_reference_link",
122 |     "named": true,
123 |     "fields": {},
124 |     "children": {
125 |       "multiple": true,
126 |       "required": true,
127 |       "types": [
128 |         {
129 |           "type": "link_label",
130 |           "named": true
131 |         },
132 |         {
133 |           "type": "link_text",
134 |           "named": true
135 |         }
136 |       ]
137 |     }
138 |   },
139 |   {
140 |     "type": "hard_line_break",
141 |     "named": true,
142 |     "fields": {}
143 |   },
144 |   {
145 |     "type": "html_tag",
146 |     "named": true,
147 |     "fields": {}
148 |   },
149 |   {
150 |     "type": "image",
151 |     "named": true,
152 |     "fields": {},
153 |     "children": {
154 |       "multiple": true,
155 |       "required": false,
156 |       "types": [
157 |         {
158 |           "type": "image_description",
159 |           "named": true
160 |         },
161 |         {
162 |           "type": "link_destination",
163 |           "named": true
164 |         },
165 |         {
166 |           "type": "link_label",
167 |           "named": true
168 |         },
169 |         {
170 |           "type": "link_title",
171 |           "named": true
172 |         }
173 |       ]
174 |     }
175 |   },
176 |   {
177 |     "type": "image_description",
178 |     "named": true,
179 |     "fields": {},
180 |     "children": {
181 |       "multiple": true,
182 |       "required": false,
183 |       "types": [
184 |         {
185 |           "type": "backslash_escape",
186 |           "named": true
187 |         },
188 |         {
189 |           "type": "code_span",
190 |           "named": true
191 |         },
192 |         {
193 |           "type": "collapsed_reference_link",
194 |           "named": true
195 |         },
196 |         {
197 |           "type": "email_autolink",
198 |           "named": true
199 |         },
200 |         {
201 |           "type": "emphasis",
202 |           "named": true
203 |         },
204 |         {
205 |           "type": "entity_reference",
206 |           "named": true
207 |         },
208 |         {
209 |           "type": "full_reference_link",
210 |           "named": true
211 |         },
212 |         {
213 |           "type": "hard_line_break",
214 |           "named": true
215 |         },
216 |         {
217 |           "type": "html_tag",
218 |           "named": true
219 |         },
220 |         {
221 |           "type": "image",
222 |           "named": true
223 |         },
224 |         {
225 |           "type": "inline_link",
226 |           "named": true
227 |         },
228 |         {
229 |           "type": "latex_block",
230 |           "named": true
231 |         },
232 |         {
233 |           "type": "numeric_character_reference",
234 |           "named": true
235 |         },
236 |         {
237 |           "type": "shortcut_link",
238 |           "named": true
239 |         },
240 |         {
241 |           "type": "strikethrough",
242 |           "named": true
243 |         },
244 |         {
245 |           "type": "strong_emphasis",
246 |           "named": true
247 |         },
248 |         {
249 |           "type": "uri_autolink",
250 |           "named": true
251 |         }
252 |       ]
253 |     }
254 |   },
255 |   {
256 |     "type": "inline",
257 |     "named": true,
258 |     "root": true,
259 |     "fields": {},
260 |     "children": {
261 |       "multiple": true,
262 |       "required": false,
263 |       "types": [
264 |         {
265 |           "type": "backslash_escape",
266 |           "named": true
267 |         },
268 |         {
269 |           "type": "code_span",
270 |           "named": true
271 |         },
272 |         {
273 |           "type": "collapsed_reference_link",
274 |           "named": true
275 |         },
276 |         {
277 |           "type": "email_autolink",
278 |           "named": true
279 |         },
280 |         {
281 |           "type": "emphasis",
282 |           "named": true
283 |         },
284 |         {
285 |           "type": "entity_reference",
286 |           "named": true
287 |         },
288 |         {
289 |           "type": "full_reference_link",
290 |           "named": true
291 |         },
292 |         {
293 |           "type": "hard_line_break",
294 |           "named": true
295 |         },
296 |         {
297 |           "type": "html_tag",
298 |           "named": true
299 |         },
300 |         {
301 |           "type": "image",
302 |           "named": true
303 |         },
304 |         {
305 |           "type": "inline_link",
306 |           "named": true
307 |         },
308 |         {
309 |           "type": "latex_block",
310 |           "named": true
311 |         },
312 |         {
313 |           "type": "numeric_character_reference",
314 |           "named": true
315 |         },
316 |         {
317 |           "type": "shortcut_link",
318 |           "named": true
319 |         },
320 |         {
321 |           "type": "strikethrough",
322 |           "named": true
323 |         },
324 |         {
325 |           "type": "strong_emphasis",
326 |           "named": true
327 |         },
328 |         {
329 |           "type": "uri_autolink",
330 |           "named": true
331 |         }
332 |       ]
333 |     }
334 |   },
335 |   {
336 |     "type": "inline_link",
337 |     "named": true,
338 |     "fields": {},
339 |     "children": {
340 |       "multiple": true,
341 |       "required": false,
342 |       "types": [
343 |         {
344 |           "type": "link_destination",
345 |           "named": true
346 |         },
347 |         {
348 |           "type": "link_text",
349 |           "named": true
350 |         },
351 |         {
352 |           "type": "link_title",
353 |           "named": true
354 |         }
355 |       ]
356 |     }
357 |   },
358 |   {
359 |     "type": "latex_block",
360 |     "named": true,
361 |     "fields": {},
362 |     "children": {
363 |       "multiple": true,
364 |       "required": true,
365 |       "types": [
366 |         {
367 |           "type": "backslash_escape",
368 |           "named": true
369 |         },
370 |         {
371 |           "type": "latex_span_delimiter",
372 |           "named": true
373 |         }
374 |       ]
375 |     }
376 |   },
377 |   {
378 |     "type": "link_destination",
379 |     "named": true,
380 |     "fields": {},
381 |     "children": {
382 |       "multiple": true,
383 |       "required": false,
384 |       "types": [
385 |         {
386 |           "type": "backslash_escape",
387 |           "named": true
388 |         },
389 |         {
390 |           "type": "entity_reference",
391 |           "named": true
392 |         },
393 |         {
394 |           "type": "numeric_character_reference",
395 |           "named": true
396 |         }
397 |       ]
398 |     }
399 |   },
400 |   {
401 |     "type": "link_label",
402 |     "named": true,
403 |     "fields": {},
404 |     "children": {
405 |       "multiple": true,
406 |       "required": false,
407 |       "types": [
408 |         {
409 |           "type": "backslash_escape",
410 |           "named": true
411 |         },
412 |         {
413 |           "type": "entity_reference",
414 |           "named": true
415 |         },
416 |         {
417 |           "type": "numeric_character_reference",
418 |           "named": true
419 |         }
420 |       ]
421 |     }
422 |   },
423 |   {
424 |     "type": "link_text",
425 |     "named": true,
426 |     "fields": {},
427 |     "children": {
428 |       "multiple": true,
429 |       "required": false,
430 |       "types": [
431 |         {
432 |           "type": "backslash_escape",
433 |           "named": true
434 |         },
435 |         {
436 |           "type": "code_span",
437 |           "named": true
438 |         },
439 |         {
440 |           "type": "email_autolink",
441 |           "named": true
442 |         },
443 |         {
444 |           "type": "emphasis",
445 |           "named": true
446 |         },
447 |         {
448 |           "type": "entity_reference",
449 |           "named": true
450 |         },
451 |         {
452 |           "type": "hard_line_break",
453 |           "named": true
454 |         },
455 |         {
456 |           "type": "html_tag",
457 |           "named": true
458 |         },
459 |         {
460 |           "type": "image",
461 |           "named": true
462 |         },
463 |         {
464 |           "type": "latex_block",
465 |           "named": true
466 |         },
467 |         {
468 |           "type": "numeric_character_reference",
469 |           "named": true
470 |         },
471 |         {
472 |           "type": "strikethrough",
473 |           "named": true
474 |         },
475 |         {
476 |           "type": "strong_emphasis",
477 |           "named": true
478 |         },
479 |         {
480 |           "type": "uri_autolink",
481 |           "named": true
482 |         }
483 |       ]
484 |     }
485 |   },
486 |   {
487 |     "type": "link_title",
488 |     "named": true,
489 |     "fields": {},
490 |     "children": {
491 |       "multiple": true,
492 |       "required": false,
493 |       "types": [
494 |         {
495 |           "type": "backslash_escape",
496 |           "named": true
497 |         },
498 |         {
499 |           "type": "entity_reference",
500 |           "named": true
501 |         },
502 |         {
503 |           "type": "numeric_character_reference",
504 |           "named": true
505 |         }
506 |       ]
507 |     }
508 |   },
509 |   {
510 |     "type": "shortcut_link",
511 |     "named": true,
512 |     "fields": {},
513 |     "children": {
514 |       "multiple": false,
515 |       "required": true,
516 |       "types": [
517 |         {
518 |           "type": "link_text",
519 |           "named": true
520 |         }
521 |       ]
522 |     }
523 |   },
524 |   {
525 |     "type": "strikethrough",
526 |     "named": true,
527 |     "fields": {},
528 |     "children": {
529 |       "multiple": true,
530 |       "required": true,
531 |       "types": [
532 |         {
533 |           "type": "backslash_escape",
534 |           "named": true
535 |         },
536 |         {
537 |           "type": "code_span",
538 |           "named": true
539 |         },
540 |         {
541 |           "type": "collapsed_reference_link",
542 |           "named": true
543 |         },
544 |         {
545 |           "type": "email_autolink",
546 |           "named": true
547 |         },
548 |         {
549 |           "type": "emphasis",
550 |           "named": true
551 |         },
552 |         {
553 |           "type": "emphasis_delimiter",
554 |           "named": true
555 |         },
556 |         {
557 |           "type": "entity_reference",
558 |           "named": true
559 |         },
560 |         {
561 |           "type": "full_reference_link",
562 |           "named": true
563 |         },
564 |         {
565 |           "type": "hard_line_break",
566 |           "named": true
567 |         },
568 |         {
569 |           "type": "html_tag",
570 |           "named": true
571 |         },
572 |         {
573 |           "type": "image",
574 |           "named": true
575 |         },
576 |         {
577 |           "type": "inline_link",
578 |           "named": true
579 |         },
580 |         {
581 |           "type": "latex_block",
582 |           "named": true
583 |         },
584 |         {
585 |           "type": "numeric_character_reference",
586 |           "named": true
587 |         },
588 |         {
589 |           "type": "shortcut_link",
590 |           "named": true
591 |         },
592 |         {
593 |           "type": "strikethrough",
594 |           "named": true
595 |         },
596 |         {
597 |           "type": "strong_emphasis",
598 |           "named": true
599 |         },
600 |         {
601 |           "type": "uri_autolink",
602 |           "named": true
603 |         }
604 |       ]
605 |     }
606 |   },
607 |   {
608 |     "type": "strong_emphasis",
609 |     "named": true,
610 |     "fields": {},
611 |     "children": {
612 |       "multiple": true,
613 |       "required": true,
614 |       "types": [
615 |         {
616 |           "type": "backslash_escape",
617 |           "named": true
618 |         },
619 |         {
620 |           "type": "code_span",
621 |           "named": true
622 |         },
623 |         {
624 |           "type": "collapsed_reference_link",
625 |           "named": true
626 |         },
627 |         {
628 |           "type": "email_autolink",
629 |           "named": true
630 |         },
631 |         {
632 |           "type": "emphasis",
633 |           "named": true
634 |         },
635 |         {
636 |           "type": "emphasis_delimiter",
637 |           "named": true
638 |         },
639 |         {
640 |           "type": "entity_reference",
641 |           "named": true
642 |         },
643 |         {
644 |           "type": "full_reference_link",
645 |           "named": true
646 |         },
647 |         {
648 |           "type": "hard_line_break",
649 |           "named": true
650 |         },
651 |         {
652 |           "type": "html_tag",
653 |           "named": true
654 |         },
655 |         {
656 |           "type": "image",
657 |           "named": true
658 |         },
659 |         {
660 |           "type": "inline_link",
661 |           "named": true
662 |         },
663 |         {
664 |           "type": "latex_block",
665 |           "named": true
666 |         },
667 |         {
668 |           "type": "numeric_character_reference",
669 |           "named": true
670 |         },
671 |         {
672 |           "type": "shortcut_link",
673 |           "named": true
674 |         },
675 |         {
676 |           "type": "strikethrough",
677 |           "named": true
678 |         },
679 |         {
680 |           "type": "strong_emphasis",
681 |           "named": true
682 |         },
683 |         {
684 |           "type": "uri_autolink",
685 |           "named": true
686 |         }
687 |       ]
688 |     }
689 |   },
690 |   {
691 |     "type": "!",
692 |     "named": false
693 |   },
694 |   {
695 |     "type": "\"",
696 |     "named": false
697 |   },
698 |   {
699 |     "type": "#",
700 |     "named": false
701 |   },
702 |   {
703 |     "type": "$",
704 |     "named": false
705 |   },
706 |   {
707 |     "type": "%",
708 |     "named": false
709 |   },
710 |   {
711 |     "type": "&",
712 |     "named": false
713 |   },
714 |   {
715 |     "type": "'",
716 |     "named": false
717 |   },
718 |   {
719 |     "type": "(",
720 |     "named": false
721 |   },
722 |   {
723 |     "type": ")",
724 |     "named": false
725 |   },
726 |   {
727 |     "type": "*",
728 |     "named": false
729 |   },
730 |   {
731 |     "type": "+",
732 |     "named": false
733 |   },
734 |   {
735 |     "type": ",",
736 |     "named": false
737 |   },
738 |   {
739 |     "type": "-",
740 |     "named": false
741 |   },
742 |   {
743 |     "type": "-->",
744 |     "named": false
745 |   },
746 |   {
747 |     "type": ".",
748 |     "named": false
749 |   },
750 |   {
751 |     "type": "/",
752 |     "named": false
753 |   },
754 |   {
755 |     "type": ":",
756 |     "named": false
757 |   },
758 |   {
759 |     "type": ";",
760 |     "named": false
761 |   },
762 |   {
763 |     "type": "<",
764 |     "named": false
765 |   },
766 |   {
767 |     "type": "<!--",
768 |     "named": false
769 |   },
770 |   {
771 |     "type": "<![CDATA[",
772 |     "named": false
773 |   },
774 |   {
775 |     "type": "<?",
776 |     "named": false
777 |   },
778 |   {
779 |     "type": "=",
780 |     "named": false
781 |   },
782 |   {
783 |     "type": ">",
784 |     "named": false
785 |   },
786 |   {
787 |     "type": "?",
788 |     "named": false
789 |   },
790 |   {
791 |     "type": "?>",
792 |     "named": false
793 |   },
794 |   {
795 |     "type": "@",
796 |     "named": false
797 |   },
798 |   {
799 |     "type": "[",
800 |     "named": false
801 |   },
802 |   {
803 |     "type": "\\",
804 |     "named": false
805 |   },
806 |   {
807 |     "type": "]",
808 |     "named": false
809 |   },
810 |   {
811 |     "type": "]]>",
812 |     "named": false
813 |   },
814 |   {
815 |     "type": "^",
816 |     "named": false
817 |   },
818 |   {
819 |     "type": "_",
820 |     "named": false
821 |   },
822 |   {
823 |     "type": "`",
824 |     "named": false
825 |   },
826 |   {
827 |     "type": "code_span_delimiter",
828 |     "named": true
829 |   },
830 |   {
831 |     "type": "email_autolink",
832 |     "named": true
833 |   },
834 |   {
835 |     "type": "emphasis_delimiter",
836 |     "named": true
837 |   },
838 |   {
839 |     "type": "entity_reference",
840 |     "named": true
841 |   },
842 |   {
843 |     "type": "latex_span_delimiter",
844 |     "named": true
845 |   },
846 |   {
847 |     "type": "numeric_character_reference",
848 |     "named": true
849 |   },
850 |   {
851 |     "type": "uri_autolink",
852 |     "named": true
853 |   },
854 |   {
855 |     "type": "{",
856 |     "named": false
857 |   },
858 |   {
859 |     "type": "|",
860 |     "named": false
861 |   },
862 |   {
863 |     "type": "}",
864 |     "named": false
865 |   },
866 |   {
867 |     "type": "~",
868 |     "named": false
869 |   }
870 | ]
```

--------------------------------------------------------------------------------
/deps/tree-sitter-markdown/tree-sitter-markdown-inline/src/scanner.c:
--------------------------------------------------------------------------------

```cpp
  1 | #include "tree_sitter/parser.h"
  2 | 
  3 | #ifdef _MSC_VER
  4 | #define UNUSED __pragma(warning(suppress : 4101))
  5 | #else
  6 | #define UNUSED __attribute__((unused))
  7 | #endif
  8 | 
  9 | // For explanation of the tokens see grammar.js
 10 | typedef enum {
 11 |     ERROR,
 12 |     TRIGGER_ERROR,
 13 |     CODE_SPAN_START,
 14 |     CODE_SPAN_CLOSE,
 15 |     EMPHASIS_OPEN_STAR,
 16 |     EMPHASIS_OPEN_UNDERSCORE,
 17 |     EMPHASIS_CLOSE_STAR,
 18 |     EMPHASIS_CLOSE_UNDERSCORE,
 19 |     LAST_TOKEN_WHITESPACE,
 20 |     LAST_TOKEN_PUNCTUATION,
 21 |     STRIKETHROUGH_OPEN,
 22 |     STRIKETHROUGH_CLOSE,
 23 |     LATEX_SPAN_START,
 24 |     LATEX_SPAN_CLOSE,
 25 |     UNCLOSED_SPAN
 26 | } TokenType;
 27 | 
 28 | // Determines if a character is punctuation as defined by the markdown spec.
 29 | static bool is_punctuation(int32_t chr) {
 30 |     return (chr >= '!' && chr <= '/') || (chr >= ':' && chr <= '@') ||
 31 |            (chr >= '[' && chr <= '`') || (chr >= '{' && chr <= '~');
 32 | }
 33 | 
 34 | // State bitflags used with `Scanner.state`
 35 | 
 36 | // TODO
 37 | static UNUSED const uint8_t STATE_EMPHASIS_DELIMITER_MOD_3 = 0x3;
 38 | // Current delimiter run is opening
 39 | static const uint8_t STATE_EMPHASIS_DELIMITER_IS_OPEN = 0x1 << 2;
 40 | 
 41 | // Convenience function to emit the error token. This is done to stop invalid
 42 | // parse branches. Specifically:
 43 | // 1. When encountering a newline after a line break that ended a paragraph, and
 44 | // no new block
 45 | //    has been opened.
 46 | // 2. When encountering a new block after a soft line break.
 47 | // 3. When a `$._trigger_error` token is valid, which is used to stop parse
 48 | // branches through
 49 | //    normal tree-sitter grammar rules.
 50 | //
 51 | // See also the `$._soft_line_break` and `$._paragraph_end_newline` tokens in
 52 | // grammar.js
 53 | static bool error(TSLexer *lexer) {
 54 |     lexer->result_symbol = ERROR;
 55 |     return true;
 56 | }
 57 | 
 58 | typedef struct {
 59 |     // Parser state flags
 60 |     uint8_t state;
 61 |     uint8_t code_span_delimiter_length;
 62 |     uint8_t latex_span_delimiter_length;
 63 |     // The number of characters remaining in the currrent emphasis delimiter
 64 |     // run.
 65 |     uint8_t num_emphasis_delimiters_left;
 66 | 
 67 | } Scanner;
 68 | 
 69 | // Write the whole state of a Scanner to a byte buffer
 70 | static unsigned serialize(Scanner *s, char *buffer) {
 71 |     unsigned size = 0;
 72 |     buffer[size++] = (char)s->state;
 73 |     buffer[size++] = (char)s->code_span_delimiter_length;
 74 |     buffer[size++] = (char)s->latex_span_delimiter_length;
 75 |     buffer[size++] = (char)s->num_emphasis_delimiters_left;
 76 |     return size;
 77 | }
 78 | 
 79 | // Read the whole state of a Scanner from a byte buffer
 80 | // `serizalize` and `deserialize` should be fully symmetric.
 81 | static void deserialize(Scanner *s, const char *buffer, unsigned length) {
 82 |     s->state = 0;
 83 |     s->code_span_delimiter_length = 0;
 84 |     s->latex_span_delimiter_length = 0;
 85 |     s->num_emphasis_delimiters_left = 0;
 86 |     if (length > 0) {
 87 |         size_t size = 0;
 88 |         s->state = (uint8_t)buffer[size++];
 89 |         s->code_span_delimiter_length = (uint8_t)buffer[size++];
 90 |         s->latex_span_delimiter_length = (uint8_t)buffer[size++];
 91 |         s->num_emphasis_delimiters_left = (uint8_t)buffer[size++];
 92 |     }
 93 | }
 94 | 
 95 | static bool parse_leaf_delimiter(TSLexer *lexer, uint8_t *delimiter_length,
 96 |                                  const bool *valid_symbols,
 97 |                                  const char delimiter,
 98 |                                  const TokenType open_token,
 99 |                                  const TokenType close_token) {
100 |     uint8_t level = 0;
101 |     while (lexer->lookahead == delimiter) {
102 |         lexer->advance(lexer, false);
103 |         level++;
104 |     }
105 |     lexer->mark_end(lexer);
106 |     if (level == *delimiter_length && valid_symbols[close_token]) {
107 |         *delimiter_length = 0;
108 |         lexer->result_symbol = close_token;
109 |         return true;
110 |     }
111 |     if (valid_symbols[open_token]) {
112 |         // Parse ahead to check if there is a closing delimiter
113 |         size_t close_level = 0;
114 |         while (!lexer->eof(lexer)) {
115 |             if (lexer->lookahead == delimiter) {
116 |                 close_level++;
117 |             } else {
118 |                 if (close_level == level) {
119 |                     // Found a matching delimiter
120 |                     break;
121 |                 }
122 |                 close_level = 0;
123 |             }
124 |             lexer->advance(lexer, false);
125 |         }
126 |         if (close_level == level) {
127 |             *delimiter_length = level;
128 |             lexer->result_symbol = open_token;
129 |             return true;
130 |         }
131 |         if (valid_symbols[UNCLOSED_SPAN]) {
132 |             lexer->result_symbol = UNCLOSED_SPAN;
133 |             return true;
134 |         }
135 |     }
136 |     return false;
137 | }
138 | 
139 | static bool parse_backtick(Scanner *s, TSLexer *lexer,
140 |                            const bool *valid_symbols) {
141 |     return parse_leaf_delimiter(lexer, &s->code_span_delimiter_length,
142 |                                 valid_symbols, '`', CODE_SPAN_START,
143 |                                 CODE_SPAN_CLOSE);
144 | }
145 | 
146 | static bool parse_dollar(Scanner *s, TSLexer *lexer,
147 |                          const bool *valid_symbols) {
148 |     return parse_leaf_delimiter(lexer, &s->latex_span_delimiter_length,
149 |                                 valid_symbols, '$', LATEX_SPAN_START,
150 |                                 LATEX_SPAN_CLOSE);
151 | }
152 | 
153 | static bool parse_star(Scanner *s, TSLexer *lexer, const bool *valid_symbols) {
154 |     lexer->advance(lexer, false);
155 |     // If `num_emphasis_delimiters_left` is not zero then we already decided
156 |     // that this should be part of an emphasis delimiter run, so interpret it as
157 |     // such.
158 |     if (s->num_emphasis_delimiters_left > 0) {
159 |         // The `STATE_EMPHASIS_DELIMITER_IS_OPEN` state flag tells us wether it
160 |         // should be open or close.
161 |         if ((s->state & STATE_EMPHASIS_DELIMITER_IS_OPEN) &&
162 |             valid_symbols[EMPHASIS_OPEN_STAR]) {
163 |             s->state &= (~STATE_EMPHASIS_DELIMITER_IS_OPEN);
164 |             lexer->result_symbol = EMPHASIS_OPEN_STAR;
165 |             s->num_emphasis_delimiters_left--;
166 |             return true;
167 |         }
168 |         if (valid_symbols[EMPHASIS_CLOSE_STAR]) {
169 |             lexer->result_symbol = EMPHASIS_CLOSE_STAR;
170 |             s->num_emphasis_delimiters_left--;
171 |             return true;
172 |         }
173 |     }
174 |     lexer->mark_end(lexer);
175 |     // Otherwise count the number of stars
176 |     uint8_t star_count = 1;
177 |     while (lexer->lookahead == '*') {
178 |         star_count++;
179 |         lexer->advance(lexer, false);
180 |     }
181 |     bool line_end = lexer->lookahead == '\n' || lexer->lookahead == '\r' ||
182 |                     lexer->eof(lexer);
183 |     if (valid_symbols[EMPHASIS_OPEN_STAR] ||
184 |         valid_symbols[EMPHASIS_CLOSE_STAR]) {
185 |         // The desicion made for the first star also counts for all the
186 |         // following stars in the delimiter run. Rembemer how many there are.
187 |         s->num_emphasis_delimiters_left = star_count - 1;
188 |         // Look ahead to the next symbol (after the last star) to find out if it
189 |         // is whitespace punctuation or other.
190 |         bool next_symbol_whitespace =
191 |             line_end || lexer->lookahead == ' ' || lexer->lookahead == '\t';
192 |         bool next_symbol_punctuation = is_punctuation(lexer->lookahead);
193 |         // Information about the last token is in valid_symbols. See grammar.js
194 |         // for these tokens for how this is done.
195 |         if (valid_symbols[EMPHASIS_CLOSE_STAR] &&
196 |             !valid_symbols[LAST_TOKEN_WHITESPACE] &&
197 |             (!valid_symbols[LAST_TOKEN_PUNCTUATION] ||
198 |              next_symbol_punctuation || next_symbol_whitespace)) {
199 |             // Closing delimiters take precedence
200 |             s->state &= ~STATE_EMPHASIS_DELIMITER_IS_OPEN;
201 |             lexer->result_symbol = EMPHASIS_CLOSE_STAR;
202 |             return true;
203 |         }
204 |         if (!next_symbol_whitespace && (!next_symbol_punctuation ||
205 |                                         valid_symbols[LAST_TOKEN_PUNCTUATION] ||
206 |                                         valid_symbols[LAST_TOKEN_WHITESPACE])) {
207 |             s->state |= STATE_EMPHASIS_DELIMITER_IS_OPEN;
208 |             lexer->result_symbol = EMPHASIS_OPEN_STAR;
209 |             return true;
210 |         }
211 |     }
212 |     return false;
213 | }
214 | 
215 | static bool parse_tilde(Scanner *s, TSLexer *lexer, const bool *valid_symbols) {
216 |     lexer->advance(lexer, false);
217 |     // If `num_emphasis_delimiters_left` is not zero then we already decided
218 |     // that this should be part of an emphasis delimiter run, so interpret it as
219 |     // such.
220 |     if (s->num_emphasis_delimiters_left > 0) {
221 |         // The `STATE_EMPHASIS_DELIMITER_IS_OPEN` state flag tells us wether it
222 |         // should be open or close.
223 |         if ((s->state & STATE_EMPHASIS_DELIMITER_IS_OPEN) &&
224 |             valid_symbols[STRIKETHROUGH_OPEN]) {
225 |             s->state &= (~STATE_EMPHASIS_DELIMITER_IS_OPEN);
226 |             lexer->result_symbol = STRIKETHROUGH_OPEN;
227 |             s->num_emphasis_delimiters_left--;
228 |             return true;
229 |         }
230 |         if (valid_symbols[STRIKETHROUGH_CLOSE]) {
231 |             lexer->result_symbol = STRIKETHROUGH_CLOSE;
232 |             s->num_emphasis_delimiters_left--;
233 |             return true;
234 |         }
235 |     }
236 |     lexer->mark_end(lexer);
237 |     // Otherwise count the number of tildes
238 |     uint8_t star_count = 1;
239 |     while (lexer->lookahead == '~') {
240 |         star_count++;
241 |         lexer->advance(lexer, false);
242 |     }
243 |     bool line_end = lexer->lookahead == '\n' || lexer->lookahead == '\r' ||
244 |                     lexer->eof(lexer);
245 |     if (valid_symbols[STRIKETHROUGH_OPEN] ||
246 |         valid_symbols[STRIKETHROUGH_CLOSE]) {
247 |         // The desicion made for the first star also counts for all the
248 |         // following stars in the delimiter run. Rembemer how many there are.
249 |         s->num_emphasis_delimiters_left = star_count - 1;
250 |         // Look ahead to the next symbol (after the last star) to find out if it
251 |         // is whitespace punctuation or other.
252 |         bool next_symbol_whitespace =
253 |             line_end || lexer->lookahead == ' ' || lexer->lookahead == '\t';
254 |         bool next_symbol_punctuation = is_punctuation(lexer->lookahead);
255 |         // Information about the last token is in valid_symbols. See grammar.js
256 |         // for these tokens for how this is done.
257 |         if (valid_symbols[STRIKETHROUGH_CLOSE] &&
258 |             !valid_symbols[LAST_TOKEN_WHITESPACE] &&
259 |             (!valid_symbols[LAST_TOKEN_PUNCTUATION] ||
260 |              next_symbol_punctuation || next_symbol_whitespace)) {
261 |             // Closing delimiters take precedence
262 |             s->state &= ~STATE_EMPHASIS_DELIMITER_IS_OPEN;
263 |             lexer->result_symbol = STRIKETHROUGH_CLOSE;
264 |             return true;
265 |         }
266 |         if (!next_symbol_whitespace && (!next_symbol_punctuation ||
267 |                                         valid_symbols[LAST_TOKEN_PUNCTUATION] ||
268 |                                         valid_symbols[LAST_TOKEN_WHITESPACE])) {
269 |             s->state |= STATE_EMPHASIS_DELIMITER_IS_OPEN;
270 |             lexer->result_symbol = STRIKETHROUGH_OPEN;
271 |             return true;
272 |         }
273 |     }
274 |     return false;
275 | }
276 | 
277 | static bool parse_underscore(Scanner *s, TSLexer *lexer,
278 |                              const bool *valid_symbols) {
279 |     lexer->advance(lexer, false);
280 |     // If `num_emphasis_delimiters_left` is not zero then we already decided
281 |     // that this should be part of an emphasis delimiter run, so interpret it as
282 |     // such.
283 |     if (s->num_emphasis_delimiters_left > 0) {
284 |         // The `STATE_EMPHASIS_DELIMITER_IS_OPEN` state flag tells us wether it
285 |         // should be open or close.
286 |         if ((s->state & STATE_EMPHASIS_DELIMITER_IS_OPEN) &&
287 |             valid_symbols[EMPHASIS_OPEN_UNDERSCORE]) {
288 |             s->state &= (~STATE_EMPHASIS_DELIMITER_IS_OPEN);
289 |             lexer->result_symbol = EMPHASIS_OPEN_UNDERSCORE;
290 |             s->num_emphasis_delimiters_left--;
291 |             return true;
292 |         }
293 |         if (valid_symbols[EMPHASIS_CLOSE_UNDERSCORE]) {
294 |             lexer->result_symbol = EMPHASIS_CLOSE_UNDERSCORE;
295 |             s->num_emphasis_delimiters_left--;
296 |             return true;
297 |         }
298 |     }
299 |     lexer->mark_end(lexer);
300 |     // Otherwise count the number of stars
301 |     uint8_t underscore_count = 1;
302 |     while (lexer->lookahead == '_') {
303 |         underscore_count++;
304 |         lexer->advance(lexer, false);
305 |     }
306 |     bool line_end = lexer->lookahead == '\n' || lexer->lookahead == '\r' ||
307 |                     lexer->eof(lexer);
308 |     if (valid_symbols[EMPHASIS_OPEN_UNDERSCORE] ||
309 |         valid_symbols[EMPHASIS_CLOSE_UNDERSCORE]) {
310 |         // The desicion made for the first underscore also counts for all the
311 |         // following underscores in the delimiter run. Rembemer how many there are.
312 |         s->num_emphasis_delimiters_left = underscore_count - 1;
313 |         // Look ahead to the next symbol (after the last underscore) to find out if it
314 |         // is whitespace punctuation or other.
315 |         bool next_symbol_whitespace =
316 |             line_end || lexer->lookahead == ' ' || lexer->lookahead == '\t';
317 |         bool next_symbol_punctuation = is_punctuation(lexer->lookahead);
318 |         // Information about the last token is in valid_symbols. See grammar.js
319 |         // for these tokens for how this is done.
320 |         if (valid_symbols[EMPHASIS_CLOSE_UNDERSCORE] &&
321 |             !valid_symbols[LAST_TOKEN_WHITESPACE] &&
322 |             (!valid_symbols[LAST_TOKEN_PUNCTUATION] ||
323 |              next_symbol_punctuation || next_symbol_whitespace)) {
324 |             // Closing delimiters take precedence
325 |             s->state &= ~STATE_EMPHASIS_DELIMITER_IS_OPEN;
326 |             lexer->result_symbol = EMPHASIS_CLOSE_UNDERSCORE;
327 |             return true;
328 |         }
329 |         if (!next_symbol_whitespace && (!next_symbol_punctuation ||
330 |                                         valid_symbols[LAST_TOKEN_PUNCTUATION] ||
331 |                                         valid_symbols[LAST_TOKEN_WHITESPACE])) {
332 |             s->state |= STATE_EMPHASIS_DELIMITER_IS_OPEN;
333 |             lexer->result_symbol = EMPHASIS_OPEN_UNDERSCORE;
334 |             return true;
335 |         }
336 |     }
337 |     return false;
338 | }
339 | 
340 | static bool scan(Scanner *s, TSLexer *lexer, const bool *valid_symbols) {
341 |     // A normal tree-sitter rule decided that the current branch is invalid and
342 |     // now "requests" an error to stop the branch
343 |     if (valid_symbols[TRIGGER_ERROR]) {
344 |         return error(lexer);
345 |     }
346 | 
347 |     // Decide which tokens to consider based on the first non-whitespace
348 |     // character
349 |     switch (lexer->lookahead) {
350 |         case '`':
351 |             // A backtick could mark the beginning or ending of a code span or a
352 |             // fenced code block.
353 |             return parse_backtick(s, lexer, valid_symbols);
354 |         case '$':
355 |             return parse_dollar(s, lexer, valid_symbols);
356 |         case '*':
357 |             // A star could either mark the beginning or ending of emphasis, a
358 |             // list item or thematic break. This code is similar to the code for
359 |             // '_' and '+'.
360 |             return parse_star(s, lexer, valid_symbols);
361 |         case '_':
362 |             return parse_underscore(s, lexer, valid_symbols);
363 |         case '~':
364 |             return parse_tilde(s, lexer, valid_symbols);
365 |     }
366 |     return false;
367 | }
368 | 
369 | void *tree_sitter_markdown_inline_external_scanner_create() {
370 |     Scanner *s = (Scanner *)malloc(sizeof(Scanner));
371 |     deserialize(s, NULL, 0);
372 |     return s;
373 | }
374 | 
375 | bool tree_sitter_markdown_inline_external_scanner_scan(
376 |     void *payload, TSLexer *lexer, const bool *valid_symbols) {
377 |     Scanner *scanner = (Scanner *)payload;
378 |     return scan(scanner, lexer, valid_symbols);
379 | }
380 | 
381 | unsigned tree_sitter_markdown_inline_external_scanner_serialize(void *payload,
382 |                                                                 char *buffer) {
383 |     Scanner *scanner = (Scanner *)payload;
384 |     return serialize(scanner, buffer);
385 | }
386 | 
387 | void tree_sitter_markdown_inline_external_scanner_deserialize(void *payload,
388 |                                                               const char *buffer,
389 |                                                               unsigned length) {
390 |     Scanner *scanner = (Scanner *)payload;
391 |     deserialize(scanner, buffer, length);
392 | }
393 | 
394 | void tree_sitter_markdown_inline_external_scanner_destroy(void *payload) {
395 |     Scanner *scanner = (Scanner *)payload;
396 |     free(scanner);
397 | }
398 | 
```
Page 2/10FirstPrevNextLast