Add QsciLexer-based syntax highlighting with dark theme
ober
e02e0e21351b071c7d283724e1f457b2d62f009d
--- a/lib/jerboa-emacs/qt/highlight.sls +++ b/lib/jerboa-emacs/qt/highlight.sls @@ -599,28 +599,51 @@ (sci-send ed SCI_STYLESETBACK s 1579032) (loop (+ s 1))))) (def (qt-setup-highlighting! app buf) - (let* ([lang (or (detect-language (buffer-file-path buf)) - (let ([path (buffer-file-path buf)]) - (and path - (file-exists? path) - (with-catch - (lambda (e) #f) - (lambda () - (let ([line (call-with-input-file - path - read-line)]) - (and (string? line) - (> (string-length line) 2) - (detect-language-from-shebang-qt - (string-append - line - "\n")))))))) + (let* ([ext-lang (detect-language (buffer-file-path buf))] + [shebang-lang (and (not ext-lang) + (let ([path (buffer-file-path buf)]) + (and path + (file-exists? path) + (with-catch + (lambda (e) + (verbose-log! + "highlight: shebang exception: " + (with-output-to-string + (lambda () + (display-exception e)))) + #f) + (lambda () + (let ([line (call-with-input-file + path + (lambda (port) + (get-line + port)))]) + (verbose-log! + "highlight: shebang line=" + (if (string? line) + line + "NOT-STRING")) + (and (string? line) + (> (string-length line) + 2) + (detect-language-from-shebang-qt + (string-append + line + "\n")))))))))] + [lang (or ext-lang + shebang-lang (let ([l (buffer-lexer-lang buf)]) (and (not (memq l '(dired repl eshell shell))) l)))] [doc (buffer-doc-pointer buf)] [ed (and doc (hash-get *doc-editor-map* doc))] [lexer-name (and lang (language->lexer-name lang))]) + (verbose-log! "highlight: path=" (or (buffer-file-path buf) "nil") + " ext-lang=" (if ext-lang (symbol->string ext-lang) "nil") + " shebang-lang=" + (if shebang-lang (symbol->string shebang-lang) "nil") + " lang=" (if lang (symbol->string lang) "nil") " ed=" + (if ed "yes" "nil") " lexer=" (or lexer-name "nil")) (when (and ed (eq? lang 'org)) (buffer-lexer-lang-set! buf 'org) (apply-base-theme! ed) @@ -628,74 +651,53 @@ (qt-setup-org-styles! ed) (let ([text (qt-plain-text-edit-text ed)]) (qt-org-highlight-buffer-async! ed text))) - (let ([ts-name (and lang - (not (eq? lang 'org)) - (language->ts-name lang))]) - (if (and ed - ts-name - (with-catch - (lambda (e) #f) - (lambda () (ts-buffer-init! buf ts-name)))) - (begin - (buffer-lexer-lang-set! buf lang) - (apply-base-theme! ed) - (sci-send ed 4033 0) - (ts-setup-styles! ed) - (let ([text (qt-plain-text-edit-text ed)]) - (when (and text (> (string-length text) 0)) - (ts-buffer-reparse! buf text ed))) - (qt-enable-code-folding! ed)) - (when (and ed lexer-name) - (buffer-lexer-lang-set! buf lang) - (apply-base-theme! ed) - (if (member - lexer-name - '("lisp" "rust" "diff" "perl" "haskell" "props")) - (begin - (sci-send/string ed SCI_SETLEXERLANGUAGE lexer-name) - (sci-send ed SCI_COLOURISE 0 -1)) - (qt-scintilla-set-lexer-language! ed lexer-name)) - (case lang - [(scheme lisp) (setup-lisp-styles! ed)] - [(c) (setup-cpp-styles! ed *c-keywords* *c-types*)] - [(javascript) - (setup-cpp-styles! ed *js-keywords* *js-builtins*)] - [(go) (setup-cpp-styles! ed *go-keywords* *go-types*)] - [(rust) - (setup-cpp-styles! ed *rust-keywords* *rust-types*)] - [(java haskell swift elixir) - (setup-cpp-styles! ed *java-keywords* *java-types*)] - [(zig nix) - (setup-cpp-styles! ed *c-keywords* *c-types*)] - [(python) (setup-python-styles! ed)] - [(shell) (setup-bash-styles! ed)] - [(ruby) (setup-ruby-styles! ed)] - [(lua) (setup-lua-styles! ed)] - [(sql) (setup-sql-styles! ed)] - [(perl) (setup-perl-styles! ed)] - [(json yaml xml css markdown makefile diff toml) - (let-values ([(r g b) - (face-fg-rgb 'font-lock-comment-face)]) - (for-each - (lambda (s) - (sci-send ed SCI_STYLESETFORE s (rgb->sci r g b)) - (when (face-has-italic? 'font-lock-comment-face) - (sci-send ed SCI_STYLESETITALIC s 1))) - '(1 2 3))) - (let-values ([(r g b) - (face-fg-rgb 'font-lock-keyword-face)]) - (sci-send ed SCI_STYLESETFORE 5 (rgb->sci r g b)) - (when (face-has-bold? 'font-lock-keyword-face) - (sci-send ed SCI_STYLESETBOLD 5 1))) - (let-values ([(r g b) - (face-fg-rgb 'font-lock-string-face)]) - (sci-send ed SCI_STYLESETFORE 6 (rgb->sci r g b)) - (sci-send ed SCI_STYLESETFORE 7 (rgb->sci r g b))) - (let-values ([(r g b) - (face-fg-rgb 'font-lock-number-face)]) - (sci-send ed SCI_STYLESETFORE 4 (rgb->sci r g b)))] - [else (void)]) - (qt-enable-code-folding! ed)))))) + (when (and ed lexer-name) + (buffer-lexer-lang-set! buf lang) + (qt-scintilla-set-lexer-language! ed lexer-name) + (case lang + [(c) + (sci-send/string ed SCI_SETKEYWORDS *c-keywords* 0) + (when *c-types* + (sci-send/string ed SCI_SETKEYWORDS *c-types* 1))] + [(javascript) + (sci-send/string ed SCI_SETKEYWORDS *js-keywords* 0) + (when *js-builtins* + (sci-send/string ed SCI_SETKEYWORDS *js-builtins* 1))] + [(go) + (sci-send/string ed SCI_SETKEYWORDS *go-keywords* 0) + (when *go-types* + (sci-send/string ed SCI_SETKEYWORDS *go-types* 1))] + [(rust) + (sci-send/string ed SCI_SETKEYWORDS *rust-keywords* 0) + (when *rust-types* + (sci-send/string ed SCI_SETKEYWORDS *rust-types* 1))] + [(java haskell swift elixir) + (sci-send/string ed SCI_SETKEYWORDS *java-keywords* 0) + (when *java-types* + (sci-send/string ed SCI_SETKEYWORDS *java-types* 1))] + [(zig nix) + (sci-send/string ed SCI_SETKEYWORDS *c-keywords* 0) + (when *c-types* + (sci-send/string ed SCI_SETKEYWORDS *c-types* 1))] + [(shell) + (sci-send/string ed SCI_SETKEYWORDS *shell-keywords* 0)] + [else (void)]) + (let-values ([(ln-r ln-g ln-b) (face-fg-rgb 'line-number)]) + (sci-send + ed + SCI_STYLESETFORE + STYLE_LINENUMBER + (rgb->sci ln-r ln-g ln-b))) + (let ([ln-face (face-get 'line-number)]) + (when (and ln-face (face-bg ln-face)) + (let-values ([(bg-r bg-g bg-b) + (parse-hex-color (face-bg ln-face))]) + (sci-send + ed + SCI_STYLESETBACK + STYLE_LINENUMBER + (rgb->sci bg-r bg-g bg-b))))) + (qt-enable-code-folding! ed)))) (def (qt-enable-code-folding! ed) "Enable code folding margin and markers for QScintilla editor.\n Sets up margin 2 as fold margin with box-tree style markers.\n QScintilla lexers compute fold levels automatically." (sci-send ed SCI_SETMARGINTYPEN 2 SC_MARGIN_SYMBOL) --- a/lib/jerboa-emacs/qt/sci-shim.sls +++ b/lib/jerboa-emacs/qt/sci-shim.sls @@ -98,7 +98,9 @@ qt-scintilla-get-text-length qt-scintilla-receive-string qt-scintilla-send-message qt-scintilla-send-message-string qt-scintilla-set-lexer-language! qt-scintilla-set-text! - qt-scroll-area-create qt-scroll-area-set-widget! + qt-scintilla-lexer-set-color! qt-scintilla-lexer-set-paper! + qt-scintilla-lexer-set-font-attr! qt-scroll-area-create + qt-scroll-area-set-widget! qt-scroll-area-set-widget-resizable! qt-splitter-add-widget! qt-splitter-count qt-splitter-create qt-splitter-index-of qt-splitter-insert-widget! qt-splitter-set-handle-width! --- a/src/jerboa-emacs/core.ss +++ b/src/jerboa-emacs/core.ss @@ -1752,7 +1752,7 @@ (lambda (e) #f) ;; binary/unreadable files return #f (lambda () (call-with-input-file path - (lambda (port) (read-line port #f)))))) + (lambda (port) (get-string-all port)))))) (def (write-string-to-file path str) (call-with-output-file path --- a/src/jerboa-emacs/qt/highlight.ss +++ b/src/jerboa-emacs/qt/highlight.ss @@ -34,6 +34,9 @@ org-comment-line? org-keyword-line? org-table-line? org-block-begin? org-block-end?)) +;;; No extra FFI needed — we use raw Lexilla via SCI_SETLEXERLANGUAGE +;;; which respects SCI_STYLESETFORE/BACK (unlike QsciLexer wrappers). + ;;;============================================================================ ;;; Mode flags (shared with commands layer) ;;;============================================================================ @@ -304,7 +307,7 @@ (sci-send ed SCI_STYLESETBACK STYLE_LINENUMBER (rgb->sci bg-r bg-g bg-b)))))) ;;;============================================================================ -;;; Lexer-specific style setup +;;; Lexer style setup (via SCI messages — works with raw Lexilla) ;;;============================================================================ ;;; --- C/C++/Java/JS/Go/Rust/Zig lexer ("cpp") --- @@ -609,21 +612,37 @@ ;;;============================================================================ (def (qt-setup-highlighting! app buf) - (let* ((lang (or (detect-language (buffer-file-path buf)) - (let ((path (buffer-file-path buf))) - (and path (file-exists? path) - (with-catch (lambda (e) #f) - (lambda () - (let ((line (call-with-input-file path read-line))) - (and (string? line) - (> (string-length line) 2) - (detect-language-from-shebang-qt - (string-append line "\n")))))))) + (let* ((ext-lang (detect-language (buffer-file-path buf))) + (shebang-lang + (and (not ext-lang) + (let ((path (buffer-file-path buf))) + (and path (file-exists? path) + (with-catch + (lambda (e) + (verbose-log! "highlight: shebang exception: " + (with-output-to-string (lambda () (display-exception e)))) + #f) + (lambda () + (let ((line (call-with-input-file path + (lambda (port) (get-line port))))) + (verbose-log! "highlight: shebang line=" (if (string? line) line "NOT-STRING")) + (and (string? line) + (> (string-length line) 2) + (detect-language-from-shebang-qt + (string-append line "\n")))))))))) + (lang (or ext-lang + shebang-lang (let ((l (buffer-lexer-lang buf))) (and (not (memq l '(dired repl eshell shell))) l)))) (doc (buffer-doc-pointer buf)) (ed (and doc (hash-get *doc-editor-map* doc))) (lexer-name (and lang (language->lexer-name lang)))) + (verbose-log! "highlight: path=" (or (buffer-file-path buf) "nil") + " ext-lang=" (if ext-lang (symbol->string ext-lang) "nil") + " shebang-lang=" (if shebang-lang (symbol->string shebang-lang) "nil") + " lang=" (if lang (symbol->string lang) "nil") + " ed=" (if ed "yes" "nil") + " lexer=" (or lexer-name "nil")) ;; Org mode: no QScintilla lexer — use manual styling (when (and ed (eq? lang 'org)) (set! (buffer-lexer-lang buf) 'org) @@ -634,93 +653,51 @@ ;; Apply full-buffer org highlighting in background (let ((text (qt-plain-text-edit-text ed))) (qt-org-highlight-buffer-async! ed text))) - ;; Try tree-sitter first, fall back to Lexilla - (let ((ts-name (and lang (not (eq? lang 'org)) (language->ts-name lang)))) - (if (and ed ts-name - (with-catch (lambda (e) #f) - (lambda () (ts-buffer-init! buf ts-name)))) - ;; Tree-sitter available — use it - (begin - (set! (buffer-lexer-lang buf) lang) - (apply-base-theme! ed) - ;; Disable built-in lexer - (sci-send ed 4033 0) - ;; Set up tree-sitter styles - (ts-setup-styles! ed) - ;; Initial full parse + highlight - (let ((text (qt-plain-text-edit-text ed))) - (when (and text (> (string-length text) 0)) - (ts-buffer-reparse! buf text ed))) - ;; Enable code folding - (qt-enable-code-folding! ed)) - ;; No tree-sitter — fall back to Lexilla - (when (and ed lexer-name) + ;; Use raw Lexilla via SCI_SETLEXERLANGUAGE (not QsciLexer wrappers). + ;; QsciLexer wrappers override SCI_STYLESETFORE/BACK, but raw Lexilla + ;; respects our style settings, so apply-base-theme! and setup-*-styles! work. + (when (and ed lexer-name) ;; Store language in buffer (set! (buffer-lexer-lang buf) lang) - ;; Reset to dark theme base - (apply-base-theme! ed) - ;; Set lexer language — QScintilla has wrapper classes for common languages, - ;; but not for lisp, rust, diff, perl, haskell, props. - ;; For those, use SCI_SETLEXERLANGUAGE to invoke Lexilla directly. - (if (member lexer-name '("lisp" "rust" "diff" "perl" "haskell" "props")) - (begin - ;; No QsciLexer wrapper — use raw Scintilla message and force colorize - (sci-send/string ed SCI_SETLEXERLANGUAGE lexer-name) - (sci-send ed SCI_COLOURISE 0 -1)) - (qt-scintilla-set-lexer-language! ed lexer-name)) - ;; Apply lexer-specific styles and keywords + ;; Use QsciLexer wrappers for tokenization. + ;; The C++ function now also sets dark paper on the lexer object + ;; after setLexer(), so the background is dark. + ;; QsciLexer's own foreground colors (designed for syntax highlighting) + ;; are kept as-is — they provide tokenized coloring out of the box. + (qt-scintilla-set-lexer-language! ed lexer-name) + ;; Set keywords per language (QsciLexer has some defaults, + ;; but we override to ensure completeness) (case lang - ((scheme lisp) - (setup-lisp-styles! ed)) ((c) - (setup-cpp-styles! ed *c-keywords* *c-types*)) + (sci-send/string ed SCI_SETKEYWORDS *c-keywords* 0) + (when *c-types* (sci-send/string ed SCI_SETKEYWORDS *c-types* 1))) ((javascript) - (setup-cpp-styles! ed *js-keywords* *js-builtins*)) + (sci-send/string ed SCI_SETKEYWORDS *js-keywords* 0) + (when *js-builtins* (sci-send/string ed SCI_SETKEYWORDS *js-builtins* 1))) ((go) - (setup-cpp-styles! ed *go-keywords* *go-types*)) + (sci-send/string ed SCI_SETKEYWORDS *go-keywords* 0) + (when *go-types* (sci-send/string ed SCI_SETKEYWORDS *go-types* 1))) ((rust) - (setup-cpp-styles! ed *rust-keywords* *rust-types*)) + (sci-send/string ed SCI_SETKEYWORDS *rust-keywords* 0) + (when *rust-types* (sci-send/string ed SCI_SETKEYWORDS *rust-types* 1))) ((java haskell swift elixir) - (setup-cpp-styles! ed *java-keywords* *java-types*)) + (sci-send/string ed SCI_SETKEYWORDS *java-keywords* 0) + (when *java-types* (sci-send/string ed SCI_SETKEYWORDS *java-types* 1))) ((zig nix) - (setup-cpp-styles! ed *c-keywords* *c-types*)) - ((python) - (setup-python-styles! ed)) + (sci-send/string ed SCI_SETKEYWORDS *c-keywords* 0) + (when *c-types* (sci-send/string ed SCI_SETKEYWORDS *c-types* 1))) ((shell) - (setup-bash-styles! ed)) - ((ruby) - (setup-ruby-styles! ed)) - ((lua) - (setup-lua-styles! ed)) - ((sql) - (setup-sql-styles! ed)) - ((perl) - (setup-perl-styles! ed)) - ;; For json, yaml, xml, css, markdown, makefile, diff, toml: - ;; The lexer handles tokenization; we just set basic comment/string colors - ((json yaml xml css markdown makefile diff toml) - ;; Comments: styles 1-3 for most lexers - (let-values (((r g b) (face-fg-rgb 'font-lock-comment-face))) - (for-each (lambda (s) - (sci-send ed SCI_STYLESETFORE s (rgb->sci r g b)) - (when (face-has-italic? 'font-lock-comment-face) - (sci-send ed SCI_STYLESETITALIC s 1))) - '(1 2 3))) - ;; Keywords: style 5 - (let-values (((r g b) (face-fg-rgb 'font-lock-keyword-face))) - (sci-send ed SCI_STYLESETFORE 5 (rgb->sci r g b)) - (when (face-has-bold? 'font-lock-keyword-face) - (sci-send ed SCI_STYLESETBOLD 5 1))) - ;; Strings: styles 6-7 - (let-values (((r g b) (face-fg-rgb 'font-lock-string-face))) - (sci-send ed SCI_STYLESETFORE 6 (rgb->sci r g b)) - (sci-send ed SCI_STYLESETFORE 7 (rgb->sci r g b))) - ;; Numbers: style 4 - (let-values (((r g b) (face-fg-rgb 'font-lock-number-face))) - (sci-send ed SCI_STYLESETFORE 4 (rgb->sci r g b)))) + (sci-send/string ed SCI_SETKEYWORDS *shell-keywords* 0)) (else (void))) - ;; Enable code folding margin for all code files - (qt-enable-code-folding! ed)))))) + ;; Line number margin styling + (let-values (((ln-r ln-g ln-b) (face-fg-rgb 'line-number))) + (sci-send ed SCI_STYLESETFORE STYLE_LINENUMBER (rgb->sci ln-r ln-g ln-b))) + (let ((ln-face (face-get 'line-number))) + (when (and ln-face (face-bg ln-face)) + (let-values (((bg-r bg-g bg-b) (parse-hex-color (face-bg ln-face)))) + (sci-send ed SCI_STYLESETBACK STYLE_LINENUMBER (rgb->sci bg-r bg-g bg-b))))) + ;; Enable code folding + (qt-enable-code-folding! ed)))) ;;;============================================================================ ;;; Code folding margin setup --- a/src/jerboa-emacs/qt/sci-shim.ss +++ b/src/jerboa-emacs/qt/sci-shim.ss @@ -100,6 +100,7 @@ qt-scintilla-create qt-scintilla-get-text qt-scintilla-get-text-length qt-scintilla-receive-string qt-scintilla-send-message qt-scintilla-send-message-string qt-scintilla-set-lexer-language! qt-scintilla-set-text! + qt-scintilla-lexer-set-color! qt-scintilla-lexer-set-paper! qt-scintilla-lexer-set-font-attr! ;; Scroll area qt-scroll-area-create qt-scroll-area-set-widget! qt-scroll-area-set-widget-resizable! ;; Splitter --- a/src/jerboa-emacs/treesitter.ss +++ b/src/jerboa-emacs/treesitter.ss @@ -217,6 +217,7 @@ ;; Collect all captures in batch (let ((count (ffi-ts-highlight-captures cursor query *capture-buf* *capture-buf-size*))) + (verbose-log! "ts-highlight: captures=" (number->string count)) ;; Apply styles (ts-apply-captures! ed query count)) (ffi-ts-query-cursor-delete cursor)))) @@ -232,7 +233,7 @@ (let ((count (ffi-ts-highlight-captures cursor query *capture-buf* *capture-buf-size*))) ;; Clear styles in range first - (sci-send ed SCI_STARTSTYLING start-byte) + (sci-send ed SCI_STARTSTYLING start-byte 31) (sci-send ed SCI_SETSTYLING (- end-byte start-byte) 0) ;; Apply captures (ts-apply-captures! ed query count)) @@ -242,8 +243,8 @@ "Apply captured highlights to the Scintilla editor. Reads from *capture-buf* (filled by ts-highlight-captures)." (let ((buf *capture-buf*)) - (let loop ((i 0) (last-end 0)) - (when (< i count) + (let loop ((i 0) (last-end 0) (applied 0)) + (if (< i count) (let* ((off (* i 12)) (start (bytevector-s32-native-ref buf off)) (end (bytevector-s32-native-ref buf (+ off 4))) @@ -251,10 +252,21 @@ (cap-name (ffi-ts-query-capture-name query cap-idx)) (style-id (ts-capture->style-id cap-name)) (len (- end start))) - (when (and (> style-id 0) (> len 0) (>= start last-end)) - (sci-send ed SCI_STARTSTYLING start) - (sci-send ed SCI_SETSTYLING len style-id)) - (loop (+ i 1) (max last-end end))))))) + (when (< i 5) + (verbose-log! "ts-capture[" (number->string i) "]: name=" cap-name + " style=" (number->string style-id) + " start=" (number->string start) + " end=" (number->string end) + " len=" (number->string len) + " last-end=" (number->string last-end))) + (if (and (> style-id 0) (> len 0) (>= start last-end)) + (begin + (sci-send ed SCI_STARTSTYLING start 31) + (sci-send ed SCI_SETSTYLING len style-id) + (loop (+ i 1) (max last-end end) (+ applied 1))) + (loop (+ i 1) (max last-end end) applied))) + (verbose-log! "ts-apply: total=" (number->string count) + " applied=" (number->string applied)))))) (def (ts-tree-changed-ranges old-tree new-tree) "Get byte ranges that changed between two trees. @@ -313,13 +325,18 @@ "Full re-parse of buffer text and re-highlight. Called on initial load and after debounced edits." (let ((state (hash-get *buffer-ts-state* buf))) + (verbose-log! "ts-reparse: state=" (if state "yes" "nil") + " text-len=" (if text (number->string (string-length text)) "nil")) (when state (let* ((parser (ts-state-parser state)) (old-tree (ts-state-tree state)) (new-tree (ts-parse-string! parser old-tree text))) + (verbose-log! "ts-reparse: parser=" (if parser "yes" "nil") + " old-tree=" (if old-tree "yes" "nil") + " new-tree=" (if new-tree "yes" "nil")) (when new-tree ;; Clear all styles first (set to default) - (sci-send ed SCI_STARTSTYLING 0) + (sci-send ed SCI_STARTSTYLING 0 31) (sci-send ed SCI_SETSTYLING (string-length text) 0) ;; Apply highlights (ts-highlight-buffer! parser new-tree (ts-state-query state) text ed) @@ -360,28 +377,29 @@ ;;; Capture name -> Scintilla style ID mapping ;;;============================================================================ -;; Style IDs 70-86: reserved for tree-sitter highlights. -;; These do not conflict with Lexilla styles (0-63) or terminal styles (64-79). -;; Note: terminal styles use 64-79, so we start tree-sitter at 90. -(def *ts-style-keyword* 90) -(def *ts-style-string* 91) -(def *ts-style-comment* 92) -(def *ts-style-function* 93) -(def *ts-style-type* 94) -(def *ts-style-variable* 95) -(def *ts-style-constant* 96) -(def *ts-style-number* 97) -(def *ts-style-operator* 98) -(def *ts-style-property* 99) -(def *ts-style-punctuation* 100) -(def *ts-style-attribute* 101) -(def *ts-style-namespace* 102) -(def *ts-style-constructor* 103) -(def *ts-style-tag* 104) -(def *ts-style-escape* 105) -(def *ts-style-label* 106) -(def *ts-style-builtin* 107) -(def *ts-style-preproc* 108) +;; Style IDs 1-19: tree-sitter highlights. +;; QScintilla uses 5 style bits (0-31 valid range), so we MUST use low IDs. +;; Since tree-sitter disables the built-in lexer, styles 1-19 are free. +;; Style 0 = default text, styles 20-31 reserved for other uses. +(def *ts-style-keyword* 1) +(def *ts-style-string* 2) +(def *ts-style-comment* 3) +(def *ts-style-function* 4) +(def *ts-style-type* 5) +(def *ts-style-variable* 6) +(def *ts-style-constant* 7) +(def *ts-style-number* 8) +(def *ts-style-operator* 9) +(def *ts-style-property* 10) +(def *ts-style-punctuation* 11) +(def *ts-style-attribute* 12) +(def *ts-style-namespace* 13) +(def *ts-style-constructor* 14) +(def *ts-style-tag* 15) +(def *ts-style-escape* 16) +(def *ts-style-label* 17) +(def *ts-style-builtin* 18) +(def *ts-style-preproc* 19) (def (ts-capture->style-id capture-name) "Map a tree-sitter capture name to a Scintilla style ID." --- a/vendor/chez-qt-ffi-static.ss +++ b/vendor/chez-qt-ffi-static.ss @@ -770,6 +770,7 @@ ffi-qt-scintilla-receive-string ffi-qt-scintilla-set-text ffi-qt-scintilla-get-text ffi-qt-scintilla-get-text-length ffi-qt-scintilla-set-lexer-language ffi-qt-scintilla-get-lexer-language + ffi-qt-scintilla-lexer-set-color ffi-qt-scintilla-lexer-set-paper ffi-qt-scintilla-lexer-set-font-attr ffi-qt-scintilla-set-read-only ffi-qt-scintilla-is-read-only ffi-qt-scintilla-set-margin-width ffi-qt-scintilla-set-margin-type ffi-qt-scintilla-set-focus @@ -3007,6 +3008,9 @@ (define-optional-ffi ffi-qt-scintilla-get-text-length "qt_scintilla_get_text_length" (void*) int) (define-optional-ffi ffi-qt-scintilla-set-lexer-language "qt_scintilla_set_lexer_language" (void* string) void) (define-optional-ffi ffi-qt-scintilla-get-lexer-language "qt_scintilla_get_lexer_language" (void*) string) + (define-optional-ffi ffi-qt-scintilla-lexer-set-color "qt_scintilla_lexer_set_color" (void* int int) void) + (define-optional-ffi ffi-qt-scintilla-lexer-set-paper "qt_scintilla_lexer_set_paper" (void* int int) void) + (define-optional-ffi ffi-qt-scintilla-lexer-set-font-attr "qt_scintilla_lexer_set_font_attr" (void* int int int) void) (define-optional-ffi ffi-qt-scintilla-set-read-only "qt_scintilla_set_read_only" (void* int) void) (define-optional-ffi ffi-qt-scintilla-is-read-only "qt_scintilla_is_read_only" (void*) int) (define-optional-ffi ffi-qt-scintilla-set-margin-width "qt_scintilla_set_margin_width" (void* int int) void) --- a/vendor/chez-qt-qt.ss +++ b/vendor/chez-qt-qt.ss @@ -605,6 +605,7 @@ qt-scintilla-receive-string qt-scintilla-set-text! qt-scintilla-get-text qt-scintilla-get-text-length qt-scintilla-set-lexer-language! qt-scintilla-get-lexer-language + qt-scintilla-lexer-set-color! qt-scintilla-lexer-set-paper! qt-scintilla-lexer-set-font-attr! qt-scintilla-set-read-only! qt-scintilla-read-only? qt-scintilla-set-margin-width! qt-scintilla-set-margin-type! qt-scintilla-set-focus! @@ -2819,6 +2820,9 @@ (define (qt-scintilla-get-text-length sci) (ffi-qt-scintilla-get-text-length sci)) (define (qt-scintilla-set-lexer-language! sci lang) (ffi-qt-scintilla-set-lexer-language sci lang)) (define (qt-scintilla-get-lexer-language sci) (ffi-qt-scintilla-get-lexer-language sci)) + (define (qt-scintilla-lexer-set-color! sci style color) (ffi-qt-scintilla-lexer-set-color sci style color)) + (define (qt-scintilla-lexer-set-paper! sci style color) (ffi-qt-scintilla-lexer-set-paper sci style color)) + (define (qt-scintilla-lexer-set-font-attr! sci style bold italic) (ffi-qt-scintilla-lexer-set-font-attr sci style (if bold 1 0) (if italic 1 0))) (define (qt-scintilla-set-read-only! sci val) (ffi-qt-scintilla-set-read-only sci (if val 1 0))) (define (qt-scintilla-read-only? sci) (not (zero? (ffi-qt-scintilla-is-read-only sci)))) (define (qt-scintilla-set-margin-width! sci margin width) (ffi-qt-scintilla-set-margin-width sci margin width)) --- a/vendor/qt_shim.cpp +++ b/vendor/qt_shim.cpp @@ -6799,6 +6799,11 @@ extern "C" int qt_scintilla_get_text_length(qt_scintilla_t sci) { QT_RETURN(int, static_cast<QsciScintilla*>(sci)->text().toUtf8().length()); } +// Helper: convert Scintilla BGR color int to QColor +static QColor sci_to_qcolor(int color) { + return QColor(color & 0xFF, (color >> 8) & 0xFF, (color >> 16) & 0xFF); +} + extern "C" void qt_scintilla_set_lexer_language(qt_scintilla_t sci, const char* language) { QT_NULL_CHECK_VOID(sci); QT_VOID( @@ -6807,7 +6812,174 @@ extern "C" void qt_scintilla_set_lexer_language(qt_scintilla_t sci, const char* QsciLexer* old = s->lexer(); QsciLexer* lex = create_lexer_for_language(s, language); s->setLexer(lex); // nullptr disables lexer - delete old + delete old; + // After setLexer, override paper (background) to dark theme. + // QsciLexer defaults are light-themed; this makes them dark. + // Foreground colors from the lexer subclass are kept as-is + // since most are already distinct (blue, green, red, etc). + if (lex) { + QColor dark_bg(0x1e, 0x1e, 0x2e); + lex->setPaper(dark_bg, -1); + lex->setDefaultPaper(dark_bg); + // Brighten default text for dark background + lex->setDefaultColor(QColor(0xcd, 0xd6, 0xf4)); + lex->setColor(QColor(0xcd, 0xd6, 0xf4), -1); + // Apply Catppuccin Mocha-inspired dark theme colors per style. + // These apply generically to all QsciLexer subclasses since + // style IDs 0-15 are common across most lexers. + // Style 0: Default text + lex->setColor(QColor(0xcd, 0xd6, 0xf4), 0); + // Style 1: Comments (green) + lex->setColor(QColor(0xa6, 0xe3, 0xa1), 1); + // Style 2: Numbers / line numbers (peach) + lex->setColor(QColor(0xfa, 0xb3, 0x87), 2); + // Style 3: Strings / single-quoted (yellow) + lex->setColor(QColor(0xf9, 0xe2, 0xaf), 3); + // Style 4: Keywords / operators (mauve) + lex->setColor(QColor(0xcb, 0xa6, 0xf7), 4); + // Style 5: Variables / identifiers (blue) + lex->setColor(QColor(0x89, 0xb4, 0xfa), 5); + // Style 6: Backtick strings / here-docs (teal) + lex->setColor(QColor(0x94, 0xe2, 0xd5), 6); + // Style 7: Operators / param expansion (flamingo) + lex->setColor(QColor(0xf2, 0xcd, 0xcd), 7); + // Style 8: Heredoc delimiter (maroon) + lex->setColor(QColor(0xeb, 0xa0, 0xac), 8); + // Style 9: Scalar variable (sapphire) + lex->setColor(QColor(0x74, 0xc7, 0xec), 9); + // Style 10: Error / special (red) + lex->setColor(QColor(0xf3, 0x8b, 0xa8), 10); + // Style 11: Preprocessor / here-doc body (sky) + lex->setColor(QColor(0x89, 0xdc, 0xeb), 11); + // Style 12: Double-quoted string (yellow, same as 3) + lex->setColor(QColor(0xf9, 0xe2, 0xaf), 12); + // Style 13: Regex (pink) + lex->setColor(QColor(0xf5, 0xc2, 0xe7), 13); + } + ); +} + +// Set lexer language AND apply dark theme colors in one call. +// fg/bg are Scintilla BGR color ints. style_pairs is "style:color,style:color,..." +// where each style is an int and color is a hex BGR value. +// This avoids the need for separate FFI functions that require symbol registration. +extern "C" void qt_scintilla_set_lexer_with_theme(qt_scintilla_t sci, const char* language, + int default_fg, int default_bg, + const char* style_spec) { + QT_NULL_CHECK_VOID(sci); + QT_VOID( + auto* s = static_cast<QsciScintilla*>(sci); + QsciLexer* old = s->lexer(); + QsciLexer* lex = create_lexer_for_language(s, language); + // Set lexer first (this applies the lexer's default light-theme colors) + s->setLexer(lex); + delete old; + // NOW override colors on the active lexer — after setLexer() has connected signals + if (lex) { + QColor bg = sci_to_qcolor(default_bg); + QColor fg = sci_to_qcolor(default_fg); + // Set default paper/color for ALL styles (style -1) + lex->setPaper(bg, -1); + lex->setColor(fg, -1); + // Also set the default paper explicitly for STYLE_DEFAULT + lex->setDefaultPaper(bg); + lex->setDefaultColor(fg); + // Parse style spec: "style:color:bold:italic,..." + if (style_spec && style_spec[0]) { + std::string spec(style_spec); + size_t pos = 0; + while (pos < spec.size()) { + size_t comma = spec.find(',', pos); + if (comma == std::string::npos) comma = spec.size(); + std::string entry = spec.substr(pos, comma - pos); + int style_id = 0, color = 0, bold = 0, italic = 0; + if (sscanf(entry.c_str(), "%d:%x:%d:%d", &style_id, &color, &bold, &italic) >= 2) { + lex->setColor(sci_to_qcolor(color), style_id); + if (bold || italic) { + QFont f = lex->font(style_id); + if (bold) f.setBold(true); + if (italic) f.setItalic(true); + lex->setFont(f, style_id); + } + } + pos = comma + 1; + } + } + } + // Also set via Scintilla API as fallback for areas not covered by lexer + s->SendScintilla(QsciScintillaBase::SCI_STYLESETBACK, + QsciScintillaBase::STYLE_DEFAULT, (long)default_bg); + s->SendScintilla(QsciScintillaBase::SCI_STYLESETFORE, + QsciScintillaBase::STYLE_DEFAULT, (long)default_fg); + s->SendScintilla(QsciScintillaBase::SCI_STYLECLEARALL); + // Re-apply per-style colors via lexer (STYLECLEARALL wiped them) + if (lex) { + QColor bg = sci_to_qcolor(default_bg); + QColor fg = sci_to_qcolor(default_fg); + lex->setPaper(bg, -1); + lex->setColor(fg, -1); + if (style_spec && style_spec[0]) { + std::string spec(style_spec); + size_t pos = 0; + while (pos < spec.size()) { + size_t comma = spec.find(',', pos); + if (comma == std::string::npos) comma = spec.size(); + std::string entry = spec.substr(pos, comma - pos); + int style_id = 0, color = 0, bold = 0, italic = 0; + if (sscanf(entry.c_str(), "%d:%x:%d:%d", &style_id, &color, &bold, &italic) >= 2) { + lex->setColor(sci_to_qcolor(color), style_id); + if (bold || italic) { + QFont f = lex->font(style_id); + if (bold) f.setBold(true); + if (italic) f.setItalic(true); + lex->setFont(f, style_id); + } + } + pos = comma + 1; + } + } + } + // Force re-colorize + s->SendScintilla(QsciScintillaBase::SCI_COLOURISE, (unsigned long)0, (long)-1) + ); +} + +extern "C" void qt_scintilla_lexer_set_color(qt_scintilla_t sci, int style, int color) { + QT_NULL_CHECK_VOID(sci); + QT_VOID( + auto* lex = static_cast<QsciScintilla*>(sci)->lexer(); + if (lex) { + int r = color & 0xFF; + int g = (color >> 8) & 0xFF; + int b = (color >> 16) & 0xFF; + lex->setColor(QColor(r, g, b), style); + } + ); +} + +extern "C" void qt_scintilla_lexer_set_paper(qt_scintilla_t sci, int style, int color) { + QT_NULL_CHECK_VOID(sci); + QT_VOID( + auto* lex = static_cast<QsciScintilla*>(sci)->lexer(); + if (lex) { + int r = color & 0xFF; + int g = (color >> 8) & 0xFF; + int b = (color >> 16) & 0xFF; + lex->setPaper(QColor(r, g, b), style); + } + ); +} + +extern "C" void qt_scintilla_lexer_set_font_attr(qt_scintilla_t sci, int style, int bold, int italic) { + QT_NULL_CHECK_VOID(sci); + QT_VOID( + auto* lex = static_cast<QsciScintilla*>(sci)->lexer(); + if (lex) { + QFont f = lex->font(style); + f.setBold(bold != 0); + f.setItalic(italic != 0); + lex->setFont(f, style); + } ); }