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

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

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

--------------------------------------------------------------------------------
/src/core/config_manager.cpp:
--------------------------------------------------------------------------------

```cpp
  1 | #include "config_manager.h"
  2 | #include <algorithm>
  3 | #include <cstdlib>
  4 | #include <filesystem>
  5 | #include <fstream>
  6 | #include <functional>
  7 | #include <iostream>
  8 | #include <memory>
  9 | #include <sstream>
 10 | 
 11 | #include <yaml-cpp/yaml.h>
 12 | 
 13 | // EFSW includes
 14 | #include <efsw/efsw.h>
 15 | #include <efsw/efsw.hpp>
 16 | 
 17 | namespace fs = std::filesystem;
 18 | 
 19 | // Static initialization
 20 | std::string ConfigManager::config_dir_cache_;
 21 | std::string ConfigManager::active_theme_ = "default";
 22 | std::vector<ConfigReloadCallback> ConfigManager::reload_callbacks_;
 23 | std::unique_ptr<efsw::FileWatchListener> ConfigManager::watcher_listener_ =
 24 |     nullptr;
 25 | std::unique_ptr<efsw::FileWatcher> ConfigManager::watcher_instance_ = nullptr;
 26 | std::atomic<bool> ConfigManager::reload_pending_ = false;
 27 | EditorConfig ConfigManager::editor_config_;
 28 | SyntaxConfig ConfigManager::syntax_config_;
 29 | 
 30 | // --- EFSW Listener Class ---
 31 | 
 32 | // We define a custom listener that efsw will use to communicate events.
 33 | class ConfigFileListener : public efsw::FileWatchListener
 34 | {
 35 | public:
 36 |   void handleFileAction(efsw::WatchID watchid, const std::string &dir,
 37 |                         const std::string &filename, efsw::Action action,
 38 |                         std::string oldFilename) override
 39 |   {
 40 |     // We are interested in Modification and Renaming (e.g., atomic save by
 41 |     // editor)
 42 |     if (filename == "config.yaml" &&
 43 |         (action == efsw::Action::Modified || action == efsw::Action::Moved))
 44 |     {
 45 |       // Call the static handler in ConfigManager
 46 |       ConfigManager::handleFileChange();
 47 |     }
 48 |   }
 49 | };
 50 | 
 51 | // -----------------------------------------------------------------
 52 | // CONFIG DIRECTORY AND PATH GETTERS
 53 | // -----------------------------------------------------------------
 54 | 
 55 | std::string ConfigManager::getConfigDir()
 56 | {
 57 |   // Return cached value if already determined
 58 |   if (!config_dir_cache_.empty())
 59 |   {
 60 |     return config_dir_cache_;
 61 |   }
 62 | 
 63 |   std::vector<std::string> search_paths;
 64 | 
 65 | #ifdef _WIN32
 66 |   // Windows: %APPDATA%\arceditor
 67 |   const char *appdata = std::getenv("APPDATA");
 68 |   if (appdata)
 69 |   {
 70 |     search_paths.push_back(std::string(appdata) + "\\arceditor");
 71 |   }
 72 |   // Fallback: %USERPROFILE%\.config\arceditor
 73 |   const char *userprofile = std::getenv("USERPROFILE");
 74 |   if (userprofile)
 75 |   {
 76 |     search_paths.push_back(std::string(userprofile) + "\\.config\\arceditor");
 77 |   }
 78 | #else
 79 |   // Linux/macOS: XDG_CONFIG_HOME or ~/.config
 80 |   const char *xdg_config = std::getenv("XDG_CONFIG_HOME");
 81 |   if (xdg_config)
 82 |   {
 83 |     search_paths.push_back(std::string(xdg_config) + "/arceditor");
 84 |   }
 85 | 
 86 |   const char *home = std::getenv("HOME");
 87 |   if (home)
 88 |   {
 89 |     search_paths.push_back(std::string(home) + "/.config/arceditor");
 90 |   }
 91 | #endif
 92 | 
 93 |   // Development fallback to use ./config/arceditor
 94 |   std::string cwd = fs::current_path().string();
 95 |   std::string dev_config_path = cwd + "/.config/arceditor";
 96 | 
 97 |   if (fs::exists(dev_config_path) && fs::is_directory(dev_config_path))
 98 |   {
 99 |     // Insert the local development path as the highest priority
100 |     search_paths.insert(search_paths.begin(), dev_config_path);
101 |   }
102 | 
103 |   // Find first existing config directory
104 |   for (const auto &path : search_paths)
105 |   {
106 |     if (fs::exists(path) && fs::is_directory(path))
107 |     {
108 |       config_dir_cache_ = path;
109 |       // std::cerr << "Using config directory: " << config_dir_cache_ <<
110 |       // std::endl;
111 |       return config_dir_cache_;
112 |     }
113 |   }
114 | 
115 |   // If no config dir exists, create one in the standard location (first in
116 |   // search_paths)
117 |   if (!search_paths.empty())
118 |   {
119 |     std::string target_dir = search_paths[0];
120 |     try
121 |     {
122 |       fs::create_directories(target_dir);
123 |       config_dir_cache_ = target_dir;
124 |       std::cerr << "Created config directory: " << config_dir_cache_
125 |                 << std::endl;
126 |       return config_dir_cache_;
127 |     }
128 |     catch (const fs::filesystem_error &e)
129 |     {
130 |       std::cerr << "Failed to create config directory: " << e.what()
131 |                 << std::endl;
132 |     }
133 |   }
134 | 
135 |   // Last resort: use current directory
136 |   config_dir_cache_ = cwd;
137 |   std::cerr << "Warning: Using current directory as config dir" << std::endl;
138 |   return config_dir_cache_;
139 | }
140 | 
141 | std::string ConfigManager::getThemesDir() { return getConfigDir() + "/themes"; }
142 | 
143 | std::string ConfigManager::getSyntaxRulesDir()
144 | {
145 |   return getConfigDir() + "/syntax_rules";
146 | }
147 | 
148 | std::string ConfigManager::getConfigFile()
149 | {
150 |   return getConfigDir() + "/config.yaml";
151 | }
152 | 
153 | bool ConfigManager::ensureConfigStructure()
154 | {
155 |   try
156 |   {
157 |     std::string config_dir = getConfigDir();
158 | 
159 |     // Create main config directory (already handled in getConfigDir, but safety
160 |     // check)
161 |     if (!fs::exists(config_dir))
162 |     {
163 |       fs::create_directories(config_dir);
164 |     }
165 | 
166 |     // Create subdirectories
167 |     std::string themes_dir = config_dir + "/themes";
168 |     // std::string syntax_dir = config_dir + "/syntax_rules";
169 | 
170 |     if (!fs::exists(themes_dir))
171 |     {
172 |       fs::create_directories(themes_dir);
173 |       std::cerr << "Created themes directory: " << themes_dir << std::endl;
174 |     }
175 | 
176 |     // if (!fs::exists(syntax_dir))
177 |     // {
178 |     //   fs::create_directories(syntax_dir);
179 |     //   std::cerr << "Created syntax_rules directory: " << syntax_dir
180 |     //             << std::endl;
181 |     // }
182 | 
183 |     // Create default config file if it doesn't exist
184 |     std::string config_file = getConfigFile();
185 |     if (!fs::exists(config_file))
186 |     {
187 |       createDefaultConfig(config_file);
188 |     }
189 | 
190 |     return true;
191 |   }
192 |   catch (const fs::filesystem_error &e)
193 |   {
194 |     std::cerr << "Error ensuring config structure: " << e.what() << std::endl;
195 |     return false;
196 |   }
197 | }
198 | 
199 | // -----------------------------------------------------------------
200 | // CONFIGURATION (YAML) MANAGEMENT
201 | // -----------------------------------------------------------------
202 | 
203 | bool ConfigManager::createDefaultConfig(const std::string &config_file)
204 | {
205 |   try
206 |   {
207 |     YAML::Node config;
208 |     config["appearance"]["theme"] = "default";
209 |     config["editor"]["tab_size"] = 4;
210 |     config["editor"]["line_numbers"] = true;
211 |     config["editor"]["cursor_style"] = "auto";
212 |     config["syntax"]["highlighting"] = "viewport"; // Changed to string
213 | 
214 |     std::ofstream file(config_file);
215 |     if (!file.is_open())
216 |     {
217 |       std::cerr << "Failed to create default config file" << std::endl;
218 |       return false;
219 |     }
220 |     file << "# arceditor Configuration File\n";
221 |     file << "# This file is automatically generated\n\n";
222 |     file << config;
223 |     file.close();
224 | 
225 |     std::cerr << "Created default config: " << config_file << std::endl;
226 |     return true;
227 |   }
228 |   catch (const std::exception &e)
229 |   {
230 |     std::cerr << "Failed to create config: " << e.what() << std::endl;
231 |     return false;
232 |   }
233 | }
234 | 
235 | bool ConfigManager::loadConfig()
236 | {
237 |   std::string config_file = getConfigFile();
238 | 
239 |   if (!fs::exists(config_file))
240 |   {
241 |     std::cerr << "Config file not found, using defaults" << std::endl;
242 |     return false;
243 |   }
244 | 
245 |   try
246 |   {
247 |     YAML::Node config = YAML::LoadFile(config_file);
248 | 
249 |     // Load appearance section
250 |     if (config["appearance"] && config["appearance"]["theme"])
251 |     {
252 |       active_theme_ = config["appearance"]["theme"].as<std::string>();
253 |     }
254 | 
255 |     // Load editor section
256 |     if (config["editor"])
257 |     {
258 |       if (config["editor"]["tab_size"])
259 |       {
260 |         editor_config_.tab_size = config["editor"]["tab_size"].as<int>();
261 |       }
262 |       if (config["editor"]["line_numbers"])
263 |       {
264 |         editor_config_.line_numbers =
265 |             config["editor"]["line_numbers"].as<bool>();
266 |       }
267 |       if (config["editor"]["cursor_style"])
268 |       {
269 |         editor_config_.cursor_style =
270 |             config["editor"]["cursor_style"].as<std::string>();
271 |       }
272 |     }
273 | 
274 |     // Load syntax section
275 |     if (config["syntax"] && config["syntax"]["highlighting"])
276 |     {
277 |       std::string mode_str = config["syntax"]["highlighting"].as<std::string>();
278 |       syntax_config_.highlighting = parseSyntaxMode(mode_str);
279 |     }
280 | 
281 |     return true;
282 |   }
283 |   catch (const YAML::Exception &e)
284 |   {
285 |     std::cerr << "Failed to load config: " << e.what() << std::endl;
286 |     return false;
287 |   }
288 | }
289 | 
290 | bool ConfigManager::saveConfig()
291 | {
292 |   std::string config_file = getConfigFile();
293 |   YAML::Node config;
294 | 
295 |   try
296 |   {
297 |     // Try to load existing config to preserve comments/structure
298 |     config = YAML::LoadFile(config_file);
299 |   }
300 |   catch (const YAML::BadFile &)
301 |   {
302 |     // File doesn't exist, create new structure
303 |   }
304 | 
305 |   // Update all sections
306 |   config["appearance"]["theme"] = active_theme_;
307 |   config["editor"]["tab_size"] = editor_config_.tab_size;
308 |   config["editor"]["line_numbers"] = editor_config_.line_numbers;
309 |   config["editor"]["cursor_style"] = editor_config_.cursor_style;
310 |   config["syntax"]["highlighting"] =
311 |       syntaxModeToString(syntax_config_.highlighting);
312 | 
313 |   try
314 |   {
315 |     std::ofstream file(config_file);
316 |     if (!file.is_open())
317 |     {
318 |       std::cerr << "Failed to open config file for saving" << std::endl;
319 |       return false;
320 |     }
321 |     file << "# arceditor Configuration File\n\n";
322 |     file << config;
323 |     file.close();
324 |     return true;
325 |   }
326 |   catch (const std::exception &e)
327 |   {
328 |     std::cerr << "Failed to save config: " << e.what() << std::endl;
329 |     return false;
330 |   }
331 | }
332 | 
333 | // -----------------------------------------------------------------
334 | // LIVE RELOAD IMPLEMENTATION (EFSW)
335 | // -----------------------------------------------------------------
336 | 
337 | void ConfigManager::registerReloadCallback(ConfigReloadCallback callback)
338 | {
339 |   reload_callbacks_.push_back(callback);
340 | }
341 | 
342 | void ConfigManager::handleFileChange()
343 | {
344 |   std::cerr << "Config file modified. Attempting hot reload..." << std::endl;
345 | 
346 |   // 1. Re-read the configuration file
347 |   if (!loadConfig())
348 |   {
349 |     std::cerr << "Failed to hot reload configuration. File may be invalid."
350 |               << std::endl;
351 |     return;
352 |   }
353 | 
354 |   // 2. Notify all subscribed components
355 |   for (const auto &callback : reload_callbacks_)
356 |   {
357 |     // NOTE: In a multi-threaded app, this should be marshaled to the main
358 |     // thread.
359 |     callback();
360 |   }
361 |   reload_pending_.store(true);
362 |   // std::cerr << "Configuration hot reload complete." << std::endl;
363 | }
364 | 
365 | bool ConfigManager::isReloadPending()
366 | {
367 |   // Atomically check the flag and reset it to false in one operation
368 |   return reload_pending_.exchange(false);
369 | }
370 | 
371 | bool ConfigManager::startWatchingConfig()
372 | {
373 |   // 1. Check if watching is already active
374 |   if (watcher_instance_ && watcher_listener_)
375 |   {
376 |     return true; // Already watching
377 |   }
378 | 
379 |   // 2. Instantiate the FileWatcher and Listener
380 |   try
381 |   {
382 |     // The FileWatcher instance is heavy and should be long-lived
383 |     watcher_instance_ = std::make_unique<efsw::FileWatcher>();
384 | 
385 |     // The listener is the custom class we defined earlier
386 |     watcher_listener_ = std::make_unique<ConfigFileListener>();
387 | 
388 |     // 3. Get the directory to watch (the config directory)
389 |     std::string configDir = getConfigDir();
390 | 
391 |     // 4. Add the watch. The 'true' is for recursive watching,
392 |     // but the listener only cares about 'config.yaml' anyway.
393 |     efsw::WatchID watchID = watcher_instance_->addWatch(
394 |         configDir, watcher_listener_.get(), false // false for non-recursive
395 |     );
396 | 
397 |     if (watchID < 0)
398 |     {
399 |       std::cerr << "Error: EFSW failed to add watch for config directory: "
400 |                 << configDir << std::endl;
401 |       // Cleanup pointers if the watch failed
402 |       watcher_instance_.reset();
403 |       watcher_listener_.reset();
404 |       return false;
405 |     }
406 | 
407 |     // 5. Start the watcher thread
408 |     watcher_instance_->watch(); // This starts the background thread
409 | 
410 |     // std::cerr << "Config watching started for: " << configDir << std::endl;
411 |     return true;
412 |   }
413 |   catch (const std::exception &e)
414 |   {
415 |     std::cerr << "Fatal Error starting EFSW watcher: " << e.what() << std::endl;
416 |     watcher_instance_.reset();
417 |     watcher_listener_.reset();
418 |     return false;
419 |   }
420 | }
421 | 
422 | // -----------------------------------------------------------------
423 | // THEME AND SYNTAX MANAGEMENT
424 | // -----------------------------------------------------------------
425 | 
426 | std::string ConfigManager::getThemeFile(const std::string &theme_name)
427 | {
428 |   std::string themes_dir = getThemesDir();
429 |   std::string theme_file = themes_dir + "/" + theme_name + ".theme";
430 | 
431 |   if (fs::exists(theme_file))
432 |   {
433 |     return theme_file;
434 |   }
435 | 
436 |   // Fallback: Check project's "themes" directory for default files
437 |   std::string dev_theme = "themes/" + theme_name + ".theme";
438 |   if (fs::exists(dev_theme))
439 |   {
440 |     return dev_theme;
441 |   }
442 | 
443 |   std::cerr << "Theme file not found: " << theme_name << std::endl;
444 |   return "";
445 | }
446 | 
447 | std::string ConfigManager::getSyntaxFile(const std::string &language)
448 | {
449 |   std::string syntax_dir = getSyntaxRulesDir();
450 |   std::string syntax_file = syntax_dir + "/" + language + ".yaml";
451 | 
452 |   if (fs::exists(syntax_file))
453 |   {
454 |     return syntax_file;
455 |   }
456 | 
457 |   // Fallback: Check project's "syntax_rules" directory for default files
458 |   std::string dev_syntax = "treesitter/" + language + ".yaml";
459 |   if (fs::exists(dev_syntax))
460 |   {
461 |     return dev_syntax;
462 |   }
463 | 
464 |   return ""; // Not found
465 | }
466 | 
467 | std::string ConfigManager::getActiveTheme() { return active_theme_; }
468 | 
469 | bool ConfigManager::setActiveTheme(const std::string &theme_name)
470 | {
471 |   // Verify theme exists
472 |   std::string theme_file = getThemeFile(theme_name);
473 |   if (theme_file.empty())
474 |   {
475 |     std::cerr << "Cannot set theme, file not found: " << theme_name
476 |               << std::endl;
477 |     return false;
478 |   }
479 | 
480 |   active_theme_ = theme_name;
481 |   saveConfig(); // Persist the change
482 |   return true;
483 | }
484 | 
485 | bool ConfigManager::copyProjectFilesToConfig()
486 | {
487 |   try
488 |   {
489 |     std::string config_dir = getConfigDir();
490 | 
491 |     // Copy themes
492 |     if (fs::exists("themes") && fs::is_directory("themes"))
493 |     {
494 |       std::string target_themes = config_dir + "/themes";
495 |       fs::create_directories(target_themes);
496 | 
497 |       for (const auto &entry : fs::directory_iterator("themes"))
498 |       {
499 |         if (entry.is_regular_file() && entry.path().extension() == ".theme")
500 |         {
501 |           std::string filename = entry.path().filename().string();
502 |           std::string target = target_themes + "/" + filename;
503 | 
504 |           // Only copy if doesn't exist (don't overwrite user themes)
505 |           if (!fs::exists(target))
506 |           {
507 |             fs::copy_file(entry.path(), target);
508 |             std::cerr << "Copied theme: " << filename << std::endl;
509 |           }
510 |         }
511 |       }
512 |     }
513 | 
514 |     // Copy syntax rules
515 |     if (fs::exists("syntax_rules") && fs::is_directory("syntax_rules"))
516 |     {
517 |       std::string target_syntax = config_dir + "/syntax_rules";
518 |       fs::create_directories(target_syntax);
519 | 
520 |       for (const auto &entry : fs::directory_iterator("syntax_rules"))
521 |       {
522 |         if (entry.is_regular_file() && entry.path().extension() == ".yaml")
523 |         {
524 |           std::string filename = entry.path().filename().string();
525 |           std::string target = target_syntax + "/" + filename;
526 | 
527 |           // Only copy if doesn't exist
528 |           if (!fs::exists(target))
529 |           {
530 |             fs::copy_file(entry.path(), target);
531 |             std::cerr << "Copied syntax rule: " << filename << std::endl;
532 |           }
533 |         }
534 |       }
535 |     }
536 | 
537 |     return true;
538 |   }
539 |   catch (const fs::filesystem_error &e)
540 |   {
541 |     std::cerr << "Error copying project files: " << e.what() << std::endl;
542 |     return false;
543 |   }
544 | }
545 | 
546 | SyntaxMode ConfigManager::parseSyntaxMode(const std::string &mode_str)
547 | {
548 |   std::string lower = mode_str;
549 |   std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
550 | 
551 |   if (lower == "none" || lower == "false" || lower == "off")
552 |     return SyntaxMode::NONE;
553 |   else if (lower == "viewport" || lower == "lazy" || lower == "dynamic")
554 |     return SyntaxMode::VIEWPORT;
555 |   else if (lower == "full" || lower == "immediate" || lower == "true")
556 |     return SyntaxMode::FULL;
557 | 
558 |   std::cerr << "Unknown syntax mode '" << mode_str << "', using viewport"
559 |             << std::endl;
560 |   return SyntaxMode::VIEWPORT;
561 | }
562 | 
563 | std::string ConfigManager::syntaxModeToString(SyntaxMode mode)
564 | {
565 |   switch (mode)
566 |   {
567 |   case SyntaxMode::NONE:
568 |     return "none";
569 |   case SyntaxMode::VIEWPORT:
570 |     return "viewport";
571 |   case SyntaxMode::FULL:
572 |     return "full";
573 |   default:
574 |     return "viewport";
575 |   }
576 | }
577 | 
578 | // NEW: Setters
579 | void ConfigManager::setTabSize(int size)
580 | {
581 |   if (size < 1)
582 |     size = 1;
583 |   if (size > 16)
584 |     size = 16;
585 |   editor_config_.tab_size = size;
586 |   saveConfig();
587 | }
588 | 
589 | void ConfigManager::setLineNumbers(bool enabled)
590 | {
591 |   editor_config_.line_numbers = enabled;
592 |   saveConfig();
593 | }
594 | 
595 | void ConfigManager::setCursorStyle(const std::string &style)
596 | {
597 |   editor_config_.cursor_style = style;
598 |   saveConfig();
599 | }
600 | 
601 | void ConfigManager::setSyntaxMode(SyntaxMode mode)
602 | {
603 |   syntax_config_.highlighting = mode;
604 |   saveConfig();
605 | }
```

