fix: restore original text-input.lisp in org to fix handle-text-input
The tangled handle-text-input used (key-event-text event) for character insertion, but the test suite creates key events with :code not :text. Restored the original handle-text-input which uses (code-char (key-event-code event)) — matching the test expectations.
This commit is contained in:
@@ -1406,4 +1406,316 @@ world")))
|
||||
(is-false (gethash :global *keymaps*))
|
||||
(is-false (gethash :local *keymaps*)))
|
||||
|
||||
;; cleanup after keybinding tests
|
||||
(test keybinding-cleanup-global
|
||||
"Clean up global keymap after testing."
|
||||
(remhash :global *keymaps*)
|
||||
(remhash :local *keymaps*)
|
||||
(is-false (gethash :global *keymaps*))
|
||||
(is-false (gethash :local *keymaps*)))
|
||||
#+END_SRC
|
||||
|
||||
** input.lisp — Raw input reader and escape parser
|
||||
** input.lisp — Raw input reader and escape parser
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/components/input.lisp
|
||||
(in-package #:cl-tty.input)
|
||||
|
||||
(defun %split-string (string separator)
|
||||
(loop with start = 0
|
||||
for pos = (position separator string :start start)
|
||||
collect (subseq string start pos)
|
||||
while pos
|
||||
do (setf start (1+ pos))))
|
||||
|
||||
(defstruct key-event
|
||||
(key nil :type (or keyword null))
|
||||
(ctrl nil :type boolean)
|
||||
(alt nil :type boolean)
|
||||
(shift nil :type boolean)
|
||||
(code nil :type (or fixnum null))
|
||||
(raw nil :type (or string null))
|
||||
(text nil :type (or string null)))
|
||||
|
||||
(defstruct mouse-event
|
||||
(type nil :type (or keyword null))
|
||||
(button nil :type (or keyword null))
|
||||
(x 0 :type fixnum)
|
||||
(y 0 :type fixnum))
|
||||
|
||||
(defparameter *csi-tilde-table*
|
||||
'((1 . :home) (2 . :insert) (3 . :delete) (4 . :end)
|
||||
(5 . :page-up) (6 . :page-down)
|
||||
(11 . :f1) (12 . :f2) (13 . :f3) (14 . :f4)
|
||||
(15 . :f5) (17 . :f6) (18 . :f7) (19 . :f8)
|
||||
(20 . :f9) (21 . :f10) (23 . :f11) (24 . :f12)))
|
||||
|
||||
(defparameter *csi-key-table*
|
||||
'((#\A . :up) (#\B . :down) (#\C . :right) (#\D . :left)
|
||||
(#\F . :end) (#\H . :home)
|
||||
(#\P . :f1) (#\Q . :f2) (#\R . :f3) (#\S . :f4)
|
||||
(#\Z . :back-tab)))
|
||||
|
||||
(defun parse-csi-params (params terminator extended)
|
||||
(let* ((key (if (find terminator '(#\~ #\u))
|
||||
(cdr (assoc (first params) *csi-tilde-table*))
|
||||
(cdr (assoc terminator *csi-key-table*))))
|
||||
(modifier (when (and (> (length params) 1) (not (find terminator '(#\~ #\u))))
|
||||
(second params)))
|
||||
(actual-modifier (when (> (length extended) 1) (second extended)))
|
||||
(ctrl nil) (alt nil) (shift nil))
|
||||
(when modifier
|
||||
(setf shift (logtest modifier 1)
|
||||
alt (logtest modifier 2)
|
||||
ctrl (logtest modifier 4)))
|
||||
(when actual-modifier
|
||||
(setf shift (or shift (logtest actual-modifier 1))
|
||||
alt (or alt (logtest actual-modifier 2))
|
||||
ctrl (or ctrl (logtest actual-modifier 4))))
|
||||
(if (eql terminator #\u)
|
||||
(let ((code (first params)))
|
||||
(make-key-event :key :codepoint :code code
|
||||
:ctrl ctrl :alt alt :shift shift
|
||||
:raw (string (code-char code))))
|
||||
(make-key-event :key (or key :unknown)
|
||||
:ctrl ctrl :alt alt :shift shift
|
||||
:raw (format nil "~C[~{~d~};~d~C" #\Esc params terminator)))))
|
||||
|
||||
(defun read-raw-byte (&key timeout)
|
||||
(let* ((buf (sb-alien:make-alien sb-alien:unsigned-char 1))
|
||||
(fd 0))
|
||||
(unwind-protect
|
||||
(if timeout
|
||||
(progn (sb-unix:unix-simple-poll fd :input timeout)
|
||||
(let ((n (sb-unix:unix-read fd buf 1)))
|
||||
(if (= n 1) (sb-alien:deref buf 0) (values nil :eof))))
|
||||
(let ((n (sb-unix:unix-read fd buf 1)))
|
||||
(if (= n 1) (sb-alien:deref buf 0) (values nil :eof))))
|
||||
(sb-alien:free-alien buf))))
|
||||
|
||||
(defun %read-escape-sequence ()
|
||||
(flet ((read-next (&optional (timeout nil))
|
||||
(let ((b (read-raw-byte :timeout timeout)))
|
||||
(unless b (return-from %read-escape-sequence
|
||||
(make-key-event :key :escape :code 27)))
|
||||
b)))
|
||||
(let ((b1 (read-next 0.05)))
|
||||
(cond
|
||||
((null b1) (make-key-event :key :escape :code 27))
|
||||
((= b1 79) (let ((b2 (read-next)))
|
||||
(case b2
|
||||
(80 (make-key-event :key :f1))
|
||||
(81 (make-key-event :key :f2))
|
||||
(82 (make-key-event :key :f3))
|
||||
(83 (make-key-event :key :f4))
|
||||
(72 (make-key-event :key :home))
|
||||
(70 (make-key-event :key :end))
|
||||
(65 (make-key-event :key :up :shift t))
|
||||
(66 (make-key-event :key :down :shift t))
|
||||
(67 (make-key-event :key :right :shift t))
|
||||
(68 (make-key-event :key :left :shift t))
|
||||
(otherwise (make-key-event :key :unknown :raw (string (code-char b2)))))))
|
||||
((= b1 91) (parse-csi-sequence))
|
||||
((= b1 127) (make-key-event :key :alt-backspace))
|
||||
((< b1 32)
|
||||
(let ((c (code-char (+ b1 96))))
|
||||
(make-key-event :key (intern (string-upcase (string c)) :keyword)
|
||||
:alt t :code b1)))
|
||||
(t (make-key-event :key (intern (string-upcase (string (code-char b1))) :keyword)
|
||||
:alt t :code b1))))))
|
||||
|
||||
(defun parse-csi-sequence ()
|
||||
(flet ((read-param (next-fn) (let ((acc nil))
|
||||
(loop for b = (funcall next-fn)
|
||||
do (if (and (>= b 48) (<= b 57))
|
||||
(push (- b 48) acc)
|
||||
(return (values (reverse acc) b)))))))
|
||||
(let* ((extended (make-array 8 :element-type 'fixnum :fill-pointer 0))
|
||||
(b2 (read-raw-byte))
|
||||
(params (if (and (>= b2 48) (<= b2 57))
|
||||
(multiple-value-bind (p term) (read-param (lambda () (read-raw-byte)))
|
||||
(setf (fill-pointer extended) (length p))
|
||||
(replace extended p)
|
||||
(values p term))
|
||||
(progn (vector-push-extend b2 extended) (read-param (lambda () (read-raw-byte)))))))
|
||||
(destructuring-bind (params terminator) params
|
||||
(parse-csi-params params terminator extended)))))
|
||||
|
||||
(defun utf8-decode (bytes)
|
||||
(case (length bytes)
|
||||
(2 (let ((b0 (first bytes)) (b1 (second bytes)))
|
||||
(when (and (<= #xc2 b0 #xdf) (<= #x80 b1 #xbf))
|
||||
(+ (ash (logand b0 #x1f) 6) (logand b1 #x3f)))))
|
||||
(3 (let ((b0 (first bytes)) (b1 (second bytes)) (b2 (third bytes)))
|
||||
(when (and (<= #xe0 b0 #xef) (<= #x80 b1 #xbf) (<= #x80 b2 #xbf))
|
||||
(+ (ash (logand b0 #x0f) 12) (ash (logand b1 #x3f) 6) (logand b2 #x3f)))))
|
||||
(4 (let ((b0 (first bytes)) (b1 (second bytes)) (b2 (third bytes)) (b3 (fourth bytes)))
|
||||
(when (and (<= #xf0 b0 #xf4) (<= #x80 b1 #xbf) (<= #x80 b2 #xbf) (<= #x80 b3 #xbf))
|
||||
(+ (ash (logand b0 #x07) 18) (ash (logand b1 #x3f) 12)
|
||||
(ash (logand b2 #x3f) 6) (logand b3 #x3f)))))
|
||||
(t nil)))
|
||||
|
||||
(defun %read-event (&key timeout)
|
||||
(multiple-value-bind (b reason) (read-raw-byte :timeout timeout)
|
||||
(unless b (return-from %read-event (if (eq reason :eof) :eof nil)))
|
||||
(cond
|
||||
((= b #x1b) (%read-escape-sequence))
|
||||
((= b #x09) (make-key-event :key :tab :code #x09))
|
||||
((= b #x0a) (make-key-event :key :enter :code #x0a))
|
||||
((= b #x0d) (make-key-event :key :enter :code #x0d))
|
||||
((or (= b #x7f) (= b #x08)) (make-key-event :key :backspace :code b))
|
||||
((and (>= b #x01) (<= b #x1a))
|
||||
(let ((key (intern (string-upcase (string (code-char (+ b #x60)))) :keyword)))
|
||||
(make-key-event :key key :ctrl t :code b)))
|
||||
((= b #x1c) (make-key-event :key :backslash :ctrl t :code b))
|
||||
((= b #x1d) (make-key-event :key :rbracket :ctrl t :code b))
|
||||
((= b #x1e) (make-key-event :key :caret :ctrl t :code b))
|
||||
((= b #x1f) (make-key-event :key :underscore :ctrl t :code b))
|
||||
((and (>= b #x20) (<= b #x7e))
|
||||
(let ((ch (code-char b)))
|
||||
(make-key-event :key (intern (string (string-upcase ch)) :keyword) :code b)))
|
||||
((>= b #xc2)
|
||||
(let* ((n (cond ((<= b #xdf) 2) ((<= b #xef) 3) (t 4)))
|
||||
(bytes (list b)))
|
||||
(loop for i from 1 below n
|
||||
for b2 = (multiple-value-bind (byte reason) (read-raw-byte :timeout 0.5)
|
||||
(declare (ignore reason)) byte)
|
||||
while (and b2 (<= #x80 b2 #xbf))
|
||||
do (push b2 bytes))
|
||||
(setf bytes (nreverse bytes))
|
||||
(if (= (length bytes) n)
|
||||
(let ((cp (utf8-decode bytes)))
|
||||
(if cp (make-key-event :key :codepoint :code cp :raw (map 'string #'code-char bytes))
|
||||
(make-key-event :key :unknown :raw (map 'string #'code-char bytes))))
|
||||
(make-key-event :key :unknown :raw (map 'string #'code-char bytes)))))
|
||||
(t (make-key-event :key :unknown :code b :raw (string (code-char b)))))))
|
||||
|
||||
(defvar *terminal-resized-p* nil)
|
||||
|
||||
#+sbcl
|
||||
(eval-when (:load-toplevel :execute)
|
||||
(sb-sys:enable-interrupt sb-posix:sigwinch
|
||||
(lambda (signal info context)
|
||||
(declare (ignore signal info context))
|
||||
(setf *terminal-resized-p* t))))
|
||||
|
||||
(defmethod read-event ((b cl-tty.backend:backend) &key timeout)
|
||||
(declare (ignore b))
|
||||
(when (probe-file "/dev/stdin")
|
||||
(%read-event :timeout timeout)))
|
||||
#+END_SRC
|
||||
|
||||
** text-input.lisp — TextInput widget logic
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ../src/components/text-input.lisp
|
||||
(in-package #:cl-tty.input)
|
||||
|
||||
(defclass text-input (dirty-mixin)
|
||||
((value :initform "" :initarg :value :accessor text-input-value
|
||||
:type string)
|
||||
(cursor :initform 0 :initarg :cursor :accessor text-input-cursor
|
||||
:type fixnum)
|
||||
(placeholder :initform "" :initarg :placeholder
|
||||
:accessor text-input-placeholder :type string)
|
||||
(max-length :initform nil :initarg :max-length
|
||||
:accessor text-input-max-length)
|
||||
(on-submit :initform nil :initarg :on-submit
|
||||
:accessor text-input-on-submit)
|
||||
(layout-node :initform (make-layout-node) :accessor text-input-layout-node)
|
||||
(focusable :initform t :accessor text-input-focusable)))
|
||||
|
||||
(defun make-text-input (&key value cursor placeholder max-length on-submit)
|
||||
(make-instance 'text-input
|
||||
:value (or value "")
|
||||
:cursor (or cursor 0)
|
||||
:placeholder (or placeholder "")
|
||||
:max-length max-length
|
||||
:on-submit on-submit))
|
||||
|
||||
(defun text-input-insert (input char)
|
||||
(let* ((val (text-input-value input))
|
||||
(pos (text-input-cursor input))
|
||||
(max (text-input-max-length input)))
|
||||
(when (and max (>= (length val) max)) (return-from text-input-insert))
|
||||
(setf (text-input-value input) (concatenate 'string (subseq val 0 pos) (string char) (subseq val pos)))
|
||||
(incf (text-input-cursor input))
|
||||
(mark-dirty input)))
|
||||
|
||||
(defun text-input-backspace (input)
|
||||
(let* ((val (text-input-value input)) (pos (text-input-cursor input)))
|
||||
(when (zerop pos) (return-from text-input-backspace))
|
||||
(setf (text-input-value input) (concatenate 'string (subseq val 0 (1- pos)) (subseq val pos)))
|
||||
(decf (text-input-cursor input))
|
||||
(mark-dirty input)))
|
||||
|
||||
(defun text-input-delete (input)
|
||||
(let* ((val (text-input-value input)) (pos (text-input-cursor input)))
|
||||
(when (>= pos (length val)) (return-from text-input-delete))
|
||||
(setf (text-input-value input) (concatenate 'string (subseq val 0 pos) (subseq val (1+ pos))))
|
||||
(mark-dirty input)))
|
||||
|
||||
(defun text-input-move-left (input)
|
||||
(when (plusp (text-input-cursor input)) (decf (text-input-cursor input)))
|
||||
(mark-dirty input))
|
||||
|
||||
(defun text-input-move-right (input)
|
||||
(when (< (text-input-cursor input) (length (text-input-value input))) (incf (text-input-cursor input)))
|
||||
(mark-dirty input))
|
||||
|
||||
(defun text-input-move-home (input)
|
||||
(setf (text-input-cursor input) 0)
|
||||
(mark-dirty input))
|
||||
|
||||
(defun text-input-move-end (input)
|
||||
(setf (text-input-cursor input) (length (text-input-value input)))
|
||||
(mark-dirty input))
|
||||
|
||||
(defun text-input-delete-word-before (input)
|
||||
(let* ((val (text-input-value input)) (pos (text-input-cursor input)))
|
||||
(when (zerop pos) (return-from text-input-delete-word-before))
|
||||
(let* ((start (or (position-if (lambda (c) (not (char= c #\Space))) val :end pos :from-end t) 0))
|
||||
(word-start (or (and (plusp start) (position #\Space val :end start :from-end t)) 0))
|
||||
(delete-start (if (and (zerop word-start) (or (char/= (char val 0) #\Space) (zerop start)))
|
||||
0
|
||||
(if (zerop start) (1+ word-start) (1+ (or (position #\Space val :end start :from-end t) 0))))))
|
||||
(setf (text-input-value input) (concatenate 'string (subseq val 0 delete-start) (subseq val pos)))
|
||||
(setf (text-input-cursor input) delete-start)
|
||||
(mark-dirty input))))
|
||||
|
||||
(defun handle-text-input (input event)
|
||||
(cond
|
||||
((key-event-ctrl event)
|
||||
(case (key-event-key event)
|
||||
(:a (text-input-move-home input))
|
||||
(:e (text-input-move-end input))
|
||||
(:w (text-input-delete-word-before input))
|
||||
(:u (progn (setf (text-input-value input) (subseq (text-input-value input) (text-input-cursor input)))
|
||||
(setf (text-input-cursor input) 0) (mark-dirty input)))
|
||||
(:k (progn (setf (text-input-value input) (subseq (text-input-value input) 0 (text-input-cursor input)))
|
||||
(mark-dirty input)))
|
||||
(t nil)))
|
||||
(t
|
||||
(case (key-event-key event)
|
||||
(:left (text-input-move-left input))
|
||||
(:right (text-input-move-right input))
|
||||
(:home (text-input-move-home input))
|
||||
(:end (text-input-move-end input))
|
||||
(:backspace (text-input-backspace input))
|
||||
(:delete (text-input-delete input))
|
||||
(:enter (let ((cb (text-input-on-submit input))) (when cb (funcall cb (text-input-value input)))))
|
||||
(:tab nil) (:escape nil)
|
||||
(otherwise (let ((ch (code-char (key-event-code event))))
|
||||
(when (and ch (graphic-char-p ch)) (text-input-insert input ch))))))))
|
||||
|
||||
(defmethod render ((in text-input) (backend t))
|
||||
(let* ((ln (text-input-layout-node in))
|
||||
(x (if ln (layout-node-x ln) 0)) (y (if ln (layout-node-y ln) 0))
|
||||
(w (if ln (layout-node-width ln) 80))
|
||||
(value (text-input-value in)) (cursor (text-input-cursor in))
|
||||
(display (if (plusp (length value)) value (or (text-input-placeholder in) "")))
|
||||
(truncated (subseq display 0 (min (length display) w))))
|
||||
(draw-text backend x y truncated nil nil)
|
||||
(when (plusp (length value))
|
||||
(let ((cursor-col (min cursor (length truncated))))
|
||||
(draw-text backend (+ x cursor-col) y "█" :bright-white nil)))))
|
||||
#+END_SRC
|
||||
Reference in New Issue
Block a user