Here's a minimal snippet to get things going:
(define-prefix-command 'foo)
(define-key foo "a" 'bar)
(define-key foo "b" 'baz)
(global-set-key (kbd "C-M-r") 'foo)
Now I can "call" the foo keymap when I press C-M-r.
But I wonder how I can do this from code, e.g. something like:
(funcall (lambda-from-keymap 'foo))
After this call, I expect the focus to be in the minibuffer, expecting either
a, or b or C-h to be entered.
Is something like this possible?
You can use read-key-sequence and lookup-key to implement this:
(defun call-keymap (map &optional prompt)
"Read a key sequence and call the command it's bound to in MAP."
;; Note: MAP must be a symbol so we can trick `describe-bindings' into giving
;; us a nice help text.
(let* ((overriding-local-map `(keymap (,map . ,map)))
(help-form `(describe-bindings ,(vector map)))
(key (read-key-sequence prompt))
(cmd (lookup-key map key t)))
(if (functionp cmd) (call-interactively cmd)
(user-error "%s is undefined" key))))
If you hit C-h read-key-sequence still waits for you to complete the sequence. I think you could simulate Emacs' normal behaviour by looping with read-key instead, it's a bit more involved though.
Use it like this:
(defun bar () (interactive) (message "you called bar"))
(defun baz () (interactive) (message "you called baz"))
(define-prefix-command 'foo)
(define-key foo "a" 'bar)
(define-key foo "b" 'baz)
(global-set-key (kbd "C-M-r") 'foo)
(defun call-foo ()
(interactive)
;; Note: pass the symbol form of the keymap so we can give nice help
(call-keymap 'foo "enter a foo command: "))
(global-set-key (kbd "C-c f") 'call-foo)
If the keymap is bound to a key sequence, you can invoke it by emulating the exact key sequence by setting unread-command-events:
(setq unread-command-events
(mapcar (lambda (e) `(t . ,e))
(listify-key-sequence (kbd "C-M-r"))
You need to have foo interactive. I did so using:
(global-set-key (kbd "C-M-r") (lambda () (interactive) ( foo)))
This should fix your problem.
Related
I have buffers named *terminal<1>*, *terminal<2>*, *terminal<3>*, etc. Is there a way to bind a key combination that will take an argument for the number? That is, I want to bind C-c 1 to switch to *terminal<1>* and C-c 2 to switch to *terminal<2>*, and so on. If I can't do this directly, is it possible to do metaprogramming in Elisp that will define all the functions for me?
In this suggestion, the interactive switch-to-terminal will take either a prefix argument C-u 2 for example, or prompt the user.
The macro then makes setting up the key bindings a little easier.
Finally I bind C-c 1 to C-c 4 to switch to *terminal<1>* to *terminal<4>*.
(defun switch-to-terminal (buf-num)
(interactive "NNumber of buffer to visist: ")
(let* ((buf-name (format "*terminal<%d>*" buf-num))
(buf (get-buffer buf-name)))
(unless buf
(error "No buffer %s" buf-name))
(switch-to-buffer buf)))
(defmacro bind-switch-to-terminal (num)
`(global-set-key
,(kbd (format "C-c %d" num))
(lambda ()
(interactive)
(switch-to-terminal ,num))))
(bind-switch-to-terminal 1)
(bind-switch-to-terminal 2)
(bind-switch-to-terminal 3)
(bind-switch-to-terminal 4)
This change uses the same switch-to-terminal function, but replaces bind-switch-to-terminal with a function. The lexical-let* allows for the creation of a closure to create unique terminal switching functions, the dotimes loop then binds for C-c 1 to C-c 9.
(defun bind-switch-to-terminal (num)
(lexical-let* ((buf-num num)
(switch-func
(lambda ()
(interactive)
(switch-to-terminal buf-num))))
(global-set-key
(kbd (format "C-c %d" buf-num))
switch-func)))
(dotimes (num 9)
(bind-switch-to-terminal (1+ num)))
You can bind keys as usual:
(global-set-key (kbd "C-c 1") (lambda ()
(interactive)
(switch-to-buffer "*terminal<1>*")))
To create all the shortcuts from 1 to 9 we would use macros.
edit: This buggy version may put you on tracks. I give up :(
(defmacro gototerminal (count)
`(global-set-key (kbd ,(concat "C-c " (number-to-string count)))
;; with the comma I want to evaluate what is inside concat
(lambda () (interactive)
(switch-to-buffer (concat "*terminal<" ,count ">*"))))
)
(progn (setq count 1)
(while (< count 10)
(gototerminal count)
(setq count (1+ count))
))
ps: an elisp debugger is edebug. Set it with C-u C-M-x
I would write a function that calls interactive with an n argument which indicates that the function reads a number from the mini buffer:
(defun test (x)
(interactive "nNumber of buffer to visit: ")
(message (concat "received number: " (number-to-string x))))
Binding this to a key will let you enter a number in the mini buffer.
Another way is to use a numerical argument:
(defun test (x)
(interactive "P")
(message (concat "received number: " (number-to-string x))))
Say you bind this function to C-c c, you can then pass it the number 2 as an argument by pressing C-u 2 C-c c.
If you avoid the usage of an existing prefix key like C-c you can trigger a command with one keypress e.g. F9.
This command can have a single key as input.
Example:
(defun test (k)
(interactive "K")
(message "Pressed key: %d" (- (aref k 0) ?0)))
(local-set-key [f9] 'test)
Emacs' Bookmarks are great, as is navigating the list of buffers. But I find it nice to have shorter chords to get directly to where I want to go. So I have been accruing a repetitive set of functions and keybindings for all my favorite files and folders.
I wondered if I could use a dolist to handle this repetitiveness, but am no good programmer.
Here is one of the (seven and rising) repetitively defined functions, and a feeble attempt at writing the dolist following:
(defun jump-to-main ()
(interactive)
(find-file main))
(global-set-key (kbd "C-c m") 'jump-to-main)
This might barely qualify as pseudocode:
(dolist (x '(("m" main)
("t" tech))
(defun (concat 'jump-to- (cdr x)) ()
(interactive)
(find-file (cdr x)))
(global-set-key (kbd (concat "C-c " (car x)))
'(concat 'jump-to- (cdr x)))
))
The payoff in an elegant init file, versus how fast I am at solving at Lisp problems... hoping the Stackoverflow can save me.
Other strategies and approaches are appreciated.
EDIT:
With lawlist's suggestion, my question may reduce and be more clear presented the following way. I would like to reduce the repetition in the following series of keybindings.
(global-set-key (kbd "C-c A")
(lambda ()
(interactive)
(find-file fileA)))
(global-set-key (kbd "C-c B")
(lambda ()
(interactive)
(find-file fileB)))
...
(global-set-key (kbd "C-c Z")
(lambda ()
(interactive)
(find-file fileZ)))
fileK for example expands to something like "~/fileK.txt".
EDIT:
So here is another try:
(dolist (x '(("m" main)
("t" tech))
(global-set-key (kbd (concat "C-c " (car x)))
(lambda ()
(interactive)
(find-file (cdr x))
))))
The keybinding part seems okay, but (find-file (cdr x)) isn't doing what I need, and I couldn't fix it in a small amount of googling lisp.
Here are the expressions I'm using to focus on the broken parts:
(setq somefile "~/somefile.txt")
(setq x '("s" . somefile))
(concat "C-c " (car x))
(find-file (cdr x))
The last line is the one that doesn't work, as (cdr x) apparently evaluates to (main). I tried slipping in an eval to expand the main variable, but it doesn't seem...
Reading Phil's answer now, this may take me a while.
FWIW, a fairly direct translation of your pseudo-code is:
(defvar my-file-main (expand-file-name "~/main") "My main file")
(defvar my-file-tech (expand-file-name "~/tech") "My tech file")
(dolist (x '(("m" . my-file-main)
("t" . my-file-tech)))
(let* ((sym (cdr x))
(func (intern (concat "jump-to-" (symbol-name sym)))))
(defalias func `(lambda ()
,(format "Jump to file `%s'." (symbol-name sym))
(interactive)
(find-file ,sym)))
(global-set-key (kbd (concat "C-c " (car x))) func)))
I added the following code to my .emacs file.
(defun delete-right-window ()
(interactive)
(windmove-right)
(delete-window))
(defun delete-left-window ()
(interactive)
(windmove-left)
(delete-window))
(defun delete-below-window ()
(interactive)
(windmove-down)
(delete-window))
(defun delete-above-window ()
(interactive)
(windmove-up)
(delete-window))
(global-set-key (kbd "C-s-<right>") 'delete-right-window)
(global-set-key (kbd "C-s-<left>") 'delete-left-window)
(global-set-key (kbd "C-s-<down>") 'delete-below-window)
(global-set-key (kbd "C-s-<up>") 'delete-above-window)
As you can see, most of the codes are repetitive.
I read How do I pass a function as a parameter to in elisp? and tried to refactor the code to passing windmove-* function as follow:
(defun delete-other-window (callback)
(interactive)
(funcall callback)
(delete-window))
...
(defun delete-right ()
(delete-other-window 'windmove-right))
And I bound keystroke like this:
(global-set-key (kbd "C-s-<right>") 'delete-right)
But when I hit C-s-<right> it doesn't work, only display Wrong type argument: commandp, delete-right in the mini buffer.
What am I missing or what should I do to work the code correctly?
Here's a fix:
(defun delete-after (fn)
`(lambda () (interactive)
(,fn)
(delete-window)))
(global-set-key (kbd "C-s-<right>") (delete-after 'windmove-right))
(global-set-key (kbd "C-s-<left>") (delete-after 'windmove-left))
(global-set-key (kbd "C-s-<down>") (delete-after 'windmove-down))
(global-set-key (kbd "C-s-<up>") (delete-after 'windmove-up))
To make a function into a command, you need to add the special form interactive to it:
(defun delete-right ()
(interactive)
(delete-other-window 'windmove-right))
The problem with your code is that it's the function bind to the key that should be interactive. Not one of the function it call:
(defun delete-other-window (callback)
(funcall callback)
(delete-window))
(defun delete-right ()
(interactive)
(delete-other-window 'windmove-right))
You could also use a macro:
(defmacro defun-delete-other-window (direction)
`(defun ,(intern (concat "delete-" direction)) ()
(interactive)
(,(intern (concat "windmove-" direction)))
(delete-window)))
(defun-delete-other-window "right")
I've got a snippet I want to bind to a key:
(define-key (lisp-interaction-mode-map) (kdb "C-c e")
(let ((result (eval (read (buffer-substring
(point-at-bol) (point-at-eol))))))
(goto-char (point-at-eol))
(insert (format " ; %s" result))))
however, when C-c v that in *scratch*, I get
define-key: Symbol's function definition is void: lisp-interaction-mode-map
You have parentheses around lisp-interaction-mode-map, which Lisp interprets to mean that you want to call the function named lisp-interaction-mode-map. Instead, you want to use it as variable.
I also did some other edits you'll need to get what you have to work. I assumed you wrote read in order to prompt for a user-inputted string:
(define-key lisp-interaction-mode-map (kbd "C-c C-e")
(lambda (result)
(interactive (list (read-from-minibuffer (buffer-substring (point-at-bol) (point-at-eol)))))
(goto-char (point-at-eol))
(insert (format " ; %s" result))))
What's wrong with the normal form?
(define-key lisp-interaction-mode-map [(control e)] 'eval-defun)
I would like the "delete to end of word" command to delete the word, regardless of cursor position.
(defun my-kill-word ()
(interactive)
(backward-word)
(kill-word 1))
(global-set-key (kbd "M-d") 'my-kill-word)
A better code could be:
(defun my-kill-word ()
(interactive)
(unless (looking-at "\\<")
(backward-word))
(kill-word 1))
(global-set-key (kbd "M-d") 'my-kill-word)
So we move backward only if we are not at the beginning of the word
yet.