Locally override keybinding for emacs major mode - emacs

What is the proper way to override a keybinding for a major mode so it only affects the buffer-local keymap? I thought I could use local-set-key or make-local-variable, but my attempts there affect the keymaps globally (shown below).
Is it necessary to copy the map, as done below, or create a minor mode with a different keymap? It would be nice to be able to make it a local variable if possible.
For example, say I want to have original bound to C-c C-c in the global emacs-lisp-mode-map but after calling jump-to-other-buffer I want C-c C-c to be bound to local-version only in that buffer.
(defun original ()
(interactive)
(message "original"))
(defun local-version ()
(interactive)
(message "local binding"))
;; open new buffer in emacs-lisp mode and set a local key
(defun jump-to-other-buffer ()
(interactive)
(with-current-buffer (get-buffer-create "*test1*")
(emacs-lisp-mode)
;; These change bindings in all elisp buffers
;; (make-local-variable 'emacs-lisp-mode-map)
;; (define-key emacs-lisp-mode-map (kbd "C-c C-c") 'local-version)
;; (local-set-key (kbd "C-c C-c") 'local-version)
(let ((overriding-local-map (copy-keymap emacs-lisp-mode-map)))
(define-key overriding-local-map (kbd "C-c C-c") 'local-version)
(use-local-map overriding-local-map))
(pop-to-buffer (current-buffer))))
;; default binding in elisp buffers
(define-key emacs-lisp-mode-map (kbd "C-c C-c") 'original)

Related

Evil Emacs: is there a way to disable vim-like keys in «insert» mode?

I like VIM idea of text objects, so I installed EVIL (a Emacs plugin to emulate VIM features). But I'd like «insert» mode to leave Emacs keybindings unchanged (except perhaps Escape which is to switch into «normal» mode). Any way to achieve this?
By the way: ATM «insert» mode have a mixed set of hotkeys, which isn't very comfortable either way. E.g. the «M-b» works as in Emacs, but the «C-o» works as in VIM.
In the #emacs IRC channel I was told that someone already solved a similar problem. Here's the modified version I use:
(require 'evil)
;; remove all keybindings from insert-state keymap
(setcdr evil-insert-state-map nil)
;; but [escape] should switch back to normal state
(define-key evil-insert-state-map [escape] 'evil-normal-state)
(define-key evil-normal-state-map (kbd "C-u") 'evil-scroll-up)
(define-key evil-normal-state-map (kbd "[ m") 'beginning-of-defun)
(define-key evil-normal-state-map (kbd "] m") 'end-of-defun)
(define-key evil-normal-state-map (kbd "k") 'evil-previous-visual-line)
(define-key evil-normal-state-map (kbd "j") 'evil-next-visual-line)
(evil-mode t)
(setq evil-jumps-cross-buffers nil) ;; for C-o and C-i to not cross buffers
(provide 'emvil)
The (provide 'emvil) is to allow require 'ing it in the configuration. I also found it useful to jump-to-definition in the next split screen unless the definition is in the buffer I'm currently in. Here's the code:
(defun evil-goto-definition-next-split ()
"If there's a free split, goto definition in this split,
otherwise use current one (except when a definition in the
current split)"
(interactive)
(let ((origin-spl (selected-window))
(origin-buf (current-buffer)))
(evil-goto-definition)
(when (and (eq origin-spl (selected-window)) ;; otherwise it's done
(not (eq origin-buf (current-buffer)))) ;; otherwise either definition not found, or
;; it's in the same buffer
(let ((defin-buf (current-buffer))
(defin-point (point)))
(switch-to-buffer origin-buf)
(other-window 1)
(switch-to-buffer defin-buf)
(goto-char defin-point)
))
))
(define-key evil-normal-state-map (kbd "g d") 'evil-goto-definition-next-split)

How to unset a keybinding in a map locally to a mode?

I have set up a key binding for a function that modifies the behavior of Emacs auto-complete:
;; ISSUE: when I type the whole candidate string and then press [SPC],
;; Emacs will insert two spaces.
(define-key ac-menu-map (kbd "SPC")
(defun ac-complete-with-space ()
"Select the candidate and append a space. Save your time for typing space."
(interactive)
(ac-complete)
;; FIXME: this auto-inserts two spaces.
(insert " ")
))
... and I want to disable this key binding in ac-menu-map in org-mode only.
I have tried the following:
;; Avoid always selecting unwanted first candidate with auto-complete
;; when writing in org-mode.
(add-hook 'org-mode-hook
(lambda ()
;; (define-key ac-menu-map (kbd "SPC") nil)
;; (define-key ac-menu-map (kbd "SPC") 'self-insert-command)
;; (setq-local ac-menu-map (delq (kbd "SPC") ac-menu-map))
))
Unfortunately, this does not unset the key binding locally (i.e., only in org-mode). Instead, it removes the key binding from the ac-menu-map everywhere.
A different approach to solve your problem would be to check in ac-complete-with-space the mode. If org-mode, then call self-insert-command, else follow your current logic.
(defun ac-complete-with-space ()
"Select the candidate and append a space. save your time for typing space."
(interactive)
(ac-complete)
(insert " ")
)
;; NOTE: ac-completing-map is the parent map of ac-menu-map.
(define-key ac-completing-map (kbd "SPC") 'ac-complete-with-space)
(add-hook 'org-mode-hook
(lambda ()
(define-key ac-menu-map (kbd "SPC") 'self-insert-command)))

Emacs: how to get the global shortcut value

I have the following global keyboard shortcut in Emacs:
(global-set-key (kbd "C-<right>") 'forward-word)
For the org-mode I decided to redefine this shortcut. If the cursor stands on a link, then go to the link location. Otherwise - use forward-word function.
(defun is-link-p ()
(if (org-in-regexp org-bracket-link-regexp)
t))
(defun follow-link-or-next-word ()
(interactive)
(if (is-link-p)
(org-open-at-point)
(forward-word)))
(add-hook 'org-mode-hook (lambda ()
(define-key org-mode-map (kbd "C-<right>") 'follow-link-or-next-word)))
Is it possible to change org-mode shortcut in the following manner: instead of calling (forward-word), find what function is globally bound to "C-<right>" and call it instead.
Thus I won't need to change (forward-word) twice in case I decide to change the global shortcut.
I think you're looking for the function (lookup-key keymap key &optional accept-defaults)
This function returns the definition of key in keymap. All the other
functions described in this chapter that look up keys use lookup-key.
Here are examples:
(lookup-key (current-global-map) "\C-x\C-f")
⇒ find-file
(lookup-key (current-global-map) (kbd "C-x C-f"))
⇒ find-file
You could extend your functions:
(defun is-link-p ()
(if (org-in-regexp org-bracket-link-regexp)
t))
(defun follow-link-or-default-action()
(interactive)
(let ((global-default (lookup-key (current-global-map) (kbd "C-<right>"))))
(if (is-link-p)
(org-open-at-point)
(funcall global-default))))
(add-hook 'org-mode-hook (lambda ()
(define-key org-mode-map (kbd "C-<right>") 'follow-link-or-default-action)))

How to override/change mode key bindings in elisp?

In particular, when I load dired-x, it sets M-o to toggle the omit minor mode. I use M-o for other-window, so I would like to change the key that dired-x binds to something else. I've attempted setting the key after the mode loads like this:
(add-hook 'dired-mode-hook
(lambda ()
(dired-omit-mode 1)
(global-set-key (kbd "M-o") 'other-window)
))
but to no avail.
Slightly better than adding another copy of your custom global binding to the local mode map, would be removing the local binding so that it no longer shadows the global binding. You might also give that function a new key before you do this.
(eval-after-load "dired-x"
'(progn
;; Add an alternative local binding for the command
;; bound to M-o
(define-key dired-mode-map (kbd "C-c o")
(lookup-key dired-mode-map (kbd "M-o")))
;; Unbind M-o from the local keymap
(define-key dired-mode-map (kbd "M-o") nil)))
The dired-mode bindings "shadow" the global ones so your "global-set-key" isn't helping. What you want to do is override the dired-mode binding:
(add-hook 'dired-mode-hook
(lambda ()
(dired-omit-mode 1)
(define-key dired-mode-map (kbd "M-o") 'other-window)
))

Useful keyboard shortcuts and tips for ESS/R

I would like to ask regular ESS/R users what key bindings do they use frequently and tips on using ESS/R.
I have set several shortcuts in my .emacs file. The most useful are:
C-tab to switch between the R command line and the file (similar to josh answer, but much faster):
(global-set-key [C-tab] 'other-window)
Control and up/down arrow keys to search history with matching what you've already typed:
(define-key comint-mode-map [C-up] 'comint-previous-matching-input-from-input)
(define-key comint-mode-map [C-down] 'comint-next-matching-input-from-input)
Comment-uncomment a selected region with C-d or C-maj-d
(defun uncomment-region (beg end)
"Like `comment-region' invoked with a C-u prefix arg."
(interactive "r")
(comment-region beg end -1))
(define-key ess-mode-map (kbd "C-d") 'comment-region)
(define-key ess-mode-map (kbd "C-S-d") 'uncomment-region)
Also I've also enabled CUA mode (from options menu) and reconfigured quite a lot of shortcuts to require only two keystrokes (instead of four in standard mode):
;; Delete selection when pressing [delete] key
(delete-selection-mode t)
;; ESS Mode (.R file)
(define-key ess-mode-map "\C-l" 'ess-eval-line-and-step)
(define-key ess-mode-map "\C-p" 'ess-eval-function-or-paragraph-and-step)
(define-key ess-mode-map "\C-r" 'ess-eval-region)
;; iESS Mode (R console)
(define-key inferior-ess-mode-map "\C-u" 'comint-kill-input)
(define-key inferior-ess-mode-map "\C-w" 'backward-kill-word)
(define-key inferior-ess-mode-map "\C-a" 'comint-bol)
(define-key inferior-ess-mode-map [home] 'comint-bol)
;; Comint Mode (R console as well)
(define-key comint-mode-map "\C-e" 'comint-show-maximum-output)
(define-key comint-mode-map "\C-r" 'comint-show-output)
(define-key comint-mode-map "\C-o" 'comint-kill-output)
;; Search with C-f / C-F (control-maj-F for backware search)
(global-set-key "\C-f" 'isearch-forward)
(global-set-key (kbd "C-S-f") 'isearch-backward)
(define-key isearch-mode-map "\C-f" 'isearch-repeat-forward)
(define-key isearch-mode-map (kbd "C-S-f") 'isearch-repeat-backward)
;; Save with C-s / C-S
(global-set-key (kbd "C-s") 'save-buffer)
(global-set-key (kbd "C-S-s") 'write-file)
;; need to redefine them for isearch mode (don't know why)
(define-key isearch-mode-map (kbd "C-s") 'save-buffer)
(define-key isearch-mode-map (kbd "C-S-s") 'write-file)
;; Pause = dedicate window.
(defun toggle-current-window-dedication ()
(interactive)
(let* ((window (selected-window))
(dedicated (window-dedicated-p window)))
(set-window-dedicated-p window (not dedicated))
(message "Window %sdedicated to %s"
(if dedicated "no longer " "")
(buffer-name))))
(global-set-key [pause] 'toggle-current-window-dedication)
;; delete = delete
(global-set-key [delete] 'delete-char)
;; C-b = list buffers
(global-set-key (kbd "C-b") 'bs-show)
You will find many more useful shortcuts in ESS documentation.
C-c C-z ess-switch-to-end-of-ESS
is nice to jump from your source file that you are editing foo.R to the R console
I found this link to be extremely helpful. It provides elisp code to make Shift+Enter do many common tasks in a context dependent fashion.
http://kieranhealy.org/blog/archives/2009/10/12/make-shift-enter-do-a-lot-in-ess/
Great stuff, have been using it for ages. Unfortunately as of 15-11-2013 the uncomment key binding may not work due to EMACS changes (I think, at least it was working before I loaded the latest version). This is because the default uncomment function has 3 arguments but the one defined above has 2. The best way to fix this is to simply delete the uncomment function from the code and retain the keybinding, so it uses the default uncomment function. Or in other words just use this:
(define-key ess-mode-map (kbd "C-d") 'comment-region)
(define-key ess-mode-map (kbd "C-S-d") 'uncomment-region)
M-n and M-p in the ESS R console for next/previous command.