update packages
This commit is contained in:
@@ -1,13 +1,10 @@
|
||||
(define-package "key-chord" "20240910.1441" "map pairs of simultaneously pressed keys to commands"
|
||||
;; -*- no-byte-compile: t; lexical-binding: nil -*-
|
||||
(define-package "key-chord" "20250330.2011"
|
||||
"Map pairs of simultaneously pressed keys to commands."
|
||||
'((emacs "24"))
|
||||
:commit "fc75b1451759121601110da8ddfadcf5156318af" :authors
|
||||
'(("David Andersson" . "l.david.anderssonsverige.nu"))
|
||||
:maintainers
|
||||
'(("David Andersson" . "l.david.anderssonsverige.nu"))
|
||||
:maintainer
|
||||
'("David Andersson" . "l.david.anderssonsverige.nu")
|
||||
:keywords
|
||||
'("keyboard" "chord" "input"))
|
||||
;; Local Variables:
|
||||
;; no-byte-compile: t
|
||||
;; End:
|
||||
:url "https://github.com/LemonBreezes/key-chord"
|
||||
:commit "cb646e815c61f253ad9fdfbe058049dda4e2b32b"
|
||||
:revdesc "cb646e815c61"
|
||||
:keywords '("keyboard" "chord" "input")
|
||||
:authors '(("David Andersson" . "l.david.anderssonsverige.nu"))
|
||||
:maintainers '(("LemonBreezes" . "look@strawberrytea.xyz")))
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
;;; key-chord.el --- map pairs of simultaneously pressed keys to commands -*- lexical-binding: t; -*-
|
||||
;;; key-chord.el --- map pairs of simultaneously pressed keys to commands
|
||||
;; -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2003, 2005, 2008, 2012 David Andersson
|
||||
|
||||
;; Author: David Andersson <l.david.andersson(at)sverige.nu>
|
||||
;; Package-Version: 0.7.1
|
||||
;; Maintainer: LemonBreezes <look@strawberrytea.xyz>
|
||||
;; Package-Version: 20250330.2011
|
||||
;; Package-Revision: cb646e815c61
|
||||
;; Package-Requires: ((emacs "24"))
|
||||
;; Keywords: keyboard chord input
|
||||
;; URL: https://github.com/LemonBreezes/key-chord
|
||||
|
||||
;; This program is free software; you can redistribute it and/or
|
||||
;; modify it under the terms of the GNU General Public License as
|
||||
@@ -35,23 +39,66 @@
|
||||
"Map pairs of simultaneously pressed keys to commands."
|
||||
:group 'bindings)
|
||||
|
||||
(defun key-chord-in-read-char-context-p ()
|
||||
"Return non-nil if we're in a read-char or similar context.
|
||||
This is determined by checking if `overriding-local-map` is set to
|
||||
one of the special read keymaps."
|
||||
(and overriding-local-map
|
||||
(memq overriding-local-map
|
||||
(list read-key-empty-map read-key-full-map))))
|
||||
|
||||
(defcustom key-chord-two-keys-delay 0.1
|
||||
"Max time delay between two key press to be considered a key chord."
|
||||
:type 'float)
|
||||
|
||||
(defcustom key-chord-one-key-delay 0.2
|
||||
"Max time delay between two press of the same key to be considered a key chord.
|
||||
This should normally be a little longer than `key-chord-two-keys-delay'."
|
||||
"Max time delay between two press of the same key for a chord.
|
||||
This should normally be a little longer than
|
||||
`key-chord-two-keys-delay'."
|
||||
:type 'float)
|
||||
|
||||
(defcustom key-chord-in-macros t
|
||||
(defcustom key-chord-in-macros nil
|
||||
"If nil, don't expand key chords when executing keyboard macros.
|
||||
|
||||
If non-nil, expand chord sequenses in macros, but only if a
|
||||
similar chord was entered during the last interactive macro
|
||||
recording. (This carries a bit of guesswork. We can't know for
|
||||
sure when executing whether two keys were typed quickly or slowly
|
||||
when recorded.)"
|
||||
If non-nil, expand chord sequenses in macros, but only if a similar
|
||||
chord was entered during the last interactive macro recording. (This
|
||||
carries a bit of guesswork. We can't know for sure when executing
|
||||
whether two keys were typed quickly or slowly when recorded. Be careful
|
||||
enabling this as it could cause key chords to be expanded in places
|
||||
where they shouldn't be)."
|
||||
:type 'boolean)
|
||||
|
||||
(defcustom key-chord-one-key-min-delay 0.0
|
||||
"Minimum delay (in seconds) between two presses for a double-tap key chord.
|
||||
The delay applies for chords using the same key. If the delay between two
|
||||
identical key presses is less than this value (as when holding a key), the
|
||||
chord will not trigger."
|
||||
:type 'float)
|
||||
|
||||
(defcustom key-chord-typing-detection nil
|
||||
"If non-nil, detect typing and temporarily disable chord detection.
|
||||
This helps avoid accidental chord triggering during fast typing. This
|
||||
also dramatically improves the performance of key-chord when typing text
|
||||
by acting like a debounce."
|
||||
:type 'boolean)
|
||||
|
||||
(defcustom key-chord-typing-speed-threshold 0.1
|
||||
"Maximum delay between keystrokes to be considered part of typing flow.
|
||||
Measured in seconds. If keys are pressed faster than this threshold,
|
||||
key-chord detection will be temporarily disabled."
|
||||
:type 'float)
|
||||
|
||||
(defcustom key-chord-typing-reset-delay 0.5
|
||||
"Time after which to reset typing detection if no keys are pressed.
|
||||
Measured in seconds. After this much idle time, key-chord detection
|
||||
will be re-enabled."
|
||||
:type 'float)
|
||||
|
||||
(defcustom key-chord-use-key-tracking t
|
||||
"If non-nil, track which keys are used in chords to optimize lookups.
|
||||
This improves performance by avoiding unnecessary `key-chord-lookup-key'
|
||||
calls. However, it could potentially cause issues if key chords are
|
||||
defined outside of the normal key-chord-define* functions."
|
||||
:type 'boolean)
|
||||
|
||||
;; Internal vars
|
||||
@@ -67,6 +114,20 @@ when recorded.)"
|
||||
(defvar key-chord-in-last-kbd-macro nil)
|
||||
(defvar key-chord-defining-kbd-macro nil)
|
||||
|
||||
|
||||
;; Typing detection variables
|
||||
(defvar key-chord-in-typing-flow nil
|
||||
"Non-nil when user appears to be typing text rather than executing commands.")
|
||||
(defvar key-chord-last-key-time nil
|
||||
"Time when the last key was pressed.")
|
||||
(defvar key-chord-typing-reset-time nil
|
||||
"Time after which typing flow should be reset.")
|
||||
|
||||
|
||||
;; Key tracking for optimization
|
||||
(defvar key-chord-keys-in-use (make-vector 256 nil)
|
||||
"Vector indicating which keys are used in any chord.")
|
||||
|
||||
;;;###autoload
|
||||
(define-minor-mode key-chord-mode
|
||||
"Map pairs of simultaneously pressed keys to commands.
|
||||
@@ -77,7 +138,8 @@ and `key-chord-one-key-delay'."
|
||||
:global t
|
||||
(setq input-method-function
|
||||
(and key-chord-mode
|
||||
'key-chord-input-method)))
|
||||
'key-chord-input-method))
|
||||
(key-chord-reset-typing-detection))
|
||||
|
||||
;;;###autoload
|
||||
(defun key-chord-define-global (keys command)
|
||||
@@ -121,6 +183,48 @@ cases is shared with all other buffers in the same major mode."
|
||||
(interactive "sUnset key chord locally (2 keys): ")
|
||||
(key-chord-define (current-local-map) keys nil))
|
||||
|
||||
;;;###autoload
|
||||
(defun key-chord-register-keys (key1 key2)
|
||||
"Register KEY1 and KEY2 as being used in a key chord.
|
||||
This function should be called by packages that define key chords
|
||||
outside of the standard key-chord-define functions."
|
||||
;; Always register keys regardless of key-tracking setting
|
||||
(when (and (integerp key1) (< key1 256))
|
||||
(aset key-chord-keys-in-use key1 t))
|
||||
(when (and (integerp key2) (< key2 256))
|
||||
(aset key-chord-keys-in-use key2 t)))
|
||||
|
||||
;;;###autoload
|
||||
(defun key-chord-unregister-keys (key1 key2)
|
||||
"Unregister KEY1 and KEY2 as being used in a key chord.
|
||||
This should only be called if you're certain these keys are not
|
||||
used in any other chords."
|
||||
;; Always attempt to unregister keys regardless of key-tracking setting
|
||||
;; Only unregister if we're sure the keys aren't used elsewhere
|
||||
(let ((remove-key1 t)
|
||||
(remove-key2 t))
|
||||
;; Check if keys are used in any other chords
|
||||
(let ((maps (list (current-global-map) (current-local-map))))
|
||||
(dolist (map (append maps (current-minor-mode-maps)))
|
||||
(when map
|
||||
;; Check if key1 is used in any other chord
|
||||
(dotimes (k 256)
|
||||
(when (and (not (and (= k key2) (= key1 key2)))
|
||||
(or (key-chord-lookup-key1 map (vector 'key-chord key1 k))
|
||||
(key-chord-lookup-key1 map (vector 'key-chord k key1))))
|
||||
(setq remove-key1 nil)))
|
||||
|
||||
;; Check if key2 is used in any other chord
|
||||
(dotimes (k 256)
|
||||
(when (and (not (and (= k key1) (= key1 key2)))
|
||||
(or (key-chord-lookup-key1 map (vector 'key-chord key2 k))
|
||||
(key-chord-lookup-key1 map (vector 'key-chord k key2))))
|
||||
(setq remove-key2 nil))))))
|
||||
|
||||
;; Remove keys from tracking vector if not used elsewhere
|
||||
(when remove-key1 (aset key-chord-keys-in-use key1 nil))
|
||||
(when remove-key2 (aset key-chord-keys-in-use key2 nil))))
|
||||
|
||||
;;;###autoload
|
||||
(defun key-chord-define (keymap keys command)
|
||||
"Define in KEYMAP, a key-chord of the two keys in KEYS starting a COMMAND.
|
||||
@@ -131,19 +235,28 @@ can be used.
|
||||
|
||||
COMMAND can be an interactive function, a string, or nil.
|
||||
If COMMAND is nil, the key-chord is removed."
|
||||
(if (/= 2 (length keys))
|
||||
(error "Key-chord keys must have two elements"))
|
||||
;; Exotic chars in a string are >255 but define-key wants 128..255
|
||||
;; for those.
|
||||
(let ((key1 (logand 255 (aref keys 0)))
|
||||
(key2 (logand 255 (aref keys 1))))
|
||||
(when (/= 2 (length keys))
|
||||
(error "Key-chord keys must have two elements"))
|
||||
(let ((key1 (aref keys 0))
|
||||
(key2 (aref keys 1)))
|
||||
;; Check that both keys are bytes
|
||||
(unless (and (integerp key1) (< key1 256)
|
||||
(integerp key2) (< key2 256))
|
||||
(error "Key-chord keys must both be bytes (characters with codes < 256)"))
|
||||
|
||||
(if (eq key1 key2)
|
||||
(define-key keymap (vector 'key-chord key1 key2) command)
|
||||
(define-key keymap (vector 'key-chord key1 key2) command)
|
||||
(define-key keymap (vector 'key-chord key2 key1) command))))
|
||||
(define-key keymap (vector 'key-chord key2 key1) command))
|
||||
|
||||
;; Update key tracking directly
|
||||
(if command
|
||||
(key-chord-register-keys key1 key2)
|
||||
(key-chord-unregister-keys key1 key2))))
|
||||
|
||||
(defun key-chord-lookup-key1 (keymap key)
|
||||
"Like lookup-key but no third arg and no numeric return value."
|
||||
"Like `lookup-key' but no third arg and no numeric return value.
|
||||
KEYMAP is the keymap to look up the KEY in."
|
||||
(let ((res (lookup-key keymap key)))
|
||||
(and (not (numberp res))
|
||||
res)))
|
||||
@@ -168,48 +281,178 @@ Commands. Please ignore that."
|
||||
(interactive)
|
||||
(describe-bindings [key-chord]))
|
||||
|
||||
(defun key-chord-reset-all-state ()
|
||||
"Reset all key-chord state variables."
|
||||
(setq key-chord-last-unmatched nil)
|
||||
(setq key-chord-in-typing-flow nil)
|
||||
(setq key-chord-last-key-time nil)
|
||||
(setq key-chord-typing-reset-time nil))
|
||||
|
||||
(defun key-chord-reset-typing-detection ()
|
||||
"Reset typing detection state when `key-chord-mode' is toggled."
|
||||
(key-chord-reset-all-state))
|
||||
|
||||
(defun key-chord-reset-typing-mode ()
|
||||
"Reset the typing detection mode."
|
||||
(setq key-chord-in-typing-flow nil)
|
||||
(setq key-chord-last-unmatched nil))
|
||||
|
||||
(defun key-chord-pause-typing-flow ()
|
||||
"Temporarily pause typing flow detection to allow chord detection."
|
||||
(when key-chord-typing-detection
|
||||
(setq key-chord-in-typing-flow nil)))
|
||||
|
||||
(defun key-chord-valid-chord-char-p (char)
|
||||
"Return non-nil if CHAR is valid for chord detection.
|
||||
A valid character is an integer in the range 0-255."
|
||||
(and (integerp char) (< char 256)))
|
||||
|
||||
(defun key-chord-execute-chord (first-char next-char)
|
||||
"Execute the chord composed of FIRST-CHAR and NEXT-CHAR.
|
||||
Returns the key-chord event to be processed."
|
||||
;; Record the chord for macro tracking
|
||||
(setq key-chord-defining-kbd-macro
|
||||
(cons first-char key-chord-defining-kbd-macro))
|
||||
;; Reset state after successful chord execution
|
||||
(setq key-chord-last-unmatched nil)
|
||||
;; Return the chord event
|
||||
(list 'key-chord first-char next-char))
|
||||
|
||||
(defun key-chord-no-chord (first-char &optional next-char)
|
||||
"Handle the case when no chord is matched.
|
||||
FIRST-CHAR is the first character pressed.
|
||||
If NEXT-CHAR is provided, it will be added to `unread-command-events`."
|
||||
(when next-char
|
||||
(setq unread-command-events (cons next-char unread-command-events)))
|
||||
(setq key-chord-last-unmatched first-char)
|
||||
(list first-char))
|
||||
|
||||
(defun key-chord-check-typing-flow (current-time)
|
||||
"Check if user is in typing mode based on timing between keystrokes.
|
||||
CURRENT-TIME is the time at which the function was called."
|
||||
(when key-chord-typing-detection
|
||||
;; Check if we need to reset typing flow based on elapsed time
|
||||
(when (and key-chord-typing-reset-time
|
||||
(> (float-time current-time) key-chord-typing-reset-time))
|
||||
(setq key-chord-in-typing-flow nil)
|
||||
(setq key-chord-last-unmatched nil)) ;; Reset last-unmatched when exiting typing flow
|
||||
|
||||
;; Set new reset time
|
||||
(setq key-chord-typing-reset-time
|
||||
(+ (float-time current-time) key-chord-typing-reset-delay))
|
||||
|
||||
;; Check if we're in typing flow based on timing
|
||||
(unless key-chord-in-typing-flow
|
||||
(when key-chord-last-key-time
|
||||
(let ((elapsed (float-time (time-subtract current-time key-chord-last-key-time))))
|
||||
(when (< elapsed key-chord-typing-speed-threshold)
|
||||
(setq key-chord-in-typing-flow t)))))
|
||||
|
||||
;; Update last key time
|
||||
(setq key-chord-last-key-time current-time)))
|
||||
|
||||
(defun key-chord-input-method (first-char)
|
||||
"Input method controlled by key bindings with the prefix `key-chord'."
|
||||
(cond
|
||||
((and (not (eq first-char key-chord-last-unmatched))
|
||||
(key-chord-lookup-key (vector 'key-chord first-char)))
|
||||
(let ((delay (if (key-chord-lookup-key
|
||||
(vector 'key-chord first-char first-char))
|
||||
key-chord-one-key-delay
|
||||
key-chord-two-keys-delay)))
|
||||
(cond ((if executing-kbd-macro
|
||||
(not (memq first-char key-chord-in-last-kbd-macro))
|
||||
(when (bound-and-true-p eldoc-mode)
|
||||
(eldoc-pre-command-refresh-echo-area))
|
||||
(sit-for delay 'no-redisplay))
|
||||
(setq key-chord-last-unmatched nil)
|
||||
(list first-char))
|
||||
(t ; input-pending-p
|
||||
(let* ((input-method-function nil)
|
||||
(next-char (read-event))
|
||||
(res (vector 'key-chord first-char next-char)))
|
||||
(cond ((key-chord-lookup-key res)
|
||||
(setq key-chord-defining-kbd-macro
|
||||
(cons first-char key-chord-defining-kbd-macro))
|
||||
(list 'key-chord first-char next-char))
|
||||
(t ;put back next-char and return first-char
|
||||
(setq unread-command-events
|
||||
(cons next-char unread-command-events))
|
||||
(when (eq first-char next-char)
|
||||
(setq key-chord-last-unmatched first-char))
|
||||
(list first-char))))))))
|
||||
(t ; no key-chord keymap
|
||||
(setq key-chord-last-unmatched first-char)
|
||||
(list first-char))))
|
||||
"Input method controlled by key bindings with the prefix `key-chord'.
|
||||
FIRST-CHAR is the first character input by the user."
|
||||
;; Check if we're in a read-char context and bypass key-chord if so
|
||||
(if (key-chord-in-read-char-context-p)
|
||||
(progn
|
||||
(key-chord-reset-all-state)
|
||||
(list first-char))
|
||||
|
||||
;; Not in read-char context, proceed with normal key-chord processing
|
||||
(progn
|
||||
;; Check typing mode (but not during macro execution)
|
||||
(unless executing-kbd-macro
|
||||
(key-chord-check-typing-flow (current-time)))
|
||||
|
||||
(cond
|
||||
;; For any non-valid chord character, bypass chord detection
|
||||
((not (key-chord-valid-chord-char-p first-char))
|
||||
(setq key-chord-last-unmatched nil) ;; Reset for safety
|
||||
(list first-char))
|
||||
|
||||
;; Skip chord detection if in typing mode (but not during macro execution)
|
||||
((and key-chord-typing-detection
|
||||
key-chord-in-typing-flow
|
||||
(not executing-kbd-macro))
|
||||
;; Don't update key-chord-last-unmatched in typing flow mode
|
||||
(list first-char))
|
||||
|
||||
;; Skip chord detection during macro execution if key-chord-in-macros is nil
|
||||
;; or if the key is not in a recorded chord
|
||||
((and executing-kbd-macro
|
||||
(or (not key-chord-in-macros)
|
||||
(not (memq first-char key-chord-in-last-kbd-macro))))
|
||||
(key-chord-no-chord first-char))
|
||||
|
||||
;; Optimization: Skip chord detection completely if key tracking is enabled
|
||||
;; and first-char is not used in any chord
|
||||
((and key-chord-use-key-tracking
|
||||
(not (aref key-chord-keys-in-use first-char)))
|
||||
(key-chord-no-chord first-char))
|
||||
|
||||
;; Continue with chord detection for keys that might be part of a chord
|
||||
((not (eq first-char key-chord-last-unmatched))
|
||||
(key-chord-pause-typing-flow) ;; Temporarily exit typing flow for chord detection
|
||||
(let ((res (key-chord-lookup-key (vector 'key-chord first-char))))
|
||||
(if (not res)
|
||||
(key-chord-no-chord first-char)
|
||||
(let ((start-time (current-time))
|
||||
(delay (if (key-chord-lookup-key
|
||||
(vector 'key-chord first-char first-char))
|
||||
key-chord-one-key-delay
|
||||
key-chord-two-keys-delay)))
|
||||
|
||||
;; Read second character
|
||||
(let ((next-char (read-event nil nil delay)))
|
||||
(cond
|
||||
;; No second character received within timeout
|
||||
((null next-char)
|
||||
(key-chord-no-chord first-char))
|
||||
|
||||
;; Handle invalid next character
|
||||
((not (key-chord-valid-chord-char-p next-char))
|
||||
(key-chord-no-chord first-char next-char))
|
||||
|
||||
;; Handle double-press of the same key (with timing checks)
|
||||
((eq first-char next-char)
|
||||
(let ((elapsed (float-time (time-subtract (current-time) start-time))))
|
||||
(if (< elapsed key-chord-one-key-min-delay)
|
||||
;; Too fast (key held down) - not a chord
|
||||
(key-chord-no-chord first-char next-char)
|
||||
;; Check if this is a valid same-key chord
|
||||
(if (key-chord-lookup-key (vector 'key-chord first-char next-char))
|
||||
(key-chord-execute-chord first-char next-char)
|
||||
;; Not a valid chord
|
||||
(key-chord-no-chord first-char next-char)))))
|
||||
|
||||
;; Handle two different keys
|
||||
(t
|
||||
;; Check if this is a valid two-key chord
|
||||
(if (key-chord-lookup-key (vector 'key-chord first-char next-char))
|
||||
(key-chord-execute-chord first-char next-char)
|
||||
;; Not a valid chord
|
||||
(key-chord-no-chord first-char next-char)))))))))
|
||||
;; Key was last unmatched
|
||||
(t (key-chord-no-chord first-char))))))
|
||||
|
||||
(defun key-chord--start-kbd-macro (_append &optional _no-exec)
|
||||
"Reset key chord tracking when a keyboard macro is started.
|
||||
_APPEND and _NO-EXEC are ignored arguments from `start-kbd-macro'."
|
||||
(setq key-chord-defining-kbd-macro nil))
|
||||
|
||||
(advice-add 'start-kbd-macro :after #'key-chord--start-kbd-macro)
|
||||
|
||||
|
||||
(defun key-chord--end-kbd-macro (&optional _repeat _loopfunc)
|
||||
"Save key chord information when a keyboard macro ends.
|
||||
_REPEAT and _LOOPFUNC are ignored arguments from `end-kbd-macro'."
|
||||
(setq key-chord-in-last-kbd-macro key-chord-defining-kbd-macro))
|
||||
|
||||
(advice-add 'end-kbd-macro :after #'key-chord--end-kbd-macro)
|
||||
|
||||
|
||||
(provide 'key-chord)
|
||||
;; Local Variables:
|
||||
;; indent-tabs-mode: nil
|
||||
|
||||
Reference in New Issue
Block a user