Emacs, как связать команду со всеми ключами, за исключением нескольких ключей? - PullRequest
4 голосов
/ 18 декабря 2011

Я хочу создать дополнительный режим (foo-mode), который имеет свою раскладку клавиш (foo-mode-map), но когда пользователь нажимает любую клавишу, не входящую в (foo-mode-map), дополнительный режимуволиться.Как связать turn-off-foo-mode все остальные ключи?

РЕДАКТИРОВАТЬ: вот решение, которое я придумал, основываясь на выбранном ответе.Он также принимает числовой ввод.

(defalias 'foo-electric-delete 'backward-kill-word)

(defun foo-mode-quit (&optional arg)
  (interactive)
  (let ((global-binding (lookup-key (current-global-map)
                                    (single-key-description last-input-event))))
    (unless (eq last-input-event ?\C-g)
      (push last-input-event unread-command-events))
    (unless (memq global-binding
                  '(negative-argument digit-argument))
      (foo-mode -1))))

(defvar foo-mode-map
  (let ((map (make-keymap)))
    (set-char-table-range (nth 1 map) t 'foo-mode-quit)
    (define-key map "-" 'negative-argument)
    (dolist (k (number-sequence ?0 ?9))
      (define-key map (char-to-string k) 'digit-argument))
    (define-key map [backspace] 'foo-electric-delete)
    map))

(define-minor-mode foo-mode
  "Toggle Foo mode.
     With no argument, this command toggles the mode.
     Non-null prefix argument turns on the mode.
     Null prefix argument turns off the mode.

     When Foo mode is enabled, the control delete key
     gobbles all preceding whitespace except the last.
     See the command \\[foo-electric-delete]."
  ;; The initial value.
  :init-value nil
  ;; The indicator for the mode line.
  :lighter " Foo"
  ;; The minor mode bindings.
  :keymap foo-mode-map
  :group 'foo)

Ответы [ 4 ]

4 голосов
/ 18 декабря 2011

Решение 2: Вы можете использовать set-char-table-range, чтобы установить все символы на карте.Например:

(defvar foo-mode-map
  (let ((map (make-keymap)))
    (set-char-table-range (nth 1 map) t 'foo-turn-off-foo-mode)
    ...
    map))
4 голосов
/ 19 декабря 2011

Я включил полный рабочий пример для создания второстепенного режима с желаемым поведением; ключ должен использовать set-char-table-range на раскладке, созданной make-keymap, которая создает плотную раскладку с полными char-table; использование этого на разреженной карте ключей, созданной с помощью make-sparse-keymap, не будет работать.

(defalias 'foo-electric-delete 'backward-kill-word)

(defun foo-mode-quit (&optional arg)
  (interactive)
  (foo-mode -1))

(defvar foo-mode-map
  (let (map (make-keymap))
    (set-char-table-range (nth 1 map) t 'foo-mode-quit)
    (define-key map [backspace] 'foo-electric-delete)
    map))

(define-minor-mode foo-mode
  "Toggle Foo mode.
     With no argument, this command toggles the mode.
     Non-null prefix argument turns on the mode.
     Null prefix argument turns off the mode.

     When Foo mode is enabled, the control delete key
     gobbles all preceding whitespace except the last.
     See the command \\[foo-electric-delete]."
  ;; The initial value.
  :init-value nil
  ;; The indicator for the mode line.
  :lighter " Foo"
  ;; The minor mode bindings.
  :keymap foo-mode-map
  :group 'foo)

(defvar major-baz-mode-map '(keymap (t . major-baz-mode-default-function)))

Установка привязки по умолчанию для карты основного режима более проста, и я привожу здесь этот пример, но, как я уже отмечал выше, этот тип запасной таблицы ключей не будет работать для вспомогательного режима:

(defvar major-baz-mode-map '(keymap (t . major-baz-mode-default-function)))

Это обсуждается в документации в Формат раскладок , где говорится:

(t . binding)

This specifies a default key binding; any event not bound by other
elements of the keymap is given binding as its binding. Default
bindings allow a keymap to bind all possible event types without
having to enumerate all of them. A keymap that has a default binding
completely masks any lower-precedence keymap, except for events
explicitly bound to nil (see below).
4 голосов
/ 18 декабря 2011

Одна вещь, которую вы должны решить, - если, когда пользователь нажимает другую клавишу, вы должны просто выйти из своего режима, или если вы должны выполнить команду, связанную с рассматриваемой клавишей (как в incremental-search).

Один из способов - использовать pre-command-hook, чтобы проверить, является ли this-command одной из ваших команд, и отключить режим, если это не так.

1 голос
/ 19 декабря 2011

Ответ 3:

Я бы сказал, что выбранное решение слишком сложно.Начать манипулировать очередью событий - это не то, что вы обычно хотели бы сделать.

Вместо этого я бы предложил совершенно другое решение.Если вы оставляете режим всегда включенным, вы можете привязать такие клавиши, как backspace , к функции, которая могла бы сама определять, должна ли она работать на символьной основе или на словах.

Ниже приведено простое доказательство концепции.Он по-прежнему имеет явную функцию для входа в режим слова, но его можно обобщить в функции, которые одновременно активируют режим слова и выполняют какие-то действия.

(defun foo-electric-enabled nil
  "Non-nil when foo-mode operate in word mode.")

(defun foo-electric-delete (&optional arg)
  (interactive "p")
  (if (and foo-electric-enabled
           (memq last-command '(kill-region
                                foo-electric-delete
                                foo-enable-word-operations)))
      (call-interactively 'backward-kill-word)
    (setq foo-electric-enabled nil)
    (call-interactively 'backward-delete-char-untabify)))

(defvar foo-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map [backspace] 'foo-electric-delete)
    map))

(defun foo-enable-word-operations ()
  (interactive)
  (setq foo-electric-enabled t))

(define-minor-mode foo-mode
  "Toggle Foo mode.
With no argument, this command toggles the mode.
Non-null prefix argument turns on the mode.
Null prefix argument turns off the mode.

When Foo mode is enabled, the backspace key
gobbles all preceding whitespace except the last.
See the command \\[foo-electric-delete]."
  ;; The initial value.
  :init-value nil
  ;; The indicator for the mode line.
  :lighter " Foo"
  ;; The minor mode bindings.
  :keymap foo-mode-map
  :group 'foo)

Приведенный выше код можно улучшить, но я оставляюв качестве упражнения для читателя:

  • Вместо проверки, является ли функция членом литерального списка, может существовать список для всего модуля, в качестве альтернативы, вы можете использовать свойства для функцийэто следует рассматривать так, как если бы вы хотели оставаться в режиме слова.

  • Такие вещи, как индикатор строки состояния, всегда включены.Реализуя этот пакет как использование других второстепенных режимов, вы можете использовать второй, чтобы владеть индикатором строки режима и указывать, что пакет находится в режиме слова, заменяя foo-electric-enabled и foo-enable-word-operations.

  • Функция foo-electric-delete явно вызывает либо backward-kill-word, либо backword-delete-char-untabify.Существует целый ряд методов для вызова того, что было привязано к метабазу и возврат соответственно.

...