--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------

```
  1 | cmake_minimum_required(VERSION 3.16)
  2 | project(arc VERSION 0.0.1 LANGUAGES CXX C)
  3 | 
  4 | set(CMAKE_CXX_STANDARD 20)
  5 | set(CMAKE_CXX_STANDARD_REQUIRED ON)
  6 | 
  7 | # Define a central location for external dependencies
  8 | set(DEPS_DIR ${CMAKE_SOURCE_DIR}/deps)
  9 | 
 10 | # Initialize Tree-sitter as enabled by default
 11 | set(TREE_SITTER_ENABLED TRUE)
 12 | 
 13 | # Debug Delta
 14 | # add_compile_definitions(DEBUG_DELTA_UNDO)
 15 | 
 16 | # ----------------------------------------------------
 17 | # 1. Tree-sitter Core Library
 18 | # ----------------------------------------------------
 19 | 
 20 | # Manually list core source files
 21 | set(TS_CORE_SOURCES
 22 |     ${DEPS_DIR}/tree-sitter-core/lib/src/language.c
 23 |     ${DEPS_DIR}/tree-sitter-core/lib/src/lexer.c
 24 |     ${DEPS_DIR}/tree-sitter-core/lib/src/node.c
 25 |     ${DEPS_DIR}/tree-sitter-core/lib/src/parser.c
 26 |     ${DEPS_DIR}/tree-sitter-core/lib/src/query.c
 27 |     ${DEPS_DIR}/tree-sitter-core/lib/src/tree.c
 28 |     ${DEPS_DIR}/tree-sitter-core/lib/src/tree_cursor.c
 29 |     ${DEPS_DIR}/tree-sitter-core/lib/src/alloc.c
 30 |     ${DEPS_DIR}/tree-sitter-core/lib/src/get_changed_ranges.c
 31 |     ${DEPS_DIR}/tree-sitter-core/lib/src/stack.c
 32 |     ${DEPS_DIR}/tree-sitter-core/lib/src/subtree.c
 33 |     ${DEPS_DIR}/tree-sitter-core/lib/src/point.c
 34 |     ${DEPS_DIR}/tree-sitter-core/lib/src/wasm_store.c
 35 | )
 36 | 
 37 | # Check if Tree-sitter headers exist
 38 | if(NOT EXISTS ${DEPS_DIR}/tree-sitter-core/lib/include/tree_sitter/api.h)
 39 |     message(WARNING "Tree-sitter header files not found at ${DEPS_DIR}/tree-sitter-core/lib/include/tree_sitter/api.h")
 40 |     set(TREE_SITTER_ENABLED FALSE)
 41 | endif()
 42 | 
 43 | if(TREE_SITTER_ENABLED)
 44 |     # Verify core files exist
 45 |     set(MISSING_FILES "")
 46 |     foreach(file ${TS_CORE_SOURCES})
 47 |         if(NOT EXISTS ${file})
 48 |             list(APPEND MISSING_FILES ${file})
 49 |         endif()
 50 |     endforeach()
 51 | 
 52 |     if(MISSING_FILES)
 53 |         message(WARNING "Missing Tree-sitter core files: ${MISSING_FILES}")
 54 |         set(TREE_SITTER_ENABLED FALSE)
 55 |     else()
 56 |         message(STATUS "Tree-sitter core sources detected: ${TS_CORE_SOURCES}")
 57 |         message(STATUS "Building Tree-sitter core library.")
 58 | 
 59 |         add_library(tree-sitter-core STATIC ${TS_CORE_SOURCES})
 60 | 
 61 |         # Explicitly set these as C sources
 62 |         set_source_files_properties(${TS_CORE_SOURCES} PROPERTIES LANGUAGE C)
 63 | 
 64 |         # The public API headers are in lib/include/
 65 |         target_include_directories(tree-sitter-core PUBLIC
 66 |             ${DEPS_DIR}/tree-sitter-core/lib/include
 67 |         )
 68 |         # Explicitly set C standard for C files
 69 |         target_compile_features(tree-sitter-core PUBLIC c_std_99)
 70 | 
 71 |         # Force static runtime for Tree-sitter to match main executable
 72 |         if(MSVC)
 73 |             target_compile_options(tree-sitter-core PRIVATE
 74 |                 $<$<CONFIG:Debug>:/MTd>
 75 |                 $<$<CONFIG:Release>:/MT>
 76 |             )
 77 |         endif()
 78 | 
 79 |         set(TS_LIBRARIES tree-sitter-core)
 80 |         set(TS_INCLUDES ${DEPS_DIR}/tree-sitter-core/lib/include)
 81 | 
 82 |         message(STATUS "Tree-sitter enabled with include path: ${TS_INCLUDES}")
 83 |     endif()
 84 | else()
 85 |     message(WARNING "Tree-sitter core sources or headers not found. Disabling Tree-sitter features.")
 86 |     set(TREE_SITTER_ENABLED FALSE)
 87 |     set(TS_LIBRARIES "")
 88 |     set(TS_INCLUDES "")
 89 | endif()
 90 | 
 91 | # ----------------------------------------------------
 92 | # 2. Language Parsers (Auto-Discovery) - FIXED FOR WINDOWS
 93 | # ----------------------------------------------------
 94 | 
 95 | if(TREE_SITTER_ENABLED)
 96 |     message(STATUS "=== Tree-sitter Auto-Discovery ===")
 97 | 
 98 |     # Define parsers - use CMake lists instead of colon-separated strings
 99 |     # Format: list of pairs (lang_name, parser_path)
100 |     set(PARSER_NAMES
101 |         "python" "c" "cpp" "rust" "markdown" "javascript" "typescript" "tsx" "zig" "go"
102 |     )
103 |     set(PARSER_PATHS
104 |         "${DEPS_DIR}/tree-sitter-python"
105 |         "${DEPS_DIR}/tree-sitter-c"
106 |         "${DEPS_DIR}/tree-sitter-cpp"
107 |         "${DEPS_DIR}/tree-sitter-rust"
108 |         "${DEPS_DIR}/tree-sitter-markdown/tree-sitter-markdown"
109 |         "${DEPS_DIR}/tree-sitter-javascript"
110 |         "${DEPS_DIR}/tree-sitter-typescript/typescript"
111 |         "${DEPS_DIR}/tree-sitter-typescript/tsx"
112 |         "${DEPS_DIR}/tree-sitter-zig"
113 |         "${DEPS_DIR}/tree-sitter-go"
114 |     )
115 | 
116 |     set(DISCOVERED_PARSERS "")
117 | 
118 |     # Iterate using indices
119 |     list(LENGTH PARSER_NAMES parser_count)
120 |     math(EXPR parser_count "${parser_count} - 1")
121 | 
122 |     foreach(i RANGE ${parser_count})
123 |         list(GET PARSER_NAMES ${i} lang_name)
124 |         list(GET PARSER_PATHS ${i} parser_dir)
125 | 
126 |         if(NOT EXISTS ${parser_dir})
127 |             message(STATUS "  ✗ Skipping ${lang_name}: directory not found at ${parser_dir}")
128 |             continue()
129 |         endif()
130 | 
131 |         # Collect source files
132 |         set(PARSER_SOURCES "")
133 | 
134 |         # Check for parser.c (required)
135 |         if(EXISTS ${parser_dir}/src/parser.c)
136 |             list(APPEND PARSER_SOURCES ${parser_dir}/src/parser.c)
137 |         else()
138 |             message(STATUS "  ✗ Skipping ${lang_name}: no parser.c at ${parser_dir}/src/")
139 |             continue()
140 |         endif()
141 | 
142 |         # Check for scanner files (optional)
143 |         if(EXISTS ${parser_dir}/src/scanner.c)
144 |             list(APPEND PARSER_SOURCES ${parser_dir}/src/scanner.c)
145 |         endif()
146 | 
147 |         if(EXISTS ${parser_dir}/src/scanner.cc)
148 |             list(APPEND PARSER_SOURCES ${parser_dir}/src/scanner.cc)
149 |         endif()
150 | 
151 |         # Create library
152 |         add_library(tree-sitter-${lang_name} STATIC ${PARSER_SOURCES})
153 | 
154 |         # Set as C sources
155 |         set_source_files_properties(${PARSER_SOURCES} PROPERTIES LANGUAGE C)
156 | 
157 |         # Set C99 standard
158 |         target_compile_features(tree-sitter-${lang_name} PUBLIC c_std_99)
159 | 
160 |         # Force static runtime for parsers to match main executable
161 |         if(MSVC)
162 |             target_compile_options(tree-sitter-${lang_name} PRIVATE
163 |                 $<$<CONFIG:Debug>:/MTd>
164 |                 $<$<CONFIG:Release>:/MT>
165 |             )
166 |         endif()
167 | 
168 |         # Include directories for scanner files
169 |         target_include_directories(tree-sitter-${lang_name} PRIVATE
170 |             ${parser_dir}/src
171 |         )
172 | 
173 |         # Add to libraries list
174 |         list(APPEND TS_LIBRARIES tree-sitter-${lang_name})
175 |         list(APPEND DISCOVERED_PARSERS ${lang_name})
176 | 
177 |         message(STATUS "  ✓ Built parser: ${lang_name}")
178 |     endforeach()
179 | 
180 |     # ----------------------------------------------------
181 |     # 3. Generate Language Registry Header (Auto-registration)
182 |     # ----------------------------------------------------
183 | 
184 |     if(DISCOVERED_PARSERS)
185 |         set(LANG_REGISTRY_FILE "${CMAKE_BINARY_DIR}/generated/language_registry.h")
186 |         file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/generated")
187 | 
188 |         # Build the header file content
189 |         set(REGISTRY_CONTENT "// Auto-generated by CMake - DO NOT EDIT MANUALLY\n")
190 |         string(APPEND REGISTRY_CONTENT "// Generated from: ${CMAKE_CURRENT_LIST_FILE}\n\n")
191 |         string(APPEND REGISTRY_CONTENT "#pragma once\n\n")
192 |         string(APPEND REGISTRY_CONTENT "#ifdef TREE_SITTER_ENABLED\n\n")
193 |         string(APPEND REGISTRY_CONTENT "#include <tree_sitter/api.h>\n")
194 |         string(APPEND REGISTRY_CONTENT "#include <unordered_map>\n")
195 |         string(APPEND REGISTRY_CONTENT "#include <string>\n\n")
196 | 
197 |         # Extern declarations for all discovered languages
198 |         string(APPEND REGISTRY_CONTENT "// External language function declarations\n")
199 |         string(APPEND REGISTRY_CONTENT "extern \"C\" {\n")
200 |         foreach(lang ${DISCOVERED_PARSERS})
201 |             string(APPEND REGISTRY_CONTENT "  const TSLanguage *tree_sitter_${lang}();\n")
202 |         endforeach()
203 |         string(APPEND REGISTRY_CONTENT "}\n\n")
204 | 
205 |         # Registration function
206 |         string(APPEND REGISTRY_CONTENT "// Auto-register all available languages\n")
207 |         string(APPEND REGISTRY_CONTENT "inline void registerAllLanguages(std::unordered_map<std::string, const TSLanguage* (*)()>& registry) {\n")
208 |         foreach(lang ${DISCOVERED_PARSERS})
209 |             string(APPEND REGISTRY_CONTENT "  registry[\"${lang}\"] = tree_sitter_${lang};\n")
210 |         endforeach()
211 |         string(APPEND REGISTRY_CONTENT "}\n\n")
212 | 
213 |         # List of available languages as a comment
214 |         string(APPEND REGISTRY_CONTENT "// Available languages: ")
215 |         string(JOIN DISCOVERED_PARSERS ", " LANG_LIST)
216 |         string(APPEND REGISTRY_CONTENT "${LANG_LIST}\n\n")
217 | 
218 |         string(APPEND REGISTRY_CONTENT "#endif // TREE_SITTER_ENABLED\n")
219 | 
220 |         # Write the file
221 |         file(WRITE ${LANG_REGISTRY_FILE} "${REGISTRY_CONTENT}")
222 | 
223 |         message(STATUS "Generated language registry: ${LANG_REGISTRY_FILE}")
224 |         message(STATUS "  Registered parsers: ${DISCOVERED_PARSERS}")
225 |     else()
226 |         message(WARNING "No parsers discovered, skipping registry generation")
227 |         set(TREE_SITTER_ENABLED FALSE)
228 |     endif()
229 | 
230 |     message(STATUS "=== End Tree-sitter Auto-Discovery ===")
231 | 
232 | endif()
233 | 
234 | if(NOT TREE_SITTER_ENABLED)
235 |     message(STATUS "Tree-sitter disabled - using fallback syntax highlighting")
236 |     set(TS_LIBRARIES "")
237 |     set(TS_INCLUDES "")
238 | endif()
239 | 
240 | # ----------------------------------------------------
241 | # 4. EFSW (Event File System Watcher) - For Live Reloading
242 | # ----------------------------------------------------
243 | set(EFSW_BASE_DIR ${DEPS_DIR}/efsw)
244 | set(EFSW_SOURCES "")
245 | 
246 | # List ALL required core files from the flattened structure (src/efsw/)
247 | list(APPEND EFSW_SOURCES
248 |     # Core Files (Unconditional)
249 |     ${EFSW_BASE_DIR}/src/efsw/Debug.cpp
250 |     ${EFSW_BASE_DIR}/src/efsw/DirWatcherGeneric.cpp
251 |     ${EFSW_BASE_DIR}/src/efsw/DirectorySnapshot.cpp
252 |     ${EFSW_BASE_DIR}/src/efsw/DirectorySnapshotDiff.cpp
253 |     ${EFSW_BASE_DIR}/src/efsw/FileInfo.cpp
254 |     ${EFSW_BASE_DIR}/src/efsw/FileSystem.cpp
255 |     ${EFSW_BASE_DIR}/src/efsw/FileWatcher.cpp
256 |     ${EFSW_BASE_DIR}/src/efsw/FileWatcherCWrapper.cpp
257 |     ${EFSW_BASE_DIR}/src/efsw/FileWatcherImpl.cpp
258 |     ${EFSW_BASE_DIR}/src/efsw/Log.cpp
259 |     ${EFSW_BASE_DIR}/src/efsw/String.cpp
260 |     ${EFSW_BASE_DIR}/src/efsw/System.cpp
261 |     ${EFSW_BASE_DIR}/src/efsw/Watcher.cpp
262 | 
263 |     # CRITICAL FIX: Add Generic implementation for default constructors
264 |     ${EFSW_BASE_DIR}/src/efsw/FileWatcherGeneric.cpp
265 |     ${EFSW_BASE_DIR}/src/efsw/WatcherGeneric.cpp
266 | )
267 | 
268 | # Conditionally add the correct platform backend
269 | if(CMAKE_SYSTEM_NAME MATCHES "Linux")
270 |     list(APPEND EFSW_SOURCES
271 |         ${EFSW_BASE_DIR}/src/efsw/FileWatcherInotify.cpp
272 |         ${EFSW_BASE_DIR}/src/efsw/WatcherInotify.cpp
273 |         ${EFSW_BASE_DIR}/src/efsw/platform/posix/FileSystemImpl.cpp
274 |         ${EFSW_BASE_DIR}/src/efsw/platform/posix/SystemImpl.cpp
275 |     )
276 | elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin") # macOS
277 |     list(APPEND EFSW_SOURCES
278 |         ${EFSW_BASE_DIR}/src/efsw/FileWatcherFSEvents.cpp
279 |         ${EFSW_BASE_DIR}/src/efsw/WatcherFSEvents.cpp
280 |         ${EFSW_BASE_DIR}/src/efsw/platform/posix/FileSystemImpl.cpp
281 |         ${EFSW_BASE_DIR}/src/efsw/platform/posix/SystemImpl.cpp
282 |     )
283 | elseif(WIN32)
284 |     list(APPEND EFSW_SOURCES
285 |         ${EFSW_BASE_DIR}/src/efsw/FileWatcherWin32.cpp
286 |         ${EFSW_BASE_DIR}/src/efsw/WatcherWin32.cpp
287 |         ${EFSW_BASE_DIR}/src/efsw/platform/win/FileSystemImpl.cpp
288 |         ${EFSW_BASE_DIR}/src/efsw/platform/win/SystemImpl.cpp
289 |     )
290 | else()
291 |     list(APPEND EFSW_SOURCES
292 |         ${EFSW_BASE_DIR}/src/efsw/FileWatcherGeneric.cpp
293 |         ${EFSW_BASE_DIR}/src/efsw/WatcherGeneric.cpp
294 |     )
295 | endif()
296 | 
297 | if(EFSW_SOURCES)
298 |     add_library(efsw STATIC ${EFSW_SOURCES})
299 | 
300 |     target_include_directories(efsw PUBLIC
301 |         ${EFSW_BASE_DIR}/include
302 |         ${EFSW_BASE_DIR}/src
303 |     )
304 | 
305 |     # Force static runtime for EFSW to match main executable
306 |     if(MSVC)
307 |         target_compile_options(efsw PRIVATE
308 |             $<$<CONFIG:Debug>:/MTd>
309 |             $<$<CONFIG:Release>:/MT>
310 |         )
311 |     endif()
312 | 
313 |     find_package(Threads REQUIRED)
314 |     target_link_libraries(efsw PRIVATE Threads::Threads)
315 | 
316 |     if(CMAKE_SYSTEM_NAME MATCHES "Linux")
317 |         target_link_libraries(efsw PRIVATE rt)
318 |     elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")
319 |         target_link_libraries(efsw PRIVATE CoreServices)
320 |     endif()
321 | 
322 |     message(STATUS "Added EFSW from deps folder.")
323 |     set(EFSW_LIBRARIES efsw)
324 | else()
325 |     message(WARNING "EFSW sources missing. Live reload feature disabled.")
326 |     set(EFSW_LIBRARIES "")
327 | endif()
328 | 
329 | # ----------------------------------------------------
330 | # 5. Platform-Specific Settings
331 | # ----------------------------------------------------
332 | 
333 | # Windows-specific optimizations
334 | if(WIN32)
335 |     set(VCPKG_APPLOCAL_DEPS OFF)
336 |     set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF)
337 |     set(CMAKE_COLOR_MAKEFILE OFF)
338 | 
339 |     if(MINGW)
340 |         set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++ -static")
341 |     endif()
342 | 
343 |     if(MSVC)
344 |         set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
345 |     endif()
346 | endif()
347 | 
348 | # Find packages - platform-specific approach
349 | if(WIN32)
350 |     set(PDCURSESMOD_DIR ${DEPS_DIR}/PDCursesMod/wincon)
351 | 
352 |     if(EXISTS "${PDCURSESMOD_DIR}/pdcurses.lib")
353 |         message(STATUS "Using locally built PDCursesMod from ${PDCURSESMOD_DIR}")
354 | 
355 |         add_library(pdcursesmod STATIC IMPORTED)
356 |         set_target_properties(pdcursesmod PROPERTIES
357 |             IMPORTED_LOCATION "${PDCURSESMOD_DIR}/pdcurses.lib"
358 |             INTERFACE_INCLUDE_DIRECTORIES "${DEPS_DIR}/PDCursesMod"
359 |         )
360 | 
361 |         set(CURSES_LIBRARIES pdcursesmod)
362 |         set(CURSES_INCLUDE_DIRS "${DEPS_DIR}/PDCursesMod")
363 |     else()
364 |         message(FATAL_ERROR "PDCursesMod not found at ${PDCURSESMOD_DIR}. Please build it manually with nmake -f Makefile.vc HAVE_VT=Y")
365 |     endif()
366 | 
367 | elseif(ANDROID)
368 |     find_package(PkgConfig REQUIRED)
369 |     pkg_check_modules(NCURSES REQUIRED ncurses)
370 |     set(CURSES_LIBRARIES ${NCURSES_LIBRARIES})
371 |     set(CURSES_INCLUDE_DIRS ${NCURSES_INCLUDE_DIRS})
372 | 
373 | else()
374 |     find_package(Curses REQUIRED)
375 |     set(CURSES_LIBRARIES ${CURSES_LIBRARIES})
376 |     set(CURSES_INCLUDE_DIRS ${CURSES_INCLUDE_DIR})
377 | endif()
378 | 
379 | find_package(yaml-cpp CONFIG REQUIRED)
380 | 
381 | # ----------------------------------------------------
382 | # 6. Main Executable
383 | # ----------------------------------------------------
384 | 
385 | # Source files
386 | set(SOURCES
387 |     src/main.cpp
388 |     src/core/editor.cpp
389 |     src/core/buffer.cpp
390 |     src/core/config_manager.cpp
391 |     src/ui/input_handler.cpp
392 |     # src/ui/renderer.cpp
393 |     src/ui/style_manager.cpp
394 |     src/features/syntax_config_loader.cpp
395 |     src/features/syntax_highlighter.cpp
396 | )
397 | 
398 | # Create executable
399 | add_executable(arc ${SOURCES})
400 | 
401 | # Conditionally add Tree-sitter compile definition
402 | if(TREE_SITTER_ENABLED)
403 |     target_compile_definitions(arc PRIVATE TREE_SITTER_ENABLED)
404 |     message(STATUS "Compiling with Tree-sitter support enabled")
405 | else()
406 |     message(STATUS "Compiling without Tree-sitter support")
407 | endif()
408 | 
409 | # Link libraries
410 | target_link_libraries(arc PRIVATE
411 |     yaml-cpp::yaml-cpp
412 |     ${CURSES_LIBRARIES}
413 |     ${TS_LIBRARIES}
414 |     ${EFSW_LIBRARIES}
415 | )
416 | 
417 | if(WIN32)
418 |     # Link the Windows Multimedia library required by PDCursesMod's beep()
419 |     target_link_libraries(arc PRIVATE winmm)
420 | endif()
421 | 
422 | # Include directories
423 | target_include_directories(arc PRIVATE
424 |     .
425 |     ${CURSES_INCLUDE_DIRS}
426 |     ${TS_INCLUDES}
427 | )
428 | 
429 | # Add generated headers directory if Tree-sitter is enabled
430 | if(TREE_SITTER_ENABLED)
431 |     target_include_directories(arc PRIVATE ${CMAKE_BINARY_DIR}/generated)
432 | endif()
433 | 
434 | # Compiler flags with optimizations
435 | if(MSVC)
436 |     target_compile_options(arc PRIVATE /W4 /MP)
437 |     target_compile_options(arc PRIVATE $<$<CONFIG:Debug>:/MTd> $<$<CONFIG:Release>:/MT>)
438 | else()
439 |     target_compile_options(arc PRIVATE -Wall -Wextra)
440 | 
441 |     if(ANDROID)
442 |         target_link_libraries(arc PRIVATE ${NCURSES_LINK_LIBRARIES})
443 |     endif()
444 | 
445 |     if(CMAKE_BUILD_TYPE STREQUAL "Debug")
446 |         target_compile_options(arc PRIVATE -g1 -O0 -fno-omit-frame-pointer)
447 |     else()
448 |         target_compile_options(arc PRIVATE -O2)
449 |     endif()
450 | endif()
451 | 
452 | # ----------------------------------------------------
453 | # 7. Build Summary
454 | # ----------------------------------------------------
455 | 
456 | message(STATUS "")
457 | message(STATUS "========================================")
458 | message(STATUS "Arc Editor Build Configuration")
459 | message(STATUS "========================================")
460 | message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
461 | message(STATUS "Compiler: ${CMAKE_CXX_COMPILER_ID}")
462 | message(STATUS "Platform: ${CMAKE_SYSTEM_NAME}")
463 | message(STATUS "Curses library: ${CURSES_LIBRARIES}")
464 | message(STATUS "Tree-sitter enabled: ${TREE_SITTER_ENABLED}")
465 | if(TREE_SITTER_ENABLED)
466 |     message(STATUS "  Tree-sitter libraries: ${TS_LIBRARIES}")
467 |     message(STATUS "  Tree-sitter includes: ${TS_INCLUDES}")
468 |     message(STATUS "  Discovered parsers: ${DISCOVERED_PARSERS}")
469 | endif()
470 | message(STATUS "EFSW enabled: ${EFSW_LIBRARIES}")
471 | if(WIN32)
472 |     message(STATUS "VCPKG_APPLOCAL_DEPS: ${VCPKG_APPLOCAL_DEPS}")
473 | endif()
474 | message(STATUS "========================================")
475 | message(STATUS "")
476 | 
```

