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 |  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 ®istry_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 | ```