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.el
is the file to place your:
- 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 forgc-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 infont-family-list
.grandview-fixed-font
: Thefixed-pitch
font, which should included infont-family-list
.grandview-variable-font
: Thevariable-pitch
font, which should included infont-family-list
.grandview-CJK-font
: The font used for CJK characters.
- other arbitrary codes to load before loading the main config
- packages expecting further evaluation or testing
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.
Make sure to backup your own emacs config before installation.
cp -r ~/.emacs.d ~/.emacs.d.bak
Here are the dependencies of this project.
Package | Description | Required |
---|---|---|
fd | A modern find | |
rg | A modern grep | |
git | Version control | |
noto-fonts-emoji | Font for emojis | optional |
words | English words completion | optional |
font-victor-mono | Default fixed pitch font | optional |
ttf-sarasa-gothic | Default variable pitch font | optional |
xdotool | Automation tool for X11 | optional |
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.
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.
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
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
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
(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)))))
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
(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))
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))
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 . ("\"" . "\""))
(?\' . ("\'" . "\'"))
(?` . ("`" . "`"))))
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)
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)
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))
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
(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)))
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)))
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))
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
(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))))
- 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))
(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)))
(straight-use-package
'(nerd-icons :repo "rainstormstudio/nerd-icons.el"
:host github
:files (:defaults "data")))
(straight-use-package
'(nerd-fonts :host github :repo "twlz0ne/nerd-fonts.el"))
;; (once '(:hooks pre-command-hook)
(require 'nerd-fonts)
;; )
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.
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, hitC-[
(abort-recursive-edit
), though the regularC-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
(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)))))
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))
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))
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))
(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))))
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))
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))
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)))
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)))
This section contains all core keybindings of Grandview.
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)
(once '(:packages meow)
(bind-keys :map meow-insert-state-keymap
("M-<backspace>" . meow-kill-whole-line)
("<C-i>" . meow-right)
("C-o" . meow-left)))
(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)))
(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))
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)))
- 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))
(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 ": "))))
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))
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))
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))
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
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 "\\|^\\..*$")))
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)))
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)))
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%[email protected]: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)))
(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))
(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)))
(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))
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)))
(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)))))))
(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)
(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)))))
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"))))
(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))
(straight-use-package 'org-appear)
(add-hook 'org-mode-hook 'org-appear-mode)
(setq org-appear-autolinks t)
(setq org-hide-emphasis-markers t)
(straight-use-package 'org-modern)
(add-hook 'org-mode-hook 'org-modern-mode)
(add-hook 'org-agenda-finalize-hook 'org-modern-agenda)
(straight-use-package '(org-habit :type built-in))
(once '(:packages org)
(appendq! org-modules '(org-habit))
(setq org-habit-graph-column 60))
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)))
(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))))
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))
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)
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)
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
(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))
(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
(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)))
(straight-use-package 'tempel)
(add-hook 'prog-mode-hook '+tempel-setup-capf)
(add-hook 'text-mode-hook '+tempel-setup-capf)
;;;###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)))
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 withindent-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. ThePROMPT
is displayed directly in the buffer as default value. IfNOINSERT
is non-nil, no field is inserted. Then the minibuffer is used for prompting and the value is bound toNAME
.(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>)
Likep
described above, butFORM
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:
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
(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)))))
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
orexpression
. 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)
(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)
(straight-use-package 'auto-dim-other-buffers)
(auto-dim-other-buffers-mode)
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))
(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.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)))
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)
(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)))
- 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))
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))
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)))
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))
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))
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)))
(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
(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))
(straight-use-package 'which-key)
(once '(:hooks pre-command-hook) (which-key-mode))
(straight-use-package 'git-gutter)
(setq! git-gutter:modified-sign "⏽"
git-gutter:added-sign "⏽"
git-gutter:deleted-sign "⏽")
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)))
(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)))
(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)))
(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.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)))))
(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)))
(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)))
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 keywordA
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))
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 "*"))
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)))
(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)))
(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)))
(bind-keys
:map grandview-apps-map
("l" . murl-open)) ; "l" for live streaming
(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))))
(straight-use-package 'fanyi)
(bind-key "t" 'fanyi-dwim grandview-apps-map)
(once '(:packages fanyi)
(setq! fanyi-providers '(fanyi-etymon-provider fanyi-longman-provider)))
(straight-use-package 'forge)
(straight-use-package 'rust-mode)
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))
(straight-use-package '(python :type built-in))
(setq python-indent-offset 4)
(setq python-indent-guess-indent-offset-verbose nil)
(straight-use-package 'lua-mode)
(setq lua-indent-level 2)
(straight-use-package 'yaml-mode)
(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]?\\'")))
(straight-use-package '(sh-script :type built-in))
(setq sh-basic-offset 2)
(straight-use-package 'emmet-mode)
Run compiler as inferior of Emacs, parse error messages.
(bind-keys :map grandview-prog-map
("p" . compile)
("P" . recompile))
(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\\'"))
(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
(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)))
(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
(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)))))))
(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"))))
(straight-use-package 'rainbow-mode)
(add-hook 'prog-mode-hook 'rainbow-mode)
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
(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)))))
(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
(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)))
(straight-use-package 'restclient)
+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))
(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)))
Copyright (c) 2020-2022 Alex Lu <[email protected]>
This file is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 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/.
Here are the local variables in this file.
eldoc-documentation-functions
: make eldoc works the same way as in a normal elisp fileorg-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))