alexluigit / emacs-grandview

原非大观。

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Emacs Grandview

Overview

Introduction

Emacs grandview is my literate Emacs configuration which has a pretty straightforward project structure:

  • Early init: early-init.el
  • Init: init.el
  • User config (optional): user.el
  • Main config file (.org): This file (defaults to grandview.org)

User config

user.el is the file to place your:

  1. grandview specfic options which include:
    • grandview-cache-dir: Cache directory for grandview.
    • grandview-org-file: Path for grandview main config .org file.
    • grandview-gc-cons-threshold: The value for gc-cons-threshold.
    • grandview-theme: The default theme, it should be a theme name.
    • grandview-font-size: Font size being applied.
    • grandview-default-font: The default font, which should included in font-family-list.
    • grandview-fixed-font: The fixed-pitch font, which should included in font-family-list.
    • grandview-variable-font: The variable-pitch font, which should included in font-family-list.
    • grandview-CJK-font: The font used for CJK characters.
  2. other arbitrary codes to load before loading the main config
  3. packages expecting further evaluation or testing

This .org file

The elisp source blocks in this file is tangled to a regular .el file which is then evaluated by Emacs. Sections named Autoload are tangled to separate files upon which corresponding autoloads are generated. Whenever the content of this file changes, this tangling process is fired up on the execution of kill-emacs command, after that the old config is replaced by the newly generated one.

A section with bold and uppercase title means it is a core config in Grandview. Unless you know exactly what you are doing, it is NOT recommended to comment out these sections. Since codes in other sections may rely on orientations in the section to work properly, so be sure to edit these sections with caution.

Screenshots

File manager

https://user-images.githubusercontent.com/16313743/169456875-ed5af1e7-57cd-4203-96e9-9038119721b9.png

IDE

https://user-images.githubusercontent.com/16313743/169660050-b66d09b7-617e-46a0-a2a9-138c570d1336.png

Org-mode

https://user-images.githubusercontent.com/16313743/169660518-bb1fd05c-089a-41db-830d-43784ae14d6d.png

Lightning fast startup

https://user-images.githubusercontent.com/16313743/169660620-c5f7bef5-499a-4ea2-8a39-3e0f2801cb24.png

Installation

Make sure to backup your own emacs config before installation.

cp -r ~/.emacs.d ~/.emacs.d.bak

Dependencies

Here are the dependencies of this project.

PackageDescriptionRequired
fdA modern find
rgA modern grep
gitVersion control
noto-fonts-emojiFont for emojisoptional
wordsEnglish words completionoptional
font-victor-monoDefault fixed pitch fontoptional
ttf-sarasa-gothicDefault variable pitch fontoptional
xdotoolAutomation tool for X11optional

Tangling

Clone the repo and tangle the config.

git clone https://www.github.com/alexluigit/emacs-grandview
## move the repo to ~/.config/emacs, which conforms to the XDG spec
mv ~/emacs-grandview ~/.config/emacs/
## or use symlink
# mv ~/emacs-grandview ~/Code/emacs-grandview
# ln -sf ~/Code/emacs-grandview ~/.config/emacs
## or you can put it to ~/.emacs.d, it's an old convention
# mv ~/emacs-grandview ~/.emacs.d

## let it tangles itself
emacs --bg-daemon
## hooray, enjoy the Grandview
emacsclient -cn
## NOTE: You should be able to start/restart Emacs by 'rem' bash
## command from now on, see the "** Restart Emacs"  section below.

Restart Emacs

Although Emacs already provided functionalities like unload-feature and remove-hook to eliminate the side effects of certain packages or user configurations, most of the time it’s still easier to reload your Emacs configurations through a complete restart. Luckily, Emacs 30+ comes with this feature out of the box, that is the restart-emacs command. However, when Emacs hangs up, we can not expect it to evaluate any elisp code, in this case, a shell script that helps us to restart Emacs can be very handy.

Source

Here is the source code for the rem (acronym for restart-emacs) command. The first elisp code block is consumed by the second code block to inject a few values such as file path into the actual shell script.

You don’t need to install this script if you have followed Installation section. This script will be tangled to ~/.local/bin/rem, so make sure ~/.local/bin is in your PATH.

(pcase type
  ("main" (format "%s" grandview-cache-dir))
  ("pkg-builds" (straight--build-dir))
  ("pkg-repos" (straight--repos-dir)))
_is_ime_rime () { [[ -d ~/.config/rime ]] || [[ -d ~/.local/share/fcitx5/rime ]]; }

ELCs=false REPOs=false RESET=false DEBUG="" INIT_DIR=""
BIN="emacs"
[[ $(uname) == "Darwin" ]] && BIN="/Applications/Emacs.app/Contents/MacOS/Emacs"

[[ "$GRANDVIEW_HOME" ]] && INIT_DIR="--init-directory $GRANDVIEW_HOME"

while getopts "pPrd" opt; do
  case $opt in
    p) ELCs=true;;
    P) REPOs=true;;
    r) RESET=true;;
    d) DEBUG=--debug-init;;
  esac
done
shift $((OPTIND -1))

emacs_cmd="$BIN --bg-daemon $DEBUG $INIT_DIR"
notify-send "Restarting emacs..." 2>/dev/null
kill -9 $(pgrep -x '[Ee]macs') 2>/dev/null
$RESET && rm -rf '<<grandview-cache(type="main")>>' 2>/dev/null
$ELCs && rm -rf '<<grandview-cache(type="pkg-builds")>>' 2>/dev/null
$REPOs && rm -rf '<<grandview-cache(type="pkg-repos")>>' 2>/dev/null
_is_ime_rime && eval GTK_IM_MODULE=emacs XMODIFIERS=@im=emacs $emacs_cmd \
    || eval $emacs_cmd
command -v xdotool >/dev/null 2>&1 && xdotool set_desktop 0 2>&1
emacsclient -cn

Usage

Here are the available flags of this command.

  • -r: delete Grandview’s cache before restarting. (re-tangle)
  • -p: delete all .elc build of packages before restarting. (rebuild)
  • -P: delete all package repos before restarting. (re-download & rebuild)
  • -d: use --debug-init flag for the daemon

TEXT EDITING

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.

  • Unbind SPC in *messages* buffer since we use it as the leader key
  • Recenter the screen and highlight the keywords after we call next/previous-error
(once '(:packages simple)
  (bind-key "SPC" nil messages-buffer-mode-map)
  (add-hook 'next-error-hook #'recenter)
  (setq next-error-message-highlight t)) ; added in Emacs 28.1

Autoload

