Skip to content

Commit f9e7fc0

Browse files
committed
Fix ghostel-send-next-key for prefix keys (C-x, C-h) and modified keys (M-x)
read-key-sequence consults keymaps, so prefix keys like C-x never returned — they waited for a continuation keystroke. Modified keys like M-x hit the multi-byte branch which misinterpreted the meta bit as a Unicode codepoint (producing ø). Switch to read-event which bypasses keymap lookup entirely, and route modified keys through ghostel--send-encoded instead of trying to encode them as UTF-8. Fixes #62
1 parent 907da53 commit f9e7fc0

2 files changed

Lines changed: 102 additions & 18 deletions

File tree

ghostel.el

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -694,26 +694,50 @@ Used for prompt navigation and optional re-application after full redraws.")
694694
(defun ghostel-send-next-key ()
695695
"Read the next key event and send it to the terminal.
696696
This is an escape hatch for sending keys that are normally
697-
intercepted by Emacs (e.g., interrupt or prefix keys)."
697+
intercepted by Emacs (e.g., interrupt or prefix keys).
698+
Uses `read-event' so that prefix keys return immediately instead
699+
of waiting for a continuation keystroke."
698700
(interactive)
699-
(let* ((key (read-key-sequence "Send key: "))
700-
(char (aref key 0)))
701+
(let ((event (read-event "Send key: ")))
701702
(cond
702-
;; Control character
703-
((and (integerp char) (<= char 31))
704-
(ghostel--send-key (string char)))
705-
;; Regular character
706-
((and (integerp char) (< char 128))
707-
(ghostel--send-key (string char)))
708-
;; Multi-byte character
709-
((integerp char)
710-
(ghostel--send-key (encode-coding-string (string char) 'utf-8)))
711-
;; Function key / special key — look up in keymap
703+
;; Control character (C-@=0, C-a=1 through C-_=31)
704+
((and (integerp event) (<= event 31))
705+
(ghostel--send-key (string event)))
706+
;; ASCII (32-127)
707+
((and (integerp event) (<= event 127))
708+
(ghostel--send-key (string event)))
709+
;; Non-ASCII character without modifier bits — send as UTF-8
710+
((and (integerp event) (< event #x400000))
711+
(ghostel--send-key (encode-coding-string (string event) 'utf-8)))
712+
;; Modified key (M-x, C-M-a, etc.) or function key — use encoder
712713
(t
713-
(let* ((binding (key-binding key)))
714-
(if (and binding (commandp binding))
715-
(call-interactively binding)
716-
(message "ghostel: unrecognized key %S" key)))))))
714+
(let* ((base (event-basic-type event))
715+
(mods (event-modifiers event))
716+
(key-name (cond
717+
((eq base 'backtab) "tab")
718+
((integerp base)
719+
(and (< base 128) (string base)))
720+
((eq base 'deletechar) "delete")
721+
((and base (symbolp base)) (symbol-name base))
722+
((and (null base) (symbolp event))
723+
(replace-regexp-in-string
724+
"\\`\\(?:[CMSHs]-\\)*" "" (symbol-name event)))
725+
(t nil)))
726+
(mods (if (eq base 'backtab) (cons 'shift mods) mods))
727+
(mod-str (mapconcat
728+
#'identity
729+
(delq nil
730+
(mapcar
731+
(lambda (m)
732+
(pcase m
733+
('shift "shift") ('control "ctrl")
734+
('meta "meta") ('alt "alt")
735+
('hyper "hyper") ('super "super")))
736+
mods))
737+
",")))
738+
(if key-name
739+
(ghostel--send-encoded key-name mod-str)
740+
(message "ghostel: unrecognized key %S" event)))))))
717741

718742
(defun ghostel--send-key (key)
719743
"Send KEY string to the terminal process.

test/ghostel-test.el

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1740,6 +1740,61 @@ cell, so the visual line width must equal the terminal column count."
17401740
(should-not recenter-called))))
17411741
(kill-buffer buf))))
17421742

