yibie / emacs-grandview

原非大观。

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Emacs Configuration

Overview

About

This project is my emacs configuration.

Structure

  • Init file (init.el / early-init.el)
  • Optimization options (early-optimize.el)
  • Core libraries (core/*, core/autoload/*)
  • extensions (lisp/*)
  • Main config file (ale.org)

doc

;;; minimal.el --- -*- lexical-binding: t -*-
;;; full.el --- -*- lexical-binding: t -*-

COPYING

Copyright (c) 2020-2021 Alex Lu <alexluigit@gmail.com>

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 3 of the License, 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 this file. If not, see http://www.gnu.org/licenses/.

Quickstart

Dependencies

Here are the dependency list (it might not be a complete list) that this project required or optionally required.

PackageDescriptionRequired?
fdA modern find
rgA modern grep
exaA modern ls
gitVersion control
noto-fonts-emojiFont for emojisoptional
wordsEnglish words completionoptional
victor-mono-nerd-fontDefault fixed pitch fontoptional
sarasa-gothic (font)Default variable pitch fontoptional
xdotoolAutomation tool for X11optional

Install

This section contails instructions on how to use this config.

  • Make sure to backup your own emacs config before installation.
cp -r ~/.emacs.d ~/.emacs.d.bak
  • Clone the repo and tangle the config.
git clone https://www.github.com/alexluigit/emacs-grandview
# either put it to ~/.emacs.d
mv ~/emacs-grandview ~/.emacs.d
# or conform XDG spec
mv ~/emacs-grandview ~/.config/emacs/
# or use symlink
mv ~/emacs-grandview ~/Code/emacs-grandview
ln -sf ~/Code/emacs-grandview ~/.config/emacs

# Tangle the config
emacs --daemon
emacsclient -e '(kill-emacs)'

CLI

Script for starting up emacs.

  • Why this bash script?

    Emacs is a single-threaded program, so if it hangs, we can not expect it to evaluate any elisp code.

  • What does this script do?

    If this org file get modified, ale-init-build function will tangle this file. After the build function finishes, it kill current emacs and open emacs with new init file.

    Since ale-init-build function is added to kill-emacs-hook, emacs main process only get killed after the tangle function finishes (unless we use ‘-9’ flag in kill command). In my case, it usually takes emacs less than 1s to tangle this org file, so if emacs main process keep alive for over 3s after killall emacs command, it probably means emacs get frozen, so we just kill it with kill -9.

  • How to use this script?

    You don’t need to install this script if you have followed Installation section. This script will be tangled to ~/.local/share/em, so if you want to get access to em restart command from terminal, make sure ~/.local/bin/em is in your PATH.

Here is the content of the script.

_is_ime_rime () { [[ -d ~/.config/rime ]] || [[ -d ~/.local/share/fcitx5/rime ]]; }

restart () {
  notify-send "Restarting emacs..." 2>/dev/null
  emacs_pid=$(pidof emacs)
  timeout=300
  counter=0
  killall emacs
  while $(kill -0 $emacs_pid 2>/dev/null) && [[ $counter -lt $timeout ]]; do
    counter=$((counter + 1))
    sleep 0.01
  done
  kill -9 $emacs_pid 2>/dev/null
  rm -rf ~/.config/emacs/eln-cache 2>/dev/null
  [[ $1 == "-p" ]] && rm -rf ~/.cache/emacs/{straight/build,eln} 2>/dev/null
  [[ $1 == "-r" ]] && rm -rf ~/.cache/emacs/ale 2>/dev/null
  [[ $1 == "-R" ]] && rm -rf ~/.cache/emacs/{ale,straight,eln} 2>/dev/null
  _is_ime_rime && GTK_IM_MODULE=emacs XMODIFIERS=@im=emacs emacs --daemon || emacs --daemon
  command -v xdotool >/dev/null 2>&1 && xdotool set_desktop 0
  emacsclient -cne '(delete-file "~/nohup.out")' >/dev/null 2>&1
}

open () { emacs -nw ${@}; }

[[ -z "$@" ]] || ! $(declare -f -F $1 >/dev/null 2>&1) && { open ${@:1}; exit 0; }
$1 ${@:2}

You can also restart emacs by invoking restart-emacs command inside emacs (again, comfirm your $PATH environment variable).

CORE

Load core of emacs-grandview. It is NOT recommended to delete / comment out this section.

Editor

Basic editing (simple.el)

simple.el consists of a grab-bag of basic Emacs commands not specifically related to some major mode or to file-handling.

(use-package simple
  :straight (:type built-in))

Modal editing (meow.el)

Unlike evil-mode, which tries to create a whole vim emulation in emacs, meow only focus on bringing the goodness of modal editing to vanilla emacs.

You may noticed that I didn’t include any keybindings of meow here, that’s because it can be very lengthy and should be configured separately, see Keybindings for details.

If you want to know more about meow or modal editing in general, check out meow.

Autoload

;;; ale/autoload/meow.el --- -*- lexical-binding: t -*-

(require 'meow)

(defvar ale-meow-fallback-key-cmd-alist
  '(("SPC" . consult-buffer)
    ("/" . consult-line))
  "Doc.")

;;;###autoload
(defadvice! +meow-query-replace-ad (fn &rest args)
  "Call `meow-query-replace' and auto fill prompt with region text."
  :around #'meow-query-replace
  (unless (region-active-p) (meow-mark-symbol 1))
  (call-interactively 'kill-ring-save)
  (exchange-point-and-mark)
  (deactivate-mark t)
  (run-with-timer 0.05 nil 'yank)
  (apply fn args))

;;;###autoload
(defun ale-meow-save ()
  (interactive)
  (save-excursion
    (meow--with-selection-fallback
     (meow--prepare-region-for-kill)
     (call-interactively 'kill-ring-save))))

;;;###autoload
(defun ale-meow-escape ()
  (interactive)
  (cond
   ((minibufferp)
    (keyboard-escape-quit))
   ((region-active-p)
    (meow-cancel))
   (t (call-interactively 'execute-extended-command))))

;;;###autoload
(defun ale-meow-with-key-fallback ()
  "Execute fallback command if exists.
If `last-input-event' is bounded to any command in current
major-mode, call that command, otherwise call its fallback
command defined in `ale-meow-fallback-key-cmd-alist'."
  (interactive)
  (let* ((key (meow--parse-input-event last-input-event))
         (rebind-key (concat meow-motion-remap-prefix key)))
    (if (and (key-binding (kbd rebind-key)) (not (derived-mode-p 'special-mode)))
        (meow--execute-kbd-macro rebind-key)
      (funcall (alist-get key ale-meow-fallback-key-cmd-alist #'ignore nil #'string=)))))

;;;###autoload
(defun ale--bounds-of-tag ()
  (meow--bounds-of-regexp "<.*>"))

;;;###autoload
(defun ale--inner-of-tag ()
  (when-let ((pos (ale--bounds-of-tag)))
    (save-mark-and-excursion
      (let (ibeg iend)
        (goto-char (car pos))
        (setq ibeg (search-forward ">"))
        (goto-char (cdr pos))
        (setq iend (search-backward "<"))
        (cons ibeg iend)))))

;;;###autoload
(defun ale-meow-insert ()
  (interactive)
  (meow--switch-state 'insert))

;;;###autoload
(defun ale-meow-insert-at-first-non-whitespace ()
  (interactive)
  (back-to-indentation)
  (meow-insert))

Config

(use-package meow
  :straight (meow :type git :depth full :host github :repo "meow-edit/meow")
  :demand t
  :init
  (setq meow-keymap nil)
  (meow-global-mode)
  :config
  (advice-add 'meow--maybe-highlight-num-positions :override #'ignore)
  (advice-add 'meow-start-kmacro-or-insert-counter :around #'silent!)
  (advice-add 'meow-end-or-call-kmacro :around #'silent!)
  (meow--thing-register 'tag #'ale--inner-of-tag #'ale--bounds-of-tag)
  (setq meow-visit-sanitize-completion nil)
  (setq meow-use-clipboard t)
  (setq meow-esc-delay 0.001)
  (setq meow-keypad-describe-delay 0.5)
  (setq meow-select-on-change t)
  (setq meow-cursor-type-normal 'box)
  (setq meow-cursor-type-insert '(bar . 4))
  (setq meow-cursor-type-default 'hbar)
  (setq meow-selection-command-fallback
        '((meow-replace . meow-yank)
          (meow-reverse . back-to-indentation)
          (meow-change . meow-change-char)
          (ale-meow-save . ale-pulse-save-line)
          (meow-kill . meow-kill-whole-line)
          (meow-pop-selection . ale-files-revert-buffer-no-ask)
          (meow-cancel . keyboard-quit)
          (meow-delete . meow-C-d)))
  (setq meow-char-thing-table
        '((?r . round)
          (?b . square) ;; `b' for bracket
          (?c . curly)
          (?s . string)
          (?e . symbol)
          (?w . window)
          (?B . buffer)
          (?p . paragraph)
          (?\[ . line)
          (?\] . line)
          (?d . defun)
          (?i . indent)
          (?t . tag)
          (?x . extend)))
  (add-to-list 'meow-mode-state-list '(helpful-mode . normal))
  (add-to-list 'meow-mode-state-list '(message-buffer-mode . normal)))

Quick goto char (avy.el)

Jump to any visible text.

(use-package avy
  :config
  (setq avy-timeout-seconds 0.3)
  (setq avy-all-windows nil)
  (setq avy-keys '(?a ?r ?s ?t ?n ?e ?i ?o)))

Symbol pairs (embrace.el)

embrace.el is a package for symbol pairs insert/change/delete which resembles to surround.vim in vim.

I’ve forked this package to extract embrace-default-pairs out, so we can use keys like ~,r~ to select an inner parenthesis block (this assumes your comma key has been bound to meow-inner-of-thing.)

(use-package embrace
  :straight
  (embrace :type git :depth full :host github
           :repo "cute-jumper/embrace.el"
           :fork (:host github :repo "alexluigit/embrace.el"))
  :after-call meow--selection-type
  :init
  (setq embrace-default-pairs
        '((?r . ("(" . ")"))
          (?R . ("( " . " )"))
          (?c . ("{" . "}"))
          (?C . ("{ " . " }"))
          (?\[ . ("[" . "]"))
          (?\] . ("[ " . " ]"))
          (?a . ("<" . ">"))
          (?A . ("< " . " >"))
          (?s . ("\"" . "\""))
          (?\' . ("\'" . "\'"))
          (?` . ("`" . "`")))))

Keyboard orientation (kbd.el)

Autoload

;;; ale/autoload/kbd.el --- -*- lexical-binding: t -*-

;;;###autoload
(defun ale-kbd-C-i-fix ()
  "Make emacs differentiate C-i and Tab keys.

For historical reason, terminal can not tell the difference between
some key storkes. For example, `C-i' and `<tab>', `C-m' and `Return',
etc. By default, emacs follow this convention, but it doesn't mean
emacs are not able to tell the difference. To change this behaviour,
we can use `input-decode-map' to give `C-i' different meaning."
  (if IS-GUI
      (add-hook 'after-make-frame-functions
                (lambda (f) (with-selected-frame f (ale-kbd--C-i-fix-GUI))))
    (add-hook 'window-setup-hook 'ale-kbd--C-i-fix-TERM)))

(defun ale-kbd--C-i-fix-GUI ()
  "Helper for `ale-kbd-C-i-fix'."
  (define-key input-decode-map [?\C-i] [C-i]))

(defun ale-kbd--C-i-fix-TERM ()
  "Helper for `ale-kbd-C-i-fix'."
  (bind-keys
   ("<f6>" . better-jumper-jump-forward)
   :map minibuffer-local-map
   ("<f6>" . forward-char)
   :map meow-insert-state-keymap
   ("<f6>" . ale-insert-ctrl-i)))

Config

For historical reason, terminal can not tell the difference between some key storkes. For example, C-i and tab, C-m and Return, etc. By default, emacs follow this convention, but it doesn’t mean emacs are not able to tell the difference. To change this behaviour, we can use input-decode-map to give C-i different meaning. See ale-kbd--C-i-fix-GUI and ale-kbd--C-i-fix-TERM for more details.

(ale-kbd-C-i-fix)

Core UI

Window placement (window.el)

Autoload

;;; ale/autoload/window.el --- extensions for window.el -*- lexical-binding: t; -*-

(defgroup ale-window ()
  "Tweaks for windows."
  :group 'windows)

;;;###autoload
(defun ale-split-window-right ()
  (interactive)
  (split-window-right) (other-window 1))

;;;###autoload
(defun ale-split-window-below ()
  (interactive)
  (split-window-below) (other-window 1))

;; Inspired by Pierre Neidhardt's windower:
;; https://gitlab.com/ambrevar/emacs-windower/-/blob/master/windower.el
(defvar ale--windows-current nil
  "Current window configuration.")

(defvar ale-monocle-mode)

;;;###autoload
(defun ale-monocle-disable ()
  "Set variable `ale-simple-monocle' to nil, when appropriate.
To be hooked to `window-configuration-change-hook'."
  (when (and ale-monocle-mode
             (not (cl-find-if
                   (lambda (w) (eq (window-parameter w 'window-side) 'bottom))
                   (window-list)))
             (not (one-window-p)))
    (delete-other-windows)
    (ale-monocle-mode -1)
    (set-window-configuration ale--windows-current)))

;;;###autoload
(define-minor-mode ale-monocle-mode
  "Toggle between multiple windows and single window.
This is the equivalent of maximising a window.  Tiling window
managers such as DWM, BSPWM refer to this state as 'monocle'."
  :lighter " -M-"
  :global nil
  (let ((config ale--windows-current)
        (buf (current-buffer)))
    (if (one-window-p)
        (when config
          (set-window-configuration config))
      (setq ale--windows-current (current-window-configuration))
      (when (window-parameter nil 'window-side) (delete-window))
      (delete-other-windows)
      (switch-to-buffer buf))))

Config

The display-buffer-alist is intended as a rule-set for controlling the display of windows. The objective is to create a more intuitive workflow where targeted buffer groups or types are always shown in a given location, on the premise that predictability improves usability.

For each buffer action in it we can define several functions for selecting the appropriate window. These are executed in sequence, but my usage thus far suggests that a simpler method is just as effective for my case.

Additionally, I’ve set split-height-threshold to nil and split-width-threshold to 0 to ensure every new window will open in horizontal split.

(use-package window
  :straight (:type built-in)
  :hook
  (window-configuration-change . ale-monocle-disable)
  :config
  (setq display-buffer-alist
        `(("\\*\\(Flymake\\|Messages\\|Backtrace\\|Warnings\\|Compile-Log\\|Custom\\)\\*"
           (display-buffer-in-side-window)
           (window-height . 0.2)
           (side . top))
          ("^\\*\\(Help\\|helpful\\).*"
           (display-buffer-in-side-window)
           (window-width . 0.4)
           (side . right))
          ("\\*\\vc-\\(incoming\\|outgoing\\|Output\\|Register Preview\\).*"
           (display-buffer-at-bottom))))
  (setq help-window-select t)
  (setq window-combination-resize t)
  (setq even-window-sizes 'height-only)
  (setq window-sides-vertical nil)
  (setq switch-to-buffer-in-dedicated-window 'pop)
  (setq split-height-threshold nil))

Tabs as workspaces (tab-bar.el)

(use-package tab-bar
  :straight (:type built-in)
  :config
  (setq tab-bar-new-tab-choice "~/"))

Pulse line (pulse.el)

Autoload

;;; ale/autoload/pulse.el --- -*- lexical-binding: t -*-

(require 'pulse)

(defgroup ale-pulse ()
  "Extensions for `pulse.el'."
  :group 'editing)

(defcustom ale-pulse-pulse-command-list
  '(recenter-top-bottom
    reposition-window
    consult--jump-nomark
    ace-select-window)
  "Commands that should automatically `ale-pulse-pulse-line'.
You must restart function `ale-pulse-line-mode' for changes to
take effect."
  :type 'list
  :group 'ale-pulse)

(defface ale-pulse-line
  '((default :extend t)
    (((class color) (min-colors 88) (background light))
     :background "#8eecf4")
    (t :inverse-video t :background "#004065"))
  "Default face for `ale-pulse-pulse-line'."
  :group 'ale-pulse)

;;;###autoload
(defun ale-pulse-pulse-line (&optional face kill)
  "Temporarily highlight the current line with optional FACE."
  (interactive)
  (let ((beg (if (eobp)
                 (line-beginning-position 0)
               (line-beginning-position)))
        (end (line-beginning-position 2))
        (pulse-delay .05)
        (face (or face 'ale-pulse-line)))
    (pulse-momentary-highlight-region beg end face)
    (when kill (kill-ring-save beg end))))

;;;###autoload
(defun ale-pulse-save-line ()
  "Temporarily highlight the current line and copy it."
  (interactive)
  (ale-pulse-pulse-line nil t))

;;;###autoload
(define-minor-mode ale-pulse-line-mode
  "Set up for `ale-pulse-pulse-command-list'."
  :init-value nil
  :global t
  (if ale-pulse-line-mode
      (dolist (fn ale-pulse-pulse-command-list)
        (advice-add fn :after (lambda (&rest _) (interactive) (ale-pulse-pulse-line))))
    (dolist (fn ale-pulse-pulse-command-list)
      (advice-remove fn (lambda (&rest _) (interactive) (ale-pulse-pulse-line))))))

Config

(frame-enable! 'ale-pulse-line-mode)

Transient commands (transient.el)

transient.el built-in package in emacs 28 for transient commands.

(straight-use-package `(transient ,@(when (>= emacs-major-version 28) '(:type built-in))))

(use-package transient
  :config
  (setq transient-show-popup -0.5)
  (transient-bind-q-to-quit)
  :bind
  (nil
   :map transient-map
   ("<escape>" . transient-quit-all)
   :map transient-sticky-map
   ("ESC" . transient-quit-all)))

Automatic opacity adjustment (+opacity.el)

Autoload

;;; ale/autoload/+opacity.el --- -*- lexical-binding: t -*-

(defcustom ale-opacity 80
  "Default frame opacity."
  :group 'ale
  :type 'integer)

(defcustom ale-opacity-disabled-predicates '()
  "A list of predicate functions in which the `ale-opacity-auto-mode' will not be turned on."
  :group 'ale
  :type 'hook)

;;;###autoload
(defun ale-opacity-set (&optional percent)
  (interactive "P")
  (cond ((or (and percent (not current-prefix-arg))
             (numberp percent))
         (setq ale-opacity (* 10 percent))
         (set-frame-parameter (selected-frame) 'alpha-background ale-opacity))
        ((equal current-prefix-arg '(4))
         (ale-opacity-default))
        (t
         (let ((opa (frame-parameter nil 'alpha-background))
               (low 60) (high 100))
           (if (eq opa low)
               (set-frame-parameter (selected-frame) 'alpha-background high)
             (set-frame-parameter (selected-frame) 'alpha-background low))))))

(defun ale-opacity-disable ()
  (set-frame-parameter (selected-frame) 'alpha-background 100))

(defun ale-opacity-default ()
  (set-frame-parameter (selected-frame) 'alpha-background ale-opacity))

;;;###autoload
(defun ale-opacity-auto ()
  "Setup frame opacity according to current major-mode."
  (if (seq-find 'funcall ale-opacity-disabled-predicates)
      (ale-opacity-disable)
    (ale-opacity-default)))

;;;###autoload
(define-minor-mode ale-opacity-auto-mode
  "Minor mode for adjusting frame opacity."
  :lighter " ale-auto-opacity"
  :group 'ale
  :global t
  (ale-opacity-default)
  (if ale-opacity-auto-mode
      (add-hook 'window-configuration-change-hook #'ale-opacity-auto)
    (remove-hook 'window-configuration-change-hook #'ale-opacity-auto)))

Config

(frame-enable! 'ale-opacity-auto-mode)

Completion framework

The optimal way of using Emacs is through searching and narrowing selection candidates. Spend less time worrying about where things are on the screen and more on how fast you can bring them into focus. This is, of course, a matter of realigning priorities, as we still wish to control every aspect of the interface.

Minibuffer and completion functions (minibuffer.el)

The minibuffer is the epicentre of extended interactivity with all sorts of Emacs workflows: to select a buffer, open a file, provide an answer to some prompt, such as a number, regular expression, password, and so on.

What my minibuffer config does:

Intangible cursors

Disallow user move cursors into prompt.

Recursive minibuffers

Enable recursive minibuffers. This practically means that you can start something in the minibuffer, switch to another window, call the minibuffer again, run some commands, and then move back to what you initiated in the original minibuffer. Or simply call an M-x command while in the midst of a minibuffer session. To exit, hit C-[ (abort-recursive-edit), though the regular C-g should also do the trick.

The minibuffer-depth-indicate-mode will show a recursion indicator, represented as a number, next to the minibuffer prompt, if a recursive edit is in progress.

(use-package minibuffer
  :straight (:type built-in)
  :config
  (setq enable-recursive-minibuffers t)
  (setq minibuffer-eldef-shorten-default t)
  (setq! minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt))
  (minibuffer-depth-indicate-mode 1))

Minibuffer history (savehist.el)

Keeps a record of actions involving the minibuffer.

(use-package savehist
  :straight (:type built-in)
  :after-call minibuffer-setup-hook
  :config
  (setq savehist-file (locate-user-emacs-file "savehist"))
  (setq history-length 10000)
  (setq history-delete-duplicates t)
  (setq savehist-save-minibuffer-history t)
  (savehist-mode))

Vertical completion candidates (vertico.el)

Vertico provides a performant and minimalistic vertical completion UI based on the default completion system. By reusing the built-in facilities, Vertico achieves full compatibility with built-in Emacs completion commands and completion tables.

Here I just modified face for current candidate and make height of vertico window as a constant value.

(use-package vertico
  :after-call pre-command-hook
  :config
  (setq resize-mini-windows 'grow-only)
  (set-face-attribute 'vertico-current nil :background
                      (face-attribute 'lazy-highlight :background nil t)
                      :weight 'semi-bold)
  (vertico-mode 1))

Match candidates made easy (orderless.el)

This package provides an orderless completion style that divides the pattern into components (space-separated by default), and matches candidates that match all of the components in any order.

Autoload

;;; ale/autoload/orderless.el --- -*- lexical-binding: t -*-

(require 'pinyinlib)

(defun ale-orderless-pinyin-only-initialism (pattern)
  "Leading pinyin initialism regex generator."
  (if (< (length pattern) 10)
      (pinyinlib-build-regexp-string pattern t nil t)
    pattern))

;;;###autoload
(defun ale-orderless-literal-dispatcher (pattern _index _total)
  "Literal style dispatcher using the equals sign as a prefix."
  (when (string-suffix-p "=" pattern)
    `(orderless-literal . ,(substring pattern 0 -1))))

;;;###autoload
(defun ale-orderless-initialism-dispatcher (pattern _index _total)
  "Leading initialism dispatcher using the comma sign as a prefix."
  (when (string-prefix-p "," pattern)
    `(orderless-strict-leading-initialism . ,(substring pattern 1))))

;;;###autoload
(defun ale-orderless-pinyin-dispatcher (pattern _index _total)
  "Pinyin initialism dispatcher using the backtick sign as a prefix."
  (when (string-prefix-p "`" pattern)
    `(ale-orderless-pinyin-only-initialism . ,(substring pattern 1))))

;;;###autoload
(defun ale-orderless-without-literal-dispatcher (pattern _index _total)
  (when (string-prefix-p "~" pattern)
    `(orderless-without-literal . ,(substring pattern 1))))

Config

Setup completion styles in minibuffer.

Not that we have set orderless-component-separator to the function orderless-escapable-split-on-space. This allows us to match candidates with literal spaces. Suppose you are browsing dired.el and try to locate the dired function, you can issue a consult-outline command and input “defun dired\ \(\)”, this gives you (defun dired (dirname &optional switches) as the sole match rather than all of the dired-* noise.

(use-package pinyinlib)

(use-package orderless
  :after-call minibuffer-setup-hook
  :config
  (setq completion-styles '(orderless))
  (setq orderless-component-separator #'orderless-escapable-split-on-space)
  (setq orderless-matching-styles
        '(ale-orderless-pinyin-only-initialism
          orderless-initialism
          orderless-prefixes
          orderless-regexp))
  (setq orderless-style-dispatchers
        '(ale-orderless-literal-dispatcher
          ale-orderless-initialism-dispatcher
          ale-orderless-without-literal-dispatcher
          ale-orderless-pinyin-dispatcher)))

Useful commands using completion (consult.el)

Consult implements a set of consult-<thing> commands which use completing-read to select from a list of candidates. Consult provides an enhanced buffer switcher consult-buffer and search and navigation commands like consult-imenu and consult-line. Searching through multiple files is supported by the asynchronous consult-grep command. Many Consult commands allow previewing candidates - if a candidate is selected in the completion view, the buffer shows the candidate immediately.

The Consult commands are compatible with completion systems based on the Emacs completing-read API, including the default completion system, Icomplete, Selectrum, Vertico and Embark.

Autoload

;;; ale/autoload/consult.el --- -*- lexical-binding: t -*-

(require 'consult)
(autoload 'consult-org "consult-org")

(defun ale-consult-ripgrep-current-file ()
  "Call `consult-ripgrep' for the current buffer (a single file)."
  (interactive)
  (let ((consult-project-root-function #'ignore)
        (consult-ripgrep-args
         (concat "rg "
                 "--null "
                 "--line-buffered "
                 "--color=never "
                 "--line-number "
                 "--smart-case "
                 "--no-heading "
                 "--max-columns=1000 "
                 "--max-columns-preview "
                 "--search-zip "
                 "--with-filename "
                 (shell-quote-argument buffer-file-name))))
    (consult-ripgrep)))

;;;###autoload
(defun ale-consult-line-advisor (fn &rest args)
  "An advice for `consult-line'.

When in a very large file (total lines > 100000), call
`consult-ripgrep' on current file, otherwise execute it
directly."
  (interactive)
  (let ((total-lines (count-lines (point-min) (point-max))))
    (if (> total-lines 100000)
        (ale-consult-ripgrep-current-file)
      (apply fn args))))

;;;###autoload
(defun ale-consult-outline-advisor (fn &rest args)
  "An advice for `consult-outline'.

When in `org-mode', call `consult-org-heading', otherwise call
`consult-outline'."
  (if (derived-mode-p 'org-mode)
      (consult-org-heading)
    (apply fn args)))

;;;###autoload
(defun ale-consult-project-root ()
  (when-let (project (project-current))
    (car (project-roots project))))

Config

(use-package consult
  :after-call minibuffer-setup-hook
  :init
  (setq completion-in-region-function #'consult-completion-in-region)
  (setq register-preview-delay 0.2)
  (setq register-preview-function #'consult-register-format)
  (advice-add #'register-preview :override #'consult-register-window)
  (advice-add #'completing-read-multiple :override #'consult-completing-read-multiple)
  (advice-add #'consult-outline :around #'ale-consult-outline-advisor)
  (advice-add #'consult-line :around #'ale-consult-line-advisor)
  (setq xref-show-xrefs-function #'consult-xref
        xref-show-definitions-function #'consult-xref)
  :bind
  (nil
   :map meow-normal-state-keymap
   ("/" . consult-line)
   :map ale-mct-map
   ("/" . consult-line-multi)
   ("e" . consult-compile-error)
   ("r" . consult-ripgrep)
   ("k" . consult-keep-lines)
   ("i" . consult-imenu-multi)
   ("f" . consult-focus-lines)
   ("o" . consult-outline)
   ("R" . consult-register)
   ("y" . consult-yank-from-kill-ring)
   ("m" . consult-minor-mode-menu)
   ("c" . consult-complex-command)
   ("C" . consult-mode-command))
  :config
  (setq consult-project-root-function #'ale-consult-project-root)
  (setq consult-line-numbers-widen t)
  (setq consult-async-min-input 3)
  (setq consult-async-input-debounce 0.5)
  (setq consult-async-input-throttle 0.8)
  (setq consult-narrow-key ">"))

Keyboard version right-click (embark.el)

This package provides a sort of right-click contextual menu for Emacs, accessed through the `embark-act’ command (which you should bind to a convenient key), offering you relevant actions to use on a target determined by the context.

Autoload

;;; ale/autoload/embark.el --- -*- lexical-binding: t -*-

(autoload 'consult-grep "consult")
(autoload 'consult-line "consult")
(autoload 'consult-imenu "consult-imenu")
(autoload 'consult-outline "consult")

(defvar ale-embark-become-general-map
  (let ((map (make-sparse-keymap)))
    (define-key map (kbd "g") 'consult-grep)
    map)
  "General custom cross-package `embark-become' keymap.")

(defvar ale-embark-become-line-map
  (let ((map (make-sparse-keymap)))
    (define-key map (kbd "l") 'consult-line)
    (define-key map (kbd "i") 'consult-imenu)
    (define-key map (kbd "s") 'consult-outline) ; as my default is 'M-s s'
    map)
  "Line-specific custom cross-package `embark-become' keymap.")

(defvar embark-become-file+buffer-map)
(autoload 'project-switch-to-buffer "project")
(autoload 'project-find-file "project")

(defvar ale-embark-become-file+buffer-map
  (let ((map (make-sparse-keymap)))
    (set-keymap-parent map embark-become-file+buffer-map)
    (define-key map (kbd "B") 'project-switch-to-buffer)
    (define-key map (kbd "F") 'project-find-file)
    map)
  "File+buffer custom cross-package `embark-become' keymap.")

(defvar embark-become-keymaps)

;;;###autoload
(define-minor-mode ale-embark-keymaps
  "Add or remove keymaps from Embark.
This is based on the value of `ale-embark-add-keymaps'
and is meant to keep things clean in case I ever wish to disable
those so-called 'extras'."
  :init-value nil
  :global t
  (let ((maps '(ale-embark-become-general-map
                ale-embark-become-line-map
                ale-embark-become-file+buffer-map)))
    (if ale-embark-keymaps
        (dolist (map maps)
          (cl-pushnew map embark-become-keymaps))
      (setq embark-become-keymaps
            (dolist (map maps)
              (delete map embark-become-keymaps))))))

Config

(use-package embark
  :after-call dired-after-readin-hook minibuffer-setup-hook
  :bind
  (("C-." . embark-act)
   :map minibuffer-local-map ("C-." . embark-act) ("C-," . embark-become)
   :map embark-collect-mode-map ("C-." . embark-act))
  :config
  (use-package embark-consult :demand t)
  (ale-embark-keymaps 1)
  (setq embark-quit-after-action t)
  (setq embark-action-indicator
        (let ((act (propertize "Act" 'face 'success)))
          (cons act (concat act " on '%s'"))))
  (setq embark-become-indicator (propertize "Become" 'face 'warning)))

Candidate annotation (marginalia.el)

This is a utility jointly developed by Daniel Mendler and Omar Antolín Camarena that provides annotations to completion candidates. It is meant to be framework-agnostic, so it works with Selectrum, Icomplete, vertico, and Embark.

(use-package marginalia
  :after-call minibuffer-setup-hook
  :config
  (marginalia-mode)
  (setq marginalia-align 'center))

Auto completion in buffer (company.el)

company is a modular completion framework. Modules for retrieving completion candidates are called backends, modules for displaying them are frontends. It comes with many backends, e.g. company-etags. These are distributed in separate files and can be used individually.

tng in company-tng-mode means tab and go, in this mode tab key will complete and move to the next candidate meanwhile keep company window open.

Autoload

;;; ale/autoload/company.el --- Extension for company.el -*- lexical-binding: t -*-

(defvar ale-company-disabled-modes '(org-mode))

;;;###autoload
(defun ale-company-enable-ispell (&optional force)
  "Add `company-ispell' to local `company-backends'."
  (interactive "P")
  (require 'company-ispell)
  (when (company-ispell-available)
    (when (or force
              (not (memq major-mode ale-company-disabled-modes)))
      (make-local-variable 'company-backends)
      (if (memq 'company-ispell company-backends)
          (setq-local company-backends (delq 'company-ispell company-backends))
        (add-to-list 'company-backends 'company-ispell)))))

Config

(use-package company
  :after-call self-insert-command
  :hook
  (text-mode . ale-company-enable-ispell)
  :config
  (global-company-mode)
  (company-tng-mode)
  (setq company-idle-delay 0.0))

KEYBINDINGS

This section contains all core keybindings of emacs-grandview.

INSERT

(bind-keys
 :map meow-insert-state-keymap
 ("C-u" . meow-kill-whole-line)
 ("<C-i>" . meow-right)
 ("C-o" . meow-left))

NORMAL

(meow-normal-define-key
 '("0" . meow-digit-argument)
 '("1" . meow-digit-argument)
 '("2" . meow-digit-argument)
 '("3" . meow-digit-argument)
 '("4" . meow-digit-argument)
 '("5" . meow-digit-argument)
 '("6" . meow-digit-argument)
 '("7" . meow-digit-argument)
 '("8" . meow-digit-argument)
 '("9" . meow-digit-argument)
 '("<escape>" . ale-meow-escape)
 '("<backspace>" . scroll-down)
 '("'" . scroll-up)
 '("%" . ale-match-paren)
 '(";" . meow-reverse)
 '("," . meow-inner-of-thing)
 '("." . meow-bounds-of-thing)
 '("<" . beginning-of-buffer)
 '(">" . end-of-buffer)
 '("[" . meow-beginning-of-thing)
 '("]" . meow-end-of-thing)
 '("-" . negative-argument)
 '("=" . meow-query-replace)
 '("+" . meow-query-replace-regexp)
 '("\\" . meow-pop-selection)
 '("|" . meow-pop-all-selection)
 '("^" . meow-last-buffer)
 '("a" . ale-meow-insert)
 '("A" . ale-meow-insert-at-first-non-whitespace)
 '("b" . meow-back-word)
 '("B" . meow-back-symbol)
 '("c" . meow-change)
 '("C" . meow-change-save)
 '("d" . meow-delete)
 '("e" . meow-line)
 '("E" . ale-inner-line)
 '("f" . meow-next-word)
 '("F" . meow-next-symbol)
 '("g" . meow-grab)
 '("G" . meow-pop-grab)
 '("h" . embrace-commander)
 '("i" . meow-right)
 '("I" . meow-right-expand)
 '("j" . ale-top-join-line)
 '("J" . meow-join)
 '("k" . meow-kill)
 '("K" . meow-kmacro-matches)
 '("l" . meow-kmacro-lines)
 '("m" . meow-mark-word)
 '("M" . meow-mark-symbol)
 '("n" . meow-next)
 '("N" . meow-open-below)
 '("o" . meow-left)
 '("O" . meow-left-expand)
 '("p" . meow-prev)
 '("P" . meow-open-above)
 '("q" . ale-quit)
 '("r" . meow-search)
 '("s" . meow-sync-grab)
 '("S" . meow-swap-grab)
 '("t" . avy-goto-char-timer)
 '("T" . avy-resume)
 '("u" . undo)
 '("U" . undo-redo)
 '("v" . meow-visit)
 '("w" . meow-block)
 '("W" . meow-block-expand)
 '("x" . ale-meow-save)
 '("y" . meow-replace)
 '("Y" . meow-yank-pop)
 '("z" . meow-start-kmacro-or-insert-counter)
 '("Z" . meow-end-or-call-kmacro))

LEADER

(meow-leader-define-key
 '("0" . delete-window)
 '("1" . delete-other-windows)
 '("2" . ale-split-window-below)
 '("3" . ale-split-window-right)
 '("4" . ctl-x-4-prefix)
 '("5" . ctl-x-5-prefix)
 '("8" . insert-char)
 '("9" . tab-map)
 '("SPC" . ale-meow-with-key-fallback)
 '("?" . describe-keymap)
 '("/" . describe-symbol)
 '(";" . ale-comment-or-uncomment-region)
 '("a" . ale-apps-map)
 '("e" . dired-jump)
 '("f" . ale-files-map)
 '("E" . eval-expression)
 '("i" . list-buffers)
 '("k" . kill-this-buffer)
 '("n" . ale-project-find-file)
 '("o" . ale-org-map)
 '("p" . project-map)
 '("r" . register-map)
 '("t" . ale-mct-map)
 '("w" . save-buffer)
 '("z" . window-toggle-side-windows))

(bind-keys
 :map ale-files-map
 ("." . ale-files-dotfiles)
 ("e" . ale-files-edit-emacs-config)
 ("l" . find-library)
 ("r" . ale-files-rename-file-and-buffer)
 ("u" . ale-files-in-user-dirs)
 :map ale-apps-map
 ("o" . ale-opacity-set)
 ("=" . count-words))

GLOBAL

(bind-keys
 :map global-map
 ("C-;" . exchange-point-and-mark)
 ("M-n" . ale-tab-next)
 ("M-p" . ale-tab-previous)
 ("M-SPC" . ale-monocle-mode) ; replaced `just-one-space'
 ("C-c C-M-m" . ale-show-messages)
 :map minibuffer-local-map
 ("S-<return>" . ale-files-other-window)
 ("C-u" . meow-kill-whole-line)
 ("<C-i>" . forward-char)
 ("C-o" . backward-char))

These are some default keybindings that will be available when the current major mode doesn’t specify one.

(define-key meow-motion-state-keymap (kbd ale-local-leader-key) meow-leader-keymap)
(define-key meow-motion-state-keymap
            (kbd (concat ale-local-leader-key " " ale-local-leader-key)) 'ale-meow-leader-space)

(meow-motion-overwrite-define-key
 '("<escape>" . ale-meow-escape)
 '("/" . ale-meow-with-key-fallback))

Text editor

Jump list (better-jumper.el)

Create a jump list (same concept in vim) in emacs.

Autoload

;;; ale/autoload/better-jumper.el --- -*- lexical-binding: t -*-

(defvar ale-jumper-cmd-alist
  '(beginning-of-buffer
    end-of-buffer
    forward-sexp
    backward-sexp
    meow-next
    meow-prev
    meow-search
    avy-goto-char-timer
    er/expand-region
    xref-find-definitions
    pop-to-buffer)
  "A list of functions for `ale-jumper-sensible-jump-mode'.")

(defun ale-jumper-advice (fn &rest args)
  (let ((old-buf (current-buffer))
        (old-pos (point)))
    (apply fn args)
    (when (or (not (eq old-buf (current-buffer)))
              (> (abs (- (line-number-at-pos old-pos) (line-number-at-pos (point)))) 1))
      (better-jumper-set-jump old-pos))))

;;;###autoload
(define-minor-mode ale-jumper-sensible-jump-mode
  "Add sensible commands to jump list."
  :init-value nil
  :global t
  :group 'convenience
  (if ale-jumper-sensible-jump-mode
      (dolist (sym ale-jumper-cmd-alist)
        (advice-add sym :around 'ale-jumper-advice))
    (dolist (sym ale-jumper-cmd-alist)
      (advice-remove sym 'ale-jumper-advice))))

Config

(use-package better-jumper
  :after-call pre-command-hook
  :bind
  ("<C-i>" . better-jumper-jump-forward)
  ("C-o" . better-jumper-jump-backward)
  :config
  (better-jumper-mode +1)
  (ale-jumper-sensible-jump-mode))

Long line text (so-long.el)

Consistent performance is the reason to enable global-so-long-mode, built into Emacs versions >= 27, which allows the active major mode to gracefully adapt to buffers with very long lines. What “very long” means is, of course, configurable: M-x find-library so-long covers several customisation options, though I find that the defaults require no further intervention from my part.

(use-package so-long
  :after-call find-file-hook
  :straight (:type built-in)
  :config
  (global-so-long-mode))

Fill line (fill.el)

The fill.el library is a tiny wrapper around some Emacs settings and modes that are scrattered around several files, which control (i) how paragraphs or comments in programming modes should be wrapped to a given column count, and (ii) what constitutes a sentence. I put them all together here to make things easier to track.

ale-fill-fill-mode sets my desired default column width for all buffers, while it applies another value for programming modes (in case there is a need to control the two cases separately). Those values are stored in the variables ale-fill-default-column and ale-fill-prog-mode-column respectively. My minor mode also enables auto-fill-mode in text-mode and prog-mode buffers through the appropriate hooks. Disabling ale-fill-fill-mode will remove all those customisations.

Autoload

;;; autoload/fill.el --- -*- lexical-binding: t -*-

(defgroup ale-fill ()
  "Tweak for filling paragraphs."
  :group 'fill)

(defcustom ale-fill-default-column 80
  "Default width for `fill-column'."
  :type 'integer
  :group 'ale-fill)

(defcustom ale-fill-prog-mode-column 80
  "`prog-mode' width for `fill-column'.
Also see `ale-fill-default-column'."
  :type 'integer
  :group 'ale-fill)

(defun ale-fill--fill-prog ()
  "Set local value of `fill-column' for programming modes.
Meant to be called via `prog-mode-hook'."
  (setq-local fill-column ale-fill-prog-mode-column))

;;;###autoload
(define-minor-mode ale-fill-fill-mode
  "Set up fill-mode and relevant variable."
  :init-value nil
  :global t
  (if ale-fill-fill-mode
      (progn
        (setq-default fill-column ale-fill-default-column)
        (add-hook 'prog-mode-hook #'ale-fill--fill-prog)
        (add-hook 'text-mode-hook #'turn-on-auto-fill))
    (setq-default fill-column 80)
    (remove-hook 'prog-mode-hook #'ale-fill--fill-prog)
    (remove-hook 'text-mode-hook #'turn-on-auto-fill)))

Config

(use-package fill
  :straight (:type built-in)
  :init
  (ale-fill-fill-mode 1)
  (setq ale-fill-default-column 80)
  (setq ale-fill-prog-mode-column 80)
  (setq colon-double-space nil)
  (setq adaptive-fill-mode t))

Cross reference (xref.el)

xref provides helpful commands for code navigation and discovery.

(use-package xref
  :straight (:type built-in)
  :config
  (setq xref-show-definitions-function #'xref-show-definitions-completing-read)
  (setq xref-show-xrefs-function #'xref-show-definitions-completing-read)
  (setq xref-file-name-display 'project-relative)
  (setq xref-search-program 'ripgrep))

Interactive diff, patch, or merge conflict (ediff.el)

This package provides a convenient way of simultaneous browsing through the differences between a pair (or a triple) of files or buffers. The files being compared, file-A, file-B, and file-C (if applicable) are shown in separate windows (side by side, one above the another, or in separate frames), and the differences are highlighted as you step through them. You can also copy difference regions from one buffer to another (and recover old differences if you change your mind).

(use-package ediff
  :config
  (setq ediff-keep-variants nil)
  (setq ediff-make-buffers-readonly-at-startup nil)
  (setq ediff-merge-revisions-with-ancestor t)
  (setq ediff-show-clashes-only t)
  (setq ediff-split-window-function 'split-window-horizontally)
  (setq ediff-window-setup-function 'ediff-setup-windows-plain)
  ;; Tweak those for safer identification and removal
  (setq ediff-combination-pattern
        '("<<<<<<< ale-ediff-combine Variant A" A
          ">>>>>>> ale-ediff-combine Variant B" B
          "####### ale-ediff-combine Ancestor" Ancestor
          "======= ale-ediff-combine End"))
  (defun ale/ediff-flush-combination-pattern ()
    "Remove my custom `ediff-combination-pattern' markers.
This is a quick-and-dirty way to get rid of the markers that are
left behind by `smerge-ediff' when combining the output of two
diffs.  While this could be automated via a hook, I am not yet
sure this is a good approach."
    (interactive)
    (flush-lines ".*ale-ediff.*" (point-min) (point-max) nil)))

Input method (rime.el)

Autoload

;;; ale/autoload/rime.el --- -*- lexical-binding: t -*-

;;;###autoload
(defun ale/rime-return-advice (fn &rest args)
  "Make return key (commit script text) compatible with vterm."
  (interactive)
  (if (eq major-mode 'vterm-mode)
      (progn
        (let ((input (rime-lib-get-input)))
          (execute-kbd-macro (kbd "<escape>"))
          (toggle-input-method)
          (dotimes (i (length input))
            (execute-kbd-macro (kbd (substring input i (+ i 1)))))
          (toggle-input-method)))
    (apply fn args)))

;;;###autoload
(defun ale/rime--candidate-num-format (num select-labels)
  "Format for the number before each candidate."
  (if select-labels
      (format "%s " (nth (1- num) select-labels))
    (format "%d. " num)))

Config

(use-package rime
  :after-call self-insert-command
  :bind
  (("S-SPC" . toggle-input-method)
   :map rime-active-mode-map
   ("C-`" . rime-send-keybinding)
   ("C-k" . rime-send-keybinding)
   ("<C-i>" . rime-send-keybinding)
   ("C-o" . rime-send-keybinding)
   ("C-a" . rime-send-keybinding)
   ("C-e" . rime-send-keybinding)
   ("<escape>" . (lambda () (interactive) (execute-kbd-macro (kbd "C-g"))))
   ([tab] . rime-send-keybinding))
  :config
  (setq default-input-method "rime")
  (setq rime-disable-predicates '(meow-normal-mode-p
                                  meow-motion-mode-p
                                  meow-keypad-mode-p
                                  rime-predicate-after-alphabet-char-p))
  (setq rime-inline-predicates '(rime-predicate-space-after-cc-p
                                 rime-predicate-current-uppercase-letter-p))
  (set-face-attribute 'rime-preedit-face nil
                      :foreground "#80c0e0" :background "#404040"
                      :inverse-video nil :weight 'bold)
  (setq rime-show-candidate 'posframe)
  (setq rime-posframe-style 'vertical)
  (setq rime-posframe-properties
        (list :font "Sarasa Mono SC"
              :internal-border-width 10))
  (setq rime-title "")
  (setq rime-candidate-num-format-function 'ale/rime--candidate-num-format)
  (advice-add 'rime--return :around 'ale/rime-return-advice))

Snippet (yasnippet.el)

(use-package yasnippet
  :after-call self-insert-command
  :config
  (setq! yas-snippet-dirs `(,(expand-file-name "snippets" INIT-DIR)))
  (yas-global-mode))

Pair insertion (eletric.el)

Emacs labels as `electric’ any behaviour that involves contextual auto-insertion of characters.

  • Indent automatically.
  • If electric-pair-mode is enabled (which I might do manually), insert quotes and brackets in pairs. Only do so if there is no alphabetic character after the cursor.
  • To get those numbers, evaluate (string-to-char CHAR) where CHAR is the one you are interested in. For example, get the literal tab’s character with `(string-to-char “\t”)’.
  • While inputting a pair, inserting the closing character will just skip over the existing one, rather than add a new one.
  • Do not skip over whitespace when operating on pairs. Combined with the above point, this means that a new character will be inserted, rather than be skipped over. I find this better, because it prevents the point from jumping forward, plus it allows for more natural editing.
  • The rest concern the conditions for transforming quotes into their curly equivalents. I keep this disabled, because curly quotes are distinct characters. It is difficult to search for them. Just note that on GNU/Linux you can type them directly by hitting the “compose” key and then an angled bracket (< or >) followed by a quote mark.

Autoload

;;; ale/autoload/electric.el --- -*- lexical-binding: t -*-

;;;###autoload
(defun ale-electric-inhibit-< ()
  (setq-local electric-pair-inhibit-predicate
              `(lambda (c) (if (char-equal c ?<) t (,electric-pair-inhibit-predicate c)))))

Config

(use-package electric
  :config
  (advice-add 'electric-pair-post-self-insert-function :around
              (lambda (fn &rest args) (let ((mark-active nil)) (apply fn args))))
  (setq electric-pair-inhibit-predicate 'electric-pair-conservative-inhibit)
  (setq electric-pair-preserve-balance t)
  (setq electric-pair-pairs
        '((8216 . 8217)
          (8220 . 8221)
          (171 . 187)))
  (setq electric-pair-skip-self 'electric-pair-default-skip-self)
  (setq electric-pair-skip-whitespace nil)
  (setq electric-pair-skip-whitespace-chars '(9 10 32))
  (setq electric-quote-context-sensitive t)
  (setq electric-quote-paragraph t)
  (setq electric-quote-string nil)
  (setq electric-quote-replace-double t)
  (electric-indent-mode 1)
  (electric-pair-mode 1)
  (electric-quote-mode -1)
  :hook
  (org-mode . ale-electric-inhibit-<)
  (minibuffer-setup . (lambda () (unless (eq this-command 'eval-expression) (electric-pair-mode 0))))
  (minibuffer-exit . (lambda () (electric-pair-mode 1))))

Parentheses (paren.el / rainbow-delimiters.el)

Configure the mode that highlights matching delimiters or parentheses. I consider this of utmost importance when working with languages such as elisp.

Summary of what these do:

  • Activate the mode upon startup.
  • Show the matching delimiter/parenthesis if on screen, else show nothing. It is possible to highlight the expression enclosed by the delimiters, by using either mixed or expression. The latter always highlights the entire balanced expression, while the former will only do so if the matching delimiter is off screen.
  • show-paren-when-point-in-periphery lets you highlight parentheses even if the point is in their vicinity. This means the beginning or end of the line, with space in between. I used that for a long while and it server me well. Now that I have a better understanding of Elisp, I disable it.
  • Do not highlight a match when the point is on the inside of the parenthesis.
  • Use rainbow color for delimiters
(use-package paren
  :straight (:type built-in)
  :after-call meow-block meow-line self-insert-command
  :config
  (setq show-paren-style 'parenthesis)
  (setq show-paren-when-point-in-periphery nil)
  (setq show-paren-when-point-inside-paren nil)
  (show-paren-mode))

(use-package rainbow-delimiters
  :hook
  (prog-mode . rainbow-delimiters-mode))

Prettify symbols (prog-mode.el)

(use-package prog-mode
  :straight nil
  :hook (prog-mode . prettify-symbols-mode)
  :config
  (setq-default prettify-symbols-alist
                '(("lambda" . )
                  ("<-" . ?←)
                  ("->" . ?→)
                  ("->>" . ?↠)
                  ("=>" . ?⇒)
                  ("/=" . ?≠)
                  ("!=" . ?≠)
                  ("==" . ?≡)
                  ("<=" . ?≤)
                  (">=" . ?≥)
                  ("=<<" . (?= (Br . Bl) ?≪))
                  (">>=" . (?≫ (Br . Bl) ?=))
                  ("<=<" . ?↢)
                  (">=>" . ?↣)))
  (setq prettify-symbols-unprettify-at-point 'right-edge))

File management

Find files (files.el)

(use-package files
  :straight (:type built-in)
  :config
  (setq confirm-kill-processes nil)
  (setq large-file-warning-threshold 50000000)
  (setq permanently-enabled-local-variables '(lexical-binding encoding)))

Recent files (recentf.el)

Keep a record of all recently opened files.

(use-package recentf
  :straight (:type built-in)
  :after-call find-file-hook dirvish
  :config
  (setq recentf-max-saved-items 100)
  (add-to-list 'recentf-exclude (lambda (f) (not (string= (file-truename f) f))))
  (recentf-mode 1))

Restore file place (saveplace.el)

Just remember where the point is in any given file. This can often be a subtle reminder of what you were doing the last time you visited that file, allowing you to pick up from there.

(use-package saveplace
  :straight (:type built-in)
  :after-call find-file-hook
  :config
  (setq save-place-file (locate-user-emacs-file "saveplace"))
  (setq save-place-forget-unreadable-files t)
  (save-place-mode 1))

Auto refresh file content (autorevert.el)

This mode ensures that the buffer is updated whenever the file changes. A change can happen externally or by some other tool inside of Emacs (e.g. kill a Magit diff).

(use-package autorevert
  :straight (:type built-in)
  :after-call self-insert-command
  :config
  (setq auto-revert-verbose t)
  (global-auto-revert-mode))

Dired (dired.el)

Dired is a built-in tool that performs file management operations inside of an Emacs buffer. It is simply superb!

(use-package dired
  :straight (:type built-in)
  :bind
  (nil
   :map dired-mode-map
   ("/" . dired-goto-file)
   ("a" . dired-create-empty-file)
   ("i" . wdired-change-to-wdired-mode)
   ("I" . dired-insert-subdir)
   ("K" . dired-kill-subdir)
   ("O" . dired-find-file-other-window)
   ("<" . beginning-of-buffer)
   (">" . end-of-buffer)
   ("[" . dired-prev-dirline)
   ("]" . dired-next-dirline)
   ("o" . dired-up-directory)
   ("^" . mode-line-other-buffer)
   ("x" . dired-do-delete)
   ("X" . dired-do-flagged-delete)
   ("y" . dired-do-copy)
   ("." . dired-omit-mode))
  :config
  (setq dired-kill-when-opening-new-dired-buffer t) ;; added in emacs 28
  (setq dired-clean-confirm-killing-deleted-buffers nil)
  (setq dired-recursive-copies 'always)
  (setq dired-recursive-deletes 'always)
  (setq delete-by-moving-to-trash t)
  (setq dired-dwim-target t)
  (setq dired-listing-switches "-AGhlv --group-directories-first --time-style=long-iso"))

(use-package dired-x
  :straight (:type built-in)
  :after dired
  :init
  (setq! dired-bind-info nil)
  :config
  (setq dired-omit-files
        (concat dired-omit-files "\\|^\\..*$")))

(use-package dired-aux
  :straight (:type built-in)
  :after dired
  :config
  (setq dired-do-revert-buffer t))

A better dired interface (dirvish.el)

This package empowers dired by giving it a modern UI in a unintrusive way. Emacs users deserve a file manager better than those popular ones on terminal such as ranger, vifm, lf since Emacs is more than a terminal emulator.

(use-package dirvish
  :after-call post-command-hook
  :bind
  (nil
   :map dirvish-mode-map
   ("SPC" . dirvish-show-history)
   ("f"   . dirvish-menu-file-info-cmds)
   ("r"   . dirvish-roam)
   ("M-c" . dirvish-ui-config)
   ("M-f" . dirvish-toggle-fullscreen)
   ("M-l" . dirvish-change-depth)
   ([remap dired-summary] . dirvish-dispatch)
   ([remap dired-do-copy] . dirvish-yank)
   ([remap mode-line-other-buffer] . dirvish-other-buffer)
   :map ale-files-map
   ("a" . dirvish-browse-all-directories)
   ("n" . dirvish)
   ("m" . dirvish-dired)
   ("t" . dirvish-side))
  :config
  (setq! dirvish-roam-dirs-alist ale-roam-dirs-alist)
  (setq dirvish-debug-p t)
  (defun +dirvish-fullframep ()
    (and (dirvish-curr) (not (dirvish-dired-p))))
  (add-to-list 'ale-term-position-alist
               '(+dirvish-fullframep . ((window-height . 0.4) (side . bottom))))
  (dirvish-override-dired-mode)
  (dirvish-yank-display-progress)
  (dirvish-peek-mode)
  (setq dirvish-trash-dir-alist '(("/mnt/HDD/" . ".Trash/files"))))

A fd procelain (fd-dired.el)

(use-package fd-dired
  :after-call dired-noselect
  :bind
  ("C-c f" . fd-dired))

Useful dired extensions (dired-hacks)

(use-package dired-narrow
  :after-call dired-noselect
  :bind
  (nil
   :map dired-mode-map
   ("N" . dired-narrow)))

(use-package dired-subtree
  :after-call dired-noselect
  :bind
  (nil
   :map dired-mode-map
   ("TAB" . dired-subtree-toggle)))

(use-package dired-filter
  :after-call dired-noselect
  :bind
  (:map dired-mode-map
        ([remap dired-omit-mode] . dired-filter-mode)))

Project management (project.el)

Autoload

;;; ale/autoload/project.el --- -*- lexical-binding: t -*-

(require 'cl-lib)
(require 'project)
(require 'vc)

(defgroup ale-project ()
  "Extensions for project.el and related libraries."
  :group 'project)

(defcustom ale-project-commit-log-limit 25
  "Limit commit logs for project to N entries by default.
A value of 0 means 'unlimited'."
  :type 'integer
  :group 'ale-project)

(cl-defmethod project-root ((project (head local)))
  "Project root for PROJECT with HEAD and LOCAL."
  (cdr project))

;; Copied from Manuel Uberti and tweaked accordingly:
;; <https://www.manueluberti.eu/emacs/2020/11/14/extending-project/>.
(defun ale-project--project-files-in-directory (dir)
  "Use `fd' to list files in DIR."
  (unless (executable-find "fd")
    (error "Cannot find 'fd' command is shell environment $PATH"))
  (let* ((default-directory dir)
         (localdir (file-local-name (expand-file-name dir)))
         (command (format "fd -t f -H -0 . %s" localdir)))
    (project--remote-file-names
     (split-string (shell-command-to-string command) "\0" t))))

(cl-defmethod project-files ((project (head vc)) &optional dirs)
  "Override `project-files' to use `fd' in local projects.
Project root for PROJECT with HEAD and VC, plus optional
DIRS."
  (mapcan #'ale-project--project-files-in-directory
          (or dirs (list (project-root project)))))

(defun ale-project--directory-subdirs (dir)
  "Return list of subdirectories in DIR."
  (cl-remove-if (lambda (x) (string-match-p "\\.git" x))
                (cl-remove-if-not (lambda (x) (file-directory-p x))
                                  (directory-files-recursively dir ".*" t t))))

;;;###autoload
(defun ale-project-find-subdir ()
  "Find subdirectories in the current project, using completion."
  (interactive)
  (let* ((pr (project-current t))
         (dir (cdr pr))
         (dirs-raw (ale-project--directory-subdirs dir))
         (subdirs (ale-minibuffer-append-metadata 'file dirs-raw))
         (directory (completing-read "Select Project subdir: " subdirs)))
    (dired directory)))

;;;###autoload
(defun ale-project-commit-log (&optional arg)
  "Print commit log for the current project.
With optional prefix ARG (\\[universal-argument]) shows expanded
commit messages and corresponding diffs.

The log is limited to the integer specified by
`ale-project-commit-log-limit'.  A value of 0 means
'unlimited'."
  (interactive "P")
  (let* ((pr (project-current t))
         (dir (cdr pr))
         (default-directory dir) ; otherwise fails at spontaneous M-x calls
         (backend (vc-responsible-backend dir))
         (num ale-project-commit-log-limit)
         (int (if (numberp num) num (error "%s is not a number" n)))
         (limit (if (= int 0) t int))
         (diffs (if arg 'with-diff nil))
         (vc-log-short-style (unless diffs '(directory))))
    (vc-print-log-internal backend (list dir) nil nil limit diffs)))

;;;###autoload
(defun ale-project-retrieve-tag ()
  "Run `vc-retrieve-tag' on project and switch to the root dir.
Basically switches to a new branch or tag."
  (interactive)
  (let* ((pr (project-current t))
         (dir (cdr pr))
         (default-directory dir) ; otherwise fails at spontaneous M-x calls
         (name
          (vc-read-revision "Tag name: "
                            (list dir)
                            (vc-responsible-backend dir))))
    (vc-retrieve-tag dir name)
    (project-dired)))

(autoload 'magit-status "magit")

;;;###autoload
(defun ale-project-magit-status ()
  "Run `magit-status' on project."
  (interactive)
  (let* ((pr (project-current t))
         (dir (cdr pr)))
    (magit-status dir)))

;;;###autoload
(defun ale-project-find-file (&optional force)
  "Same as `project-find-file' except using magit for project
choosing.
With a universal prefix to choose project anyway."
  (interactive "P")
  (if (or force (null (project-current)))
      (let ((current-prefix-arg '(4))
            (display-buffer-alist '(("magit: .*" (display-buffer-same-window)))))
        (call-interactively 'magit-status))
    (project-find-file)))

Config

(use-package project
  :straight (:type built-in)
  :config
  (setq project-switch-commands
        '((project-find-file "File" ?\r)
          (ale-project-find-subdir "Subdir" ?s)
          (project-find-regexp "Grep" ?g)
          (project-dired "Dired" ?d)
          (ale-project-retrieve-tag "Tag switch" ?t)
          (ale-project-magit-status "Magit" ?m)
          (ale-project-commit-log "Log VC" ?l)))
  (setq ale-project-commit-log-limit 25)
  :bind
  (nil
   :map project-prefix-map
   ("l" . ale-project-commit-log)
   ("m" . ale-project-magit-status)
   ("s" . ale-project-find-subdir)
   ("t" . ale-project-retrieve-tag)))

Writable dired (wdired.el)

(use-package wdired
  :config
  (setq wdired-allow-to-change-permissions t)
  (setq wdired-create-parent-directories t))

Trash (trashed.el)

trashed applies the principles of dired to the management of the user’s filesystem trash. Use C-h m to see the docs and keybindings for its major mode.

Basically, its interaction model is as follows:

  • m to mark for some deferred action, such as D to delete, R to restore.
  • t to toggle the status of all items as marked. Use this without marks to m (mark) all items, then call a deferred action to operate on them.
  • d to mark for permanent deletion.
  • r to mark for restoration.
  • x to execute these special marks.
(use-package trashed
  :config
  (setq trashed-action-confirmer 'y-or-n-p)
  (setq trashed-use-header-line t)
  (setq trashed-sort-key '("Date deleted" . t))
  (setq trashed-date-format "%Y-%m-%d %H:%M:%S"))

Org mode

Org (org.el)

In its purest form, Org is a markup language that is similar to Markdown: symbols are used to denote the meaning of a construct in its context, such as what may represent a headline element or a phrase that calls for emphasis.

What lends Org its super powers though is everything else built around it: a rich corpus of Elisp functions that automate, link, combine, enhance, structure, or otherwise enrich the process of using this rather straightforward system of plain text notation.

Couched in those terms, Org is at once a distribution of well integrated libraries and a vibrant ecosystem that keeps producing new ideas and workflows on how to organise one’s life with plain text.

This section is all about basic configurations for how does a .org file should look like which can be described briefly as follows:

  • use bigger fonts for different levels of heading
  • show ellipsis marker when a node is folded
  • center text when make sense
  • indent text according to outline structure
  • display inline images in url automatically

Autoload

;;; ale/autoload/org.el -*- lexical-binding: t; -*-

;;;###autoload
(defun ale-org-font-setup ()
  "Setup variable-pitch fonts in org-mode."
  (interactive)
  (variable-pitch-mode)
  (let ((variable-pitch `(:font ,(font-chooser! ale-variable-fonts)))
        (default `(:font ,(font-chooser! ale-default-fonts))))
    (custom-theme-set-faces
     'user
     `(org-level-1 ((t (,@variable-pitch :height 1.5))))
     `(org-level-2 ((t (,@variable-pitch :height 1.4))))
     `(org-level-3 ((t (,@variable-pitch :height 1.3))))
     `(org-level-4 ((t (,@variable-pitch :height 1.2))))
     `(org-block ((t (,@default :inherit (default shadow)
                                :height 1.0
                                :background ,(face-attribute 'org-block-begin-line :background)
                                :extend t))))
     `(org-block-begin-line ((t (:foreground "#606060" :extend t))))
     '(org-tag ((t (:inherit (shadow) :weight bold :height 0.8)))))))

Config

(use-package org
  :straight (:type built-in)
  :hook
  (org-mode . ale-org-font-setup)
  (org-mode . org-indent-mode)
  :config
  (setq org-adapt-indentation nil)
  (setq org-hide-leading-stars t)
  (setq org-startup-folded t)
  (setq org-confirm-babel-evaluate nil)
  (setq org-ellipsis "")
  (setq org-agenda-start-with-log-mode t)
  (setq org-log-done 'time)
  (setq org-log-into-drawer t)
  (setq org-image-actual-width nil)
  (setq org-display-remote-inline-images 'download)
  :bind
  (nil
   :map org-mode-map
   ("C-c S-l" . org-toggle-link-display)
   ("C-c C-S-l" . org-insert-last-stored-link)))

Identifiers for org entries (org-id.el)

Autoload

;;; ale/autoload/org-id.el -*- lexical-binding: t; -*-

(require 'org-id)

(defun ale-org-id-new (&optional prefix)
  "Create a new globally unique ID.

An ID consists of two parts separated by a colon:
- a prefix
- a unique part that will be created according to `org-id-method'.

PREFIX can specify the prefix, the default is given by the
variable `org-id-prefix'.  However, if PREFIX is the symbol
`none', don't use any prefix even if `org-id-prefix' specifies
one. So a typical ID could look like \"Org-4nd91V40HI\"."
  (let* ((prefix (if (eq prefix 'none)
                     ""
                   (concat (or prefix org-id-prefix) "-")))
         unique)
    (when (equal prefix "-") (setq prefix ""))
    (cond
     ((memq org-id-method
            '(uuidgen uuid))
      (setq unique (org-trim (shell-command-to-string org-id-uuid-program)))
      (unless (org-uuidgen-p unique)
        (setq unique (org-id-uuid))))
     ((eq org-id-method 'org)
      (let* ((etime (org-reverse-string (org-id-time-to-b36)))
             (postfix (when org-id-include-domain
                        (require 'message)
                        (concat "@"
                                (message-make-fqdn)))))
        (setq unique (concat etime postfix))))
     (t (error "Invalid `org-id-method'")))
    (concat prefix (car (split-string unique "-")))))

;;;###autoload
(defun ale-org-custom-id-get (&optional pom create prefix)
  "Get the CUSTOM_ID property of the entry at point-or-marker POM.

If POM is nil, refer to the entry at point. If the entry does not
have an CUSTOM_ID, the function returns nil. However, when CREATE
is non nil, create a CUSTOM_ID if none is present already. PREFIX
will be passed through to `ale-org-id-new'. In any case, the
CUSTOM_ID of the entry is returned."
  (interactive)
  (org-with-point-at pom
    (let* ((orgpath (mapconcat #'identity (org-get-outline-path) "-"))
           (heading (replace-regexp-in-string
                     "/\\|~\\|\\[\\|\\]" ""
                     (replace-regexp-in-string
                      "[[:space:]]+" "_" (if (string= orgpath "")
                                             (org-get-heading t t t t)
                                           (concat orgpath "-" (org-get-heading t t t t))))))
           (id (org-entry-get nil "CUSTOM_ID")))
      (cond
       ((and id (stringp id) (string-match "\\S-" id))
        id)
       (create (setq id (ale-org-id-new (concat prefix heading)))
               (org-entry-put pom "CUSTOM_ID" id)
               (org-id-add-location id
                                    (buffer-file-name (buffer-base-buffer)))
               id)))))

;;;###autoload
(defun ale-org-add-ids-to-headlines-in-file (&optional force)
  "Add CUSTOM_ID properties to all headlines in the current file
which do not already have one.

Only adds ids if the `auto-id' option is set to `t' in the file
somewhere. ie, #+OPTIONS: auto-id:t"
  (interactive "P")
  (save-excursion
    (widen)
    (goto-char (point-min))
    (when (re-search-forward "^#\\+OPTIONS:.*auto-id:t" (point-max) t)
      (when force
        (org-map-entries (lambda () (org-entry-delete nil "CUSTOM_ID"))))
      (org-map-entries (lambda () (ale-org-custom-id-get (point) 'create))))))

;;;###autoload
(defun ale-org-id-update ()
  (add-hook 'before-save-hook
            (lambda ()
              (when (and (eq major-mode 'org-mode)
                         (eq buffer-read-only nil))
                (ale-org-add-ids-to-headlines-in-file)))))

Config

(use-package org-id
  :straight (:type built-in)
  :config
  (setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id)
  :hook
  (org-mode . ale-org-id-update))

Literate programming (ob.el)

Thanks to https://blog.d46.us/advanced-emacs-startup

(use-package ob
  :straight (:type built-in)
  :after-call org-cycle
  :hook (org-babel-after-execute . org-redisplay-inline-images)
  :config
  (use-package org-tempo
    :straight (:type built-in)
    :after-call self-insert-command
    :config
    (add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp")))
  (setq org-babel-default-header-args:sh    '((:results . "output replace"))
        org-babel-default-header-args:bash  '((:results . "output replace"))
        org-babel-default-header-args:shell '((:results . "output replace"))))

Source code block (org-src.el)

(use-package org-src
  :straight (:type built-in)
  :after-call org-mode
  :config
  (push '("conf-unix" . conf-unix) org-src-lang-modes)
  (setq org-edit-src-content-indentation 0)
  (setq org-src-window-setup 'split-window-right))

Bullet (org-superstar.el)

(use-package org-superstar
  :config
  (setq org-superstar-item-bullet-alist '((?* . ?•) (?+ . ?+) (?- . ?•)))
  (setq org-superstar-remove-leading-stars t)
  (setq org-superstar-headline-bullets-list '("" "" "" "" "" "" ""))
  :hook
  (org-mode . org-superstar-mode))

Reveal invisible org elements (org-appear.el)

(use-package org-appear
  :hook (org-mode . org-appear-mode)
  :config
  (setq org-hide-emphasis-markers t))

Habit (org-habit.el)

(use-package org-habit
  :straight (:type built-in)
  :config
  (add-to-list 'org-modules 'org-habit)
  (setq org-habit-graph-column 60))

Wiki (org-roam.el)

(use-package org-roam
  :init
  (setq org-id-link-to-org-use-id t)
  (setq org-roam-v2-ack t)
  :custom
  (org-roam-directory (file-truename "~/Documents/roam"))
  (org-roam-completion-everywhere t)
  :bind
  (nil
   :map ale-org-map
   ("l" . org-roam-buffer-toggle)
   ("f" . org-roam-node-find)
   ("g" . org-roam-graph)
   ("i" . org-roam-node-insert)
   ("c" . org-roam-capture)
   ("j" . org-roam-dailies-capture-today))
  :config
  (org-roam-setup))

Slide (org-tree-slide.el)

Autoload

;;; ale/autoload/org-tree-slide.el -*- lexical-binding: t; -*-

(defcustom +org-tree-slide-text-scale 1.5
  "Text scaling for `org-tree-slide-mode'."
  :group 'org-tree-slide
  :type 'number)

(defcustom +org-tree-hide-elements
  '("^[[:space:]]*\\(#\\+\\)\\(\\(?:BEGIN\\|END\\|ATTR\\)[^[:space:]]+\\).*" ; src block
    "^\\(\\*+\\)"                                                            ; leading stars
    "\\(^:PROPERTIES:\\(.*\n\\)+?:END:\\)")                                  ; :PROPERTIES:.*:END:
  "Regexps of org elements to hide in `org-tree-slide-mode'."
  :group 'org-tree-slide
  :type '(repeat string))

;;;###autoload
(defun +org-tree-slide--simple-header (blank-lines)
  "Set the header with overlay.

Some number of BLANK-LINES will be shown below the header."
  (org-tree-slide--hide-slide-header)
  (setq org-tree-slide--header-overlay
        (make-overlay (point-min) (+ 1 (point-min))))
  (overlay-put org-tree-slide--header-overlay
               'face
               'org-tree-slide-header-overlay-face)
  (if org-tree-slide-header
      (overlay-put org-tree-slide--header-overlay 'display
                   (concat
                    (when org-tree-slide-breadcrumbs
                      (concat "\n" (org-tree-slide--get-parents
                                    org-tree-slide-breadcrumbs)))
                    (org-tree-slide--get-blank-lines blank-lines)))
    (overlay-put org-tree-slide--header-overlay 'display
                 (org-tree-slide--get-blank-lines blank-lines))))

;;;###autoload
(defun +org-tree-slide-hide-elements-h ()
  "Hide org constructs defined in `+org-tree-hide-elements'."
  (dolist (reg +org-tree-hide-elements)
    (save-excursion
      (goto-char (point-min))
      (while (re-search-forward reg nil t)
        (org-flag-region (match-beginning 1) (match-end 0) org-tree-slide-mode t)))))

;;;###autoload
(defun +org-tree-slide-prettify-slide-h ()
  "Set up the org window for presentation."
  (cond (org-tree-slide-mode
         (when (bound-and-true-p flyspell-mode) (flyspell-mode -1))
         (text-scale-set +org-tree-slide-text-scale)
         (ale-monocle-mode +1)
         (when (fboundp 'writeroom-mode) (writeroom-mode +1))
         (ignore-errors (org-latex-preview '(4))))
        (t
         (text-scale-set 0)
         (when (fboundp 'writeroom-mode) (writeroom-mode -1))
         (ale-monocle-mode -1)
         (+frame-cursor-dim-mode -1)
         (org-clear-latex-preview)
         (org-mode))))

Config

org-tree-slide.el is a presentation tool using org-mode.

(use-package org-tree-slide
  :bind
  (nil
   :map org-tree-slide-mode-map
   ("<left>" . org-tree-slide-move-previous-tree)
   ("<right>" . org-tree-slide-move-next-tree)
   :map ale-org-map
   ("S" . org-tree-slide-mode))
  :config
  (setq org-tree-slide-activate-message " ")
  (setq org-tree-slide-deactivate-message " ")
  (setq org-tree-slide-modeline-display nil)
  (setq org-tree-slide-heading-emphasis t)
  (setq org-tree-slide-breadcrumbs
        (propertize "" 'display `(height ,(face-attribute 'org-level-1 :height))))
  (add-hook 'org-tree-slide-after-narrow-hook #'org-display-inline-images)
  (add-hook 'org-tree-slide-after-narrow-hook #'+frame-cursor-dim-mode)
  (add-hook 'org-tree-slide-mode-hook #'+org-tree-slide-hide-elements-h)
  (add-hook 'org-tree-slide-play-hook #'+org-tree-slide-hide-elements-h)
  (add-hook 'org-tree-slide-mode-hook #'+org-tree-slide-prettify-slide-h)
  (advice-add 'org-tree-slide--set-slide-header :override '+org-tree-slide--simple-header))

User interface

Theme

modus-vivendi is a built-in theme in emacs (version >= 28) created by Protesilaos Stavrou.

(setq modus-themes-links '(no-underline))
(setq modus-themes-mode-line '(borderless))
(add-hook 'emacs-startup-hook (lambda () (load-theme 'modus-vivendi)))

Fonts (fonts.el)

Autoload

;;; ale/autoload/fonts.el --- -*- lexical-binding: t -*-

(require 'cl-lib)

;;;###autoload
(defun ale-font-setup ()
  "Setup default/fixed-pitch/variable-pitch/zh-font."
  (interactive)
  (custom-theme-set-faces
   'user
   '(font-lock-keyword-face ((t (:slant italic))))
   '(font-lock-variable-name-face ((t (:weight demibold))))
   '(font-lock-function-name-face ((t (:weight demibold)))))
  (when-let ((default (font-chooser! ale-default-fonts))
             (fixed-pitch (font-chooser! ale-fixed-fonts))
             (variable-pitch (font-chooser! ale-variable-fonts))
             (zh-font (font-spec :family (font-chooser! ale-zh-fonts))))
    (custom-theme-set-faces
     'user
     `(default ((t (:font ,(font-spec :family default :size ale-font-size)))))
     `(fixed-pitch ((t (:font ,(font-spec :family fixed-pitch :size ale-font-size)))))
     `(variable-pitch ((t (:font ,(font-spec :family variable-pitch :size ale-font-size))))))
    (unless (equal zh-font (font-spec :family variable-pitch))
      (setq face-font-rescale-alist (list (cons zh-font ale-zh-font-scale))))
    (dolist (charset '(kana han cjk-misc bopomofo))
      (set-fontset-font (frame-parameter nil 'font) charset zh-font))
    (when (< emacs-major-version 28)
      (set-fontset-font t 'symbol (font-spec :family "Noto Color Emoji")))))

;;;###autoload
(defun ale-font-cn-set-title (beg end)
  (interactive "r")
  (remove-overlays beg end)
  (let ((ov (make-overlay beg end)))
    (overlay-put ov 'display '(height 1.5))))

;;;###autoload
(defun ale-font-cn-set-quote (beg end)
  (interactive "r")
  (remove-overlays beg end)
  (let ((ov (make-overlay beg end)))
    (overlay-put ov 'face 'font-lock-comment-face)))

Config

(frame-enable! 'ale-font-setup)

;; https://lists.gnu.org/archive/html/emacs-devel/2021-11/msg01636.html
(defun +modeline-font-temp-fix ()
  (when-let* ((font-name (font-chooser! ale-default-fonts))
              (font (font-spec :family font-name :size ale-font-size)))
    (set-face-attribute 'mode-line nil :font font)
    (set-face-attribute 'mode-line-inactive nil :font font)))

(frame-enable! '+modeline-font-temp-fix)

Frame (frame.el)

Frame settings.

Autoload

;;; ale/autoload/frame.el --- -*- lexical-binding: t -*-

(defvar +frame-cursor-saved-color
  (frame-parameter nil 'cursor-color))

(defcustom +frame-cursor-dim-color "#606060"
  "Cursor color for `+frame-cursor-dim-mode'."
  :group 'cursor :type 'string)

;;;###autoload
(define-minor-mode +frame-cursor-dim-mode
  "Enable dimmed `cursor-color' for current frame."
  :global t
  :lighter nil
  :group 'cursor
  (if +frame-cursor-dim-mode
      (progn
        (setq-local cursor-type nil)
        (blink-cursor-mode -1)
        (set-cursor-color +frame-cursor-dim-color))
    (blink-cursor-mode +1)
    (set-cursor-color +frame-cursor-saved-color)))

Tab (+tab.el)

Tabs for frequently used buffers.

Autoload

;;; ale/autoload/+tab.el --- Tabs for frequently used buffers -*- lexical-binding: t -*-

(require 'cl-lib)

(defvar ale-tab-count-freq-idle-time 1
  "Add used frequency after being idle for this much secs.")

(defvar ale-tab-visible-buffer-limit 4
  "Max number of visible buffers in a group.")

(defvar ale-tab-project-root-function
  (lambda ()
    (when-let ((project (project-current nil)))
      (expand-file-name (cdr project))))
  "A function that returns project root for current buffer.")

(defvar ale-tab-separator "|"
  "A string used as separator between tabs.")

(defvar ale-tab-update-hook nil
  "Hook to run when tabs need to be redisplayed.
You should customize this hook to run code that's needed to
update the UI.  `ale-tab-string' can be used in the code.")

(defface ale-tab-active-tab-face
  '((((background light))
     :background "#d5c9c0" :foreground "#282828"
     :bold t :inherit mode-line-active)
    (t
     :background "#504945" :foreground "#fbf1c7"
     :bold t :inherit mode-line-active))
  "Face for current tab.")

(defface ale-tab-inactive-tab-face
  '((((background light))
     :foreground "#665c54" :inherit 'mode-line-active)
    (t
     :foreground "#bdae93" :bold nil :inherit 'mode-line-active))
  "Face for inactive tabs.")

(defface ale-tab-separator-face
  '((((background light))
     :foreground "#bdae93" :bold t :inherit 'mode-line-active)
    (t
     :foreground "#665c54" :bold t :inherit 'mode-line-active))
  "Face for separator.")

(defface ale-tab-rest-face
  '((t :bold t :inherit 'font-lock-comment-face))
  "Face for current tab.")

(defvar-local ale-tab/buffer-group nil)

(defun ale-tab/buffer-group (&optional buffer)
  "Return the group name of BUFFER.
When BUFFER is nil, use current buffer."
  (let* ((buffer (or buffer (current-buffer)))
         (name (buffer-name buffer))
         group)
    (cond
     ;; These should be hidden.
     ((eq (aref name 0) ?\s) nil)
     ((eq (aref name 0) ?*) nil)
     (t "*Others*"))))

(defvar ale-tab/timer nil
  "Timer used for count buffer used frequency.")

(defvar-local ale-tab/buffer-freq 0
  "Used frequency of current buffer.")

(defun ale-tab/buffer-freq (buf)
  "Return the used frequency of buffer BUF."
  (or (buffer-local-value 'ale-tab/buffer-freq buf)
      0))

(defun ale-tab/increase-buffer-freq ()
  "Increase the used frequency of current buffer by 1."
  (cl-incf ale-tab/buffer-freq))

(defun ale-tab/buffer-freq-higher-p (buf1 buf2)
  "Return t if the used frequency of BUF1 is higher than BUF2."
  (> (ale-tab/buffer-freq buf1)
     (ale-tab/buffer-freq buf2)))

(defun ale-tab/insert-buf (buf bufs)
  "Insert BUF into sorted BUFS.
BUFS is sorted in the decreasing order of used frequency.  The
insertion keeps this order.
This is non-destructive and the list after insertion is returned."
  (let ((freqs (mapcar #'ale-tab/buffer-freq bufs))
        (freq (ale-tab/buffer-freq buf))
        idx)
    (cl-dotimes (n (length freqs))
      (when (> freq (nth n freqs))
        (setq idx n)
        (cl-return nil)))
    (if (null idx)
        (append bufs (list buf))
      (append (cl-subseq bufs 0 idx)
              (list buf)
              (cl-subseq bufs idx)))))

(defun ale-tab-start-count-freq ()
  "Start counting buffer used frequency."
  (setq ale-tab/timer
        (run-with-idle-timer ale-tab-count-freq-idle-time
                             t #'ale-tab/increase-buffer-freq)))

(defun ale-tab-stop-count-freq ()
  "Stop counting buffer used frequency."
  (cancel-timer ale-tab/timer)
  (setq ale-tab/timer nil))

(defvar ale-tab/sorted-buffer-list nil
  "Buffer list sorted by used frequency.
This contains all non-hidden buffers returned by `buffer-list'.
It's updated by `ale-tab/update-buffer-list'.")

(defvar ale-tab/last-active-buffer nil
  "Last active buffer.
Minibuffer doesn't count.  This is updated by
`ale-tab/update-buffer-list'.")

(defvar ale-tab/inhibit-resort nil
  "Non-nil means don't resort `ale-tab/sorted-buffer-list'.")

(defun ale-tab/update-buffer-list ()
  "Update internal data when appropriate."
  (unless (window-minibuffer-p)
    (let ((current-buffer (current-buffer)))
      ;; We don't update when the current buffer is not changed, and non of the
      ;; non-hidden buffers is killed.
      (unless (and (eq current-buffer ale-tab/last-active-buffer)
                   (cl-every #'buffer-live-p
                             ale-tab/sorted-buffer-list))
        (unless ale-tab/inhibit-resort
          (let ((bufs (buffer-list)))
            (setq bufs (cl-remove-if-not #'ale-tab/buffer-group bufs))
            (setq bufs (sort bufs #'ale-tab/buffer-freq-higher-p))
            (setq ale-tab/sorted-buffer-list bufs)))
        (run-hooks 'ale-tab-update-hook)
        (setq ale-tab/last-active-buffer (current-buffer))))))

(defun ale-tab-visible-tabs-and-remain-num ()
  "Return the visible tabs and number of remaining tabs in a cons cell.
When the current buffer is a hidden buffer, return nil."
  (let* ((buf (current-buffer))
         (group (ale-tab/buffer-group buf))
         (counter 0)
         tabs)
    (when group
      (dolist (b ale-tab/sorted-buffer-list)
        (when (equal (ale-tab/buffer-group b) group)
          (if (< (length tabs) ale-tab-visible-buffer-limit)
              (push b tabs)
            (cl-incf counter))))
      (cons (nreverse tabs)
            counter))))

(defun ale-tab-visible-tabs ()
  "Return the visible tabs."
  (let* ((buf (current-buffer))
         (group (ale-tab/buffer-group buf))
         tabs)
    (when group
      (cl-dolist (b ale-tab/sorted-buffer-list)
        (when (equal (ale-tab/buffer-group b) group)
          (push b tabs)
          (when (= (length tabs) ale-tab-visible-buffer-limit)
            (cl-return))))
      (nreverse tabs))))

(defun ale-tab-string ()
  "Return a string that shows the tabs for current buffer.
Possible ways of using this string is to show it in the mode line
or header line, see `mode-line-format' and `header-line-format'
for instructions.  See \"site-lisp/toki-modeline.el\" for
example.
The string may look like
    tab1 | tab2 | 2+..tab5
\"tab1\" and \"tab2\" are the actual tabs, \"2+\" means there are
2 more buffers in the current group that's not shown.  When the
current buffer is one of these invisible buffers (\"tab5\" in
this case), it is shown after the \"1+\" part.
Notice that though \"tab5\" is shown to indicate the current
buffer, technically it's not in the tabs, and is still considered
\"invisible\" by `ale-tab-kill-invisible-buffers-in-group'.
Current and non-active buffers are distinguished by faces."
  (let* ((current-buf (current-buffer))
         (tabs-and-remain (ale-tab-visible-tabs-and-remain-num))
         (tabs (car tabs-and-remain))
         (tab-visible-p (memq current-buf tabs))
         (num (cdr tabs-and-remain))
         (rest (unless (or (eq num 0) (null num))
                 (propertize (concat " + " (number-to-string num) "..")
                             'face 'ale-tab-rest-face)))
         (get-string (lambda (buf)
                       (if (eq buf current-buf)
                           (propertize (concat " " (buffer-name buf) " ")
                                       'face 'ale-tab-active-tab-face)
                         (propertize (concat " " (buffer-name buf) " ")
                                     'face 'ale-tab-inactive-tab-face))))
         (separator (propertize ale-tab-separator
                                'face 'ale-tab-separator-face)))
    (when (and rest (not tab-visible-p))
      (setq rest (concat rest
                         (propertize (buffer-name current-buf)
                                     'face 'ale-tab-active-tab-face))))
    (if tabs
        (string-join (nconc (mapcar get-string tabs) (when rest (list rest)))
                     separator)
      (propertize (concat " " (buffer-name current-buf) " ") 'face 'ale-tab-active-tab-face))))

(defun ale-tab-previous ()
  "Switch to the previous tab.
When the current buffer is the first tab, or not in the tabs,
switch to the last tab."
  (interactive)
  (let* ((buf (current-buffer))
         (tabs (ale-tab-visible-tabs))
         (idx (cl-position buf tabs :test #'eq))
         (ale-tab/inhibit-resort t))
    (cond
     ((or (null idx) (eq idx 0))
      (switch-to-buffer (car (last tabs))))
     (t
      (switch-to-buffer (nth (1- idx) tabs))))))

(defun ale-tab-next ()
  "Switch to the next tab.
When the current buffer is the last tab, or not in the tabs,
switch to the first tab."
  (interactive)
  (let* ((buf (current-buffer))
         (tabs (ale-tab-visible-tabs))
         (idx (cl-position buf tabs :test #'eq))
         (ale-tab/inhibit-resort t))
    (cond
     ((or (null idx) (eq idx (1- (length tabs))))
      (switch-to-buffer (car tabs)))
     (t
      (switch-to-buffer (nth (1+ idx) tabs))))))

(defun ale-tab-kill-invisible-buffers-in-group ()
  "Kill all buffers that's not in the tabs in current group.
Notice when the current buffer is not in the tabs, though it may
still be shown after the \"n+\" part in the tabs, it will be
killed."
  (interactive)
  (when-let ((group (ale-tab/buffer-group (current-buffer)))
             (tabs (ale-tab-visible-tabs)))
    (dolist (b (buffer-list))
      (when (and (equal (ale-tab/buffer-group b) group)
                 (not (memq b tabs)))
        (kill-buffer b)))))

(defun ale-tab-kill-buffers-in-group ()
  "Kill all buffers in current group."
  (interactive)
  (when-let ((group (ale-tab/buffer-group (current-buffer))))
    (dolist (b (buffer-list))
      (when (equal (ale-tab/buffer-group b) group)
        (kill-buffer b)))))

(defun ale-tab-switch-to-buffer-in-group ()
  "Switch to a buffer in current group."
  (interactive)
  (when-let ((group (ale-tab/buffer-group (current-buffer))))
    (let (bufs collection)
      (dolist (b (buffer-list))
        (when (equal (ale-tab/buffer-group b) group)
          (push b bufs)))
      (setq bufs (mapcar #'buffer-name (nreverse bufs)))
      (setq collection
            (lambda (str pred action)
              (if (eq action 'metadata)
                  '(metadata
                    (category . buffer)
                    (cycle-sort-function . identity)
                    (display-sort-function . identity))
                (complete-with-action action bufs str pred))))
      (switch-to-buffer
       (completing-read "Switch to: " collection nil t)))))

;;;###autoload
(define-minor-mode ale-tab-mode
  "Minor mode for maintaining data for showing tabs.
This mode doesn't offer an UI for showing the tabs.  See
`ale-tab-update-hook' and `ale-tab-string' to know how to
plug-in an UI for tabs."
  :global t
  (if ale-tab-mode
      (progn
        (ale-tab-start-count-freq)
        (ale-tab/update-buffer-list)
        (add-hook 'buffer-list-update-hook
                  #'ale-tab/update-buffer-list))
    (ale-tab-stop-count-freq)
    (remove-hook 'buffer-list-update-hook
                 #'ale-tab/update-buffer-list)))

Config

(ale-tab-mode)

Modeline (+modeline.el)

ale-modeline-mode provides following infos in modeline:

  • Meow current state (INSERT/NORMAL…)
  • Filename
  • Current line / column
  • Total lines
  • Git branch

Autoload

;;; ale/autoload/+modeline.el --- A minimal mode-line. -*- lexical-binding: t; -*-

(defvar flycheck-current-errors)
(defvar anzu--state)
(defvar anzu--cached-count)
(defvar anzu--overflow-p)
(defvar anzu--current-position)
(defvar anzu--total-matched)
(defvar multiple-cursors-mode)
(declare-function flycheck-count-errors "flycheck" (errors))
(declare-function meow-indicator "meow")
(declare-function ale-tab-string "autoload/tab")

(defgroup ale-modeline nil
  "A minimal mode-line configuration inspired by doom-modeline."
  :group 'mode-line)

(defcustom ale-modeline-show-eol-style nil
  "If t, the EOL style of the current buffer will be displayed in the mode-line."
  :group 'ale-modeline
  :type 'boolean)

(defcustom ale-modeline-show-encoding-information nil
  "If t, the encoding format of the current buffer will be displayed in the mode-line."
  :group 'ale-modeline
  :type 'boolean)

(defcustom ale-modeline-show-cursor-point nil
  "If t, the value of `point' will be displayed next to the cursor position in the mode-line."
  :group 'ale-modeline
  :type 'boolean)

(defface ale-modeline-input-method
  '((t (:inherit (bold))))
  "Face used for input method indicator in the mode-line."
  :group 'ale-modeline)

(defface ale-modeline-status-neutral
  '((t (:inherit (shadow bold))))
  "Face used for neutral or inactive status indicators in the mode-line."
  :group 'ale-modeline)

(defface ale-modeline-status-info
  '((t (:inherit (font-lock-constant-face bold))))
  "Face used for generic status indicators in the mode-line."
  :group 'ale-modeline)

(defface ale-modeline-status-success
  '((t (:inherit (success))))
  "Face used for success status indicators in the mode-line."
  :group 'ale-modeline)

(defface ale-modeline-status-warning
  '((t (:inherit (warning))))
  "Face for warning status indicators in the mode-line."
  :group 'ale-modeline)

(defface ale-modeline-status-error
  '((t (:inherit (error))))
  "Face for error stauts indicators in the mode-line."
  :group 'ale-modeline)

(defface ale-modeline-unimportant
  '((t (:inherit (shadow))))
  "Face used for less important mode-line elements."
  :group 'ale-modeline)

(defface ale-modeline-modified
  '((t (:inherit (error))))
  "Face used for the 'modified' indicator symbol in the mode-line."
  :group 'ale-modeline)

(defun ale-modeline--string-trim-left (string)
  "Remove whitespace at the beginning of STRING."
  (if (string-match "\\`[ \t\n\r]+" string)
      (replace-match "" t t string)
    string))

(defun ale-modeline--string-trim-right (string)
  "Remove whitespace at the end of STRING."
  (if (string-match "[ \t\n\r]+\\'" string)
      (replace-match "" t t string)
    string))

(defun ale-modeline--string-trim (string)
  "Remove whitespace at the beginning and end of STRING."
  (ale-modeline--string-trim-left (ale-modeline--string-trim-right string)))

(defun ale-modeline--format (left right)
  "Return a string of `window-width' length containing LEFT and
RIGHT, aligned respectively."
  (let ((reserve (string-width right)))
    (concat left
            (propertize " " 'display
                        `((space :align-to (- (+ right right-fringe right-margin) ,reserve))))
            right)))

(defun ale-modeline--place-segment (segment)
  "If SEGMENT is non-nil, return its context with a suffix
whitespace, else return nil."
  (let* ((seg (intern (format "ale-modeline-segment-%s" segment)))
         (str (funcall seg)))
    (when (and str (not (string= str "")))
      (concat str " "))))

(defvar-local ale-modeline--vc-text nil)
(defun ale-modeline--update-vc-segment (&rest _)
  "Update `ale-modeline--vc-text' against the current VCS state."
  (setq ale-modeline--vc-text
        (when (and vc-mode buffer-file-name)
          (let ((backend (vc-backend buffer-file-name))
                (state (vc-state buffer-file-name (vc-backend buffer-file-name))))
            (let ((face 'mode-line-neutral))
              (concat (cond ((memq state '(edited added))
                             (setq face 'ale-modeline-status-info)
                             (propertize "+ " 'face face))
                            ((eq state 'needs-merge)
                             (setq face 'ale-modeline-status-warning)
                             (propertize "<> " 'face face))
                            ((eq state 'needs-update)
                             (setq face 'ale-modeline-status-warning)
                             (propertize "" 'face face))
                            ((memq state '(removed conflict unregistered))
                             (setq face 'ale-modeline-status-error)
                             (propertize "" 'face face))
                            (t
                             (setq face 'ale-modeline-status-neutral)
                             (propertize "· " 'face face)))
                      (propertize (substring vc-mode (+ (if (eq backend 'Hg) 2 3) 2))
                                  'face face
                                  'mouse-face face)))))))

(defvar-local ale-modeline--flycheck-text nil)
(defun ale-modeline--update-flycheck-segment (&optional status)
  "Update `ale-modeline--flycheck-text' against the reported flycheck STATUS."
  (setq ale-modeline--flycheck-text
        (pcase status
          ('finished (if flycheck-current-errors
                         (let-alist (flycheck-count-errors flycheck-current-errors)
                           (let ((sum (+ (or .error 0) (or .warning 0))))
                             (propertize (concat "⚑ Issues: " (number-to-string sum))
                                         'face (if .error
                                                   'ale-modeline-status-error
                                                 'ale-modeline-status-warning))))
                       (propertize " ✓ Good " 'face 'ale-modeline-status-success)))
          ('running (propertize " Δ Checking " 'face 'ale-modeline-status-info))
          ('errored (propertize " ✖ Error " 'face 'ale-modeline-status-error))
          ('interrupted (propertize " ⏵ Paused " 'face 'ale-modeline-status-neutral))
          ('no-checker ""))))

(defvar-local ale-modeline-total-lines "")
(defun ale-modeline-count-lines ()
  (setq ale-modeline-total-lines (int-to-string (count-lines (point-min) (point-max)))))

(defun ale-modeline-segment-total-lines ()
  "Display total lines of current buffer."
  (propertize ale-modeline-total-lines 'face 'ale-modeline-unimportant))

(defun ale-modeline-segment-editing-state ()
  "Display current input state."
  (when (bound-and-true-p meow-mode)
    (ale-modeline--string-trim (meow-indicator))))

(defsubst ale-modeline-segment-macro-recording ()
  "Display current macro being recorded."
  (when (or defining-kbd-macro executing-kbd-macro)
    (propertize "KM" 'face 'warning)))

(defun ale-modeline-segment-modified ()
  "Displays a color-coded buffer modification/read-only indicator in the mode-line."
  (when (not (string-match-p "\\*.*\\*" (buffer-name)))
    (if (buffer-modified-p)
        (propertize "" 'face 'ale-modeline-modified)
      (when (and buffer-read-only (buffer-file-name))
        (propertize "" 'face 'ale-modeline-unimportant)))))

(defun ale-modeline-segment-anzu ()
  "Displays color-coded anzu status information in the mode-line (if available)."
  (when (and (boundp 'anzu--state) anzu--state)
    (cond ((eq anzu--state 'replace-query)
           (format #("Replace: %d  " 0 11 (face ale-modeline-status-warning)) anzu--cached-count))
          (anzu--overflow-p
           (format #("%d/%d+  " 0 3 (face ale-modeline-status-info) 3 6 (face ale-modeline-status-error)) anzu--current-position anzu--total-matched))
          (t
           (format #("%d/%d  " 0 5 (face ale-modeline-status-info)) anzu--current-position anzu--total-matched)))))

(defun ale-modeline-segment-position ()
  "Displays the current cursor position in the mode-line."
  (concat "%l:%c"
          (when ale-modeline-show-cursor-point (propertize (format ":%d" (point)) 'face 'ale-modeline-unimportant))))

(defun ale-modeline-segment-eol ()
  "Displays the EOL style of the current buffer in the mode-line."
  (when ale-modeline-show-eol-style
    (pcase (coding-system-eol-type buffer-file-coding-system)
      (0 "LF")
      (1 "CRLF")
      (2 "CR"))))

(defun ale-modeline-segment-encoding ()
  "Displays the encoding and EOL style of the buffer in the mode-line."
  (when ale-modeline-show-encoding-information
    (let ((sys (coding-system-plist buffer-file-coding-system)))
      (cond ((memq (plist-get sys :category) '(coding-category-undecided coding-category-utf-8))
             "UTF-8")
            (t (upcase (symbol-name (plist-get sys :name))))))))

(defun ale-modeline-segment-vc ()
  "Displays color-coded version control information in the mode-line."
  ale-modeline--vc-text)

(defun ale-modeline-segment-tab ()
  "Return tabs."
  (when (bound-and-true-p ale-tab-mode) (ale-tab-string)))

(defun ale-modeline-segment-input-method ()
  "Displays the current major mode in the mode-line."
  (when (bound-and-true-p rime-mode) (ale-modeline--string-trim (rime-lighter))))

(defun ale-modeline-segment-flycheck ()
  "Displays color-coded flycheck information in the mode-line (if available)."
  ale-modeline--flycheck-text)

(defvar ale-modeline--default-mode-line mode-line-format
  "Store the default mode-line format")

;;;###autoload
(define-minor-mode ale-modeline-mode
  "Toggle ale-modeline on or off."
  :group 'ale-modeline
  :global t
  :lighter nil
  (if ale-modeline-mode
      (progn
        (add-hook 'flycheck-status-changed-functions #'ale-modeline--update-flycheck-segment)
        (add-hook 'flycheck-mode-hook #'ale-modeline--update-flycheck-segment)
        (add-hook 'after-save-hook #'ale-modeline--update-vc-segment)
        (add-hook 'find-file-hook 'ale-modeline-count-lines)
        (add-hook 'after-save-hook 'ale-modeline-count-lines)
        (add-hook 'after-revert-hook 'ale-modeline-count-lines)
        (advice-add #'vc-refresh-state :after #'ale-modeline--update-vc-segment)
        (setq-default mode-line-format
                      '((:eval
                         (ale-modeline--format
                          (format-mode-line
                           '(" "
                             (:eval (ale-modeline--place-segment 'editing-state))
                             (:eval (ale-modeline--place-segment 'macro-recording))
                             (:eval (ale-modeline--place-segment 'modified))
                             (:eval (ale-modeline--place-segment 'tab))))
                          (format-mode-line
                           '((:eval (ale-modeline--place-segment 'anzu))
                             (:eval (ale-modeline--place-segment 'position))
                             (:eval (ale-modeline--place-segment 'total-lines))
                             (:eval (ale-modeline--place-segment 'eol))
                             (:eval (ale-modeline--place-segment 'encoding))
                             (:eval (ale-modeline--place-segment 'vc))
                             (:eval (ale-modeline--place-segment 'input-method))
                             (:eval (ale-modeline--place-segment 'flycheck)))))))))
    (progn
      (remove-hook 'find-file-hook #'ale-modeline-count-lines)
      (remove-hook 'after-save-hook #'ale-modeline-count-lines)
      (remove-hook 'after-revert-hook #'ale-modeline-count-lines)
      (remove-hook 'flycheck-status-changed-functions #'ale-modeline--update-flycheck-segment)
      (remove-hook 'flycheck-mode-hook #'ale-modeline--update-flycheck-segment)
      (remove-hook 'after-save-hook #'ale-modeline--update-vc-segment)
      (advice-remove #'vc-refresh-state #'ale-modeline--update-vc-segment)
      (setq-default mode-line-format ale-modeline--default-mode-line))))

Config

(frame-enable! 'ale-modeline-mode)

Frame margin (fringe.el)

Create a 20 pixel margin for emacs frame.

(add-to-list 'default-frame-alist '(internal-border-width . 20))
(add-to-list 'default-frame-alist '(left-fringe . 1))
(add-to-list 'default-frame-alist '(right-fringe . 1))
(fringe-mode '(1 . 1))

Pixel scrolling (pixel-scroll.el)

Pixelwise scrolling in emacs. This was added in emacs version > 29, you need to add --with-xinput2 in build flags to enable this feature.

(when (boundp 'pixel-scroll-precision-mode)
  (pixel-scroll-precision-mode 1))

Icons (vscode-icon.el)

This package is a utility for using and formatting various Icon fonts within Emacs. Icon Fonts allow you to propertize and format icons the same way you would normal text. This enables things such as better scaling of and anti aliasing of the icons.

(use-package vscode-icon)

A colorful dired (diredfl.el)

Additional syntax highlighting in dired buffer.

(use-package diredfl
  :hook (dired-mode . diredfl-mode)
  :config
  (set-face-attribute 'diredfl-dir-name nil :bold t))

Interactive query replace (anzu.el)

anzu.el provides a minor mode which displays ‘current match/total matches’ in the mode-line in various search modes. This makes it easy to understand how many matches there are in the current buffer for your search query.

(use-package anzu
  :after-call isearch-mode
  :bind
  ([remap query-replace] . anzu-query-replace)
  ([remap query-replace-regexp] . anzu-query-replace-regexp)
  :config
  (global-anzu-mode +1))

Alternative isearch UI (isearch-mb.el)

This package provides an alternative isearch UI based on the minibuffer. This allows editing the search string in arbitrary ways without any special maneuver; unlike standard isearch, cursor motion commands do not end the search. Moreover, the search status information in the echo area and some keybindings are slightly simplified.

(use-package isearch-mb
  :after-call isearch-mode
  :config
  (isearch-mb-mode)
  (add-to-list 'isearch-mb--with-buffer #'consult-isearch-history)
  (add-to-list 'isearch-mb--after-exit #'anzu-isearch-query-replace)
  :bind
  (nil
   :map isearch-mb-minibuffer-map
   ([remap previous-matching-history-element] . consult-isearch-history)))

Window position (transpose-frame.el)

The transpose-frame library defines a set of commands for shifting the layout of Emacs windows. Rather than me describing how these work, I strongly encourage you to read the “Commentary” section in the source code. Do it with M-x find-library transpose-frame.

(use-package transpose-frame)

Distraction-free writing (writeroom-mode.el)

Autoload

;;; ale/autoload/writeroom-mode.el --- -*- lexical-binding: t -*-

;;;###autoload
(defun +visual-fill-center-text ()
  "Centering text."
  (interactive)
  (setq-local visual-fill-column-width 120)
  (setq-local visual-fill-column-center-text t)
  (visual-fill-column-mode 1))

Config

(use-package writeroom-mode
  :after-call find-file-hook
  :hook
  (after-init . global-writeroom-mode)
  :config
  (setq split-width-threshold 120
        writeroom-width 128
        writeroom-bottom-divider-width 0
        writeroom-fringes-outside-margins t
        writeroom-fullscreen-effect nil
        writeroom-major-modes '(text-mode prog-mode conf-mode special-mode Info-mode dired-mode)
        writeroom-major-modes-exceptions '(process-menu-mode proced-mode)
        writeroom-maximize-window nil
        writeroom-mode-line t)
  (org-mode . +visual-fill-center-text))

Key bindings hint (which-key.el)

(use-package which-key
  :init
  (which-key-mode 1 ))

Buffer list (ibuffer.el)

ibuffer.el ships with Emacs and it provides a drop-in replacement for list-buffers. Compared to its counterpart, it allows for granular control over the buffer list and is more powerful overall.

(use-package ibuffer
  :init
  (advice-add 'list-buffers :override 'ibuffer)
  :bind
  (nil
   :map ibuffer-mode-map
   ("M-o" . nil)
   ("* f" . ibuffer-mark-by-file-name-regexp)
   ("* g" . ibuffer-mark-by-content-regexp)
   ("* n" . ibuffer-mark-by-name-regexp)
   ("s n" . ibuffer-do-sort-by-alphabetic)
   ("/ g" . ibuffer-filter-by-content))
  :config
  (setq ibuffer-expert t)
  (setq ibuffer-display-summary nil)
  (setq ibuffer-use-other-window nil)
  (setq ibuffer-show-empty-filter-groups nil)
  (setq ibuffer-movement-cycle nil)
  (setq ibuffer-default-sorting-mode 'filename/process)
  (setq ibuffer-use-header-line t)
  (setq ibuffer-default-shrink-to-minimum-size nil)
  (setq ibuffer-never-show-predicates '("^ \\*.*"))
  (setq ibuffer-formats
        '((mark modified read-only locked " "
                (name 30 30 :left :elide)
                " "
                (size 9 -1 :right)
                " "
                (mode 16 16 :left :elide)
                " " filename-and-process)
          (mark " " (name 16 -1) " " filename)))
  (setq ibuffer-saved-filter-groups nil)
  (setq ibuffer-old-time 48)
  (add-hook 'ibuffer-mode-hook (lambda () (interactive) (hl-line-mode) (ibuffer-update 0))))

Hunk indicator (git-gutter.el)

(use-package git-gutter
  :config
  (custom-set-variables
   '(git-gutter:modified-sign "")
   '(git-gutter:added-sign "")
   '(git-gutter:deleted-sign "")))

Utils

Window motions (ace-window.el)

Index based window motions.

(use-package ace-window
  :bind
  ("M-o" . ace-select-window)
  :config
  (setq aw-keys '(?a ?r ?s ?t ?n ?e ?i ?o)))

Vterm (vterm.el)

Autoload

;;; ale/autoload/vterm.el --- -*- lexical-binding: t -*-

(require 'vterm)

(defcustom ale-vterm-kill-whole-line-cmd
  'vterm-send-C-u
  "Command for kill whole line in vterm buffer."
  :type 'symbol :group 'vterm)

(defvar ale-vterm-buffers nil
  "The list of non-dedicated vterm buffers.")

(defvar ale-vterm-index 0
  "The index of current vterm buffer.")

;;;###autoload
(defun ale-vterm-escape-advisor (fn &rest args)
  (if (derived-mode-p 'vterm-mode)
      (vterm-send-escape)
    (apply fn args)))

;;;###autoload
(defun ale-vterm-kill-whole-line-advisor (fn &rest args)
  (if (derived-mode-p 'vterm-mode)
      (funcall ale-vterm-kill-whole-line-cmd)
    (apply fn args)))

(defun ale-vterm--disable-side-window (fn &rest args)
  "Prevent vterm size adjust break selection."
  (unless (and (region-active-p)
               (derived-mode-p 'vterm-mode))
    (apply fn args)))

;;;###autoload
(defun vterm-send-C-delete ()
  (interactive)
  (vterm-send-key "<delete>" nil nil 0))

;;;###autoload
(defun vterm-send-M-return ()
  (interactive)
  (vterm-send-escape)
  (vterm-send-return))

;;;###autoload
(defun vterm-send-M-/ ()
  (interactive)
  (vterm-send-key "/" nil 0 nil))

;;;###autoload
(defun vterm-send-F5 ()
  (interactive)
  (vterm-send-key "<f5>" nil nil nil))

;;;###autoload
(defun vterm-send-M-apostrophe ()
  (interactive)
  (vterm-send-key "'" nil 0 nil))

;;;###autoload
(defun vterm-send-M-quote ()
  (interactive)
  (vterm-send-key "\"" nil 0 nil))

(defun ale-vterm--get-win-params ()
  "Parse `ale-term-position-alist' to get vterm display parameters."
  `(("^\\*vterm.*"
     (display-buffer-in-side-window)
     (window-parameters . ((mode-line-format . none)))
     ,@(cl-loop for (pred . pos) in ale-term-position-alist
                when (funcall pred)
                return pos))))

;;;###autoload
(defun ale-vterm-toggle (&optional project-root)
  "Toggle vterm.
If called with prefix argument, create a new vterm buffer with
project root directory as `default-directory'."
  (interactive "P")
  (if (eq major-mode 'vterm-mode)
      (delete-window)
    (let* ((display-buffer-alist (ale-vterm--get-win-params))
           (buf (nth ale-vterm-index ale-vterm-buffers))
           (pr-root (or (cdr-safe (project-current)) default-directory))
           (default-directory (if project-root pr-root default-directory))
           (index (if buf (ale-vterm--get-index buf) 0)))
      (add-to-list 'ale-vterm-buffers (vterm index))
      (ale-vterm--insert))))

(defun ale-vterm--get-index (buf)
  (let* ((name (buffer-name buf)))
    (string-match "\\*vterm\\*\<\\([0-9]+\\)\>" name)
    (string-to-number (cl-subseq name (match-beginning 1) (match-end 1)))))

(declare-function meow-insert "meow-command")
(declare-function evil-insert-state "evil")
(defun ale-vterm--insert ()
  (cond
   ((featurep 'meow)
    (meow-insert))
   ((featurep 'evil)
    (evil-insert-state))))

;;;###autoload
(defun ale-vterm-new ()
  "Create new vterm buffer."
  (interactive)
  (let ((new-index (1+ (ale-vterm--get-index (car ale-vterm-buffers))))
        (display-buffer-alist (ale-vterm--get-win-params)))
    (add-to-list 'ale-vterm-buffers (vterm new-index))
    (ale-vterm--insert)))

;;;###autoload
(defun ale-vterm-next (&optional arg)
  "Select next vterm buffer.
Create new one if no vterm buffer exists."
  (interactive "P")
  (let* ((curr-index (cl-position (current-buffer) ale-vterm-buffers))
         (new-index (+ curr-index (or arg -1)))
         (buf (nth new-index ale-vterm-buffers)))
    (when buf
      (switch-to-buffer buf)
      (setq ale-vterm-index new-index))))

;;;###autoload
(defun ale-vterm-prev (&optional arg)
  "Select previous vterm buffer."
  (interactive "p")
  (ale-vterm-next arg))

(defun ale-vterm--kill-buffer ()
  "Remove killed buffer from `ale-vterm-buffers'.

Used as a hook function added to `kill-buffer-hook'."
  (let* ((buf (current-buffer))
         (name (buffer-name buf)))
    (when (string-prefix-p "*vterm" name)
      (delq! buf ale-vterm-buffers))))

;;;###autoload
(define-minor-mode ale-vterm-mux-mode
  "Show/hide multiple vterm windows under control."
  :global t
  :group 'vterm
  (if ale-vterm-mux-mode
      (progn
        (advice-add 'display-buffer-in-side-window :around 'ale-vterm--disable-side-window)
        (add-hook 'kill-buffer-hook #'ale-vterm--kill-buffer))
    (advice-remove 'display-buffer-in-side-window 'ale-vterm--disable-side-window)
    (remove-hook 'kill-buffer-hook #'ale-vterm--kill-buffer)))

Config

(use-package vterm
  :config
  (ale-vterm-mux-mode)
  (setq vterm-max-scrollback 5000)
  (set-face-attribute 'vterm-color-white nil :foreground "#cccccc")
  (set-face-attribute 'vterm-color-black nil :foreground "#111111")
  (advice-add 'meow-insert-exit :around #'ale-vterm-escape-advisor)
  (advice-add 'meow-kill-whole-line :around #'ale-vterm-kill-whole-line-advisor)
  :bind
  (("M--" . ale-vterm-toggle)
   :map vterm-mode-map
   ("M--" . ale-vterm-toggle)
   ("M-;" . ale-vterm-new)
   ("M-'" . vterm-send-M-apostrophe)
   ("M-\"" . vterm-send-M-quote)
   ("M-/" . vterm-send-M-/)
   ("M-." . ale-vterm-next)
   ("M-," . ale-vterm-prev)
   ("M-RET" . vterm-send-M-return)
   ("s-n" . vterm-next-prompt)
   ("s-p" . vterm-previous-prompt)
   ("S-SPC" . nil)
   ("S-<escape>" . (lambda () (interactive) (meow-normal-mode) (meow--update-cursor)))
   ("<C-i>" . vterm-send-C-i)
   ("C-<delete>" . vterm-send-C-delete)
   ("C-<return>" . vterm-send-F5)))

Git porcelain (magit.el)

(use-package magit
  :config
  (setq magit-display-buffer-function 'magit-display-buffer-same-window-except-diff-v1)
  (setq magit-define-global-key-bindings nil)
  (setq git-commit-summary-max-length 68)
  (setq git-commit-known-pseudo-headers
        '("Signed-off-by"
          "Acked-by"
          "Modified-by"
          "Cc"
          "Suggested-by"
          "Reported-by"
          "Tested-by"
          "Reviewed-by"))
  (setq git-commit-style-convention-checks
        '(non-empty-second-line
          overlong-summary-line))
  (setq magit-diff-refine-hunk t)
  (setq magit-repository-directories '(("~/Code" . 1) ("~" . 1)))
  :bind (("C-M-g" . magit-status-here)
         :map magit-diff-section-base-map
         ("<C-return>" . magit-diff-visit-file-other-window)))

Helpful (helpful.el)

Helpful.el provides a better help buffer. Here are some tweaks I made for this package and built-in help buffer:

  • disable auto jump to other end when cycle through buttons never
  • open new window when invoking helpful-visit-references. auto
  • focus newly opened help buffer (same behaviour as helpful.el)

Autoload

;;; ale/autoload/helpful.el --- -*- lexical-binding: t -*-

(defvar ale-helpful-initialized nil)

;;;###autoload
(defun ale-helpful-mode-hook ()
  ;; FIXME: A better way?
  (setq ale-helpful-initialized nil)
  (advice-add 'find-file :before
              (lambda (&rest _)
                (when (and (not ale-helpful-initialized) (derived-mode-p 'helpful-mode))
                  (switch-to-buffer "*scratch*")
                  (switch-to-prev-buffer)
                  (setq ale-helpful-initialized t))))
  (visual-line-mode))

Config

(use-package helpful
  :hook (helpful-mode . ale-helpful-mode-hook)
  :bind
  (("C-h K" . #'describe-keymap)  ; overrides `Info-goto-emacs-key-command-node'
   ([remap describe-function] . #'helpful-callable)
   ([remap describe-symbol] . #'helpful-symbol)
   ([remap describe-key] . #'helpful-key)
   :map helpful-mode-map
   ("M-n" . (lambda () (interactive) (forward-button 1 nil 1 t)))
   ("M-p" . (lambda () (interactive) (backward-button 1 nil 1 t))))
  :config
  (defvar read-symbol-positions-list nil)
  (setq find-function-C-source-directory "~/.cache/paru/clone/emacs-git/src/emacs-git/src"))

Emacs Manual (info.el)

(use-package info
  :straight (:type built-in)
  :bind
  (nil
   :map Info-mode-map
   ("n" . next-line)
   ("p" . previous-line)
   ("C-n" . Info-next)
   ("C-p" . Info-prev)
   ("M-n" . forward-paragraph)
   ("M-p" . backward-paragraph)))

Man page (man.el)

(use-package man
  :straight (:type built-in)
  :bind
  (nil
   :map Man-mode-map
   ("q" . kill-this-buffer))
  :config
  (setq Man-notify-method 'newframe))

Ripgrep (rg.el)

Autoload

The purpose of the function ale/rg-define-toggle is to avoid executing rg-define-toggle directly in use-package, because this macro introduces too many deps that we don’t need at startup.

;;; ale-rg.el -*- lexical-binding: t; -*-

;;;###autoload
(defun ale/rg-define-toggles ()
  "Define toggles in `rg-mode'."
  (rg-define-toggle "--context 3" (kbd "C"))
  (rg-define-toggle "-A 5" (kbd "A")))

(provide 'ale-rg)

Config

(use-package rg
  :config
  (ale/rg-define-toggles)
  :bind
  (nil
   :map ale-mct-map
   ("g" . rg)))

Writable grep (wgrep.el)

With wgrep we can directly edit the results of a grep and save the changes to all affected buffers. In principle, this is the same as what the built-in occur offers. We can use it to operate on a list of matches by leveraging the full power of Emacs’ editing capabilities (e.g. keyboard macros, query and replace a regexp .etc).

(use-package wgrep
  :config
  (setq wgrep-auto-save-buffer t)
  (setq wgrep-change-readonly-file t)
  :bind
  (nil
   :map wgrep-mode-map
   ("M-n" . next-error-no-select)
   ("M-p" . previous-error-no-select)))

Pdf reader (pdf-tools.el)

(use-package pdf-tools
  :after-call dired-after-readin-hook minibuffer-setup-hook
  :config
  (pdf-tools-install)
  (setq-default pdf-view-display-size 'fit-page)
  ;; automatically annotate highlights
  (setq pdf-annot-activate-created-annotations t)
  ;; turn off cua so copy works
  (add-hook 'pdf-view-mode-hook (lambda () (cua-mode 0)))
  ;; more fine-grained zooming
  (setq pdf-view-resize-factor 1.1)
  ;; keyboard shortcuts
  :bind
  (nil
   :map pdf-view-mode-map
   ("C-s" . isearch-forward)
   ("h" . pdf-annot-add-highlight-markup-annotation)
   ("t" . 'pdf-annot-add-text-annotation)
   ("D" . 'pdf-annot-delete)))

Epub reader (nov.el)

(use-package shrface
  :after nov
  :config
  (shrface-basic)
  (shrface-trial)
  (add-to-list 'shr-external-rendering-functions
               '(span . shrface-tag-span))
  (shrface-default-keybindings) ; setup default keybindings
  (setq shrface-href-versatile t))

(use-package nov
  :init
  (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))
  (add-hook 'nov-mode-hook 'ale/nov-setup)
  :config
  (advice-add 'nov-render-title :override #'ignore)
  (setq nov-shr-rendering-functions '((img . nov-render-img)
                                      (title . nov-render-title)
                                      (b . shr-tag-b)))
  (setq nov-shr-rendering-functions
        (append nov-shr-rendering-functions
                shr-external-rendering-functions))
  (defun ale/nov-setup ()
    (require 'shrface)
    (shrface-mode)))

Murl (murl.el)

Autoload

;;; ale/autoload/murl.el -*- lexical-binding: t; -*-

(require 'json)

(defvar ale/murl-list-file (expand-file-name "~/.cache/murl/main_list.json"))

(defun ale/murl--playlist ()
  (append (json-read-file ale/murl-list-file) nil))

(defun ale/murl--get-attr (title attr)
  (cl-dolist (i (ale/murl--playlist))
    (when (string= title (cdr (assq 'title i)))
      (cl-return (cdr (assq attr i))))))

;;;###autoload
(defun ale/murl-open (&optional no-hist)
  "Select video or stream to play in mpv."
  (interactive "P")
  (unless no-hist
    (let* ((clip (condition-case nil (current-kill 0 t) (error ""))))
      (set-text-properties 0 (length clip) nil clip)
      (when-let* ((is-url (string-prefix-p "http" clip))
                  (json (shell-command-to-string (concat "murl -P 1088 json '" clip "'")))
                  (valid (string-prefix-p "{" json))
                  (obj (json-read-from-string json))
                  (playlist (ale/murl--playlist)))
        (cl-pushnew obj playlist :test 'equal)
        (with-temp-buffer
          (insert (json-encode (vconcat playlist)))
          (json-pretty-print-buffer)
          (write-region (point-min) (point-max) ale/murl-list-file)))))
  (let* ((cands-raw (mapcar (lambda (i) (cdr (assq 'title i))) (ale/murl--playlist)))
         (annotation (lambda (s) (marginalia--documentation (ale/murl--get-attr s 'url))))
         (cands (completion-append-metadata! annotation cands-raw))
         (title (completing-read "murls: " cands))
         (sub (ale/murl--get-attr title 'sub)))
    (call-process "murl" nil 0 nil "-r" "-f" "-P" "1088" "-s" sub (ale/murl--get-attr title 'url))))

Config

(bind-keys
 :map ale-apps-map
 ("m" . ale/murl-open))

Dictionary (fanyi.el)

(use-package fanyi
  :bind
  (nil
   :map ale-apps-map
   ("t" . fanyi-dwim))
  :custom
  (fanyi-providers '(fanyi-etymon-provider
                     fanyi-longman-provider)))

Forges (forge.el)

(use-package forge)

Programming Languages

Rust

(use-package rust-mode
  :hook
  (rust-mode . (lambda () (setq indent-tabs-mode nil))))

C|C++

(use-package ob-C
  :straight (:type built-in)
  :after-call org-cycle
  :commands (org-babel-execute:C
             org-babel-expand:C
             org-babel-execute:cpp
             org-babel-expand:cpp)
  :config
  (add-to-list 'org-structure-template-alist '("cc" . "src C"))
  (add-to-list 'org-structure-template-alist '("cp" . "src cpp")))

Python

(use-package python
  :straight (:type built-in)
  :config
  (setq python-indent-offset 4)
  (setq python-indent-guess-indent-offset-verbose nil))
(use-package ob-python
  :straight (:type built-in)
  :after-call org-cycle
  :commands (org-babel-execute:python)
  :config
  (add-to-list 'org-structure-template-alist '("py" . "src python")))

Lua

(use-package lua-mode
  :config
  (setq lua-indent-level 2))

Yaml

(use-package yaml-mode)

JavaScript

(use-package js
  :straight (:type built-in)
  :config
  (setq js-indent-level 2))

(use-package web-mode
  :config
  (setq web-mode-content-types-alist '(("jsx" . "\\.js[x]?\\'")))
  :hook
  (web-mode . (lambda ()
                (emmet-mode)
                (setq web-mode-markup-indent-offset 2)
                (setq web-mode-code-indent-offset 2)
                (setq web-mode-script-padding 0)))
  :mode (("\\.vue\\'" . web-mode)
         ("\\.jsx?$" . web-mode)))
(use-package ob-js
  :straight (:type built-in)
  :after-call org-cycle
  :commands (org-babel-execute:js)
  :config
  (add-to-list 'org-structure-template-alist '("js" . "src js")))

Bash|Zsh

(use-package sh-script
  :straight (:type built-in)
  :config
  (setq sh-basic-offset 2))
(use-package ob-shell
  :straight (:type built-in)
  :after-call org-cycle
  :commands (org-babel-execute:bash)
  :config
  (add-to-list 'org-structure-template-alist '("sh" . "src bash")))

HTML

(use-package emmet-mode)

LaTeX

(use-package ob-latex
  :straight (:type built-in)
  :after-call org-cycle
  :commands (org-babel-execute:latex org-babel-expand:latex)
  :config
  (add-to-list 'org-structure-template-alist '("la" . "src latex")))

Makefile

(use-package ob-makefile
  :straight (:type built-in)
  :after-call org-cycle
  :commands (org-babel-execute:makefile)
  :config
  (add-to-list 'org-structure-template-alist '("mk" . "src makefile")))

DevTools

LSP (lsp.el)

Autoload

;;; ale/autoload/lsp.el --- -*- lexical-binding: t -*-

(defun ale/lsp-pyright-import-venv ()
  "Fix `import' resolution with projects with virtual env like conda."
  (require 'lsp-pyright)
  (let* ((pr-root (lsp-workspace-root))
         (py-ver "python3.9")
         (extra-path (concat pr-root "/envs/lib/" py-ver "/site-packages")))
    (setq lsp-pyright-extra-paths (vector extra-path))))

(defun ale/lsp--inhibit ()
  "Disable `lsp-deferred' in minibuffer."
  (advice-add 'lsp-deferred :override #'ignore))

(defun ale/lsp--recover ()
  "Recover `lsp-deferred' after quit minibuffer."
  (advice-remove 'lsp-deferred #'ignore))

;;;###autoload
(define-minor-mode ale/lsp-mode
  "Inhibit lsp in minibuffer."
  :init-value nil
  :global t
  (if ale/lsp-mode
      (progn
        (add-hook 'minibuffer-setup-hook 'ale/lsp--inhibit)
        (add-hook 'minibuffer-exit-hook 'ale/lsp--recover))
    (progn
      (remove-hook 'minibuffer-setup-hook 'ale/lsp--inhibit)
      (remove-hook 'minibuffer-exit-hook 'ale/lsp--recover))))

(provide 'ale-lsp)

Config

(use-package lsp-mode
  :hook ((sh-mode
          lua-mode
          python-mode
          web-mode
          typescript-mode
          rust-mode)
         . lsp-deferred)
  :init
  (ale/lsp-mode)
  :config
  (setq lsp-eldoc-hook nil)
  (setq lsp-signature-auto-activate t)
  (setq lsp-signature-function 'lsp-signature-posframe)
  (setq lsp-signature-doc-lines 20)
  (setq lsp-server-install-dir (expand-file-name (concat user-emacs-directory "lsp")))
  (add-to-list 'lsp-file-watch-ignored-directories "[/\\\\]\\envs\\'")
  (setq lsp-headerline-breadcrumb-segments '(path-up-to-project file symbols)))

Extensions

(use-package lsp-tailwindcss
  :after (lsp-mode web-mode)
  :init
  (setq lsp-tailwindcss-add-on-mode t))

(use-package lsp-pyright
  :hook (python-mode . ale/lsp-pyright-import-venv)
  :init
  (when (executable-find "python3")
    (setq lsp-pyright-python-executable-cmd "python3")))

(use-package lsp-ui
  :after-call lsp-deferred
  :config
  (setq lsp-ui-sideline-show-code-actions nil)
  (setq lsp-ui-doc-position 'bottom)
  :hook
  (lsp-mode . lsp-ui-mode))

Virturl environment (conda.el)

Autoload

;;; ale/autoload/conda.el --- -*- lexical-binding: t -*-

;;;###autoload
(defun ale-conda-env-activate-for-buffer-ad ()
  "Advice for `conda-env-activate-for-buffer'.

Make it support local `conda-env-subdirectory' such as `./envs'."
  (interactive)
  (if-let ((dir-local-env (bound-and-true-p conda-project-env-path)))
      (conda-env-activate dir-local-env)
    (when-let* ((filename (buffer-file-name))
                (yml-file (conda--find-env-yml (f-dirname filename)))
                (yml-path (f-dirname yml-file))
                (prefix-env (concat yml-path "/" conda-env-subdirectory "/")))
      (if (file-exists-p prefix-env)
          (unless (string= prefix-env (or conda-env-current-name ""))
            (conda-env-activate-path prefix-env))
        (when-let ((env-name (conda-env-name-to-dir
                              (conda--get-name-from-env-yml yml-file))))
          (unless (string= env-name (or conda-env-current-name ""))
            (conda-env-activate env-name)))))))

;;;###autoload
(define-minor-mode +conda-env-autoactivate-mode
  "Toggle +conda-env-autoactivate mode.

This mode automatically tries to activate a conda environment for the current
buffer."
  :group 'conda
  :global t
  (if +conda-env-autoactivate-mode
      (progn
        (advice-add 'pop-to-buffer :after #'conda--switch-buffer-auto-activate)
        (advice-add 'switch-to-buffer :after #'conda--switch-buffer-auto-activate))
    (advice-remove 'pop-to-buffer #'conda--switch-buffer-auto-activate)
    (advice-remove 'switch-to-buffer #'conda--switch-buffer-auto-activate)))

Config

(use-package conda
  :after-call find-file-hook
  :config
  (setq conda-anaconda-home "/opt/miniconda/")
  (setq conda-env-home-directory "/opt/miniconda/")
  (setq conda-message-on-environment-switch t)
  (advice-add 'conda-env-activate-for-buffer
              :override #'ale-conda-env-activate-for-buffer-ad)
  (+conda-env-autoactivate-mode t)
  :hook
  (conda-postactivate . (lambda () (interactive) (jupyter-available-kernelspecs t))))

Jupyter (jupyter.el)

(use-package jupyter
  :after-call (org-cycle conda-preactivate-hook)
  :config
  ;; FIXME: jupyter require this function but it doesn't exist in emacs-version > 28.
  (defun ansi-color--find-face (codes)
    "Return the face corresponding to CODES."
    (let (faces)
      (while codes
        (let ((face (ansi-color-get-face-1 (pop codes))))
          (unless (eq face 'default)
            (push face faces))))
      (if (cdr faces) (nreverse faces) (car faces))))
  (require 'ob-jupyter)
  (org-babel-jupyter-override-src-block "python")
  (setq org-babel-default-header-args:python '((:async . "yes") (:kernel . "python3"))))

Colorizer (rainbow-mode.el)

(use-package rainbow-mode
  :hook
  (prog-mode . rainbow-mode))

Formatter (reformatter.el)

reformatter.el (created by Purcell) lets you easily define an idiomatic command to reformat the current buffer using a command-line program, together with an optional minor mode which can apply this command automatically on save.

(use-package reformatter
  :init
  (defun ale-format-buffer ()
    (interactive)
    (let* ((mode-name (string-remove-suffix "-mode" (format "%s" major-mode)))
           (func-name (intern (format "%s-format-buffer" mode-name))))
      (if (functionp func-name)
          (funcall func-name)
        (user-error
         (format
          "No available formatter for %s. Use `reformatter-define' to create it."
          major-mode)))))
  :config
  (reformatter-define lua-format
    :program "stylua"
    :args '("--indent-width" "2" "-")
    :lighter " styLua")
  (reformatter-define python-format
    :program "black"
    :args '("-")
    :lighter " blackFMT")
  :bind
  (nil
   :map ale-apps-map
   ("f" . ale-format-buffer)))

Indent lines (highlight-indent-guides.el)

(use-package highlight-indent-guides
  :hook
  (prog-mode . (lambda ()
                 (unless (eq major-mode 'js-mode)
                   (highlight-indent-guides-mode))))
  :config
  (setq highlight-indent-guides-method 'bitmap)
  (setq highlight-indent-guides-bitmap-function 'highlight-indent-guides--bitmap-line)
  (setq highlight-indent-guides-responsive 'stack)
  (setq highlight-indent-guides-delay 0)
  (setq highlight-indent-guides-suppress-auto-error t)
  (setq highlight-indent-guides-auto-stack-odd-face-perc 15)
  (setq highlight-indent-guides-auto-stack-even-face-perc 25)
  (setq highlight-indent-guides-auto-stack-character-face-perc 35))

Syntax checker (flycheck.el)

(use-package flycheck
  :after-call prog-mode-hook
  :config
  (flycheck-add-mode 'javascript-eslint 'web-mode)
  (global-flycheck-mode)
  (setq-default flycheck-emacs-lisp-load-path 'inherit)
  (setq-default flycheck-disabled-checkers
                (append flycheck-disabled-checkers '(javascript-jshint json-jsonlist))))

REST client (restclient.el)

(use-package restclient)

Scratch buffers (scratch.el)

ale/scratch will produce a org-mode buffer with right header-args for current jupyter kernel. Use it with SPC f s, doing it with a prefix argument (C-u) will prompt for a major mode instead. Simple yet super effective!

Autoload

;;; ale/autoload/scratch.el --- -*- lexical-binding: t -*-

(defun ale-scratch--list-modes ()
  "List known major modes."
  (cl-loop for sym the symbols of obarray
           for name = (symbol-name sym)
           when (and (functionp sym)
                     (not (member sym minor-mode-list))
                     (string-match "-mode$" name)
                     (not (string-match "--" name)))
           collect (substring name 0 -5)))

;;;###autoload
(defun ale/scratch (query-for-mode)
  "Create or switch to an org-mode scratch buffer with jupyter session configured.
If called with prefix arg, prompt for a major mode for the buffer."
  (interactive "P")
  (let ((buf (get-buffer "*ALE-scratch*")))
    (unless buf
      (let* ((new-buf (generate-new-buffer "*ALE-scratch*"))
             (jpt-session (or (bound-and-true-p conda-env-current-name) "base"))
             (text (concat "#+PROPERTY: header-args:python :session " jpt-session))
             (mode "org"))
        (with-current-buffer new-buf
          (when query-for-mode
            (setq mode (completing-read "Mode: " (ale-scratch--list-modes) nil t nil nil))
            (setq text (format "Scratch buffer for: %s" mode)))
          (insert text)
          (funcall (intern (concat mode "-mode")))
          (setq-local org-image-actual-width '(1024))
          (unless (string= mode "org") (comment-region (point-min) (point-max)))
          (insert "\n\n")
          (setq buf new-buf))))
    (pop-to-buffer buf)))

Config

(bind-keys
 :map ale-apps-map
 ("s" . ale/scratch))

About

原非大观。


Languages

Language:Emacs Lisp 100.0%