“/Show me your init file and I’ll tell you who you are./” – old proverb slightly modified
- Literate programming
- Construction Kit
- Space-time optimization
- Space optimization
- Time optimization
- Window navigation
- Display buffers in the most suitable window
- Shorter answers
- Don’t spend time answering useless questions
- Using the
ESC
key as a cancel key - Dedicated keymap
C-z
my-map - Insert-pair keys
- List structural editing
- Efficient navigation in different modes
- Better keybindings
- Repeat mode
- Context menu mode
- Useful features
- Useful settings
- Major modes
- Used packages
- Multi-language features
- Fixes for packages
- Fix timer in battery
- Fix inconsistency in motion keys
- Fix isearch
- Fix next-error
- Fix xref
- Fix info
- Fix set-variable
- Fix sit-for
- Fix follow-mode
- Fix
*Messages*
buffer - Fix mark in two windows with same buffer
- Fix outline-regexp
- Fix outline-mode in NEWS files
- Fix outline faces
- Fix org-mode faces
- Fix org-mode keys
- Fix org-mode code blocks
- Fix mode-line faces
- Fix dired
- Fix file sorting order between vc-dir and vc-diff
- Fall back between vc-diff and diff-backup
- Use project directories everywhere
- Other
- Copyright
First of all, as you may already noticed, this file follows the paradigm called Literate programming. It is a text document that has both of these features:
- Weaving: it is rendered to HTML while displayed either on GitLab/GitHub or when exported to HTML;
- Tangling: the same document is executed during Emacs loading.
One line of code that initiates loading is in the separate file init.el.
This makes it the smallest init.el
file possible, having metric of just 1 SLOC.
The single line in init.el
is the following:
(org-babel-load-file (locate-file "README.org" load-path))
It exports source code from this file to the file README.el
.
To support lexical binding, the output file should have the
lexical-binding
cookie in its header:
;;; .emacs --- Emacs init file -*- lexical-binding: t; buffer-read-only: t -*-
You can read more about details of weaving and tangling in the Org manual.
Using the same file README.org
as an self-executable document
fits nicely into the definition of Emacs where Emacs is the
self-documenting editor, and this file is a self-documenting init file.
There are many different definitions what Emacs really is — some call it Integrated Development Environment (IDE), some call it a Lisp machine, etc. and all of them are right. What definition would be more relevant in the context of this configuration file is that Emacs not just an editor, but the Editor Construction Kit — that means an editor creation toolkit whose blocks could be combined in infinitely many ways to build a completely new editor — and not just an editor but anything possible: web browser, mail client, butterfly… So Emacs is more like a UI framework, and a platform for apps.
And indeed, looking at different customizations, it’s easy to notice that none of them are like other editors based on the same Emacs core. Their differences are so significant that it is hard to believe that they come from the same source.
While constructing an own editor, one of the most important goals is to optimize it in both dimensions: in space and time.
Optimization of space means to make usage of screen space more optimal, and optimization of time means to reduce time required to perform different tasks.
An example where both space and time is optimized is automatic reposition
of text in windows, so when text is automatically aligned, you don’t need
to spend time scrolling the buffer backward/forward, and space is not wasted
for parts of the buffer that are not relevant to the task at hand.
One function that help to achieve optimal reposition is reposition-window
,
use it where possible in hooks that display a new location:
(defun my-reposition-window ()
"Override default command that doesn't handle some modes."
(unless (pos-visible-in-window-p)
(if (derived-mode-p '( fundamental-mode dired-mode Man-mode
markdown-mode conf-mode mail-archives-mode))
(recenter-top)
(reposition-window))))
(add-hook 'next-error-hook 'my-reposition-window)
(add-hook 'find-function-after-hook 'my-reposition-window)
(add-hook 'imenu-after-jump-hook 'my-reposition-window)
(add-hook 'xref-after-return-hook 'my-reposition-window)
(add-hook 'xref-after-jump-hook 'my-reposition-window)
(remove-hook 'xref-after-jump-hook 'recenter)
And fix packages to support better reposition, i.e. define rules how these packages should recenter windows:
Adjust window to show the current outline completely:
(add-hook
'org-mode-hook
(lambda ()
;; ‘C-M-l’ (reposition-window) relies on ‘beginning-of-defun’
;; to make the current org outline heading visible.
(setq-local beginning-of-defun-function
(lambda () (org-previous-visible-heading 1)))
(setq-local end-of-defun-function
(lambda () (org-next-visible-heading 1)))))
(add-hook
'outline-mode-hook
(lambda ()
;; ‘C-M-l’ (reposition-window) relies on ‘beginning-of-defun’
;; to make the current outline heading visible.
(setq-local beginning-of-defun-function
(lambda () (outline-previous-visible-heading 1)))
(setq-local end-of-defun-function
(lambda () (outline-next-visible-heading 1)))))
Adjust window to show the current diff hunk completely:
(with-eval-after-load 'diff-mode
;; ‘C-M-l’ (reposition-window) relies on ‘beginning-of-defun’
;; to make the current hunk visible.
(add-hook 'diff-mode-hook
(lambda ()
(setq-local beginning-of-defun-function #'diff-beginning-of-hunk)
(setq-local end-of-defun-function #'diff-end-of-hunk))))
Instead of the default behavior that recenters to the middle of the screen, add customization that recenter to the middle of the top half of the screen to reduce time spent for scrolling and adjusting the position of edited text:
(setq-default
recenter-positions '(0.15 top)
next-error-recenter 15
compare-windows-recenter '(15 15))
(defvar my-recenter-position nil
"Default recenter position.")
(when (boundp 'recenter-positions)
(setq my-recenter-position (car recenter-positions)))
(defun recenter-top ()
(interactive)
(recenter (round (* my-recenter-position (window-height)))))
Let C-M-a
(beginning-of-defun
) not scroll the window
when after jumping point stays within current window bounds:
(define-advice beginning-of-defun (:around (ofun &rest args) recenter-top)
(let ((w-s (window-start))
(w-e (window-end)))
(apply ofun args)
(when (and
;; Only when used interactively
(eq this-command 'beginning-of-defun)
;; And only when jumping outside of window
;; to the center of the window
(or (< (point) w-s) (> (point) w-e)))
(recenter-top))))
By default, Emacs looks like a typical GUI application with the menu bar, tool bar, scroll bars, etc. The problem is that these nice-looking UI elements occupy precious screen real estate. Some parts of this configuration deal with this problem by reclaiming unused space to maximize information density on the screen.
Get rid of all space-wasting garbage and minimize clutter.
(and (fboundp 'menu-bar-mode) (menu-bar-mode -1))
(and (fboundp 'scroll-bar-mode) (scroll-bar-mode -1))
(and (fboundp 'tool-bar-mode) (tool-bar-mode -1))
(and (fboundp 'tooltip-mode) (fboundp 'x-show-tip) (tooltip-mode -1))
Tabs introduced in Emacs 27 can be used without the tab-bar when
tab-bar-show
is customized to nil
. Without the tab-bar you can switch
between tabs using completion on tab names, or using tab-switcher
that is
like task switcher in some window managers invoked by Alt+Tab
.
When the tab-bar is displayed, it’s useful to show tab numbers,
to be able to select a tab by its ordinal number by typing e.g.
s-1
to select the first tab, etc.
(setopt
tab-bar-tab-hints t
tab-bar-select-tab-modifiers '(super))
By default, the tab shows the name of the current buffer, but showing all buffer names in the tab name gives more information:
(setopt
tab-bar-tab-name-function 'tab-bar-tab-name-all)
Also show tab groups and the clock at the right edge of the tab-bar:
(setopt
tab-bar-format
'(tab-bar-format-menu-bar
;; tab-bar-format-history
tab-bar-format-tabs-groups
tab-bar-format-align-right
tab-bar-format-global))
;; This expects that ‘tab-bar-format’ contains ‘tab-bar-format-global’:
(display-time-mode +1)
Other useful settings for tab groups:
(setopt
tab-bar-tab-post-change-group-functions
'(tab-bar-move-tab-to-group))
Whereas tab-bar and tab-lines still take screen space, they are sometimes indispensable especially on devices with touch screen such as smartphones where you can touch tabs to select them:
(when (fboundp 'tab-bar-mode) (tab-bar-mode 1))
(when (fboundp 'global-tab-line-mode) (global-tab-line-mode 1))
;; Allow selecting tabs in xterm on Android
(unless window-system (xterm-mouse-mode 1))
To make tab switching as quick as possible, this configuration uses
the key `
located near the TAB
key, so switching frames
with the help of a window manager is performed by Alt+Tab
,
and switching tabs with window configurations is by Alt+`
.
So you don’t need to rely on mouse that is too slow UI device.
And this tab-switcher
can be used even without the tab-bar.
After displaying a list of tabs, a previous tab can by selected
by one key `
, the second tab by two keys `
, etc. Moving up
is by Shift-`
, and selecting a previous tab is by Alt+`
:
(when (featurep 'tab-bar)
(define-key global-map [(meta ?`)] 'tab-switcher)
(define-key global-map [(super ?`)] 'tab-switcher)
(define-key global-map [(meta ?\xa7)] 'tab-switcher)
;; (define-key global-map [(meta ?\x8a7)] 'tab-switcher)
(with-eval-after-load 'tab-bar
(define-key tab-switcher-mode-map [(meta ?`)] 'tab-switcher-select)
(define-key tab-switcher-mode-map [(super ?`)] 'tab-switcher-select)
(define-key tab-switcher-mode-map [(meta ?\xa7)] 'tab-switcher-select)
;; (define-key tab-switcher-mode-map [(meta ?\x8a7)] 'tab-switcher-select)
(define-key tab-switcher-mode-map [( ?`)] 'tab-switcher-next-line)
(define-key tab-switcher-mode-map [( ?\xa7)] 'tab-switcher-next-line)
;; (define-key tab-switcher-mode-map [(?\x8a7)] 'tab-switcher-next-line)
(define-key tab-switcher-mode-map [( ?~)] 'tab-switcher-prev-line)
(define-key tab-switcher-mode-map [( ?\xbd)] 'tab-switcher-prev-line)
;; (define-key tab-switcher-mode-map [(?\x8bd)] 'tab-switcher-prev-line)
))
tab-bar-history-mode
is like winner-mode
but replaces it
with the same keybindings C-c left
and C-c right
:
(when (fboundp 'tab-bar-history-mode) (tab-bar-history-mode 1))
Too dangerous key C-x t 1
:
(unbind-key "1" tab-prefix-map)
(add-hook 'tab-bar-tab-post-select-functions
(lambda (_from-tab to-tab)
;; Pulse if not visited the tab for more than 10 minutes
(when (> (- (float-time) (alist-get 'time to-tab)) 600)
(pulse-momentary-highlight-one-line))))
In tab-line-mode
use C-x left
to go to the left tab
whereas keep C-x C-left
to go to the most recently used tab:
;; https://lists.gnu.org/archive/html/emacs-devel/2024-07/msg00235.html
(with-eval-after-load 'tab-line
;; Fall back to global 'previous-buffer':
(keymap-unset tab-line-mode-map "C-x C-<left>")
;; Fall back to global 'next-buffer':
(keymap-unset tab-line-mode-map "C-x C-<right>"))
To use maximum screen space, my Emacs frame covers the entire screen
and has no menus, no toolbars, no scrollbars, no title and no borders.
Such customization on 1024x768 display mode and 6x10 font produces
Emacs text screen resolution 168 columns x 75 lines.
split-window-horizontally
gives two windows with 83 columns x 75 lines.
And follow-mode
displays one buffer with 83 columns x 150 lines.
On 1366x768 this gives 225 columns x 75 lines, this means either
2 horizontally split windows each 112 columns wide, or
3 horizontally split windows each 75 columns wide.
On 2560x1400 this gives 255 columns x 70 lines.
(cond
((eq window-system 'x)
;; (create-fontset-from-ascii-font "-rfx-fixed-medium-r-normal--10-*-*-*-c-60-koi8-*")
;; (create-fontset-from-ascii-font "-misc-fixed-medium-r-*--10-*-*-*-*-*-*-*")
(setq default-frame-alist
(append
'(
;; Better to set font in ~/.Xresources, e.g. "emacs.font: DejaVu Sans Mono-12"
;; A lot of different fonts were tried to pick the best one:
;;(font . "-*-*-medium-r-normal--10-*-*-*-c-60-fontset-koi8_r_10")
;;? (font . "-rfx-fixed-medium-r-normal--10-*-*-*-c-60-koi8-*")
;;? (font . "-rfx-fixed-medium-r-normal--10-*-*-*-c-60-*-*")
;; (font . "-misc-fixed-medium-r-normal--10-100-75-75-c-60-iso10646-1")
;; (font . "-*-*-medium-r-*--10-*-*-*-*-*-fontset-iso8859_1_10")
;; (font . "-misc-fixed-medium-r-normal--10-*-*-*-c-60-iso8859-1")
;; Unlike iso8859-1, iso10646-* correctly combines accented chars:
;; (font . "-misc-fixed-medium-r-normal--15-*-*-*-c-60-iso10646-*")
(cursor-type . bar)
;; To win a lot of screen pixels:
(vertical-scroll-bars . nil)
(horizontal-scroll-bars . nil)
(scroll-bar-width . 0)
(internal-border-width . 0)
(menu-bar-lines . 0)
(tool-bar-lines . 0)
(line-spacing . 0))
default-frame-alist))))
To make the Emacs frame truly maximized, we need additionally make it undecorated
that removes any remaining window decorations including the title bar:
(add-hook 'after-make-frame-functions 'toggle-frame-maximized)
(add-hook 'after-make-frame-functions
(lambda (frame)
(modify-frame-parameters frame '((undecorated . t)))
;; Some OS resources change background to grey, revert it back to white:
(modify-frame-parameters frame '((background-color . "white")))
;; For some OS window managers that don't put focus to new frames:
(select-frame-set-input-focus frame)))
;; Apply ‘undecorated’ to new frames created by these commands:
(define-advice make-frame-on-monitor (:around (ofun monitor &optional display parameters) undecorated)
(funcall ofun monitor display (append '((undecorated . t)) parameters)))
(define-advice make-frame-on-current-monitor (:around (ofun &optional parameters) undecorated)
(funcall ofun (append '((undecorated . t)) parameters)))
;; Undecorate the initial frame as well
(modify-frame-parameters nil '((undecorated . t)))
Note that in Emacs 29 instead of above you can use:
(add-hook 'window-size-change-functions 'frame-hide-title-bar-when-maximized)
In earlier versions there was no way to unframe and maximize Emacs window from Emacs,
so it was necessary to use such code in ~/.sawfish/rc
:
(require 'sawfish.wm.state.maximize)
(define (my-customize-emacs-window w)
(when (string-match "emacs" (nth 2 (get-x-property w 'WM_CLASS)))
(window-put w 'type 'unframed)
(maximize-window w)))
(add-hook 'before-add-window-hook my-customize-emacs-window t)
There are different ways to maximize initial frame after loading the init file:
emacs -mm
that sets (setq initial-frame-alist '((fullscreen . maximized)))
or (add-to-list 'default-frame-alist '(fullscreen . maximized))
or (toggle-frame-maximized)
or (set-frame-size (selected-frame) 210 80)
(that works only in KDE).
Below is the only way that works reliably on GNU/Linux:
(add-hook 'after-init-hook
(lambda ()
(run-at-time
"1 second" nil
'shell-command-to-string ; to not overwrite the echo area
"wmctrl -r :ACTIVE: -b add,maximized_vert,maximized_horz")
;; Fix a recent bug that breaks frame dimensions after desktop frame restore:
;; I get a maximized frame visually, but internally with unmaximized dimensions,
;; i.e. mouse avoidance moves the mouse pointer to the middle of the frame
;; instead to the edges, etc.
;; (toggle-frame-maximized)
;; (toggle-frame-maximized)
)
t)
Splitting the windows horizontally to 3 or 4 columns, and enabling follow-mode
allows fitting as much as possible of the buffer contents on the screen:
(defun split-window-horizontally-3 ()
(interactive)
(delete-other-windows)
(split-window-horizontally)
(split-window-horizontally)
(balance-windows)
(other-window -1))
(defun follow-mode-3 ()
(interactive)
(split-window-horizontally-3)
(follow-mode 1))
(add-hook 'my-map-defined-hook
(lambda ()
(define-key my-map "3" 'split-window-horizontally-3)
(define-key my-map "f3" 'follow-mode-3)))
(defun split-window-horizontally-4 ()
(interactive)
(delete-other-windows)
(split-window-horizontally)
(split-window-horizontally)
(other-window 2)
(split-window-horizontally)
(other-window 2))
(defun follow-mode-4 ()
(interactive)
(split-window-horizontally-4)
(follow-mode 1))
(add-hook 'my-map-defined-hook
(lambda ()
(define-key my-map "4" 'split-window-horizontally-4)
(define-key my-map "f4" 'follow-mode-4)))
To get a better overview of the functions implemented in a prog-mode buffer,
or the document sections available in a text-mode buffer, it’s easy to enable
outline-minor-mode-cycle
and use TAB
keys with modifiers to cycle visibility.
Also this allows hiding uninteresting parts of the buffer, while showing the
currently relevant parts of the buffer.
Typing TAB
on a heading line cycles visibility of the current outline.
Typing S-TAB
on a heading line cycles visibility globally of the whole buffer.
This works in all modes including derived from prog-mode and text-mode.
Some modes might require setting a suitable value of outline-regexp
.
In some modes that use not too much highlighting, and where outline faces
don’t conflict with major-mode’s faces, it’s possible also to enable
outline-minor-mode-highlight
to highlight outline headings.
An example of the mode where heading highlighting could be enabled is
dictionary-mode
customized at the bottom of this file.
(setq-default
outline-minor-mode-cycle t
outline-minor-mode-highlight t)
;; It might be useful to use short keys only on headings.
;; This works only when ‘outline-minor-mode-cycle-filter’
;; is not enabled partially, so outline navigation could be used
;; when navigation keys move point to ‘bolp’.
;; (map-keymap (lambda (key binding)
;; (outline-minor-mode-cycle--bind
;; outline-minor-mode-cycle-map
;; (vector key) binding))
;; outline-mode-prefix-map)
(let ((cmds '(
;; Like in ‘outline-mode-map’:
("C-c C-u" outline-up-heading)
("C-c C-n" outline-next-visible-heading)
("C-c C-p" outline-previous-visible-heading)
("C-c <down>" outline-next-visible-heading)
("C-c <up>" outline-previous-visible-heading)
;; ("M-<down>" outline-next-visible-heading)
;; ("M-<up>" outline-previous-visible-heading)
;; ("M-<left>" outline-hide-subtree)
;; ("M-<right>" outline-show-subtree)
)))
(dolist (command cmds)
(outline-minor-mode-cycle--bind
outline-minor-mode-cycle-map
(kbd (nth 0 command)) (nth 1 command)
(lambda (cmd)
(when (and (outline-on-heading-p t) (bolp)
;; Exclude emacs-lisp-mode:
;; outline-minor-mode-highlight
;; BETTER:
;; (buffer-file-name (current-buffer))
;; buffer-read-only
)
cmd)))))
(keymap-set outline-navigation-repeat-map "<down>" #'outline-next-visible-heading)
(keymap-set outline-navigation-repeat-map "<up>" #'outline-previous-visible-heading)
(keymap-set outline-overlay-button-map "+" #'outline-show-subtree)
(keymap-set outline-overlay-button-map "-" #'outline-hide-subtree)
(keymap-set outline-overlay-button-map "*" #'outline-show-subtree)
(keymap-set outline-overlay-button-map "\\" #'outline-hide-leaves)
(keymap-set outline-overlay-button-map "/ s" #'outline-show-by-heading-regexp)
(keymap-set outline-overlay-button-map "/ h" #'outline-hide-by-heading-regexp)
(defun my-outline-minor-mode ()
;; Enable in modes with reasonable ‘outline-regexp’:
(when (and (seq-some #'local-variable-p '(outline-search-function outline-regexp))
(not (derived-mode-p '(org-mode markdown-mode bash-ts-mode))))
;; Don't override major mode font-lock
(setq-local outline-minor-mode-highlight nil)
(outline-minor-mode +1)))
(add-hook 'find-file-hook 'my-outline-minor-mode)
(add-hook 'apropos-mode-hook 'my-outline-minor-mode)
(add-hook 'shortdoc-mode-hook 'my-outline-minor-mode)
Also enable cycling on filename headings in xref buffers:
(add-hook 'xref-after-update-hook
(lambda ()
(setq-local outline-minor-mode-highlight nil
outline-default-state 1
outline-default-rules '((match-regexp . "ChangeLog\\|test/manual/etags")))
(outline-minor-mode +1)))
Also use Org-like C-c C-o
to open external links:
(with-eval-after-load 'goto-addr
(define-key goto-address-highlight-keymap
(kbd "C-c C-o") #'goto-address-at-point))
Time optimization mostly means less clanking on keyboard to save time for more productive activities.
The fastest way to navigate between windows is by using directional keys
set by windmove
, so typing an arrow key will indicate the direction
where you want to move, e.g. s-right
switches to the right window, etc.
With this configuration you can also type s-M-right
to display the next
buffer in the right window, C-x s-right
to delete the window on the right,
and S-s-right
to swap the current buffer with the buffer in the right window.
Holding the S-s-
keys and typing arrow keys will move windows up/down, left/right
like tiles in the 15-puzzle:
(setq-default
windmove-default-keybindings '(nil super)
windmove-display-default-keybindings '(nil super meta)
windmove-delete-default-keybindings `(,(kbd "C-x") super)
windmove-swap-states-default-keybindings '(nil shift super))
;; (require 'windmove)
(windmove-mode)
Another important setting windmove-create-window
is to automatically
create a new window when trying to move to another window. This means that
the same key s-right
that switches to the right window, also creates
a new window on the right, when there is no window yet in that direction.
Also when trying to display a buffer in another window, and there is no
window yet in that direction, it creates a new window. Another useful
option is windmove-wrap-around
, so typing s-right
in the rightmost
window moves to the leftmost window. When these both options are
enabled at the same time, they maintain a configuration with two windows
horizontally, and two windows vertically, since I don’t need more than two
windows in a row:
(setq-default
windmove-create-window t
windmove-wrap-around t)
It’s better when most buffers pop up in the same window, so there is no need to switch windows back and forth:
(add-to-list 'display-buffer-alist
`(,(rx bos "*"
(or "Help" "Apropos" "Colors" "Buffer List"
"Command History" "Dictionary" "Locate"
"Messages" "Proced" "eww" "snd"
(and "gud-" (+ (any "a-z0-9")))
"grep" "erlang" "haskell"
;; Handle both "*shell*" and e.g. "*emacs-shell*"
;; generated by ‘project-shell’:
(and (? (* nonl) "-") (or "shell" "compilation"))
"Shell Command Output"
(and "SQL: " (+ (any "A-za-z")))
"Diff" "vc-dir" "vc-log" "vc-search-log")
"*"
;; Uniquifed buffer name with optional suffix in angle brackets
(? (and "<" (+ (not (any ">"))) ">"))
eos)
display-buffer-same-window
(inhibit-same-window . nil)
;; Inhibit resizing Help buffers (bug#51062)
(window-height . nil)))
Clicking a link from the *Help*
buffer opens source code in the same window:
(defun display-buffer-from-help-p (_buffer-name _action)
(unless current-prefix-arg
(with-current-buffer (window-buffer)
(derived-mode-p '(help-mode)))))
(add-to-list 'display-buffer-alist
'(display-buffer-from-help-p
display-buffer-same-window
(inhibit-same-window . nil)
;; Inhibit resizing Help buffers when navigating in them (bug#51062)
(window-height . nil)))
Note that in Emacs 29 instead of above you can customize help-window-keep-selected
.
Visiting new files using next-error
commands should be in the same window:
(defun display-buffer-from-next-error-p (_buffer-name _action)
(unless current-prefix-arg
(memq this-command '(next-error previous-error))))
(add-to-list 'display-buffer-alist
'(display-buffer-from-next-error-p
display-buffer-same-window
(inhibit-same-window . nil)))
Visit grep/xref hits in the same window where all previous hits were visited:
(defvar-local display-buffer-last-window nil)
(setq display-buffer-base-action
'(nil . ((some-window
. (lambda (_buffer alist)
(let ((last-window (buffer-local-value
'display-buffer-last-window
(window-buffer))))
;; (message "! last-window=%S" last-window)
(or (and (eq this-command (car last-window))
(window-live-p (cdr last-window))
(cdr last-window))
(get-mru-window nil nil t))))))))
(define-advice display-buffer-record-window (:after (type window buffer) set-last-window)
(with-current-buffer (window-buffer)
;; TODO: maybe later turn cons into alist ((COMMAND . WINDOW))
(setq-local display-buffer-last-window (cons this-command window))))
Below is an old implementation of the above:
(defun display-buffer-from-grep-p (_buffer-name _action)
(with-current-buffer (window-buffer)
(and (memq this-command '(compile-goto-error xref-goto-xref my-xref-goto-xref))
(derived-mode-p '(compilation-mode xref--xref-buffer-mode)))))
(defvar-local display-buffer-previous-window nil)
(add-to-list 'display-buffer-alist
'(display-buffer-from-grep-p
display-buffer-in-previous-window
(previous-window . display-buffer-previous-window)
(inhibit-same-window . nil))
;; Append to not override display-buffer-same-window
'append)
(define-advice xref-goto-xref (:around (ofun &rest args) previous-window)
(let ((buffer (current-buffer)))
(apply ofun args)
(with-current-buffer buffer
(setq-local display-buffer-previous-window (selected-window)))))
(define-advice compile-goto-error (:around (ofun &rest args) previous-window)
(let ((buffer (current-buffer)))
(apply ofun args)
(with-current-buffer buffer
(setq-local display-buffer-previous-window (selected-window)))))
(define-advice window--display-buffer (:around (ofun &rest args) previous-window)
(let ((buffer (current-buffer))
(window (apply ofun args)))
(with-current-buffer buffer
(setq-local display-buffer-previous-window window))
window))
Debugging should use the same window:
(define-advice edebug-pop-to-buffer (:around (ofun buffer &optional window) same-window)
(when (string-prefix-p "edebug" (format "%s" real-this-command))
(window--display-buffer buffer (old-selected-window) 'reuse
'(nil (inhibit-same-window . nil))))
(funcall ofun buffer window))
Use single letters y
or n
for answers instead of complete words yes
or no
.
A longer word was intended for cases where giving the wrong answer would
have serious consequences, but in reality with short answers you decide
how long a pause you need in order to realize what the question is about.
;; (fset 'yes-or-no-p 'y-or-n-p)
;; New option in Emacs 28 instead of previous line:
(setq-default use-short-answers t)
Enable all disabled commands such as narrow-to-region
, etc.
(setq disabled-command-function nil)
One of the biggest productivity boosts is making the ESC
key
to get out of some modal states like it does in other programs
and what is the main purpose of this key according to its name
ESCAPE
.
By default, in Emacs ESC
is a useless duplicate of the Meta
key
that doesn’t work on consoles. But it makes no sense on window systems
and text terminals where the Meta
key works fine, so on a window system
there is no need to use ESC
as a prefix key. Use a single [escape]
key
instead of knocking it 3 times:
(when window-system
(define-key global-map [escape] 'keyboard-escape-quit)
(define-key isearch-mode-map [escape] 'isearch-cancel)
;; (define-key completion-list-mode-map [escape] 'delete-completion-window))
(define-key completion-list-mode-map [escape] 'switch-to-minibuffer))
Make the prefix key C-z
for my personal keymap.
On qwerty-keyboards C-z
is one of the most accessible keys
like C-x
and C-c
, but the prefix key C-c
is reserved
for mode-specific commands (both user-defined and standard Emacs extensions).
The standard binding of C-z
(suspend-emacs
or iconify-or-deiconify-frame
)
is reassigned here to double key sequence C-z C-z
.
(defvar my-map
(let ((map (make-sparse-keymap))
(c-z (global-key-binding "\C-z")))
(global-unset-key "\C-z")
(define-key global-map "\C-z" map)
(define-key map "\C-z" c-z)
map))
(run-hooks 'my-map-defined-hook)
My map can be used from isearch:
;; (define-key isearch-mode-map "\C-z" my-map)
;; (define-key isearch-mode-map "\C-z" 'isearch-other-control-char)
Various useful commands are bound on this keymap:
(when window-system
;; Set ESC-modifier to C-z escape
;; This is useful to invoke ‘M-TAB’ or ‘M-|’ on keyboards with AltGr key,
;; as ‘C-z ESC TAB’ or ‘C-z ESC |’
(define-key my-map [escape] esc-map)
(define-key my-map "t" 'toggle-truncate-lines)
(define-key my-map "v" nil)
(define-key my-map "vs" 'set-variable)
(define-key my-map "vc" 'customize-variable)
(define-key my-map "vtw2" (lambda () (interactive) (setq-local tab-width 2) (force-mode-line-update)))
(define-key my-map "r" 'revert-buffer)
(define-key my-map "\C-q" 'quoted-insert) ; because global C-q is rebound above
;; ‘C-z -’ and ‘C-z C--’ inserts a vertical line.
(define-key my-map [(control ?-)] (lambda () (interactive) (insert "\f\n"))) ; because global C-q C-l is rebound above
(define-key my-map "-" (lambda () (interactive) (insert "\f\n"))) ; because global C-q C-l is rebound above
;; TEST: try ‘C-z C-x C-x C-x C-x ...’, try ‘C-x z C-z C-z C-z’ (repeat.el)
)
One of the most useful commands is insert-pair
. When it’s bound to such
keys as M-“, M-‘, M-[, M-{, typing these keys always maintains the
syntactically valid structures of paired and balanced constructs. So e.g.
typing M-(
inserts balanced parentheses, M-"
inserts a closed string, etc.
Point is positioned inside the inserted pair.
Here’s a short table of mappings:
Key | Insert pair |
---|---|
M-( | () |
M-[ | [] |
M-{ | {} |
M-” | ”” or “” |
M-’ | ” or ‘’ |
(defun use-fancy-quotes-p ()
(and (memq (coding-system-base buffer-file-coding-system) '(utf-8 utf-8-emacs))
(or ;; (and comment-start (nth 4 (syntax-ppss)))
(and (derived-mode-p '(text-mode))
(not (and (derived-mode-p '(org-mode))
(consp (get-text-property (point) 'face))
(memq 'org-block (get-text-property (point) 'face))))
(not (derived-mode-p '(vc-git-log-edit-mode)))
(not (derived-mode-p '(sgml-mode)))
(not (derived-mode-p '(yaml-mode)))
)
;; (derived-mode-p '(fundamental-mode))
)))
;; Modify esc-map when not on a tty:
(when window-system
;; Insert paired characters (either ''/"" or ‘’/“” depending on mode)
(define-key esc-map "\""
(lambda ()
(interactive)
(let ((insert-pair-alist
(cons
(if (use-fancy-quotes-p)
(if (and (not (eobp)) (eq (aref char-script-table (char-after)) 'cyrillic))
'(?\" ?\« ?\»)
'(?\" ?\“ ?\”))
'(?\" ?\" ?\"))
insert-pair-alist)))
(call-interactively 'insert-pair))))
;; (define-key esc-map "`" 'insert-pair)
;; (define-key global-map "\M-`" 'insert-pair)
(define-key esc-map "'"
(lambda ()
(interactive)
(let ((insert-pair-alist
(cons
(if (use-fancy-quotes-p)
'(?\' ?\‘ ?\’)
'(?\' ?\' ?\'))
insert-pair-alist)))
(call-interactively 'insert-pair))))
;; Optionally, make ' insert backquote `'.
;; (add-to-list 'insert-pair-alist '(?\' ?\` ?\'))
(define-key esc-map "[" 'insert-pair)
(define-key esc-map "{" 'insert-pair)
(define-key esc-map ")" 'up-list))
(define-key my-map "`" 'insert-pair)
(define-key my-map "<" 'insert-pair)
;; (defun insert-pair-without-space ()
;; (interactive)
;; (let ((parens-require-spaces nil))
;; (call-interactively 'insert-pair)))
;; (defun insert-pair-with-space ()
;; (interactive)
;; (let ((parens-require-spaces t))
;; (call-interactively 'insert-pair)))
;; (define-key esc-map "[" 'insert-pair-without-space)
;; (define-key esc-map "(" 'insert-pair-with-space)
And two keybindings to accompany the above:
(define-key ctl-x-map "\M-(" 'delete-pair) ;; the reverse of ‘M-(’
(define-key ctl-x-map "\C-\M-u" 'raise-sexp) ;; like ‘C-M-u’
This is why there is no need to use such extra packages as paredit
.
Here are some examples of what you can do with the aforementioned keys:
;; In these examples ‘-!-’ denotes the point location,
;; and optional ‘-¡-’ denotes the other end of the selected region.
;; When you type ‘M-(’
;; Before: (foo (a b c) -!-d e f-¡-)
;; After: (foo (a b c) (-!-d e f))
;; When you type ‘C-x M-(’
;; Before: (foo (a b c) -!-(d e f))
;; After: (foo (a b c) -!-d e f)
;; When you type ‘C-x M-C-u’
;; Before: (foo (a b c) (-!-d e f))
;; After: (foo (a b c) -!-d)
;; When you type ‘C-x M-C-u’ once, then twice.
;; Before: (foo (a b c) (d -!-e f-¡-))
;; First: (foo (a b c) -!-e f-¡-)
;; Second: -!-e f
This is my most frequently used DWIM command bound to C-RET
in Lisp modes.
Since I don’t use electric-indent-mode
, this is its less-obtrusive replacement
that does all the necessary things depending on context: indents the
current line, inserts a newline, and indents the next expression.
(defun my-reindent-then-newline-and-indent-and-indent-sexp ()
"Reindent current line, insert newline, then indent the new line.
Move backward out of one level of parentheses.
Indent each line of the list starting just after point."
(interactive "*")
(reindent-then-newline-and-indent)
(save-excursion
(condition-case nil (backward-up-list) (error nil))
(indent-sexp)))
(define-key emacs-lisp-mode-map [(control return)]
'my-reindent-then-newline-and-indent-and-indent-sexp)
(define-key lisp-interaction-mode-map [(control return)]
'my-reindent-then-newline-and-indent-and-indent-sexp)
(define-key lisp-mode-map [(control return)]
'my-reindent-then-newline-and-indent-and-indent-sexp)
(with-eval-after-load 'scheme
(define-key scheme-mode-map [(control return)]
'my-reindent-then-newline-and-indent-and-indent-sexp))
This is another frequently used DWIM command bound to C-backspace
.
It’s almost the reverse of C-RET
defined above: joins two lines
and indents the joined code. IOW, both commands keep the indentation
always consistent.
(defun my-join-line-and-indent-sexp ()
"Join this line to previous and fix up whitespace at join.
Move backward out of one level of parentheses.
Indent each line of the list starting just after point."
(interactive "*")
(join-line)
(save-excursion
(condition-case nil (backward-up-list) (error nil))
(let ((indent-sexp-function (key-binding "\e\C-q")))
(if indent-sexp-function (call-interactively indent-sexp-function)))))
(defun my-join-line-and-indent-sexp-or-backward-kill-word ()
"If point is on the whitespaces at the beginning of a line,
then join this line to previous and indent each line of the upper list.
Otherwise, kill characters backward until encountering the end of a word."
(interactive)
(if (save-excursion (and (skip-chars-backward " \t") (bolp)))
(my-join-line-and-indent-sexp)
(backward-kill-word 1)))
;; Bind globally, not only in Lisp modes:
(global-set-key [C-backspace] 'my-join-line-and-indent-sexp-or-backward-kill-word)
;; (define-key lisp-mode-map [(control backspace)]
;; 'my-join-line-and-indent-sexp-or-backward-kill-word)
;; (define-key emacs-lisp-mode-map [(control backspace)]
;; 'my-join-line-and-indent-sexp-or-backward-kill-word)
;; (with-eval-after-load 'scheme
;; (define-key scheme-mode-map [(control backspace)]
;; 'my-join-line-and-indent-sexp-or-backward-kill-word))
A smart version if completion is bound to TAB
in Lisp modes:
(defun my-lisp-indent-or-complete (&optional arg)
"Complete Lisp symbol, or indent line or region.
If the character preceding point is symbol-constituent, then perform
completion on Lisp symbol preceding point using ‘lisp-complete-symbol’.
Otherwise, call ‘indent-for-tab-command’ that indents line or region."
(interactive "P")
(if (and (not (and transient-mark-mode mark-active
(not (eq (region-beginning) (region-end)))))
(memq (char-syntax (preceding-char)) (list ?w ?_))
(not (bobp)))
(completion-at-point)
(indent-for-tab-command arg)))
(define-key emacs-lisp-mode-map (kbd "TAB") 'my-lisp-indent-or-complete)
A smarter jumping to the beginning of the line:
(defun my-beginning-of-line-or-indentation (arg)
"Jump to the beginning of the line or to the indentation (like ‘M-m’)."
(interactive "^p")
(if (bolp)
(beginning-of-line-text arg) ; (back-to-indentation) ?
(if (fboundp 'move-beginning-of-line)
(move-beginning-of-line arg)
(beginning-of-line arg))))
;; (put 'my-beginning-of-line-or-indentation 'isearch-move t)
(define-key global-map [(control ?a)] 'my-beginning-of-line-or-indentation)
This is a more general version that also handles numbered lists:
(defun my-reindent-then-newline-and-indent ()
"Create the next number item in the numbered list, or reindent."
(interactive)
(let ((num 1))
(if (save-excursion
(backward-paragraph)
(forward-line)
(not (and (looking-at "^\\s-*\\([0-9]\\)\\.")
(setq num (match-string 1)))))
(reindent-then-newline-and-indent)
(insert (format "\n\n%s. " (1+ (string-to-number num)))))))
(define-key global-map [(control return)] 'reindent-then-newline-and-indent)
(define-key global-map [(control shift return)] 'my-reindent-then-newline-and-indent)
(define-key global-map [S-return] 'electric-newline-and-maybe-indent)
The most efficient way of navigation in Emacs is like those used in browsers Lynx and Mozilla. Its basic features are the following:
M-right
visits a link under point. In Help and Info buffer it’s a real link, and if there is no link under point, then move in history of visited pages forwards. In Dired when point is on a directory line, thenM-right
opens a new Dired buffer, otherwise visits a file under point. In other modesM-right
tries to use a thing under point: opens a help buffer for a variable or function under point, or visits a link found under point, etc.M-left
goes back: in file buffers it opens the Dired buffer with file directory, and puts point on its file line. WhenM-left
is typed in a Dired buffer, then it goes up and opens another Dired buffer with the parent directory. In Help and Info buffers,M-left
navigates the history of visited nodes backwards.M-down
goes to the next thing in the current buffer. In Help/Info/Man buffers it moves point to the next link. If there are no links visible on the current screen, then it scrolls one page forward, like it does in Lynx.M-up
is the inverse ofM-down
, it either moves point to a previous link, or scrolls one page backwards.
Then a key sequence M-right M-left M-down ...
(i.e. just press and hold
the Meta
key while using the arrow keys) can be used to quickly inspect
files one by one in a Dired buffer, or from a menu of links to Info nodes, etc.
(defun my-go-back ()
"Go back from current buffer and jump to Dired."
(interactive)
(let* ((prev-buffer (car (nth 0 (window-prev-buffers))))
(prev-dired (when (buffer-live-p prev-buffer)
(with-current-buffer prev-buffer
(eq major-mode 'dired-mode))))
(jump-dired (or prev-dired (derived-mode-p '(vc-dir-mode)))))
;; Keep the buffer displayed on the frame or in a tab
(if (or (> (length (get-buffer-window-list (current-buffer) t t)) 1)
(tab-bar-get-buffer-tab (current-buffer) t t))
(if jump-dired (dired-jump) (quit-window))
;; Go to the top to not store emacs-places.
;; (goto-char (point-min))
(if jump-dired
(kill-current-buffer-and-dired-jump)
(quit-window-kill-buffer)))))
(defun my-find-thing-at-point (&optional arg)
"Find variable, function or file at point."
(interactive "P")
(cond ((not (eq (variable-at-point) 0))
(call-interactively 'describe-variable))
((function-called-at-point)
(call-interactively 'describe-function))
((thing-at-point 'url)
(browse-url (thing-at-point 'url) arg))
(t (find-file-at-point))))
(define-key global-map [(meta left)] 'my-go-back)
(define-key global-map [(meta right)] 'my-find-thing-at-point)
(defun my-next-link-or-scroll-page-forward (next-point)
"Scroll one screen forward when no more next links are visible on the screen.
The argument ‘next-point’ is the point's position of the next link."
(if (and (> (window-end) next-point) (> next-point (point)))
(goto-char next-point)
(if (>= (window-end) (point-max))
(goto-char (point-max))
(progn (View-scroll-page-forward-set-page-size) (move-to-window-line 0)))))
(defun my-prev-link-or-scroll-page-backward (prev-point)
"Scroll one screen backward when no more previous links are visible on the screen.
The argument ‘prev-point’ is the point's position of the previous link."
(if (and (< (window-start) prev-point) (< prev-point (point)))
(goto-char prev-point)
(if (<= (window-start) (point-min))
(goto-char (point-min))
(progn (View-scroll-page-backward-set-page-size)))))
Below is customization of different modes to support convenient navigation:
(defun my-help-follow ()
"Either follow the link, or go forward in history."
(interactive)
(if (button-at (point))
(push-button)
(help-go-forward)))
;; Please note that ‘help-next-ref’ is better than ‘Info-next-reference’
;; because it uses ‘message’ instead of ‘error’ if “No cross references”.
(with-eval-after-load 'help-mode
;; Mozilla-like navigation:
;; (define-key help-mode-map [(meta left)] 'help-go-back)
;; (define-key help-mode-map [(meta right)] 'my-help-follow)
;; Lynx-like navigation:
(define-key help-mode-map [(meta up)]
(lambda () (interactive)
(my-prev-link-or-scroll-page-backward
(save-excursion
(ignore-errors (backward-button 1))
(point)))))
(define-key help-mode-map [(meta down)]
(lambda () (interactive)
(my-next-link-or-scroll-page-forward
(save-excursion
(ignore-errors (forward-button 1))
(point))))))
(defun my-Info-forward (&optional fork)
"Follow the nearest node, or to go history forward, if point is not on ref."
(interactive "P")
(condition-case error
(Info-follow-nearest-node fork)
(error
(if (equal "Point neither on reference nor in menu item description"
(cadr error))
(Info-history-forward)
(message "%s" (cadr error))))))
;; Info with look-and-feel of Midnight Commander, Lynx (Links) and Mozilla.
(with-eval-after-load 'info
(define-key Info-mode-map [(control shift insert)]
(lambda () (interactive) (Info-copy-current-node-name 0)))
;; Mozilla-like navigation:
(define-key Info-mode-map [(meta right)] 'my-Info-forward)
(define-key Info-mode-map [(meta left)] 'Info-last)
;; Lynx-like navigation:
(define-key Info-mode-map [(meta up)]
(lambda ()
(interactive)
(my-prev-link-or-scroll-page-backward
(save-excursion
(ignore-errors
(Info-prev-reference))
(point)))))
(define-key Info-mode-map [(meta down)]
(lambda ()
(interactive)
(my-next-link-or-scroll-page-forward
(save-excursion
(ignore-errors
(Info-next-reference))
(point)))))
;; more/less scrolling style
(define-key Info-mode-map [return]
(lambda ()
(interactive)
(if nil ;;TODO: add predicate function to info.el to check (point) for Info refs
(my-Info-forward)
;; (View-scroll-line-forward)
(progn (scroll-up 1) (move-to-window-line -1) (beginning-of-line)))))
;; ThinkPad additional keys, try to use them
(when (equal (upcase (system-name)) "THINKPAD")
(define-key Info-mode-map [osfPrior] 'Info-last)
(define-key Info-mode-map [osfNext] 'Info-follow-nearest-node)))
(with-eval-after-load 'man
;; Don't use ‘man-mode-syntax-table’ that sets word syntax to ‘.’, ‘_’, ‘:’.
(add-hook 'Man-mode-hook
(lambda ()
(set-syntax-table text-mode-syntax-table)))
(add-hook 'Man-cooked-hook 'outline-minor-mode)
;; Mozilla-like navigation:
(define-key Man-mode-map [(meta right)] 'push-button) ;; 'man-follow
;; No need to kill Man buffer because it is not saved to desktop.
(define-key Man-mode-map [(meta left)] 'quit-window)
;; Lynx-like navigation:
(define-key Man-mode-map [(meta up)]
(lambda ()
(interactive)
(my-prev-link-or-scroll-page-backward
(save-excursion
(ignore-errors (Man-previous-section 1))
(point)))))
(define-key Man-mode-map [(meta down)]
(lambda ()
(interactive)
(my-next-link-or-scroll-page-forward
(save-excursion
(ignore-errors (Man-next-section 1))
(point)))))
(define-key Man-mode-map [f2] 'toggle-truncate-lines)
;; (define-key view-mode-map (kbd "TAB") 'other-window) ; used for next-ref
;; more/less scrolling style
(define-key Man-mode-map [return] 'View-scroll-line-forward))
(with-eval-after-load 'view
(define-key view-mode-map " " 'View-scroll-page-forward-set-page-size)
(define-key view-mode-map "g" (lambda () (interactive) (revert-buffer nil t t)))
(define-key view-mode-map "l" 'View-goto-line)
(define-key view-mode-map [f2] 'toggle-truncate-lines)
;; (define-key view-mode-map (kbd "TAB") 'other-window) ; used for next-ref
;; global: (define-key view-mode-map [(meta right)] 'find-file-at-point)
;; Commented out to use the global keybinding:
;; (define-key view-mode-map [(meta left)]
;; (lambda ()
;; (interactive)
;; ;; Go to the top to not store emacs-places.
;; (goto-char (point-min))
;; (View-quit)))
(define-key view-mode-map [(meta down)]
(lambda ()
(interactive)
(if (>= (window-end) (point-max))
(goto-char (point-max))
(View-scroll-page-forward-set-page-size))))
(define-key view-mode-map [(meta up)]
(lambda ()
(interactive)
(if (<= (window-start) (point-min))
(goto-char (point-min))
(View-scroll-page-backward-set-page-size))))
;; qv http://thread.gmane.org/gmane.emacs.devel/111117/focus=112357
(define-advice View-scroll-line-forward (:after (&rest _args) bottomize)
"Fix point position to be at the bottom line."
(move-to-window-line -1)
(beginning-of-line))
;; Remove verbosity from view.el functions (bug#21893):
;; Also no need to set ‘view-inhibit-help-message’.
(when (boundp 'inhibit-message-regexps)
(add-to-list 'inhibit-message-regexps "^End of buffer")))
(with-eval-after-load 'diff-mode
(define-key diff-mode-map [(meta down)] 'diff-hunk-next)
(define-key diff-mode-map [(meta up)] 'diff-hunk-prev)
(define-key diff-mode-map [(control meta down)] 'diff-file-next)
(define-key diff-mode-map [(control meta up)] 'diff-file-prev)
(add-hook 'diff-mode-hook
(lambda ()
;; Some modes use own TAB keys at the beginning of the line,
;; such as e.g. ‘diff-mode’ where TAB goes to the next hunk,
;; so allow cycling when point is not at BOL:
(setq-local outline-minor-mode-cycle-filter '(lambda nil (not (bolp))))
(setq-local outline-default-state 2
;; TODO: maybe also add (match-regexp . "public/packs")
outline-default-rules '(subtree-has-long-lines)
outline-default-long-line 1000)
(outline-minor-mode +1)
;; Disable line truncation because need to
;; see everything while looking at diffs:
(setq-local truncate-lines nil)))
(add-hook 'vc-diff-finish-functions 'outline-apply-default-state))
(require 'dired)
(define-key dired-mode-map [(meta left)]
;; Mozilla-like navigation
(lambda (_arg)
(interactive "P")
(if (not (and (memq ?R (append dired-actual-switches nil))
(dired-between-files)))
(dired-up-directory)
(if (dired-subdir-hidden-p (dired-current-directory))
(dired-tree-up 1)
(progn (dired-hide-subdir 1) (dired-previous-line 1))))))
(define-key dired-mode-map [(meta right)]
;; Mozilla-like navigation
(lambda (_arg)
(interactive "P")
(if (not (and (memq ?R (append dired-actual-switches nil))
(dired-between-files)))
(dired-view-file)
(if (dired-subdir-hidden-p (dired-current-directory))
(progn (dired-hide-subdir 1)
(dired-prev-subdir 1)
(dired-next-line 4))
(dired-view-file)))))
(define-key dired-mode-map [(meta down)] 'dired-next-line)
(define-key dired-mode-map [(control meta down)] 'dired-next-dirline)
(define-key dired-mode-map (kbd "TAB") 'dired-next-dirline) ;; maybe other-window
(define-key dired-mode-map [(meta up)] 'dired-previous-line)
(define-key dired-mode-map [(control meta up)] 'dired-prev-dirline)
(define-key dired-mode-map [(shift iso-lefttab)] 'dired-prev-dirline)
(add-hook 'archive-mode-hook
(lambda ()
(define-key archive-mode-map [f3] 'archive-view)
(define-key archive-mode-map "q" 'quit-window-kill-buffer)
(define-key archive-mode-map [(meta right)] 'archive-view) ;; archive-extract
(define-key archive-mode-map [(meta left)] 'quit-window-kill-buffer)
(define-key archive-mode-map [(meta up)] 'archive-previous-line)
(define-key archive-mode-map [(meta down)] 'archive-next-line)))
(add-hook 'tar-mode-hook
(lambda ()
(define-key tar-mode-map [f3] 'tar-view)
(define-key tar-mode-map "q" 'quit-window-kill-buffer)
(define-key tar-mode-map [(meta right)] 'tar-view)
(define-key tar-mode-map [(meta left)] 'quit-window-kill-buffer)
(define-key tar-mode-map [(meta up)] 'tar-previous-line)
(define-key tar-mode-map [(meta down)] 'tar-next-line)))
(add-hook 'comint-mode-hook ;; 'comint-load-hook
(lambda ()
;; See http://lists.gnu.org/archive/html/emacs-devel/2014-12/msg00299.html
(define-key comint-mode-map [S-return] 'newline)
;; (define-key comint-mode-map "\C-zo" 'comint-kill-output-since-last-prompt)
;; define M-up and M-down instead of C-up and C-down
(define-key comint-mode-map [(meta down)] 'comint-next-prompt)
(define-key comint-mode-map [(meta up)] 'comint-previous-prompt)
(define-key comint-mode-map [C-up] nil)
(define-key comint-mode-map [C-down] nil)
(define-key comint-mode-map "\er" 'comint-history-isearch-backward)))
(with-eval-after-load 'image-mode
(define-key image-mode-map "q" 'quit-window-kill-buffer)
(define-key image-mode-map [(meta left)] 'quit-window-kill-buffer)
;; Browse prev/next images according to their order in Dired
(define-key image-mode-map [(left)] 'image-previous-file)
(define-key image-mode-map [(right)] 'image-next-file)
(define-key image-mode-map [(control left)] 'image-backward-hscroll)
(define-key image-mode-map [(control right)] 'image-forward-hscroll))
(with-eval-after-load 'doc-view
(define-key doc-view-mode-map [(meta left)] 'quit-window-kill-buffer)
;; Get back original keybindings overridden below in ‘image-mode-map’.
;; Left/right arrows are needed in PDF to scroll horizontally
;; PDF images that often are wider than window dimensions,
;; but in image-mode non-PDF images are scaled automatically
;; to fit to the window dimensions.
(define-key doc-view-mode-map [(left)] 'image-backward-hscroll)
(define-key doc-view-mode-map [(right)] 'image-forward-hscroll))
Window commands:
(defun my-move-to-window-top ()
"Position point to the top line of the window."
(interactive)
(move-to-window-line 0))
(define-key global-map [(control prior)] 'my-move-to-window-top)
(define-key global-map [(control kp-prior)] 'my-move-to-window-top)
(defun my-move-to-window-bottom ()
"Position point to the bottom line of the window."
(interactive)
(move-to-window-line -1))
(define-key global-map [(control next)] 'my-move-to-window-bottom)
(define-key global-map [(control kp-next)] 'my-move-to-window-bottom)
(defun my-windows-balance ()
(interactive)
(other-window 1)
(balance-windows)
(shrink-window-if-larger-than-buffer)
(other-window -1))
(define-key my-map "wb" 'my-windows-balance)
Vertical scrolling:
(define-key global-map [(control down)] 'scroll-up-line)
(define-key global-map [(control up)] 'scroll-down-line)
(define-key global-map [(control kp-down)] 'scroll-up-line)
(define-key global-map [(control kp-up)] 'scroll-down-line)
Better navigation:
(define-key global-map [(control kp-home)] 'beginning-of-buffer)
(define-key global-map [(control kp-end)] 'end-of-buffer)
(define-key global-map [(control shift kp-5)] 'goto-line)
(define-key global-map [(control kp-begin)] 'goto-line)
For other-window scrolling with M-<PgUp>
, M-<PgDn>
, M-<Home>
, M-<End>
,
use the most recently used window:
(setq other-window-scroll-default (lambda () (get-mru-window t t t)))
Also use recenter-top-bottom
after M-<Home>
and don’t recenter after M-<End>
:
(define-advice beginning-of-buffer-other-window (:after (&rest _args) recenter)
(with-selected-window (other-window-for-scrolling)
(recenter-top-bottom)))
(define-advice end-of-buffer-other-window (:after (&rest _args) recenter)
(with-selected-window (other-window-for-scrolling)
(recenter -1)))
Use new dwim case commands:
(define-key esc-map "u" 'upcase-dwim)
(define-key esc-map "l" 'downcase-dwim)
(define-key esc-map "c" 'capitalize-dwim)
Alias:
(define-key global-map [(meta kp-divide)] 'hippie-expand)
Functional keys:
(define-key global-map [f1] 'info)
(define-key global-map [(control f1)] 'info-lookup-symbol)
(define-key global-map [f2] 'save-buffer)
;; (define-key global-map [f9] 'call-last-kbd-macro)
(define-key global-map [(meta f7)] 'grep) ; Commander-like
(define-key global-map [(meta shift f7)] 'grep-find)
Like standard Emacs 22 commands (bound to C-x left/right
):
(define-key global-map [f11] 'previous-buffer) ;; my-buffer-prev
(define-key global-map [f12] 'next-buffer) ;; my-buffer-next
Like standard Emacs 22 commands (bound to M-g n/p
):
(define-key global-map [(control f11)] 'previous-error)
(define-key global-map [(control f12)] 'next-error)
(define-key global-map [(control shift f11)] 'compilation-previous-file)
(define-key global-map [(control shift f12)] 'compilation-next-file)
Easier-to-type grep invocations:
(define-key goto-map "re" 'grep)
(define-key goto-map "rr" 'rgrep)
(define-key goto-map "rl" 'lgrep)
(define-key goto-map "rv" 'vc-git-grep)
(define-key goto-map "\M-r\M-e" 'grep)
(define-key goto-map "\M-r\M-r" 'rgrep)
(define-key goto-map "\M-r\M-l" 'lgrep)
(define-key goto-map "\M-r\M-v" 'vc-git-grep)
(setq-default repeat-exit-key "RET")
(setq-default repeat-exit-timeout 0.5)
(repeat-mode +1)
(define-key resize-window-repeat-map [up] 'enlarge-window)
(define-key resize-window-repeat-map [right] 'enlarge-window-horizontally)
(define-key resize-window-repeat-map [left] 'shrink-window-horizontally)
(define-key resize-window-repeat-map [down] 'shrink-window)
(define-key goto-map [M-down] 'next-error)
(define-key goto-map [M-up] 'previous-error)
(define-key next-error-repeat-map [M-down] 'next-error)
(define-key next-error-repeat-map [M-up] 'previous-error)
Override repeat-exit-timeout
for the debugger commands
where the repeat key can be pressed after a long delay.
(dolist (command '( gud-next gud-step gud-stepi gud-cont
gud-refresh gud-finish gud-up gud-down
;; These as well because there is no need to type
;; a self-inserting keys after these commands:
next-error previous-error))
(put command 'repeat-exit-timeout 'no))
(context-menu-mode +1)
(add-hook 'context-menu-functions 'dictionary-context-menu 15)
It converts e.g. https://en.wikipedia.org/wiki/%CE%A9 to more nice-looking https://en.wikipedia.org/wiki/Ω when copying a URL from a web browser to Emacs:
(define-advice gui-selection-value (:around (ofun &rest args) url-decode)
(let ((value (apply ofun args)))
(when (and (stringp value)
(string-match-p
(rx bos "http" (* nonl) "%" (* nonl) eos) value))
(setq value (decode-coding-string (url-unhex-string value) 'utf-8))
;; Encode spaces back again because ffap/thing-at-point fail at spaces
(setq value (replace-regexp-in-string " " "%20" value)))
value))
(defvar kill-ring-save-set-region-p nil)
;; When M-w (kill-ring-save) is called without active region, copy text at point.
(define-advice kill-ring-save (:before (&rest _args) set-region-if-inactive)
(interactive (lambda (spec)
(setq kill-ring-save-set-region-p nil)
(unless (use-region-p)
(let ((bounds (or (bounds-of-thing-at-point 'url)
(bounds-of-thing-at-point 'filename)
(bounds-of-thing-at-point 'symbol)
(bounds-of-thing-at-point 'sexp))))
(unless bounds
(signal 'mark-inactive nil))
(goto-char (car bounds))
(push-mark (cdr bounds) t t)
(setq kill-ring-save-set-region-p t)))
(advice-eval-interactive-spec spec))))
;; Indicate copied region, especially needed when
;; the region was activated by the advice above
(define-advice kill-ring-save (:after (&rest _args) indicate-copied-region)
;; When the region was set by the advice above,
;; only then display its text.
(when kill-ring-save-set-region-p
(let ((text (substring-no-properties (current-kill 0))))
(message "Copied text \"%s\""
(query-replace-descr ; don't show newlines literally
(if (> (length text) 64)
(concat (substring text 0 64) "..." (substring text -16))
text))))))
The following features are new in Emacs 27:
- Show match numbers in the search prompt;
- Use
shift
key to pull text from the buffer to the search string; - Scroll off the screen while Isearch is still active:
(setq-default
isearch-lazy-count t
isearch-allow-scroll 'unlimited
isearch-yank-on-move 'shift
isearch-allow-motion t
isearch-motion-changes-direction nil
isearch-repeat-on-direction-change t)
With non-nil isearch-allow-motion
, up
and down
arrow keys
go to the previous/next matches:
(put 'previous-line 'isearch-motion '(left-char . backward))
(put 'next-line 'isearch-motion '(right-char . forward))
;; (put 'previous-line 'isearch-motion '((lambda () (forward-line 0)) . backward))
;; (put 'next-line 'isearch-motion '(forward-line . forward))
;; (put 'left-char 'isearch-motion '(left-char . backward))
;; (put 'right-char 'isearch-motion '(right-char . forward))
Save and restore window start positions on returning back to previous search hit.
So when the next search hit is off the screen, then use reposition-window
to fit the text unit as much as possible on the screen. When the next search hit
is still on the same screen, don’t scroll the screen to avoid shaking.
On returning to previous search results with the DEL
key, restore exactly
the same screen state that was before.
;; TODO: try to use ‘add-function’
(setq isearch-push-state-function
(lambda ()
;; Recenter new search hits outside of window boundaries
(when (and isearch-success
(not (pos-visible-in-window-p))
;; (not (and (bound-and-true-p isearch-allow-motion)
;; (memq this-command '(scroll-up-command scroll-down-command))))
;; ‘follow-mode’ doesn't need recentering
(not (bound-and-true-p follow-mode)))
;; reposition-window takes too much time in large buffers
(if (or (derived-mode-p '( fundamental-mode dired-mode Man-mode
markdown-mode conf-mode sh-mode bash-ts-mode))
(> (buffer-size) 1000000))
(recenter-top)
(condition-case nil
;; Prevent errors from reposition-window
(reposition-window)
(error nil))))
`(lambda (cmd)
(when isearch-success
(set-window-start nil ,(window-start))))))
(defun isearch-refresh-state ()
"Refresh the last search state.
This might be necessary when e.g. the window was manually recentered with
‘C-l C-l’, so new window-start should be updated in push-state-function above
before searching for the next hit."
;; Pop and discard the previous state
(pop isearch-cmds)
;; Push a new state
(isearch-push-state))
(define-advice isearch-repeat-forward (:before (&rest _args) refresh-state)
(isearch-refresh-state))
(define-advice isearch-repeat-backward (:before (&rest _args) refresh-state)
(isearch-refresh-state))
In Emacs 27, isearch-beginning-of-buffer
is bound to M-s M-<
.
Bind it to the shorter key that doesn’t exit Isearch:
;; Commented out since no need with ‘isearch-allow-motion’:
;; (define-key isearch-mode-map "\M-<" 'isearch-beginning-of-buffer)
;; (define-key isearch-mode-map "\M->" 'isearch-end-of-buffer)
(define-key isearch-mode-map (kbd "TAB") 'isearch-complete)
(define-key minibuffer-local-isearch-map (kbd "TAB") 'isearch-complete-edit)
In Emacs 28, yank-pop
uses the minibuffer to read a yanked strings.
Use this in isearch as well:
(define-key isearch-mode-map "\M-y" 'isearch-yank-pop)
I admit this could be a separate package:
(require 'seq)
(defcustom isearch-lazy-hints nil
"Show numeric hints on isearch lazy-highlighted matches."
:type 'boolean
:group 'lazy-highlight)
(defface isearch-lazy-hint
'((t :inherit lazy-highlight))
"Face for lazy highlighting of counter hints."
:group 'lazy-highlight
:group 'basic-faces)
(defvar isearch-lazy-hints-overlays nil)
(defun isearch-lazy-hints-cleanup ()
(while isearch-lazy-hints-overlays
(delete-overlay (pop isearch-lazy-hints-overlays))))
(defun isearch-lazy-hint (pos count)
(let* ((ov (make-overlay pos pos)
;; (if (or (and isearch-forward (> count 0))
;; (and (not isearch-forward) (< count 0)))
;; (make-overlay (1- pos) pos)
;; (make-overlay pos (1+ pos)))
)
(hint (number-to-string count)))
(set-text-properties 0 (length hint)
'(face isearch-lazy-hint
display ((height 0.7) (raise 0.3)))
hint)
(overlay-put ov 'after-string hint)
;; (overlay-put ov 'display hint)
(overlay-put ov 'priority 1000)
(overlay-put ov 'window (selected-window))
(push ov isearch-lazy-hints-overlays)))
(defun isearch-lazy-hints ()
(when isearch-lazy-hints
(isearch-lazy-hints-cleanup)
(let* ((wgs (window-group-start))
(wge (window-group-end))
(p (or isearch-other-end (point)))
(grouped-overlays
(seq-group-by (lambda (ov)
(let* ((os (overlay-start ov))
(oe (overlay-end ov)))
(cond
((or (< os wgs) (> oe wge)) nil)
((> oe p) 'after)
(t 'before))))
isearch-lazy-highlight-overlays)))
(seq-map-indexed
(lambda (ov index)
(isearch-lazy-hint (if isearch-forward (overlay-end ov) (overlay-start ov))
(1+ index)))
(cdr
;; Skip the current match
(seq-sort-by #'overlay-start (if isearch-forward #'< #'>)
(cdr (assq (if isearch-forward 'after 'before)
grouped-overlays)))))
(seq-map-indexed
(lambda (ov index)
(isearch-lazy-hint (if isearch-forward (overlay-start ov) (overlay-end ov))
(- (1+ index))))
(seq-sort-by #'overlay-start (if isearch-forward #'> #'<)
(cdr (assq (if isearch-forward 'before 'after)
grouped-overlays)))))))
(defun isearch-toggle-lazy-hints ()
(interactive)
(when isearch-lazy-hints
(isearch-lazy-hints-cleanup))
(setq isearch-lazy-hints (not isearch-lazy-hints))
(when isearch-lazy-hints
(isearch-lazy-hints)))
;; (add-hook 'isearch-mode-end-hook 'isearch-lazy-hints-cleanup)
;; To clean also after ispell lazy-highlight
(define-advice lazy-highlight-cleanup (:after (&optional _force _procrastinate))
(isearch-lazy-hints-cleanup))
;; TODO: add to the end of isearch-lazy-highlight-new-loop
(add-hook 'isearch-update-post-hook 'isearch-lazy-hints)
;; TODO: call isearch-lazy-hint from isearch-lazy-highlight-update?
(advice-add 'isearch-lazy-highlight-update :after
'isearch-lazy-hints)
(define-key isearch-mode-map (kbd "C-+") 'isearch-toggle-lazy-hints)
This is like M-z
(zap-to-char
):
(defun skip-to-char (arg char)
"Skip up to and including ARGth occurrence of CHAR.
Case is ignored if ‘case-fold-search’ is non-nil in the current buffer.
Goes backward if ARG is negative; error if CHAR not found."
(interactive "^p\ncSkip to char: ")
(search-forward (char-to-string char) nil nil arg))
and it can be used in Isearch:
;; Allow ‘C-SPC C-M-z $ M-s M-.’
(define-key esc-map "\C-z" 'skip-to-char)
;; Allow ‘C-s C-M-z $’ when ‘isearch-yank-on-move’ is ‘t’
;; (put 'skip-to-char 'isearch-move t)
Ignore diff-mode hunk indicators such as +
or -
at the
beginning of the diff lines while searching if the diff hunk
is unchanged. For example, put the deleted hunk to the search string,
then search it for the next match, and it will find the hunk
moved to another part of the file:
(isearch-define-mode-toggle diff-hunk "+" diff-hunk-to-regexp "\
Ignore diff-mode hunk indicators such as ‘+’ or ‘-’ at bol.")
(defun diff-hunk-to-regexp (string &optional _lax _from)
(replace-regexp-in-string
"[[:space:]]+" "[[:space:]]+"
(replace-regexp-in-string
"^\\(\\\\\\+\\|-\\)" "\\(^\\)[+-]"
(regexp-quote string) nil t)))
(add-hook 'diff-mode-hook
(lambda ()
(setq-local search-default-mode 'diff-hunk-to-regexp)))
C-RET
exits but doesn’t add the current search string to the search ring.
Also moves point to the beginning of the found search string.
(define-key isearch-mode-map [(control return)] 'isearch-exit)
(add-hook 'isearch-mode-end-hook
(lambda ()
;; Exiting isearch with C-RET
(when (eq 'return (event-basic-type last-input-event))
(when (memq 'control (event-modifiers last-input-event))
;; Move point to the beginning of the found search string
(if (region-active-p)
(exchange-point-and-mark)
(when (and isearch-forward isearch-other-end)
(goto-char isearch-other-end)))
;; Don't add the current search string to the search ring
(if isearch-regexp
(setq regexp-search-ring (cdr regexp-search-ring))
(setq search-ring (cdr search-ring)))))))
S-RET
exits and activates the region on the found match.
(define-key isearch-mode-map [(shift return)] 'my-isearch-exit-activate-region)
(define-key isearch-mode-map [(control shift return)] 'my-isearch-exit-activate-region)
(defun my-isearch-exit-activate-region ()
"Exit search and activate the region on the found match."
(interactive)
(unless (or (use-region-p) (not isearch-other-end))
(push-mark isearch-other-end t 'activate))
(isearch-exit))
M-RET
exits and leaves lazy-highlighted matches on the screen after exiting isearch.
(define-key isearch-mode-map [(meta return)] 'my-isearch-exit-leave-lazy-highlight)
(defun my-isearch-exit-leave-lazy-highlight ()
"Exit search and leave extra match highlighting."
(interactive)
(let ((lazy-highlight-cleanup nil))
(when isearch-lazy-highlight
(isearch-lazy-highlight-new-loop (point-min) (point-max)))
(isearch-exit)))
Note that to make the feature above more useful, you might want also to enable highlighting all matches in the buffer, not only the portion visible on the screen, then after exiting all matches in the buffer remain highlighted:
(setq-default
isearch-lazy-highlight 'all-windows
lazy-highlight-buffer t)
Enable char-folding in isearch:
(setq-default
search-default-mode 'char-fold-to-regexp)
and customize it:
(require 'char-fold)
(setq char-fold-symmetric t)
;; Add some typographical punctuation marks
(setq char-fold-include
(append char-fold-include
'((?- "–" "—"))))
;; Allow search to match accented Cyrillic chars, so e.g. in etc/HELLO
;; “Здравствуйте” will match “Здра́вствуйте” and vice versa:
(setq char-fold-include
(append char-fold-include
'((?а "а́") (?А "А́")
(?е "е́") (?Е "Е́")
(?и "и́") (?И "И́")
(?о "о́") (?О "О́")
(?у "у́") (?У "У́")
(?ы "ы́") (?Ы "Ы́")
(?э "э́") (?Э "Э́")
(?ю "ю́") (?Ю "Ю́")
(?я "я́") (?Я "Я́"))))
;; Allow searching with Cyrillic translit
;; https://en.wikipedia.org/wiki/Transliteration
;; https://en.wikipedia.org/wiki/Romanization_of_Russian#Transliteration_table
(setq char-fold-include
(append char-fold-include
'((?а "a")
(?б "b")
(?в "v" "w")
(?г "g")
(?д "d")
(?е "e")
(?ё "jo" "yo")
(?ж "v" "zh")
(?з "z")
(?и "i")
(?й "j" "y")
(?к "k")
(?л "l")
(?м "m")
(?н "n")
(?о "o")
(?п "p")
(?р "r")
(?с "s")
(?т "t")
(?у "u")
(?ф "f")
(?х "h")
(?ц "c")
(?ч "ch")
(?ш "sh")
(?щ "sch")
;; (?ъ "")
(?ы "y")
;; (?ь "")
(?э "e")
(?ю "ju" "yu")
(?я "ja" "ya"))))
(char-fold-update-table)
Better interactive arguments for text-property-search-forward
(see bug#36486)
(defun search-text-property (property &optional value predicate not-current)
"Same as ‘text-property-search-forward’, but better interactive arguments.
Added support for reading the second argument VALUE that allows reading
symbols as well as strings. Unlike ‘text-property-search-forward’, this
command can find combined text properties, e.g. can find the property
‘face’ and the value ‘hi-yellow’ in the buffer with the text property
containing the list of values ‘(hi-yellow font-lock-keyword-face)’.
Also ensure the whole buffer is fontified by ‘font-lock’ to be able
to find all text properties with font-lock face."
(interactive
(let* ((property (completing-read "Search for property: " obarray
nil nil nil nil '("markchars")))
(property (when (> (length property) 0)
(intern property obarray)))
(value (when property
(read-from-minibuffer "Search for property value (quote strings): "
nil nil t nil '("nil" "confusable")))))
(list property value)))
(font-lock-ensure)
(text-property-search-forward property value
(or predicate
(lambda (val p-val)
(if (and (listp p-val) (not (listp val)))
(member val p-val)
(equal val p-val))))
not-current))
Make the *Occur*
buffer names unique and writable
(like in compilation-mode-hook
in this file).
(add-hook 'occur-hook
(lambda ()
(occur-rename-buffer t)
(setq buffer-read-only nil)))
(defun substitute-regexp (substitution)
"Use s/old/new/g regexp syntax for ‘query-replace’."
(interactive
(list
(read-from-minibuffer "Substitute regexp: " '("s///g" . 3) nil nil
'query-replace-history nil t)))
(if (string-match "\\`s/\\(.*\\)/\\(.*\\)/\\([gi]*\\)" substitution)
(let* ((sregex (match-string 1 substitution))
(ssubst (match-string 2 substitution))
(sflags (match-string 3 substitution))
(case-fold-search (string-match "i" sflags)))
(perform-replace
sregex ssubst (string-match "g" sflags)
t nil nil nil
(if (and transient-mark-mode mark-active) (region-beginning))
(if (and transient-mark-mode mark-active) (region-end))))
(error "Invalid syntax")))
;; FROM my answer in https://emacs.stackexchange.com/questions/27135/search-replace-like-feature-for-swapping-text/27170#27170
(defun query-swap-strings (from-string to-string &optional delimited start end backward region-noncontiguous-p)
"Swap occurrences of FROM-STRING and TO-STRING."
(interactive
(let ((common
(query-replace-read-args
(concat "Query swap"
(if current-prefix-arg
(if (eq current-prefix-arg '-) " backward" " word")
"")
(if (use-region-p) " in region" ""))
nil)))
(list (nth 0 common) (nth 1 common) (nth 2 common)
(if (use-region-p) (region-beginning))
(if (use-region-p) (region-end))
(nth 3 common)
(if (use-region-p) (region-noncontiguous-p)))))
(perform-replace
(concat "\\(" (regexp-quote from-string) "\\)\\|" (regexp-quote to-string))
`(replace-eval-replacement replace-quote (if (match-string 1) ,to-string ,from-string))
t t delimited nil nil start end backward region-noncontiguous-p))
See https://lists.gnu.org/archive/html/emacs-devel/2014-12/msg00299.html
(define-key minibuffer-local-map [S-return] 'newline)
Complete history in all non-completion minibuffers like ‘rgrep’ (bug#62800):
(keymap-set minibuffer-local-map "TAB" 'minibuffer-complete-history)
(keymap-set minibuffer-local-map "<backtab>" 'minibuffer-complete-defaults)
Remove potentially dangerous commands from the history immediately. Also like in Bash HISTCONTROL:
“A colon-separated list of values controlling how commands are saved on the history list. If the list of values includes ignorespace, lines which begin with a space character are not saved in the history list. A value of ignoredups causes lines matching the previous history entry to not be saved.”
TODO
: We already have history-delete-duplicates
that corresponds to ignoredups
,
but still no option that would corresponds to ignorespace
.
(define-advice add-to-history (:around (ofun history-var newelt &rest args) ignorespace)
(unless (or (and (memq history-var
'( shell-command-history compile-history
read-expression-history))
(string-match-p "\\`\\(?:rm\\|git rm\\| \\)" newelt))
;; I often type `M-p RET' inadvertently
(and (eq history-var 'compile-history)
(string-match-p "bootstrap" newelt)))
(apply ofun history-var newelt args)))
M-k
in the minibuffer deletes the minibuffer history element:
(defun delete-history-element ()
"Delete the current minibuffer history element from the history.
After deleting the element, the history position is changed either
to the the previous history element, or to the next history element
if the deleted element was the last in the history list."
(interactive)
(cond
((= minibuffer-history-position 1)
(set minibuffer-history-variable
(cdr (symbol-value minibuffer-history-variable))))
((> minibuffer-history-position 1)
(setcdr (nthcdr (- minibuffer-history-position 2)
(symbol-value minibuffer-history-variable))
(nthcdr minibuffer-history-position
(symbol-value minibuffer-history-variable)))))
(condition-case nil (next-history-element 1) (error nil))
(condition-case nil (previous-history-element 1) (error nil)))
(define-key minibuffer-local-map "\ek" 'delete-history-element)
(define-key minibuffer-local-isearch-map "\ek" 'delete-history-element)
Use the substring
completion style for C-x p f M-n
and C-x v b l
:
;; (define-advice project-find-file (:around (ofun &rest args) substring)
;; (let ((completion-styles '(flex)))
;; (apply ofun args)))
;; (define-advice vc-print-branch-log (:around (ofun &rest args) substring)
;; (interactive (lambda (spec)
;; (minibuffer-with-setup-hook
;; (lambda ()
;; (setq-local completion-styles '(flex)))
;; (advice-eval-interactive-spec spec))))
;; (apply ofun args))
(add-hook 'prog-mode-hook #'completion-preview-mode)
(with-eval-after-load 'completion-preview
(keymap-set completion-preview-active-mode-map "<down>" #'completion-preview-next-candidate)
(keymap-set completion-preview-active-mode-map "<up>" #'completion-preview-prev-candidate)
(keymap-set completion-preview-active-mode-map "C-w" #'completion-preview-insert-word))
;; TODO: use bookmark.el?
;; TODO: add Info node and line number
(defun qv (&optional url anchor count)
"Add or activate live bookmarks.
When called interactively, put the address of the current location
inside a function call to ‘qv’ into the clipboard that can be
pasted in another buffer that stores bookmarks.
Otherwise, after typing ‘C-x C-e’ on the bookmark funcall
goes to the saved location."
(interactive)
(if (called-interactively-p 'any)
(kill-new
(message "%s"
(concat "(qv "
(cond
(buffer-file-name
(format "\"%s\"\n %S" ;; "\"%s\" %s"
(abbreviate-file-name buffer-file-name)
;;(line-number-at-pos)
(replace-regexp-in-string
"[\n]+" " "
(replace-regexp-in-string
"^[\s\t]+" ""
(if (region-active-p)
(prog1 (buffer-substring-no-properties
(region-beginning)
(region-end))
(deactivate-mark))
(buffer-substring-no-properties
(line-beginning-position)
(line-end-position))))))))
")")))
(push-mark nil t)
(cond
((file-exists-p url)
(pop-to-buffer-same-window (find-file-noselect url))
(cond
;; Line number
((integerp anchor)
(goto-char (point-min))
(forward-line (1- anchor)))
;; Line regexp
((and (stringp anchor)
(string-match-p "^^" anchor))
(goto-char (point-min))
(when (re-search-forward anchor nil nil count)
(goto-char (match-beginning 0))))
;; Line string
((stringp anchor)
(goto-char (point-min))
(when (re-search-forward
(format "%s" ;; "[\s\t]*%s"
(replace-regexp-in-string
"[[:space:]]+" "[[:space:]]+"
(regexp-quote anchor)))
nil nil count)
(goto-char (match-beginning 0)))))))))
Use box cursor for overwrite-mode
, and red cursor for quail active input
and while repeating is active in repeat-mode
:
(defun my-change-cursor (&rest _)
"Change cursor color and type depending on insertion mode and input method."
(set-cursor-color
(cond (repeat-in-progress "blue")
(current-input-method "red3") ;; "AntiqueWhite4"
;; ((eq (frame-parameter (selected-frame) 'background-mode) 'dark)
;; "DarkGrey")
(t "black")))
(setq-default cursor-type
(cond (overwrite-mode 'box)
(t 'bar))))
(add-hook 'post-command-hook 'my-change-cursor t)
;; Also update the cursor on the repeat timer events:
(add-function :after repeat-echo-function #'my-change-cursor)
Create display table to modify some display elements:
(or standard-display-table (setq standard-display-table (make-display-table)))
Display page delimiter ^L
as a horizontal line to make it more noticeable:
(aset standard-display-table ?\^L (vconcat (make-vector 64 ?-) "^L"))
Display triangle for outline of invisible lines (for more information, see the ELisp manual):
(if (facep 'escape-glyph)
(let* ((face (lsh (face-id 'escape-glyph) 22)) ;; 22 was 19 in Emacs 22
;; (backslash (+ face ?\\))
;; TRIANGULAR BULLET keeps the default font height
(dot (+ face ?…))) ;; ?‣
;; (aset standard-display-table 2208 (vector backslash ?\s)) ; no-break space
;; (aset standard-display-table 2221 (vector backslash ?-)) ; soft hyphen
;; (set-char-table-extra-slot standard-display-table 2 backslash) ; \364
;; (set-char-table-extra-slot standard-display-table 3 (+ face ?^)) ; ^@
;; (set-char-table-extra-slot standard-display-table 4 (vector dot dot dot))
(set-char-table-extra-slot standard-display-table 4 (vector dot))))
Tabify only initial whitespace
(with-eval-after-load 'tabify
(setq tabify-regexp "^\t* [ \t]+"))
;; Do not use customization to not corrupt .emacs with literal
;; control characters.
;; The next line is bad, because \n is bad for ‘C-M-s SPC $’
;; (setq search-whitespace-regexp "[ \t\r\n]+")
;; TRY to ignore punctuation, BAD because C-w (‘isearch-yank-word-or-char’)
;; doesn't yank punctuation characters, so use word search instead of this:
;; (setq search-whitespace-regexp "\\W+")
;; TRY to match newlines like in ‘compare-windows-whitespace’:
(setq search-whitespace-regexp "\\(?:\\s-\\|\n\\)+") ; bug#35802
;; Actually this line doesn't affect ‘search-whitespace-regexp’ defined below.
(with-eval-after-load 'info
(setq Info-search-whitespace-regexp "\\(?:\\s-\\|\n\\)+"))
;; TRY:
;; Like ‘word-search-regexp’
(defun search-whitespace-regexp (string &optional _lax)
"Return a regexp which ignores whitespace.
Uses the value of the variable ‘search-whitespace-regexp’."
(if (or (not (stringp search-whitespace-regexp))
(null (if isearch-regexp
isearch-regexp-lax-whitespace
isearch-lax-whitespace)))
string
;; FIXME: this is not strictly correct implementation because it ignores
;; ‘subregexp-context-p’ and replaces spaces inside char set group like
;; in ‘C-M-s M-s SPC [ ]’, it converts it to ["\\(?:\\s-\\|\n\\)+"] !
(replace-regexp-in-string
search-whitespace-regexp
search-whitespace-regexp ;; or replace by " " that is handled by search-spaces-regexp
(regexp-quote string) nil t)))
;; (defun search-forward-lax-whitespace (string &optional bound noerror count)
;; (re-search-forward (search-whitespace-regexp (regexp-quote string)) bound noerror count))
;; (defun search-backward-lax-whitespace (string &optional bound noerror count)
;; (re-search-backward (search-whitespace-regexp (regexp-quote string)) bound noerror count))
;; (defun re-search-forward-lax-whitespace (regexp &optional bound noerror count)
;; (re-search-forward (search-whitespace-regexp regexp) bound noerror count))
;; (defun re-search-backward-lax-whitespace (regexp &optional bound noerror count)
;; (re-search-backward (search-whitespace-regexp regexp) bound noerror count))
While canonically-space-region
removes extra spaces and leaves two spaces
at the end of sentences, a new function canonically-double-space-region
adds two spaces at the end of sentences (then they end with just one space)
when filling the paragraph with M-q
:
(defun canonically-double-space-region (beg end)
(interactive "*r")
(canonically-space-region beg end)
(unless (markerp end) (setq end (copy-marker end t)))
(let* ((sentence-end-double-space nil) ; to get right regexp below
;; See also the variable `sentence-end-base':
(end-spc-re (rx (>= 5 (not (in ".?!…"))) (regexp (sentence-end)))))
(save-excursion
(goto-char beg)
(while (and (< (point) end)
(re-search-forward end-spc-re end t))
(unless (or (>= (point) end)
(looking-back "[[:space:]]\\{2\\}\\|\n" 3))
(insert " "))))))
(define-advice fill-paragraph (:before (&rest _args) double-space)
(when (use-region-p)
(canonically-double-space-region
(region-beginning)
(region-end))))
While using repunctuate-sentences
, skip some known abbreviations:
(add-function :after-while repunctuate-sentences-filter
(lambda (start end)
(not (looking-back (rx (or "e.g." "i.e.")
" ")
5))))
(add-function :after-while repunctuate-sentences-filter
(lambda (start end)
(not (looking-back (rx (or (and "т." (any "екнч") ".")
"см.")
" ")
5))))
(add-hook 'text-mode-hook 'turn-on-auto-fill)
(add-hook 'fill-nobreak-predicate 'fill-single-char-nobreak-p)
;; (add-hook 'fill-nobreak-predicate 'fill-french-nobreak-p)
;; (add-hook 'fill-nobreak-predicate 'fill-single-word-nobreak-p)
(defun my-flyspell-mode ()
;; Avoid duplicate calls when ‘flyspell-mode’ is enabled by ‘.dir-locals.el’,
;; because the first call uses the global value of ‘ispell-personal-dictionary’,
;; and so restarts the Ispell process.
(unless (progn (hack-dir-local-variables)
(member '(mode . flyspell) file-local-variables-alist))
(if (derived-mode-p '(prog-mode))
(flyspell-prog-mode)
(flyspell-mode))))
(add-hook 'text-mode-hook 'my-flyspell-mode)
(add-hook 'change-log-mode-hook 'my-flyspell-mode)
(add-hook 'prog-mode-hook 'my-flyspell-mode)
Non-customizable variables:
(setq gc-cons-percentage 0.3) ; seems now it's customizable
(setq print-gensym t)
(setq print-circle t)
For a new non-file buffer set its major mode based on the buffer name.
For example, C-x b newbuffer.el
will set the major mode in a new buffer
to emacs-lisp-mode
by the file extension .el
in the buffer name.
(setq-default major-mode (lambda ()
(if buffer-file-name
(fundamental-mode)
(let ((buffer-file-name (buffer-name)))
(set-auto-mode)))))
Note that this has some problems, e.g. in autoinsert.el
that uses
(eq major-mode (default-value 'major-mode))
.
Don’t reuse existing diff buffers, and add more highlighting:
(with-eval-after-load 'diff-mode
;; Note that this pollutes with temp buffers in org-src-font-lock-fontify-block
;; that has ‘(get-buffer-create (format " *org-src-fontification:%s*" lang-mode))’
;; because it renames internal buffers, so they can't be reused.
(add-hook 'diff-mode-hook 'rename-uniquely)
;; Make revision separators more noticeable:
(setq diff-font-lock-keywords
(append diff-font-lock-keywords
'(("^\\(?:diff\\|revno:\\|Only in\\|Binary files\\)" (0 'match prepend)))))
;; Allow hi-lock overlays over diff-refine overlays
(add-hook 'diff-mode-hook
(lambda ()
(setq-local hi-lock-use-overlays t))))
(with-eval-after-load 'log-view
(add-hook 'log-view-mode-hook 'rename-uniquely))
(with-eval-after-load 'log-edit
(add-hook 'log-edit-mode-hook 'rename-uniquely))
;; Don't shrink diff and log buffers:
(remove-hook 'vc-diff-finish-functions 'vc-shrink-buffer-window)
(remove-hook 'vc-log-finish-functions 'vc-shrink-buffer-window)
(define-generic-mode 'diff-generic-mode
(list ?#)
nil
'(("^\\(<-? \\)" 1 'font-lock-keyword-face)
("^\\(-?> \\)" 1 'font-lock-function-name-face)
("^\\(\\(<!\\|!>\\) .*\\)" 1 'font-lock-warning-face))
(list "\\.subpatch\\'")
nil
"For diffuniq and subpatch.")
;; Prevent git-diff from calling pager
;; (setenv "PAGER" "/bin/cat")
;; (setenv "PAGER") (getenv "PAGER")
Date-based backup extension with tilde omitted in dired-x:
(with-eval-after-load 'ediff-ptch
(setq ediff-default-backup-extension (format-time-string ".~ediff-%Y-%m-%d~" (current-time)))
(custom-reevaluate-setting 'ediff-backup-extension)
(custom-reevaluate-setting 'ediff-backup-specs))
Fix keybindings and run vc-dir
in project root:
(with-eval-after-load 'vc-hooks
;; Because ‘C-x v =’ is easily mistyped as ‘C-x v +’
(define-key vc-prefix-map "+" 'vc-diff)
(define-key vc-prefix-map "S" 'vc-log-search)
;; Unbind dangerous commands
(define-key vc-prefix-map "u" nil))
(with-eval-after-load 'vc-dir
;; Because ‘=’ is easily mistyped as ‘+’
(define-key vc-dir-mode-map "+" 'vc-diff)
;; Often ‘v’ is mistyped to view files like in Dired
(define-key vc-dir-mode-map "v" 'vc-dir-view-file)
(define-key vc-dir-mode-map "." 'vc-next-action)
;; Like ‘i’ is Dired
(define-key vc-dir-mode-map "i" 'vc-dir-show-fileentry))
(define-key my-map "d" 'project-vc-dir)
Highlight found occurrences in *vc-search-log*
output buffer of vc-log-search
.
Warning: uses Emacs regexps to highlight Git regexp — their syntax might differ!
(define-advice vc-git-log-search (:after (buffer pattern) highlight)
(with-current-buffer buffer
(when pattern
(vc-run-delayed
(highlight-regexp pattern 'hi-yellow)))))
(add-hook 'log-view-mode-hook
(lambda ()
(vc-run-delayed
(highlight-regexp "bug#" 'hi-yellow))))
At the top of the log buffer add an extra line that can be used
as the region beginning for log-view-diff
to compare revisions
with the current working tree (bug#35860, bug#62940):
(define-advice vc-git-print-log (:after (_files buffer &optional _shortlog _start-revision _limit) insert-top-line)
(when (and (memq vc-log-view-type '(long short))
(not (memq this-command '(vc-print-branch-log))))
;; For '(add-hook 'log-view-mode-hook 'rename-uniquely)'
(setq buffer (get-buffer buffer))
;; Wrap next lines with 'vc-run-delayed' when using 'insert'
;; instead of 'insert-before-markers-and-inherit' below.
(with-current-buffer buffer
(save-excursion
(goto-char (point-min))
(let ((inhibit-read-only t))
(insert-before-markers-and-inherit
(propertize "(Type 'd' here to show diffs with working version)\n"
'font-lock-face 'shadow)))))))
When project-mode-line
is customized to t
and
vc-display-status
is customized to no-backend
,
then it’s nice to remove the space between the
mode-line indicators for the project name and vc status
to display the joined status like “project-branch”:
(define-advice vc-mode-line (:after (&rest _args) remove-space)
(when vc-mode (setq vc-mode (string-trim-left vc-mode))))
Use display-fill-column-indicator
to limit the width of log entries:
;; TODO: better to add to .dir-locals.el (bug#36861)
(add-hook 'vc-git-log-edit-mode-hook
(lambda ()
(setq fill-column 78)
(setq display-fill-column-indicator-column 78)
(display-fill-column-indicator-mode t)))
Warn about commits behind after switching the branch
that helps not to forget about git pull
:
(define-advice vc-switch-branch (:after (&rest _) commits-behind)
(let ((buffer (get-buffer-create "*vc-commits-behind*")))
(vc-call-backend (vc-deduce-backend) 'log-incoming buffer "")
(vc-run-delayed
(with-current-buffer buffer
(let ((lines (count-lines (point-min) (point-max))))
(unless (zerop lines)
(warn "%s commits behind" lines)))))))
(require 'dired-aux) ;; For ‘dired-shell-stuff-it’
(require 'dired-x)
;; HINT: the following expression is useful for ‘M-(’ ‘dired-mark-sexp’
;; to mark files by their type:
;; (string-match "perl" (shell-command-to-string (concat "file " name)))
(define-key dired-mode-map [(control enter)] 'dired-do-open)
(define-key dired-mode-map [(control return)] 'dired-do-open)
;; Add different directory sorting keys
(mapc (lambda (elt)
(define-key dired-mode-map (car elt)
`(lambda ()
(interactive)
(dired-sort-other (concat dired-listing-switches ,(cadr elt))))))
'(([(control f3)] "" "by name")
([(control f4)] " -X" "by extension")
([(control f5)] " -t" "by time")
([(control f6)] " -S" "by size")
([(control shift f3)] " -r" "by reverse name")
([(control shift f4)] " -rX" "by reverse extension")
([(control shift f5)] " -rt" "by reverse time")
([(control shift f6)] " -rS" "by reverse size")))
;; The following two bindings allow to quickly look to the file and return back
;; to dired by pressing [f3] twice (same keys are used in Midnight Commander)
(define-key dired-mode-map [f3] 'dired-view-file)
(define-key global-map [f3] 'kill-current-buffer)
(define-key global-map [(control f3)] 'kill-current-buffer-and-dired-jump)
(define-key dired-mode-map [(shift f3)] 'dired-find-file-literally)
;; Alternative definitions on keyboards with problematic Fn keys
(define-key global-map "\C-q" 'quit-window-kill-buffer)
(define-key global-map "\C-xj" 'kill-current-buffer-and-dired-jump)
;; (define-key global-map "\C-n" 'kill-current-buffer)
;; (define-key global-map "\C-b" 'kill-current-buffer-and-dired-jump)
;; Other unused keys:
;; "\C-f" (use for search?)
;; "\C-p" (use for pretty-print)
;; "\C-i", "\C-v", "\C-m"
;; The following two bindings allow to open file for editing by [f4],
;; and return back to dired without killing the buffer.
(define-key dired-mode-map [f4] 'dired-find-file) ;; 'dired-view-file
(define-key global-map [f4] 'dired-jump)
(define-key dired-mode-map [(shift f5)] 'dired-count-sizes)
;; TEST
;; (define-key dired-mode-map [up] 'dired-previous-line)
;; (define-key dired-mode-map [down] 'dired-next-line)
;; The following keys resemble *Commander's bindings.
;; But currently I use original Emacs bindings: "C", "R", "D"
;; (define-key dired-mode-map [f5] 'dired-do-copy)
;; (define-key dired-mode-map [f6] 'dired-do-rename)
;; (define-key dired-mode-map [f8] 'dired-do-delete)
(define-key dired-mode-map [delete] 'dired-do-delete)
(define-key dired-mode-map [f7] 'dired-create-directory)
(define-key dired-mode-map [(shift f7)] 'find-dired)
(define-key dired-mode-map [(control meta ?=)] 'dired-compare-directories)
(defun dired-in-project-root ()
"Run ‘dired’ in project root directory."
(interactive)
(let* ((project (project-current))
(root (and project (car (project-roots project)))))
(dired (or (and root (file-directory-p root) root) default-directory))))
;; The same as `C-x p D'
(define-key ctl-x-map "D" 'dired-in-project-root)
(defun my-dired-do-shell-command-on-current-file ()
"Run a shell command on the current file instead of marked files."
(interactive)
(let ((dired-marker-char ?M)) ; ?M is unused marker char
(call-interactively 'dired-do-shell-command)))
(define-key dired-mode-map "%!"
'my-dired-do-shell-command-on-current-file)
(define-key dired-mode-map [(control meta ?!)]
'my-dired-do-shell-command-on-current-file)
(defun my-dired-mark (arg)
"Mark ARG files and print the total size of marked files."
(interactive "P")
(dired-mark arg)
(dired-count-sizes dired-marker-char))
(define-key dired-mode-map [insert] 'my-dired-mark)
(defun my-dired-unmark-backward (arg)
"Move up lines, remove deletion flag there and print size of marked files."
(interactive "p")
(dired-unmark-backward arg)
(dired-count-sizes dired-marker-char))
(define-key dired-mode-map [backspace] 'my-dired-unmark-backward)
(define-key dired-mode-map [(control shift insert)]
(lambda () (interactive) (dired-copy-filename-as-kill 0)))
(define-key dired-mode-map [remap next-line] nil)
(define-key dired-mode-map [remap previous-line] nil)
;; qv http://thread.gmane.org/gmane.emacs.devel/153150/focus=153151
(define-key dired-mode-map "\M-=" 'dired-backup-diff)
;; Get coding from the file, so diff will output in the correct coding:
(define-advice dired-backup-diff (:around (ofun &rest args) coding)
(let* ((filename (dired-get-filename))
(coding-system (when (and (file-regular-p filename)
(not (file-remote-p filename)))
(with-temp-buffer
(insert-file-contents filename nil 0 1024)
buffer-file-coding-system)))
(coding-system-for-read (or coding-system coding-system-for-read))
(coding-system-for-write (or coding-system coding-system-for-write)))
(apply ofun args)))
;; Get coding from the file, so diff will output in the correct coding:
(define-advice dired-diff (:around (ofun &rest args) coding)
(let* ((filename (dired-get-filename))
(coding-system (when (and (file-regular-p filename)
(not (file-remote-p filename)))
(with-temp-buffer
(insert-file-contents filename nil 0 1024)
buffer-file-coding-system)))
(coding-system-for-read (or coding-system coding-system-for-read))
(coding-system-for-write (or coding-system coding-system-for-write)))
(apply ofun args)))
;; (define-key dired-mode-map "\C-y" (lambda (&optional arg)
;; (interactive)
;; (dired-find-file)
;; (goto-char (point-max))
;; (yank arg)))
(define-key dired-mode-map "q" 'quit-window-kill-buffer)
(add-hook 'dired-after-readin-hook
(lambda ()
;; Set name of dired buffers to absolute directory name.
;; Use non-nil arg ‘unique’ for ‘rename-buffer’
;; because vc-dir that creates duplicate buffers.
;; SEE ALSO https://emacs.stackexchange.com/q/2123
;; (when (stringp dired-directory)
;; ;; cf with (add-hook 'dired-after-readin-hook 'rename-uniquely)
;; (rename-buffer dired-directory t))
;; TODO: Maybe better to change ‘dired-internal-noselect’
;; from ‘(create-file-buffer (directory-file-name dirname))’
;; to just ‘(create-file-buffer dirname)’ that leaves the final slash,
;; but the problem is that ‘uniquify--create-file-buffer-advice’
;; changes absolute directory name with slash to short name dir.
;; The current solution still relies on uniquify, but adds
;; the final slash to dired buffer names, e.g. "dir/"
(when (stringp dired-directory)
(rename-buffer
(file-name-as-directory
(file-name-nondirectory
(directory-file-name dired-directory)))
t))))
(add-hook 'dired-mode-hook
(lambda ()
;; Omit file extensions only in well-known directories, because
;; I don't want to miss omitted files in unknown directories!
;; Omit only in some large directories that I use often.
(when (string-match-p "emacs/\\(sv\\|git\\|bzr\\|cvs\\)" default-directory)
(setq dired-omit-mode t)
;; Set variable above because the next calls dired-omit-expunge twice:
;; (dired-omit-mode 1) because of this in dired-x.el:
;; (add-hook 'dired-after-readin-hook 'dired-omit-expunge)
)
;; Use old "\M-o" instead of new "\C-x\M-o".
(define-key dired-mode-map "\M-o" 'dired-omit-mode)))
;; http://mail.gnu.org/archive/html/emacs-devel/2004-04/msg01190.html
;; http://mail.gnu.org/archive/html/emacs-devel/2004-04/msg01247.html
;; (define-key dired-mode-map "r" 'wdired-change-to-wdired-mode)
;; (OLD) This is a bad keybinding because I sometimes type ‘C-x C-q’
;; in *Locate* buffer (which is in dired-mode) and do ‘flush-lines’
;; (define-key dired-mode-map "\C-x\C-q" 'wdired-change-to-wdired-mode)
;; UPDATE: http://thread.gmane.org/gmane.emacs.devel/118678/focus=118684
;; Beware: C-x C-q is bound in locate-mode and find-dired
(with-eval-after-load 'wdired
(define-key wdired-mode-map [return] 'wdired-finish-edit)
(define-key wdired-mode-map [kp-enter] 'wdired-finish-edit)
;; BAD, better to add a new rule at the end of ‘keyboard-escape-quit’:
;; (define-key wdired-mode-map [escape] 'wdired-abort-changes)
)
(with-eval-after-load 'locate
;; Redefine ‘locate-default-make-command-line’.
(defun locate-make-command-line-ignore-case (search-string)
(list locate-command "-i" search-string))
(setq locate-make-command-line 'locate-make-command-line-ignore-case))
Highlight all matches in the *Locate*
buffer like in the *Occur*
buffer
(add-hook 'locate-post-command-hook
(lambda ()
(save-excursion
(goto-char (point-min))
(when (or (re-search-forward "Matches for .* using filter \\(.*\\):" nil t)
(re-search-forward "Matches for \\(.*\\):" nil t))
(highlight-regexp
(match-string-no-properties 1)
'match)))))
(defun project-shell-maybe ()
"Run shell in project root directory or in the current directory."
(interactive)
(if (project-current)
(project-shell)
;; Use ‘create-file-buffer’ to uniquify shell buffer names.
(shell (create-file-buffer "*shell*"))))
(define-key my-map "s" 'project-shell-maybe)
Per-project shell history:
(define-advice shell (:around (ofun &rest args) per-tree-history)
(let ((process-environment (copy-sequence process-environment))
(root (locate-dominating-file default-directory ".bash_history")))
(when root
(setenv "HISTFILE" (expand-file-name ".bash_history" root)))
(apply ofun args)))
Override comint-delchar-or-maybe-eof
on C-d
in shell:
(defun my-shell-c-d (&optional arg)
(interactive "p")
;; (let* ((proc (get-buffer-process (current-buffer)))))
(cond ((and (eobp)
(save-excursion
(let ((inhibit-field-text-motion t))
(goto-char (line-beginning-position))
(looking-at-p "^iex.*>\s*$"))))
(let ((process (get-buffer-process (current-buffer))))
(process-send-string process ":init.stop()\n")))
((and (eobp)
(save-excursion
(let ((inhibit-field-text-motion t))
(goto-char (line-beginning-position))
;; e.g. “dev:cljs.user=> ”
(looking-at-p "^[a-z:]*cljs\\..*=>\s*$"))))
(let ((process (get-buffer-process (current-buffer))))
(process-send-string process ":cljs/quit\n")))
(t
(comint-delchar-or-maybe-eof arg))))
(when delete-selection-mode
(put 'comint-delchar-or-maybe-eof 'delete-selection 'supersede)
(put 'my-shell-c-d 'delete-selection 'supersede))
Also rename shell buffers uniquely:
(with-eval-after-load 'shell
;; This affects “*Async Shell Command*” buffers
(add-hook 'shell-command-mode-hook 'rename-uniquely)
;; This affects “*shell*” buffers
(add-hook 'shell-mode-hook 'rename-uniquely)
;; Turn off dirtrack because it fails in Bash on Heroku.
(add-hook 'shell-mode-hook (lambda () (shell-dirtrack-mode -1)))
(define-key shell-mode-map "\C-d" 'my-shell-c-d))
C-j
switches to the “*Shell Command Output*” buffer
instead of displaying short output in the echo area:
;; TODO: Another idea from http://thread.gmane.org/gmane.emacs.bugs/4533
;; Use M-RETURN to run ‘async-shell-command’.
(define-advice shell-command (:around (ofun &rest args) messagify)
(let ((messages-buffer-max-lines
;; Don't add output to the *Messages* buffer
;; when S-RET displays the *Shell Command Output* buffer.
(unless (memq last-input-event '(S-return ?\C-j))
messages-buffer-max-lines)))
(apply ofun args)
(when (memq last-input-event '(S-return ?\C-j))
(message "") ;; Clear the echo area
(pop-to-buffer "*Shell Command Output*")
(goto-char (point-min)))))
;; The same as the previous ‘define-advice’.
(define-advice dired-do-shell-command (:around (ofun &rest args) messagify)
(let ((messages-buffer-max-lines
(unless (memq last-input-event '(S-return ?\C-j))
messages-buffer-max-lines)))
(apply ofun args)
(when (memq last-input-event '(S-return ?\C-j))
(message "")
(pop-to-buffer "*Shell Command Output*")
(goto-char (point-min)))))
;; The same as the previous ‘define-advice’.
(define-advice dired-smart-shell-command (:around (ofun &rest args) messagify)
(let ((messages-buffer-max-lines
(unless (memq last-input-event '(S-return ?\C-j))
messages-buffer-max-lines)))
(apply ofun args)
(when (memq last-input-event '(S-return ?\C-j))
(message "")
(pop-to-buffer "*Shell Command Output*")
(goto-char (point-min)))))
This command has so many bindings because it’s difficult to type with AltGr
key:
(define-key esc-map "|" 'shell-command-on-region-or-buffer)
(define-key esc-map "\M-|" 'shell-command-on-region-or-buffer) ; ‘M-ESC |’
(define-key global-map [(control ?|)] 'shell-command-on-region-or-buffer)
(define-key my-map "|" 'shell-command-on-region-or-buffer)
This is a new mode to visit previous shell outputs saved to log files. It highlights prompts with special faces, so it’s easier to browse old log files:
(defvar shell-log-regexp
(rx-let ((c (* (not (any "#$%>\12"))))) ; any non-prompt char
(rx bol
(group-n 1 c (or
;; Bash or Ruby prompt
(and (or "@" "irb" ) c ":" c)
;; Elixir prompt
(and "iex" c))
(any "#$%>") (* " "))
(group-n 2 (* nonl))
eol)))
(defvar shell-log-font-lock-keywords
;; ‘shell-prompt-pattern’ can't be used: it finds too many false matches
`((,shell-log-regexp
(1 'comint-highlight-prompt)
(2 'comint-highlight-input))
(,(bound-and-true-p shell-prompt-pattern)
(0 'comint-highlight-prompt)))
"Shell prompts to highlight in Shell Log mode.")
(define-derived-mode shell-log-mode shell-mode "Shell-Log"
"Font-lock for shell logs."
(put 'shell-log-mode 'mode-class nil)
(setq-local font-lock-defaults '(shell-log-font-lock-keywords t)))
(defun shell-log-or-compilation-mode ()
"Enable either ‘shell-log-mode’ or ‘compilation-mode’ based on log contents."
(require 'shell)
(let ((mode (save-excursion
(goto-char (point-min))
(cond ((re-search-forward
(rx (or "./configure" (and bol "Compilation")))
nil t)
'compilation-mode)
((re-search-forward shell-log-regexp nil t)
'shell-log-mode)
((re-search-forward shell-prompt-pattern nil t)
'shell-mode)))))
(when mode (funcall mode))))
(add-to-list 'auto-mode-alist '("\\.log\\'" . shell-log-or-compilation-mode))
Word-based rgrep
and project-find-regexp
:
(defun wrgrep ()
"Word-based version of ‘rgrep’.
Modifies ‘grep-find-template’ to add the option ‘-w’ that matches whole words."
(interactive)
(let ((grep-host-defaults-alist nil)
(grep-find-template
(replace-regexp-in-string "<C>" "-w \\&" grep-find-template)))
(call-interactively 'rgrep)))
(defun project-find-word (regexp)
"Word-based version of ‘project-find-regexp’.
Modifies REGEXP to add the word boundaries to match whole words."
(interactive (list (project--read-regexp)))
(project-find-regexp (concat "\\b" regexp "\\b")))
(define-key project-prefix-map "w" 'project-find-word)
Don’t pollute manually added entries in grep-history
with constructed commands:
(define-advice lgrep (:around (ofun &rest args) no-history)
(let ((grep-history '()))
(apply ofun args)))
(define-advice vc-git-grep (:around (ofun &rest args) no-history)
(let ((grep-history '()))
(apply ofun args)))
Project-based compilation:
(defun my-compile ()
(interactive)
(compile
;; Use previous command from history
;; instead of the default from ‘compile-command’
(compilation-read-command (car compile-history))
;; Don't use compilation-shell-minor-mode
nil))
(define-key global-map [(control f9)] 'my-compile)
;; UNUSED!!!
(defun my-project-compile ()
(interactive)
;; Like ‘project-compile’
(let ((default-directory (project-root (project-current t)))
(compilation-buffer-name-function
(lambda (name-of-mode)
(generate-new-buffer-name
(project-prefixed-buffer-name name-of-mode)))))
(compile
;; Use previous command from history
;; instead of the default from ‘compile-command’
(compilation-read-command (car compile-history))
;; Don't use ‘compilation-shell-minor-mode’
nil)))
(define-key my-map "m" 'project-compile) ; “m” has mnemonics “make”
;; For ‘C-x p c’ (project-compile) and subsequent 'g' (recompile):
(setopt project-compilation-buffer-name-function
(lambda (name-of-mode)
(generate-new-buffer-name
(project-prefixed-buffer-name name-of-mode))))
Compilation buffer names:
(with-eval-after-load 'compile
(add-hook 'compilation-mode-hook
(lambda ()
;; (rename-uniquely)
(setq buffer-read-only nil))))
;; Create unique buffer name for ‘compile’ and ‘grep’.
(setq compilation-buffer-name-function
(lambda (name-of-mode)
(generate-new-buffer-name
(concat "*" (downcase name-of-mode) "*"))))
;; Currently NOT USED
(defun my-compilation-buffer-name-function (name-of-mode)
(cond
((and (bound-and-true-p mode-command)
(eq mode-command major-mode)
(eq major-mode (nth 1 compilation-arguments)))
(buffer-name))
((let ((window-buffers
(delete-dups
(delq nil (mapcar (lambda (w)
(with-current-buffer (window-buffer w)
(if (and (bound-and-true-p mode-command)
(eq mode-command major-mode))
(window-buffer w))))
(window-list))))))
(if (eq (length window-buffers) 1)
(car window-buffers))))
((generate-new-buffer-name
(concat "*" (downcase name-of-mode) "*")))))
(if (boundp 'mode-command)
(setq compilation-buffer-name-function
'my-compilation-buffer-name-function))
;; UNUSED!
;; After running compilation/grep hide the header of the compilation/grep
;; buffer which contains information rarely useful to see (i.e. mode:
;; grep; default-directory:..., start time). This header can be hidden by
;; setting the output window's start at position of the 4-th output line.
;; TODO: try to put 'invisible property on it, because next doesn't work well.
;; But commented out because I don't like this code anymore:
(add-hook 'compilation-finish-functions
(lambda (cur-buffer _msg)
(mapc (lambda (window)
(set-window-start window
(save-excursion
(goto-char (point-min))
(line-beginning-position 4))))
(get-buffer-window-list cur-buffer))))
;; Run the compiled program in gdb if live gdb buffer exists
(add-hook 'compilation-finish-functions
(lambda (cur-buffer msg)
(when (and (string-match-p "finished" msg)
(eq (with-current-buffer cur-buffer major-mode)
'compilation-mode)) ;; i.e. not grep-mode
(let ((gdb-buffer (seq-find (lambda (b)
(string-match-p
"*gud-emacs*"
(buffer-name b)))
(buffer-list))))
(when (and (buffer-live-p gdb-buffer)
;; (get-buffer-window gdb-buffer t)
(get-buffer-process gdb-buffer)
(eq (process-status (get-buffer-process gdb-buffer)) 'run))
(with-current-buffer gdb-buffer
(goto-char (point-max))
(insert "r")
(comint-send-input)))))))
(with-eval-after-load 'grep
;; Use ‘cdr’ to add new only after the first that is "all" by default:
(push '("ch" . "*.[chm]") (cdr grep-files-aliases)) ; override existing alias with added *.m
(push '("clj" . "*.clj*") (cdr grep-files-aliases))
(push '("js" . "*.js *.jsx *.vue") (cdr grep-files-aliases))
(push '("ex" . "*.ex* *.eex *.erl") (cdr grep-files-aliases))
(push '("rb" . "*.rb *.erb *.rake *.haml *.yml *.yaml *.js *.coffee *.sass Gemfile Gemfile.lock")
(cdr grep-files-aliases)))
This is useful with customized grep-use-headings
with xref-like output:
(add-hook 'grep-mode-hook
(lambda ()
(require 'xref) ;; to be able to use `xref-file-header'
(face-spec-set 'grep-heading '((t (:inherit xref-file-header))))
(setq-local outline-minor-mode-use-buttons 'insert)
(setq-local compilation-line-face 'xref-line-number)
(outline-minor-mode +1)))
(add-to-list 'display-buffer-alist
`(,(rx bos "*"
(or (and "input/output of " (+ (any "A-za-z"))) ; gdb
"Edebug Results")
"*" eos)
(display-buffer-reuse-window
display-buffer-below-selected)
(dedicated . nil)
(window-height . 10)))
Increase throughput of process output:
(setq read-process-output-max (* 1024 1024))
(define-advice proced (:after (&rest _args) recenter-at-bottom)
(goto-char (point-max))
(recenter -1))
One of the most useful settings is using ansi
for terminal output
that highlights escape sequences emitted by many programs:
(setq-default
comint-terminfo-terminal "ansi")
;; (setenv "TERM" "dumb")
;; because it should be
;; (setenv "TERM" "emacs")
;; (setenv "TERM" "ansi")
;; Actually above commented out has no effect here, see ‘setenv’ in ‘after-init-hook’ below:
(add-hook 'after-init-hook
(lambda ()
;; https://lists.gnu.org/archive/html/emacs-devel/2019-12/msg00043.html
;; This needs to be run with timer since ‘normal-top-level’ overrides
;; (setenv "TERM" "dumb") at the end without running more hooks.
(run-at-time "15 seconds" nil 'setenv "TERM" "ansi")))
(setq-default
;; FIXME: ‘comint-history-isearch’ can't be customized by ‘setq-default’
;; because ‘custom-reevaluate-setting’ in ‘comint-history-isearch-end’
;; reverts this customized value:
;; comint-history-isearch 'dwim
comint-input-ignoredups t
comint-input-ring-size 65535
comint-move-point-for-output nil)
;; (setq-default comint-pager "cat | head -1000")
;; (setq-default comint-pager "cat | sed -e '1000a*** OUTPUT TRUNCATED ***' -e '1000q'")
(when delete-selection-mode
(put 'comint-delchar-or-maybe-eof 'delete-selection 'supersede))
(require 'package)
(setq package-enable-at-startup nil)
(unless (assoc "melpa" package-archives)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t))
(unless package--initialized
(package-initialize))
;; Try a built-in use-package
(unless (require 'use-package nil t)
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package t))
(eval-when-compile
(require 'use-package)))
(setq use-package-always-ensure t)
Pre-load some useful packages:
(require 'misc)
(require 'tempo)
(require 'wid-edit)
(require 'generic)
(require 'generic-x)
;; Use standard js-mode instead of javascript-generic-mode from generic-x.
(setq auto-mode-alist (delete (rassoc 'javascript-generic-mode auto-mode-alist)
auto-mode-alist))
;; (and (require 'ffap) (ffap-bindings))
;; Don't bind ffap keybindings anymore, because now ‘C-x C-f M-n’
;; gets the filename at point when ffap.el is loaded
(require 'ffap)
(use-package markchars
:custom
(markchars-face-confusable 'markchars-heavy)
(markchars-what '(markchars-confusables)))
(when (require 'ee-autoloads nil t)
(define-key global-map [f1] 'ee-info)
(define-key global-map [(super tab)] 'ee-buffers)
;; (define-key ctl-x-map [(control tab)] 'ee-buffers)
;; (define-key my-map [(control tab)] 'ee-buffers)
(with-eval-after-load 'ee-buffers
(define-key ee-buffers-keymap [(super tab)] 'ee-view-record-select-or-expansion-show-or-hide))
(define-key my-map "eb" 'ee-buffers)
(define-key my-map "ehc" 'ee-history-command)
(define-key my-map "ehe" 'ee-history-extended-command)
(define-key my-map "ehs" 'ee-history-shell-command)
(define-key my-map "ei" 'ee-imenu)
(define-key my-map "em" 'ee-marks)
(define-key my-map "eo" 'ee-outline)
(define-key my-map "epr" 'ee-programs)
(define-key my-map "eps" 'ee-ps)
(define-key my-map "et" 'ee-tags)
(define-key my-map "ewa" 'ee-windows-add)
(define-key my-map "eww" 'ee-windows)
;; (define-key global-map [(meta ?\xa7)] 'ee-windows-and-add-current)
;; (define-key global-map [(meta ?\x8a7)] 'ee-windows-and-add-current)
;; (define-key global-map [(meta ?`)] 'ee-windows-and-add-current)
;; (define-key global-map [(super ?`)] 'ee-windows-and-add-current)
(with-eval-after-load 'ee-windows
(define-key ee-windows-keymap [(meta ?\xa7)] 'ee-windows-select-and-delete-current)
;; (define-key ee-windows-keymap [(meta ?\x8a7)] 'ee-windows-select-and-delete-current)
(define-key ee-windows-keymap [(meta ?`)] 'ee-windows-select-and-delete-current)
(define-key ee-windows-keymap [(super ?`)] 'ee-windows-select-and-delete-current)
(define-key ee-windows-keymap [( ?\xa7)] 'ee-view-record-next)
;; (define-key ee-windows-keymap [(?\x8a7)] 'ee-view-record-next)
(define-key ee-windows-keymap [( ?`)] 'ee-view-record-next)
(define-key ee-windows-keymap [( ?\xbd)] 'ee-view-record-prev)
;; (define-key ee-windows-keymap [(?\x8bd)] 'ee-view-record-prev)
(define-key ee-windows-keymap [( ?~)] 'ee-view-record-prev)))
(setq-default
org-catch-invisible-edits 'smart
org-confirm-babel-evaluate nil
org-replace-disputed-keys t
org-special-ctrl-o nil
org-startup-folded nil
org-support-shift-select t)
(setq-default
org-src-ask-before-returning-to-edit-buffer nil
org-src-fontify-natively t
org-src-preserve-indentation t
org-src-tab-acts-natively t
org-src-window-setup 'split-window-below)
;; Unfortunately, Org window customization has no effect because
;; ‘display-buffer-alist’ is ignored in ‘org-no-popups’
;; until ‘org-switch-to-buffer-other-window’ is overridden below:
;; This was fixed in org-mode after removing display-buffer-alist from org-no-popups:
;; (defalias 'org-switch-to-buffer-other-window 'switch-to-buffer-other-window)
(add-to-list 'display-buffer-alist
'("\\`\\*\\(Org \\(Links\\|Select\\)\\|Org-Babel Error Output\\)\\*\\'"
display-buffer-below-selected
(window-height . fit-window-to-buffer)
(preserve-size . (nil . t))))
(add-hook 'org-mode-hook
(lambda ()
;; For (info "(org) Structure Templates")
(require 'org-tempo)
;; Fix paragraph navigation
(setq-local paragraph-start (default-value 'paragraph-start))
(setq-local paragraph-separate (default-value 'paragraph-separate))))
(global-set-key "\C-cl" 'org-store-link)
(global-set-key "\C-ca" 'org-agenda)
(global-set-key "\C-cb" 'org-iswitchb)
Fix keys broken by org:
(with-eval-after-load 'org-keys
;; Revert hijacked keys to their original bindings
(define-key org-mode-map (kbd "C-<tab>") 'tab-next)
(define-key org-mode-map (kbd "M-<left>") 'my-go-back)
(define-key org-mode-map (kbd "M-<right>") 'my-find-thing-at-point)
(define-key org-mode-map (kbd "C-M-t") 'transpose-sexps)
;; Undefine hijacked remappings
(define-key org-mode-map (vector 'remap 'backward-paragraph) nil)
(define-key org-mode-map (vector 'remap 'forward-paragraph) nil)
(define-key org-mode-map (vector 'remap 'fill-paragraph) nil)
;; Because I often accidentally type these keys
(define-key org-mode-map (kbd "C-c C-x C-s") nil)
(define-key org-mode-map (kbd "C-c C-x C-a") nil)
(when delete-selection-mode
(put 'org-self-insert-commandorg-return 'delete-selection t)
(put 'org-return 'delete-selection t)
(put 'org-open-line 'delete-selection t)))
Show the link under point immediately (from bug#42484):
(defun my-org-mode-post-command-hook ()
"Show POINT's \"help-echo\" information in the echo area.
This could be information about an org-link at POINT, or some other data."
(let* ((message-log-max) ; suppress output to *Messages* buffer
(mess (get-text-property (point) 'help-echo))
(mess (and mess (ignore-errors
(decode-coding-string (url-unhex-string mess) 'utf-8)))))
(when mess
(if (current-message)
(message "%s [%s]" (current-message) mess)
(message "%s" mess)))))
(add-hook 'org-mode-hook
(lambda ()
(add-hook 'post-command-hook 'my-org-mode-post-command-hook nil t)))
Fix keys broken by markdown-mode
:
(use-package markdown-mode
;; :custom-face
;; (markdown-blockquote-face ((t (:inherit italic))))
;; (markdown-code-face ((t (:background "grey95"))))
;; (markdown-table-face ((t (:inherit italic))))
;; ‘:custom-face’ saves faces to ‘custom-set-faces’,
;; so do the same explicitly below:
:config
(face-spec-set 'markdown-blockquote-face '((t (:inherit italic))))
(face-spec-set 'markdown-code-face '((t (:background "grey95"))))
(face-spec-set 'markdown-table-face '((t (:inherit italic))))
;; Revert hijacked keys to their original bindings
(unbind-key "C-c <left>" markdown-mode-map)
(unbind-key "C-c <right>" markdown-mode-map)
(define-key markdown-mode-map [remap backward-paragraph] nil)
(define-key markdown-mode-map [remap forward-paragraph] nil)
(add-hook 'markdown-mode-hook
(lambda ()
;; This overrides ‘markdown-line-break-face’:
;; (setq-local show-trailing-whitespace t)
;; Fix paragraph navigation
(setq-local paragraph-start (default-value 'paragraph-start))
(setq-local paragraph-separate (default-value 'paragraph-separate))
;; Allow multi-line comments
(setq-local comment-style 'multi-line ;; 'extra-line
comment-continue "")
;; Revert disabling spell-check in comments
(kill-local-variable 'flyspell-generic-check-word-predicate)))
(when delete-selection-mode
(put 'markdown-enter-key 'delete-selection t)))
Make tab names as short as possible (from the Gnus manual):
(add-to-list 'display-buffer-alist
'("\\`\\*Group\\*\\'" .
(display-buffer-in-tab
(tab-name . "G")
(tab-group . "[E]"))))
(add-to-list 'display-buffer-alist
'("\\`\\*Summary .*\\*\\'" .
(display-buffer-in-tab
(tab-name
. (lambda (buffer _alist)
(setq buffer (replace-regexp-in-string
(rx (or (and bos "*Summary "
(or (and "nnml:" (? (or "mail" "list") "."))
"nndoc+ephemeral:bug#"))
(and "*" eos)))
""
(buffer-name buffer)))
(format "G %s" buffer)))
(tab-group . "[E]"))))
Run Gnus:
(defun my-gnus ()
"Start a new Gnus, or locate the existing buffer *Group*."
(interactive)
(if (buffer-live-p (get-buffer "*Group*"))
(pop-to-buffer-same-window (get-buffer "*Group*"))
(gnus)))
(define-key my-map "g" 'my-gnus)
;; (define-key my-map "g" (lambda () (gnus 3)))
;; (define-key my-map "G" 'gnus-no-server)
(define-key my-map "G" (lambda () (interactive)
(gnus-no-server)
;; BUG? mail groups don't come automatically
(gnus-group-jump-to-group "nnml:mail.inbox")))
(define-key my-map "Q" 'smtpmail-send-queued-mail)
Gnus Group buffer:
(with-eval-after-load 'gnus
(setq gnus-group-line-format
"%M%m%S%p%P%4y:%B%(%-43,43g%) %3T %5t %2I %o\n")
;; (setq gnus-group-line-format
;; "%M%m%S%p%P%4y:%B%(%-30,30g%) %3T %5t %2I %o %s\n")
;; (setq gnus-group-line-format
;; "%M%S%5y: %(%-30,30g%) %9,9~(cut 4)d %5t %2I %2T %o %n %s\n")
(define-key gnus-group-mode-map (kbd "TAB") 'gnus-group-next-unread-group)
(define-key gnus-group-mode-map [(shift iso-lefttab)] 'gnus-group-prev-unread-group)
(setq gnus-message-archive-group
'((lambda (group)
(if (or (message-news-p)
(not group)
(and (stringp group)
(or (eq (length group) 0)
(string-match "^nnml:list\\." gnus-newsgroup-name)
(not (string-match "^nnml:" gnus-newsgroup-name)))))
"nnml:archive"
group)))))
Gnus Summary buffers:
;; Gnus breaks windows when reply is used in Summary, so switch to Article:
(defun my-gnus-reply ()
(interactive)
(gnus-summary-select-article-buffer)
(let ((buf (current-buffer)))
(gnus-summary-wide-reply-with-original nil)
(set-window-start (get-buffer-window buf) 1)))
(with-eval-after-load 'gnus-sum
(add-hook 'gnus-summary-exit-hook 'quit-window)
;; (setq gnus-summary-line-format ; TEST
;; "%U%R%z%I%(%[%1L: %1,1~(cut 1)L %4,4~(cut 4)o: %-20,20f%]%) %s\n")
;; (setq gnus-summary-line-format
;; "%U%R%z%I%(%[%4L: %4,4~(cut 4)o: %-20,20n%]%) %s\n")
;; (setq gnus-summary-line-format
;; "%U%R%z%I%(%[%4L: %4,4~(cut 4)o: %-20,20f%]%) %s\n")
;; Add 2-digit year:
(setq gnus-summary-line-format
"%U%R%z%I%(%[%4L: %6,6~(cut 2)o: %-20,20f%]%) %s\n")
(define-key gnus-summary-mode-map (kbd "TAB") 'gnus-summary-next-unread-article)
(define-key gnus-summary-mode-map [(shift iso-lefttab)]
'gnus-summary-prev-unread-article)
(define-key gnus-summary-mode-map [(meta right)] 'gnus-summary-show-thread)
(define-key gnus-summary-mode-map [(meta left)] 'gnus-summary-hide-thread)
(define-key gnus-summary-mode-map [delete] 'gnus-summary-delete-article)
;; (define-key gnus-summary-mode-map [f6] 'gnus-summary-move-article)
;; (define-key gnus-summary-mode-map "!" 'my-gnus-summary-tick-article-forward)
;; Commented out because sometimes I mistype "r" without the Shift key:
;; (define-key gnus-summary-mode-map "r" 'gnus-summary-reply-with-original)
(define-key gnus-summary-mode-map "r" 'my-gnus-reply)
(define-key gnus-summary-mode-map "R" 'my-gnus-reply)
;; Use standard keybinding instead of stupid ‘gnus-summary-show-thread’
(define-key gnus-summary-mode-map "\M-\C-s" 'isearch-forward-regexp)
;; Commented out to test multi-article Isearch
;; ;; "\M-s"
;; (define-key gnus-summary-mode-map "s" 'gnus-summary-search-article-forward)
;; ;; "\M-S"
;; (define-key gnus-summary-mode-map "M-s" 'gnus-summary-repeat-search-article-forward)
;; (define-key gnus-summary-mode-map "\C-l" 'my-recenter)
;; Disable dangerous key bindings
(define-key gnus-summary-mode-map [(meta ?g)] nil)
(define-key gnus-summary-mode-map "c" nil)
(define-key gnus-summary-mode-map "x" nil)
(define-key gnus-summary-mode-map "\C-x\C-s" nil)
(setq gnus-thread-sort-functions
'(gnus-thread-sort-by-number)
;;'(gnus-thread-sort-by-total-score (not gnus-thread-sort-by-number))
)
(add-hook 'gnus-group-mode-hook
(lambda ()
;; I don't need line and column numbers in the group buffer
(setq-local line-number-mode nil)
(setq-local column-number-mode nil)))
(add-hook 'gnus-summary-mode-hook
(lambda ()
;; I don't need line and column numbers in the summary buffer
(setq-local line-number-mode nil)
(setq-local column-number-mode nil)))
;; Zebra stripes for the summary buffer
;; (from http://www.emacswiki.org/cgi-bin/wiki/StripesMode)
;; (add-hook 'gnus-summary-mode-hook 'turn-on-stripes-mode)
)
Gnus Article buffers:
(with-eval-after-load 'gnus-art
;; Show attached patches inline:
(push "text/patch" mm-attachment-override-types)
(push "text/x-patch" mm-attachment-override-types)
(push "text/x-diff" mm-attachment-override-types)
(push "text/plain" mm-attachment-override-types)
(push "application/emacs-lisp" mm-attachment-override-types)
(setq gnus-unbuttonized-mime-types
'("multipart/signed" "multipart/mixed" "text/plain"
"text/x-org" "text/org"))
;; Set more human-readable time units:
(setq article-time-units
`((year . ,(* 60 60 24 365.25))
(month . ,(* 60 60 24 30))
(week . ,(* 60 60 24 7))
(day . ,(* 60 60 24))
(hour . ,(* 60 60))
(minute . 60)
(second . 1)))
;; I'm curious about what news readers do people use (Gnus or not ;)
(setq gnus-visible-headers
(append
(if (listp gnus-visible-headers)
gnus-visible-headers
(list gnus-visible-headers))
(list "^X-Debbugs-Cc")
(list (concat "^User-Agent:\\|^X-User-Agent:\\|"
"^X-Mailer:\\|^X-Newsreader:\\|^X-FTN-Tearline:\\|"
"^X-Http-User-Agent:"))))
;; Highlight the beginning of the bug report.
(setq gnus-emphasis-alist
(cons
'("\\(the precise symptoms of the bug\\)"
0 1 gnus-emphasis-underline)
gnus-emphasis-alist))
(add-hook 'gnus-article-mode-hook
(lambda ()
(visual-line-mode)
(setq bug-reference-url-format "http://debbugs.gnu.org/%s")
(bug-reference-mode 1))
t)
;; Put point after headers, so TAB will browse article buttons
(add-hook 'gnus-article-prepare-hook
(lambda ()
(let ((window (get-buffer-window gnus-article-buffer)))
(when window
(with-current-buffer (window-buffer window)
;; (forward-paragraph)
(set-window-point window (point))))))
t)
(define-key gnus-article-mode-map "R" 'gnus-summary-wide-reply-with-original)
;; RET scrolls the article one line at a time.
(define-key gnus-article-mode-map [return] 'my-gnus-article-press-or-scroll)
(define-key gnus-article-mode-map [(meta right)] 'my-gnus-article-press-or-scroll)
(define-key gnus-article-mode-map [(meta down)] 'widget-forward)
(define-key gnus-article-mode-map [(meta up)] 'widget-backward)
;; Disable dangerous key bindings
(define-key gnus-article-mode-map [(meta ?g)] nil))
(defun my-gnus-article-press-or-scroll ()
(interactive)
(if (or (not (get-char-property (point) 'button))
;; or point is on the bottom of the window while scrolling
(eq (point) (save-excursion (move-to-window-line -1) (point))))
(progn (scroll-up 1) (move-to-window-line -1) (beginning-of-line))
(if (eq (get-char-property (point) 'category) 'shr)
(shr-browse-url)
(gnus-article-press-button))))
;; TODO: move this command to gnus/gnus-ml.el and bind to ‘C-c C-n w’
(defun my-gnus-copy-link-gnu-lists (&optional _arg)
"Put the link to the article in the GNU archives into the kill ring.
Example:
\(browse-url (concat \"http://lists.gnu.org/archive/cgi-bin/namazu.cgi?idxname=\"
\"emacs-devel&query=\" (url-hexify-string (concat \"+message-id:\"
\"[email protected]\"))))"
(interactive "P")
(unless (derived-mode-p '(gnus-summary-mode))
(error "Not in a gnus group buffer"))
(let ((list-archive
(with-current-buffer gnus-original-article-buffer
(gnus-fetch-field "list-archive")))
(message-id
(with-current-buffer gnus-original-article-buffer
(replace-regexp-in-string
"^<\\(.*\\)>$" "\\1" (gnus-fetch-field "message-id"))))
(text-template "\
\(browse-url (concat \"http://lists.gnu.org/archive/cgi-bin/namazu.cgi?idxname=\"\n\
\"%s&query=\" (url-hexify-string (concat \"+message-id:\"\n\
\"%s\"))))")
(text))
(if (string-match "<http://lists\\.gnu\\.org/[^>]*/\\([^/>]+\\)>" list-archive)
(setq text (format text-template (match-string 1 list-archive) message-id))
(error "Not in a GNU mailing list"))
(kill-new text)
(message "%s" text)))
Actually, the above is not needed due to the supported url scheme like http://thread.gmane.org/<Message-ID>
(defun my-gnus-copy-link-gmane (&optional _arg)
"Put the link to the article on gmane.org into the kill ring.
Example:
\(browse-url (concat \"http://thread.gmane.org/\"
(url-hexify-string \"[email protected]\")))"
(interactive "P")
(unless (derived-mode-p '(gnus-summary-mode))
(error "Not in a gnus group buffer"))
(let ((message-id
(with-current-buffer gnus-original-article-buffer
(replace-regexp-in-string
"^<\\(.*\\)>$" "\\1" (gnus-fetch-field "message-id"))))
(text-template "(browse-url (concat \"http://thread.gmane.org/\"\
(url-hexify-string \"%s\")))")
(text))
(setq text (format text-template message-id))
(kill-new text)
(message "%s" text)))
Improve gnus-summary-tick-article-forward
to allow specifying tick character:
(defun my-gnus-summary-tick-article-forward (n &optional mark)
"Tick N articles forwards.
If N is negative, tick backwards instead.
The difference between N and the number of articles ticked is returned."
(interactive (list
(prefix-numeric-value current-prefix-arg)
(let ((mark (read-char "Tick char: " t)))
;; (if (memq mark (string-to-list " ?rREK$FXYCAFN*S.OQGM-+%="))
;; (error "Reserved mark"))
mark)))
(gnus-summary-mark-forward n (or mark gnus-ticked-mark)))
(require 'message) ;;(load-library "message")
;; Hunspell supports bilingual spell-checking of the mail message.
(add-hook 'message-send-hook 'ispell-message)
(add-hook 'message-mode-hook
(lambda ()
(auto-fill-mode 1)
;; Support search of ‘symbol’
(modify-syntax-entry ?` "' " message-mode-syntax-table)
(modify-syntax-entry ?' "' " message-mode-syntax-table)
;; Prevent premature sending when ‘C-c C-s’
;; is typed instead of ‘C-x C-s’
(define-key message-mode-map "\C-c\C-s" nil)
;; ‘S-RET’ like in ‘org-mode’:
(define-key message-mode-map [S-return]
(lambda ()
(interactive)
(insert "#+begin_src emacs-lisp\n#+end_src\n")))))
;; TODO: try to use (message-tab) in message mode
(when (require 'mm nil t)
(mm-parse-mailcaps)
(mm-parse-mimetypes))
(require 'server)
(unless (server-running-p)
(server-start))
(add-hook 'term-mode-hook
(lambda ()
;; (setq term-prompt-regexp "^[^#$%>\n]*[#$%>] *")
;; (setq-local mouse-yank-at-point t)
;; (make-local-variable 'transient-mark-mode)
(auto-fill-mode -1)
(setq tab-width 8)))
;; TRY:
(setq save-place-skip-check-regexp
(concat
save-place-skip-check-regexp
"\\|\\.\\(7z\\|apk\\|arc\\|jar\\|lzh\\|zip\\|zoo\\)$"
"\\|\\.t\\(ar\\.\\)?gz$"
"\\|\\.t\\(ar\\.bz2\\|bz\\)$"))
Save only such Dired buffers that are visible in windows or tabs:
(when (boundp 'desktop-buffers-not-to-save-function)
(setq desktop-buffers-not-to-save-function
(lambda (_filename bufname mode &rest _)
(or (not (memq mode '(dired-mode vc-dir-mode)))
(tab-bar-get-buffer-tab bufname t)))))
;; Configuration of ts modes:
(when (treesit-available-p)
(require 'treesit)
(when (treesit-ready-p 'bash t)
(add-to-list 'major-mode-remap-alist '(sh-mode . bash-ts-mode)))
(when (treesit-ready-p 'c t)
(add-to-list 'major-mode-remap-alist '(c-mode . c-ts-mode)))
;; (when (treesit-ready-p 'cpp t)
;; (add-to-list 'major-mode-remap-alist '(c++-mode . c++-ts-mode)))
;; (when (and (treesit-ready-p 'c t) (treesit-ready-p 'cpp t))
;; (add-to-list 'major-mode-remap-alist '(c-or-c++-mode . c-or-c++-ts-mode)))
(when (treesit-ready-p 'css t)
(add-to-list 'major-mode-remap-alist '(css-mode . css-ts-mode)))
(when (treesit-ready-p 'elixir t)
(add-to-list 'major-mode-remap-alist '(elixir-mode . elixir-ts-mode)))
(when (treesit-ready-p 'heex t)
(add-to-list 'major-mode-remap-alist '(heex-mode . heex-ts-mode)))
(when (treesit-ready-p 'html t)
(add-to-list 'major-mode-remap-alist '(html-mode . html-ts-mode))
(add-to-list 'major-mode-remap-alist '(mhtml-mode . html-ts-mode)))
(when (treesit-ready-p 'java t)
(add-to-list 'major-mode-remap-alist '(java-mode . java-ts-mode)))
(when (treesit-ready-p 'javascript t)
(add-to-list 'major-mode-remap-alist '(javascript-mode . js-ts-mode))
(add-to-list 'major-mode-remap-alist '(js-mode . js-ts-mode)))
(when (treesit-ready-p 'json t)
(add-to-list 'major-mode-remap-alist '(json-mode . json-ts-mode)))
(when (and (treesit-ready-p 'javascript t) (treesit-ready-p 'json t))
(add-to-list 'major-mode-remap-alist '(js-json-mode . js-ts-mode)))
(when (treesit-ready-p 'ruby t)
(add-to-list 'major-mode-remap-alist '(ruby-mode . ruby-ts-mode)))
(when (treesit-ready-p 'toml t)
(add-to-list 'major-mode-remap-alist '(conf-toml-mode . toml-ts-mode)))
(when (treesit-ready-p 'yaml t)
(if (require 'yaml-mode nil t)
(add-to-list 'major-mode-remap-alist '(yaml-mode . yaml-ts-mode))
(require 'yaml-ts-mode)))
(when (treesit-ready-p 'dockerfile t)
(require 'dockerfile-ts-mode)))
;; Customization of ts modes:
(when (treesit-available-p)
(when (treesit-ready-p 'javascript t)
(add-hook 'js-ts-mode-hook
(lambda ()
(treesit-font-lock-recompute-features '(property)))))
(when (treesit-ready-p 'yaml t)
(add-hook 'yaml-ts-mode-hook
(lambda ()
(auto-fill-mode -1)
;; Alternative config:
;; (setq-local treesit-font-lock-level 4)
;; (treesit-font-lock-recompute-features)
(treesit-font-lock-recompute-features '() '(string))))))
Emacs 28 provides the built-in package dictionary
, here is its customization:
(defun dictionary-search-no-prompt ()
"Search immediately with the default word without asking."
(interactive)
(require 'dictionary)
(let ((word (or (dictionary-search-default)
;; Try to get a copied word from the clipboard
(and (ignore-errors (current-kill 0))
(string-match-p "\\`[A-Za-z]+\n?\\'" (current-kill 0))
(current-kill 0)))))
(when word
(add-to-history 'dictionary-word-history word))
(dictionary-search word)))
(define-key my-map "wd" 'dictionary-search)
(define-key my-map "ww" 'dictionary-search-no-prompt)
(global-set-key "\M-s\M-d" 'dictionary-search-no-prompt)
(add-hook 'dictionary-mode-hook
(lambda ()
(setq-local outline-regexp "From")
(setq-local outline-level (lambda () 1))
(define-key dictionary-mode-map [(meta left)] 'dictionary-previous)
(define-key dictionary-mode-map [(meta right)] 'dictionary-search-no-prompt)))
(add-hook 'dictionary-post-buffer-hook
(lambda ()
(outline-minor-mode +1)
;; Align screen to the bottom of the output buffer
(goto-char (point-max))
(recenter -2 t)))
Alternative Keyboard Feature implemented in bug#9751 and posted to http://ru-emacs.livejournal.com/82428.html This is now available from https://github.com/a13/reverse-im.el
(defun reverse-input-method (input-method)
"Build the reverse mapping of single letters from INPUT-METHOD."
(interactive
(list (read-input-method-name "Use input method (default current): ")))
(if (and input-method (symbolp input-method))
(setq input-method (symbol-name input-method)))
(let ((current current-input-method)
(modifiers '(nil (control) (meta) (control meta))))
(when input-method
(activate-input-method input-method))
(when (and current-input-method (bound-and-true-p quail-keyboard-layout))
(dolist (map (cdr (quail-map)))
(let* ((to (car map))
(from (quail-get-translation
(cadr map) (char-to-string to) 1)))
(when (and (characterp from) (characterp to))
(dolist (mod modifiers)
(define-key local-function-key-map
(vector (append mod (list from)))
(vector (append mod (list to)))))))))
(when input-method
(activate-input-method current))))
;; (reverse-input-method "cyrillic-jcuken")
Delete codings like utf-*-with-signature
(they hide BOMs)
to allow to always display the BOM (Byte-order mark signature)
to be able to remove it without the need to visit files literally
or with C-x RET c utf-8 RET C-x C-f
.
(setq auto-coding-regexp-alist
(delete (rassoc 'utf-16be-with-signature auto-coding-regexp-alist)
(delete (rassoc 'utf-16le-with-signature auto-coding-regexp-alist)
(delete (rassoc 'utf-8-with-signature auto-coding-regexp-alist)
auto-coding-regexp-alist))))
Use buffer’s coding for the output of base64-decode
(bug#38587)
(can be overridden by C-x RET c
):
(define-advice base64-decode-region (:after (beg end &rest _) coding)
(decode-coding-region
beg (min end (point-max))
(or coding-system-for-write
buffer-file-coding-system)))
When some package lacks necessary features that I need, and I’m not sure if such functionality would be generally useful to be added to that package, in this case I implement such a feature in my init file that later could be adapted into a patch to submit for the package to improve.
The built-in package battery
doesn’t provide an additional timer that
periodically would check if the laptop is not on AC power line, then
display battery status on the mode line.
(defvar my-battery-timer nil)
(when (and (require 'battery nil t)
(bound-and-true-p battery-status-function)
(functionp battery-status-function))
(when (and (boundp 'my-battery-timer) (timerp my-battery-timer))
(cancel-timer my-battery-timer))
(setq my-battery-timer
;; Check periodically if went off-line and
;; discharging battery needs to be displayed
(run-at-time t 600 (lambda ()
(display-battery-mode
(if (member (cdr (assoc ?L (funcall battery-status-function)))
'("AC" "on-line" "N/A"))
0 1))))))
Implementation of right-word
/ left-word
is broken - it doesn’t take
into account mixed content when words of one script are intermingled with
words of another script. This advice fixes this:
(defun right-script-p ()
(or (eq (current-bidi-paragraph-direction) 'right-to-left)
(unless (bobp) (memq (get-char-code-property (char-before) 'bidi-class) '(R NSM)))
(unless (eobp) (memq (get-char-code-property (char-after) 'bidi-class) '(R NSM)))))
(define-advice right-word (:around (ofun &rest args) mixed-content)
(if (right-script-p)
(apply #'backward-word args)
(apply ofun args)))
(define-advice left-word (:around (ofun &rest args) mixed-content)
(if (right-script-p)
(apply #'forward-word args)
(apply ofun args)))
There was no symmetry for sexp like in right-char
/ left-char
and right-word
/ left-word
(bug#36923)
(defun right-sexp (&optional arg)
"Move across one balanced expression (sexp) to the right.
Depending on the bidirectional context, this may move either forward
or backward in the buffer. See more at ‘forward-sexp’."
(interactive "^p")
(if (right-script-p)
(backward-sexp arg t)
(forward-sexp arg t)))
(defun left-sexp (&optional arg)
"Move across one balanced expression (sexp) to the left.
Depending on the bidirectional context, this may move either backward
or forward in the buffer. See more at ‘backward-sexp’."
(interactive "^p")
(if (right-script-p)
(forward-sexp arg t)
(backward-sexp arg t)))
(define-key global-map [(control left)] 'left-sexp)
(define-key global-map [(control right)] 'right-sexp)
(define-key global-map [(control kp-left)] 'left-sexp)
(define-key global-map [(control kp-right)] 'right-sexp)
(define-key global-map [(control meta left)] 'left-word)
(define-key global-map [(control meta right)] 'right-word)
(define-key global-map [(control meta up)] 'backward-paragraph)
(define-key global-map [(control meta down)] 'forward-paragraph)
Fix controversial keybindings added in Emacs 23:
(define-key global-map [home] 'beginning-of-visual-line)
(define-key global-map [end] 'end-of-visual-line)
(define-key global-map [up] 'previous-line)
(define-key global-map [down] 'next-line)
Fix compare-windows
:
(define-key global-map [(control ?=)] 'compare-windows)
;; alternative: (lambda () (interactive) (compare-windows t))
;; I often mistype ‘compare-windows’ as ‘comapre-windows’, allow both:
(defalias 'comapre-windows 'compare-windows)
Set isearch-scroll
on some commands to be able to use them without exiting isearch-mode
:
(put 'narrow-to-defun 'isearch-scroll t)
(put 'widen 'isearch-scroll t)
(put 'toggle-truncate-lines 'isearch-scroll t)
(put 'comint-show-output 'isearch-scroll t) ;; bound to ‘C-M-l’
;; Mostly for ‘C-s M-s o’
(put 'windmove-display-up 'isearch-scroll t)
(put 'windmove-display-down 'isearch-scroll t)
(put 'windmove-display-left 'isearch-scroll t)
(put 'windmove-display-right 'isearch-scroll t)
Often after C-M-left
(left-word
) that puts point at the beginning of
the current word, I want to start searching with yanking this word to the
search string by typing C-s C-w
, but sometimes I don’t release the Ctrl
key
before typing C-s
, so actually C-M-s
is typed that inadvertently activates
the regexp search and adds the search string into the wrong history ring
regexp-search-ring
. So here is a fix for such a situation:
(define-advice isearch-mode (:around (ofun forward &optional regexp &rest args) regexp-after-left-word)
(when (and regexp (eq last-command 'left-word))
(setq regexp nil))
(apply ofun forward regexp args))
With the M-0
prefix argument, next-error
will go to the next buffer and
quit the previous buffer:
(setq-default next-error-found-function 'next-error-quit-window)
(defvar my-next-error-prev-buffer nil)
(defun my-next-error ()
;; Get rid of file buffers visited during going through results.
(when (and my-next-error-prev-buffer
(not (eq my-next-error-prev-buffer (current-buffer)))
;; buffer not edited
(memq buffer-undo-list '(nil t))
;; only on consequent keystrokes
(memq this-command '(next-error previous-error))
(memq (with-current-buffer next-error-last-buffer major-mode)
'(grep-mode xref--xref-buffer-mode)))
;; TODO: preserve existing file buffers, and positions in all file buffers
;; (goto-char (point-min)) ...
(kill-buffer my-next-error-prev-buffer))
(setq my-next-error-prev-buffer (current-buffer)))
(add-hook 'next-error-hook 'my-next-error)
;; See bug#20489: 25.0.50; next-error-find-buffer chooses non-current buffer without good reason
;; See bug#28864: 25.3.50; next-error-no-select does select
;; (setq next-error-find-buffer-function
;; (lambda (&optional avoid-current extra-test-inclusive extra-test-exclusive)
;; (window-parameter nil 'next-error-buffer)))
;; (add-hook 'next-error-hook
;; (lambda ()
;; (set-window-parameter
;; nil 'next-error-buffer next-error-last-buffer)))
(add-function :override next-error-find-buffer-function
#'next-error-buffer-on-selected-frame)
(add-function :after-until next-error-find-buffer-function
#'next-error-buffer-unnavigated-current)
Show hidden outlines like isearch does:
(add-hook 'next-error-hook
(lambda ()
(when (and outline-minor-mode (invisible-p (point)))
(outline-show-entry))))
;; This supposes ‘display-buffer-alist’ to be customized to contain:
;; '((display-buffer-to-xref-p display-buffer-maybe-below-selected) ...)
(defun display-buffer-to-xref-p (buffer-name _action)
(and (stringp buffer-name)
(string-match-p "\\`\\*\\(xref\\)\\*\\(\\|<[0-9]+>\\)\\'"
buffer-name)
(memq this-command '(xref-find-definitions))))
(add-to-list 'display-buffer-alist
'(display-buffer-to-xref-p
;; TODO:
;; display-buffer-maybe-below-selected
display-buffer-in-direction
(direction . below)
(window-height . fit-window-to-buffer)))
;; UNUSED
(defun display-buffer-from-xref-p (_buffer-name _action)
;; TODO: check xref--original-window xref--original-window-intent?
(string-match-p "\\`\\*\\(xref\\)\\*\\(\\|<[0-9]+>\\)\\'"
(buffer-name (window-buffer))))
;; Use this keybinding only in buffers created by xref-find-definitions,
;; but not by e.g. project-find-regexp
(defun my-xref-goto-xref ()
(interactive)
(if (memq xref--original-command '(xref-find-definitions))
;; (call-interactively 'xref-quit-and-goto-xref)
(call-interactively 'xref-goto-xref)
(setq xref--original-window nil)
(call-interactively 'xref-goto-xref)))
(with-eval-after-load 'xref
(defvar xref--original-command nil)
(define-advice xref-find-definitions (:after (&rest _args) from-xref)
(with-current-buffer (window-buffer)
(setq-local xref--original-command 'xref-find-definitions)))
(define-key xref--button-map [(control ?m)] 'my-xref-goto-xref)
(define-key xref--xref-buffer-mode-map (kbd "C-j") 'xref-quit-and-goto-xref)
;; Don't quit the xref window by TAB
;; (define-key xref--xref-buffer-mode-map (kbd "TAB") nil)
;; Like compilation-next-error and compilation-previous-error:
(define-key xref--xref-buffer-mode-map (kbd "TAB") 'xref-next-line-no-show)
(define-key xref--xref-buffer-mode-map (kbd "<backtab>") 'xref-prev-line-no-show)
;; (add-hook 'xref--xref-buffer-mode-hook 'rename-uniquely)
)
(with-eval-after-load 'project
;; Instead of numbers append the regexp to the xref buffer name:
(define-advice project-find-regexp (:around (ofun &rest args) unique)
(require 'xref)
(let ((xref-buffer-name
(generate-new-buffer-name
(format "%s<%s>" xref-buffer-name (car args)))))
(apply ofun args))))
It’s easier to type C-5 C-h C-i
with control key pressed for all keys:
(define-key global-map "\C-h\C-i" 'info)
Allow set-variable to set internal variables, not only customizable ones:
(define-advice set-variable (:around (ofun &rest args) override-custom-variable)
(interactive (lambda (spec)
(cl-letf (((symbol-function 'custom-variable-p)
(lambda (v)
(and (symbolp v) (boundp v)))))
(advice-eval-interactive-spec spec))))
(cl-letf (((symbol-function 'custom-variable-p) #'always))
(apply ofun args)))
Don’t use delay after displaying useless error messages:
(define-advice ispell-parse-output (:around (ofun &rest args) sit-for)
(cl-letf (((symbol-function 'sit-for) #'ignore))
(apply ofun args)))
(define-advice isearch--momentary-message (:around (ofun string &rest args) sit-for)
(if (string-prefix-p "Too many words" string)
(cl-letf (((symbol-function 'sit-for) #'ignore))
(apply ofun string args))
(apply ofun string args)))
(with-eval-after-load 'follow
(define-key follow-mode-map [remap recenter-top-bottom] #'follow-recenter)
(define-key follow-mode-map [remap scroll-up-command] #'follow-scroll-up)
(define-key follow-mode-map [remap scroll-down-command] #'follow-scroll-down)
(define-key follow-mode-map [remap View-scroll-page-forward] #'follow-scroll-up)
(define-key follow-mode-map [remap View-scroll-page-forward-set-page-size] #'follow-scroll-up)
(define-key follow-mode-map [remap View-scroll-page-backward] #'follow-scroll-down))
(add-hook 'messages-buffer-mode-hook
(lambda ()
(setq buffer-read-only nil)
(fundamental-mode)))
Make mark buffer-and-window-local (mark-active-window). Posted to https://lists.gnu.org/archive/html/emacs-devel/2018-09/msg00716.html
(defvar-local mark-active-window nil)
;; (add-hook 'activate-mark-hook (lambda () (setq mark-active-window (selected-window))))
(define-advice activate-mark (:after (&rest _args) mark-active-window)
(setq mark-active-window (selected-window)))
;; Can't use deactivate-mark-hook because when clicking mouse in another window
;; with the same buffer it calls both activate-mark and deactivate-mark,
;; but deactivate-mark checks if the region is active (region-active-p),
;; and doesn't advance further because mark-active was set to nil in the redisplay
;; hook below. OTOH, the advice is used unconditionally.
;; (add-hook 'deactivate-mark-hook (lambda () (setq mark-active-window nil)))
(define-advice deactivate-mark (:after (&rest _args) mark-active-window)
(setq mark-active-window nil))
(defun redisplay--update-mark-active-window (window)
(when mark-active-window
(setq mark-active (eq mark-active-window window))))
;; Problem: when compiled without optimization CFLAGS='-O0' then
;; quick region selection experiences lags that results in wrong selection.
;; Another problem is that in ‘follow-mode’ ‘set-mark-command’ messes windows.
;; (add-hook 'pre-redisplay-functions #'redisplay--update-mark-active-window)
By default, outline-regexp
includes the page delimiter ^L
.
When a file contains a lot of page delimiters, they are displayed
as empty lines that take too much screen space for nothing (bug#51016).
So remove page delimiters from the default value:
(with-eval-after-load 'outline
(setq-default outline-regexp (string-replace "\^L" "" (default-value 'outline-regexp))))
Workaround a bug in NEWS files where Symbol search fails.
Use solution like in change-log-mode-syntax-table
.
See more at bug#31231.
(add-hook
'outline-mode-hook
(lambda ()
(when (string-match-p "^NEWS" (buffer-name))
(let ((table (make-syntax-table)))
(modify-syntax-entry ?` "' " table)
(modify-syntax-entry ?' "' " table)
(set-syntax-table table)))))
Outline faces inherit from font-lock faces that is a bad thing to do because often it’s needed to customize font-lock faces to better highlight syntactic constructs in programming languages. But such face customization also changes outline faces that breaks distinguishability of outline faces. So need to restore original distinctive colors in outline faces:
(with-eval-after-load 'outline
(set-face-foreground 'outline-1 "Blue1")
(set-face-foreground 'outline-2 "VioletRed4") ;; "sienna"
(set-face-foreground 'outline-3 "Purple")
(set-face-foreground 'outline-4 "Firebrick")
(set-face-foreground 'outline-5 "ForestGreen")
(set-face-foreground 'outline-6 "dark cyan")
(set-face-foreground 'outline-7 "dark slate blue")
(set-face-foreground 'outline-8 "sienna") ;; "VioletRed4"
;; Header colors like in Wikipedia
(mapc (lambda (f) (set-face-background f "#ddeeff")) outline-font-lock-faces)
(mapc (lambda (f) (set-face-extend f t)) outline-font-lock-faces))
(with-eval-after-load 'org-faces
;; Header colors like in Wikipedia
(mapc (lambda (f) (set-face-background f "#ddeeff")) org-level-faces)
;; TODO: :extend should be fixed in next version of Org, so remove later:
(mapc (lambda (f) (set-face-extend f t)) org-level-faces))
Allow using shifted keys in org with isearch when isearch-yank-on-move
is customized to the value shift
:
(with-eval-after-load 'org-keys
(dolist (org-shiftcommand
'(org-shiftmetaleft org-shiftmetaright
org-shiftmetaup org-shiftmetadown
org-shiftup org-shiftdown
org-shiftright org-shiftleft
org-shiftcontrolright org-shiftcontrolleft
org-shiftcontrolup org-shiftcontroldown))
(put org-shiftcommand 'isearch-move 'enabled)))
(with-eval-after-load 'org
(defun org-call-for-shift-select (cmd)
(let ((this-command-keys-shift-translated (not isearch-mode)))
(call-interactively cmd))))
Try to emulate the S-RET
key in code blocks in org-mode. See more at
https://lists.gnu.org/archive/html/emacs-orgmode/2020-11/msg00247.html
(with-eval-after-load 'org-keys
;; But this breaks ‘S-RET’ in tables.
(define-key org-mode-map (kbd "S-<return>") 'org-babel-demarcate-block))
Why wouldn’t org-mode always redisplay inserted inline images automatically after executing SRC block?
(with-eval-after-load 'org
(add-hook 'org-babel-after-execute-hook 'org-redisplay-inline-images))
Strip any ANSI escape sequences from the output:
(defun my-ansi-color-apply-on-org-region ()
(save-excursion
(let ((beg (org-babel-where-is-src-block-result)))
(when beg
(goto-char beg)
(ansi-color-apply-on-region
beg (org-element-property :end (org-element-at-point)) nil)))))
(with-eval-after-load 'org
(add-hook 'org-babel-after-execute-hook 'my-ansi-color-apply-on-org-region))
Make the default mode-line faces look like active/inactive tabs on the tab bar:
(set-face-attribute 'mode-line-active nil :box '(:line-width -1 :style released-button))
(set-face-attribute 'mode-line-inactive nil :box '(:line-width -1 :style released-button))
(set-face-attribute 'mode-line-active nil :background "grey85")
(set-face-attribute 'mode-line-inactive nil :background "grey75")
(set-face-attribute 'mode-line-active nil :foreground "black")
(set-face-attribute 'mode-line-inactive nil :foreground "black")
(set-face-attribute 'mode-line-inactive nil :weight 'normal)
;; Variable pitch is better on the mode line
;; (set-face-attribute 'mode-line-active nil :inherit '(mode-line variable-pitch))
;; (set-face-attribute 'mode-line-inactive nil :inherit '(mode-line variable-pitch))
(set-face-attribute 'mode-line nil :inherit '(variable-pitch))
For dir-local repository settings:
(put 'dired-vc-rename-file 'safe-local-variable 'booleanp)
Fix nobreak-char-display
in Dired file sizes (see bug#44236):
(when (fboundp 'markchars-mode)
(add-hook 'dired-mode-hook
(lambda ()
(setq-local nobreak-char-display nil)
(setq-local markchars-what
(append (default-value 'markchars-what)
'(markchars-nobreak-space
markchars-nobreak-hyphen)))
(markchars-mode 1))))
Typing =
in a vc-dir buffer displays a vc-diff buffer where files are
sorted by different order than files are sorted in the vc-dir buffer.
For example, there is a need to commit only part of changes. To decide what files should be marked in the vc-dir buffer, a convenient way is to browse diffs in the vc-diff buffer one by one and mark corresponding files in the vc-dir buffer. But since these file lists are in different order, the task becomes a major hassle because vc-diff doesn’t distinguish between files and directories while sorting them by name, but vc-dir puts directories at the end after files.
This can be fixed by creating a git-specific orderfile
in vc-dir
and using it in vc-git-diff
:
(defvar vc-git-orderfile nil)
(define-advice vc-dir-deduce-fileset (:after (&rest _) create-orderfile)
(when (and vc-ewoc (eq this-command 'vc-diff))
(let* ((tmpfile (make-temp-file "vc-git-orderfile-"))
files)
(ewoc-map (lambda (filearg)
(push (vc-dir-fileinfo->name filearg) files))
vc-ewoc)
(with-temp-file tmpfile
(mapcar (lambda (file) (insert file "\n")) (nreverse files)))
(setq vc-git-orderfile tmpfile))))
(define-advice vc-git-diff (:around (ofun &rest args) use-orderfile)
(if (and vc-git-orderfile (file-exists-p vc-git-orderfile))
(let ((vc-git-diff-switches
(append (list (format "-O%s" vc-git-orderfile))
vc-git-diff-switches)))
(unwind-protect
(apply ofun args)
(delete-file vc-git-orderfile)
(setq vc-git-orderfile nil)))
(apply ofun args)))
When diff-backup
is accidentally used on a file registered in
a version-control system then fall back to the vc-diff
command.
And vice versa: when C-x v =
(vc-diff
) is accidentally typed on
a non-vc file then fall back to diff-backup
. (Bug#41779)
(define-advice diff-backup (:around (ofun file &optional switches) fallback-to-vc-diff)
(condition-case err
(funcall ofun file switches)
(error
;; Fall back to vc-diff
(if (vc-backend file)
(let ((vc-diff-switches switches))
(require 'vc)
(vc-diff-internal
t (list (vc-backend file) (list file)) nil nil t))
(error (cadr err))))))
(define-advice vc-diff (:around (ofun &rest args) fallback-to-diff-backup)
(if (vc-deduce-backend)
(apply ofun args)
;; Fall back to non-vc diff-backup
(let ((file (if (derived-mode-p '(dired-mode))
(dired-get-filename)
buffer-file-name)))
(if file
(diff-backup file vc-diff-switches)
(error "File is not under version control")))))
When a prompt of some commands such as rgrep
or vc-print-log
asks for
a directory name, allow M-n
to access the most recently used project
directories saved in ~/.emacs.d/projects
.
(define-advice read-directory-name (:around (ofun &rest args) project-defaults)
(minibuffer-with-setup-hook
(lambda ()
(when (featurep 'project)
(setq-local minibuffer-default-add-function
(lambda () (project-known-project-roots)))))
(let ((ret (apply ofun args)))
(when (featurep 'project)
;; Update project list with selected project dir
(let ((default-directory ret))
(project-current)))
ret)))
(define-advice vc-dir (:after (dir &optional _backend) add-project)
(when (featurep 'project)
;; Add current vc project dir to project list
(let ((default-directory dir))
(project-current))))
Add the default project command bound to the RET
key,
so either C-x p RET
or C-x p p RET
will open Dired:
(with-eval-after-load 'project
(define-key project-prefix-map [return] 'project-dired))
I often mistype ‘C-x p’ as ‘C-x C-p’:
(with-eval-after-load 'project
(define-key ctl-x-map "\C-p" project-prefix-map))
Allow C-x C-f M-n
to access the most recently used files.
(define-advice find-file (:around (ofun &rest args) recentf-list)
(interactive (lambda (spec)
(minibuffer-with-setup-hook
(lambda ()
(when (featurep 'recentf)
(setq-local minibuffer-default-add-function
(lambda () recentf-list))))
(advice-eval-interactive-spec spec))))
(apply ofun args))
Blinking cursors are distracting - turn blink OFF:
here (*) (*) (*)
indicates how the cursor blinks.
(and (fboundp 'blink-cursor-mode) (blink-cursor-mode (- (*) (*) (*))))
Display the time of the Emacs initialization:
(when (fboundp 'emacs-init-time)
(add-hook 'after-init-hook (lambda () (message "%s" (emacs-init-time))) t))
Copyright (C) 1989-2024 Juri Linkov <[email protected]>
This file is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version.
This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with GNU Emacs; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
time-stamp
in these local variables specifies the format
of the property DATE
at the beginning of this file, so that
on saving it is updated automatically.
- Local Variables:
- eval: (add-hook ‘before-save-hook ‘time-stamp nil t)
- time-stamp-start: “DATE: ”
- time-stamp-format: “%:y-%02m-%02d”
- time-stamp-end: “$”
- time-stamp-line-limit: 15
- End: