Passing a function to a parameter in elisp - emacs

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")

Related

How can I configure a hook with a lambda? (Saving parameters to use in lambdas)

I am currently trying to set up a C++ hook for setting shortcuts to compile different projects and I have the following code:
(defun configure-proj (proj key)
(add-hook 'c++-mode-hook
(function (lambda ()
(local-set-key (kbd (concatenate 'string key " u")) (lambda () (compile-unit-tests proj)))
(local-set-key (kbd (concatenate 'string key " d")) (lambda () (compile-debug proj)))
(local-set-key (kbd (concatenate 'string key " r")) (lambda () (compile-balanced proj)))
(local-set-key (kbd (concatenate 'string key " i")) (lambda () (compile-func-interactive proj "balanced" "11")))
(local-set-key (kbd (concatenate 'string key " c")) 'clean-all)))))
(configure-proj "Proj name" "<f4>")
The error states that there is a File mode specification error: (void-variable key). I know that the error in my code is that the key parameter from configure-proj is not being copied when the hook is being created, but I don't know how to fix it. Can anyone help?
Edit: Forgot to show how I run configure-proj.
As explained by coredump, the problem is that your config file is not using lexical scoping. So add -*- lexical-binding:t -*- somewhere on the first line of the file.
If you don't wish to enable lexical-binding for the whole library, then you could alternatively use a workaround like:
(defun configure-proj (proj key)
(add-hook 'c++-mode-hook
`(lambda ()
(local-set-key ,(kbd (concatenate 'string key " u")) (lambda () (interactive) (compile-unit-tests proj)))
(local-set-key ,(kbd (concatenate 'string key " d")) (lambda () (interactive) (compile-debug proj)))
(local-set-key ,(kbd (concatenate 'string key " r")) (lambda () (interactive) (compile-balanced proj)))
(local-set-key ,(kbd (concatenate 'string key " i")) (lambda () (interactive) (compile-func-interactive proj "balanced" "11")))
(local-set-key ,(kbd (concatenate 'string key " c")) 'clean-all))))

Elisp: call keymap from code?

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.

Emacs; custom functions defined repetitively, how to use dolist, or other approach

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)))

One-Key Macros in Emacs

I am trying to use One-Key Macros (as was written here)
(defun toggle-kbd-macro-recording-on ()
"One-key keyboard macros: turn recording on."
(interactive)
(define-key
global-map
(events-to-keys (this-command-keys) t)
'toggle-kbd-macro-recording-off)
(start-kbd-macro nil))
(defun toggle-kbd-macro-recording-off ()
"One-key keyboard macros: turn recording off."
(interactive)
(define-key
global-map
(events-to-keys (this-command-keys) t)
'toggle-kbd-macro-recording-on)
(end-kbd-macro))
(global-set-key '[(f1)] 'call-last-kbd-macro)
(global-set-key '[(shift f1)] 'toggle-kbd-macro-recording-on)
But when I press Shift-F1 I get error:
define-key: Symbol's function
definition is void: events-to-keys
How can I fix it?
The events-to-keys function seems to be something obsolete. From what I can tell, it should work by just writing:
(defun toggle-kbd-macro-recording-on ()
"One-key keyboard macros: turn recording on."
(interactive)
(define-key
global-map
(this-command-keys)
'toggle-kbd-macro-recording-off)
(start-kbd-macro nil))
(defun toggle-kbd-macro-recording-off ()
"One-key keyboard macros: turn recording off."
(interactive)
(define-key
global-map
(this-command-keys)
'toggle-kbd-macro-recording-on)
(end-kbd-macro))
(global-set-key '[(f1)] 'call-last-kbd-macro)
(global-set-key '[(shift f1)] 'toggle-kbd-macro-recording-on)

How to I remap the Emacs command M-d into the macro M-b, M-d?

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.