;;;###autoload
(defadvice! delete-backward-char-ad (fn &rest args)
  "Do not try to delete char when the last char is read-only."
  :around #'delete-backward-char
  (unless (get-text-property (1- (point)) 'read-only) (apply fn args)))

;;;###autoload
(defadvice! next-error-no-select-ad (fn &rest args)
  "Do not open new window when calling `next-error-no-select'."
  :around #'next-error-no-select
  (let ((split-width-threshold nil)) (apply fn args)))

;;;###autoload
(defadvice! previous-error-no-select-ad (fn &rest args)
  "Do not open new window when calling `previous-error-no-select'."
  :around #'previous-error-no-select
  (let ((split-width-threshold nil)) (apply fn args)))

;;;###autoload
(defadvice! yank-ad (&rest _)
  "Make `yank' behave like paste (p) command in vim."
  :before #'yank
  (when-let ((clip (condition-case nil (current-kill 0 t) (error ""))))
    (set-text-properties 0 (length clip) nil clip)
    (when (string-suffix-p "\n" clip)
      (goto-char (line-beginning-position)))))

;;;###autoload
(defun +simple-pop-local-mark-ring ()
  "Move cursor to last mark position of current buffer.
Call this repeatedly will cycle all positions in `mark-ring'."
  (interactive)
  (set-mark-command t))

;;;###autoload
(defun +simple-join-line ()
  "Join the current line with the line beneath it."
  (interactive)
  (delete-indentation 1))

;;;###autoload
(defun +simple-mark-inner-line ()
  "Mark inner line and move cursor to bol."
  (interactive)
  (save-window-excursion
    (move-end-of-line 1)
    (set-mark-command nil)
    (back-to-indentation)))

;; Copied from `xah-fly-keys'
;;;###autoload
(defun +toggle-letter-case ()
  "Toggle the letter case of current word or selection.
Always cycle in this order: Init Caps, ALL CAPS, all lower.
URL `http://xahlee.info/emacs/emacs/modernization_upcase-word.html'
Version: 2020-06-26"
  (interactive)
  (let ((deactivate-mark nil) $p1 $p2)
    (if (region-active-p)
        (setq $p1 (region-beginning) $p2 (region-end))
      (save-excursion
        (skip-chars-backward "[:alpha:]")
        (setq $p1 (point))
        (skip-chars-forward "[:alpha:]")
        (setq $p2 (point))))
    (when (not (eq last-command this-command))
      (put this-command 'state 0))
    (cond
     ((equal 0 (get this-command 'state))
      (upcase-initials-region $p1 $p2)
      (put this-command 'state 1))
     ((equal 1 (get this-command 'state))
      (upcase-region $p1 $p2)
      (put this-command 'state 2))
     ((equal 2 (get this-command 'state))
      (downcase-region $p1 $p2)
      (put this-command 'state 0)))))

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.

(straight-use-package 'meow)
(require 'meow)

(meow-global-mode)

(once '(:before self-insert-command)
  (setq meow-visit-sanitize-completion nil)
  (setq meow-use-clipboard t)
  (setq meow-esc-delay 0.001)
  (setq meow-keypad-start-keys '((?c . ?c) (?x . ?x)))
  (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)
          (+meow-save . +meow-save-line)
          (meow-kill . meow-kill-whole-line)
          (meow-pop-selection . meow-pop-grab)
          (meow-beacon-change . meow-beacon-change-char)
          (meow-cancel . keyboard-quit)
          (meow-delete . meow-C-d)))
  (setq meow-char-thing-table
        '((?r . round) (?b . square) (?c . curly) (?s . string) (?e . symbol)
          (?w . window) (?B . buffer) (?p . paragraph) (?< . line) (?> . line)
          (?d . defun) (?i . indent) (?x . extend) (?. . sentence)))
  (appendq! meow-mode-state-list
            '((helpful-mode . normal)
              (Man-mode . normal)
              (message-buffer-mode . normal))))

Autoload

;;;###autoload
(defadvice! meow-search-ad (&rest _)
  "Do not highlight number positions."
  :after #'meow-search
  (recenter))

;;;###autoload
(defadvice! meow-query-replace-ad (&rest _)
  "Call `meow-query-replace' and auto fill prompt with region text."
  :before #'meow-query-replace
  (unless (region-active-p) (meow-mark-symbol 1))
  (let ((text (buffer-substring-no-properties (region-beginning) (region-end))))
    (exchange-point-and-mark)
    (deactivate-mark t)
    (run-with-timer 0.05 nil 'insert text)))

;;;###autoload
(defadvice! meow-insert-exit-ad (&rest _)
  "Quit `completion-in-region-mode' after `meow-insert-exit'."
  :after #'meow-insert-exit
  (completion-in-region-mode -1))

;;;###autoload
(defadvice! meow-inhibit-highlight-num-positions-ad (&rest _)
  "Do not highlight number positions."
  :override #'meow--maybe-highlight-num-positions
  (ignore))

(defun +meow-save-line ()
  "Fallback command for `+meow-save'."
  (interactive)
  (let ((beg (if (eobp)
                 (line-beginning-position 0)
               (line-beginning-position)))
        (end (line-beginning-position 2)))
    (kill-ring-save beg end)))

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

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

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

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

Quick goto char (avy.el)

Jump to any visible text.

(straight-use-package 'avy)

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

(straight-use-package
 '(embrace :type git :host github :repo "cute-jumper/embrace.el"
           :fork (:host github :repo "alexluigit/embrace.el")))

(setq embrace-default-pairs
      '((?r . ("(" . ")"))
        (?R . ("( " . " )"))
        (?c . ("{" . "}"))
        (?C . ("{ " . " }"))
        (?\[ . ("[" . "]"))
        (?\] . ("[ " . " ]"))
        (?a . ("<" . ">"))
        (?A . ("< " . " >"))
        (?s . ("\"" . "\""))
        (?\' . ("\'" . "\'"))
        (?` . ("`" . "`"))))

Tab for Indentation (indent.el)

I believe tabs, in the sense of inserting the tab character, are best suited for indentation. While spaces are superior at precisely aligning text. However, I understand that elisp uses its own approach, which I do not want to interfere with. Also, Emacs tends to perform alignments by mixing tabs with spaces, which can actually lead to misalignments depending on certain variables such as the size of the tab. As such, I am disabling tabs by default.

If there ever is a need to use different settings in other modes, we can customise them via hooks. This is not an issue I have encountered yet and am therefore refraining from solving a problem that does not affect me.

Note that tab-always-indent will first do indentation and then try to complete whatever you have typed in.

(setq-default tab-always-indent t)
(setq-default tab-first-completion 'word-or-paren-or-punct) ; Emacs 27
(setq-default indent-tabs-mode nil)

USER INTERFACE

Basics

Show current key strokes in echo area after 0.25s

Disable bidirectional text scanning for a modest performance boost. I’ve set this to nil in the past, but the bidi-display-reordering’s docs say that is an undefined state and suggest the value left-to-right to be just as good.

Do not display continuation lines Do not disable the erase-buffer command

By default, page scrolling should keep the point at the same visual position, rather than force it to the top or bottom of the viewport. This eliminates the friction of guessing where the point has warped to.

As for per-line scrolling, I dislike the default behaviour of visually re-centring the point: it is too aggressive as a standard mode of interaction. With the following setq-default, the point will stay at the top/bottom of the screen while moving in that direction (use C-l to reposition it).

(setq-default bidi-display-reordering 'left-to-right)
(setq-default bidi-paragraph-direction 'left-to-right)
(setq bidi-inhibit-bpa t)
(setq-default truncate-lines t)
(setq echo-keystrokes 0.25)
(setq scroll-conservatively 101)
(setq scroll-up-aggressively 0.01)
(setq scroll-down-aggressively 0.01)
(setq auto-window-vscroll nil)
(setq scroll-step 1)
(setq scroll-margin 1)
(setq hscroll-step 1)
(setq hscroll-margin 1)
(put 'erase-buffer 'disabled nil)

Theme

Recommended themes (using their package names):

  • modus-vivendi A built-in theme in emacs (version >= 28) created by Protesilaos Stavrou.
  • ef-themes Yet another theme suite developed by Prot.
  • doom-themes A megapack of popular themes, including aesthetic extensions for popular packages.
  • apropospriate-theme Apropospriate theme.
  • lambda-themes Lambda themes.
  • color-theme-sanityinc-tomorrow SanityInc tomorrow theme.
  • mindre-theme Mindre theme.
(straight-use-package `(modus-themes ,@(when (> emacs-major-version 28) '(:type built-in))))
(straight-use-package 'ef-themes)
(straight-use-package '(lambda-themes :host github :repo "lambda-emacs/lambda-themes"))
(straight-use-package 'apropospriate-theme)
(straight-use-package 'doom-themes)
(straight-use-package 'color-theme-sanityinc-tomorrow)
(straight-use-package 'mindre-theme)
(once '(:hooks after-init-hook)
  (setq modus-themes-common-palette-overrides
        '((underline-link unspecified)
          (underline-link-visited unspecified)
          (underline-link-symbolic unspecified)))
  (setq ef-themes-to-toggle '(ef-summer ef-winter)
        ef-themes-mixed-fonts t
        ef-themes-variable-pitch-ui t)
  (load-theme grandview-theme t))

Fonts (fonts.el)

Here are some recommended fonts for programming or general text editing.

  • Victor Mono
  • Sarasa Mono SC
  • Fira Code Retina

A list of my favorite CJK fonts.

  • LXGW WenKai Mono
  • HarmonyOS Sans SC Light
  • Smartisan Compact CNS
  • 青鸟华光简报宋二
  • FZSuXinShiLiuKaiS-R-GB
(if (daemonp)
    (add-hook 'after-make-frame-functions #'+font-setup)
  (+font-setup))

Autoload

;;;###autoload
(defun +font-setup (&optional frame)
  "Setup default/fixed-pitch/variable-pitch/zh-font."
  (cl-loop with font-families = (font-family-list frame)
           for font in (list grandview-default-font grandview-fixed-font
                             grandview-variable-font)
           for name in '(default fixed-pitch variable-pitch)
           for fspec = (font-spec :family font)
           if (member font font-families) do
           (custom-theme-set-faces
            'user `(,name ((t (:font ,fspec :height ,grandview-font-size)))))
           else do (message "Font `%s' is not available" font)
           finally
           (progn
             (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)))))
             (if (member grandview-CJK-font font-families)
                 (dolist (charset '(kana han cjk-misc bopomofo))
                   (set-fontset-font t charset (font-spec :family grandview-CJK-font)))
               (message "Font `%s' is not available" grandview-CJK-font))
             (unless (> emacs-major-version 27)
               (set-fontset-font t 'symbol (font-spec :family "Noto Color Emoji"))))))

;;;###autoload
(defun +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 +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)))

Buffer management (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.

(straight-use-package '(ibuffer :type built-in))

(once '(:packages ibuffer)
  (add-hook 'ibuffer-mode-hook
            (lambda () (hl-line-mode) (ibuffer-update 0)))
  (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)
  (bind-keys :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)))

Window commands (window.el)

Window selection (windmove.el)

Directional window-selection routines.

(straight-use-package '(windmove :type built-in))

(bind-keys :map global-map
           ("M-o" . other-window)
           ("M-N" . windmove-down)
           ("M-P" . windmove-up)
           ("M-I" . windmove-right)
           ("M-O" . windmove-left))

Window placement (window.el)

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.

Disable cursor-in-non-selected-windows and highlight-nonselected-windows reduces rendering/line scan work for Emacs in non-focused windows.

(once '(:hooks window-configuration-change-hook)
  (setq-default cursor-in-non-selected-windows nil)
  (setq highlight-nonselected-windows nil)
  (setq display-buffer-alist
        `(("\\*\\(Flymake\\|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))
          ("\\*compilation\\*"
           (display-buffer-in-side-window)
           (window-height . 0.2)
           (side . 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)
  (setq split-width-threshold 120))

Autoload

;;;###autoload
(defun +show-messages (&optional erase)
  "Show *Messages* buffer in other frame.
If ERASE is non-nil, erase the buffer before switching to it."
  (interactive "P")
  (when erase
    (let ((inhibit-read-only t))
      (with-current-buffer "*Messages*" (erase-buffer))))
  (let ((win (get-buffer-window "*Messages*" t))
        (after-make-frame-functions nil))
    (if (window-live-p win)
        (delete-frame (window-frame win))
      (with-selected-frame (make-frame)
        (set-window-parameter (selected-window) 'no-other-window t)
        (switch-to-buffer "*Messages*")))))

(defvar +monocle--saved-window-configuration nil
  "Last window configuration before enabling `+monocle-mode'.")

;;;###autoload
(define-minor-mode +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'."
  :global t
  (let ((config +monocle--saved-window-configuration)
        (buf (current-buffer)))
    (if (one-window-p)
        (when config
          (set-window-configuration config))
      (setq +monocle--saved-window-configuration (current-window-configuration))
      (when (window-parameter nil 'window-side) (delete-window))
      (delete-other-windows)
      (switch-to-buffer buf))))

Frame parameters (frame.el)

  • Remove title bar on macOS
  • Enter fullscreen automatically on macOS
  • Adjust frame opacity dynamically
(when  (eq system-type 'darwin)
  (add-to-list 'default-frame-alist '(undecorated . t))
  (add-to-list 'default-frame-alist '(fullscreen . maximized)))

(when (> emacs-major-version 28)
  (add-hook 'window-configuration-change-hook #'+frame-opacity-auto))

Autoload

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

(defcustom +frame-opacity (if (eq system-type 'gnu/linux) 80 90)
  "Default frame opacity."
  :group 'grandview
  :type 'integer)

(defun +frame-opacity--adjust (opacity)
  (pcase system-type
    ('darwin (set-frame-parameter nil 'alpha `(,opacity ,opacity)))
    (_ (set-frame-parameter nil 'alpha-background opacity))))

;;;###autoload
(defun +frame-opacity-auto ()
  "Setup frame opacity according to current major-mode."
  (+frame-opacity--adjust +frame-opacity))

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

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

Icon library (nerd-icons.el)

(straight-use-package
 '(nerd-icons :repo "rainstormstudio/nerd-icons.el"
              :host github
              :files (:defaults "data")))

Nerd fonts (nerd-fonts.el)

(straight-use-package
 '(nerd-fonts :host github :repo "twlz0ne/nerd-fonts.el"))

;; (once '(:hooks pre-command-hook)
(require 'nerd-fonts)
  ;; )

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.

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

Autoload

;;;###autoload
(defun +minibuffer-append-metadata (metadata candidates)
  "Append METADATA for CANDIDATES."
  (let ((entry (if (functionp metadata)
                   `(metadata (annotation-function . ,metadata))
                 `(metadata (category . ,metadata)))))
    (lambda (string pred action)
      (if (eq action 'metadata)
          entry
        (complete-with-action action candidates string pred)))))

Minibuffer history (savehist.el)

Keeps a record of actions involving the minibuffer.

(once '(:hooks minibuffer-setup-hook)
  (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.

(straight-use-package 'vertico)

(once '(:hooks pre-command-hook)
  (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.

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.

(straight-use-package 'pinyinlib)
(straight-use-package 'orderless)

(autoload 'pinyinlib-build-regexp-string "pinyinlib")
(setq completion-styles '(orderless partial-completion basic))
(setq orderless-component-separator #'orderless-escapable-split-on-space)
(setq orderless-matching-styles
      '(+orderless-pinyin-only-initialism
        orderless-initialism
        orderless-prefixes
        orderless-regexp))
(setq orderless-style-dispatchers
      '(+orderless-literal-dispatcher
        +orderless-initialism-dispatcher
        +orderless-without-literal-dispatcher
        +orderless-pinyin-dispatcher))

Autoload

(defun +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 +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 +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 +orderless-pinyin-dispatcher (pattern _index _total)
  "Pinyin initialism dispatcher using the backtick sign as a prefix."
  (when (string-prefix-p "`" pattern)
    `(+orderless-pinyin-only-initialism . ,(substring pattern 1))))

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

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.

(straight-use-package 'consult)

(once '(:packages vertico)
  (setq completion-in-region-function #'consult-completion-in-region)
  (advice-add #'register-preview :override #'consult-register-window)
  (setq register-preview-delay 0.2)
  (setq register-preview-function #'consult-register-format)
  (setq xref-show-xrefs-function #'consult-xref)
  (setq xref-show-definitions-function #'consult-xref)
  (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 ">")
  (bind-keys :map grandview-mct-map
             ("/" . consult-line-multi)
             ("t" . consult-mark)
             ("T" . consult-global-mark)
             ("a" . consult-apropos)
             ("e" . consult-compile-error)
             ("r" . consult-ripgrep)
             ("k" . consult-kmacro)
             ("K" . consult-keep-lines)
             ("i" . consult-imenu-multi)
             ("n" . consult-focus-lines) ; narrow
             ("o" . consult-outline)
             ("R" . consult-register)
             ("y" . consult-yank-from-kill-ring)
             ("m" . consult-bookmark)
             ("c" . consult-complex-command)
             ("C" . consult-mode-command)
             ("M" . consult-minor-mode-menu)))

(once '(:packages meow)
  (bind-key "/" 'consult-line meow-normal-state-keymap))

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.

(straight-use-package 'marginalia)

(once '(:packages vertico)
  (marginalia-mode)
  (setq marginalia-align 'left))

Completion overlay region function (corfu.el)

Corfu enhances the default completion in region function with a completion overlay. The current candidates are shown in a popup below or above the point. Corfu can be considered the minimalistic completion-in-region counterpart of Vertico.

We also enabled corfu-doc-mode to show documentation of the candidates in a pop-up window.

(straight-use-package 'corfu)

(once '(:before self-insert-command)
  (setq! corfu-auto t)
  (setq! corfu-auto-delay 0.05)
  (setq! corfu-auto-prefix 2)
  (setq! corfu-cycle t)
  (setq! corfu-preselect 'prompt)
  (setq! corfu-on-exact-match nil)
  (global-corfu-mode)
  (bind-keys :map corfu-map
             ("TAB" . corfu-next)
             ([tab] . corfu-next)
             ("S-TAB" . corfu-previous)
             ([backtab] . corfu-previous)
             ("M-n" . nil)
             ("M-p" . nil)))

Completion at point Extensions (cape.el)

Let your completions fly! This package provides additional completion backends in the form of Capfs (completion-at-point-functions).

(straight-use-package 'cape)

(once '(:before self-insert-command)
  (setq! cape-dict-file "/usr/share/dict/words")
  (add-to-list 'completion-at-point-functions #'cape-file)
  (add-to-list 'completion-at-point-functions #'cape-dabbrev)
  (add-to-list 'completion-at-point-functions #'cape-keyword)
  (add-to-list 'completion-at-point-functions #'cape-ispell)
  (add-to-list 'completion-at-point-functions #'cape-dict)
  (define-prefix-command '+cape-map)
  (defvar +cape-prefix-map (make-sparse-keymap))
  (defalias '+cape-map +cape-prefix-map)
  (bind-keys :map global-map
             ("C-M-/" . +cape-map)       ; remapped `dabbrev-completion'
             :map +cape-prefix-map
             ("c" . completion-at-point) ; capf
             ("t" . complete-tag)        ; etags
             ("d" . cape-dabbrev)        ; or dabbrev-completion
             ("f" . cape-file)
             ("k" . cape-keyword)
             ("s" . cape-symbol)
             ("a" . cape-abbrev)
             ("i" . cape-ispell)
             ("l" . cape-line)
             ("w" . cape-dict)
             ("\\" . cape-tex)
             ("_" . cape-tex)
             ("^" . cape-tex)
             ("&" . cape-sgml)
             ("r" . cape-rfc1345)))

Keybindings

This section contains all core keybindings of Grandview.

Orientation

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. On GUI, we can use input-decode-map to give C-i different meaning. On terminal, we rebind <f6> to C-i, so make sure you have relevant settings in your terminal emulator’s settings.

;; (define-key input-decode-map [?\C-i] [C-i])
(add-hook 'after-make-frame-functions
          (lambda (f) (with-selected-frame f
                        (define-key input-decode-map [?\C-i] [C-i]))))

macOS specific settings.

(setq mac-command-modifier 'meta)
(setq mac-option-modifier 'super)

INSERT

(once '(:packages meow)
  (bind-keys :map meow-insert-state-keymap
             ("M-<backspace>" . meow-kill-whole-line)
             ("<C-i>" . meow-right)
             ("C-o" . meow-left)))

NORMAL

(once '(:packages meow)
  (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>" . +meow-escape)
   '("<backspace>" . meow-pop-selection)
   '("," . meow-inner-of-thing)
   '("." . meow-bounds-of-thing)
   '("<" . meow-beginning-of-thing)
   '(">" . meow-end-of-thing)
   '("-" . negative-argument)
   '("=" . meow-query-replace)
   '("+" . meow-query-replace-regexp)
   '("^" . meow-last-buffer)
   '("a" . +meow-insert)
   '("A" . +meow-insert-at-first-non-whitespace)
   '("b" . meow-block)
   '("B" . meow-to-block)
   '("c" . meow-change)
   '("C" . meow-change-save)
   '("d" . meow-delete)
   '("e" . meow-line)
   '("E" . +simple-mark-inner-line)
   '("f" . meow-find)
   '("F" . forward-sexp)
   '("g" . meow-grab)
   '("G" . meow-sync-grab)
   '("h" . embrace-commander)
   '("i" . meow-right)
   '("I" . meow-right-expand)
   '("j" . +simple-join-line)
   '("J" . meow-join)
   '("k" . meow-kill)
   '("K" . meow-C-k)
   '("l" . consult-goto-line)
   '("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" . quit-window)
   '("r" . meow-reverse)
   '("R" . repeat)
   '("s" . meow-search)
   '("S" . meow-pop-search)
   '("t" . avy-goto-char-timer)
   '("T" . avy-resume)
   '("u" . undo)
   '("U" . undo-redo)
   '("v" . consult-mark)
   '("V" . consult-global-mark)
   '("w" . meow-next-word)
   '("W" . meow-back-word)
   '("x" . +meow-save)
   '("y" . meow-replace)
   '("Y" . meow-yank-pop)
   '("z" . meow-start-kmacro-or-insert-counter)
   '("Z" . meow-end-or-call-kmacro)))

LEADER

(once '(:packages meow)
  (meow-leader-define-key
   '("SPC" . consult-buffer)
   '("0" . delete-window)
   '("1" . delete-other-windows)
   '("2" . split-window-below)
   '("3" . split-window-right)
   '("4" . ctl-x-4-prefix)
   '("5" . ctl-x-5-prefix)
   '("8" . insert-char)
   '("9" . grandview-tab-map)
   '("?" . describe-keymap)
   '("/" . describe-symbol)
   '(";" . comment-line)
   '("," . beginning-of-buffer)
   '("." . end-of-buffer)
   '("a" . grandview-apps-map)
   '("e" . dired-jump)
   '("E" . eval-expression)
   '("f" . grandview-files-map)
   '("i" . ibuffer)
   '("k" . kill-this-buffer)
   '("n" . +project-find-file)
   '("o" . grandview-org-map)
   '("p" . grandview-prog-map)
   '("P" . grandview-project-map)
   '("r" . grandview-reg-map)
   '("t" . grandview-mct-map)
   '("w" . grandview-win/tabs-map)
   '("z" . window-toggle-side-windows)))

(bind-keys :map grandview-files-map
           ("w" . save-buffer) ; [SPC x s] in Colemak is painful to press
           ("g" . grandview-config)
           :map grandview-apps-map
           ("d" . toggle-debug-on-error)
           ("o" . +frame-opacity-set)
           ("=" . count-words)
           ("m" . +show-messages))

GLOBAL

All major bindings work globally.

(bind-keys :map global-map
           ("<f6>" . +simple-pop-local-mark-ring)
           ("M-SPC" . +monocle-mode) ; replaced `just-one-space'
           ("S-SPC" . toggle-input-method)
           ("M-u"   . +toggle-letter-case)
           ("<C-i>" . +simple-pop-local-mark-ring)
           ("C-o" . pop-global-mark)
           ("s-n" . scroll-up-command)
           ("s-p" . scroll-down-command)
           ("M-o" . other-window)
           ("M-n" . forward-paragraph)
           ("M-p" . backward-paragraph)
           :map tab-prefix-map
           ("w"   . other-window)
           :map minibuffer-local-map
           ("<mouse-8>" . exit-minibuffer)
           ("M-<backspace>" . meow-kill-whole-line)
           ("<f6>" . forward-char)
           ("<C-i>" . forward-char)
           ("C-o" . backward-char)
           :map meow-insert-state-keymap
           ("<f6>" . meow-right)
           :map image-map
           ("o" . nil)
           ("w" . image-save))

These keybindings are available when the current major mode doesn’t define that key.

(when (featurep 'meow)
  (meow-motion-overwrite-define-key '("<escape>" . +meow-escape)))

File management

File/Directory handling functions (files.el)

  • Save modified buffers automatically
  • Utilities:
    • +files-find-dotfiles
    • +files-sudo-find
    • +files-rename-file-and-buffer
    • +files-find-user-files
(setq large-file-warning-threshold 50000000)
(setq permanently-enabled-local-variables '(lexical-binding encoding))
(setq auto-save-default nil)
(setq +files-user-dirs-alist
      '(((title . "  Shows")        (path . "/mnt/HDD/Share"))
        ((title . "  Coding")       (path . "/mnt/HDD/Dev"))
        ((title . "  Books")        (path . "/mnt/HDD/Book"))
        ((title . "  Videos")       (path . "/mnt/HDD/Video"))
        ((title . "  Notes")        (path . "~/Documents/notes"))
        ((title . "  Photos")       (path . "~/Pictures"))
        ((title . "  Downloads")    (path . "~/Downloads"))))
(setq confirm-kill-processes nil)
(auto-save-visited-mode)

(bind-keys
 :map grandview-files-map
 ("." . +files-find-dotfiles)
 ("s" . +files-sudo-find)
 ("r" . +files-rename-file-and-buffer)
 ("u" . +files-find-user-files))

Autoload

(defcustom +files-dotfiles-repo (getenv "DOTPATH")
  "Doc."
  :group 'grandview :type 'string)

(defcustom +files-user-dirs-alist
  '(((title . "  Photos")       (path . "~/Pictures/"))
    ((title . "  Videos")       (path . "~/Video/"))
    ((title . "  Downloads")    (path . "~/Downloads/")))
  "Doc."
  :group 'grandview :type '(repeat list))

(defun +files--in-directory (dir &optional prompt)
  "Use `fd' to list files in DIR."
  (let* ((default-directory dir)
         (command "fd -H -t f -0")
         (output (shell-command-to-string command))
         (files-raw (split-string output "\0" t))
         (files (+minibuffer-append-metadata 'file files-raw))
         (file (completing-read (or prompt "Open file: ") files)))
    (find-file (concat dir "/" file))))

;;;###autoload
(defun +files-rename-file-and-buffer (name)
  "Apply NAME to current file and rename its buffer.
Do not try to make a new directory or anything fancy."
  (interactive
   (list (read-string "Rename current file: " (buffer-file-name))))
  (let* ((file (buffer-file-name)))
    (if (vc-registered file)
        (vc-rename-file file name)
      (rename-file file name))
    (set-visited-file-name name t t)))

;;;###autoload
(defun +files-find-dotfiles ()
  "Open files in dotfiles repo."
  (interactive)
  (unless +files-dotfiles-repo
    (user-error "`+files-dotfiles-repo' is undefined"))
  (+files--in-directory +files-dotfiles-repo " Dotfiles: "))

;;;###autoload
(defun +files-sudo-find ()
  "Reopen current file as root."
  (interactive)
  (let ((file (buffer-file-name)))
    (find-file (if (file-writable-p file)
                   file
                 (concat "/sudo::" file)))))

;;;###autoload
(defun +files-find-user-files ()
  "Open files in directories defined in `+files-user-dirs-alist'."
  (interactive)
  (let* ((cands-raw
          (mapcar (lambda (i) (cdr (assq 'title i))) +files-user-dirs-alist))
         (get-item (lambda (s field)
                     (cl-dolist (i +files-user-dirs-alist)
                       (when (string= s (cdr (assq 'title i)))
                         (cl-return (cdr (assq field i)))))))
         (annotation
          (lambda (s) (marginalia--documentation (funcall get-item s 'path))))
         (cands (+minibuffer-append-metadata annotation cands-raw))
         (title (completing-read "Open: " cands nil t))
         (path (funcall get-item title 'path)))
    (+files--in-directory path (concat title ": "))))

Find libraries (find-func.el)

This packages provides the find-library command which allows us browsing the source code of Emacs efficiently, want to have to look on dired.el? Just M-x find-library RET dired. Even better, we can introspect the C code of Emwacs itself as long as the find-function-C-source-directory is set properly.

(straight-use-package 'find-func)

(when (eq system-type 'gnu/linux)
  (setq find-function-C-source-directory "~/Code/emacs/src"))
(bind-keys :map grandview-files-map
           ("l" . find-library))

Recent files (recentf.el)

Keep a record of all recently opened files.

(straight-use-package '(recentf :type built-in))

(once '(:before after-find-file)
  (setq recentf-max-saved-items 100)
  (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.

(straight-use-package '(saveplace :type built-in))

(once '(:hooks find-file-hook)
  (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).

(straight-use-package '(autorevert :type built-in))

(once '(:hooks find-file-hook)
  (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!

(straight-use-package '(dired :type built-in))
(straight-use-package '(dired-x :type built-in))
(straight-use-package '(dired-aux :type built-in))
(straight-use-package 'diredfl)

(add-hook 'dired-mode-hook 'diredfl-mode)
(add-hook 'dirvish-directory-view-mode-hook 'diredfl-mode)
(once '(:packages diredfl)
  (set-face-attribute 'diredfl-dir-name nil :bold t)
  (add-hook 'enable-theme-functions
            (lambda (_theme)
              (set-face-attribute 'diredfl-dir-name nil :bold t))))

(when (eq system-type 'darwin) (setq insert-directory-program "gls"))
(setq dired-listing-switches
      "-l --almost-all --human-readable --time-style=long-iso --group-directories-first --no-group")

(once '(:before dired-noselect dired-jump dirvish-curr)
  (setq mouse-1-click-follows-link nil)
  (setq dired-mouse-drag-files t)                   ; added in Emacs 29
  (setq mouse-drag-and-drop-region-cross-program t) ; added in Emacs 29
  (setq dired-kill-when-opening-new-dired-buffer t) ; added in Emacs 28
  (setq dired-recursive-copies 'always)
  (setq dired-recursive-deletes 'always)
  (setq delete-by-moving-to-trash t)
  (setq dired-dwim-target t)
  (setq! dired-bind-info nil)
  (setq! dired-bind-man nil)
  (setq dired-clean-confirm-killing-deleted-buffers nil)
  (setq dired-do-revert-buffer t)
  (setq dired-auto-revert-buffer #'dired-directory-changed-p)
  (bind-keys :map dired-mode-map
             ("/" . dired-goto-file)
             ("," . dired-create-directory)
             ("." . dired-create-empty-file)
             ("I" . dired-insert-subdir)
             ("K" . dired-kill-subdir)
             ("O" . dired-find-file-other-window)
             ("[" . 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)))

(with-eval-after-load 'dired-x
  (setq dired-omit-files (concat dired-omit-files "\\|^\\..*$")))

Writable Dired (wdired.el)

Bulk renaming files like a breeze.

(straight-use-package '(wdired :type built-in))

(once '(:packages dired)
  (setq wdired-allow-to-change-permissions t)
  (setq wdired-create-parent-directories t)
  (bind-keys :map dired-mode-map
             ("i" . wdired-change-to-wdired-mode)))

Use dired to browse and manipulate images (image-dired.el)

image-dired allows us to browse and manipulate images using Dired.

  • show bigger sized thumbnail image, we are in the 21st century
  • no not display original image in other window when flag/mark files
    • it’s very slow
    • if I want to view the bigger image, I use dirvish instead
  • tweak the keybindings to my preferences
(straight-use-package '(image-dired :type built-in))

(once '(:packages image-dired)
  (setq image-dired-thumb-size 256)
  (setq image-dired-marking-shows-next nil)
  (bind-keys :map image-dired-thumbnail-mode-map
             ("n" . image-dired-next-line)
             ("p" . image-dired-previous-line)
             ("i" . image-dired-forward-image)
             ("o" . image-dired-backward-image)))

A polished dired with batteries included (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.

(straight-use-package '(dirvish :type git :repo "alexluigit/dirvish" :depth full))

(dirvish-override-dired-mode)
(dirvish-side-follow-mode)
(dirvish-peek-mode)
(add-hook 'dirvish-setup-hook 'dirvish-emerge-mode)
(setq dirvish-attributes
      '(vc-state file-size git-msg subtree-state nerd-icons collapse file-time))
(setq! dirvish-subtree-state-style 'nerd)
(setq dirvish-mode-line-format '(:left (sort symlink) :right (vc-info yank index)))
(setq dirvish-header-line-height '(25 . 35))
(setq dirvish-side-width 38)
(setq dirvish-header-line-format '(:left (path) :right (free-space)))
(setq dirvish-path-separators (list "" "" ""))
(once '(:hooks pre-command-hook)
  (setq! dirvish-quick-access-entries
         '(("o" "~/"                          "Home")
           ("d" "/opt/dotfiles/"              "Dotfiles")
           ("u" "~/.cache/emacs/"             "Emacs cache")
           ("p" "~/Code/"                     "Code")
           ("n" "~/Downloads/"                "Downloads")
           ("w" "~/Pictures/wallpaper/"       "Wallpaper")
           ("m" "/mnt/"                       "Mounted Drives")
           ("s" "/mnt/HDD/Share/"             "Shared files")
           ("a" "🔍\\\.org$📁~/Documents/📁" "AllNotes")
           ("t" "~/.local/share/Trash/files/" "Trash")
           ("W"  "/smb:alex%192.168.0.177@192.168.0.177:share/")))
  (bind-keys :map 'dirvish-mode-map
             ("<mouse-1>" . dirvish-subtree-toggle-or-open)     ; left click for expand/collapse dir or open file
             ("<mouse-2>" . dired-mouse-find-file-other-window) ; middle click for opening file / entering dir in other window
             ("<mouse-3>" . dired-mouse-find-file)              ; right click for opening file / entering dir
             ("<mouse-8>" . dired-do-shell-command)             ; side button for shell command execution
             ("SPC" . consult-buffer)
             ("M-n" . dirvish-history-go-forward)
             ("M-p" . dirvish-history-go-backward)
             ("h"   . dirvish-history-jump)
             ("^"   . dirvish-history-last)
             ("TAB" . dirvish-subtree-toggle)
             ("a"   . dirvish-quick-access)
             ("f"   . dirvish-file-info-menu)
             ("v"   . dirvish-vc-menu)
             ("*"   . dirvish-mark-menu)
             ("N"   . dirvish-narrow)
             ("M-e" . dirvish-emerge-menu)
             ("M-t" . dirvish-layout-toggle)
             ("M-s" . dirvish-setup-menu)
             ("M-j" . dirvish-fd-jump)
             ([remap dired-sort-toggle-or-edit] . dirvish-quicksort)
             ([remap dired-do-redisplay] . dirvish-ls-switches-menu)
             ([remap dired-do-copy] . dirvish-yank-menu)
             :map mode-specific-map
             ("e" . dirvish-dwim)
             :map grandview-files-map
             ("e" . dirvish)
             ("f" . dirvish-fd)
             ("n" . dirvish-side)
             ("o" . dirvish-quick-access)
             ("b" . dirvish-fd-jump)))

Project management (project.el)

(straight-use-package '(project :type built-in))

(setq project-switch-commands
      '((project-find-file "File" ?\r)
        (+project-find-subdir "Subdir" ?s)
        (project-dired "Dired" ?d)
        (+project-retrieve-tag "Tag switch" ?t)
        (+project-magit-status "Magit" ?m)
        (+project-commit-log "Log VC" ?l)))
(setq +project-commit-log-limit 25)

(bind-keys :map project-prefix-map
           ("l" . +project-commit-log)
           ("m" . +project-magit-status)
           ("s" . +project-find-subdir)
           ("t" . +project-retrieve-tag))

Autoload

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

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

;;;###autoload
(cl-defmethod project-root ((project (head local)))
  "Project root for PROJECT with HEAD and LOCAL."
  (if (< emacs-major-version 29)
      (cdr-safe project)
    (car (project-roots project))))

;; Copied from Manuel Uberti and tweaked accordingly:
;; <https://www.manueluberti.eu/emacs/2020/11/14/extending-project/>.
(defun +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 #'+project--project-files-in-directory
          (or dirs (list (project-root project)))))

(defun +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 +project-find-subdir ()
  "Find subdirectories in the current project, using completion."
  (interactive)
  (let* ((pr (project-current t))
         (dir (project-root pr))
         (dirs-raw (+project--directory-subdirs dir))
         (subdirs (+minibuffer-append-metadata 'file dirs-raw))
         (directory (completing-read "Select Project subdir: " subdirs)))
    (dired directory)))

;;;###autoload
(defun +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
`+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 +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 +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 +project-magit-status ()
  "Run `magit-status' on project."
  (interactive)
  (let* ((pr (project-current t))
         (dir (project-root pr)))
    (magit-status dir)))

;;;###autoload
(defun +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)))

Working with remote files (tramp.el)

(straight-use-package '(tramp :type built-in))

(once '(:packages tramp)
  (add-to-list 'tramp-connection-properties
               (list (regexp-quote "/ssh:buzz:") "direct-async-process" t))
  (setq tramp-verbose 0)
  (setq tramp-auto-save-directory (locate-user-emacs-file "tramp/"))
  (setq tramp-chunksize 2000)
  (setq! tramp-use-ssh-controlmaster-options nil))

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
(straight-use-package '(org :type built-in))

(once '(:packages org)
  (add-hook 'org-mode-hook '+org-font-setup)
  (add-hook 'org-mode-hook 'org-indent-mode)
  (add-hook 'org-tab-first-hook 'org-end-of-line)
  (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-keys :map grandview-org-map ("o" . consult-org-heading)
             :map org-mode-map
             ("C-c l" . org-insert-last-stored-link)))

Autoload

(require 'org-faces)

;;;###autoload
(defadvice! org-fill-paragraph-ad (&rest _)
  "Let `org-fill-paragraph' works inside of src block in Org-mode."
  :before-while #'org-fill-paragraph
  (let* ((element (save-excursion (beginning-of-line) (org-element-at-point)))
         (type (org-element-type element)))
    (if (and (eq type 'src-block)
             (> (line-beginning-position)
                (org-element-property :post-affiliated element))
             (< (line-beginning-position)
                (org-with-point-at (org-element-property :end element)
                  (skip-chars-backward " \t\n")
                  (line-beginning-position))))
        (progn (org-babel-do-in-edit-buffer (fill-paragraph)) nil)
      t)))

;;;###autoload
(defun +org-font-setup ()
  "Setup variable-pitch fonts for org-mode."
  (variable-pitch-mode)
  (let* ((families (font-family-list))
         (fallback `(:font ,(car families)))
         (variable-pitch (if (member grandview-variable-font families)
                             `(:font ,grandview-variable-font) fallback))
         (default (if (member grandview-default-font families)
                      `(:font ,grandview-default-font) fallback)))
    (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-table ((t (,@default))))
     `(org-verbatim ((t (,@default))))
     `(org-formula ((t (,@default))))
     `(org-code ((t (,@default))))
     `(org-block ((t (,@default))))
     `(org-block-begin-line ((t (:foreground "#606060" :extend t))))
     '(org-tag ((t (:inherit (shadow) :weight bold :height 0.8)))))))

Identifiers for org entries (org-id.el)

(straight-use-package '(org-id :type built-in))

(add-hook 'org-mode-hook '+org-id-update)
(setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id)

Autoload

(require 'org-id)

(defvar-local +org-id-auto nil)

(defun +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 +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 `+org-id-new'. In any case, the
CUSTOM_ID of the entry is returned."
  (interactive)
  (org-with-point-at pom
    (let* ((path (mapconcat #'identity (org-get-outline-path) "-"))
           (h-str (concat (unless (equal path "") (concat path "-"))
                          (org-get-heading t t t t)))
           (heading (replace-regexp-in-string
                     "/\\|~\\|\\[\\|\\]" ""
                     (replace-regexp-in-string "[[:space:]]+" "_" h-str)))
           (id (org-entry-get nil "CUSTOM_ID")))
      (cond
       ((and id (stringp id) (string-match "\\S-" id))
        id)
       (create (setq id (+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 +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 +org-id-auto
      (when force
        (org-map-entries (lambda () (org-entry-delete nil "CUSTOM_ID"))))
      (org-map-entries (lambda () (+org-custom-id-get (point) 'create))))))

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

Literate programming (ob.el)

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

(straight-use-package '(ob :type built-in))
(straight-use-package '(ob-C :type built-in))
(straight-use-package '(ob-js :type built-in))
(straight-use-package '(ob-shell :type built-in))
(straight-use-package '(ob-latex :type built-in))
(straight-use-package '(ob-makefile :type built-in))
(straight-use-package '(ob-csharp :host github :repo "samwdp/ob-csharp"))

(once '(:packages org)
  (autoload 'org-babel-execute:C "ob-C")
  (autoload 'org-babel-expand:C "ob-C")
  (autoload 'org-babel-execute:cpp "ob-C")
  (autoload 'org-babel-expand:cpp "ob-C")
  (autoload 'org-babel-execute:csharp "ob-csharp")
  (autoload 'org-babel-execute:python "ob-python")
  (autoload 'org-babel-execute:js "ob-js")
  (autoload 'org-babel-execute:bash "ob-shell")
  (autoload 'org-babel-expand:latex "ob-latex")
  (autoload 'org-babel-execute:latex "ob-latex")
  (autoload 'org-babel-execute:makefile "ob-makefile")
  (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)

(straight-use-package '(org-src :type built-in))

(once '(:packages org-src)
  (setq org-src-window-setup 'split-window-right)
  (push '("conf-unix" . conf-unix) org-src-lang-modes))

Reveal invisible org elements (org-appear.el)

(straight-use-package 'org-appear)

(add-hook 'org-mode-hook 'org-appear-mode)
(setq org-appear-autolinks t)
(setq org-hide-emphasis-markers t)

Modern org style (org-modern.el)

(straight-use-package 'org-modern)

(add-hook 'org-mode-hook 'org-modern-mode)
(add-hook 'org-agenda-finalize-hook 'org-modern-agenda)

Habit (org-habit.el)

(straight-use-package '(org-habit :type built-in))

(once '(:packages org)
  (appendq! org-modules '(org-habit))
  (setq org-habit-graph-column 60))

Slide (org-tree-slide.el)

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

(straight-use-package 'org-tree-slide)

(bind-key "S" 'org-tree-slide-mode grandview-org-map)

(once '(:packages org-tree-slide)
  (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)
  (bind-keys :map 'org-tree-slide-mode-map
             ("<left>" . org-tree-slide-move-previous-tree)
             ("<right>" . org-tree-slide-move-next-tree)))

Autoload

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

;;;###autoload
(defadvice! +org-tree-slide-simple-header-a (blank-lines)
  "Set the header with overlay.
Some number of BLANK-LINES will be shown below the header."
  :override #'org-tree-slide--set-slide-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)
         (+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))
         (+monocle-mode -1)
         (+frame-cursor-dim-mode -1)
         (org-clear-latex-preview)
         (org-mode))))

Text editing

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.

(straight-use-package '(so-long :type built-in))

(once '(:hooks find-file-hook) (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. Although fill-column variable is not defined in fill.el, I believe put them all together here make things easier to track.

With regard to paragraphs, I find that a double space is the best way to delimit sentences in source form, where a monospaced typeface is customary. There is no worry that this will be shown on a website or rendered version of a document, because processors know how to handle spacing. We do this to make phrases easier to tell apart, but also to render unambiguous commands like forward-sentence.

(straight-use-package '(fill :type built-in))

(add-hook 'text-mode-hook 'turn-on-auto-fill)

(setq-default fill-column 80)
(setq colon-double-space nil)
(setq adaptive-fill-mode t)
(setq sentence-end-double-space t)
(setq sentence-end-without-period nil)

Cross reference (xref.el)

xref provides helpful commands for code navigation and discovery.

(straight-use-package '(fill :type built-in))

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

(straight-use-package '(ediff :type built-in))

(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
      '("<<<<<<< grandv-ediff-combine Variant A" A
        ">>>>>>> grandv-ediff-combine Variant B" B
        "####### grandv-ediff-combine Ancestor" Ancestor
        "======= grandv-ediff-combine End"))

Autoload

;;;###autoload
(defun +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 ".*grandv-ediff.*" (point-min) (point-max) nil))

Input method (rime.el)

(straight-use-package 'rime)

(setq default-input-method "rime")
(once '(:packages rime)
  (when (eq system-type 'darwin)
    (setq! rime-librime-root (expand-file-name "librime" user-emacs-directory)))
  (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))
  (setq rime-show-candidate 'posframe)
  (setq rime-posframe-style 'vertical)
  (setq rime-posframe-properties '(:internal-border-width 10 :lines-truncate t))
  (setq rime-title "")
  (setq rime-candidate-num-format-function #'+rime-candidate-num-fmt)
  (custom-theme-set-faces
   'user '(rime-preedit-face ((t (:inherit lazy-highlight)))))
  (bind-keys :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)))

Autoload

;;;###autoload
(defadvice! rime-return-a (fn &rest args)
  "Make return key (commit script text) compatible with vterm."
  :around #'rime-return
  (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 +rime-candidate-num-fmt (num select-labels)
  "Format for the number before each candidate."
  (if select-labels
      (format "%s " (nth (1- num) select-labels))
    (format "%d. " num)))

Snippet (tempel.el)

(straight-use-package 'tempel)

(add-hook 'prog-mode-hook '+tempel-setup-capf)
(add-hook 'text-mode-hook '+tempel-setup-capf)

Autoload

;;;###autoload
(defadvice! tempel-condition-ad (modes plist)
  "Return non-nil if one of MODES matches and the PLIST condition is satisfied."
  :override #'tempel--condition-p
  (and
   (cl-loop
    for m in modes thereis
    (or (eq m #'fundamental-mode)
        (derived-mode-p m)
        (when-let* (((derived-mode-p 'org-mode))
                    (element (org-element-context))
                    ((eq 'src-block (car-safe element))))
          (if-let* ((lang (plist-get (cadr element) :language))
                    (mode (org-src-get-lang-mode lang))
                    ((fboundp mode)))
              mode
            #'fundamental-mode))))
   (or (not (plist-member plist :condition))
       (save-excursion
         (save-restriction
           (save-match-data
             (eval (plist-get plist :condition) 'lexical)))))))

;;;###autoload
(defun +tempel-setup-capf ()
  ;; Add the Tempel Capf to `completion-at-point-functions'.
  ;; `tempel-expand' only triggers on exact matches. Alternatively use
  ;; `tempel-complete' if you want to see all matches, but then you
  ;; should also configure `tempel-trigger-prefix', such that Tempel
  ;; does not trigger too often when you don't expect it. NOTE: We add
  ;; `tempel-expand' *before* the main programming mode Capf, such
  ;; that it will be tried first.
  (setq-local completion-at-point-functions
              (cons #'tempel-complete completion-at-point-functions)))

Templates

All the Tempo syntax elements are fully supported. The syntax elements are described in detail in the docstring of tempo-define-template in tempo.el. We document the important ones here:

  • “string” Inserts a string literal.
  • p Inserts an unnamed placeholder field.
  • n Inserts a newline.
  • > Indents with indent-according-to-mode.
  • r Inserts the current region.
  • r> The region, but indented.
  • n> Inserts a newline and indents.
  • & Insert newline if there is only whitespace between line start and point.
  • % Insert newline if there is only whitespace between point and line end.
  • o Like % but leaves the point before newline.
  • (s NAME) Inserts a named field.
  • (p PROMPT <NAME> <NONINS>) Insert an optionally named field with a prompt. The PROMPT is displayed directly in the buffer as default value. If NOINSERT is non-nil, no field is inserted. Then the minibuffer is used for prompting and the value is bound to NAME.
  • (r PROMPT <NAME> <NOINSERT>) Insert region or act like (p ...).
  • (r> PROMPT <NAME> <NOINSERT>) Act like (r ...), but indent region.

Furthermore Tempel supports syntax extensions:

  • (p FORM <NAME> <NONINS>) Like p described above, but FORM is evaluated.
  • (FORM ...) Other Lisp forms are evaluated. Named fields are lexically bound.

Use caution with templates which execute arbitrary code!

fundamental-mode ;; Available everywhere

(today (format-time-string "%Y-%m-%d"))

prog-mode

(fixme (if (derived-mode-p 'emacs-lisp-mode) ";; " comment-start) "FIXME ")
(todo (if (derived-mode-p 'emacs-lisp-mode) ";; " comment-start) "TODO ")
(bug (if (derived-mode-p 'emacs-lisp-mode) ";; " comment-start) "BUG ")
(hack (if (derived-mode-p 'emacs-lisp-mode) ";; " comment-start) "HACK ")

latex-mode

(begin "\\begin{" (s env) "}" > n> r> "\\end{" (s env) "}")
(frac "\\frac{" p "}{" p "}")
(enumerate "\\begin{enumerate}\n\\item " r> n> "\\end{enumerate}")
(itemize "\\begin{itemize}\n\\item " r> n> "\\end{itemize}")

lisp-mode emacs-lisp-mode ;; Specify multiple modes

(lambda "(lambda (" p ")" n> r> ")")

emacs-lisp-mode

(use "(straight-use-package '" p ")")
(cond "(cond (" p ")" n> "()" n> "()" ")")
(lambda "(lambda (" p ")" n> r> ")")
(var "(defvar " p "\n  \"" p "\")")
(const "(defconst " p "\n  \"" p "\")")
(custom "(defcustom " p "\n  \"" p "\"" n> ":type '" p ")")
(face "(defface " p " '((t :inherit " p "))\n  \"" p "\")")
(group "(defgroup " p " nil\n  \"" p "\"" n> ":group '" p n> ":prefix \"" p "-\")")
(macro "(defmacro " p " (" p ")\n  \"" p "\"" n> r> ")")
(fun "(defun " p " (" p ")\n  \"" p "\"" n> r> ")")
(let "(let (" p ")" n> r> ")")
(star "(let* (" p ")" n> r> ")")
(rec "(letrec (" p ")" n> r> ")")
(command "(defun " p " (" p ")\n  \"" p "\"" n> "(interactive)" n> r> ")")

eshell-mode

(for "for " (p "i") " in " p " { " p " }")
(while "while { " p " } { " p " }")
(until "until { " p " } { " p " }")
(if "if { " p " } { " p " }")
(if-else "if { " p " } { " p " } { " p " }")
(unless "unless { " p " } { " p " }")
(unless-else "unless { " p " } { " p " } { " p " }")

text-mode

(cut "--8<---------------cut here---------------start------------->8---" n r n
     "--8<---------------cut here---------------end--------------->8---" n)
(asciibox "+-" (make-string (length str) ?-) "-+" n
          "| " (s str)                       " |" n
          "+-" (make-string (length str) ?-) "-+" n)
(rot13 (p "plain text" text) n "----" n (rot13 text))
(calc (p "taylor(sin(x),x=0,3)" formula) n "----" n (format "%s" (calc-eval formula)))

rst-mode

(title (make-string (length title) ?=) n (p "Title: " title) n (make-string (length title) ?=) n)

java-mode

(class "public class " (p (file-name-base (or (buffer-file-name) (buffer-name)))) " {" n> r> n "}")

c-mode :condition (re-search-backward "^\\S-*$" (line-beginning-position) 'noerror)

(inc "#include <" (p (concat (file-name-base (or (buffer-file-name) (buffer-name))) ".h")) ">")
(incc "#include \"" (p (concat (file-name-base (or (buffer-file-name) (buffer-name))) ".h")) "\"")

org-mode

(title "#+title: " p n "#+author: Alex Lu" n "#+language: en" n n)
(quote "#+begin_quote" n> r> n> "#+end_quote")
(example "#+begin_example" n> r> n> "#+end_example")
(center "#+begin_center" n> r> n> "#+end_center")
(comment "#+begin_comment" n> r> n> "#+end_comment")
(verse "#+begin_verse" n> r> n> "#+end_verse")
(src "#+begin_src " p n> r> n> "#+end_src")
(elisp "#+begin_src emacs-lisp" n> r> n "#+end_src")
(cc "#+begin_src C" n> r> n "#+end_src")
(cpp "#+begin_src cpp" n> r> n "#+end_src")
(csharp "#+begin_src csharp" n> r> n "#+end_src")
(python "#+begin_src python" n> r> n "#+end_src")
(js "#+begin_src js" n> r> n "#+end_src")
(bash "#+begin_src bash" n> r> n "#+end_src")
(latex "#+begin_src latex" n> r> n "#+end_src")

;; Local Variables:
;; mode: lisp-data
;; outline-regexp: "[a-z]"
;; End:

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.
(straight-use-package '(electric :type built-in))

(add-hook 'org-mode-hook '+electric-inhibit-<)
(add-hook 'minibuffer-setup-hook
          (lambda () (unless (eq this-command 'eval-expression)
                       (electric-pair-mode 0))))
(add-hook 'minibuffer-exit-hook (lambda () (electric-pair-mode 1)))

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

Autoload

;;;###autoload
(defadvice! electric-pair-post-self-insert-a (fn &rest args)
  "Doc."
  :around #'electric-pair-post-self-insert-function
  (let ((mark-active nil)) (apply fn args)))

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

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
(straight-use-package 'rainbow-delimiters)

(add-hook 'prog-mode-hook 'rainbow-delimiters-mode)

(setq show-paren-style 'parenthesis)
(setq show-paren-when-point-in-periphery nil)
(setq show-paren-when-point-inside-paren nil)
(show-paren-mode)

Prettify symbols (prog-mode.el)

(add-hook 'prog-mode-hook 'prettify-symbols-mode)

(setq-default prettify-symbols-alist
              '(("<-" . ?←)
                ("->" . ?→)
                ("->>" . ?↠)
                ("=>" . ?⇒)
                ("/=" . ?≠)
                ("!=" . ?≠)
                ("==" . ?≡)
                ("<=" . ?≤)
                (">=" . ?≥)
                ("=<<" . (?= (Br . Bl) ?≪))
                (">>=" . (?≫ (Br . Bl) ?=))
                ("<=<" . ?↢)
                (">=>" . ?↣)))
(setq prettify-symbols-unprettify-at-point 'right-edge)

User interface

Makes windows without focus less prominent (auto-dim-other-buffers.el)

(straight-use-package 'auto-dim-other-buffers)

(auto-dim-other-buffers-mode)

Workspaces (tabspaces.el)

Use the inbuilt tab-bar.el and the package tabspaces.el to create workspaces.

  • General tab bar customization
  • Enable tabspaces-mode
  • Create some default workspaces
  • Filter buffers for consult tabspaces
  • Rename the first tab (generated by tab-bar itself) to Default
(straight-use-package 'tabspaces)

(require 'tabspaces)
(tabspaces-mode 1)
(add-hook 'server-after-make-frame-hook #'+tabspaces-setup)

(setq tab-bar-separator " "
      tab-bar-close-button-show nil
      tab-bar-close-last-tab-choice 'tab-bar-mode-disable
      tab-bar-tab-name-format-function #'+tabspaces-tab-name-format-comfortable
      tab-bar-format '(+tabspaces-format-menu-bar
                       tab-bar-format-tabs
                       tab-bar-separator
                       tab-bar-format-align-right
                       tab-bar-format-global)
      tabspaces-keymap-prefix nil
      tabspaces-default-tab "Default"
      tabspaces-include-buffers '("*scratch*" "*Messages*")
      tabspaces-remove-to-default t)

(once '(:packages tabspaces)
  (bind-keys :map tab-prefix-map
             ("C" . tabspaces-clear-buffers)
             ("R" . tabspaces-remove-selected-buffer)
             ("k" . tabspaces-kill-buffers-close-workspace)
             ("s" . tabspaces-switch-or-create-workspace)))

(once '(:packages consult)
  (consult-customize consult--source-buffer :hidden t :default nil)
  (defvar consult--source-workspace
    (list :name "Workspace Buffers"
          :narrow ?w
          :history 'buffer-name-history
          :category 'buffer
          :state #'consult--buffer-state
          :default t
          :items (lambda () (consult--buffer-query
                             :predicate #'tabspaces--local-buffer-p
                             :sort 'visibility
                             :as #'buffer-name)))
    "Set Workspace buffer list for consult buffer.")
  (add-to-list 'consult-buffer-sources 'consult--source-workspace))

Autoload

(defcustom +tabspaces-startup-workspaces nil
  "Workspaces being created at startup."
  :group 'grandview :type 'alist)

;;;###autoload
(defun +tabspaces-format-menu-bar ()
  "Produce the Menu button for the tab bar that shows the menu bar."
  `((menu-bar menu-item (propertize " 𝝺" 'face 'tab-bar)
              tab-bar-menu-bar :help "Menu Bar")))

;;;###autoload
(defun +tabspaces-tab-name-format-comfortable (tab i)
  (propertize (concat " " (tab-bar-tab-name-format-default tab i) " ")
              'face (funcall tab-bar-tab-face-function tab)))

(defun +tabspaces-create-workspace (name &optional path)
  "Setup the workspace with name NAME and startup path PATH."
  (if (member name (tabspaces--list-tabspaces))
      (progn (tab-bar-switch-to-tab name)
             (when (and (not (get-buffer name)) path) (dirvish path)))
    (tab-bar-new-tab)
    (tab-bar-rename-tab name)
    (when path (dirvish path))))

;;;###autoload
(defun +tabspaces-setup ()
  "Setup tabspaces when a frame is created as well."
  (cl-loop for (name . path) in +tabspaces-startup-workspaces
           do (+tabspaces-create-workspace name path))
  (tab-bar-select-tab 1)
  (tab-bar-rename-tab "Default"))

Transient commands (transient.el)

transient.el implements support for powerful keyboard-driven menus. Such menus can be used as simple visual command dispatchers. More complex menus take advantage of infix arguments, which are somewhat similar to prefix arguments, but are more flexible and discoverable. This package is inbuilt with Emacs 28+.

(once '(:packages transient)
  (setq transient-enable-popup-navigation nil)
  (setq transient-default-level 7)
  (setq transient-show-popup -0.2)
  (transient-bind-q-to-quit)
  (setq transient-display-buffer-action '(display-buffer-below-selected))
  (bind-keys :map transient-map
             ("<escape>" . transient-quit-all)
             :map transient-sticky-map
             ("ESC" . transient-quit-all)))

Mode line (mode-line.el)

The following infos in modeline are provided:

Left
  • Meow current state (INSERT/NORMAL…)
  • Buffer info (icon, name, modified state)
  • Macro recording state
Right
  • Current line / column
  • Input method

Besides, it’s easy to define your own mode line segments with +mode-line-define-segment, and don’t forget to put your newly defined segments in +mode-line-format.

(+mode-line-mode)

Autoload

(defun +mode-line--fmt-setter (k fmt)
  "Setter for `+mode-line-format'."
  (cl-labels ((expand (l)
                (cl-loop for s in l collect
                         `(:eval (+mode-line--suffix
                                  ',(intern (format "+mode-line-%s-seg" s)))))))
    (let ((fmt-left (or (expand (plist-get fmt :left)) mode-line-format))
          (fmt-right (expand (plist-get fmt :right))))
      (set k `((:eval
                (let* ((str-right (format-mode-line ',fmt-right))
                       (spec `((space :align-to
                                      (- (+ right right-fringe right-margin)
                                         ,(string-width str-right))))))
                  (concat (format-mode-line ',fmt-left)
                          (propertize " " 'display spec)
                          str-right))))))))

(defcustom +mode-line-format
  '(:left
    (bar editing-state buffer-info macro-rec)
    :right
    (position input-method))
  "Mode line SEGMENTs aligned to left/right respectively.
The SEGMENTs are defined by `+mode-line-define'."
  :set #'+mode-line--fmt-setter)

(defcustom +mode-line-height 30
  "Doc.")

(defvar +mode-line-selected-window nil)

(defun +mode-line-disable-locally ()
  "Do not show mode line in this buffer."
  (setq mode-line-format nil))

(defun +mode-line--window-active ()
  "Return t if mode line is in active window."
  (unless (and (bound-and-true-p mini-frame-frame)
               (and (frame-live-p mini-frame-frame)
                    (frame-visible-p mini-frame-frame)))
    (and +mode-line-selected-window
         (eq (+mode-line--get-current-window) +mode-line-selected-window))))

(defun +mode-line--get-current-window (&optional frame)
  "Get the current window but should exclude the child windows."
  (if (and (fboundp 'frame-parent) (frame-parent frame))
      (frame-selected-window (frame-parent frame))
    (frame-selected-window frame)))

(defun +mode-line-record-selected-window-h (&rest _)
  "Update `+mode-line-selected-window' on redisplay."
  (let ((win (+mode-line--get-current-window)))
    (setq +mode-line-selected-window
          (if (minibuffer-window-active-p win)
              (minibuffer-selected-window)
            win))))

(add-hook 'window-selection-change-functions #'+mode-line-record-selected-window-h)

(defun +mode-line--suffix (ml-func)
  "If ML-FUNC return a non-empty string, append a space to it."
  (when-let (str (funcall ml-func)) (concat str " ")))

(cl-defmacro +mode-line-define (name &optional docstr &rest body)
  "Define a mode line segment NAME with BODY and DOCSTR."
  (declare (indent defun) (doc-string 2))
  (let ((ml (intern (format "+mode-line-%s-seg" name))))
    `(defun ,ml () ,docstr ,@body)))

(+mode-line-define bar
  (when (and (display-graphic-p) (image-type-available-p 'pbm))
    (propertize
     " " 'display
     (ignore-errors
       (create-image
        (concat (format "P1\n%i %i\n" 2 +mode-line-height)
                (make-string (* 2 +mode-line-height) ?1) "\n")
        'pbm t :foreground "None" :ascent 'center)))))

(+mode-line-define position
  (concat (propertize "l " 'face 'font-lock-keyword-face)
          (propertize "%l " 'face 'font-lock-doc-face)
          (propertize "c " 'face 'font-lock-keyword-face)
          (propertize "%c" 'face 'font-lock-doc-face)))

(+mode-line-define editing-state
  (when (bound-and-true-p meow-mode) (meow-indicator)))

(+mode-line-define buffer-info
  (concat
   (and buffer-file-name (nerd-icons-icon-for-file
                          buffer-file-name :height 0.75 :v-adjust 0.02))
   " "
   (propertize "%b"
               'face (cond ((and buffer-file-name (buffer-modified-p))
                            'marginalia-modified)
                           ((+mode-line--window-active) 'bold)
                           (t 'mode-line-inactive))
               'mouse-face 'mode-line-highlight
               'help-echo
               "Buffer name\nmouse-1: Previous buffer\nmouse-3: Next buffer"
               'local-map mode-line-buffer-identification-keymap)))

(+mode-line-define macro-rec
  (when (or defining-kbd-macro executing-kbd-macro)
    (propertize "KM" 'face 'warning)))

(+mode-line-define input-method
  (when (bound-and-true-p rime-mode) (rime-lighter)))

(defvar +mode-line--default-mode-line mode-line-format
  "Store the default mode-line format")

;;;###autoload
(define-minor-mode +mode-line-mode
  "Toggle `+mode-line-mode' on or off."
  :global t
  (if +mode-line-mode
      (progn
        (setq-default mode-line-format +mode-line-format)
        (add-hook 'compilation-mode-hook #'+mode-line-disable-locally))
    (setq-default mode-line-format +mode-line--default-mode-line)
    (remove-hook 'compilation-mode-hook #'+mode-line-disable-locally)))

Frame margin (fringe.el)

  • Redefine word wrap arrow at fringe
  • Create a 20 pixel margin for emacs frame
(define-fringe-bitmap 'right-curly-arrow  [])
(define-fringe-bitmap 'left-curly-arrow  [])
(add-to-list 'default-frame-alist '(internal-border-width . 10))
(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))

Pulse highlight on demand or after select functions (pulsar.el)

Another great package from Prot!

(straight-use-package 'pulsar)

(pulsar-global-mode)
(once '(:packages pulsar)
  (appendq! pulsar-pulse-functions
            (list 'windmove-left 'windmove-right
                  'windmove-up 'windmove-down
                  'previous-window-any-frame
                  'next-window-any-frame '+meow-save)))

VSCode style icons (vscode-icon.el)

Similar to all-the-icons.el, vscode-icon is a icon library which provides VSCode style icons with image format.

(straight-use-package 'vscode-icon)

(once '(:packages vscode-icon)
  (push '("jpg" . "image") vscode-icon-file-alist)
  (push '("7z" . "zip") vscode-icon-file-alist)
  (push '("mkv" . "video") vscode-icon-file-alist)
  (push '("epub" . "storybook") vscode-icon-file-alist))

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.

(straight-use-package 'anzu)

(once '(:hooks pre-command-hook)
  (global-anzu-mode)
  (bind-key [remap query-replace] 'anzu-query-replace)
  (bind-key [remap query-replace-regexp] 'anzu-query-replace-regexp))

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.

(straight-use-package 'isearch-mb)

(isearch-mb-mode)

(once '(:packages isearch-mb)
  (add-to-list 'isearch-mb--with-buffer #'consult-isearch-history)
  (add-to-list 'isearch-mb--after-exit #'anzu-isearch-query-replace)
  (bind-keys :map isearch-mb-minibuffer-map
             ([remap previous-matching-history-element] . consult-isearch-history)))

Distraction-free writing (writeroom-mode.el)

(straight-use-package 'writeroom-mode)

(once '(:before pre-command-hook)
  (setq 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)
        writeroom-major-modes-exceptions '(process-menu-mode proced-mode)
        writeroom-maximize-window nil
        writeroom-mode-line t)
  (global-writeroom-mode))

(add-hook 'org-mode-hook #'+visual-fill-center-text)

Autoload

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

Key bindings hint (which-key.el)

(straight-use-package 'which-key)

(once '(:hooks pre-command-hook) (which-key-mode))

Hunk indicator (git-gutter.el)

(straight-use-package 'git-gutter)

(setq! git-gutter:modified-sign ""
       git-gutter:added-sign ""
       git-gutter:deleted-sign "")

Utils

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.

(straight-use-package 'embark)
(straight-use-package 'embark-consult)

(once '(:hooks pre-command-hook)
  (setq embark-quit-after-action t)
  (bind-keys :map global-map
             ("C-." . embark-act)
             :map minibuffer-local-map
             ("C-." . embark-act)
             ("C-," . embark-become)))

Vterm (vterm.el)

(straight-use-package 'vterm)

(bind-key "M-`" '+vterm-toggle)

(once '(:packages vterm)
  (+vterm-mux-mode)
  (setq vterm-max-scrollback 5000)
  (add-hook 'vterm-copy-mode-hook
            (lambda () (if vterm-copy-mode (meow-normal-mode) (meow-insert-mode))))
  (bind-keys :map vterm-mode-map
             ("M-`" . +vterm-toggle)
             ("M--" . +vterm-new)
             ("M-'" . vterm-send-M-apostrophe)
             ("M-\"" . vterm-send-M-quote)
             ("M-/" . vterm-send-M-/)
             ("M-." . +vterm-next)
             ("M-," . +vterm-prev)
             ("M-RET" . vterm-send-M-return)
             ("s-n" . vterm-next-prompt)
             ("s-p" . vterm-previous-prompt)
             ("S-SPC" . nil)
             ("S-<escape>" . vterm-copy-mode)
             ("C-<return>" . vterm-send-F5)
             :map vterm-copy-mode-map
             ("S-<escape>" . vterm-copy-mode-done)))

Autoload

(require 'vterm)

(defcustom +vterm-position-alist
  '((always . ((window-height . 0.2) (side . bottom))))
  "doc")

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

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

;;;###autoload
(defadvice! +vterm-escape-a (fn &rest args)
  :around #'meow-insert-exit
  (if (derived-mode-p 'vterm-mode)
      (vterm-send-escape)
    (apply fn args)))

;;;###autoload
(defadvice! +vterm-kill-whole-line-a (fn &rest args)
  :around #'meow-kill-whole-line
  (if (derived-mode-p 'vterm-mode)
      (vterm-send-key "<f5>" nil nil nil)
    (apply fn args)))

;;;###autoload
(defadvice! +vterm-backword-char-a (fn &rest args)
  :around #'meow-right
  (if (derived-mode-p 'vterm-mode)
      (vterm-send-key "<f6>" nil nil nil)
    (apply fn args)))

(defun +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-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 +vterm--get-win-params ()
  "Parse `+vterm-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 +vterm-position-alist
                thereis (and (funcall pred) pos)))))

;;;###autoload
(defun +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 (+vterm--get-win-params))
           (buf (nth +vterm-index +vterm-buffers))
           (pr-root (or (ignore-errors
                          (project-root (project-current)))
                        default-directory))
           (default-directory (if project-root pr-root default-directory))
           (index (if buf (+vterm--get-index buf) 0)))
      (add-to-list '+vterm-buffers (vterm index))
      (+vterm--insert))))

(defun +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 +vterm--insert ()
  (cond ((featurep 'meow) (meow-insert)) ((featurep 'evil) (evil-insert-state))))

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

;;;###autoload
(defun +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) +vterm-buffers))
         (new-index (+ curr-index (or arg -1)))
         (buf (nth new-index +vterm-buffers)))
    (when buf
      (switch-to-buffer buf)
      (setq +vterm-index new-index))))

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

(defun +vterm--kill-buffer ()
  "Remove killed buffer from `+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 +vterm-buffers))))

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

Git porcelain (magit.el)

(straight-use-package 'magit)

(bind-key "C-M-g" 'magit-status-here)
(once '(:packages magit)
  (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-keys :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)
(straight-use-package 'helpful)

(bind-keys :map global-map
           ("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))

(once '(:packages helpful)
  (bind-keys :map helpful-mode-map
             ("M-n" . (lambda () (interactive) (forward-button 1 nil 1 t)))
             ("M-p" . (lambda () (interactive) (backward-button 1 nil 1 t)))))

Emacs Manual (info.el)

(straight-use-package '(info :type built-in))

(once '(:packages info)
  (bind-keys :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)

(straight-use-package '(man :type built-in))

(setq Man-notify-method 'newframe)
(once '(:packages man)
  (bind-keys :map Man-mode-map ("q" . kill-this-buffer)))

Ripgrep (rg.el)

A search package based on the ripgrep command line tool. It allows you to interactively create searches, doing automatic searches based on the editing context, refining and modifying search results and much more. It is also highly configurable to be able to fit different users’ needs.

Some additions I’ve added to rg transient:

  • C key to toggle --context 3 flag show 3 lines of text before and after the keyword
  • A key to toggle -A 5 flag like --context, but only show text after the keyword
  • press SPC p p to search in current project, no ask for confirmation
(straight-use-package 'rg)

(once '(:hooks find-file-hook)
  (+rg-setup) ; for lazy loading
  (bind-key "r" 'rg-project-all-files-no-ask grandview-prog-map))

Autoload

The purpose of wrapping rg-define-toggle/search is to avoid executing them immediately, because these macros introduce dependencies that we don’t need at startup.

;;;###autoload
(defun +rg-setup ()
  "Define toggles in `rg-mode'."
  (rg-define-toggle "--context 3" (kbd "C"))
  (rg-define-toggle "-A 5" (kbd "A"))
  (rg-define-search rg-project-all-files-no-ask
    :dir project :files "*"))

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

(straight-use-package 'wgrep)

(once '(:packages wgrep)
  (setq wgrep-auto-save-buffer t)
  (setq wgrep-change-readonly-file t)
  (bind-keys :map wgrep-mode-map
             ("M-n" . next-error-no-select)
             ("M-p" . previous-error-no-select)))

Pdf reader (pdf-tools.el)

(straight-use-package 'pdf-tools)

(once '(:hooks find-file-hook)
  (pdf-tools-install))

(once '(:packages pdf-tools)
  (setq-default pdf-view-display-size 'fit-page)
  (setq pdf-annot-activate-created-annotations t) ; automatically annotate highlights
  (add-hook 'pdf-view-mode-hook (lambda () (cua-mode 0))) ; turn off cua so copy works
  (setq pdf-view-resize-factor 1.1) ; more fine-grained zooming
  (bind-keys :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)

(straight-use-package 'nov)
(push '("\\.epub\\'" . nov-mode) auto-mode-alist)
(setq nov-shr-rendering-functions '((img . nov-render-img)
                                    (title . nov-render-title)
                                    (b . shr-tag-b)))

Murl (murl.el)

(bind-keys
 :map grandview-apps-map
 ("l" . murl-open)) ; "l" for live streaming

Autoload

(require 'json)

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

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

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

;;;###autoload
(defun 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 10801 json '" clip "'")))
                  (valid (string-prefix-p "{" json))
                  (obj (json-read-from-string json))
                  (playlist (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) murl-list-file)))))
  (let* ((cands-raw (mapcar (lambda (i) (cdr (assq 'title i))) (murl--playlist)))
         (annotation (lambda (s) (marginalia--documentation
                                  (murl--get-attr s 'url))))
         (cands (+minibuffer-append-metadata annotation cands-raw))
         (title (completing-read "murls: " cands))
         (sub (murl--get-attr title 'sub)))
    (call-process "murl" nil 0 nil "-r" "-f" "-P" "10801" "-s" sub
                  (murl--get-attr title 'url))))

Dictionary (fanyi.el)

(straight-use-package 'fanyi)

(bind-key "t" 'fanyi-dwim grandview-apps-map)
(once '(:packages fanyi)
  (setq! fanyi-providers '(fanyi-etymon-provider fanyi-longman-provider)))

Forges (forge.el)

(straight-use-package 'forge)

Programming Languages

Rust

(straight-use-package 'rust-mode)

Csharp

Sharper is a Transient-based menu for the dotnet CLI. It aims to cover the most common scenarios. To invoke it, type M-x sharper-main-transient in a .cs file.

(straight-use-package 'sharper)
(once '(:hooks csharp-mode-hook)
  (require 'sharper))

;; A temporary fix for dotnet-sdk installation path on Apple Silicon machines
(when (and (eq system-type 'darwin)
           (string-match "aarch64-.*" system-configuration))
  (setenv "DOTNET_ROOT" "/usr/local/share/dotnet"))

(add-to-list 'auto-mode-alist '("\\.[a]?xaml\\'" . nxml-mode))

Python

(straight-use-package '(python :type built-in))

(setq python-indent-offset 4)
(setq python-indent-guess-indent-offset-verbose nil)

Lua

(straight-use-package 'lua-mode)

(setq lua-indent-level 2)

Yaml

(straight-use-package 'yaml-mode)

JavaScript

(straight-use-package '(js :type built-in))
(straight-use-package 'web-mode)

(add-hook 'web-mode-hook
          (lambda ()
            (emmet-mode)
            (setq web-mode-markup-indent-offset 2)
            (setq web-mode-code-indent-offset 2)
            (setq web-mode-script-padding 0)))
(add-to-list 'auto-mode-alist '("\\.vue\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.jsx?$" . web-mode))

(setq js-indent-level 2)
(setq web-mode-content-types-alist '(("jsx" . "\\.js[x]?\\'")))

Bash|Zsh

(straight-use-package '(sh-script :type built-in))

(setq sh-basic-offset 2)

HTML

(straight-use-package 'emmet-mode)

DevTools

Compiler (compile.el)

Run compiler as inferior of Emacs, parse error messages.

(bind-keys :map grandview-prog-map
           ("p" . compile)
           ("P" . recompile))

LSP (lsp.el)

(straight-use-package 'lsp-mode)

(dolist (lang '(sh lua python web typescript rust csharp))
  (define-prefix-command '+lsp-map)
  (bind-keys :map mode-specific-map ("l" . +lsp-map))
  (add-hook (intern (format "%s-mode-hook" lang)) 'lsp-deferred))

(once '(:hooks lsp-mode-hook)
  (defalias '+lsp-map lsp-command-map))

(once '(:packages lsp-mode)
  (setq lsp-completion-provider :none) ;; we use Corfu!
  (setq lsp-enable-imenu nil)
  (setq lsp-eldoc-hook nil)
  (setq lsp-enable-snippet 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 (locate-user-emacs-file "lsp")))
  (setq lsp-headerline-breadcrumb-enable t)
  (setq lsp-lua-completion-keyword-snippet "Disable")
  (add-hook 'lsp-mode-hook #'+lsp-setup-orderless-h)
  (add-to-list 'lsp-file-watch-ignored-directories "[/\\\\]\\envs\\'"))

Extensions

(straight-use-package 'lsp-tailwindcss)
(straight-use-package 'lsp-pyright)
(straight-use-package 'lsp-ui)

(add-hook 'python-mode-hook '+lsp-pyright-import-venv)
(add-hook 'lsp-mode-hook 'lsp-ui-mode)
(setq lsp-tailwindcss-add-on-mode t)
(when (executable-find "python3")
  (setq lsp-pyright-python-executable-cmd "python3"))
(setq lsp-ui-doc-position 'bottom)
(setq lsp-ui-doc-show-with-cursor t)
(setq lsp-ui-doc-show-with-mouse t)

Autoload

;;;###autoload
(defadvice! +lsp-volar--vue-project-p (workspace-root)
  "Make 'lsp-volar' support nuxt project."
  :override #'lsp-volar--vue-project-p
  (when-let* ((package-json (f-join workspace-root "package.json"))
              (exist (f-file-p package-json))
              (config (json-read-file package-json))
              (dependencies (alist-get 'dependencies config)))
    (or (alist-get 'vue dependencies) (alist-get 'nuxt dependencies))))

;;;###autoload
(defun +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))))

;;;###autoload
(defun +lsp-setup-orderless-h ()
  "Configure orderless for `lsp-mode'."
  (setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
        '(orderless)))

Virturl environment (conda.el)

(straight-use-package 'conda)

(setq conda-anaconda-home "/opt/miniconda/")
(setq conda-env-home-directory "/opt/miniconda/")
(setq conda-message-on-environment-switch t)
(bind-key "C" '+conda-activate-for-buffer grandview-prog-map)
(add-hook 'conda-postactivate-hook
          (lambda () (require 'jupyter) (jupyter-available-kernelspecs t)))

Autoload

;;;###autoload
(defun +conda-activate-for-buffer ()
  "Enhanced `conda-env-activate-for-buffer'.
It support local `conda-env-subdirectory' such as `./envs'."
  (interactive)
  (let* ((filename (buffer-file-name))
         (yml-file (and filename (conda--find-env-yml (f-dirname filename))))
         (yml-path (and yml-file (f-dirname yml-file)))
         (prefix-env (concat yml-path "/" conda-env-subdirectory "/")))
    (cond ((or (null filename) (null yml-file))
           (conda-env-activate "base"))
          ((bound-and-true-p conda-project-env-path)
           (conda-env-activate conda-project-env-path))
          ((file-exists-p prefix-env)
           (unless (string= prefix-env (or conda-env-current-name ""))
             (conda-env-activate-path prefix-env)))
          (t
           (let ((env-name (conda-env-name-to-dir
                            (conda--get-name-from-env-yml yml-file))))
             (when (and env-name (not (equal env-name conda-env-current-name)))
               (conda-env-activate env-name)))))))

Jupyter (jupyter.el)

(straight-use-package 'jupyter)

(once '(:packages jupyter)
  ;; See https://github.com/nnicandro/emacs-jupyter/issues/380.
  (defun jupyter-ansi-color-apply-on-region (begin end)
    (ansi-color-apply-on-region begin end t))
  (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)

(straight-use-package 'rainbow-mode)
(add-hook 'prog-mode-hook '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.

(straight-use-package 'reformatter)

(bind-key "f" '+format-buffer grandview-apps-map)
(once '(:packages reformatter)
  (reformatter-define lua-format
    :program "stylua"
    :args '("--indent-width" "2" "-")
    :lighter " styLua")
  (reformatter-define python-format
    :program "black"
    :args '("-")
    :lighter " blackFMT"))

Autoload

;;;###autoload
(defun +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)))))

Syntax checker (flymake.el)

(straight-use-package '(flymake :type built-in))

(add-hook 'prog-mode-hook '+flymake-enable)
(add-hook 'text-mode-hook 'flymake-mode)
(bind-keys :map grandview-prog-map
           ("e" . flymake-goto-next-error)
           ("E" . flymake-goto-prev-error)
           ("S" . flymake-start))

Autoload

;;;###autoload
(defun +flymake-enable ()
  "Inhibit `flymake-mode' under certain circumstances."
  (unless (or (equal default-directory
                     (or (file-name-directory grandview-org-file)
                         user-emacs-directory))
              (eq (current-buffer) (get-buffer "*scratch*")))
    (flymake-mode +1)))

REST client (restclient.el)

(straight-use-package 'restclient)

Scratch buffers (scratch.el)

+scratch command produces 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!

(bind-keys :map grandview-apps-map
           ("s" . +scratch))

Autoload

(defun +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 +scratch (query-for-mode)
  "Create or switch to an org-mode scratch buffer.
A jupyter session is attached to the buffer.
If called with prefix arg, prompt for a major mode for the buffer."
  (interactive "P")
  (let ((buf (get-buffer "*Grandview-scratch*")))
    (unless buf
      (let* ((new-buf (generate-new-buffer "*Grandview-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: " (+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)))

About this file

COPYING

Copyright (c) 2020-2022 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/.

File local variables

Here are the local variables in this file.

  • eldoc-documentation-functions: make eldoc works the same way as in a normal elisp file
  • org-edit-src-content-indentation: do not add spaces at line beginning in src blocks
  • +org-id-auto: generate :CUSTOM_ID for every heading on saving.
(defun grandview--org-eldoc-funcall (_callback &rest _ignored)
  "Fix `elisp-eldoc-funcall' in `org-mode'."
  (when (eq (org-element-type (org-element-at-point)) 'src-block)
    (let* ((sym-info (elisp--fnsym-in-current-sexp))
           (fn-sym (car sym-info)))
      (when (fboundp fn-sym)
        (message "%s: %s"
                 (propertize (format "%s" fn-sym) 'face 'font-lock-function-name-face)
                 (apply #'elisp-get-fnsym-args-string sym-info))))))

(defun grandview-setup-literate-file ()
  "Setup for `grandview-org-file'."
  (setq-local eldoc-documentation-functions
              '(elisp-eldoc-var-docstring grandview--org-eldoc-funcall))
  (setq-local org-edit-src-content-indentation 0)
  (setq-local +org-id-auto t))

About

原非大观。


Languages

Language:Emacs Lisp 100.0%