link0ff / emacs-init

My Emacs init configuration

Home Page:https://gitlab.com/link0ff/emacs-init

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

.emacs — Emacs init file

“/Show me your init file and I’ll tell you who you are./” – old proverb slightly modified

Table of Content

Literate programming

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:

  1. Weaving: it is rendered to HTML while displayed either on GitLab/GitHub or when exported to HTML;
  2. 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.

Construction Kit

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.

Space-time optimization

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.

Reposition windows

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."
  (if (derived-mode-p 'fundamental-mode 'dired-mode 'Man-mode 'conf-mode 'mail-archives-mode 'markdown-mode)
      (recenter-top)
    (reposition-window)))

(add-hook 'next-error-hook 'my-reposition-window)
(add-hook 'find-function-after-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:

org

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

outline

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

diff

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

Recenter windows

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

Space optimization

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.

Remove unused display elements

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 with tab-bar and tab-lines

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 I prefer to show all buffer names in the tab name:

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

Full-screen Emacs desktop

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.

(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
         '(
           ;; 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)

Show the current buffer in 3 or 4 columns

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

Org-like (S-)TAB heading cycle in outline-regexp capable buffers

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

(define-key outline-navigation-repeat-map (kbd "<down>")
            #'outline-next-visible-heading)
(define-key outline-navigation-repeat-map (kbd "<up>")
            #'outline-previous-visible-heading)
(defun my-outline-minor-mode ()
  ;; Enable in modes with reasonable ‘outline-regexp’:
  (when (derived-mode-p 'emacs-lisp-mode 'ruby-mode 'ruby-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)

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

Time optimization mostly means less clanking on keyboard to save time for more productive activities.

Window navigation

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)

Display buffers in the most suitable window

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 window where all previous hits were visited:

(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))
         (or (derived-mode-p 'compilation-mode)
             (derived-mode-p '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 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))

Shorter answers

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)

Don’t spend time answering useless questions

Enable all disabled commands such as narrow-to-region, etc.

(setq disabled-command-function nil)

Using the ESC key as a cancel key

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

Dedicated keymap C-z my-map

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

Insert-pair keys

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:

KeyInsert 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’

List structural editing

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)

Efficient navigation in different modes

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, then M-right opens a new Dired buffer, otherwise visits a file under point. In other modes M-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. When M-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 of M-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:

help

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

info

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

man

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

view

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

diff

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

dired

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

archive/tar

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

comint

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

image-mode

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

doc-view

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

Better keybindings

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)

Repeat mode

(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

(context-menu-mode +1)
(add-hook 'context-menu-functions 'dictionary-context-menu 15)

Useful features

Copy/Paste

Decode URL copied from web browser

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

Copy text at point without activating the region

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

Search/Replace

Enable new isearch features

The following features are new in Emacs 27:

  1. Show match numbers in the search prompt;
  2. Use shift key to pull text from the buffer to the search string;
  3. 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))

Smoother isearch navigation

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

Useful isearch keys

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)

isearch-lazy-hints

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)

isearch-yank-until-char alike

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)

isearch-diff-hunk

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

Better isearch exiting

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)

char-fold settings

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)

text-property-search

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

occur

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

replace

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

Minibuffer and Completions

See https://lists.gnu.org/archive/html/emacs-devel/2014-12/msg00299.html

(define-key minibuffer-local-map [S-return] 'newline)

history/defaults completion

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)

deletion of history items

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)

completion styles

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

completion-preview-mode

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

Evaluable bookmarks

;; 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
                                   "^[\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)
      (find-file 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)))))))))

Useful settings

Cursor settings

Use box cursor for overwrite-mode, and red cursor for quail active input:

(defun my-change-cursor ()
  "Change cursor color and type depending on insertion mode and input method."
  (set-cursor-color
   (cond (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)

Display settings

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

Whitespace settings

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

Spelling features

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

(add-hook 'text-mode-hook       'flyspell-mode)
(add-hook 'change-log-mode-hook 'flyspell-mode)
(add-hook 'prog-mode-hook       'flyspell-prog-mode)

Other settings

Non-customizable variables:

(setq gc-cons-percentage 0.3) ; seems now it's customizable
(setq print-gensym t)
(setq print-circle t)

Major modes

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

diff

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

ediff

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

vc

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

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

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

dired

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

;; Uses editor/viewer info from /usr/bin/run-mailcap
(defun my-dired-run-find-file ()
  "My view file for dired."
  (interactive)
  (let* ((file (dired-get-filename)))
    (cond
     ;; OBSOLETED mm-mime-info by mailcap-mime-info
     ;; ((let* ((command
     ;;          (and (functionp 'mm-mime-info)
     ;;               (mm-mime-info
     ;;                (mm-extension-to-mime (file-name-extension file))))))
     ;;    (if (and command (stringp command))
     ;;        ;; always return ‘t’ for ‘cond’
     ;;        (or (ignore (async-shell-command (concat (format command file) "&")))
     ;;            t))))
     ;; ((string-match "\\.html?$" file) (w3-open-local file))
     ;; OBSOLETED by ‘browse-url-of-dired-file’ bound to ‘W’
     ;; ((string-match "\\.html?$" file)
     ;;  (cond
     ;;   ((fboundp 'browse-url)
     ;;    (browse-url file))))
     ;; OBSOLETED by ‘dired-do-load’ bound to ‘L’.
     ;; ((string-match "\\.elc?$" file)
     ;;  (load-file file))
     ;; OBSOLETED by ‘dired-do-info’ bound to ‘I’.
     ;; ((string-match "\\.info?$" file)
     ;;  (info file))
     (;; (or (string-match "\\.jpe?g$" file)
      ;;           (string-match "\\.gif$" file)
      ;;           (string-match "\\.pdf$" file))
      (let* ((file-list (list (dired-get-filename)))
             (command (shell-command-guess file-list))
             (async-shell-command-display-buffer nil))
        (if (listp command)
            (setq command (car command)))
        (if command
            (async-shell-command
             (dired-shell-stuff-it command file-list nil 0)))))
     (t
      (message "%s" file)))))

(define-key dired-mode-map [(control enter)]  'my-dired-run-find-file)
(define-key dired-mode-map [(control return)] 'my-dired-run-find-file)

;; 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)
              )
            ;; Use old "\M-o" instead of new "\C-x\M-o".
            (define-key dired-mode-map "\M-o" 'dired-omit-mode)))
(keymap-set dired-mode-map "X" #'dired-do-open)

wdired

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

locate

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

shell

(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)))
(with-eval-after-load 'shell
  ;; This affects also “*Async Shell Command*” 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"
    (lambda (&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))))))

;; S-RET switches to the *Shell Command Output* buffer
;; instead of displaying 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.
(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)

shell-log

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-font-lock-keywords
  ;; ‘shell-prompt-pattern’ can't be used: it finds too many false matches
  `((,(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))
     (1 'comint-highlight-prompt)
     (2 'comint-highlight-input)))
  "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."
  (let ((mode (if (save-excursion
                    (goto-char (point-min))
                    (re-search-forward
                     (rx (or "./configure" (and bol "Compilation")))
                     nil t))
                  'compilation-mode
                (require 'shell)
                'shell-log-mode)))
    (call-interactively mode)))

(add-to-list 'auto-mode-alist '("\\.log\\'" . shell-log-or-compilation-mode))

compile/grep

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)

(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" 'my-project-compile) ; “m” has mnemonics “make”

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

debugger

(add-to-list 'display-buffer-alist
             `(,(rx bos "*"
                    (or (and "input/output of " (+ (any "A-za-z"))) ; gdb
                        "Edebug Results") ; edebug
                    "*" eos)
               (display-buffer-reuse-window
                display-buffer-below-selected)
               (dedicated . nil)
               (window-height . 10)))

process

Increase throughput of process output:

(setq read-process-output-max (* 1024 1024))

proced

(define-advice proced (:after (&rest _args) recenter-at-bottom)
  (goto-char (point-max))
  (recenter -1))

ansi

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

comint

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

Used packages

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

ee

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

org

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

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

markdown-mode

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)
  (add-hook 'markdown-mode-hook
            (lambda ()
              ;; 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)))

gnus

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

  ;; 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:\"
\"12345678.fsf@gnu.org\"))))"
  (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 \"12345678.fsf@gnu.org\")))"
  (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)))

message

(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

mime

(when (require 'mm nil t)
  (mm-parse-mailcaps)
  (mm-parse-mimetypes))

gnuserv

(require 'server)
(unless (server-running-p)
   (server-start))

term

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

save-place

;; 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\\)$"))

desktop

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

tree-sitter

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

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

Multi-language features

dictionary

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

(with-eval-after-load 'dictionary
  (add-to-list 'debug-ignored-errors
               "^No match for \"[^\"]+\" with strategy \"[^\"]+\" in dictionary"))

quail

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

mule

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

Fixes for packages

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.

Fix timer in battery

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

Fix inconsistency in motion keys

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)

Fix isearch

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

Fix next-error

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

Fix xref

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

Fix info

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)

Fix set-variable

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-flet ((custom-variable-p (_v) t))
    (apply ofun args)))

Fix follow-mode

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

Fix *Messages* buffer

(add-hook 'messages-buffer-mode-hook
          (lambda ()
            (setq buffer-read-only nil)
            (fundamental-mode)))

Fix mark in two windows with same buffer

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)

Fix outline-regexp

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

Fix outline-mode in NEWS files

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

Fix outline faces

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

Fix org-mode 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))

Fix org-mode keys

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

Fix org-mode code blocks

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

Fix mode-line faces

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

Fix dired

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

Fix file sorting order between vc-dir and vc-diff

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

Fall back between vc-diff and diff-backup

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

Use project directories everywhere

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

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

Other

No blinking

Blinking cursors are distracting - turn blink OFF: here (*) (*) (*) indicates how cursor blinks.

(and (fboundp 'blink-cursor-mode) (blink-cursor-mode (- (*) (*) (*))))

Initialization stats

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

Copyright (C) 1989-2024 Juri Linkov <juri@linkov.net>

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.

Local Variables

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:

About

My Emacs init configuration

https://gitlab.com/link0ff/emacs-init

License:GNU General Public License v3.0


Languages

Language:Emacs Lisp 100.0%