Archived
Initial commit for v1.0 of Simply Emacs.
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
|||||||
|
# Auto-generated by Custom (M-x customize) - not hand-edited, don't track
|
||||||
|
custom.el
|
||||||
|
|
||||||
|
# Local state / caches
|
||||||
|
backups/
|
||||||
|
auto-save/
|
||||||
|
auto-save-list/
|
||||||
|
eln-cache/
|
||||||
|
elpa/
|
||||||
|
transient/
|
||||||
|
tree-sitter/
|
||||||
|
.cache/
|
||||||
|
|
||||||
|
# Byte-compiled / elc
|
||||||
|
*.elc
|
||||||
|
|
||||||
|
places
|
||||||
|
projects
|
||||||
|
recentf
|
||||||
|
history
|
||||||
|
todo
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# SimplyEmacs
|
||||||
|
|
||||||
|
> Vanilla Emacs at heart, with Evil scoped strictly to buffer editing and basic motion.
|
||||||
|
|
||||||
|
## Philosophy
|
||||||
|
|
||||||
|
SimplyEmacs is my personal, versioned Emacs setup — built up piece by piece and
|
||||||
|
treated like a project with tags and releases.
|
||||||
|
|
||||||
|
Simple idea: stick as close to vanilla Emacs as possible, but keep movement comfortable.
|
||||||
|
Evil handles normal modal editing inside regular text buffers, while in modes like
|
||||||
|
Dired or Magit it's stripped down to a paper-thin navigation layer (hjkl for moving
|
||||||
|
around) so all the native keybinds and features keep working as intended.
|
||||||
|
It gives you comfortable motion without burying how Emacs actually works.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── init.el # bootstrap, general UI, built-in settings, module loader
|
||||||
|
├── lisp/
|
||||||
|
│ ├── init-evil.el # evil-mode, ex commands, buffer-editing keybinds
|
||||||
|
│ ├── init-dired.el # evil-style navigation for dired
|
||||||
|
│ ├── init-completion.el # vertico, orderless, marginalia, corfu, consult, which-key
|
||||||
|
│ ├── init-project.el # project.el scoped to ~/Projects, with a sticky project pin
|
||||||
|
│ ├── init-build.el # simple build system around build.sh scripts
|
||||||
|
│ ├── init-dev.el # per-language tooling (Odin, Lua, TOML) + eglot
|
||||||
|
│ ├── init-git.el # version control with Magit
|
||||||
|
│ ├── init-theme.el # gruvbox-dark-hard + a few face tweaks
|
||||||
|
│ └── init-cheatsheet.el # in-Emacs keybinding reference (toggle with C-c /)
|
||||||
|
└── custom.el # auto-generated by M-x customize, gitignored
|
||||||
|
```
|
||||||
|
|
||||||
|
`init.el` is meant to stay readable as "what does bare Emacs look and feel
|
||||||
|
like" — general UI, backups/autosave, scrolling, removing confirmations, etc.
|
||||||
|
Everything else is one file per concern under `lisp/`, `require`d at the
|
||||||
|
bottom of `init.el`. Custom's auto-generated output is redirected to its
|
||||||
|
own `custom.el` (gitignored) so a stray `M-x customize` never shows up
|
||||||
|
as noise in a diff.
|
||||||
|
|
||||||
|
## Included
|
||||||
|
|
||||||
|
- **Completion** — vertico + vertico-posframe, orderless, marginalia, corfu, consult
|
||||||
|
- **Discovery** — which-key
|
||||||
|
- **Editing** — evil (buffer editing only)
|
||||||
|
- **Files** — dired, tuned with evil-style `hjkl` navigation
|
||||||
|
- **Dev tooling** — tree-sitter + odin-ts-mode + eglot (OLS) for Odin, plus lua-mode, toml-mode, and markdown-mode
|
||||||
|
- **Theme** — gruvbox-dark-hard, darkened background/fringe/line-numbers to `#111111`
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone <this-repo> ~/.config/emacs
|
||||||
|
emacs
|
||||||
|
```
|
||||||
|
|
||||||
|
First launch will refresh package archives and install everything declared
|
||||||
|
via `use-package`.
|
||||||
|
|
||||||
|
## Adding a language
|
||||||
|
|
||||||
|
Add a new `;;;; <Language>` section to `lisp/init-dev.el` following the Odin
|
||||||
|
example (tree-sitter grammar source, mode package, eglot hook).
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
|
||||||
|
Tagged releases (`v1.0`, `v1.1`, ...) mark points where the config is in a
|
||||||
|
known-good, "I'd hand this to past-me" state.
|
||||||
|
Day-to-day tweaks happen over time and, once they've proven themselves,
|
||||||
|
eventually land in a version.
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
;;; early-init.el --- Pre-init: GC ceiling and frame chrome -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; Runs before package.el initialises and before the first frame is drawn, so
|
||||||
|
;; it is the only place two things can take effect:
|
||||||
|
;; 1. Raising the GC ceiling, so the whole startup (package load + init.el)
|
||||||
|
;; runs without garbage-collection pauses.
|
||||||
|
;; 2. Removing the menu/tool/scroll bars via `default-frame-alist', so they
|
||||||
|
;; are never drawn — toggling the minor modes in init.el instead draws
|
||||||
|
;; them and then removes them, a visible flicker on launch.
|
||||||
|
;;
|
||||||
|
;; Nothing here may depend on a package: none are loaded yet.
|
||||||
|
;;
|
||||||
|
;; IMPORTANT: the GC bump below is only half the pair. Restore a working value
|
||||||
|
;; once startup is done by adding this to init.el:
|
||||||
|
;;
|
||||||
|
;; (add-hook 'emacs-startup-hook
|
||||||
|
;; (lambda () (setq gc-cons-threshold (* 16 1024 1024))))
|
||||||
|
;;
|
||||||
|
;; Without it, GC never runs again and Emacs slowly eats memory.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Garbage Collection
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Hold GC off for the whole startup; init.el lowers it back on
|
||||||
|
;; `emacs-startup-hook' (see the Commentary above).
|
||||||
|
|
||||||
|
(setq gc-cons-threshold most-positive-fixnum)
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Frame Chrome (no flicker)
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Disable the bars before the first frame exists.
|
||||||
|
|
||||||
|
(push '(menu-bar-lines . 0) default-frame-alist)
|
||||||
|
(push '(tool-bar-lines . 0) default-frame-alist)
|
||||||
|
(push '(vertical-scroll-bars) default-frame-alist)
|
||||||
|
|
||||||
|
;; Don't resize the frame when the UI/fonts change during startup.
|
||||||
|
(setq frame-inhibit-implied-resize t)
|
||||||
|
|
||||||
|
;;; early-init.el ends here
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
;;; init.el --- SimplyEmacs: bootstrap, UI, built-in settings -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; The "bare Emacs" layer: package bootstrap, general UI, fonts, built-in
|
||||||
|
;; behaviour, and the module loader. Anything beyond vanilla UI/built-in
|
||||||
|
;; settings lives in its own lisp/ file and is required near the bottom, so
|
||||||
|
;; this file stays readable as "what does bare Emacs look and feel like".
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
;; Make Emacs find programs installed in ~/.local/bin
|
||||||
|
(add-to-list 'exec-path (expand-file-name "~/.local/bin"))
|
||||||
|
(setenv "PATH"
|
||||||
|
(concat (expand-file-name "~/.local/bin")
|
||||||
|
path-separator
|
||||||
|
(getenv "PATH")))
|
||||||
|
|
||||||
|
;; GC restore - see early-init.el
|
||||||
|
(add-hook 'emacs-startup-hook
|
||||||
|
(lambda () (setq gc-cons-threshold (* 16 1024 1024))))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Package Setup
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(require 'package)
|
||||||
|
|
||||||
|
(setq package-archives
|
||||||
|
'(("gnu" . "https://elpa.gnu.org/packages/")
|
||||||
|
("nongnu" . "https://elpa.nongnu.org/nongnu/")
|
||||||
|
("melpa" . "https://melpa.org/packages/")))
|
||||||
|
|
||||||
|
(package-initialize)
|
||||||
|
|
||||||
|
(unless package-archive-contents
|
||||||
|
(package-refresh-contents))
|
||||||
|
|
||||||
|
(unless (package-installed-p 'use-package)
|
||||||
|
(package-install 'use-package))
|
||||||
|
|
||||||
|
(require 'use-package)
|
||||||
|
|
||||||
|
(setq use-package-always-ensure t)
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Module Loader
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Anything beyond vanilla UI/built-in behaviour lives in its own lisp/ file
|
||||||
|
;; and is required at the bottom, so this file stays the "bare Emacs" layer.
|
||||||
|
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; General UI
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(global-so-long-mode 1)
|
||||||
|
|
||||||
|
(setq frame-resize-pixelwise t)
|
||||||
|
|
||||||
|
(setq inhibit-startup-screen t)
|
||||||
|
(setq initial-scratch-message nil)
|
||||||
|
|
||||||
|
(setq-default header-line-format mode-line-format)
|
||||||
|
(setq-default mode-line-format nil)
|
||||||
|
|
||||||
|
(blink-cursor-mode 0)
|
||||||
|
|
||||||
|
(setq ring-bell-function #'ignore)
|
||||||
|
|
||||||
|
(global-hl-line-mode 1)
|
||||||
|
(global-display-line-numbers-mode 1)
|
||||||
|
|
||||||
|
(column-number-mode 1)
|
||||||
|
(size-indication-mode 1)
|
||||||
|
|
||||||
|
(setq-default indent-tabs-mode nil)
|
||||||
|
(setq-default tab-width 4)
|
||||||
|
|
||||||
|
(fset 'yes-or-no-p #'y-or-n-p)
|
||||||
|
|
||||||
|
(repeat-mode 1)
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Fonts
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Main font
|
||||||
|
(set-face-attribute 'default nil
|
||||||
|
:family "JetBrainsMono Nerd Font"
|
||||||
|
:height 120
|
||||||
|
:weight 'regular)
|
||||||
|
|
||||||
|
;; Variable pitch font (used by Org, Markdown, etc. if enabled)
|
||||||
|
(set-face-attribute 'variable-pitch nil
|
||||||
|
:family "Noto Sans"
|
||||||
|
:height 130
|
||||||
|
:weight 'regular)
|
||||||
|
|
||||||
|
;; Fixed pitch should match the default font
|
||||||
|
(set-face-attribute 'fixed-pitch nil
|
||||||
|
:family "JetBrainsMono Nerd Font"
|
||||||
|
:height 120
|
||||||
|
:weight 'regular)
|
||||||
|
|
||||||
|
;; Slightly larger mode line
|
||||||
|
(set-face-attribute 'mode-line nil
|
||||||
|
:height 130)
|
||||||
|
(set-face-attribute 'mode-line-inactive nil
|
||||||
|
:height 130)
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Keep Projects Clean (built-in)
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(setq backup-directory-alist
|
||||||
|
`(("." . ,(expand-file-name "backups/" user-emacs-directory))))
|
||||||
|
|
||||||
|
(setq auto-save-file-name-transforms
|
||||||
|
`((".*" ,(expand-file-name "auto-save/" user-emacs-directory) t)))
|
||||||
|
|
||||||
|
(setq create-lockfiles nil)
|
||||||
|
|
||||||
|
(make-directory (expand-file-name "backups/" user-emacs-directory) t)
|
||||||
|
(make-directory (expand-file-name "auto-save/" user-emacs-directory) t)
|
||||||
|
|
||||||
|
(save-place-mode 1)
|
||||||
|
(savehist-mode 1)
|
||||||
|
(recentf-mode 1)
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Convenience (built-in)
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(setq scroll-conservatively 101)
|
||||||
|
(setq scroll-margin 15)
|
||||||
|
|
||||||
|
(electric-pair-mode 1)
|
||||||
|
|
||||||
|
(show-paren-mode 1)
|
||||||
|
|
||||||
|
;; Command Palette
|
||||||
|
(global-set-key (kbd "C-S-p") #'execute-extended-command)
|
||||||
|
|
||||||
|
(defun my/keyboard-quit ()
|
||||||
|
"Escape behaves like C-g."
|
||||||
|
(interactive)
|
||||||
|
(keyboard-quit))
|
||||||
|
|
||||||
|
(global-set-key (kbd "<escape>") #'my/keyboard-quit)
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Don't ask so many questions (built-in)
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
;; Quit immediately
|
||||||
|
(setq confirm-kill-emacs nil)
|
||||||
|
|
||||||
|
;; Don't ask before killing buffers with running processes
|
||||||
|
(setq confirm-kill-processes nil)
|
||||||
|
|
||||||
|
;; Don't ask about active processes when exiting
|
||||||
|
(setq kill-buffer-query-functions nil)
|
||||||
|
|
||||||
|
;; Follow symlinks without asking
|
||||||
|
(setq vc-follow-symlinks t)
|
||||||
|
|
||||||
|
;; Revert changed files automatically
|
||||||
|
(global-auto-revert-mode 1)
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Modules
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
;; vim motions used sparingly
|
||||||
|
(require 'init-evil)
|
||||||
|
;; file browser
|
||||||
|
(require 'init-dired)
|
||||||
|
;; code completion
|
||||||
|
(require 'init-completion)
|
||||||
|
;; project management
|
||||||
|
(require 'init-project)
|
||||||
|
;; project building
|
||||||
|
(require 'init-build)
|
||||||
|
;; development languages and lsp
|
||||||
|
(require 'init-dev)
|
||||||
|
;; version control git
|
||||||
|
(require 'init-git)
|
||||||
|
;; theme
|
||||||
|
(require 'init-theme)
|
||||||
|
;; cheatsheet
|
||||||
|
(require 'init-cheatsheet)
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Custom
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Custom's auto-generated output goes to its own custom.el so a stray
|
||||||
|
;; `M-x customize' never leaves noise in this hand-written init.
|
||||||
|
|
||||||
|
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
|
||||||
|
(when (file-exists-p custom-file)
|
||||||
|
(load custom-file))
|
||||||
|
|
||||||
|
(custom-set-variables
|
||||||
|
;; custom-set-variables was added by Custom.
|
||||||
|
;; If you edit it by hand, you could mess it up, so be careful.
|
||||||
|
;; Your init file should contain only one such instance.
|
||||||
|
;; If there is more than one, they won't work right.
|
||||||
|
'(package-vc-selected-packages
|
||||||
|
'((odin-ts-mode :url "https://github.com/Sampie159/odin-ts-mode"))))
|
||||||
|
|
||||||
|
(custom-set-faces
|
||||||
|
;; custom-set-faces was added by Custom.
|
||||||
|
;; If you edit it by hand, you could mess it up, so be careful.
|
||||||
|
;; Your init file should contain only one such instance.
|
||||||
|
;; If there is more than one, they won't work right.
|
||||||
|
)
|
||||||
|
|
||||||
|
;;; init.el ends here
|
||||||
Executable
+71
@@ -0,0 +1,71 @@
|
|||||||
|
;;; init-build.el --- Project build command -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; Build via built-in `compile', running ./build.sh from the project root.
|
||||||
|
;; compile saves buffers, reuses one output buffer, and turns file:line:col
|
||||||
|
;; into clickable jumps — a thin wrapper, not a terminal-send hack.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
;; Always show *compilation* as a fixed right-hand panel, so the toggle below
|
||||||
|
;; hits a predictable window.
|
||||||
|
(add-to-list 'display-buffer-alist
|
||||||
|
'("\\*compilation\\*"
|
||||||
|
(display-buffer-reuse-window display-buffer-in-side-window)
|
||||||
|
(side . right)
|
||||||
|
(window-width . 0.35)))
|
||||||
|
|
||||||
|
;; If t is too aggressive (e.g. auto-scrolls while trying to read mid-output
|
||||||
|
;; that hasn't errored yet), swap to:
|
||||||
|
;; (setq compilation-scroll-output 'first-error)
|
||||||
|
(setq compilation-scroll-output t)
|
||||||
|
|
||||||
|
(defun my/project-build (&optional args)
|
||||||
|
"Run ./build.sh from the project root, appending ARGS (a string) if given.
|
||||||
|
Errors if build.sh isn't readable there. Saves all buffers first."
|
||||||
|
(let* ((root (if (project-current)
|
||||||
|
(project-root (project-current))
|
||||||
|
default-directory))
|
||||||
|
(script (expand-file-name "build.sh" root))
|
||||||
|
(default-directory root))
|
||||||
|
(unless (file-readable-p script)
|
||||||
|
(user-error "No build.sh found in %s" root))
|
||||||
|
(save-some-buffers t)
|
||||||
|
(compile (string-trim (concat "./build.sh " (or args ""))))))
|
||||||
|
|
||||||
|
(defun my/build ()
|
||||||
|
"Build the current project via `./build.sh' with no arguments."
|
||||||
|
(interactive)
|
||||||
|
(my/project-build nil))
|
||||||
|
|
||||||
|
(defun my/build-with-options ()
|
||||||
|
"Build the current project, prompting for arguments to append."
|
||||||
|
(interactive)
|
||||||
|
(my/project-build (read-string "build.sh args: ")))
|
||||||
|
|
||||||
|
(defun my/toggle-build-panel ()
|
||||||
|
"Show/hide the *compilation* window from the last run (doesn't rebuild)."
|
||||||
|
(interactive)
|
||||||
|
(let ((buf (get-buffer "*compilation*")))
|
||||||
|
(cond
|
||||||
|
((null buf)
|
||||||
|
(user-error "No build output yet — run `my/build' first"))
|
||||||
|
((get-buffer-window buf)
|
||||||
|
(delete-window (get-buffer-window buf)))
|
||||||
|
(t
|
||||||
|
(display-buffer buf)))))
|
||||||
|
|
||||||
|
;; Global (not evil-scoped) so it works from any state or buffer.
|
||||||
|
;; C-c C-c build C-c C-o build w/ options C-j toggle panel
|
||||||
|
(global-set-key (kbd "C-c C-c") #'my/build)
|
||||||
|
(global-set-key (kbd "C-c C-o") #'my/build-with-options)
|
||||||
|
(global-set-key (kbd "C-j") #'my/toggle-build-panel)
|
||||||
|
|
||||||
|
(with-eval-after-load 'which-key
|
||||||
|
(which-key-add-key-based-replacements
|
||||||
|
"C-c" "code"
|
||||||
|
"C-c c" "build"
|
||||||
|
"C-c o" "build (with options)"))
|
||||||
|
|
||||||
|
(provide 'init-build)
|
||||||
|
;;; init-build.el ends here
|
||||||
Executable
+248
@@ -0,0 +1,248 @@
|
|||||||
|
;;; 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
|
||||||
Executable
+123
@@ -0,0 +1,123 @@
|
|||||||
|
;;; init-completion.el --- Minibuffer + in-buffer completion -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; Vertico + Orderless + Marginalia + Consult for minibuffer completion.
|
||||||
|
;; Corfu provides in-buffer completion.
|
||||||
|
;; Embark adds contextual actions.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
;;;; Helper functions
|
||||||
|
|
||||||
|
(defun my/find-file-below (file)
|
||||||
|
"Open FILE in a split below."
|
||||||
|
(interactive "fFile: ")
|
||||||
|
(select-window (split-window-below))
|
||||||
|
(find-file file))
|
||||||
|
|
||||||
|
(defun my/find-file-right (file)
|
||||||
|
"Open FILE in a split to the right."
|
||||||
|
(interactive "fFile: ")
|
||||||
|
(select-window (split-window-right))
|
||||||
|
(find-file file))
|
||||||
|
|
||||||
|
(defun my/vertico-split-below ()
|
||||||
|
"Open the selected Vertico file candidate below."
|
||||||
|
(interactive)
|
||||||
|
(setq unread-command-events (listify-key-sequence "2"))
|
||||||
|
(embark-act))
|
||||||
|
|
||||||
|
(defun my/vertico-split-right ()
|
||||||
|
"Open the selected Vertico file candidate to the right."
|
||||||
|
(interactive)
|
||||||
|
(setq unread-command-events (listify-key-sequence "3"))
|
||||||
|
(embark-act))
|
||||||
|
|
||||||
|
;;;; Vertico
|
||||||
|
|
||||||
|
(use-package vertico
|
||||||
|
:init
|
||||||
|
(vertico-mode)
|
||||||
|
|
||||||
|
:custom
|
||||||
|
(vertico-count 25))
|
||||||
|
|
||||||
|
(use-package vertico-posframe
|
||||||
|
:after vertico
|
||||||
|
:custom
|
||||||
|
(vertico-posframe-poshandler #'posframe-poshandler-frame-center)
|
||||||
|
(vertico-posframe-border-width 2)
|
||||||
|
(vertico-posframe-width 120)
|
||||||
|
(vertico-posframe-max-width 300)
|
||||||
|
:config
|
||||||
|
(vertico-posframe-mode))
|
||||||
|
|
||||||
|
;;;; Completion styles
|
||||||
|
|
||||||
|
(use-package orderless
|
||||||
|
:custom
|
||||||
|
(completion-styles '(orderless basic))
|
||||||
|
(completion-category-defaults nil)
|
||||||
|
(completion-category-overrides
|
||||||
|
'((file (styles partial-completion)))))
|
||||||
|
|
||||||
|
(use-package marginalia
|
||||||
|
:init
|
||||||
|
(marginalia-mode))
|
||||||
|
|
||||||
|
;;;; In-buffer completion
|
||||||
|
|
||||||
|
(use-package corfu
|
||||||
|
:init
|
||||||
|
(global-corfu-mode)
|
||||||
|
|
||||||
|
:custom
|
||||||
|
(corfu-auto t)
|
||||||
|
(corfu-auto-delay 0.15)
|
||||||
|
(corfu-auto-prefix 2)
|
||||||
|
(corfu-cycle t)
|
||||||
|
(corfu-preselect 'first)
|
||||||
|
|
||||||
|
:bind
|
||||||
|
(:map corfu-map
|
||||||
|
("C-n" . corfu-next)
|
||||||
|
("C-p" . corfu-previous)
|
||||||
|
("RET" . corfu-insert)
|
||||||
|
("<escape>" . corfu-quit))
|
||||||
|
|
||||||
|
:config
|
||||||
|
(with-eval-after-load 'evil
|
||||||
|
(evil-define-key 'insert 'global
|
||||||
|
(kbd "C-n") #'corfu-next
|
||||||
|
(kbd "C-p") #'corfu-previous)))
|
||||||
|
|
||||||
|
;;;; Minibuffer commands
|
||||||
|
|
||||||
|
(use-package consult)
|
||||||
|
|
||||||
|
;;;; Contextual actions
|
||||||
|
|
||||||
|
(use-package embark
|
||||||
|
:after vertico
|
||||||
|
:bind
|
||||||
|
(("C-." . embark-act)
|
||||||
|
:map embark-file-map
|
||||||
|
("2" . my/find-file-below)
|
||||||
|
("3" . my/find-file-right)
|
||||||
|
:map vertico-map
|
||||||
|
("C-s" . my/vertico-split-below)
|
||||||
|
("C-v" . my/vertico-split-right)))
|
||||||
|
|
||||||
|
(use-package embark-consult
|
||||||
|
:after (embark consult)
|
||||||
|
:hook
|
||||||
|
(embark-collect-mode . consult-preview-at-point-mode))
|
||||||
|
|
||||||
|
;;;; Help/discovery
|
||||||
|
|
||||||
|
(use-package which-key
|
||||||
|
:config
|
||||||
|
(which-key-mode))
|
||||||
|
|
||||||
|
(provide 'init-completion)
|
||||||
|
;;; init-completion.el ends here
|
||||||
Executable
+95
@@ -0,0 +1,95 @@
|
|||||||
|
;;; init-dev.el --- Language tooling -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; Per-language setup. Currently Odin (tree-sitter + odin-ts-mode + eglot/OLS)
|
||||||
|
;; plus markdown-mode. Add each language as its own section.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
;;;; Markdown
|
||||||
|
|
||||||
|
(use-package markdown-mode)
|
||||||
|
|
||||||
|
;;;; Odin
|
||||||
|
|
||||||
|
(setq treesit-language-source-alist
|
||||||
|
'((odin "https://github.com/tree-sitter-grammars/tree-sitter-odin")))
|
||||||
|
|
||||||
|
(use-package odin-ts-mode
|
||||||
|
:vc (:url "https://github.com/Sampie159/odin-ts-mode")
|
||||||
|
:mode "\\.odin\\'")
|
||||||
|
|
||||||
|
;;;; Lua
|
||||||
|
|
||||||
|
(use-package lua-mode
|
||||||
|
:ensure t)
|
||||||
|
|
||||||
|
;;;; TOML
|
||||||
|
|
||||||
|
(use-package toml-mode
|
||||||
|
:ensure t)
|
||||||
|
|
||||||
|
;;;; Eglot LSP
|
||||||
|
|
||||||
|
(use-package eglot
|
||||||
|
:hook (odin-ts-mode . eglot-ensure)
|
||||||
|
:bind
|
||||||
|
(:map eglot-mode-map
|
||||||
|
("C-c r" . eglot-rename)
|
||||||
|
("C-c a" . eglot-code-actions))
|
||||||
|
:config
|
||||||
|
;; Signature-only ElDoc: OLS hover already includes the signature, so
|
||||||
|
;; showing both would duplicate it in the echo area.
|
||||||
|
(add-hook
|
||||||
|
'eglot-managed-mode-hook
|
||||||
|
(lambda ()
|
||||||
|
(setq-local eldoc-documentation-functions
|
||||||
|
'(eglot-signature-eldoc-function))))
|
||||||
|
|
||||||
|
;; K is context-aware: on a diagnostic (red underline) it shows the error
|
||||||
|
;; text, otherwise the full hover payload — both in the same focused pane
|
||||||
|
;; (dismiss with q or K). The echo area keeps the live signature.
|
||||||
|
;; ] e / [ e jump between diagnostics, echoing each as you land.
|
||||||
|
(define-derived-mode my/eglot-hover-mode special-mode "Eglot-Hover"
|
||||||
|
"Major mode for the eglot hover / diagnostic pane.")
|
||||||
|
|
||||||
|
(with-eval-after-load 'evil
|
||||||
|
(evil-define-key '(normal motion) my/eglot-hover-mode-map
|
||||||
|
(kbd "q") #'quit-window
|
||||||
|
(kbd "K") #'quit-window))
|
||||||
|
|
||||||
|
(defun my/eglot-hover--display (string &rest _)
|
||||||
|
"Show STRING in a focused *eglot-hover* pane, or message if empty."
|
||||||
|
(if (and string (not (string-empty-p string)))
|
||||||
|
(let ((buf (get-buffer-create "*eglot-hover*")))
|
||||||
|
(with-current-buffer buf
|
||||||
|
(let ((inhibit-read-only t))
|
||||||
|
(erase-buffer)
|
||||||
|
(insert string)
|
||||||
|
(goto-char (point-min)))
|
||||||
|
(my/eglot-hover-mode))
|
||||||
|
(select-window (display-buffer buf))) ; focus the pane
|
||||||
|
(message "Nothing to show at point")))
|
||||||
|
|
||||||
|
(defun my/eglot-help-at-point ()
|
||||||
|
"Show diagnostics at point if any, else eglot hover docs — in the pane."
|
||||||
|
(interactive)
|
||||||
|
(let ((diags (flymake-diagnostics (point))))
|
||||||
|
(if diags
|
||||||
|
(my/eglot-hover--display
|
||||||
|
(mapconcat #'flymake-diagnostic-text diags "\n\n"))
|
||||||
|
(eglot-hover-eldoc-function #'my/eglot-hover--display))))
|
||||||
|
|
||||||
|
(with-eval-after-load 'evil
|
||||||
|
(evil-define-key 'normal 'eglot-mode-map
|
||||||
|
(kbd "K") #'my/eglot-help-at-point
|
||||||
|
(kbd "] e") #'flymake-goto-next-error
|
||||||
|
(kbd "[ e") #'flymake-goto-prev-error))
|
||||||
|
|
||||||
|
(with-eval-after-load 'which-key
|
||||||
|
(which-key-add-key-based-replacements
|
||||||
|
"C-c r" "rename"
|
||||||
|
"C-c a" "code actions")))
|
||||||
|
|
||||||
|
(provide 'init-dev)
|
||||||
|
;;; init-dev.el ends here
|
||||||
Executable
+54
@@ -0,0 +1,54 @@
|
|||||||
|
;;; init-dired.el --- Dired with evil-flavoured navigation -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; Dired stays vanilla — every binding points at a stock dired command.
|
||||||
|
;; They're remapped only so evil's normal-state keys don't shadow dired's
|
||||||
|
;; own single-letter commands.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
;; Reuse the current dired buffer when descending/ascending (no pileup).
|
||||||
|
(setq dired-kill-when-opening-new-dired-buffer t)
|
||||||
|
|
||||||
|
;; Order directories first
|
||||||
|
(setq dired-listing-switches "-agho --group-directories-first")
|
||||||
|
|
||||||
|
(with-eval-after-load 'dired
|
||||||
|
(evil-define-key 'normal dired-mode-map
|
||||||
|
;; Navigation. RET and <return> both bound so evil's global <return>
|
||||||
|
;; doesn't shadow them here.
|
||||||
|
(kbd "j") #'dired-next-line
|
||||||
|
(kbd "k") #'dired-previous-line
|
||||||
|
(kbd "h") #'dired-up-directory
|
||||||
|
(kbd "l") #'dired-find-file
|
||||||
|
(kbd "RET") #'dired-find-file
|
||||||
|
[return] #'dired-find-file
|
||||||
|
(kbd "q") #'quit-window
|
||||||
|
|
||||||
|
;; Marking.
|
||||||
|
(kbd "SPC") #'dired-mark
|
||||||
|
(kbd "m") #'dired-mark
|
||||||
|
(kbd "u") #'dired-unmark
|
||||||
|
(kbd "U") #'dired-unmark-all-marks
|
||||||
|
(kbd "t") #'dired-toggle-marks
|
||||||
|
|
||||||
|
;; Deletion: d flags, x executes flags, D deletes now.
|
||||||
|
(kbd "d") #'dired-flag-file-deletion
|
||||||
|
(kbd "x") #'dired-do-flagged-delete
|
||||||
|
(kbd "D") #'dired-do-delete
|
||||||
|
|
||||||
|
;; File operations.
|
||||||
|
(kbd "R") #'dired-do-rename
|
||||||
|
(kbd "C") #'dired-do-copy
|
||||||
|
(kbd "y") #'dired-copy-filename-as-kill
|
||||||
|
(kbd "!") #'dired-do-shell-command
|
||||||
|
(kbd "+") #'dired-create-directory
|
||||||
|
(kbd "f") #'dired-create-directory
|
||||||
|
(kbd "a") #'dired-create-empty-file
|
||||||
|
|
||||||
|
;; Misc.
|
||||||
|
(kbd "g r") #'revert-buffer ; refresh listing
|
||||||
|
(kbd "(") #'dired-hide-details-mode)) ; toggle perms/size/date columns
|
||||||
|
|
||||||
|
(provide 'init-dired)
|
||||||
|
;;; init-dired.el ends here
|
||||||
Executable
+84
@@ -0,0 +1,84 @@
|
|||||||
|
;;; init-evil.el --- Evil, scoped to buffer editing -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; Evil is deliberately narrow: modal editing in text buffers only, no
|
||||||
|
;; evil-collection. Dired/Magit/etc. keep native bindings on purpose.
|
||||||
|
;; The nav keys below use `my/project-or': project-scoped command inside a
|
||||||
|
;; project, consult fallback everywhere else.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(require 'project)
|
||||||
|
|
||||||
|
(defmacro my/project-or (project-cmd fallback-cmd)
|
||||||
|
"Call PROJECT-CMD inside a known project, else FALLBACK-CMD.
|
||||||
|
Both are unquoted command symbols."
|
||||||
|
`(lambda ()
|
||||||
|
(interactive)
|
||||||
|
(call-interactively
|
||||||
|
(if (project-current)
|
||||||
|
#',project-cmd
|
||||||
|
#',fallback-cmd))))
|
||||||
|
|
||||||
|
(defun my/insert-tab ()
|
||||||
|
"Insert a literal tab.
|
||||||
|
Bound to both TAB and <tab> to override major-mode indent in insert state;
|
||||||
|
`self-insert-command' can't handle the <tab> event (its arg is the symbol
|
||||||
|
`tab', not a character)."
|
||||||
|
(interactive)
|
||||||
|
(insert "\t"))
|
||||||
|
|
||||||
|
(use-package evil
|
||||||
|
:init
|
||||||
|
(setq evil-want-keybinding nil)
|
||||||
|
:config
|
||||||
|
(evil-mode 1)
|
||||||
|
|
||||||
|
;; Vim commands
|
||||||
|
(evil-ex-define-cmd "w" #'save-buffer)
|
||||||
|
(evil-ex-define-cmd "q" #'kill-current-buffer)
|
||||||
|
(evil-ex-define-cmd "wq" #'save-buffers-kill-terminal)
|
||||||
|
|
||||||
|
;; Half-page movement
|
||||||
|
(define-key evil-normal-state-map (kbd "C-d") #'evil-scroll-down)
|
||||||
|
(define-key evil-normal-state-map (kbd "C-u") #'evil-scroll-up)
|
||||||
|
|
||||||
|
;; Editor shortcuts
|
||||||
|
(dolist (map '(evil-normal-state-map
|
||||||
|
evil-motion-state-map))
|
||||||
|
(define-key (symbol-value map)
|
||||||
|
(kbd "C-e")
|
||||||
|
(my/project-or project-dired dired-jump))
|
||||||
|
|
||||||
|
(define-key (symbol-value map)
|
||||||
|
(kbd "C-s")
|
||||||
|
#'consult-line)
|
||||||
|
|
||||||
|
;; C-m is \r (= RET). Bind [return] to evil-ret so GUI Enter never falls
|
||||||
|
;; back to this C-m binding, leaving C-m free as its own key (GUI only).
|
||||||
|
(define-key (symbol-value map)
|
||||||
|
(kbd "C-m")
|
||||||
|
(my/project-or project-switch-to-buffer consult-buffer))
|
||||||
|
|
||||||
|
(define-key (symbol-value map)
|
||||||
|
[return]
|
||||||
|
#'evil-ret)
|
||||||
|
|
||||||
|
;; consult-ripgrep everywhere: it already scopes to the project root when
|
||||||
|
;; there is one, and shows hits in the posframe (RET jumps) like the other
|
||||||
|
;; finders — unlike project-find-regexp's separate xref pane.
|
||||||
|
(define-key (symbol-value map)
|
||||||
|
(kbd "C-f")
|
||||||
|
#'consult-ripgrep)
|
||||||
|
|
||||||
|
(define-key (symbol-value map)
|
||||||
|
(kbd "C-p")
|
||||||
|
(my/project-or project-find-file consult-fd))))
|
||||||
|
|
||||||
|
;; Literal tab on TAB/<tab> in insert state, overriding major-mode indent.
|
||||||
|
(with-eval-after-load 'evil
|
||||||
|
(evil-define-key 'insert 'global (kbd "TAB") #'my/insert-tab)
|
||||||
|
(evil-define-key 'insert 'global [tab] #'my/insert-tab))
|
||||||
|
|
||||||
|
(provide 'init-evil)
|
||||||
|
;;; init-evil.el ends here
|
||||||
Executable
+83
@@ -0,0 +1,83 @@
|
|||||||
|
;;; init-git.el --- Magit configuration -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; Use Magit with its native keybindings while preserving Evil's window
|
||||||
|
;; management.
|
||||||
|
;;
|
||||||
|
;; Magit buffers are displayed in a dedicated side window on the right.
|
||||||
|
;;
|
||||||
|
;; Custom keybindings:
|
||||||
|
;; C-c C-g - Open Magit
|
||||||
|
;; C-w - Evil window map
|
||||||
|
;; j - Next section
|
||||||
|
;; k - Previous section
|
||||||
|
;; q - Kill Magit buffer and close the side window
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(use-package magit
|
||||||
|
:after evil
|
||||||
|
|
||||||
|
:bind
|
||||||
|
(("C-c C-g" . magit-status))
|
||||||
|
|
||||||
|
:config
|
||||||
|
|
||||||
|
;; Magit appearance and behaviour.
|
||||||
|
|
||||||
|
;; Highlight the exact characters that changed.
|
||||||
|
(setq magit-diff-refine-hunk 'all)
|
||||||
|
|
||||||
|
;; Show more surrounding context in diffs.
|
||||||
|
(setq magit-diff-context-lines 5)
|
||||||
|
|
||||||
|
;; Show all transient options.
|
||||||
|
(setq transient-default-level 5)
|
||||||
|
|
||||||
|
;; Don't ask about saving modified buffers.
|
||||||
|
(setq magit-save-repository-buffers 'dontask)
|
||||||
|
|
||||||
|
;; Display Magit in a dedicated side window on the right.
|
||||||
|
|
||||||
|
(add-to-list
|
||||||
|
'display-buffer-alist
|
||||||
|
'("\\*magit"
|
||||||
|
(display-buffer-in-side-window)
|
||||||
|
(side . right)
|
||||||
|
(slot . 0)
|
||||||
|
(window-width . 0.45)
|
||||||
|
(dedicated . t)))
|
||||||
|
|
||||||
|
;; Custom commands.
|
||||||
|
|
||||||
|
(defun my/magit-quit ()
|
||||||
|
"Kill the current Magit buffer and close its window."
|
||||||
|
(interactive)
|
||||||
|
(quit-window t))
|
||||||
|
|
||||||
|
;; Keybindings.
|
||||||
|
|
||||||
|
(dolist (map '(magit-mode-map
|
||||||
|
magit-status-mode-map
|
||||||
|
magit-log-mode-map
|
||||||
|
magit-diff-mode-map
|
||||||
|
magit-revision-mode-map))
|
||||||
|
(with-eval-after-load 'magit
|
||||||
|
(when (boundp map)
|
||||||
|
(let ((keymap (symbol-value map)))
|
||||||
|
;; Keep Evil window commands.
|
||||||
|
(define-key keymap (kbd "C-w") evil-window-map)
|
||||||
|
|
||||||
|
;; Vim-style movement.
|
||||||
|
(define-key keymap (kbd "j") #'magit-section-forward)
|
||||||
|
(define-key keymap (kbd "k") #'magit-section-backward)
|
||||||
|
|
||||||
|
;; Quit Magit.
|
||||||
|
(define-key keymap (kbd "q") #'my/magit-quit)))))
|
||||||
|
|
||||||
|
(with-eval-after-load 'which-key
|
||||||
|
(which-key-add-key-based-replacements
|
||||||
|
"C-c C-g" "Magit")))
|
||||||
|
|
||||||
|
(provide 'init-git)
|
||||||
|
;;; init-git.el ends here
|
||||||
Executable
+113
@@ -0,0 +1,113 @@
|
|||||||
|
;;; init-project.el --- Project switching, scoped to ~/Projects -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; Built on built-in project.el, no third-party manager. Projects are the
|
||||||
|
;; direct children of ~/Projects, rescanned authoritatively each startup so
|
||||||
|
;; the list never accumulates stale entries. A "pin" keeps the current
|
||||||
|
;; project from hijacking to a dependency's checkout on jump-to-definition.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(require 'project)
|
||||||
|
|
||||||
|
;; A directory is a project root if under VC or containing one of these.
|
||||||
|
(setq project-vc-extra-root-markers '("build.sh"))
|
||||||
|
|
||||||
|
(defvar my/project-search-dirs
|
||||||
|
'("~/Projects")
|
||||||
|
"Parent directories scanned for known projects.")
|
||||||
|
|
||||||
|
;;;; Project list: rebuilt from a fresh scan each startup
|
||||||
|
;; project-remember-project would APPEND to the persisted list and leak stale
|
||||||
|
;; entries (old paths, stdlib dirs, the config repo). Instead scan ~/Projects
|
||||||
|
;; and assign project--list directly; a real list also stops project.el
|
||||||
|
;; re-reading the stale file this session.
|
||||||
|
|
||||||
|
(defun my/scan-projects-under (parent)
|
||||||
|
"Return `project--list' entries for direct child projects of PARENT.
|
||||||
|
Ignore symlinks; don't recurse."
|
||||||
|
(when (file-directory-p parent)
|
||||||
|
(let (entries)
|
||||||
|
(dolist (dir (directory-files parent t directory-files-no-dot-files-regexp))
|
||||||
|
(when (and (file-directory-p dir)
|
||||||
|
(not (file-symlink-p dir)))
|
||||||
|
(let ((default-directory dir))
|
||||||
|
(when-let ((project (project-current nil)))
|
||||||
|
(push (list (abbreviate-file-name (project-root project)))
|
||||||
|
entries)))))
|
||||||
|
entries)))
|
||||||
|
|
||||||
|
(setq project--list
|
||||||
|
(sort (mapcan #'my/scan-projects-under
|
||||||
|
(mapcar #'expand-file-name my/project-search-dirs))
|
||||||
|
(lambda (a b)
|
||||||
|
(time-less-p
|
||||||
|
(file-attribute-modification-time (file-attributes (car b)))
|
||||||
|
(file-attribute-modification-time (file-attributes (car a)))))))
|
||||||
|
|
||||||
|
;; Persist so the on-disk file matches (no stale artifact left behind).
|
||||||
|
(project--write-project-list)
|
||||||
|
|
||||||
|
;;;; Sticky project detection
|
||||||
|
;; project.el recomputes the project from the visited buffer's directory, so
|
||||||
|
;; jumping into a dependency's VC checkout (via `gd') would make THAT the
|
||||||
|
;; project. Instead pin the last project under our roots and reuse it for any
|
||||||
|
;; buffer outside them.
|
||||||
|
|
||||||
|
(defvar my/pinned-project nil
|
||||||
|
"Last project resolved from a buffer under `my/project-search-dirs'.")
|
||||||
|
|
||||||
|
(defun my/own-project-dir-p (dir)
|
||||||
|
"Return non-nil when DIR lives under one of `my/project-search-dirs'."
|
||||||
|
(and dir
|
||||||
|
(seq-some (lambda (parent)
|
||||||
|
(file-in-directory-p dir (expand-file-name parent)))
|
||||||
|
my/project-search-dirs)))
|
||||||
|
|
||||||
|
(defun my/project-pinned (dir)
|
||||||
|
"Resolve DIR to the pinned project when DIR is outside our roots.
|
||||||
|
Runs ahead of `project-try-vc', so foreign buffers never become current."
|
||||||
|
(and my/pinned-project
|
||||||
|
(not (my/own-project-dir-p dir))
|
||||||
|
my/pinned-project))
|
||||||
|
|
||||||
|
(defun my/remember-pinned-project (project)
|
||||||
|
"Record PROJECT as the pin when the current buffer is one of ours."
|
||||||
|
(when (and project (my/own-project-dir-p default-directory))
|
||||||
|
(setq my/pinned-project project))
|
||||||
|
project)
|
||||||
|
|
||||||
|
(add-hook 'project-find-functions #'my/project-pinned) ; before project-try-vc
|
||||||
|
(advice-add 'project-current :filter-return #'my/remember-pinned-project)
|
||||||
|
|
||||||
|
;;;; Switching and cleanup
|
||||||
|
|
||||||
|
;; Skip the "what do you want to do" menu entirely.
|
||||||
|
(setq project-switch-commands #'project-dired)
|
||||||
|
|
||||||
|
;; Widen what counts as "belongs to this project" for cleanup.
|
||||||
|
(setq project-kill-buffer-conditions
|
||||||
|
'(buffer-file-name
|
||||||
|
(major-mode . compilation-mode)
|
||||||
|
(major-mode . dired-mode)
|
||||||
|
(major-mode . shell-mode)
|
||||||
|
(major-mode . eshell-mode)
|
||||||
|
(major-mode . term-mode)))
|
||||||
|
|
||||||
|
(defun my/project-switch-fresh ()
|
||||||
|
"Save and close the current project before switching."
|
||||||
|
(interactive)
|
||||||
|
(when (project-current nil)
|
||||||
|
(save-some-buffers t)
|
||||||
|
(project-kill-buffers t))
|
||||||
|
(call-interactively #'project-switch-project))
|
||||||
|
|
||||||
|
(global-set-key (kbd "C-c C-p") #'my/project-switch-fresh)
|
||||||
|
|
||||||
|
(with-eval-after-load 'which-key
|
||||||
|
(which-key-add-key-based-replacements
|
||||||
|
"C-c C-p" "switch project (fresh)"
|
||||||
|
"C-x p" "project"))
|
||||||
|
|
||||||
|
(provide 'init-project)
|
||||||
|
;;; init-project.el ends here
|
||||||
Executable
+21
@@ -0,0 +1,21 @@
|
|||||||
|
;;; init-theme.el --- Gruvbox, dark-hard variant -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;; gruvbox-dark-hard, with background/fringe/line-numbers flattened to
|
||||||
|
;; #111111 and the vertico posframe matched to it.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(use-package gruvbox-theme
|
||||||
|
:config
|
||||||
|
(load-theme 'gruvbox-dark-hard t)
|
||||||
|
(set-face-background 'vertico-posframe "#111111")
|
||||||
|
(set-face-background 'vertico-posframe-border "#6a9589"))
|
||||||
|
|
||||||
|
(set-face-attribute 'default nil :background "#111111")
|
||||||
|
(set-face-attribute 'fringe nil :background "#111111")
|
||||||
|
(set-face-attribute 'line-number nil :background "#111111")
|
||||||
|
(set-face-attribute 'line-number-current-line nil :background "#111111")
|
||||||
|
|
||||||
|
(provide 'init-theme)
|
||||||
|
;;; init-theme.el ends here
|
||||||
Reference in New Issue
Block a user