--------------------------------------------------------------------------------
/deps/tree-sitter-markdown/bindings/rust/parser.rs:
--------------------------------------------------------------------------------

```rust
  1 | use std::collections::HashMap;
  2 | use std::num::NonZeroU16;
  3 | 
  4 | use tree_sitter::{InputEdit, Language, Node, Parser, Point, Range, Tree, TreeCursor};
  5 | 
  6 | use crate::{INLINE_LANGUAGE, LANGUAGE};
  7 | 
  8 | /// A parser that produces [`MarkdownTree`]s.
  9 | ///
 10 | /// This is a convenience wrapper around [`LANGUAGE`] and [`INLINE_LANGUAGE`].
 11 | pub struct MarkdownParser {
 12 |     parser: Parser,
 13 |     block_language: Language,
 14 |     inline_language: Language,
 15 | }
 16 | 
 17 | /// A stateful object for walking a [`MarkdownTree`] efficiently.
 18 | ///
 19 | /// This exposes the same methdos as [`TreeCursor`], but abstracts away the
 20 | /// double block / inline structure of [`MarkdownTree`].
 21 | pub struct MarkdownCursor<'a> {
 22 |     markdown_tree: &'a MarkdownTree,
 23 |     block_cursor: TreeCursor<'a>,
 24 |     inline_cursor: Option<TreeCursor<'a>>,
 25 | }
 26 | 
 27 | impl<'a> MarkdownCursor<'a> {
 28 |     /// Get the cursor's current [`Node`].
 29 |     pub fn node(&self) -> Node<'a> {
 30 |         match &self.inline_cursor {
 31 |             Some(cursor) => cursor.node(),
 32 |             None => self.block_cursor.node(),
 33 |         }
 34 |     }
 35 | 
 36 |     /// Returns `true` if the current node is from the (inline language)[INLINE_LANGUAGE]
 37 |     ///
 38 |     /// This information is needed to handle "tree-sitter internal" data like
 39 |     /// [`field_id`](Self::field_id) correctly.
 40 |     pub fn is_inline(&self) -> bool {
 41 |         self.inline_cursor.is_some()
 42 |     }
 43 | 
 44 |     /// Get the numerical field id of this tree cursor’s current node.
 45 |     ///
 46 |     /// You will need to call [`is_inline`](Self::is_inline) to find out if the
 47 |     /// current node is an inline or block node.
 48 |     ///
 49 |     /// See also [`field_name`](Self::field_name).
 50 |     pub fn field_id(&self) -> Option<NonZeroU16> {
 51 |         match &self.inline_cursor {
 52 |             Some(cursor) => cursor.field_id(),
 53 |             None => self.block_cursor.field_id(),
 54 |         }
 55 |     }
 56 | 
 57 |     /// Get the field name of this tree cursor’s current node.
 58 |     ///
 59 |     /// You will need to call [`is_inline`](Self::is_inline) to find out if the
 60 |     /// current node is an inline or block node.
 61 |     pub fn field_name(&self) -> Option<&'static str> {
 62 |         match &self.inline_cursor {
 63 |             Some(cursor) => cursor.field_name(),
 64 |             None => self.block_cursor.field_name(),
 65 |         }
 66 |     }
 67 | 
 68 |     fn move_to_inline_tree(&mut self) -> bool {
 69 |         let node = self.block_cursor.node();
 70 |         match node.kind() {
 71 |             "inline" | "pipe_table_cell" => {
 72 |                 if let Some(inline_tree) = self.markdown_tree.inline_tree(&node) {
 73 |                     self.inline_cursor = Some(inline_tree.walk());
 74 |                     return true;
 75 |                 }
 76 |             }
 77 |             _ => (),
 78 |         }
 79 |         false
 80 |     }
 81 | 
 82 |     fn move_to_block_tree(&mut self) {
 83 |         self.inline_cursor = None;
 84 |     }
 85 | 
 86 |     /// Move this cursor to the first child of its current node.
 87 |     ///
 88 |     /// This returns `true` if the cursor successfully moved, and returns `false` if there were no
 89 |     /// children.
 90 |     /// If the cursor is currently at a node in the block tree and it has an associated inline tree, it
 91 |     /// will descend into the inline tree.
 92 |     pub fn goto_first_child(&mut self) -> bool {
 93 |         match &mut self.inline_cursor {
 94 |             Some(cursor) => cursor.goto_first_child(),
 95 |             None => {
 96 |                 if self.move_to_inline_tree() {
 97 |                     if !self.inline_cursor.as_mut().unwrap().goto_first_child() {
 98 |                         self.move_to_block_tree();
 99 |                         false
100 |                     } else {
101 |                         true
102 |                     }
103 |                 } else {
104 |                     self.block_cursor.goto_first_child()
105 |                 }
106 |             }
107 |         }
108 |     }
109 | 
110 |     /// Move this cursor to the parent of its current node.
111 |     ///
112 |     /// This returns true if the cursor successfully moved, and returns false if there was no
113 |     /// parent node (the cursor was already on the root node).
114 |     /// If the cursor moves to the root node of an inline tree, the it ascents to the associated
115 |     /// node in the block tree.
116 |     pub fn goto_parent(&mut self) -> bool {
117 |         match &mut self.inline_cursor {
118 |             Some(inline_cursor) => {
119 |                 inline_cursor.goto_parent();
120 |                 if inline_cursor.node().parent().is_none() {
121 |                     self.move_to_block_tree();
122 |                 }
123 |                 true
124 |             }
125 |             None => self.block_cursor.goto_parent(),
126 |         }
127 |     }
128 | 
129 |     /// Move this cursor to the next sibling of its current node.
130 |     ///
131 |     /// This returns true if the cursor successfully moved, and returns false if there was no next
132 |     /// sibling node.
133 |     pub fn goto_next_sibling(&mut self) -> bool {
134 |         match &mut self.inline_cursor {
135 |             Some(inline_cursor) => inline_cursor.goto_next_sibling(),
136 |             None => self.block_cursor.goto_next_sibling(),
137 |         }
138 |     }
139 | 
140 |     /// Move this cursor to the first child of its current node that extends beyond the given byte offset.
141 |     ///
142 |     /// This returns the index of the child node if one was found, and returns None if no such child was found.
143 |     /// If the cursor is currently at a node in the block tree and it has an associated inline tree, it
144 |     /// will descend into the inline tree.
145 |     pub fn goto_first_child_for_byte(&mut self, index: usize) -> Option<usize> {
146 |         match &mut self.inline_cursor {
147 |             Some(cursor) => cursor.goto_first_child_for_byte(index),
148 |             None => {
149 |                 if self.move_to_inline_tree() {
150 |                     self.inline_cursor
151 |                         .as_mut()
152 |                         .unwrap()
153 |                         .goto_first_child_for_byte(index)
154 |                 } else {
155 |                     self.block_cursor.goto_first_child_for_byte(index)
156 |                 }
157 |             }
158 |         }
159 |     }
160 | 
161 |     /// Move this cursor to the first child of its current node that extends beyond the given point.
162 |     ///
163 |     /// This returns the index of the child node if one was found, and returns None if no such child was found.
164 |     /// If the cursor is currently at a node in the block tree and it has an associated inline tree, it
165 |     /// will descend into the inline tree.
166 |     pub fn goto_first_child_for_point(&mut self, index: Point) -> Option<usize> {
167 |         match &mut self.inline_cursor {
168 |             Some(cursor) => cursor.goto_first_child_for_point(index),
169 |             None => {
170 |                 if self.move_to_inline_tree() {
171 |                     self.inline_cursor
172 |                         .as_mut()
173 |                         .unwrap()
174 |                         .goto_first_child_for_point(index)
175 |                 } else {
176 |                     self.block_cursor.goto_first_child_for_point(index)
177 |                 }
178 |             }
179 |         }
180 |     }
181 | }
182 | 
183 | /// An object that holds a combined markdown tree.
184 | #[derive(Debug, Clone)]
185 | pub struct MarkdownTree {
186 |     block_tree: Tree,
187 |     inline_trees: Vec<Tree>,
188 |     inline_indices: HashMap<usize, usize>,
189 | }
190 | 
191 | impl MarkdownTree {
192 |     /// Edit the block tree and inline trees to keep them in sync with source code that has been
193 |     /// edited.
194 |     ///
195 |     /// You must describe the edit both in terms of byte offsets and in terms of
196 |     /// row/column coordinates.
197 |     pub fn edit(&mut self, edit: &InputEdit) {
198 |         self.block_tree.edit(edit);
199 |         for inline_tree in self.inline_trees.iter_mut() {
200 |             inline_tree.edit(edit);
201 |         }
202 |     }
203 | 
204 |     /// Returns the block tree for the parsed document
205 |     pub fn block_tree(&self) -> &Tree {
206 |         &self.block_tree
207 |     }
208 | 
209 |     /// Returns the inline tree for the given inline node.
210 |     ///
211 |     /// Returns `None` if the given node does not have an associated inline tree. Either because
212 |     /// the nodes type is not `inline` or because the inline content is empty.
213 |     pub fn inline_tree(&self, parent: &Node) -> Option<&Tree> {
214 |         let index = *self.inline_indices.get(&parent.id())?;
215 |         Some(&self.inline_trees[index])
216 |     }
217 | 
218 |     /// Returns the list of all inline trees
219 |     pub fn inline_trees(&self) -> &[Tree] {
220 |         &self.inline_trees
221 |     }
222 | 
223 |     /// Create a new [`MarkdownCursor`] starting from the root of the tree.
224 |     pub fn walk(&self) -> MarkdownCursor {
225 |         MarkdownCursor {
226 |             markdown_tree: self,
227 |             block_cursor: self.block_tree.walk(),
228 |             inline_cursor: None,
229 |         }
230 |     }
231 | }
232 | 
233 | impl Default for MarkdownParser {
234 |     fn default() -> Self {
235 |         let block_language = LANGUAGE.into();
236 |         let inline_language = INLINE_LANGUAGE.into();
237 |         let parser = Parser::new();
238 |         MarkdownParser {
239 |             parser,
240 |             block_language,
241 |             inline_language,
242 |         }
243 |     }
244 | }
245 | 
246 | impl MarkdownParser {
247 |     /// Parse a slice of UTF8 text.
248 |     ///
249 |     /// # Arguments:
250 |     /// * `text` The UTF8-encoded text to parse.
251 |     /// * `old_tree` A previous syntax tree parsed from the same document.
252 |     ///   If the text of the document has changed since `old_tree` was
253 |     ///   created, then you must edit `old_tree` to match the new text using
254 |     ///   [MarkdownTree::edit].
255 |     ///
256 |     /// Returns a [MarkdownTree] if parsing succeeded, or `None` if:
257 |     ///  * The timeout set with [tree_sitter::Parser::set_timeout_micros] expired
258 |     ///  * The cancellation flag set with [tree_sitter::Parser::set_cancellation_flag] was flipped
259 |     pub fn parse_with<T: AsRef<[u8]>, F: FnMut(usize, Point) -> T>(
260 |         &mut self,
261 |         callback: &mut F,
262 |         old_tree: Option<&MarkdownTree>,
263 |     ) -> Option<MarkdownTree> {
264 |         let MarkdownParser {
265 |             parser,
266 |             block_language,
267 |             inline_language,
268 |         } = self;
269 |         parser
270 |             .set_included_ranges(&[])
271 |             .expect("Can not set included ranges to whole document");
272 |         parser
273 |             .set_language(block_language)
274 |             .expect("Could not load block grammar");
275 |         let block_tree = parser.parse_with(callback, old_tree.map(|tree| &tree.block_tree))?;
276 |         let (mut inline_trees, mut inline_indices) = if let Some(old_tree) = old_tree {
277 |             let len = old_tree.inline_trees.len();
278 |             (Vec::with_capacity(len), HashMap::with_capacity(len))
279 |         } else {
280 |             (Vec::new(), HashMap::new())
281 |         };
282 |         parser
283 |             .set_language(inline_language)
284 |             .expect("Could not load inline grammar");
285 |         let mut tree_cursor = block_tree.walk();
286 | 
287 |         let mut i = 0;
288 |         'outer: loop {
289 |             let node = loop {
290 |                 let kind = tree_cursor.node().kind();
291 |                 if kind == "inline" || kind == "pipe_table_cell" || !tree_cursor.goto_first_child()
292 |                 {
293 |                     while !tree_cursor.goto_next_sibling() {
294 |                         if !tree_cursor.goto_parent() {
295 |                             break 'outer;
296 |                         }
297 |                     }
298 |                 }
299 |                 let kind = tree_cursor.node().kind();
300 |                 if kind == "inline" || kind == "pipe_table_cell" {
301 |                     break tree_cursor.node();
302 |                 }
303 |             };
304 |             let mut range = node.range();
305 |             let mut ranges = Vec::new();
306 |             if tree_cursor.goto_first_child() {
307 |                 while tree_cursor.goto_next_sibling() {
308 |                     if !tree_cursor.node().is_named() {
309 |                         continue;
310 |                     }
311 |                     let child_range = tree_cursor.node().range();
312 |                     ranges.push(Range {
313 |                         start_byte: range.start_byte,
314 |                         start_point: range.start_point,
315 |                         end_byte: child_range.start_byte,
316 |                         end_point: child_range.start_point,
317 |                     });
318 |                     range.start_byte = child_range.end_byte;
319 |                     range.start_point = child_range.end_point;
320 |                 }
321 |                 tree_cursor.goto_parent();
322 |             }
323 |             ranges.push(range);
324 |             parser.set_included_ranges(&ranges).ok()?;
325 |             let inline_tree = parser.parse_with(
326 |                 callback,
327 |                 old_tree.and_then(|old_tree| old_tree.inline_trees.get(i)),
328 |             )?;
329 |             inline_trees.push(inline_tree);
330 |             inline_indices.insert(node.id(), i);
331 |             i += 1;
332 |         }
333 |         drop(tree_cursor);
334 |         inline_trees.shrink_to_fit();
335 |         inline_indices.shrink_to_fit();
336 |         Some(MarkdownTree {
337 |             block_tree,
338 |             inline_trees,
339 |             inline_indices,
340 |         })
341 |     }
342 | 
343 |     /// Parse a slice of UTF8 text.
344 |     ///
345 |     /// # Arguments:
346 |     /// * `text` The UTF8-encoded text to parse.
347 |     /// * `old_tree` A previous syntax tree parsed from the same document.
348 |     ///   If the text of the document has changed since `old_tree` was
349 |     ///   created, then you must edit `old_tree` to match the new text using
350 |     ///   [MarkdownTree::edit].
351 |     ///
352 |     /// Returns a [MarkdownTree] if parsing succeeded, or `None` if:
353 |     ///  * The timeout set with [tree_sitter::Parser::set_timeout_micros] expired
354 |     ///  * The cancellation flag set with [tree_sitter::Parser::set_cancellation_flag] was flipped
355 |     pub fn parse(&mut self, text: &[u8], old_tree: Option<&MarkdownTree>) -> Option<MarkdownTree> {
356 |         self.parse_with(&mut |byte, _| &text[byte..], old_tree)
357 |     }
358 | }
359 | 
360 | #[cfg(test)]
361 | mod tests {
362 |     use tree_sitter::{InputEdit, Point};
363 | 
364 |     use super::*;
365 | 
366 |     #[test]
367 |     fn inline_ranges() {
368 |         let code = "# title\n\nInline [content].\n";
369 |         let mut parser = MarkdownParser::default();
370 |         let mut tree = parser.parse(code.as_bytes(), None).unwrap();
371 | 
372 |         let section = tree.block_tree().root_node().child(0).unwrap();
373 |         assert_eq!(section.kind(), "section");
374 |         let heading = section.child(0).unwrap();
375 |         assert_eq!(heading.kind(), "atx_heading");
376 |         let paragraph = section.child(1).unwrap();
377 |         assert_eq!(paragraph.kind(), "paragraph");
378 |         let inline = paragraph.child(0).unwrap();
379 |         assert_eq!(inline.kind(), "inline");
380 |         assert_eq!(
381 |             tree.inline_tree(&inline)
382 |                 .unwrap()
383 |                 .root_node()
384 |                 .child(0)
385 |                 .unwrap()
386 |                 .kind(),
387 |             "shortcut_link"
388 |         );
389 | 
390 |         let code = "# Title\n\nInline [content].\n";
391 |         tree.edit(&InputEdit {
392 |             start_byte: 2,
393 |             old_end_byte: 3,
394 |             new_end_byte: 3,
395 |             start_position: Point { row: 0, column: 2 },
396 |             old_end_position: Point { row: 0, column: 3 },
397 |             new_end_position: Point { row: 0, column: 3 },
398 |         });
399 |         let tree = parser.parse(code.as_bytes(), Some(&tree)).unwrap();
400 | 
401 |         let section = tree.block_tree().root_node().child(0).unwrap();
402 |         assert_eq!(section.kind(), "section");
403 |         let heading = section.child(0).unwrap();
404 |         assert_eq!(heading.kind(), "atx_heading");
405 |         let paragraph = section.child(1).unwrap();
406 |         assert_eq!(paragraph.kind(), "paragraph");
407 |         let inline = paragraph.child(0).unwrap();
408 |         assert_eq!(inline.kind(), "inline");
409 |         assert_eq!(
410 |             tree.inline_tree(&inline)
411 |                 .unwrap()
412 |                 .root_node()
413 |                 .named_child(0)
414 |                 .unwrap()
415 |                 .kind(),
416 |             "shortcut_link"
417 |         );
418 |     }
419 | 
420 |     #[test]
421 |     fn markdown_cursor() {
422 |         let code = "# title\n\nInline [content].\n";
423 |         let mut parser = MarkdownParser::default();
424 |         let tree = parser.parse(code.as_bytes(), None).unwrap();
425 |         let mut cursor = tree.walk();
426 |         assert_eq!(cursor.node().kind(), "document");
427 |         assert!(cursor.goto_first_child());
428 |         assert_eq!(cursor.node().kind(), "section");
429 |         assert!(cursor.goto_first_child());
430 |         assert_eq!(cursor.node().kind(), "atx_heading");
431 |         assert!(cursor.goto_next_sibling());
432 |         assert_eq!(cursor.node().kind(), "paragraph");
433 |         assert!(cursor.goto_first_child());
434 |         assert_eq!(cursor.node().kind(), "inline");
435 |         assert!(cursor.goto_first_child());
436 |         assert_eq!(cursor.node().kind(), "shortcut_link");
437 |         assert!(cursor.goto_parent());
438 |         assert!(cursor.goto_parent());
439 |         assert!(cursor.goto_parent());
440 |         assert!(cursor.goto_parent());
441 |         assert_eq!(cursor.node().kind(), "document");
442 |     }
443 | 
444 |     #[test]
445 |     fn table() {
446 |         let code = "| foo |\n| --- |\n| *bar*|\n";
447 |         let mut parser = MarkdownParser::default();
448 |         let tree = parser.parse(code.as_bytes(), None).unwrap();
449 |         dbg!(&tree.inline_trees());
450 |         let mut cursor = tree.walk();
451 | 
452 |         assert_eq!(cursor.node().kind(), "document");
453 |         assert!(cursor.goto_first_child());
454 |         assert_eq!(cursor.node().kind(), "section");
455 |         assert!(cursor.goto_first_child());
456 |         assert_eq!(cursor.node().kind(), "pipe_table");
457 |         assert!(cursor.goto_first_child());
458 |         assert!(cursor.goto_next_sibling());
459 |         assert!(cursor.goto_next_sibling());
460 |         assert_eq!(cursor.node().kind(), "pipe_table_row");
461 |         assert!(cursor.goto_first_child());
462 |         assert!(cursor.goto_next_sibling());
463 |         assert_eq!(cursor.node().kind(), "pipe_table_cell");
464 |         assert!(cursor.goto_first_child());
465 |         assert_eq!(cursor.node().kind(), "emphasis");
466 |     }
467 | }
468 | 
```

