Replace vtscreen with libvterm FFI, add row-diff rendering and C-c support
ober
66991be74e59890ced97247ce2773acc2cecc512
--- a/Makefile +++ b/Makefile @@ -33,6 +33,9 @@ run: build repl_shim.so: support/repl_shim.c gcc -shared -fPIC -O2 -o repl_shim.so support/repl_shim.c -Wall +vterm_shim.so: support/vterm_shim.c + gcc -shared -fPIC -O2 -o vterm_shim.so support/vterm_shim.c -lvterm -Wall + QT_INC := $(shell qmake6 -query QT_INSTALL_HEADERS 2>/dev/null || echo /usr/include/x86_64-linux-gnu/qt6) QT_SHIM_H := $(HOME)/mine/gerbil-qt/vendor @@ -44,7 +47,7 @@ libqt_shim.so: vendor/qt_shim.cpp -o libqt_shim.so \ -lQt6Core -lQt6Gui -lQt6Widgets -lqscintilla2_qt6 -run-qt: build repl_shim.so libqt_shim.so +run-qt: build repl_shim.so libqt_shim.so vterm_shim.so $(SCHEME) $(LIBDIRS) --script qt-main.ss # Qt backend build target --- a/build-binary-qt.ss +++ b/build-binary-qt.ss @@ -435,6 +435,13 @@ echo OK" (display "Error: pty_shim.c compilation failed\n") (exit 1)))) +;; vterm shim (libvterm FFI — jvt_* symbols from support/vterm_shim.c) +(when jemacs-static? + (let* ((cmd "gcc -c -O2 -o jemacs-qt-vterm-shim.o support/vterm_shim.c -Wall 2>&1")) + (unless (= 0 (system cmd)) + (display "Error: vterm_shim.c compilation failed\n") + (exit 1)))) + ;; repl shim (poll/nanosleep/Sdeactivate wrappers for debug REPL in static builds) (when jemacs-static? (let* ((cmd (format "gcc -c -O2 -o jemacs-qt-repl-shim.o support/repl_shim.c -I~a -Wall 2>&1" @@ -477,11 +484,11 @@ echo OK" (libqt-shim (format "~a/libqt_shim.a" qt-shim-dir)) (cmd (format "g++ -static -Wl,--export-dynamic -o jemacs-qt \ jemacs-qt-main.o jemacs-qt-chez-shim.o jemacs-qt-pcre2-shim.o jemacs-qt-jsh-ffi.o \ -jemacs-qt-pty-shim.o jemacs-qt-repl-shim.o jemacs-qt-jerboa-landlock.o jemacs-qt-sci-stubs.o \ +jemacs-qt-pty-shim.o jemacs-qt-vterm-shim.o jemacs-qt-repl-shim.o jemacs-qt-jerboa-landlock.o jemacs-qt-sci-stubs.o \ qt_static_symbols.o \ ~a ~a ~a ~a \ -L~a -lkernel -llz4 -lz \ --lm -ldl -lpthread -luuid -lncurses -lstdc++ 2>&1" +-lvterm -lm -ldl -lpthread -luuid -lncurses -lstdc++ 2>&1" libqt-shim qt-plugins qt-libs pcre2-libs chez-dir))) (printf " ~a~n" cmd) @@ -493,7 +500,7 @@ qt_static_symbols.o \ (qt-libs (shell-output "pkg-config --libs Qt6Widgets" "-lQt6Widgets -lQt6Gui -lQt6Core")) ;; Link against ./libqt_shim.so (local copy with JEMACS_CHEZ_SMP) ;; rather than qt-shim-dir (gerbil-qt vendor — no Sdeactivate) - (cmd (format "g++ -rdynamic -o jemacs-qt jemacs-qt-main.o jemacs-qt-chez-shim.o jemacs-qt-pcre2-shim.o ~a ~a -L~a -lkernel -llz4 -lz -lm -ldl -lpthread -luuid -lncurses -lstdc++ -L. -lqt_shim -lqscintilla2_qt6 -Wl,-rpath,~a -Wl,-rpath,'$ORIGIN' 2>&1" + (cmd (format "g++ -rdynamic -o jemacs-qt jemacs-qt-main.o jemacs-qt-chez-shim.o jemacs-qt-pcre2-shim.o ~a ~a -L~a -lkernel -llz4 -lz -lm -ldl -lpthread -luuid -lncurses -lstdc++ -L. -lqt_shim -lqscintilla2_qt6 -lvterm -Wl,-rpath,~a -Wl,-rpath,'$ORIGIN' 2>&1" pcre2-libs qt-libs chez-dir chez-dir))) (printf " ~a~n" cmd) (unless (= 0 (system cmd)) @@ -510,7 +517,8 @@ qt_static_symbols.o \ "jemacs_qt_scheme_boot.h" "jemacs_qt_jemacs_qt_boot.h" "jemacs-qt-all.so" "qt-main.so" "qt-main.wpo" "jemacs-qt.boot") (if jemacs-static? - '("jemacs-qt-jsh-ffi.o" "jemacs-qt-pty-shim.o" "jemacs-qt-jerboa-landlock.o" + '("jemacs-qt-jsh-ffi.o" "jemacs-qt-pty-shim.o" "jemacs-qt-vterm-shim.o" + "jemacs-qt-jerboa-landlock.o" "jemacs-qt-sci-stubs.o" "qt_static_symbols.o" "qt_static_symbols.c") '()))) @@ -536,8 +544,9 @@ qt_static_symbols.o \ (printf " ./libqt_shim.so~n") (printf " ./qt_chez_shim.so~n") (printf " ./pcre2_shim.so~n") + (printf " ./vterm_shim.so~n") (printf "~nRun:~n") (printf " ./jemacs-qt # launch Qt editor~n") (printf " ./jemacs-qt file.txt # open file~n") (printf "~nInstall:~n") - (printf " cp jemacs-qt libqt_shim.so qt_chez_shim.so pcre2_shim.so /usr/local/bin/~n"))) + (printf " cp jemacs-qt libqt_shim.so qt_chez_shim.so pcre2_shim.so vterm_shim.so /usr/local/bin/~n"))) --- a/lib/jerboa-emacs/qt/app.sls +++ b/lib/jerboa-emacs/qt/app.sls @@ -90,10 +90,13 @@ start-debug-repl! stop-debug-repl!) (jerboa core) (jerboa runtime)) - (def *vterm-render-interval-ms* 100) + (def *vterm-render-interval-ms* 33) (def *vterm-scrollback-limit* 100000) (def *vterm-last-render-time* (make-hash-table-eq)) (def *vterm-last-rendered* (make-hash-table-eq)) + (def *vterm-initialized* (make-hash-table-eq)) + (def *vterm-line-offset* (make-hash-table-eq)) + (def *vterm-row-cache* (make-hash-table-eq)) (def (vterm-render-due? ts) "Return #t if enough time has elapsed since last render for this terminal." (let ([last (hash-ref *vterm-last-render-time* ts 0.0)] @@ -123,7 +126,146 @@ (def (vterm-cleanup-state! ts) "Remove throttle state for a terminal that's done." (hash-remove! *vterm-last-render-time* ts) - (hash-remove! *vterm-last-rendered* ts)) + (hash-remove! *vterm-last-rendered* ts) + (hash-remove! *vterm-initialized* ts) + (hash-remove! *vterm-line-offset* ts) + (hash-remove! *vterm-row-cache* ts)) + (def (vterm-replace-line! ed doc-line new-text) + "Replace the content of a single line in QScintilla without touching other lines.\n doc-line is 0-based. new-text should NOT include a newline." + (let* ([line-start (sci-send + ed + SCI_POSITIONFROMLINE + doc-line)] + [line-end (sci-send ed SCI_GETLINEENDPOSITION doc-line)]) + (when (>= line-start 0) + (sci-send ed SCI_SETTARGETSTART line-start) + (sci-send ed SCI_SETTARGETEND line-end) + (sci-send/string ed SCI_REPLACETARGET new-text)))) + (def (vterm-ensure-lines! ed needed-count) + "Ensure the document has at least needed-count lines by appending newlines." + (let ([current (sci-send ed SCI_GETLINECOUNT)]) + (when (< current needed-count) + (let ([pad (make-string + (- needed-count current) + #\newline)]) + (let ([end-pos (sci-send ed SCI_GETTEXTLENGTH)]) + (sci-send ed SCI_SETTARGETSTART end-pos) + (sci-send ed SCI_SETTARGETEND end-pos) + (sci-send/string ed SCI_REPLACETARGET pad)))))) + (def (vterm-row-diff-render! ed vt ts) + "Render vtscreen rows into the QScintilla document.\n When many rows changed, uses full set-text! for efficiency.\n When few rows changed, uses per-line replacement." + (let ([rows (vtscreen-rows vt)] + [line-offset (hash-ref *vterm-line-offset* ts 0)] + [cache (hash-ref *vterm-row-cache* ts #f)]) + (when (not cache) + (set! cache (make-vector rows "")) + (hash-put! *vterm-row-cache* ts cache)) + (when (not (= (vector-length cache) rows)) + (set! cache (make-vector rows "")) + (hash-put! *vterm-row-cache* ts cache)) + (let count-dirty ([r 0] [n 0]) + (cond + [(>= r rows) + (cond + [(= n 0) (vtscreen-clear-damage! vt) #f] + [(> n 4) + (let* ([rendered (vtscreen-render vt)] + [pre-text (or (terminal-state-pre-pty-text ts) "")] + [full (if (vtscreen-alt-screen? vt) + rendered + (string-append pre-text rendered))]) + (let update ([r2 0]) + (when (< r2 rows) + (vector-set! cache r2 (vtscreen-get-row-text vt r2)) + (update (+ r2 1)))) + (vtscreen-clear-damage! vt) + (qt-plain-text-edit-set-text! ed full) + #t)] + [else + (vterm-ensure-lines! ed (+ line-offset rows)) + (let loop ([r2 0] [any-changed? #f]) + (if (>= r2 rows) + (begin (vtscreen-clear-damage! vt) any-changed?) + (if (vtscreen-row-dirty? vt r2) + (let ([new-text (vtscreen-get-row-text vt r2)] + [old-text (vector-ref cache r2)]) + (if (string=? new-text old-text) + (loop (+ r2 1) any-changed?) + (begin + (vector-set! cache r2 new-text) + (vterm-replace-line! + ed + (+ line-offset r2) + new-text) + (loop (+ r2 1) #t)))) + (loop (+ r2 1) any-changed?))))])] + [(vtscreen-row-dirty? vt r) (count-dirty (+ r 1) (+ n 1))] + [else (count-dirty (+ r 1) n)])))) + (def *vterm-next-style* 80) + (def *vterm-color-to-style* (make-hash-table)) + (def *vterm-max-styles* 128) + (def (vterm-get-or-alloc-style! ed packed-rgb) + "Get or allocate a Scintilla style for a packed 0x00RRGGBB color.\n Returns style index, or 0 if we've run out of style slots." + (or (hash-ref *vterm-color-to-style* packed-rgb #f) + (if (>= *vterm-next-style* *vterm-max-styles*) + 0 + (let ([style *vterm-next-style*]) + (set! *vterm-next-style* (+ style 1)) + (sci-send ed SCI_STYLESETFORE style packed-rgb) + (sci-send ed SCI_STYLESETBACK style 1579032) + (hash-put! *vterm-color-to-style* packed-rgb style) + style)))) + (def (vterm-apply-row-colors! ed vt row doc-line) + "Apply per-cell foreground colors to a row using Scintilla styling.\n Handles bold attribute by shifting color index +8 for indexed colors." + (let* ([cols (vtscreen-cols vt)] + [line-start (sci-send ed SCI_POSITIONFROMLINE doc-line)] + [line-end (sci-send ed SCI_GETLINEENDPOSITION doc-line)] + [line-len (min (- line-end line-start) cols)]) + (when (> line-len 0) + (let loop ([c 0] [run-start 0] [run-style #f]) + (if (>= c line-len) + (when (and run-style (> c run-start)) + (sci-send + ed + SCI_STARTSTYLING + (+ line-start run-start) + 0) + (sci-send ed SCI_SETSTYLING (- c run-start) run-style)) + (let* ([fg (vtscreen-cell-fg vt row c)] + [attrs (vtscreen-cell-attrs vt row c)] + [bold? (not (= 0 (bitwise-and attrs 1)))] + [style (cond + [(= fg -1) + (if bold? (+ *term-style-base* 15) 0)] + [else + (vterm-get-or-alloc-style! ed fg)])]) + (if (eqv? style run-style) + (loop (+ c 1) run-start run-style) + (begin + (when (and run-style (> c run-start)) + (sci-send + ed + SCI_STARTSTYLING + (+ line-start run-start) + 0) + (sci-send + ed + SCI_SETSTYLING + (- c run-start) + run-style)) + (loop (+ c 1) c style))))))))) + (def (vterm-apply-colors! ed vt ts) + "Apply colors to all dirty rows." + (let ([rows (vtscreen-rows vt)] + [line-offset (hash-ref *vterm-line-offset* ts 0)]) + (let loop ([r 0]) + (when (< r rows) + (let ([cache (hash-ref *vterm-row-cache* ts #f)]) + (when (and cache + (< r (vector-length cache)) + (> (string-length (vector-ref cache r)) 0)) + (vterm-apply-row-colors! ed vt r (+ line-offset r)))) + (loop (+ r 1)))))) (def (parse-repl-port args) "Return (port-num . filtered-args) if --repl <port> is present, else #f." (let loop ([rest args] [acc (list)]) @@ -349,42 +491,80 @@ (qt-plain-text-edit-ensure-cursor-visible! ed))) (loop (cdr wins))))))]))) (def (qt-poll-terminal-pty-batch! fr buf ts data) - "Handle batched PTY data for a terminal buffer.\n Feeds data to vtscreen immediately but throttles rendering to avoid\n replacing the entire QScintilla document more than ~10 times/sec." + "Handle batched PTY data for a terminal buffer.\n Feeds data to vtscreen immediately but throttles rendering.\n Uses row-diff rendering: only updates dirty rows in QScintilla." (let ([vt (terminal-state-vtscreen ts)]) (let loop ([wins (qt-frame-windows fr)]) (when (pair? wins) (if (eq? (qt-edit-window-buffer (car wins)) buf) (let ([ed (qt-edit-window-editor (car wins))]) (when (and vt (not (terminal-state-pre-pty-text ts))) - (terminal-state-pre-pty-text-set! - ts - (qt-plain-text-edit-text ed))) + (let ([pre-text (qt-plain-text-edit-text ed)]) + (terminal-state-pre-pty-text-set! ts pre-text) + (let ([offset (if (and pre-text + (> (string-length pre-text) + 0)) + (let count ([i 0] [n 1]) + (cond + [(>= i + (string-length pre-text)) + n] + [(char=? + (string-ref pre-text i) + #\newline) + (count (+ i 1) (+ n 1))] + [else (count (+ i 1) n)])) + 0)]) + (hash-put! *vterm-line-offset* ts offset)))) (if vt (begin (vtscreen-feed! vt data) (vterm-cap-scrollback! ts) (when (vterm-render-due? ts) - (let* ([rendered (vtscreen-render vt)] - [full (if (vtscreen-alt-screen? vt) - rendered - (string-append - (or (terminal-state-pre-pty-text - ts) - "") - rendered))] - [prev (hash-ref - *vterm-last-rendered* - ts - #f)]) - (unless (and prev (string=? prev full)) - (hash-put! *vterm-last-rendered* ts full) - (qt-plain-text-edit-set-text! ed full) - (qt-plain-text-edit-move-cursor! - ed - QT_CURSOR_END) - (qt-plain-text-edit-ensure-cursor-visible! - ed)) - (vterm-mark-rendered! ts)))) + (if (not (hash-ref *vterm-initialized* ts #f)) + (let* ([rendered (vtscreen-render vt)] + [full (if (vtscreen-alt-screen? vt) + rendered + (string-append + (or (terminal-state-pre-pty-text + ts) + "") + rendered))]) + (qt-plain-text-edit-set-text! ed full) + (hash-put! *vterm-last-rendered* ts full) + (hash-put! *vterm-initialized* ts #t) + (let* ([rows (vtscreen-rows vt)] + [cache (make-vector rows "")]) + (let init-cache ([r 0]) + (when (< r rows) + (vector-set! + cache + r + (vtscreen-get-row-text vt r)) + (init-cache (+ r 1)))) + (hash-put! *vterm-row-cache* ts cache)) + (vtscreen-clear-damage! vt) + (vterm-apply-colors! ed vt ts) + (qt-plain-text-edit-move-cursor! + ed + QT_CURSOR_END) + (qt-plain-text-edit-ensure-cursor-visible! + ed)) + (when (vtscreen-has-damage? vt) + (if (vtscreen-alt-screen? vt) + (hash-put! *vterm-line-offset* ts 0) + (void)) + (let ([changed? (vterm-row-diff-render! + ed + vt + ts)]) + (when changed? + (vterm-apply-colors! ed vt ts) + (qt-plain-text-edit-move-cursor! + ed + QT_CURSOR_END) + (qt-plain-text-edit-ensure-cursor-visible! + ed))))) + (vterm-mark-rendered! ts))) (begin (qt-plain-text-edit-move-cursor! ed QT_CURSOR_END) (qt-plain-text-edit-insert-text! @@ -640,191 +820,222 @@ (app-state-echo app) echo-label)] [else - (letrec ([do-normal-key! (lambda (code + (letrec ([terminal-pty-intercept? (lambda (code + mods) + (let* ([ctrl? (not (zero? + (bitwise-and + mods + QT_MOD_CTRL)))] + [alt? (not (zero? + (bitwise-and + mods + QT_MOD_ALT)))] + [buf (qt-current-buffer + (app-state-frame + app))]) + (and ctrl? + (not alt?) + (>= code + QT_KEY_A) + (<= code + QT_KEY_Z) + (not (= code + (+ QT_KEY_A + 23))) + (not (= code + (+ QT_KEY_A + 6))) + (cond + [(terminal-buffer? + buf) + (let ([ts (hash-get + *terminal-state* + buf)]) + (and ts + (terminal-pty-busy? + ts) + (let ([ch (integer->char + (+ 1 + (- code + QT_KEY_A)))]) + (terminal-send-input! + ts + (string + ch)) + #t)))] + [(shell-buffer? + buf) + (let ([ss (hash-get + *shell-state* + buf)]) + (and ss + (shell-pty-busy? + ss) + (let ([ch (integer->char + (+ 1 + (- code + QT_KEY_A)))]) + (shell-send-input! + ss + (string + ch)) + #t)))] + [else + #f]))))] + [do-normal-key! (lambda (code mods text) - (if (and (active-repeat-map) - (let* ([ks (qt-key-event->string + (unless (terminal-pty-intercept? + code + mods) + (if (and (active-repeat-map) + (let* ([ks (qt-key-event->string + code + mods + text)] + [repeat-cmd (and ks + (repeat-map-lookup + ks))]) + (if repeat-cmd + (begin + (execute-command! + app + repeat-cmd) + #t) + (begin + (clear-repeat-map!) + #f)))) + (void) + (let-values ([(action data new-state) + (qt-key-state-feed! + (app-state-key-state + app) code mods - text)] - [repeat-cmd (and ks - (repeat-map-lookup - ks))]) - (if repeat-cmd - (begin - (execute-command! - app - repeat-cmd) - #t) - (begin - (clear-repeat-map!) - #f)))) - (void) - (let-values ([(action data new-state) - (qt-key-state-feed! - (app-state-key-state - app) - code - mods - text)]) - (app-state-key-state-set! - app - new-state) - (when (and *which-key-timer* + text)]) + (app-state-key-state-set! + app + new-state) + (when (and *which-key-timer* + (not (eq? action + 'prefix))) + (qt-timer-stop! + *which-key-timer*) + (set! *which-key-pending-keymap* + #f)) + (if (and *qt-describe-key-pending* (not (eq? action 'prefix))) - (qt-timer-stop! - *which-key-timer*) - (set! *which-key-pending-keymap* - #f)) - (if (and *qt-describe-key-pending* - (not (eq? action - 'prefix))) - (let ([ks (qt-key-event->string - code - mods - text)]) - (qt-describe-key-result! - app - ks - action - data)) - (if *qt-quoted-insert-pending* - (qt-quoted-insert-handle! + (let ([ks (qt-key-event->string + code + mods + text)]) + (qt-describe-key-result! app - (if (and text - (> (string-length - text) - 0)) - text - (qt-key-event->string - code - mods - text))) - (case action - [(command) - (when (and (app-state-macro-recording - app) - (not (memq - data - '(start-kbd-macro - end-kbd-macro - call-last-kbd-macro - call-named-kbd-macro - name-last-kbd-macro - list-kbd-macros - save-kbd-macros - load-kbd-macros)))) - (app-state-macro-recording-set! - app - (cons + ks + action + data)) + (if *qt-quoted-insert-pending* + (qt-quoted-insert-handle! + app + (if (and text + (> (string-length + text) + 0)) + text + (qt-key-event->string + code + mods + text))) + (case action + [(command) + (when (and (app-state-macro-recording + app) + (not (memq + data + '(start-kbd-macro + end-kbd-macro + call-last-kbd-macro + call-named-kbd-macro + name-last-kbd-macro + list-kbd-macros + save-kbd-macros + load-kbd-macros)))) + (app-state-macro-recording-set! + app (cons - 'command - data) - (app-state-macro-recording - app)))) - (when (and (echo-state-message - (app-state-echo - app)) - (null? - (key-state-prefix-keys - new-state))) - (echo-clear! - (app-state-echo - app))) - (execute-command! - app - data)] - [(self-insert) - (let* ([buf (qt-current-buffer - (app-state-frame - app))] - [mode-cmd (mode-keymap-lookup - buf - data)]) - (if mode-cmd - (execute-command! - app - mode-cmd) - (begin - (when (app-state-macro-recording - app) - (app-state-macro-recording-set! - app - (cons + (cons + 'command + data) + (app-state-macro-recording + app)))) + (when (and (echo-state-message + (app-state-echo + app)) + (null? + (key-state-prefix-keys + new-state))) + (echo-clear! + (app-state-echo + app))) + (execute-command! + app + data)] + [(self-insert) + (let* ([buf (qt-current-buffer + (app-state-frame + app))] + [mode-cmd (mode-keymap-lookup + buf + data)]) + (if mode-cmd + (execute-command! + app + mode-cmd) + (begin + (when (app-state-macro-recording + app) + (app-state-macro-recording-set! + app (cons - 'self-insert - data) - (app-state-macro-recording - app)))) - (let* ([ed (qt-current-editor - (app-state-frame - app))] - [ch (string-ref - data - 0)] - [close-ch (and *auto-pair-mode* - (let ([cc (auto-pair-char - (char->integer - ch))]) - (and cc - (integer->char - cc))))] - [n (get-prefix-arg - app)]) - (cond - [(dired-buffer? - buf) - (void)] - [(image-buffer? - buf) - (void)] - [(repl-buffer? - buf) - (let* ([pos (qt-plain-text-edit-cursor-position - ed)] - [rs (hash-get - *repl-state* - buf)]) - (when (and rs - (>= pos - (repl-state-prompt-pos - rs))) - (let loop ([i 0]) - (when (< i - n) - (qt-plain-text-edit-insert-text! - ed - (string - ch)) - (loop - (+ i - 1))))))] - [(eshell-buffer? - buf) - (let loop ([i 0]) - (when (< i - n) - (qt-plain-text-edit-insert-text! - ed - (string - ch)) - (loop - (+ i - 1))))] - [(terminal-buffer? - buf) - (let ([ts (hash-get - *terminal-state* - buf)]) - (if (and ts - (terminal-pty-busy? - ts)) - (terminal-send-input! - ts - (string - ch)) + (cons + 'self-insert + data) + (app-state-macro-recording + app)))) + (let* ([ed (qt-current-editor + (app-state-frame + app))] + [ch (string-ref + data + 0)] + [close-ch (and *auto-pair-mode* + (let ([cc (auto-pair-char + (char->integer + ch))]) + (and cc + (integer->char + cc))))] + [n (get-prefix-arg + app)]) + (cond + [(dired-buffer? + buf) + (void)] + [(image-buffer? + buf) + (void)] + [(repl-buffer? + buf) + (let* ([pos (qt-plain-text-edit-cursor-position + ed)] + [rs (hash-get + *repl-state* + buf)]) + (when (and rs + (>= pos + (repl-state-prompt-pos + rs))) (let loop ([i 0]) (when (< i n) @@ -835,253 +1046,287 @@ (loop (+ i 1))))))] - [(shell-buffer? - buf) - (let ([ss (hash-get - *shell-state* - buf)]) - (if (and ss - (shell-pty-busy? - ss)) - (shell-send-input! - ss + [(eshell-buffer? + buf) + (let loop ([i 0]) + (when (< i + n) + (qt-plain-text-edit-insert-text! + ed (string ch)) - (let loop ([i 0]) - (when (< i - n) - (qt-plain-text-edit-insert-text! - ed - (string - ch)) - (loop - (+ i - 1))))))] - [else - (when (not *qt-delete-selection-enabled*) - (let ([pos (qt-plain-text-edit-cursor-position - ed)]) - (sci-send - ed - SCI_SETSEL - pos - pos))) - (cond - [(and *auto-pair-mode* - (= n - 1) - (auto-pair-closing? - (char->integer - ch))) - (let* ([pos (qt-plain-text-edit-cursor-position - ed)] - [text (qt-plain-text-edit-text - ed)] - [next-ch (and (< pos - (string-length - text)) - (string-ref - text - pos))]) - (if (and next-ch - (char=? - next-ch + (loop + (+ i + 1))))] + [(terminal-buffer? + buf) + (let ([ts (hash-get + *terminal-state* + buf)]) + (if (and ts + (terminal-pty-busy? + ts)) + (terminal-send-input! + ts + (string + ch)) + (let loop ([i 0]) + (when (< i + n) + (qt-plain-text-edit-insert-text! + ed + (string ch)) - (qt-plain-text-edit-set-cursor-position! - ed - (+ pos - 1)) - (qt-plain-text-edit-insert-text! - ed - (string - ch))))] - [(and close-ch - (= n - 1)) - (let ([pos (qt-plain-text-edit-cursor-position - ed)]) - (qt-plain-text-edit-insert-text! - ed - (string - ch - close-ch)) - (qt-plain-text-edit-set-cursor-position! - ed - (+ pos - 1)))] - [else - (let ([str (make-string - n - ch)]) + (loop + (+ i + 1))))))] + [(shell-buffer? + buf) + (let ([ss (hash-get + *shell-state* + buf)]) + (if (and ss + (shell-pty-busy? + ss)) + (shell-send-input! + ss + (string + ch)) + (let loop ([i 0]) + (when (< i + n) + (qt-plain-text-edit-insert-text! + ed + (string + ch)) + (loop + (+ i + 1))))))] + [else + (when (not *qt-delete-selection-enabled*) + (let ([pos (qt-plain-text-edit-cursor-position + ed)]) + (sci-send + ed + SCI_SETSEL + pos + pos))) + (cond + [(and *auto-pair-mode* + (= n + 1) + (auto-pair-closing? + (char->integer + ch))) + (let* ([pos (qt-plain-text-edit-cursor-position + ed)] + [text (qt-plain-text-edit-text + ed)] + [next-ch (and (< pos + (string-length + text)) + (string-ref + text + pos))]) + (if (and next-ch + (char=? + next-ch + ch)) + (qt-plain-text-edit-set-cursor-position! + ed + (+ pos + 1)) + (qt-plain-text-edit-insert-text! + ed + (string + ch))))] + [(and close-ch + (= n + 1)) + (let ([pos (qt-plain-text-edit-cursor-position + ed)]) + (qt-plain-text-edit-insert-text! + ed + (string + ch + close-ch)) + (qt-plain-text-edit-set-cursor-position! + ed + (+ pos + 1)))] + [else + (let ([str (make-string + n + ch)]) + (qt-plain-text-edit-insert-text! + ed + str))])])) + (auto-fill-check! + (qt-current-editor + (app-state-frame + app))) + (when (and *abbrev-mode-enabled* + (let ([c (string-ref + data + 0)]) + (or (char=? + c + #\space) + (char=? + c + #\newline) + (char=? + c + #\,) + (char=? + c + #\.) + (char=? + c + #\;)))) + (let* ([aed (qt-current-editor + (app-state-frame + app))] + [pos (qt-plain-text-edit-cursor-position + aed)] + [text (qt-plain-text-edit-text + aed)] + [sep-pos (- pos + 1)] + [word-end sep-pos] + [word-start (let loop ([i (- sep-pos + 1)]) + (if (< i + 0) + 0 + (let ([c (string-ref + text + i)]) + (if (or (char-alphabetic? + c) + (char-numeric? + c) + (char=? + c + #\-) + (char=? + c + #\_)) + (loop + (- i + 1)) + (+ i + 1)))))] + [word (if (> word-end