1743+
;; -----------------------------------------------------------------------
1744+
;; Test: ghostel-send-next-key
1745+
;; -----------------------------------------------------------------------
1746+
1747+
(ert-deftest ghostel-test-send-next-key-control-x ()
1748+
"send-next-key sends C-x as raw byte 24 (not intercepted by Emacs)."
1749+
(let (sent-key)
1750+
(cl-letf (((symbol-function 'ghostel--send-key)
1751+
(lambda (str) (setq sent-key str))))
1752+
(let ((unread-command-events (list ?\C-x)))
1753+
(ghostel-send-next-key))
1754+
(should (equal (string 24) sent-key)))))
1755+
1756+
(ert-deftest ghostel-test-send-next-key-control-h ()
1757+
"send-next-key sends C-h as raw byte 8."
1758+
(let (sent-key)
1759+
(cl-letf (((symbol-function 'ghostel--send-key)
1760+
(lambda (str) (setq sent-key str))))
1761+
(let ((unread-command-events (list ?\C-h)))
1762+
(ghostel-send-next-key))
1763+
(should (equal (string 8) sent-key)))))
1764+
1765+
(ert-deftest ghostel-test-send-next-key-regular-char ()
1766+
"send-next-key sends a regular character as-is."
1767+
(let (sent-key)
1768+
(cl-letf (((symbol-function 'ghostel--send-key)
1769+
(lambda (str) (setq sent-key str))))
1770+
(let ((unread-command-events (list ?a)))
1771+
(ghostel-send-next-key))
1772+
(should (equal "a" sent-key)))))
1773+
1774+
(ert-deftest ghostel-test-send-next-key-meta-x ()
1775+
"send-next-key routes M-x through the encoder with meta modifier."
1776+
(let (captured-key captured-mods
1777+
(ghostel--term 'fake))
1778+
(cl-letf (((symbol-function 'ghostel--send-encoded)
1779+
(lambda (key mods &optional _utf8)
1780+
(setq captured-key key captured-mods mods))))
1781+
(let ((unread-command-events (list ?\M-x)))
1782+
(ghostel-send-next-key))
1783+
(should (equal "x" captured-key))
1784+
(should (equal "meta" captured-mods)))))
1785+
1786+
(ert-deftest ghostel-test-send-next-key-function-key ()
1787+
"send-next-key routes function keys through the encoder."
1788+
(let (captured-key captured-mods
1789+
(ghostel--term 'fake))
1790+
(cl-letf (((symbol-function 'ghostel--send-encoded)
1791+
(lambda (key mods &optional _utf8)
1792+
(setq captured-key key captured-mods mods))))
1793+
(let ((unread-command-events (list 'up)))
1794+
(ghostel-send-next-key))
1795+
(should (equal "up" captured-key))
1796+
(should (equal "" captured-mods)))))
1797+
17431798
(defconst ghostel-test--elisp-tests
17441799
'(ghostel-test-raw-key-sequences
17451800
ghostel-test-modifier-number
@@ -1778,7 +1833,12 @@ cell, so the visual line width must equal the terminal column count."
17781833
ghostel-test-scroll-on-input-disabled
17791834
ghostel-test-control-key-bindings
17801835
ghostel-test-meta-key-bindings
1781-
ghostel-test-copy-mode-recenter)
1836+
ghostel-test-copy-mode-recenter
1837+
ghostel-test-send-next-key-control-x
1838+
ghostel-test-send-next-key-control-h
1839+
ghostel-test-send-next-key-regular-char
1840+
ghostel-test-send-next-key-meta-x
1841+
ghostel-test-send-next-key-function-key)
17821842
"Tests that require only Elisp (no native module).")
17831843

17841844
(defun ghostel-test-run-elisp ()

0 commit comments

Comments
 (0)