--------------------------------------------------------------------------------
/deps/tree-sitter-markdown/tree-sitter-markdown-inline/grammar.js:
--------------------------------------------------------------------------------

```javascript
  1 | // This grammar only concerns the inline structure according to the CommonMark Spec
  2 | // (https://spec.commonmark.org/0.30/#inlines)
  3 | // For more information see README.md
  4 | 
  5 | /// <reference types="tree-sitter-cli/dsl" />
  6 | 
  7 | const common = require('../common/common');
  8 | 
  9 | // Levels used for dynmic precedence. Ideally
 10 | // n * PRECEDENCE_LEVEL_EMPHASIS > PRECEDENCE_LEVEL_LINK for any n, so maybe the
 11 | // maginuted of these values should be increased in the future
 12 | const PRECEDENCE_LEVEL_EMPHASIS = 1;
 13 | const PRECEDENCE_LEVEL_LINK = 10;
 14 | const PRECEDENCE_LEVEL_HTML = 100;
 15 | 
 16 | // Punctuation characters as specified in
 17 | // https://github.github.com/gfm/#ascii-punctuation-character
 18 | const PUNCTUATION_CHARACTERS_REGEX = '!-/:-@\\[-`\\{-~';
 19 | 
 20 | 
 21 | // !!!
 22 | // Notice the call to `add_inline_rules` which generates some additional rules related to parsing
 23 | // inline contents in different contexts.
 24 | // !!!
 25 | module.exports = grammar(add_inline_rules({
 26 |     name: 'markdown_inline',
 27 | 
 28 |     externals: $ => [
 29 |         // An `$._error` token is never valid  and gets emmited to kill invalid parse branches. Concretely
 30 |         // this is used to decide wether a newline closes a paragraph and together and it gets emitted
 31 |         // when trying to parse the `$._trigger_error` token in `$.link_title`.
 32 |         $._error,
 33 |         $._trigger_error,
 34 | 
 35 |         // Opening and closing delimiters for code spans. These are sequences of one or more backticks.
 36 |         // An opening token does not mean the text after has to be a code span if there is no closing token
 37 |         $._code_span_start,
 38 |         $._code_span_close,
 39 | 
 40 |         // Opening and closing delimiters for emphasis.
 41 |         $._emphasis_open_star,
 42 |         $._emphasis_open_underscore,
 43 |         $._emphasis_close_star,
 44 |         $._emphasis_close_underscore,
 45 | 
 46 |         // For emphasis we need to tell the parser if the last character was a whitespace (or the
 47 |         // beginning of a line) or a punctuation. These tokens never actually get emitted.
 48 |         $._last_token_whitespace,
 49 |         $._last_token_punctuation,
 50 | 
 51 |         $._strikethrough_open,
 52 |         $._strikethrough_close,
 53 | 
 54 |         // Opening and closing delimiters for latex. These are sequences of one or more dollar signs.
 55 |         // An opening token does not mean the text after has to be latex if there is no closing token
 56 |         $._latex_span_start,
 57 |         $._latex_span_close,
 58 | 
 59 |         // Token emmited when encountering opening delimiters for a leaf span
 60 |         // e.g. a code span, that does not have a matching closing span
 61 |         $._unclosed_span
 62 |     ],
 63 |     precedences: $ => [
 64 |         // [$._strong_emphasis_star, $._inline_element_no_star],
 65 |         [$._strong_emphasis_star_no_link, $._inline_element_no_star_no_link],
 66 |         // [$._strong_emphasis_underscore, $._inline_element_no_underscore],
 67 |         [$._strong_emphasis_underscore_no_link, $._inline_element_no_underscore_no_link],
 68 |         [$.hard_line_break, $._whitespace],
 69 |         [$.hard_line_break, $._text_base],
 70 |     ],
 71 |     // More conflicts are defined in `add_inline_rules`
 72 |     conflicts: $ => [
 73 | 
 74 |         [$._closing_tag, $._text_base],
 75 |         [$._open_tag, $._text_base],
 76 |         [$._html_comment, $._text_base],
 77 |         [$._processing_instruction, $._text_base],
 78 |         [$._declaration, $._text_base],
 79 |         [$._cdata_section, $._text_base],
 80 | 
 81 |         [$._link_text_non_empty, $._inline_element],
 82 |         [$._link_text_non_empty, $._inline_element_no_star],
 83 |         [$._link_text_non_empty, $._inline_element_no_underscore],
 84 |         [$._link_text_non_empty, $._inline_element_no_tilde],
 85 |         [$._link_text, $._inline_element],
 86 |         [$._link_text, $._inline_element_no_star],
 87 |         [$._link_text, $._inline_element_no_underscore],
 88 |         [$._link_text, $._inline_element_no_tilde],
 89 | 
 90 |         [$._image_description, $._image_description_non_empty, $._text_base],
 91 |         // [$._image_description, $._image_description_non_empty, $._text_inline],
 92 |         // [$._image_description, $._image_description_non_empty, $._text_inline_no_star],
 93 |         // [$._image_description, $._image_description_non_empty, $._text_inline_no_underscore],
 94 | 
 95 |         [$._image_shortcut_link, $._image_description],
 96 |         [$.shortcut_link, $._link_text],
 97 |         [$.link_destination, $.link_title],
 98 |         [$._link_destination_parenthesis, $.link_title],
 99 | 
100 |         [$.wiki_link, $._inline_element],
101 |         [$.wiki_link, $._inline_element_no_star],
102 |         [$.wiki_link, $._inline_element_no_underscore],
103 |         [$.wiki_link, $._inline_element_no_tilde],
104 |     ],
105 |     extras: $ => [],
106 | 
107 |     rules: {
108 |         inline: $ => seq(optional($._last_token_whitespace), $._inline),
109 | 
110 |         ...common.rules,
111 | 
112 | 
113 |         // A lot of inlines are defined in `add_inline_rules`, including:
114 |         //
115 |         // * collections of inlines
116 |         // * emphasis
117 |         // * textual content
118 |         //
119 |         // This is done to reduce code duplication, as some inlines need to be parsed differently
120 |         // depending on the context. For example inlines in ATX headings may not contain newlines.
121 | 
122 |         code_span: $ => seq(
123 |             alias($._code_span_start, $.code_span_delimiter),
124 |             repeat(choice($._text_base, '[', ']', $._soft_line_break, $._html_tag)),
125 |             alias($._code_span_close, $.code_span_delimiter)
126 |         ),
127 | 
128 |         latex_block: $ => seq(
129 |             alias($._latex_span_start, $.latex_span_delimiter),
130 |             repeat(choice($._text_base, '[', ']', $._soft_line_break, $._html_tag, $.backslash_escape)),
131 |             alias($._latex_span_close, $.latex_span_delimiter),
132 |         ),
133 | 
134 |         // Different kinds of links:
135 |         // * inline links (https://github.github.com/gfm/#inline-link)
136 |         // * full reference links (https://github.github.com/gfm/#full-reference-link)
137 |         // * collapsed reference links (https://github.github.com/gfm/#collapsed-reference-link)
138 |         // * shortcut links (https://github.github.com/gfm/#shortcut-reference-link)
139 |         //
140 |         // Dynamic precedence is distributed as granular as possible to help the parser decide
141 |         // while parsing which branch is the most important.
142 |         //
143 |         // https://github.github.com/gfm/#links
144 |         _link_text: $ => prec.dynamic(PRECEDENCE_LEVEL_LINK, choice(
145 |             $._link_text_non_empty,
146 |             seq('[', ']')
147 |         )),
148 |         _link_text_non_empty: $ => seq('[', alias($._inline_no_link, $.link_text), ']'),
149 |         shortcut_link: $ => prec.dynamic(PRECEDENCE_LEVEL_LINK, $._link_text_non_empty),
150 |         full_reference_link: $ => prec.dynamic(2 * PRECEDENCE_LEVEL_LINK, seq(
151 |             $._link_text,
152 |             $.link_label
153 |         )),
154 |         collapsed_reference_link: $ => prec.dynamic(PRECEDENCE_LEVEL_LINK, seq(
155 |             $._link_text,
156 |             '[',
157 |             ']'
158 |         )),
159 |         inline_link: $ => prec.dynamic(PRECEDENCE_LEVEL_LINK, seq(
160 |             $._link_text,
161 |             '(',
162 |             repeat(choice($._whitespace, $._soft_line_break)),
163 |             optional(seq(
164 |                 choice(
165 |                     seq(
166 |                         $.link_destination,
167 |                         optional(seq(
168 |                             repeat1(choice($._whitespace, $._soft_line_break)),
169 |                             $.link_title
170 |                         ))
171 |                     ),
172 |                     $.link_title,
173 |                 ),
174 |                 repeat(choice($._whitespace, $._soft_line_break)),
175 |             )),
176 |             ')'
177 |         )),
178 | 
179 |         wiki_link: $ => prec.dynamic(2 * PRECEDENCE_LEVEL_LINK, seq(
180 |             '[', '[',
181 |             alias($._wiki_link_destination, $.link_destination),
182 |             optional(seq(
183 |                 '|',
184 |                 alias($._wiki_link_text, $.link_text)
185 |             )),
186 |             ']', ']'
187 |             )
188 |         ),
189 | 
190 |         _wiki_link_destination: $ => repeat1(choice(
191 |             $._word,
192 |             common.punctuation_without($, ['[',']', '|']),
193 |             $._whitespace,
194 |         )),
195 | 
196 |         _wiki_link_text: $ => repeat1(choice(
197 |             $._word,
198 |             common.punctuation_without($, ['[',']']),
199 |             $._whitespace,
200 |         )),
201 | 
202 |         // Images work exactly like links with a '!' added in front.
203 |         //
204 |         // https://github.github.com/gfm/#images
205 |         image: $ => choice(
206 |             $._image_inline_link,
207 |             $._image_shortcut_link,
208 |             $._image_full_reference_link,
209 |             $._image_collapsed_reference_link
210 |         ),
211 |         _image_inline_link: $ => prec.dynamic(PRECEDENCE_LEVEL_LINK, seq(
212 |             $._image_description,
213 |             '(',
214 |             repeat(choice($._whitespace, $._soft_line_break)),
215 |             optional(seq(
216 |                 choice(
217 |                     seq(
218 |                         $.link_destination,
219 |                         optional(seq(
220 |                             repeat1(choice($._whitespace, $._soft_line_break)),
221 |                             $.link_title
222 |                         ))
223 |                     ),
224 |                     $.link_title,
225 |                 ),
226 |                 repeat(choice($._whitespace, $._soft_line_break)),
227 |             )),
228 |             ')'
229 |         )),
230 |         _image_shortcut_link: $ => prec.dynamic(3 * PRECEDENCE_LEVEL_LINK, $._image_description_non_empty),
231 |         _image_full_reference_link: $ => prec.dynamic(PRECEDENCE_LEVEL_LINK, seq($._image_description, $.link_label)),
232 |         _image_collapsed_reference_link: $ => prec.dynamic(PRECEDENCE_LEVEL_LINK, seq($._image_description, '[', ']')),
233 |         _image_description: $ => prec.dynamic(3 * PRECEDENCE_LEVEL_LINK, choice($._image_description_non_empty, seq('!', '[', prec(1, ']')))),
234 |         _image_description_non_empty: $ => seq('!', '[', alias($._inline, $.image_description), prec(1, ']')),
235 | 
236 |         // Autolinks. Uri autolinks actually accept protocolls of arbitrary length which does not
237 |         // align with the spec. This is because the binary for the grammar gets to large if done
238 |         // otherwise as tree-sitters code generation is not very concise for this type of regex.
239 |         //
240 |         // Email autolinks do not match every valid email (emails normally should not be parsed
241 |         // using regexes), but this is how they are defined in the spec.
242 |         //
243 |         // https://github.github.com/gfm/#autolinks
244 |         uri_autolink: $ => /<[a-zA-Z][a-zA-Z0-9+\.\-][a-zA-Z0-9+\.\-]*:[^ \t\r\n<>]*>/,
245 |         email_autolink: $ =>
246 |             /<[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*>/,
247 | 
248 |         // Raw html. As with html blocks we do not emit additional information as this is best done
249 |         // by a proper html tree-sitter grammar.
250 |         //
251 |         // https://github.github.com/gfm/#raw-html
252 |         _html_tag: $ => choice($._open_tag, $._closing_tag, $._html_comment, $._processing_instruction, $._declaration, $._cdata_section),
253 |         _open_tag: $ => prec.dynamic(PRECEDENCE_LEVEL_HTML, seq('<', $._tag_name, repeat($._attribute), repeat(choice($._whitespace, $._soft_line_break)), optional('/'), '>')),
254 |         _closing_tag: $ => prec.dynamic(PRECEDENCE_LEVEL_HTML, seq('<', '/', $._tag_name, repeat(choice($._whitespace, $._soft_line_break)), '>')),
255 |         _tag_name: $ => seq($._word_no_digit, repeat(choice($._word_no_digit, $._digits, '-'))),
256 |         _attribute: $ => seq(repeat1(choice($._whitespace, $._soft_line_break)), $._attribute_name, repeat(choice($._whitespace, $._soft_line_break)), '=', repeat(choice($._whitespace, $._soft_line_break)), $._attribute_value),
257 |         _attribute_name: $ => /[a-zA-Z_:][a-zA-Z0-9_\.:\-]*/,
258 |         _attribute_value: $ => choice(
259 |             /[^ \t\r\n"'=<>`]+/,
260 |             seq("'", repeat(choice($._word, $._whitespace, $._soft_line_break, common.punctuation_without($, ["'"]))), "'"),
261 |             seq('"', repeat(choice($._word, $._whitespace, $._soft_line_break, common.punctuation_without($, ['"']))), '"'),
262 |         ),
263 |         _html_comment: $ => prec.dynamic(PRECEDENCE_LEVEL_HTML, seq(
264 |             '<!--',
265 |             optional(seq(
266 |                 choice(
267 |                     $._word,
268 |                     $._whitespace,
269 |                     $._soft_line_break,
270 |                     common.punctuation_without($, ['-', '>']),
271 |                     seq(
272 |                         '-',
273 |                         common.punctuation_without($, ['>']),
274 |                     )
275 |                 ),
276 |                 repeat(prec.right(choice(
277 |                     $._word,
278 |                     $._whitespace,
279 |                     $._soft_line_break,
280 |                     common.punctuation_without($, ['-']),
281 |                     seq(
282 |                         '-',
283 |                         choice(
284 |                             $._word,
285 |                             $._whitespace,
286 |                             $._soft_line_break,
287 |                             common.punctuation_without($, ['-']),
288 |                         )
289 |                     )
290 |                 ))),
291 |             )),
292 |             '-->'
293 |         )),
294 |         _processing_instruction: $ => prec.dynamic(PRECEDENCE_LEVEL_HTML, seq(
295 |             '<?',
296 |             repeat(prec.right(choice(
297 |                 $._word,
298 |                 $._whitespace,
299 |                 $._soft_line_break,
300 |                 common.punctuation_without($, []),
301 |             ))),
302 |             '?>'
303 |         )),
304 |         _declaration: $ => prec.dynamic(PRECEDENCE_LEVEL_HTML, seq(
305 |             /<![A-Z]+/,
306 |             choice(
307 |                 $._whitespace,
308 |                 $._soft_line_break,
309 |             ),
310 |             repeat(prec.right(choice(
311 |                 $._word,
312 |                 $._whitespace,
313 |                 $._soft_line_break,
314 |                 common.punctuation_without($, ['>']),
315 |             ))),
316 |             '>'
317 |         )),
318 |         _cdata_section: $ => prec.dynamic(PRECEDENCE_LEVEL_HTML, seq(
319 |             '<![CDATA[',
320 |             repeat(prec.right(choice(
321 |                 $._word,
322 |                 $._whitespace,
323 |                 $._soft_line_break,
324 |                 common.punctuation_without($, []),
325 |             ))),
326 |             ']]>'
327 |         )),
328 | 
329 |         // A hard line break.
330 |         //
331 |         // https://github.github.com/gfm/#hard-line-breaks
332 |         hard_line_break: $ => seq(choice('\\', $._whitespace_ge_2), $._soft_line_break),
333 |         _text: $ => choice($._word, common.punctuation_without($, []), $._whitespace),
334 | 
335 |         // Whitespace is divided into single whitespaces and multiple whitespaces as wee need this
336 |         // information for hard line breaks.
337 |         _whitespace_ge_2: $ => /\t| [ \t]+/,
338 |         _whitespace: $ => seq(choice($._whitespace_ge_2, / /), optional($._last_token_whitespace)),
339 | 
340 |         // Other than whitespace we tokenize into strings of digits, punctuation characters
341 |         // (handled by `common.punctuation_without`) and strings of any other characters. This way the
342 |         // lexer does not have to many different states, which makes it a lot easier to make
343 |         // conflicts work.
344 |         _word: $ => choice($._word_no_digit, $._digits),
345 |         _word_no_digit: $ => new RegExp('[^' + PUNCTUATION_CHARACTERS_REGEX + ' \\t\\n\\r0-9]+(_+[^' + PUNCTUATION_CHARACTERS_REGEX + ' \\t\\n\\r0-9]+)*'),
346 |         _digits: $ => /[0-9][0-9_]*/,
347 |         _soft_line_break: $ => seq($._newline_token, optional($._last_token_whitespace)),
348 | 
349 |         _inline_base: $ => prec.right(repeat1(choice(
350 |             $.image,
351 |             $._soft_line_break,
352 |             $.backslash_escape,
353 |             $.hard_line_break,
354 |             $.uri_autolink,
355 |             $.email_autolink,
356 |             $.entity_reference,
357 |             $.numeric_character_reference,
358 |             (common.EXTENSION_LATEX ? $.latex_block : choice()),
359 |             $.code_span,
360 |             alias($._html_tag, $.html_tag),
361 |             $._text_base,
362 |             common.EXTENSION_TAGS ? $.tag : choice(),
363 |             $._unclosed_span,
364 |         ))),
365 |         _text_base: $ => choice(
366 |             $._word,
367 |             common.punctuation_without($, ['[', ']']),
368 |             $._whitespace,
369 |             '<!--',
370 |             /<![A-Z]+/,
371 |             '<?',
372 |             '<![CDATA[',
373 |         ),
374 |         _text_inline_no_link: $ => choice(
375 |             $._text_base,
376 |             $._emphasis_open_star,
377 |             $._emphasis_open_underscore,
378 |             $._unclosed_span,
379 |         ),
380 | 
381 |         ...(common.EXTENSION_TAGS ? {
382 |             tag: $ => /#[0-9]*[a-zA-Z_\-\/][a-zA-Z_\-\/0-9]*/,
383 |         } : {}),
384 | 
385 |     },
386 | }));
387 | 
388 | // This function adds some extra inline rules. This is done to reduce code duplication, as some
389 | // rules may not contain newlines, characters like '*' and '_', ... depending on the context.
390 | //
391 | // This is by far the most ugly part of this code and should be cleaned up.
392 | function add_inline_rules(grammar) {
393 |     let conflicts = [];
394 |     for (let link of [true, false]) {
395 |         let suffix_link = link ? "" : "_no_link";
396 |         for (let delimiter of [false, "star", "underscore", "tilde"]) {
397 |             let suffix_delimiter = delimiter ? "_no_" + delimiter : "";
398 |             let suffix = suffix_delimiter + suffix_link;
399 |             grammar.rules["_inline_element" + suffix] = $ => {
400 |                 let elements = [
401 |                     $._inline_base,
402 |                     alias($['_emphasis_star' + suffix_link], $.emphasis),
403 |                     alias($['_strong_emphasis_star' + suffix_link], $.strong_emphasis),
404 |                     alias($['_emphasis_underscore' + suffix_link], $.emphasis),
405 |                     alias($['_strong_emphasis_underscore' + suffix_link], $.strong_emphasis),
406 |                 ];
407 |                 if (common.EXTENSION_STRIKETHROUGH) {
408 |                     elements.push(alias($['_strikethrough' + suffix_link], $.strikethrough));
409 |                 }
410 |                 if (delimiter !== "star") {
411 |                     elements.push($._emphasis_open_star);
412 |                 }
413 |                 if (delimiter !== "underscore") {
414 |                     elements.push($._emphasis_open_underscore);
415 |                 }
416 |                 if (delimiter !== "tilde") {
417 |                     elements.push($._strikethrough_open);
418 |                 }
419 |                 if (link) {
420 |                     elements = elements.concat([
421 |                         $.shortcut_link,
422 |                         $.full_reference_link,
423 |                         $.collapsed_reference_link,
424 |                         $.inline_link,
425 |                         // (common.EXTENSION_WIKI_LINK && $.wiki_link),
426 |                         seq(choice('[', ']'), optional($._last_token_punctuation)),
427 |                     ]);
428 |                     if (common.EXTENSION_WIKI_LINK) {
429 |                         elements.push($.wiki_link);
430 |                     }
431 |                 }
432 |                 return choice(...elements);
433 |             };
434 |             grammar.rules["_inline" + suffix] = $ => repeat1($["_inline_element" + suffix]);
435 |             if (delimiter !== "star") {
436 |                 conflicts.push(['_emphasis_star' + suffix_link, '_inline_element' + suffix_delimiter + suffix_link]);
437 |                 conflicts.push(['_emphasis_star' + suffix_link, '_strong_emphasis_star' + suffix_link, '_inline_element' + suffix_delimiter + suffix_link]);
438 |             }
439 |             if (delimiter == 'star' || delimiter == 'underscore') {
440 |                 conflicts.push(['_strong_emphasis_' + delimiter + suffix_link, '_inline_element_no_' + delimiter]);
441 |             }
442 |             if (delimiter !== "underscore") {
443 |                 conflicts.push(['_emphasis_underscore' + suffix_link, '_inline_element' + suffix_delimiter + suffix_link]);
444 |                 conflicts.push(['_emphasis_underscore' + suffix_link, '_strong_emphasis_underscore' + suffix_link, '_inline_element' + suffix_delimiter + suffix_link]);
445 |             }
446 |             if (delimiter !== "tilde") {
447 |                 conflicts.push(['_strikethrough' + suffix_link, '_inline_element' + suffix_delimiter + suffix_link]);
448 |             }
449 |         }
450 | 
451 |         if (common.EXTENSION_STRIKETHROUGH) {
452 |             grammar.rules['_strikethrough' + suffix_link] = $ => prec.dynamic(PRECEDENCE_LEVEL_EMPHASIS, seq(alias($._strikethrough_open, $.emphasis_delimiter), optional($._last_token_punctuation), $['_inline' + '_no_tilde' + suffix_link], alias($._strikethrough_close, $.emphasis_delimiter)));
453 |         }
454 |         grammar.rules['_emphasis_star' + suffix_link] = $ => prec.dynamic(PRECEDENCE_LEVEL_EMPHASIS, seq(alias($._emphasis_open_star, $.emphasis_delimiter), optional($._last_token_punctuation), $['_inline' + '_no_star' + suffix_link], alias($._emphasis_close_star, $.emphasis_delimiter)));
455 |         grammar.rules['_strong_emphasis_star' + suffix_link] = $ => prec.dynamic(2 * PRECEDENCE_LEVEL_EMPHASIS, seq(alias($._emphasis_open_star, $.emphasis_delimiter), $['_emphasis_star' + suffix_link], alias($._emphasis_close_star, $.emphasis_delimiter)));
456 |         grammar.rules['_emphasis_underscore' + suffix_link] = $ => prec.dynamic(PRECEDENCE_LEVEL_EMPHASIS, seq(alias($._emphasis_open_underscore, $.emphasis_delimiter), optional($._last_token_punctuation), $['_inline' + '_no_underscore' + suffix_link], alias($._emphasis_close_underscore, $.emphasis_delimiter)));
457 |         grammar.rules['_strong_emphasis_underscore' + suffix_link] = $ => prec.dynamic(2 * PRECEDENCE_LEVEL_EMPHASIS, seq(alias($._emphasis_open_underscore, $.emphasis_delimiter), $['_emphasis_underscore' + suffix_link], alias($._emphasis_close_underscore, $.emphasis_delimiter)));
458 |     }
459 | 
460 |     let old = grammar.conflicts
461 |     grammar.conflicts = $ => {
462 |         let cs = old($);
463 |         for (let conflict of conflicts) {
464 |             let c = [];
465 |             for (let rule of conflict) {
466 |                 c.push($[rule]);
467 |             }
468 |             cs.push(c);
469 |         }
470 |         return cs;
471 |     }
472 | 
473 |     return grammar;
474 | }
475 | 
```

--------------------------------------------------------------------------------
/src/ui/style_manager.cpp:
--------------------------------------------------------------------------------

```cpp
  1 | #include "style_manager.h"
  2 | #include <algorithm>
  3 | #include <cctype>
  4 | #include <cstring>
  5 | #include <fstream>
  6 | #include <iostream>
  7 | #ifdef _WIN32
  8 | #include <curses.h>
  9 | inline int setenv(const char *name, const char *value, int overwrite)
 10 | {
 11 |   if (!overwrite)
 12 |   {
 13 |     size_t envsize = 0;
 14 |     getenv_s(&envsize, nullptr, 0, name);
 15 |     if (envsize != 0)
 16 |       return 0; // Variable exists, don't overwrite
 17 |   }
 18 |   return _putenv_s(name, value);
 19 | }
 20 | #else
 21 | #include <ncurses.h>
 22 | #endif
 23 | #include <sstream>
 24 | 
 25 | StyleManager::StyleManager()
 26 |     : initialized(false), supports_256_colors_cache(false),
 27 |       next_custom_color_id(16)
 28 | {
 29 | }
 30 | 
 31 | void StyleManager::initialize()
 32 | {
 33 |   if (initialized)
 34 |   {
 35 |     std::cerr << "StyleManager already initialized" << std::endl;
 36 |     return;
 37 |   }
 38 | 
 39 |   // Critical: Check if ncurses has been initialized first
 40 |   if (!stdscr)
 41 |   {
 42 |     std::cerr << "ERROR: ncurses not initialized. Call initscr() first!"
 43 |               << std::endl;
 44 |     return;
 45 |   }
 46 | 
 47 |   if (!has_colors())
 48 |   {
 49 |     std::cerr << "Terminal does not support colors" << std::endl;
 50 |     return;
 51 |   }
 52 | 
 53 |   // Initialize color support
 54 |   if (start_color() == ERR)
 55 |   {
 56 |     std::cerr << "Failed to start color support" << std::endl;
 57 |     return;
 58 |   }
 59 | 
 60 |   // Use default terminal colors - CRITICAL for transparent support
 61 |   if (use_default_colors() == ERR)
 62 |   {
 63 |     std::cerr << "Warning: use_default_colors() failed, using fallback"
 64 |               << std::endl;
 65 |     // Fallback: assume black background, white foreground
 66 |     assume_default_colors(COLOR_WHITE, COLOR_BLACK);
 67 |   }
 68 | 
 69 |   // Initialize color capability cache and custom color tracking
 70 |   supports_256_colors_cache = supports_256_colors();
 71 |   next_custom_color_id = 16; // Start custom colors at ID 16
 72 |   color_cache.clear();
 73 | 
 74 |   // std::cerr << "=== UNIFIED THEME SYSTEM WITH HEX SUPPORT ===" << std::endl;
 75 |   // std::cerr << "COLORS: " << COLORS << std::endl;
 76 |   // std::cerr << "COLOR_PAIRS: " << COLOR_PAIRS << std::endl;
 77 |   // std::cerr << "256-color support: "
 78 |   //           << (supports_256_colors_cache ? "YES" : "NO") << std::endl;
 79 |   // std::cerr << "TERM: " << (getenv("TERM") ? getenv("TERM") : "not set")
 80 |   //           << std::endl;
 81 | 
 82 |   // Check for WSL-specific issues
 83 |   const char *wsl_distro = getenv("WSL_DISTRO_NAME");
 84 |   if (wsl_distro)
 85 |   {
 86 |     // std::cerr << "WSL detected: " << wsl_distro << std::endl;
 87 |     // WSL sometimes has color issues, force TERM if needed
 88 |     if (!getenv("TERM") || strcmp(getenv("TERM"), "dumb") == 0)
 89 |     {
 90 |       // std::cerr << "Setting TERM=xterm-256color for WSL" << std::endl;
 91 |       setenv("TERM", "xterm-256color", 1);
 92 |     }
 93 |   }
 94 | 
 95 |   load_default_theme();
 96 |   apply_theme();
 97 | 
 98 |   initialized = true;
 99 |   // std::cerr << "Unified theme system initialized successfully" << std::endl;
100 | }
101 | 
102 | short StyleManager::resolve_theme_color(const std::string &config_value)
103 | {
104 |   // Handle transparent/default colors
105 |   if (config_value.empty() || config_value == "transparent" ||
106 |       config_value == "default")
107 |   {
108 |     return -1; // Use terminal default
109 |   }
110 | 
111 |   // Check if it's a hex color
112 |   if (config_value.length() == 7 && config_value[0] == '#')
113 |   {
114 |     // Check cache first
115 |     auto cache_it = color_cache.find(config_value);
116 |     if (cache_it != color_cache.end())
117 |     {
118 |       return cache_it->second;
119 |     }
120 | 
121 |     // Parse the hex color
122 |     RGB rgb = parse_hex_color(config_value);
123 | 
124 |     if (supports_256_colors_cache && next_custom_color_id < COLORS)
125 |     {
126 |       // 256-color mode: use init_color for true color mapping
127 |       // Scale R, G, B from 0-255 to ncurses' 0-1000 range
128 |       short r_1000 = (rgb.r * 1000) / 255;
129 |       short g_1000 = (rgb.g * 1000) / 255;
130 |       short b_1000 = (rgb.b * 1000) / 255;
131 | 
132 |       short color_id = next_custom_color_id;
133 |       if (init_color(color_id, r_1000, g_1000, b_1000) == OK)
134 |       {
135 |         // Cache the mapping and increment for next time
136 |         auto cache_it = color_cache.find(config_value);
137 |         if (cache_it != color_cache.end())
138 |         {
139 |           return cache_it->second; // OK: using iterator
140 |         }
141 | 
142 |         color_cache[config_value] = color_id;
143 |         const_cast<StyleManager *>(this)->next_custom_color_id++;
144 | 
145 |         // std::cerr << "Created custom color " << color_id << " for "
146 |         //           << config_value << " (RGB: " << rgb.r << "," << rgb.g <<
147 |         //           ","
148 |         //           << rgb.b << ")" << std::endl;
149 |         return color_id;
150 |       }
151 |       else
152 |       {
153 |         std::cerr << "Failed to create custom color for " << config_value
154 |                   << ", falling back to closest 8-color" << std::endl;
155 |       }
156 |     }
157 | 
158 |     // Fallback to legacy 8-color mode
159 |     return find_closest_8color(rgb);
160 |   }
161 | 
162 |   // Legacy named color support (for backward compatibility)
163 |   // This handles old theme files that might still use color names
164 |   ThemeColor legacy_color = string_to_theme_color(config_value);
165 |   return theme_color_to_ncurses_color(legacy_color);
166 | }
167 | 
168 | // NEW: Hex color parsing utility
169 | RGB StyleManager::parse_hex_color(const std::string &hex_str) const
170 | {
171 |   RGB rgb;
172 | 
173 |   if (hex_str.length() != 7 || hex_str[0] != '#')
174 |   {
175 |     std::cerr << "Invalid hex color format: " << hex_str << ", using black"
176 |               << std::endl;
177 |     return RGB(0, 0, 0);
178 |   }
179 | 
180 |   try
181 |   {
182 |     // Parse each component (skip the '#')
183 |     std::string r_str = hex_str.substr(1, 2);
184 |     std::string g_str = hex_str.substr(3, 2);
185 |     std::string b_str = hex_str.substr(5, 2);
186 | 
187 |     rgb.r = std::stoi(r_str, nullptr, 16);
188 |     rgb.g = std::stoi(g_str, nullptr, 16);
189 |     rgb.b = std::stoi(b_str, nullptr, 16);
190 | 
191 |     // Clamp to valid range
192 |     rgb.r = std::max(0, std::min(255, rgb.r));
193 |     rgb.g = std::max(0, std::min(255, rgb.g));
194 |     rgb.b = std::max(0, std::min(255, rgb.b));
195 |   }
196 |   catch (const std::exception &e)
197 |   {
198 |     std::cerr << "Error parsing hex color " << hex_str << ": " << e.what()
199 |               << std::endl;
200 |     return RGB(0, 0, 0);
201 |   }
202 | 
203 |   return rgb;
204 | }
205 | 
206 | // NEW: Find closest 8-color match for fallback
207 | short StyleManager::find_closest_8color(const RGB &rgb) const
208 | {
209 |   // Define the standard 8 colors in RGB
210 |   struct ColorMapping
211 |   {
212 |     short ncurses_color;
213 |     RGB rgb;
214 |     const char *name;
215 |   };
216 | 
217 |   static const ColorMapping basic_colors[] = {
218 |       {COLOR_BLACK, RGB(0, 0, 0), "black"},
219 |       {COLOR_RED, RGB(128, 0, 0), "red"},
220 |       {COLOR_GREEN, RGB(0, 128, 0), "green"},
221 |       {COLOR_YELLOW, RGB(128, 128, 0), "yellow"},
222 |       {COLOR_BLUE, RGB(0, 0, 128), "blue"},
223 |       {COLOR_MAGENTA, RGB(128, 0, 128), "magenta"},
224 |       {COLOR_CYAN, RGB(0, 128, 128), "cyan"},
225 |       {COLOR_WHITE, RGB(192, 192, 192), "white"}};
226 | 
227 |   // Find the closest color using simple Euclidean distance
228 |   double min_distance = 1000000;
229 |   short closest_color = COLOR_WHITE;
230 | 
231 |   for (const auto &color : basic_colors)
232 |   {
233 |     double dr = rgb.r - color.rgb.r;
234 |     double dg = rgb.g - color.rgb.g;
235 |     double db = rgb.b - color.rgb.b;
236 |     double distance = dr * dr + dg * dg + db * db;
237 | 
238 |     if (distance < min_distance)
239 |     {
240 |       min_distance = distance;
241 |       closest_color = color.ncurses_color;
242 |     }
243 |   }
244 | 
245 |   return closest_color;
246 | }
247 | 
248 | // Legacy function - kept only for load_default_theme compatibility
249 | ThemeColor
250 | StyleManager::string_to_theme_color(const std::string &color_name) const
251 | {
252 |   std::string lower_name = color_name;
253 |   std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(),
254 |                  ::tolower);
255 | 
256 |   if (lower_name == "black")
257 |     return ThemeColor::BLACK;
258 |   if (lower_name == "dark_gray" || lower_name == "dark_grey")
259 |     return ThemeColor::DARK_GRAY;
260 |   if (lower_name == "gray" || lower_name == "grey")
261 |     return ThemeColor::GRAY;
262 |   if (lower_name == "light_gray" || lower_name == "light_grey")
263 |     return ThemeColor::LIGHT_GRAY;
264 |   if (lower_name == "white")
265 |     return ThemeColor::WHITE;
266 |   if (lower_name == "red")
267 |     return ThemeColor::RED;
268 |   if (lower_name == "green")
269 |     return ThemeColor::GREEN;
270 |   if (lower_name == "blue")
271 |     return ThemeColor::BLUE;
272 |   if (lower_name == "yellow")
273 |     return ThemeColor::YELLOW;
274 |   if (lower_name == "magenta")
275 |     return ThemeColor::MAGENTA;
276 |   if (lower_name == "cyan")
277 |     return ThemeColor::CYAN;
278 |   if (lower_name == "bright_red")
279 |     return ThemeColor::BRIGHT_RED;
280 |   if (lower_name == "bright_green")
281 |     return ThemeColor::BRIGHT_GREEN;
282 |   if (lower_name == "bright_blue")
283 |     return ThemeColor::BRIGHT_BLUE;
284 |   if (lower_name == "bright_yellow")
285 |     return ThemeColor::BRIGHT_YELLOW;
286 |   if (lower_name == "bright_magenta")
287 |     return ThemeColor::BRIGHT_MAGENTA;
288 |   if (lower_name == "bright_cyan")
289 |     return ThemeColor::BRIGHT_CYAN;
290 | 
291 |   std::cerr << "Unknown color name: " << color_name << ", using white"
292 |             << std::endl;
293 |   return ThemeColor::WHITE;
294 | }
295 | 
296 | // Legacy function - kept for backward compatibility
297 | int StyleManager::theme_color_to_ncurses_color(ThemeColor color) const
298 | {
299 |   switch (color)
300 |   {
301 |   case ThemeColor::BLACK:
302 |     return COLOR_BLACK;
303 |   case ThemeColor::DARK_GRAY:
304 |     return COLOR_BLACK; // Will use A_BOLD attribute for brighter black
305 |   case ThemeColor::GRAY:
306 |     return COLOR_WHITE; // Will use A_DIM attribute for dimmed white
307 |   case ThemeColor::LIGHT_GRAY:
308 |     return COLOR_WHITE; // Will use A_DIM + A_BOLD for medium brightness
309 |   case ThemeColor::WHITE:
310 |     return COLOR_WHITE;
311 |   case ThemeColor::RED:
312 |     return COLOR_RED;
313 |   case ThemeColor::GREEN:
314 |     return COLOR_GREEN;
315 |   case ThemeColor::BLUE:
316 |     return COLOR_BLUE;
317 |   case ThemeColor::YELLOW:
318 |     return COLOR_YELLOW;
319 |   case ThemeColor::MAGENTA:
320 |     return COLOR_MAGENTA;
321 |   case ThemeColor::CYAN:
322 |     return COLOR_CYAN;
323 |   case ThemeColor::BRIGHT_RED:
324 |     return COLOR_RED; // Will use A_BOLD attribute
325 |   case ThemeColor::BRIGHT_GREEN:
326 |     return COLOR_GREEN; // Will use A_BOLD attribute
327 |   case ThemeColor::BRIGHT_BLUE:
328 |     return COLOR_BLUE; // Will use A_BOLD attribute
329 |   case ThemeColor::BRIGHT_YELLOW:
330 |     return COLOR_YELLOW; // Will use A_BOLD attribute
331 |   case ThemeColor::BRIGHT_MAGENTA:
332 |     return COLOR_MAGENTA; // Will use A_BOLD attribute
333 |   case ThemeColor::BRIGHT_CYAN:
334 |     return COLOR_CYAN; // Will use A_BOLD attribute
335 |   case ThemeColor::TERMINAL:
336 |     return COLOR_RED;
337 |   default:
338 |     return COLOR_WHITE;
339 |   }
340 | }
341 | 
342 | // Legacy function - simplified since 256-color provides enough fidelity
343 | int StyleManager::theme_color_to_ncurses_attr(ThemeColor color) const
344 | {
345 |   // With 256-color support, we can rely more on actual colors than attributes
346 |   // Keep only essential attributes
347 |   switch (color)
348 |   {
349 |   case ThemeColor::BRIGHT_RED:
350 |   case ThemeColor::BRIGHT_GREEN:
351 |   case ThemeColor::BRIGHT_BLUE:
352 |   case ThemeColor::BRIGHT_YELLOW:
353 |   case ThemeColor::BRIGHT_MAGENTA:
354 |   case ThemeColor::BRIGHT_CYAN:
355 |     return A_BOLD; // Keep bold for legacy compatibility
356 |   default:
357 |     return A_NORMAL;
358 |   }
359 | }
360 | 
361 | bool StyleManager::is_light_theme() const
362 | {
363 |   // Check if background is a light hex color or legacy light theme
364 |   if (current_theme.background[0] == '#')
365 |   {
366 |     RGB bg_rgb = parse_hex_color(current_theme.background);
367 |     // Consider it light if the average RGB value is > 128
368 |     return (bg_rgb.r + bg_rgb.g + bg_rgb.b) / 3 > 128;
369 |   }
370 | 
371 |   // Legacy check for named colors
372 |   ThemeColor legacy_bg = string_to_theme_color(current_theme.background);
373 |   return (legacy_bg == ThemeColor::WHITE ||
374 |           legacy_bg == ThemeColor::LIGHT_GRAY);
375 | }
376 | 
377 | void StyleManager::load_default_theme()
378 | {
379 |   current_theme = {
380 |       "Default Dark (Hex)",
381 |       "#000000", // background
382 |       "#FFFFFF", // foreground
383 |       "#FFFFFF", // cursor
384 |       "#0000FF", // selection
385 |       "#333333", // line_highlight
386 |       "#FFFF00", // line_numbers
387 |       "#FFFF99", // line_numbers_active
388 |       "#000080", // status_bar_bg
389 |       "#FFFFFF", // status_bar_fg
390 |       "#00FFFF", // status_bar_active
391 | 
392 |       // Semantic categories
393 |       "#569CD6", // keyword
394 |       "#CE9178", // string_literal
395 |       "#B5CEA8", // number
396 |       "#6A9955", // comment
397 |       "#DCDCAA", // function_name
398 |       "#9CDCFE", // variable
399 |       "#4EC9B0", // type
400 |       "#D4D4D4", // operator
401 |       "#D4D4D4", // punctuation
402 |       "#4FC1FF", // constant
403 |       "#4EC9B0", // namespace
404 |       "#9CDCFE", // property
405 |       "#DCDCAA", // decorator
406 |       "#C586C0", // macro
407 |       "#569CD6", // label
408 | 
409 |       // Markup
410 |       "#569CD6", // markup_heading
411 |       "#D4D4D4", // markup_bold
412 |       "#CE9178", // markup_italic
413 |       "#CE9178", // markup_code
414 |       "#CE9178", // markup_code_block
415 |       "#3794FF", // markup_link
416 |       "#3794FF", // markup_url
417 |       "#6A9955", // markup_list
418 |       "#6A9955", // markup_blockquote
419 |       "#FF6B6B", // markup_strikethrough
420 |       "#6A9955"  // markup_quote
421 |   };
422 | }
423 | 
424 | void StyleManager::apply_theme()
425 | {
426 |   if (!initialized)
427 |   {
428 |     // std::cerr << "StyleManager not initialized, cannot apply theme"
429 |     //           << std::endl;
430 |     return;
431 |   }
432 | 
433 |   // std::cerr << "Applying theme: " << current_theme.name << std::endl;
434 | 
435 |   short terminal_bg = resolve_theme_color(current_theme.background);
436 |   short terminal_fg = resolve_theme_color(current_theme.foreground);
437 | 
438 |   init_pair(0, terminal_fg, terminal_bg);
439 |   const int BACKGROUND_PAIR_ID = 100;
440 |   init_pair(BACKGROUND_PAIR_ID, terminal_fg, terminal_bg);
441 | 
442 |   auto init_pair_enhanced = [&](int pair_id, const std::string &fg_color_str,
443 |                                 const std::string &bg_color_str) -> bool
444 |   {
445 |     if (pair_id >= COLOR_PAIRS)
446 |       return false;
447 |     short fg = resolve_theme_color(fg_color_str);
448 |     short bg = resolve_theme_color(bg_color_str);
449 |     int result = init_pair(pair_id, fg, bg);
450 |     return (result == OK);
451 |   };
452 | 
453 |   // UI Elements
454 |   init_pair_enhanced(2, current_theme.line_numbers, current_theme.background);
455 |   init_pair_enhanced(3, current_theme.line_numbers_active,
456 |                      current_theme.background);
457 |   init_pair_enhanced(4, "#808080", current_theme.background);
458 | 
459 |   // Status bar
460 |   init_pair_enhanced(5, current_theme.status_bar_fg,
461 |                      current_theme.status_bar_bg);
462 |   init_pair_enhanced(6, current_theme.status_bar_fg,
463 |                      current_theme.status_bar_bg);
464 |   init_pair_enhanced(7, current_theme.status_bar_active,
465 |                      current_theme.status_bar_bg);
466 |   init_pair_enhanced(8, "#00FFFF", current_theme.status_bar_bg);
467 |   init_pair_enhanced(9, "#FFFF00", current_theme.status_bar_bg);
468 |   init_pair_enhanced(10, "#00FF00", current_theme.status_bar_bg);
469 |   init_pair_enhanced(11, "#FF00FF", current_theme.status_bar_bg);
470 |   init_pair_enhanced(12, "#808080", current_theme.status_bar_bg);
471 | 
472 |   // Selection and cursor
473 |   init_pair_enhanced(13, current_theme.cursor, current_theme.background);
474 |   init_pair_enhanced(14, current_theme.foreground, current_theme.selection);
475 |   init_pair_enhanced(15, current_theme.foreground,
476 |                      current_theme.line_highlight);
477 | 
478 |   // Semantic categories (20-39)
479 |   init_pair_enhanced(20, current_theme.keyword, current_theme.background);
480 |   init_pair_enhanced(21, current_theme.string_literal,
481 |                      current_theme.background);
482 |   init_pair_enhanced(22, current_theme.number, current_theme.background);
483 |   init_pair_enhanced(23, current_theme.comment, current_theme.background);
484 |   init_pair_enhanced(24, current_theme.function_name, current_theme.background);
485 |   init_pair_enhanced(25, current_theme.variable, current_theme.background);
486 |   init_pair_enhanced(26, current_theme.type, current_theme.background);
487 |   init_pair_enhanced(27, current_theme.operator_color,
488 |                      current_theme.background);
489 |   init_pair_enhanced(28, current_theme.punctuation, current_theme.background);
490 |   init_pair_enhanced(29, current_theme.constant, current_theme.background);
491 |   init_pair_enhanced(30, current_theme.namespace_color,
492 |                      current_theme.background);
493 |   init_pair_enhanced(31, current_theme.property, current_theme.background);
494 |   init_pair_enhanced(32, current_theme.decorator, current_theme.background);
495 |   init_pair_enhanced(33, current_theme.macro, current_theme.background);
496 |   init_pair_enhanced(34, current_theme.label, current_theme.background);
497 | 
498 |   // Markup (50-61)
499 |   init_pair_enhanced(50, current_theme.markup_heading,
500 |                      current_theme.background);
501 |   init_pair_enhanced(51, current_theme.markup_bold, current_theme.background);
502 |   init_pair_enhanced(52, current_theme.markup_italic, current_theme.background);
503 |   init_pair_enhanced(53, current_theme.markup_code, current_theme.background);
504 |   init_pair_enhanced(54, current_theme.markup_code_block,
505 |                      current_theme.background);
506 |   init_pair_enhanced(55, current_theme.markup_link, current_theme.background);
507 |   init_pair_enhanced(56, current_theme.markup_url, current_theme.background);
508 |   init_pair_enhanced(57, current_theme.markup_blockquote,
509 |                      current_theme.background);
510 |   init_pair_enhanced(58, current_theme.markup_list, current_theme.background);
511 |   init_pair_enhanced(59, current_theme.operator_color,
512 |                      current_theme.background);
513 |   init_pair_enhanced(60, current_theme.markup_strikethrough,
514 |                      current_theme.background);
515 |   init_pair_enhanced(61, current_theme.markup_quote, current_theme.background);
516 | 
517 |   // Special pairs
518 |   init_pair_enhanced(70, current_theme.foreground,
519 |                      current_theme.line_highlight);
520 | 
521 |   bkgdset(' ' | COLOR_PAIR(BACKGROUND_PAIR_ID));
522 |   clear();
523 |   // refresh();
524 | }
525 | 
526 | // Also add this helper function to detect and optimize for WSL
527 | bool StyleManager::is_wsl_environment() const
528 | {
529 |   return getenv("WSL_DISTRO_NAME") != nullptr;
530 | }
531 | 
532 | // Enhanced color initialization for WSL
533 | void StyleManager::optimize_for_wsl()
534 | {
535 |   if (!is_wsl_environment())
536 |     return;
537 | 
538 |   std::cerr << "WSL environment detected - applying optimizations" << std::endl;
539 | 
540 |   // Check Windows Terminal version and capabilities
541 |   const char *wt_session = getenv("WT_SESSION");
542 |   if (wt_session)
543 |   {
544 |     std::cerr << "Windows Terminal detected" << std::endl;
545 | 
546 |     // Windows Terminal supports true color
547 |     if (supports_true_color())
548 |     {
549 |       std::cerr << "True color support detected" << std::endl;
550 |     }
551 | 
552 |     // Force TERM to get better colors
553 |     if (!getenv("TERM") || strcmp(getenv("TERM"), "xterm-256color") != 0)
554 |     {
555 |       std::cerr << "Setting TERM=xterm-256color for better WSL colors"
556 |                 << std::endl;
557 |       setenv("TERM", "xterm-256color", 1);
558 |     }
559 |   }
560 | }
561 | 
562 | // Helper function to apply color with attributes (simplified for 256-color)
563 | void StyleManager::apply_color_pair(int pair_id, ThemeColor theme_color) const
564 | {
565 |   int attrs = COLOR_PAIR(pair_id) | theme_color_to_ncurses_attr(theme_color);
566 |   attrset(attrs);
567 | }
568 | 
569 | // Legacy compatibility functions
570 | 
571 | // Terminal capability detection
572 | bool StyleManager::supports_256_colors() const { return COLORS >= 256; }
573 | 
574 | bool StyleManager::supports_true_color() const
575 | {
576 |   const char *colorterm = getenv("COLORTERM");
577 |   return (colorterm && (std::strcmp(colorterm, "truecolor") == 0 ||
578 |                         std::strcmp(colorterm, "24bit") == 0));
579 | }
580 | 
581 | void StyleManager::apply_legacy_theme(const Theme &theme)
582 | {
583 |   if (initialized)
584 |   {
585 |     apply_theme();
586 |   }
587 | }
588 | 
589 | // YAML parsing utilities remain the same
590 | std::string StyleManager::trim(const std::string &str)
591 | {
592 |   size_t start = str.find_first_not_of(" \t\n\r");
593 |   if (start == std::string::npos)
594 |     return "";
595 |   size_t end = str.find_last_not_of(" \t\n\r");
596 |   return str.substr(start, end - start + 1);
597 | }
598 | 
599 | std::string StyleManager::remove_quotes(const std::string &str)
600 | {
601 |   std::string trimmed = trim(str);
602 |   if (trimmed.length() >= 2 &&
603 |       ((trimmed.front() == '"' && trimmed.back() == '"') ||
604 |        (trimmed.front() == '\'' && trimmed.back() == '\'')))
605 |   {
606 |     return trimmed.substr(1, trimmed.length() - 2);
607 |   }
608 |   return trimmed;
609 | }
610 | 
611 | std::map<std::string, std::string>
612 | StyleManager::parse_yaml(const std::string &yaml_content)
613 | {
614 |   std::map<std::string, std::string> result;
615 |   std::istringstream stream(yaml_content);
616 |   std::string line;
617 | 
618 |   while (std::getline(stream, line))
619 |   {
620 |     std::string trimmed_line = trim(line);
621 |     if (trimmed_line.empty() || trimmed_line[0] == '#')
622 |       continue;
623 | 
624 |     size_t colon_pos = line.find(':');
625 |     if (colon_pos == std::string::npos)
626 |       continue;
627 | 
628 |     std::string key = trim(line.substr(0, colon_pos));
629 |     std::string value = trim(line.substr(colon_pos + 1));
630 |     value = remove_quotes(value);
631 | 
632 |     if (!key.empty() && !value.empty())
633 |     {
634 |       result[key] = value;
635 |     }
636 |   }
637 |   return result;
638 | }
639 | 
640 | bool StyleManager::load_theme_from_yaml(const std::string &yaml_content)
641 | {
642 |   try
643 |   {
644 |     auto config = parse_yaml(yaml_content);
645 |     NamedTheme theme;
646 | 
647 |     auto get_color = [&](const std::string &key,
648 |                          const std::string &default_color) -> std::string
649 |     {
650 |       auto it = config.find(key);
651 |       return (it != config.end()) ? it->second : default_color;
652 |     };
653 | 
654 |     theme.name = config.count("name") ? config["name"] : "Custom Theme";
655 |     theme.background = get_color("background", "#000000");
656 |     theme.foreground = get_color("foreground", "#FFFFFF");
657 |     theme.cursor = get_color("cursor", "#FFFFFF");
658 |     theme.selection = get_color("selection", "#0000FF");
659 |     theme.line_highlight = get_color("line_highlight", "#333333");
660 |     theme.line_numbers = get_color("line_numbers", "#808080");
661 |     theme.line_numbers_active = get_color("line_numbers_active", "#FFFFFF");
662 |     theme.status_bar_bg = get_color("status_bar_bg", "#000080");
663 |     theme.status_bar_fg = get_color("status_bar_fg", "#FFFFFF");
664 |     theme.status_bar_active = get_color("status_bar_active", "#00FFFF");
665 | 
666 |     // Semantic categories
667 |     theme.keyword = get_color("keyword", "#569CD6");
668 |     theme.string_literal = get_color("string_literal", "#CE9178");
669 |     theme.number = get_color("number", "#B5CEA8");
670 |     theme.comment = get_color("comment", "#6A9955");
671 |     theme.function_name = get_color("function_name", "#DCDCAA");
672 |     theme.variable = get_color("variable", "#9CDCFE");
673 |     theme.type = get_color("type", "#4EC9B0");
674 |     theme.operator_color = get_color("operator", "#D4D4D4");
675 |     theme.punctuation = get_color("punctuation", "#D4D4D4");
676 |     theme.constant = get_color("constant", "#4FC1FF");
677 |     theme.namespace_color = get_color("namespace", "#4EC9B0");
678 |     theme.property = get_color("property", "#9CDCFE");
679 |     theme.decorator = get_color("decorator", "#DCDCAA");
680 |     theme.macro = get_color("macro", "#C586C0");
681 |     theme.label = get_color("label", "#569CD6");
682 | 
683 |     // Markup
684 |     theme.markup_heading = get_color("markup_heading", "#569CD6");
685 |     theme.markup_bold = get_color("markup_bold", "#D4D4D4");
686 |     theme.markup_italic = get_color("markup_italic", "#CE9178");
687 |     theme.markup_code = get_color("markup_code", "#CE9178");
688 |     theme.markup_code_block = get_color("markup_code_block", "#CE9178");
689 |     theme.markup_link = get_color("markup_link", "#3794FF");
690 |     theme.markup_url = get_color("markup_url", "#3794FF");
691 |     theme.markup_list = get_color("markup_list", "#6A9955");
692 |     theme.markup_blockquote = get_color("markup_blockquote", "#6A9955");
693 |     theme.markup_strikethrough = get_color("markup_strikethrough", "#FF6B6B");
694 |     theme.markup_quote = get_color("markup_quote", "#6A9955");
695 | 
696 |     current_theme = theme;
697 |     if (initialized)
698 |     {
699 |       apply_theme();
700 |     }
701 |     return true;
702 |   }
703 |   catch (const std::exception &e)
704 |   {
705 |     std::cerr << "Error parsing theme: " << e.what() << std::endl;
706 |     load_default_theme();
707 |     return false;
708 |   }
709 | }
710 | 
711 | bool StyleManager::load_theme_from_file(const std::string &file_path)
712 | {
713 |   try
714 |   {
715 |     std::ifstream file(file_path);
716 |     if (!file.is_open())
717 |     {
718 |       std::cerr << "Failed to open theme file: " << file_path << std::endl;
719 |       load_default_theme();
720 |       return false;
721 |     }
722 | 
723 |     std::stringstream buffer;
724 |     buffer << file.rdbuf();
725 |     file.close();
726 | 
727 |     return load_theme_from_yaml(buffer.str());
728 |   }
729 |   catch (const std::exception &e)
730 |   {
731 |     std::cerr << "Error reading theme file " << file_path << ": " << e.what()
732 |               << std::endl;
733 |     load_default_theme();
734 |     return false;
735 |   }
736 | }
737 | 
738 | // Global instance
739 | StyleManager g_style_manager;
740 | 
741 | // Legacy API functions for backward compatibility
742 | void init_colors() { g_style_manager.initialize(); }
743 | 
744 | void load_default_theme()
745 | {
746 |   if (!g_style_manager.is_initialized())
747 |   {
748 |     g_style_manager.initialize();
749 |   }
750 | }
751 | 
752 | void apply_theme(const Theme &theme)
753 | {
754 |   g_style_manager.apply_legacy_theme(theme);
755 | }
756 | 
```

--------------------------------------------------------------------------------
/deps/tree-sitter-markdown/tree-sitter-markdown/grammar.js:
--------------------------------------------------------------------------------

```javascript
  1 | // This grammar only concerns the block structure according to the CommonMark Spec
  2 | // (https://spec.commonmark.org/0.30/#blocks-and-inlines)
  3 | // For more information see README.md
  4 | 
  5 | /// <reference types="tree-sitter-cli/dsl" />
  6 | 
  7 | const common = require('../common/common');
  8 | 
  9 | const PRECEDENCE_LEVEL_LINK = common.PRECEDENCE_LEVEL_LINK;
 10 | 
 11 | const PUNCTUATION_CHARACTERS_REGEX = '!-/:-@\\[-`\\{-~';
 12 | 
 13 | module.exports = grammar({
 14 |     name: 'markdown',
 15 | 
 16 |     rules: {
 17 |         document: $ => seq(
 18 |             optional(choice(
 19 |                 common.EXTENSION_MINUS_METADATA ? $.minus_metadata : choice(),
 20 |                 common.EXTENSION_PLUS_METADATA ? $.plus_metadata : choice(),
 21 |             )),
 22 |             alias(prec.right(repeat($._block_not_section)), $.section),
 23 |             repeat($.section),
 24 |         ),
 25 | 
 26 |         ...common.rules,
 27 |         _last_token_punctuation: $ => choice(), // needed for compatability with common rules
 28 | 
 29 |         // BLOCK STRUCTURE
 30 | 
 31 |         // All blocks. Every block contains a trailing newline.
 32 |         _block: $ => choice(
 33 |             $._block_not_section,
 34 |             $.section,
 35 |         ),
 36 |         _block_not_section: $ => choice(
 37 |             alias($._setext_heading1, $.setext_heading),
 38 |             alias($._setext_heading2, $.setext_heading),
 39 |             $.paragraph,
 40 |             $.indented_code_block,
 41 |             $.block_quote,
 42 |             $.thematic_break,
 43 |             $.list,
 44 |             $.fenced_code_block,
 45 |             $._blank_line,
 46 |             $.html_block,
 47 |             $.link_reference_definition,
 48 |             common.EXTENSION_PIPE_TABLE ? $.pipe_table : choice(),
 49 |         ),
 50 |         section: $ => choice($._section1, $._section2, $._section3, $._section4, $._section5, $._section6),
 51 |         _section1: $ => prec.right(seq(
 52 |             alias($._atx_heading1, $.atx_heading),
 53 |             repeat(choice(
 54 |                 alias(choice($._section6, $._section5, $._section4, $._section3, $._section2), $.section),
 55 |                 $._block_not_section
 56 |             ))
 57 |         )),
 58 |         _section2: $ => prec.right(seq(
 59 |             alias($._atx_heading2, $.atx_heading),
 60 |             repeat(choice(
 61 |                 alias(choice($._section6, $._section5, $._section4, $._section3), $.section),
 62 |                 $._block_not_section
 63 |             ))
 64 |         )),
 65 |         _section3: $ => prec.right(seq(
 66 |             alias($._atx_heading3, $.atx_heading),
 67 |             repeat(choice(
 68 |                 alias(choice($._section6, $._section5, $._section4), $.section),
 69 |                 $._block_not_section
 70 |             ))
 71 |         )),
 72 |         _section4: $ => prec.right(seq(
 73 |             alias($._atx_heading4, $.atx_heading),
 74 |             repeat(choice(
 75 |                 alias(choice($._section6, $._section5), $.section),
 76 |                 $._block_not_section
 77 |             ))
 78 |         )),
 79 |         _section5: $ => prec.right(seq(
 80 |             alias($._atx_heading5, $.atx_heading),
 81 |             repeat(choice(
 82 |                 alias($._section6, $.section),
 83 |                 $._block_not_section
 84 |             ))
 85 |         )),
 86 |         _section6: $ => prec.right(seq(
 87 |             alias($._atx_heading6, $.atx_heading),
 88 |             repeat($._block_not_section)
 89 |         )),
 90 | 
 91 |         // LEAF BLOCKS
 92 | 
 93 |         // A thematic break. This is currently handled by the external scanner but maybe could be
 94 |         // parsed using normal tree-sitter rules.
 95 |         //
 96 |         // https://github.github.com/gfm/#thematic-breaks
 97 |         thematic_break: $ => seq($._thematic_break, choice($._newline, $._eof)),
 98 | 
 99 |         // An ATX heading. This is currently handled by the external scanner but maybe could be
100 |         // parsed using normal tree-sitter rules.
101 |         //
102 |         // https://github.github.com/gfm/#atx-headings
103 |         _atx_heading1: $ => prec(1, seq(
104 |             $.atx_h1_marker,
105 |             optional($._atx_heading_content),
106 |             $._newline
107 |         )),
108 |         _atx_heading2: $ => prec(1, seq(
109 |             $.atx_h2_marker,
110 |             optional($._atx_heading_content),
111 |             $._newline
112 |         )),
113 |         _atx_heading3: $ => prec(1, seq(
114 |             $.atx_h3_marker,
115 |             optional($._atx_heading_content),
116 |             $._newline
117 |         )),
118 |         _atx_heading4: $ => prec(1, seq(
119 |             $.atx_h4_marker,
120 |             optional($._atx_heading_content),
121 |             $._newline
122 |         )),
123 |         _atx_heading5: $ => prec(1, seq(
124 |             $.atx_h5_marker,
125 |             optional($._atx_heading_content),
126 |             $._newline
127 |         )),
128 |         _atx_heading6: $ => prec(1, seq(
129 |             $.atx_h6_marker,
130 |             optional($._atx_heading_content),
131 |             $._newline
132 |         )),
133 |         _atx_heading_content: $ => prec(1, seq(
134 |             optional($._whitespace),
135 |             field('heading_content', alias($._line, $.inline))
136 |         )),
137 | 
138 |         // A setext heading. The underlines are currently handled by the external scanner but maybe
139 |         // could be parsed using normal tree-sitter rules.
140 |         //
141 |         // https://github.github.com/gfm/#setext-headings
142 |         _setext_heading1: $ => seq(
143 |             field('heading_content', $.paragraph),
144 |             $.setext_h1_underline,
145 |             choice($._newline, $._eof),
146 |         ),
147 |         _setext_heading2: $ => seq(
148 |             field('heading_content', $.paragraph),
149 |             $.setext_h2_underline,
150 |             choice($._newline, $._eof),
151 |         ),
152 | 
153 |         // An indented code block. An indented code block is made up of indented chunks and blank
154 |         // lines. The indented chunks are handeled by the external scanner.
155 |         //
156 |         // https://github.github.com/gfm/#indented-code-blocks
157 |         indented_code_block: $ => prec.right(seq($._indented_chunk, repeat(choice($._indented_chunk, $._blank_line)))),
158 |         _indented_chunk: $ => seq($._indented_chunk_start, repeat(choice($._line, $._newline)), $._block_close, optional($.block_continuation)),
159 | 
160 |         // A fenced code block. Fenced code blocks are mainly handled by the external scanner. In
161 |         // case of backtick code blocks the external scanner also checks that the info string is
162 |         // proper.
163 |         //
164 |         // https://github.github.com/gfm/#fenced-code-blocks
165 |         fenced_code_block: $ => prec.right(choice(
166 |             seq(
167 |                 alias($._fenced_code_block_start_backtick, $.fenced_code_block_delimiter),
168 |                 optional($._whitespace),
169 |                 optional($.info_string),
170 |                 $._newline,
171 |                 optional($.code_fence_content),
172 |                 optional(seq(alias($._fenced_code_block_end_backtick, $.fenced_code_block_delimiter), $._close_block, $._newline)),
173 |                 $._block_close,
174 |             ),
175 |             seq(
176 |                 alias($._fenced_code_block_start_tilde, $.fenced_code_block_delimiter),
177 |                 optional($._whitespace),
178 |                 optional($.info_string),
179 |                 $._newline,
180 |                 optional($.code_fence_content),
181 |                 optional(seq(alias($._fenced_code_block_end_tilde, $.fenced_code_block_delimiter), $._close_block, $._newline)),
182 |                 $._block_close,
183 |             ),
184 |         )),
185 |         code_fence_content: $ => repeat1(choice($._newline, $._line)),
186 |         info_string: $ => choice(
187 |             seq($.language, repeat(choice($._line, $.backslash_escape, $.entity_reference, $.numeric_character_reference))),
188 |             seq(
189 |                 repeat1(choice('{', '}')),
190 |                 optional(choice(
191 |                     seq($.language, repeat(choice($._line, $.backslash_escape, $.entity_reference, $.numeric_character_reference))),
192 |                     seq($._whitespace, repeat(choice($._line, $.backslash_escape, $.entity_reference, $.numeric_character_reference))),
193 |                 ))
194 |             )
195 |         ),
196 |         language: $ => prec.right(repeat1(choice($._word, common.punctuation_without($, ['{', '}', ',']), $.backslash_escape, $.entity_reference, $.numeric_character_reference))),
197 | 
198 |         // An HTML block. We do not emit addition nodes relating to the kind or structure or of the
199 |         // html block as this is best done using language injections and a proper html parsers.
200 |         //
201 |         // See the `build_html_block` function for more information.
202 |         // See the spec for the different kinds of html blocks.
203 |         //
204 |         // https://github.github.com/gfm/#html-blocks
205 |         html_block: $ => prec(1, seq(optional($._whitespace), choice(
206 |             $._html_block_1,
207 |             $._html_block_2,
208 |             $._html_block_3,
209 |             $._html_block_4,
210 |             $._html_block_5,
211 |             $._html_block_6,
212 |             $._html_block_7,
213 |         ))),
214 |         _html_block_1: $ => build_html_block($,
215 |             // new RegExp(
216 |             //     '[ \t]*<' + regex_case_insensitive_list(HTML_TAG_NAMES_RULE_1) + '([\\r\\n]|[ \\t>][^<\\r\\n]*(\\n|\\r\\n?)?)'
217 |             // ),
218 |             $._html_block_1_start,
219 |             $._html_block_1_end,
220 |             true
221 |         ),
222 |         _html_block_2: $ => build_html_block($, $._html_block_2_start, '-->', true),
223 |         _html_block_3: $ => build_html_block($, $._html_block_3_start, '?>', true),
224 |         _html_block_4: $ => build_html_block($, $._html_block_4_start, '>', true),
225 |         _html_block_5: $ => build_html_block($, $._html_block_5_start, ']]>', true),
226 |         _html_block_6: $ => build_html_block(
227 |             $,
228 |             $._html_block_6_start,
229 |             seq($._newline, $._blank_line),
230 |             true
231 |         ),
232 |         _html_block_7: $ => build_html_block(
233 |             $,
234 |             $._html_block_7_start,
235 |             seq($._newline, $._blank_line),
236 |             false
237 |         ),
238 | 
239 |         // A link reference definition. We need to make sure that this is not mistaken for a
240 |         // paragraph or indented chunk. The `$._no_indented_chunk` token is used to tell the
241 |         // external scanner not to allow indented chunks when the `$.link_title` of the link
242 |         // reference definition would be valid.
243 |         //
244 |         // https://github.github.com/gfm/#link-reference-definitions
245 |         link_reference_definition: $ => prec.dynamic(PRECEDENCE_LEVEL_LINK, seq(
246 |             optional($._whitespace),
247 |             $.link_label,
248 |             ':',
249 |             optional(seq(optional($._whitespace), optional(seq($._soft_line_break, optional($._whitespace))))),
250 |             $.link_destination,
251 |             optional(prec.dynamic(2 * PRECEDENCE_LEVEL_LINK, seq(
252 |                 choice(
253 |                     seq($._whitespace, optional(seq($._soft_line_break, optional($._whitespace)))),
254 |                     seq($._soft_line_break, optional($._whitespace)),
255 |                 ),
256 |                 optional($._no_indented_chunk),
257 |                 $.link_title
258 |             ))),
259 |             choice($._newline, $._soft_line_break, $._eof),
260 |         )),
261 |         _text_inline_no_link: $ => choice($._word, $._whitespace, common.punctuation_without($, ['[', ']'])),
262 | 
263 |         // A paragraph. The parsing tactic for deciding when a paragraph ends is as follows:
264 |         // on every newline inside a paragraph a conflict is triggered manually using
265 |         // `$._split_token` to split the parse state into two branches.
266 |         //
267 |         // One of them - the one that also contains a `$._soft_line_break_marker` will try to
268 |         // continue the paragraph, but we make sure that the beginning of a new block that can
269 |         // interrupt a paragraph can also be parsed. If this is the case we know that the paragraph
270 |         // should have been closed and the external parser will emit an `$._error` to kill the parse
271 |         // branch.
272 |         //
273 |         // The other parse branch consideres the paragraph to be over. It will be killed if no valid new
274 |         // block is detected before the next newline. (For example it will also be killed if a indented
275 |         // code block is detected, which cannot interrupt paragraphs).
276 |         //
277 |         // Either way, after the next newline only one branch will exist, so the ammount of branches
278 |         // related to paragraphs ending does not grow.
279 |         //
280 |         // https://github.github.com/gfm/#paragraphs
281 |         paragraph: $ => seq(alias(repeat1(choice($._line, $._soft_line_break)), $.inline), choice($._newline, $._eof)),
282 | 
283 |         // A blank line including the following newline.
284 |         //
285 |         // https://github.github.com/gfm/#blank-lines
286 |         _blank_line: $ => seq($._blank_line_start, choice($._newline, $._eof)),
287 | 
288 | 
289 |         // CONTAINER BLOCKS
290 | 
291 |         // A block quote. This is the most basic example of a container block handled by the
292 |         // external scanner.
293 |         //
294 |         // https://github.github.com/gfm/#block-quotes
295 |         block_quote: $ => seq(
296 |             alias($._block_quote_start, $.block_quote_marker),
297 |             optional($.block_continuation),
298 |             repeat($._block),
299 |             $._block_close,
300 |             optional($.block_continuation)
301 |         ),
302 | 
303 |         // A list. This grammar does not differentiate between loose and tight lists for efficiency
304 |         // reasons.
305 |         //
306 |         // Lists can only contain list items with list markers of the same type. List items are
307 |         // handled by the external scanner.
308 |         //
309 |         // https://github.github.com/gfm/#lists
310 |         list: $ => prec.right(choice(
311 |             $._list_plus,
312 |             $._list_minus,
313 |             $._list_star,
314 |             $._list_dot,
315 |             $._list_parenthesis
316 |         )),
317 |         _list_plus: $ => prec.right(repeat1(alias($._list_item_plus, $.list_item))),
318 |         _list_minus: $ => prec.right(repeat1(alias($._list_item_minus, $.list_item))),
319 |         _list_star: $ => prec.right(repeat1(alias($._list_item_star, $.list_item))),
320 |         _list_dot: $ => prec.right(repeat1(alias($._list_item_dot, $.list_item))),
321 |         _list_parenthesis: $ => prec.right(repeat1(alias($._list_item_parenthesis, $.list_item))),
322 |         // Some list items can not interrupt a paragraph and are marked as such by the external
323 |         // scanner.
324 |         list_marker_plus: $ => choice($._list_marker_plus, $._list_marker_plus_dont_interrupt),
325 |         list_marker_minus: $ => choice($._list_marker_minus, $._list_marker_minus_dont_interrupt),
326 |         list_marker_star: $ => choice($._list_marker_star, $._list_marker_star_dont_interrupt),
327 |         list_marker_dot: $ => choice($._list_marker_dot, $._list_marker_dot_dont_interrupt),
328 |         list_marker_parenthesis: $ => choice($._list_marker_parenthesis, $._list_marker_parenthesis_dont_interrupt),
329 |         _list_item_plus: $ => seq(
330 |             $.list_marker_plus,
331 |             optional($.block_continuation),
332 |             $._list_item_content,
333 |             $._block_close,
334 |             optional($.block_continuation)
335 |         ),
336 |         _list_item_minus: $ => seq(
337 |             $.list_marker_minus,
338 |             optional($.block_continuation),
339 |             $._list_item_content,
340 |             $._block_close,
341 |             optional($.block_continuation)
342 |         ),
343 |         _list_item_star: $ => seq(
344 |             $.list_marker_star,
345 |             optional($.block_continuation),
346 |             $._list_item_content,
347 |             $._block_close,
348 |             optional($.block_continuation)
349 |         ),
350 |         _list_item_dot: $ => seq(
351 |             $.list_marker_dot,
352 |             optional($.block_continuation),
353 |             $._list_item_content,
354 |             $._block_close,
355 |             optional($.block_continuation)
356 |         ),
357 |         _list_item_parenthesis: $ => seq(
358 |             $.list_marker_parenthesis,
359 |             optional($.block_continuation),
360 |             $._list_item_content,
361 |             $._block_close,
362 |             optional($.block_continuation)
363 |         ),
364 |         // List items are closed after two consecutive blank lines
365 |         _list_item_content: $ => choice(
366 |             prec(1, seq(
367 |                 $._blank_line,
368 |                 $._blank_line,
369 |                 $._close_block,
370 |                 optional($.block_continuation)
371 |             )),
372 |             repeat1($._block),
373 |             common.EXTENSION_TASK_LIST ? prec(1, seq(
374 |                 choice($.task_list_marker_checked, $.task_list_marker_unchecked),
375 |                 $._whitespace,
376 |                 $.paragraph,
377 |                 repeat($._block)
378 |             )) : choice()
379 |         ),
380 | 
381 |         // Newlines as in the spec. Parsing a newline triggers the matching process by making
382 |         // the external parser emit a `$._line_ending`.
383 |         _newline: $ => seq(
384 |             $._line_ending,
385 |             optional($.block_continuation)
386 |         ),
387 |         _soft_line_break: $ => seq(
388 |             $._soft_line_ending,
389 |             optional($.block_continuation)
390 |         ),
391 |         // Some symbols get parsed as single tokens so that html blocks get detected properly
392 |         _line: $ => prec.right(repeat1(choice($._word, $._whitespace, common.punctuation_without($, [])))),
393 |         _word: $ => choice(
394 |             new RegExp('[^' + PUNCTUATION_CHARACTERS_REGEX + ' \\t\\n\\r]+'),
395 |             common.EXTENSION_TASK_LIST ? choice(
396 |                 /\[[xX]\]/,
397 |                 /\[[ \t]\]/,
398 |             ) : choice()
399 |         ),
400 |         // The external scanner emits some characters that should just be ignored.
401 |         _whitespace: $ => /[ \t]+/,
402 | 
403 |         ...(common.EXTENSION_TASK_LIST ? {
404 |             task_list_marker_checked: $ => prec(1, /\[[xX]\]/),
405 |             task_list_marker_unchecked: $ => prec(1, /\[[ \t]\]/),
406 |         } : {}),
407 | 
408 |         ...(common.EXTENSION_PIPE_TABLE ? {
409 |             pipe_table: $ => prec.right(seq(
410 |                 $._pipe_table_start,
411 |                 alias($.pipe_table_row, $.pipe_table_header),
412 |                 $._newline,
413 |                 $.pipe_table_delimiter_row,
414 |                 repeat(seq($._pipe_table_newline, optional($.pipe_table_row))),
415 |                 choice($._newline, $._eof),
416 |             )),
417 | 
418 |             _pipe_table_newline: $ => seq(
419 |                 $._pipe_table_line_ending,
420 |                 optional($.block_continuation)
421 |             ),
422 | 
423 |             pipe_table_delimiter_row: $ => seq(
424 |                 optional(seq(
425 |                     optional($._whitespace),
426 |                     '|',
427 |                 )),
428 |                 repeat1(prec.right(seq(
429 |                     optional($._whitespace),
430 |                     $.pipe_table_delimiter_cell,
431 |                     optional($._whitespace),
432 |                     '|',
433 |                 ))),
434 |                 optional($._whitespace),
435 |                 optional(seq(
436 |                     $.pipe_table_delimiter_cell,
437 |                     optional($._whitespace)
438 |                 )),
439 |             ),
440 | 
441 |             pipe_table_delimiter_cell: $ => seq(
442 |                 optional(alias(':', $.pipe_table_align_left)),
443 |                 repeat1('-'),
444 |                 optional(alias(':', $.pipe_table_align_right)),
445 |             ),
446 | 
447 |             pipe_table_row: $ => seq(
448 |                 optional(seq(
449 |                     optional($._whitespace),
450 |                     '|',
451 |                 )),
452 |                 choice(
453 |                     seq(
454 |                         repeat1(prec.right(seq(
455 |                             choice(
456 |                                 seq(
457 |                                     optional($._whitespace),
458 |                                     $.pipe_table_cell,
459 |                                     optional($._whitespace)
460 |                                 ),
461 |                                 alias($._whitespace, $.pipe_table_cell)
462 |                             ),
463 |                             '|',
464 |                         ))),
465 |                         optional($._whitespace),
466 |                         optional(seq(
467 |                             $.pipe_table_cell,
468 |                             optional($._whitespace)
469 |                         )),
470 |                     ),
471 |                     seq(
472 |                         optional($._whitespace),
473 |                         $.pipe_table_cell,
474 |                         optional($._whitespace)
475 |                     )
476 |                 ),
477 |             ),
478 | 
479 |             pipe_table_cell: $ => prec.right(seq(
480 |                 choice(
481 |                     $._word,
482 |                     $._backslash_escape,
483 |                     common.punctuation_without($, ['|']),
484 |                 ),
485 |                 repeat(choice(
486 |                     $._word,
487 |                     $._whitespace,
488 |                     $._backslash_escape,
489 |                     common.punctuation_without($, ['|']),
490 |                 )),
491 |             )),
492 |         } : {}),
493 |     },
494 | 
495 |     externals: $ => [
496 |         // Quite a few of these tokens could maybe be implemented without use of the external parser.
497 |         // For this the `$._open_block` and `$._close_block` tokens should be used to tell the external
498 |         // parser to put a new anonymous block on the block stack.
499 | 
500 |         // Block structure gets parsed as follows: After every newline (`$._line_ending`) we try to match
501 |         // as many open blocks as possible. For example if the last line was part of a block quote we look
502 |         // for a `>` at the beginning of the next line. We emit a `$.block_continuation` for each matched
503 |         // block. For this process the external scanner keeps a stack of currently open blocks.
504 |         //
505 |         // If we are not able to match all blocks that does not necessarily mean that all unmatched blocks
506 |         // have to be closed. It could also mean that the line is a lazy continuation line
507 |         // (https://github.github.com/gfm/#lazy-continuation-line, see also `$._split_token` and
508 |         // `$._soft_line_break_marker` below)
509 |         //
510 |         // If a block does get closed (because it was not matched or because some closing token was
511 |         // encountered) we emit a `$._block_close` token
512 | 
513 |         $._line_ending, // this token does not contain the actual newline characters. see `$._newline`
514 |         $._soft_line_ending,
515 |         $._block_close,
516 |         $.block_continuation,
517 | 
518 |         // Tokens signifying the start of a block. Blocks that do not need a `$._block_close` because they
519 |         // always span one line are marked as such.
520 | 
521 |         $._block_quote_start,
522 |         $._indented_chunk_start,
523 |         $.atx_h1_marker, // atx headings do not need a `$._block_close`
524 |         $.atx_h2_marker,
525 |         $.atx_h3_marker,
526 |         $.atx_h4_marker,
527 |         $.atx_h5_marker,
528 |         $.atx_h6_marker,
529 |         $.setext_h1_underline, // setext headings do not need a `$._block_close`
530 |         $.setext_h2_underline,
531 |         $._thematic_break, // thematic breaks do not need a `$._block_close`
532 |         $._list_marker_minus,
533 |         $._list_marker_plus,
534 |         $._list_marker_star,
535 |         $._list_marker_parenthesis,
536 |         $._list_marker_dot,
537 |         $._list_marker_minus_dont_interrupt, // list items that do not interrupt an ongoing paragraph
538 |         $._list_marker_plus_dont_interrupt,
539 |         $._list_marker_star_dont_interrupt,
540 |         $._list_marker_parenthesis_dont_interrupt,
541 |         $._list_marker_dot_dont_interrupt,
542 |         $._fenced_code_block_start_backtick,
543 |         $._fenced_code_block_start_tilde,
544 |         $._blank_line_start, // Does not contain the newline characters. Blank lines do not need a `$._block_close`
545 | 
546 |         // Special tokens for block structure
547 | 
548 |         // Closing backticks or tildas for a fenced code block. They are used to trigger a `$._close_block`
549 |         // which in turn will trigger a `$._block_close` at the beginning the following line.
550 |         $._fenced_code_block_end_backtick,
551 |         $._fenced_code_block_end_tilde,
552 | 
553 |         $._html_block_1_start,
554 |         $._html_block_1_end,
555 |         $._html_block_2_start,
556 |         $._html_block_3_start,
557 |         $._html_block_4_start,
558 |         $._html_block_5_start,
559 |         $._html_block_6_start,
560 |         $._html_block_7_start,
561 | 
562 |         // Similarly this is used if the closing of a block is not decided by the external parser.
563 |         // A `$._block_close` will be emitted at the beginning of the next line. Notice that a
564 |         // `$._block_close` can also get emitted if the parent block closes.
565 |         $._close_block,
566 | 
567 |         // This is a workaround so the external parser does not try to open indented blocks when
568 |         // parsing a link reference definition.
569 |         $._no_indented_chunk,
570 | 
571 |         // An `$._error` token is never valid  and gets emmited to kill invalid parse branches. Concretely
572 |         // this is used to decide wether a newline closes a paragraph and together and it gets emitted
573 |         // when trying to parse the `$._trigger_error` token in `$.link_title`.
574 |         $._error,
575 |         $._trigger_error,
576 |         $._eof,
577 | 
578 |         $.minus_metadata,
579 |         $.plus_metadata,
580 | 
581 |         $._pipe_table_start,
582 |         $._pipe_table_line_ending,
583 |     ],
584 |     precedences: $ => [
585 |         [$._setext_heading1, $._block],
586 |         [$._setext_heading2, $._block],
587 |         [$.indented_code_block, $._block],
588 |     ],
589 |     conflicts: $ => [
590 |         [$.link_reference_definition],
591 |         [$.link_label, $._line],
592 |         [$.link_reference_definition, $._line],
593 |     ],
594 |     extras: $ => [],
595 | });
596 | 
597 | // General purpose structure for html blocks. The different kinds mostly work the same but have
598 | // different openling and closing conditions. Some html blocks may not interrupt a paragraph and
599 | // have to be marked as such.
600 | function build_html_block($, open, close, interrupt_paragraph) {
601 |     return seq(
602 |         open,
603 |         repeat(choice(
604 |             $._line,
605 |             $._newline,
606 |             seq(close, $._close_block),
607 |         )),
608 |         $._block_close,
609 |         optional($.block_continuation),
610 |     );
611 | }
612 | 
```
Page 3/10FirstPrevNextLast