nano.el
1 ;; nano-emacs.el --- NANO Emacs (minimal version) -*- lexical-binding: t -*- 2 3 ;; Copyright (c) 2025 Nicolas P. Rougier 4 ;; Released under the GNU General Public License 3.0 5 ;; Author: Nicolas P. Rougier <nicolas.rougier@inria.fr> 6 ;; URL: https://github.com/rougier/nano-emacs 7 8 ;; This is NANO Emacs in 256 lines, without any dependency 9 ;; Usage (command line): emacs -Q -l nano.el -[light|dark] 10 11 ;; --- Speed benchmarking ----------------------------------------------------- 12 (setq init-start-time (current-time)) 13 14 ;; --- Typography stack ------------------------------------------------------- 15 (set-face-attribute 'default nil 16 :height 140 :weight 'light :family "Roboto Mono") 17 (set-face-attribute 'bold nil :weight 'regular) 18 (set-face-attribute 'bold-italic nil :weight 'regular) 19 (set-display-table-slot standard-display-table 'truncation (make-glyph-code ?…)) 20 (set-display-table-slot standard-display-table 'wrap (make-glyph-code ?–)) 21 22 ;; --- Frame / windows layout & behavior -------------------------------------- 23 (setq default-frame-alist 24 '((height . 44) (width . 81) (left-fringe . 0) (right-fringe . 0) 25 (internal-border-width . 32) (vertical-scroll-bars . nil) 26 (bottom-divider-width . 0) (right-divider-width . 0) 27 (undecorated-round . t))) 28 (modify-frame-parameters nil default-frame-alist) 29 (setq-default pop-up-windows nil) 30 31 ;; --- Activate / Deactivate modes -------------------------------------------- 32 (tool-bar-mode -1) (menu-bar-mode -1) (blink-cursor-mode -1) 33 (global-hl-line-mode 1) (icomplete-vertical-mode 1) 34 (pixel-scroll-precision-mode 1) 35 36 ;; --- Minimal NANO (not a real) theme ---------------------------------------- 37 (defface nano-default '((t)) "") (defface nano-default-i '((t)) "") 38 (defface nano-highlight '((t)) "") (defface nano-highlight-i '((t)) "") 39 (defface nano-subtle '((t)) "") (defface nano-subtle-i '((t)) "") 40 (defface nano-faded '((t)) "") (defface nano-faded-i '((t)) "") 41 (defface nano-salient '((t)) "") (defface nano-salient-i '((t)) "") 42 (defface nano-popout '((t)) "") (defface nano-popout-i '((t)) "") 43 (defface nano-strong '((t)) "") (defface nano-strong-i '((t)) "") 44 (defface nano-critical '((t)) "") (defface nano-critical-i '((t)) "") 45 46 (defun nano-set-face (name &optional foreground background weight) 47 "Set NAME and NAME-i faces with given FOREGROUND, BACKGROUND and WEIGHT" 48 49 (apply #'set-face-attribute `(,name nil 50 ,@(when foreground `(:foreground ,foreground)) 51 ,@(when background `(:background ,background)) 52 ,@(when weight `(:weight ,weight)))) 53 (apply #'set-face-attribute `(,(intern (concat (symbol-name name) "-i")) nil 54 :foreground ,(face-background 'nano-default) 55 ,@(when foreground `(:background ,foreground)) 56 :weight regular))) 57 58 (defun nano-link-face (sources faces &optional attributes) 59 "Make FACES to inherit from SOURCES faces and unspecify ATTRIBUTES." 60 61 (let ((attributes (or attributes 62 '( :foreground :background :family :weight 63 :height :slant :overline :underline :box)))) 64 (dolist (face (seq-filter #'facep faces)) 65 (dolist (attribute attributes) 66 (set-face-attribute face nil attribute 'unspecified)) 67 (set-face-attribute face nil :inherit sources)))) 68 69 (defun nano-install-theme () 70 "Install THEME" 71 72 (set-face-attribute 'default nil 73 :foreground (face-foreground 'nano-default) 74 :background (face-background 'nano-default)) 75 (dolist (item '((nano-default . (variable-pitch variable-pitch-text 76 fixed-pitch fixed-pitch-serif)) 77 (nano-highlight . (hl-line highlight)) 78 (nano-subtle . (match region 79 lazy-highlight widget-field)) 80 (nano-faded . (shadow 81 font-lock-comment-face 82 font-lock-doc-face 83 icomplete-section 84 completions-annotations)) 85 (nano-popout . (warning 86 font-lock-string-face)) 87 (nano-salient . (success link 88 help-argument-name 89 custom-visibility 90 font-lock-type-face 91 font-lock-keyword-face 92 font-lock-builtin-face 93 completions-common-part)) 94 (nano-strong . (font-lock-function-name-face 95 font-lock-variable-name-face 96 icomplete-first-match 97 minibuffer-prompt)) 98 (nano-critical . (error 99 completions-first-difference)) 100 (nano-faded-i . (help-key-binding)) 101 (nano-default-i . (custom-button-mouse 102 isearch)) 103 (nano-critical-i . (isearch-fail)) 104 ((nano-subtle nano-strong) . (custom-button 105 icomplete-selected-match)) 106 ((nano-faded-i nano-strong) . (show-paren-match)))) 107 (nano-link-face (car item) (cdr item))) 108 109 ;; Mode & header lines 110 (set-face-attribute 'header-line nil 111 :background 'unspecified 112 :underline nil 113 :box `( :line-width 1 114 :color ,(face-background 'nano-default)) 115 :inherit 'nano-subtle) 116 (set-face-attribute 'mode-line nil 117 :background (face-background 'default) 118 :underline (face-foreground 'nano-faded) 119 :height 40 :overline nil :box nil) 120 (set-face-attribute 'mode-line-inactive nil 121 :background (face-background 'default) 122 :underline (face-foreground 'nano-faded) 123 :height 40 :overline nil :box nil)) 124 125 (defun nano-light (&rest args) 126 "NANO light theme (based on material colors)" 127 128 (interactive) 129 (nano-set-face 'nano-default "#37474F" "#FFFFFF") ;; Blue Grey / L800 130 (nano-set-face 'nano-strong "#000000" nil 'regular) ;; Black 131 (nano-set-face 'nano-highlight nil "#FAFAFA") ;; Very Light Grey 132 (nano-set-face 'nano-subtle nil "#ECEFF1") ;; Blue Grey / L50 133 (nano-set-face 'nano-faded "#90A4AE") ;; Blue Grey / L300 134 (nano-set-face 'nano-salient "#673AB7") ;; Deep Purple / L500 135 (nano-set-face 'nano-popout "#FFAB91") ;; Deep Orange / L200 136 (nano-set-face 'nano-critical "#FF6F00") ;; Amber / L900 137 (nano-install-theme)) 138 139 (defun nano-dark (&rest args) 140 "NANO dark theme (based on nord colors)" 141 142 (interactive) 143 (nano-set-face 'nano-default "#ECEFF4" "#2E3440") ;; Snow Storm 3 144 (nano-set-face 'nano-strong "#ECEFF4" nil 'regular) ;; Polar Night 0 145 (nano-set-face 'nano-highlight nil "#3B4252") ;; Polar Night 1 146 (nano-set-face 'nano-subtle nil "#434C5E") ;; Polar Night 2 147 (nano-set-face 'nano-faded "#677691") ;; 148 (nano-set-face 'nano-salient "#81A1C1") ;; Frost 2 149 (nano-set-face 'nano-popout "#D08770") ;; Aurora 1 150 (nano-set-face 'nano-critical "#EBCB8B") ;; Aurora 2 151 (nano-install-theme)) 152 153 ;; --- Command line theme chooser --------------------------------------------- 154 (add-to-list 'command-switch-alist '("-dark" . nano-dark)) 155 (add-to-list 'command-switch-alist '("-light" . nano-light)) 156 (if (member "-dark" command-line-args) (nano-dark) (nano-light)) 157 158 ;; --- Minibuffer completion -------------------------------------------------- 159 (setq tab-always-indent 'complete 160 icomplete-delay-completions-threshold 0 161 icomplete-compute-delay 0 162 icomplete-show-matches-on-no-input t 163 icomplete-hide-common-prefix nil 164 icomplete-prospects-height 9 165 icomplete-separator " . " 166 icomplete-with-completion-tables t 167 icomplete-in-buffer t 168 icomplete-max-delay-chars 0 169 icomplete-scroll t 170 resize-mini-windows 'grow-only 171 icomplete-matches-format nil) 172 (bind-key "TAB" #'icomplete-force-complete icomplete-minibuffer-map) 173 (bind-key "RET" #'icomplete-force-complete-and-exit icomplete-minibuffer-map) 174 175 ;; --- Minimal key bindings --------------------------------------------------- 176 (defun nano-quit () 177 "Quit minibuffer from anywhere (code from Protesilaos Stavrou)" 178 179 (interactive) 180 (cond ((region-active-p) (keyboard-quit)) 181 ((derived-mode-p 'completion-list-mode) (delete-completion-window)) 182 ((> (minibuffer-depth) 0) (abort-recursive-edit)) 183 (t (keyboard-quit)))) 184 185 (defun nano-kill () 186 "Delete frame or kill emacs if there is only one frame left" 187 188 (interactive) 189 (condition-case nil 190 (delete-frame) 191 (error (save-buffers-kill-terminal)))) 192 193 (bind-key "C-x k" #'kill-current-buffer) 194 (bind-key "C-x C-c" #'nano-kill) 195 (bind-key "C-x C-r" #'recentf-open) 196 (bind-key "C-g" #'nano-quit) 197 (bind-key "M-n" #'make-frame) 198 (bind-key "C-z" nil) ;; No suspend frame 199 (bind-key "C-<wheel-up>" nil) ;; No text resize via mouse scroll 200 (bind-key "C-<wheel-down>" nil) ;; No text resize via mouse scroll 201 202 ;; --- Sane settings ---------------------------------------------------------- 203 (set-default-coding-systems 'utf-8) 204 (setq-default indent-tabs-mode nil 205 ring-bell-function 'ignore 206 select-enable-clipboard t) 207 208 ;; --- OSX Specific ----------------------------------------------------------- 209 (when (eq system-type 'darwin) 210 (select-frame-set-input-focus (selected-frame)) 211 (setq mac-option-modifier nil 212 ns-function-modifier 'super 213 mac-right-command-modifier 'hyper 214 mac-right-option-modifier 'alt 215 mac-command-modifier 'meta)) 216 217 ;; --- Header & mode lines ---------------------------------------------------- 218 (setq-default mode-line-format "") 219 (setq-default header-line-format 220 '(:eval 221 (let ((prefix (cond (buffer-read-only '("RO" . nano-default-i)) 222 ((buffer-modified-p) '("**" . nano-critical-i)) 223 (t '("RW" . nano-faded-i)))) 224 (mode (concat "(" (downcase (cond ((consp mode-name) (car mode-name)) 225 ((stringp mode-name) mode-name) 226 (t "unknow"))) 227 " mode)")) 228 (coords (format-mode-line "%c:%l "))) 229 (list 230 (propertize " " 'face (cdr prefix) 'display '(raise -0.25)) 231 (propertize (car prefix) 'face (cdr prefix)) 232 (propertize " " 'face (cdr prefix) 'display '(raise +0.25)) 233 (propertize (format-mode-line " %b ") 'face 'nano-strong) 234 (propertize mode 'face 'header-line) 235 (propertize " " 'display `(space :align-to (- right ,(length coords)))) 236 (propertize coords 'face 'nano-faded))))) 237 238 ;; --- Minibuffer setup ------------------------------------------------------- 239 (defun nano-minibuffer--setup () 240 (set-window-margins nil 3 0) 241 (let ((inhibit-read-only t)) 242 (add-text-properties (point-min) (+ (point-min) 1) 243 `(display ((margin left-margin) 244 ,(format "# %s" (substring (minibuffer-prompt) 0 1)))))) 245 (setq truncate-lines t)) 246 (add-hook 'minibuffer-setup-hook #'nano-minibuffer--setup) 247 248 ;; --- Speed benchmarking ----------------------------------------------------- 249 (let ((init-time (float-time (time-subtract (current-time) init-start-time))) 250 (total-time (string-to-number (emacs-init-time "%f")))) 251 (message (concat 252 (propertize "Startup time: " 'face 'bold) 253 (format "%.2fs " init-time) 254 (propertize (format "(+ %.2fs system time)" 255 (- total-time init-time)) 'face 'shadow))))