diff --git a/org/modern-backend.org b/org/modern-backend.org index ff12a70..6e805d1 100644 --- a/org/modern-backend.org +++ b/org/modern-backend.org @@ -1,45 +1,51 @@ -#+TITLE: cl-tty Modern Backend — v0.0.2 +#+TITLE: Modern Backend #+STARTUP: content -#+FILETAGS: :cl-tty:backend:v0.0.2: -#+OPTIONS: ^:nil +#+FILETAGS: :cl-tty:backend: -* Modern Backend +* Overview -The =modern-backend= renders through raw ANSI/XTerm escape sequences. -No ncurses, no CFFI, no external dependencies — pure CL string -construction. Supports truecolor, Unicode box-drawing, OSC 8 hyperlinks, -DECICM synchronized updates, SGR mouse, and the kitty keyboard protocol. +The modern backend provides full-featured terminal rendering using raw +escape sequences. It supports truecolor 24-bit color, OSC 8 hyperlinks, +DECICM synchronized updates, SGR mouse tracking, kitty keyboard protocol, +and Unicode box-drawing characters (single, double, rounded). -** Contract +All rendering functions produce CSI/OSC escape sequences directly — no +ncurses, no terminfo, no FFI. Color resolution handles named colors +(~:red~, ~:blue~, etc.), hex strings (~"#FFD700"~), and semantic theme +roles (~:accent~, ~:error~) via the ~*theme-colors*~ hash table. -*** Constructor +* Contract -- =(make-modern-backend &key color-palette)= → modern-backend - Create a modern backend. color-palette modifies theme color mappings. +** Color and attribute helpers -*** Escape Sequence Generation +- ~(hex-to-rgb hex)~ → (values r g b) — parse "#RRGGBB" or "#RGB" +- ~(sgr-fg color)~ → escape string — foreground color escape +- ~(sgr-bg color)~ → escape string — background color escape +- ~(sgr-attr attr)~ → escape string — attribute escape (bold, italic, etc.) -All escape sequences follow ECMA-48 / ANSI X3.64 conventions: +** Cursor helpers -| Escape | Meaning | -|--------+--------------------------| -| ~ESC[~ | Control Sequence Introducer (CSI) | -| ~ESC]~ | Operating System Command (OSC) | -| ~ESC ~ | Single-character sequence | +- ~(cursor-move-escape x y)~ → escape string — CSI cursor position +- ~(cursor-style-escape shape blink)~ → escape string — DECSTR cursor shape -*** Style Resolution +** Sync and link helpers -Colors are resolved through a palette before emission: +- ~(decicm-begin)~ → escape string — enable synchronized updates +- ~(decicm-end)~ → escape string — disable synchronized updates +- ~(osc8-link url text)~ → escape string — OSC 8 hyperlink wrapper -- =(resolve-color backend hex-or-name)= → color-index - Convert hex string or semantic name to an SGR color code. - Hex ("#FFD700") → 48;2;R;G;B or 38;2;R;G;B. - Named colors (:black :red :green :yellow :blue :magenta :cyan :white) - → 8-color SGR codes. +** Border helpers -** Test Suite +- ~(border-char style pos)~ → string — Unicode box-drawing character -#+BEGIN_SRC lisp +** Modern backend class + +- ~(make-modern-backend &key output-stream)~ → modern-backend +- Implements all ~backend~ protocol methods with escape sequences + +* Tests + +#+BEGIN_SRC lisp :tangle ../src/backend/modern-tests.lisp (defpackage :cl-tty-modern-backend-test (:use :cl :fiveam :cl-tty.backend) (:export #:run-tests)) @@ -92,7 +98,7 @@ Colors are resolved through a palette before emission: "cursor-move generates correct CSI escape" (let ((b (make-modern-backend))) (is (equal (cl-tty.backend::cursor-move-escape 5 10) - (format nil "~C[6;11H" #\Esc))))) + (format nil "~C[11;6H" #\Esc))))) (test cursor-style-block "cursor-style :block generate correct escape" @@ -124,7 +130,7 @@ Colors are resolved through a palette before emission: (test osc8-escape "OSC 8 hyperlink escape wraps text" (is (equal (cl-tty.backend::osc8-link "http://example.com" "click here") - (format nil "~C]8;;http://example.com~C\\click here~C]8;;~C\\" + (format nil "~C]8;;http://example.com~C\\\\click here~C]8;;~C\\\\" #\Esc #\Esc #\Esc #\Esc)))) ;; ── Hex Parsing ──────────────────────────────────────────────── @@ -166,44 +172,50 @@ Colors are resolved through a palette before emission: (is (equal (cl-tty.backend::border-char :double :vertical) "║"))) #+END_SRC -** Implementation +* Implementation -*** Package +** Color and attribute helpers -Add to =cl-tty.backend= package: - -#+BEGIN_SRC lisp -;; In package.lisp, add to :export: -;; #:modern-backend #:make-modern-backend -;; Internal symbols (not exported, used by tests): -;; sgr-fg sgr-bg sgr-attr cursor-move-escape cursor-style-escape -;; decicm-begin decicm-end osc8-link hex-to-rgb border-char +~hex-to-rgb~ parses hex color strings into (r g b) triplets. Handles +both 6-digit (fully specified) and 3-digit (shorthand) formats. +#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp (in-package :cl-tty.backend) -#+END_SRC -*** Color Resolution - -#+BEGIN_SRC lisp (defun hex-to-rgb (hex) "Parse a hex color string like \"#FFD700\" into (values r g b). - Also handles 3-digit hex like \"#F00\"." + Also handles 3-digit hex like \"#F00\" (expands to \"#FF0000\")." (let ((clean (string-trim '(#\# #\Space) hex))) (if (= (length clean) 3) - (values (parse-integer (subseq clean 0 1) :radix 16 :junk-allowed t) - (parse-integer (subseq clean 1 2) :radix 16 :junk-allowed t) - (parse-integer (subseq clean 2 3) :radix 16 :junk-allowed t)) + ;; Expand 3-digit: #F00 -> #FF0000 + (let* ((r (parse-integer (subseq clean 0 1) :radix 16 :junk-allowed t)) + (g (parse-integer (subseq clean 1 2) :radix 16 :junk-allowed t)) + (b (parse-integer (subseq clean 2 3) :radix 16 :junk-allowed t))) + (values (+ r (* r 16)) (+ g (* g 16)) (+ b (* b 16)))) (values (parse-integer (subseq clean 0 2) :radix 16 :junk-allowed t) (parse-integer (subseq clean 2 4) :radix 16 :junk-allowed t) (parse-integer (subseq clean 4 6) :radix 16 :junk-allowed t))))) +#+END_SRC +Named color mapping and theme color store: + +#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp (defparameter *named-colors* '((:black . 0) (:red . 1) (:green . 2) (:yellow . 3) (:blue . 4) (:magenta . 5) (:cyan . 6) (:white . 7))) +(defvar *theme-colors* (make-hash-table :test 'eq) + "Hash table mapping theme keywords to hex color strings. +Populated by the theme system's load-preset. Checked by sgr-fg/sgr-bg +as a fallback when a keyword is not in *named-colors*.") +#+END_SRC + +~sgr-fg~ and ~sgr-bg~ produce the actual escape sequences. The +resolution chain is: hex → named color → theme semantic role → empty. + +#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp (defun sgr-fg (color) - "Return SGR foreground escape for COLOR. - Color can be a hex string, a keyword name, or nil." + "Return SGR foreground escape for COLOR." (if (null color) "" (cond ((and (stringp color) (char= (char color 0) #\#)) (multiple-value-bind (r g b) (hex-to-rgb color) @@ -212,9 +224,15 @@ Add to =cl-tty.backend= package: (let ((index (cdr (assoc color *named-colors*)))) (if index (format nil "~C[~dm" #\Esc (+ 30 index)) - ""))) + (let ((hex (gethash color *theme-colors*))) + (if hex + (multiple-value-bind (r g b) (hex-to-rgb hex) + (format nil "~C[38;2;~d;~d;~dm" #\Esc r g b)) + ""))))) (t "")))) +#+END_SRC +#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp (defun sgr-bg (color) "Return SGR background escape for COLOR." (if (null color) "" @@ -225,9 +243,17 @@ Add to =cl-tty.backend= package: (let ((index (cdr (assoc color *named-colors*)))) (if index (format nil "~C[~dm" #\Esc (+ 40 index)) - ""))) + (let ((hex (gethash color *theme-colors*))) + (if hex + (multiple-value-bind (r g b) (hex-to-rgb hex) + (format nil "~C[48;2;~d;~d;~dm" #\Esc r g b)) + ""))))) (t "")))) +#+END_SRC +Attribute codes map keywords to SGR numbers: + +#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp (defparameter *sgr-attr-codes* '((:bold . 1) (:dim . 2) (:italic . 3) (:underline . 4) (:blink . 5) (:reverse . 7) (:reset . 0))) @@ -240,17 +266,15 @@ Add to =cl-tty.backend= package: ""))) #+END_SRC -*** Cursor Escapes +** Cursor escapes -#+BEGIN_SRC lisp +#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp (defun cursor-move-escape (x y) "Return CSI escape to move cursor to (x, y), 1-indexed." (format nil "~C[~d;~dH" #\Esc (1+ y) (1+ x))) (defun cursor-style-escape (shape blink) - "Return DECSTR escape for cursor shape. - :block = 2, :underline = 4, :bar = 6. - Add 1 for blink variants." + "Return DECSTR escape for cursor shape." (let* ((base (case shape (:block 2) (:underline 4) (:bar 6) (t 2))) @@ -258,9 +282,9 @@ Add to =cl-tty.backend= package: (format nil "~C[~d q" #\Esc code))) #+END_SRC -*** Synchronization (DECICM) +** Sync and link escapes -#+BEGIN_SRC lisp +#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp (defun decicm-begin () "Return escape to enable synchronized updates." (format nil "~C[?2026h" #\Esc)) @@ -268,86 +292,120 @@ Add to =cl-tty.backend= package: (defun decicm-end () "Return escape to disable synchronized updates." (format nil "~C[?2026l" #\Esc)) -#+END_SRC -*** OSC 8 Hyperlinks - -#+BEGIN_SRC lisp (defun osc8-link (url text) "Wrap TEXT in an OSC 8 hyperlink to URL." - (format nil "~C]8;;~A~C\\~A~C]8;;~C\\" + (format nil "~C]8;;~A~C\\\\~A~C]8;;~C\\\\" #\Esc url #\Esc text #\Esc #\Esc)) #+END_SRC -*** Border Characters +** Border characters -#+BEGIN_SRC lisp +#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp (defparameter *border-chars* - '((:single :top-left . "┌") (:single :top-right . "┐") - (:single :bottom-left . "└") (:single :bottom-right . "┘") - (:single :horizontal . "─") (:single :vertical . "│") - (:double :top-left . "╔") (:double :top-right . "╗") - (:double :bottom-left . "╚") (:double :bottom-right . "╝") - (:double :horizontal . "═") (:double :vertical . "║") - (:rounded :top-left . "╭") (:rounded :top-right . "╮") - (:rounded :bottom-left . "╰") (:rounded :bottom-right . "╯") - (:rounded :horizontal . "─") (:rounded :vertical . "│"))) + '(((:single :top-left) . "┌") ((:single :top-right) . "┐") + ((:single :bottom-left) . "└") ((:single :bottom-right) . "┘") + ((:single :horizontal) . "─") ((:single :vertical) . "│") + ((:double :top-left) . "╔") ((:double :top-right) . "╗") + ((:double :bottom-left) . "╚") ((:double :bottom-right) . "╝") + ((:double :horizontal) . "═") ((:double :vertical) . "║") + ((:rounded :top-left) . "╭") ((:rounded :top-right) . "╮") + ((:rounded :bottom-left) . "╰") ((:rounded :bottom-right) . "╯") + ((:rounded :horizontal) . "─") ((:rounded :vertical) . "│"))) (defun border-char (style pos) "Return the Unicode box-drawing character for STYLE at POS." - (let ((char (cdr (assoc (cons style pos) *border-chars* :test #'equal)))) + (let ((char (cdr (assoc (list style pos) *border-chars* :test #'equal)))) (or char (if (member pos '(:horizontal :vertical)) (case pos (:horizontal "─") (:vertical "│")) "+")))) #+END_SRC -*** Modern Backend Class +** Modern backend class -#+BEGIN_SRC lisp +#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp (defclass modern-backend (backend) ((output-stream :initform *standard-output* + :initarg :output-stream :accessor backend-output-stream) (in-sync-p :initform nil :accessor in-sync-p))) -(defun make-modern-backend (&key color-palette) +(defun make-modern-backend (&key color-palette output-stream) (declare (ignore color-palette)) - (make-instance 'modern-backend)) + (make-instance 'modern-backend :output-stream (or output-stream *standard-output*))) +#+END_SRC +** Lifecycle + +~initialize-backend~ enters the alt screen, enables mouse tracking, +bracketed paste, and kitty keyboard protocol. ~shutdown-backend~ +restores everything. + +#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp (defmethod initialize-backend ((b modern-backend)) - ;; Enter raw mode, enable mouse, bracketed paste (backend-write b (format nil "~C[?1049h" #\Esc)) ; alt screen (backend-write b (format nil "~C[?1000h" #\Esc)) ; mouse basic (backend-write b (format nil "~C[?1002h" #\Esc)) ; mouse drag (backend-write b (format nil "~C[?1006h" #\Esc)) ; SGR mouse (backend-write b (format nil "~C[?2004h" #\Esc)) ; bracketed paste + (backend-write b (format nil "~C[?u" #\Esc)) ; kitty keyboard (cursor-hide b) (finish-output (backend-output-stream b)) b) (defmethod shutdown-backend ((b modern-backend)) (cursor-show b) - (backend-write b (format nil "~C[?2004l" #\Esc)) ; disable bracketed paste - (backend-write b (format nil "~C[?1006l" #\Esc)) ; disable SGR mouse + (backend-write b (format nil "~C[?u" #\Esc)) + (backend-write b (format nil "~C[?2004l" #\Esc)) + (backend-write b (format nil "~C[?1006l" #\Esc)) (backend-write b (format nil "~C[?1002l" #\Esc)) (backend-write b (format nil "~C[?1000l" #\Esc)) (backend-write b (format nil "~C[?1049l" #\Esc)) ; normal screen (finish-output (backend-output-stream b)) (values)) +#+END_SRC +** Backend-size via ioctl + +Uses TIOCGWINSZ to query actual terminal dimensions. The alien-sap +wrapper ensures compatibility across SBCL versions. + +#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp (defmethod backend-size ((b modern-backend)) - ;; Default fallback — real implementation queries terminal - (values 80 24)) + (let* ((+tiocgwinsz+ 21523) ; 0x5413 on Linux + (winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) + (unwind-protect + (progn + (sb-unix:unix-ioctl (sb-sys:fd-stream-fd (backend-output-stream b)) + +tiocgwinsz+ + (sb-alien:alien-sap winsize)) + (values (sb-alien:deref winsize 1) ;; cols + (sb-alien:deref winsize 0))) ;; rows + (sb-alien:free-alien winsize)))) +#+END_SRC +** Capability query and write + +#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp (defmethod backend-write ((b modern-backend) string) (let ((stream (backend-output-stream b))) (write-string string stream) + (finish-output stream) (length string))) (defmethod capable-p ((b modern-backend) feature) (member feature '(:truecolor :osc8 :sync :mouse :bracketed-paste :cursor-style :kitty-keyboard))) +#+END_SRC +** Drawing + +~draw-text~ combines cursor positioning, SGR colors, attributes, the +text itself, and a reset into a single string. This minimizes ioctl +calls — one write per draw operation. + +#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp (defmethod draw-text ((b modern-backend) x y string fg bg &key bold italic underline reverse dim blink) (let ((parts (list (cursor-move-escape x y) @@ -361,10 +419,15 @@ Add to =cl-tty.backend= package: string (sgr-attr :reset)))) (backend-write b (apply #'concatenate 'string parts)))) +#+END_SRC +~draw-border~ builds the full border as three string parts (top with +optional title, mid with sides, bottom) and writes them with minimal +output calls. + +#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp (defmethod draw-border ((b modern-backend) x y width height &key style fg bg title title-align) - (declare (ignore title title-align)) (let* ((s (or style :single)) (tl (border-char s :top-left)) (tr (border-char s :top-right)) @@ -375,31 +438,58 @@ Add to =cl-tty.backend= package: (fg-esc (sgr-fg fg)) (bg-esc (sgr-bg bg)) (reset (sgr-attr :reset)) - (top (concatenate 'string - fg-esc bg-esc tl - (make-string (- width 2) :initial-element (char h 0)) - tr reset (string #\Newline))) + (inner-width (- width 2)) + (hc (char h 0)) + (top (if (and title (plusp (length title))) + (let* ((align (or title-align :left)) + (max-tlen (- inner-width 2)) + (tlen (min (length title) max-tlen)) + (trunc-title (subseq title 0 tlen))) + (ecase align + (:left + (let ((right-hyphens (- inner-width tlen 2))) + (concatenate 'string + fg-esc bg-esc tl (string #\Space) + trunc-title (string #\Space) + (make-string (max 0 right-hyphens) :initial-element hc) + tr reset (string #\Newline)))) + (:center + (let* ((total-pad (- inner-width tlen)) + (left-pad (floor total-pad 2)) + (right-pad (- total-pad left-pad))) + (concatenate 'string + fg-esc bg-esc tl + (make-string left-pad :initial-element hc) + trunc-title + (make-string right-pad :initial-element hc) + tr reset (string #\Newline)))))) + (concatenate 'string + fg-esc bg-esc tl + (make-string inner-width :initial-element hc) + tr reset (string #\Newline)))) (mid (concatenate 'string fg-esc bg-esc v - (make-string (- width 2) :initial-element #\Space) + (make-string inner-width :initial-element #\Space) v reset (string #\Newline))) (bot (concatenate 'string fg-esc bg-esc bl - (make-string (- width 2) :initial-element (char h 0)) + (make-string inner-width :initial-element hc) br reset))) (backend-write b top) (loop repeat (- height 2) do (backend-write b mid)) (backend-write b bot))) +#+END_SRC +#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp (defmethod draw-rect ((b modern-backend) x y width height &key bg) - (let ((bg-esc (sgr-bg bg)) - (reset (sgr-attr :reset)) - (line (concatenate 'string - bg-esc - (make-string width :initial-element #\Space) - reset (string #\Newline)))) - (loop repeat height do - (backend-write b (cursor-move-escape x y)) + (let* ((bg-esc (sgr-bg bg)) + (reset (sgr-attr :reset)) + (line (concatenate 'string + bg-esc + (make-string width :initial-element #\Space) + reset (string #\Newline)))) + (loop :for row :from 0 :below height :do + (backend-write b (cursor-move-escape x (+ y row))) (backend-write b line)))) (defmethod draw-link ((b modern-backend) x y string url @@ -412,9 +502,14 @@ Add to =cl-tty.backend= package: (defmethod draw-ellipsis ((b modern-backend) x y width &key fg bg) + (declare (ignore width)) (let ((dots "...")) (draw-text b x y dots fg bg))) +#+END_SRC +** Cursor and input methods + +#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp (defmethod cursor-move ((b modern-backend) x y) (backend-write b (cursor-move-escape x y))) @@ -427,6 +522,16 @@ Add to =cl-tty.backend= package: (defmethod cursor-style ((b modern-backend) shape &key blink) (backend-write b (cursor-style-escape shape blink))) +(defmethod enable-mouse ((b modern-backend)) + (backend-write b (format nil "~C[?1000h" #\Esc)) + (backend-write b (format nil "~C[?1002h" #\Esc)) + (backend-write b (format nil "~C[?1006h" #\Esc)) + (finish-output (backend-output-stream b))) + +(defmethod enable-bracketed-paste ((b modern-backend)) + (backend-write b (format nil "~C[?2004h" #\Esc)) + (finish-output (backend-output-stream b))) + (defmethod begin-sync ((b modern-backend)) (setf (in-sync-p b) t) (backend-write b (decicm-begin))) diff --git a/src/backend/modern-tests.lisp b/src/backend/modern-tests.lisp index 3bb80e9..7e48ad7 100644 --- a/src/backend/modern-tests.lisp +++ b/src/backend/modern-tests.lisp @@ -82,7 +82,7 @@ (test osc8-escape "OSC 8 hyperlink escape wraps text" (is (equal (cl-tty.backend::osc8-link "http://example.com" "click here") - (format nil "~C]8;;http://example.com~C\\click here~C]8;;~C\\" + (format nil "~C]8;;http://example.com~C\\\\click here~C]8;;~C\\\\" #\Esc #\Esc #\Esc #\Esc)))) ;; ── Hex Parsing ──────────────────────────────────────────────── diff --git a/src/backend/modern.lisp b/src/backend/modern.lisp index dec08b0..ac2ebb2 100644 --- a/src/backend/modern.lisp +++ b/src/backend/modern.lisp @@ -1,13 +1,3 @@ -;;; modern-backend — Raw escape sequence backend -;;; Generated from org/modern-backend.org -;;; DO NOT EDIT — edit the .org file instead - -;; In package.lisp, add to :export: -;; #:modern-backend #:make-modern-backend -;; Internal symbols (not exported, used by tests): -;; sgr-fg sgr-bg sgr-attr cursor-move-escape cursor-style-escape -;; decicm-begin decicm-end osc8-link hex-to-rgb border-char - (in-package :cl-tty.backend) (defun hex-to-rgb (hex) @@ -34,10 +24,7 @@ Populated by the theme system's load-preset. Checked by sgr-fg/sgr-bg as a fallback when a keyword is not in *named-colors*.") (defun sgr-fg (color) - "Return SGR foreground escape for COLOR. - Color can be a hex string, a keyword name, or nil. - Keywords first try *named-colors*, then fall back to *theme-colors* - which resolves theme semantic roles to hex strings." + "Return SGR foreground escape for COLOR." (if (null color) "" (cond ((and (stringp color) (char= (char color 0) #\#)) (multiple-value-bind (r g b) (hex-to-rgb color) @@ -46,7 +33,6 @@ as a fallback when a keyword is not in *named-colors*.") (let ((index (cdr (assoc color *named-colors*)))) (if index (format nil "~C[~dm" #\Esc (+ 30 index)) - ;; Fall back to theme-colors hash (let ((hex (gethash color *theme-colors*))) (if hex (multiple-value-bind (r g b) (hex-to-rgb hex) @@ -55,8 +41,7 @@ as a fallback when a keyword is not in *named-colors*.") (t "")))) (defun sgr-bg (color) - "Return SGR background escape for COLOR. - Keywords first try *named-colors*, then fall back to *theme-colors*." + "Return SGR background escape for COLOR." (if (null color) "" (cond ((and (stringp color) (char= (char color 0) #\#)) (multiple-value-bind (r g b) (hex-to-rgb color) @@ -65,7 +50,6 @@ as a fallback when a keyword is not in *named-colors*.") (let ((index (cdr (assoc color *named-colors*)))) (if index (format nil "~C[~dm" #\Esc (+ 40 index)) - ;; Fall back to theme-colors hash (let ((hex (gethash color *theme-colors*))) (if hex (multiple-value-bind (r g b) (hex-to-rgb hex) @@ -89,9 +73,7 @@ as a fallback when a keyword is not in *named-colors*.") (format nil "~C[~d;~dH" #\Esc (1+ y) (1+ x))) (defun cursor-style-escape (shape blink) - "Return DECSTR escape for cursor shape. - :block = 2, :underline = 4, :bar = 6. - Add 1 for blink variants." + "Return DECSTR escape for cursor shape." (let* ((base (case shape (:block 2) (:underline 4) (:bar 6) (t 2))) @@ -108,7 +90,7 @@ as a fallback when a keyword is not in *named-colors*.") (defun osc8-link (url text) "Wrap TEXT in an OSC 8 hyperlink to URL." - (format nil "~C]8;;~A~C\\~A~C]8;;~C\\" + (format nil "~C]8;;~A~C\\\\~A~C]8;;~C\\\\" #\Esc url #\Esc text #\Esc #\Esc)) (defparameter *border-chars* @@ -140,7 +122,6 @@ as a fallback when a keyword is not in *named-colors*.") (make-instance 'modern-backend :output-stream (or output-stream *standard-output*))) (defmethod initialize-backend ((b modern-backend)) - ;; Enter raw mode, enable mouse, bracketed paste, kitty keyboard (backend-write b (format nil "~C[?1049h" #\Esc)) ; alt screen (backend-write b (format nil "~C[?1000h" #\Esc)) ; mouse basic (backend-write b (format nil "~C[?1002h" #\Esc)) ; mouse drag @@ -153,9 +134,9 @@ as a fallback when a keyword is not in *named-colors*.") (defmethod shutdown-backend ((b modern-backend)) (cursor-show b) - (backend-write b (format nil "~C[?u" #\Esc)) ; restore default keyboard - (backend-write b (format nil "~C[?2004l" #\Esc)) ; disable bracketed paste - (backend-write b (format nil "~C[?1006l" #\Esc)) ; disable SGR mouse + (backend-write b (format nil "~C[?u" #\Esc)) + (backend-write b (format nil "~C[?2004l" #\Esc)) + (backend-write b (format nil "~C[?1006l" #\Esc)) (backend-write b (format nil "~C[?1002l" #\Esc)) (backend-write b (format nil "~C[?1000l" #\Esc)) (backend-write b (format nil "~C[?1049l" #\Esc)) ; normal screen @@ -163,7 +144,6 @@ as a fallback when a keyword is not in *named-colors*.") (values)) (defmethod backend-size ((b modern-backend)) - ;; Query actual terminal dimensions via TIOCGWINSZ ioctl (let* ((+tiocgwinsz+ 21523) ; 0x5413 on Linux (winsize (sb-alien:make-alien sb-alien:unsigned-short 4))) (unwind-protect @@ -274,6 +254,7 @@ as a fallback when a keyword is not in *named-colors*.") (defmethod draw-ellipsis ((b modern-backend) x y width &key fg bg) + (declare (ignore width)) (let ((dots "...")) (draw-text b x y dots fg bg))) @@ -290,13 +271,13 @@ as a fallback when a keyword is not in *named-colors*.") (backend-write b (cursor-style-escape shape blink))) (defmethod enable-mouse ((b modern-backend)) - (backend-write b (format nil "~C[?1000h" #\Esc)) ; basic - (backend-write b (format nil "~C[?1002h" #\Esc)) ; drag - (backend-write b (format nil "~C[?1006h" #\Esc)) ; SGR + (backend-write b (format nil "~C[?1000h" #\Esc)) + (backend-write b (format nil "~C[?1002h" #\Esc)) + (backend-write b (format nil "~C[?1006h" #\Esc)) (finish-output (backend-output-stream b))) (defmethod enable-bracketed-paste ((b modern-backend)) - (backend-write b (format nil "~C[?2004h" #\Esc)) ; bracketed paste + (backend-write b (format nil "~C[?2004h" #\Esc)) (finish-output (backend-output-stream b))) (defmethod begin-sync ((b modern-backend)) @@ -307,4 +288,3 @@ as a fallback when a keyword is not in *named-colors*.") (setf (in-sync-p b) nil) (backend-write b (decicm-end)) (finish-output (backend-output-stream b))) -