/ tools / emacs / nano.el
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))))