This repository has been archived on 2026-10-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
SimplyEmacs/lisp/init-cheatsheet.el

249 lines
9.0 KiB
EmacsLisp
Executable File

;;; init-cheatsheet.el --- In-Emacs keybinding reference -*- lexical-binding: t; -*-
;;; Commentary:
;; A static reference buffer listing this config's custom keybindings,
;; opened as a non-blocking side window — same window-management model as
;; the build panel in `init-build.el' — so it can be toggled in and out of
;; view without interrupting whatever else is on screen.
;;
;; Content is real Markdown, viewed read-only via `markdown-view-mode'
;; (from the `markdown-mode' package already pulled in by `init-dev.el')
;; so headers/tables/code spans get proper fontification for free.
;;; Code:
(require 'markdown-mode) ; markdown-view-mode; make the dep explicit
(add-to-list 'display-buffer-alist
'("\\*Cheatsheet\\*"
(display-buffer-reuse-window display-buffer-in-side-window)
(side . left)
(window-width . 0.4)))
(defconst my/cheatsheet-text
"# SimplyEmacs Cheat Sheet
Custom keybindings layered on top of vanilla Emacs. Toggle this buffer
any time with `C-c /`.
Many keys use a project/fallback pattern (`my/project-or`): one action
inside a known project, a `consult`-based fallback everywhere else.
## Navigation & Search *(Evil normal/motion state)*
| Key | In a project | Fallback |
|------------|----------------------------|-------------------|
| `C-e` | `project-dired` | `dired-jump` |
| `C-m` | `project-switch-to-buffer` | `consult-buffer` |
| `C-f` | `consult-ripgrep` | `consult-ripgrep` |
| `C-p` | `project-find-file` | `consult-fd` |
| `C-s` | `consult-line` | |
| `<return>` | `evil-ret` | |
`C-f` runs `consult-ripgrep` everywhere — it already scopes to the
project root when there is one, so it needs no project/fallback split.
## Projects
| Key | Action |
|-----------|-------------------------------------------------------------------------------|
| `C-c C-p` | Switch project — saves & closes current project's buffers first, then prompts |
Projects are rescanned from `~/Projects` on every startup (direct
children only, no symlinks, most-recently-modified first). Jumping into
a dependency's source keeps the *current project* pinned to your last
real project instead of hijacking to it.
## Build
| Key | Action |
|-----------|-----------------------------------------------------------|
| `C-c C-c` | Run `./build.sh` from project root (saves buffers first) |
| `C-c C-o` | Same, but prompts for extra args to append |
| `C-j` | Toggle the `*compilation*` output panel (right side, 35%) |
Requires `build.sh` at the project root; output is a real `compile`
buffer, so `M-g n` / `M-g p` jump between errors.
## Dev / Eglot (LSP)
Wired up for Odin (`.odin`, tree-sitter + `odin-ts-mode` + OLS).
| Key | Action |
|------------|----------------------------------|
| `C-c r` | `eglot-rename` |
| `C-c a` | `eglot-code-actions` |
| `K` norm | Full hover doc in a focused pane |
| `q` or `K` | Close the hover pane |
Add a new language: new `;;;; <Language>` section in `init-dev.el`.
## Git (Magit)
`C-c C-g` opens `magit-status` (replaces the lazygit TUI). Magit keeps
its own native keys — nothing reimplemented — evil's normal state just
shadows most of them by default, so they're restored below plus a couple
of hjkl navigation aliases.
### Workflow
- Expand only the file you're working on with `TAB`.
- Move between hunks with `j` / `k`.
- Press `s` on a hunk to stage only that hunk.
- Continue staging related hunks until they form a single logical change.
- Press `c c` to create a commit from the staged changes.
- Repeat for the remaining hunks, creating small, focused commits instead of one large commit.
> **Tip:** If a single hunk contains unrelated changes, press `e` to edit the patch before staging, allowing you to split the changes into separate commits.
| Key | Action |
|-----------|----------------------|
| `j` / `n` | next section |
| `s` | stage |
| `k` / `p` | previous section |
| `u` | unstage |
| `h` | up to parent section |
| `U` | unstage all |
| `RET` | visit thing at point |
| `x` | discard |
| `g r` | refresh |
| `q` | quit / bury buffer |
**Popups** *(transient menus)*:
| Key | Action |
|-----|----------------|
| `c` | commit |
| `m` | merge |
| `d` | diff |
| `r` | rebase |
| `b` | branch |
| `t` | tag |
| `l` | log |
| `z` | stash |
| `f` | fetch |
| `e` | ediff |
| `F` | pull |
| `$` | process buffer |
| `P` | push |
Covers the daily-driver surface, not magit's entire command set —
anything not listed falls through to evil's default on that key
(harmless, just `<escape>`).
## Dired
Evil `hjkl` grafted onto stock dired commands, remapped only so evil
doesn't swallow dired's own letters.
| Key | Action |
|-------------|---------------------------|
| `j` / `k` | next / previous line |
| `d` | flag for deletion |
| `h` | up a directory |
| `x` | execute flagged deletions |
| `l` / `RET` | open file or directory |
| `D` | delete now, no flag step |
| `q` | quit window |
| `R` | rename / move |
| `SPC` / `m` | mark |
| `C` | copy |
| `u` | unmark |
| `y` | copy filename as kill |
| `U` | unmark all |
| `!` | shell command on file(s) |
| `t` | toggle marks |
| `+` / `f` | create directory |
| `g r` | refresh listing |
| `a` | create empty file |
| `(` | toggle file details |
## Evil *(buffer editing only — dired/magit/etc. keep native binds)*
**Ex commands:**
| Command | Action |
|---------|---------------------|
| `:w` | save buffer |
| `:q` | kill current buffer |
| `:wq` | save and kill Emacs |
**Insert state:**
| Key | Action |
|-------|----------------------------------------------------|
| `TAB` | insert a literal tab (overrides major-mode indent) |
## In-buffer Completion (Corfu)
Auto-popup after 2 characters, 0.15s delay.
| Key | Action |
|-----------------|--------------------|
| `C-n` / `TAB` | next candidate |
| `C-p` / `S-TAB` | previous candidate |
| `RET` | insert selection |
| `<escape>` | quit completion |
## Minibuffer Completion
Vertico *(centered posframe)* + orderless matching + marginalia +
consult. Type fragments in any order to filter.
## Misc / Global
| Key | Action |
|----------------|-----------------------------------|
| `C-S-p` | `execute-extended-command` (M-x) |
| `<escape>` | acts like `C-g` everywhere |
| *(any prefix)* | which-key shows available options |
## Look & Feel
Theme: **gruvbox-dark-hard**, background/fringe/line-numbers darkened
to `#111111`. Font: JetBrainsMono Nerd Font @ 12pt. Line numbers and
current-line highlight on globally; scroll margin 15 lines.
"
"Reference text shown by `my/toggle-cheatsheet'.")
(define-derived-mode my/cheatsheet-mode markdown-view-mode "Cheatsheet"
"Read-only `markdown-view-mode' variant for the keybinding cheat sheet.")
(with-eval-after-load 'evil
(evil-define-key '(normal motion) my/cheatsheet-mode-map
(kbd "q") #'quit-window))
(defun my/cheatsheet-buffer ()
"Return the cheatsheet buffer, creating and populating it on first use."
(let ((buf (get-buffer-create "*Cheatsheet*")))
(with-current-buffer buf
(unless (derived-mode-p 'my/cheatsheet-mode)
(let ((inhibit-read-only t))
(erase-buffer)
(insert my/cheatsheet-text)
(my/cheatsheet-mode)
(goto-char (point-min)))))
buf))
(defun my/toggle-cheatsheet ()
"Toggle a non-blocking side window showing this config's cheat sheet.
Same show/hide-only-the-window behaviour as `my/toggle-build-panel' —
doesn't rebuild the buffer each time, just shows or hides it."
(interactive)
(let* ((buf (my/cheatsheet-buffer))
(win (get-buffer-window buf)))
(if win
(delete-window win)
(display-buffer buf))))
;; Bound globally (not evil-scoped), matching the build panel keys, so it
;; works the same from normal state, insert state, or anywhere else.
(global-set-key (kbd "C-c /") #'my/toggle-cheatsheet)
(with-eval-after-load 'which-key
(which-key-add-key-based-replacements
"C-c /" "cheatsheet"))
(provide 'init-cheatsheet)
;;; init-cheatsheet.el ends here