Created
February 4, 2025 18:48
-
-
Save rougier/8d5a712aa43e3cc69e7b0e325c84eab4 to your computer and use it in GitHub Desktop.
NANO Emacs (minimal version: 256 lines)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; nano-emacs.el --- NANO Emacs (minimal version) -*- lexical-binding: t -*- | |
;; Copyright (c) 2025 Nicolas P. Rougier | |
;; Released under the GNU General Public License 3.0 | |
;; Author: Nicolas P. Rougier <[email protected]> | |
;; URL: https://github.com/rougier/nano-emacs | |
;; This is NANO Emacs in 256 lines, without any dependency | |
;; Usage (command line): emacs -Q -l nano.el -[light|dark] | |
;; --- Speed benchmarking ----------------------------------------------------- | |
(setq init-start-time (current-time)) | |
;; --- Typography stack ------------------------------------------------------- | |
(set-face-attribute 'default nil | |
:height 140 :weight 'light :family "Roboto Mono") | |
(set-face-attribute 'bold nil :weight 'regular) | |
(set-face-attribute 'bold-italic nil :weight 'regular) | |
(set-display-table-slot standard-display-table 'truncation (make-glyph-code ?…)) | |
(set-display-table-slot standard-display-table 'wrap (make-glyph-code ?–)) | |
;; --- Frame / windows layout & behavior -------------------------------------- | |
(setq default-frame-alist | |
'((height . 44) (width . 81) (left-fringe . 0) (right-fringe . 0) | |
(internal-border-width . 32) (vertical-scroll-bars . nil) | |
(bottom-divider-width . 0) (right-divider-width . 0) | |
(undecorated-round . t))) | |
(modify-frame-parameters nil default-frame-alist) | |
(setq-default pop-up-windows nil) | |
;; --- Activate / Deactivate modes -------------------------------------------- | |
(tool-bar-mode -1) (menu-bar-mode -1) (blink-cursor-mode -1) | |
(global-hl-line-mode 1) (icomplete-vertical-mode 1) | |
(pixel-scroll-precision-mode 1) | |
;; --- Minimal NANO (not a real) theme ---------------------------------------- | |
(defface nano-default '((t)) "") (defface nano-default-i '((t)) "") | |
(defface nano-highlight '((t)) "") (defface nano-highlight-i '((t)) "") | |
(defface nano-subtle '((t)) "") (defface nano-subtle-i '((t)) "") | |
(defface nano-faded '((t)) "") (defface nano-faded-i '((t)) "") | |
(defface nano-salient '((t)) "") (defface nano-salient-i '((t)) "") | |
(defface nano-popout '((t)) "") (defface nano-popout-i '((t)) "") | |
(defface nano-strong '((t)) "") (defface nano-strong-i '((t)) "") | |
(defface nano-critical '((t)) "") (defface nano-critical-i '((t)) "") | |
(defun nano-set-face (name &optional foreground background weight) | |
"Set NAME and NAME-i faces with given FOREGROUND, BACKGROUND and WEIGHT" | |
(apply #'set-face-attribute `(,name nil | |
,@(when foreground `(:foreground ,foreground)) | |
,@(when background `(:background ,background)) | |
,@(when weight `(:weight ,weight)))) | |
(apply #'set-face-attribute `(,(intern (concat (symbol-name name) "-i")) nil | |
:foreground ,(face-background 'nano-default) | |
,@(when foreground `(:background ,foreground)) | |
:weight regular))) | |
(defun nano-link-face (sources faces &optional attributes) | |
"Make FACES to inherit from SOURCES faces and unspecify ATTRIBUTES." | |
(let ((attributes (or attributes | |
'( :foreground :background :family :weight | |
:height :slant :overline :underline :box)))) | |
(dolist (face (seq-filter #'facep faces)) | |
(dolist (attribute attributes) | |
(set-face-attribute face nil attribute 'unspecified)) | |
(set-face-attribute face nil :inherit sources)))) | |
(defun nano-install-theme () | |
"Install THEME" | |
(set-face-attribute 'default nil | |
:foreground (face-foreground 'nano-default) | |
:background (face-background 'nano-default)) | |
(dolist (item '((nano-default . (variable-pitch variable-pitch-text | |
fixed-pitch fixed-pitch-serif)) | |
(nano-highlight . (hl-line highlight)) | |
(nano-subtle . (match region | |
lazy-highlight widget-field)) | |
(nano-faded . (shadow | |
font-lock-comment-face | |
font-lock-doc-face | |
icomplete-section | |
completions-annotations)) | |
(nano-popout . (warning | |
font-lock-string-face)) | |
(nano-salient . (success link | |
help-argument-name | |
custom-visibility | |
font-lock-type-face | |
font-lock-keyword-face | |
font-lock-builtin-face | |
completions-common-part)) | |
(nano-strong . (font-lock-function-name-face | |
font-lock-variable-name-face | |
icomplete-first-match | |
minibuffer-prompt)) | |
(nano-critical . (error | |
completions-first-difference)) | |
(nano-faded-i . (help-key-binding)) | |
(nano-default-i . (custom-button-mouse | |
isearch)) | |
(nano-critical-i . (isearch-fail)) | |
((nano-subtle nano-strong) . (custom-button | |
icomplete-selected-match)) | |
((nano-faded-i nano-strong) . (show-paren-match)))) | |
(nano-link-face (car item) (cdr item))) | |
;; Mode & header lines | |
(set-face-attribute 'header-line nil | |
:background 'unspecified | |
:underline nil | |
:box `( :line-width 1 | |
:color ,(face-background 'nano-default)) | |
:inherit 'nano-subtle) | |
(set-face-attribute 'mode-line nil | |
:background (face-background 'default) | |
:underline (face-foreground 'nano-faded) | |
:height 40 :overline nil :box nil) | |
(set-face-attribute 'mode-line-inactive nil | |
:background (face-background 'default) | |
:underline (face-foreground 'nano-faded) | |
:height 40 :overline nil :box nil)) | |
(defun nano-light (&rest args) | |
"NANO light theme (based on material colors)" | |
(interactive) | |
(nano-set-face 'nano-default "#37474F" "#FFFFFF") ;; Blue Grey / L800 | |
(nano-set-face 'nano-strong "#000000" nil 'regular) ;; Black | |
(nano-set-face 'nano-highlight nil "#FAFAFA") ;; Very Light Grey | |
(nano-set-face 'nano-subtle nil "#ECEFF1") ;; Blue Grey / L50 | |
(nano-set-face 'nano-faded "#90A4AE") ;; Blue Grey / L300 | |
(nano-set-face 'nano-salient "#673AB7") ;; Deep Purple / L500 | |
(nano-set-face 'nano-popout "#FFAB91") ;; Deep Orange / L200 | |
(nano-set-face 'nano-critical "#FF6F00") ;; Amber / L900 | |
(nano-install-theme)) | |
(defun nano-dark (&rest args) | |
"NANO dark theme (based on nord colors)" | |
(interactive) | |
(nano-set-face 'nano-default "#ECEFF4" "#2E3440") ;; Snow Storm 3 | |
(nano-set-face 'nano-strong "#ECEFF4" nil 'regular) ;; Polar Night 0 | |
(nano-set-face 'nano-highlight nil "#3B4252") ;; Polar Night 1 | |
(nano-set-face 'nano-subtle nil "#434C5E") ;; Polar Night 2 | |
(nano-set-face 'nano-faded "#677691") ;; | |
(nano-set-face 'nano-salient "#81A1C1") ;; Frost 2 | |
(nano-set-face 'nano-popout "#D08770") ;; Aurora 1 | |
(nano-set-face 'nano-critical "#EBCB8B") ;; Aurora 2 | |
(nano-install-theme)) | |
;; --- Command line theme chooser --------------------------------------------- | |
(add-to-list 'command-switch-alist '("-dark" . nano-dark)) | |
(add-to-list 'command-switch-alist '("-light" . nano-light)) | |
(if (member "-dark" command-line-args) (nano-dark) (nano-light)) | |
;; --- Minibuffer completion -------------------------------------------------- | |
(setq tab-always-indent 'complete | |
icomplete-delay-completions-threshold 0 | |
icomplete-compute-delay 0 | |
icomplete-show-matches-on-no-input t | |
icomplete-hide-common-prefix nil | |
icomplete-prospects-height 9 | |
icomplete-separator " . " | |
icomplete-with-completion-tables t | |
icomplete-in-buffer t | |
icomplete-max-delay-chars 0 | |
icomplete-scroll t | |
resize-mini-windows 'grow-only | |
icomplete-matches-format nil) | |
(bind-key "TAB" #'icomplete-force-complete icomplete-minibuffer-map) | |
(bind-key "RET" #'icomplete-force-complete-and-exit icomplete-minibuffer-map) | |
;; --- Minimal key bindings --------------------------------------------------- | |
(defun nano-quit () | |
"Quit minibuffer from anywhere (code from Protesilaos Stavrou)" | |
(interactive) | |
(cond ((region-active-p) (keyboard-quit)) | |
((derived-mode-p 'completion-list-mode) (delete-completion-window)) | |
((> (minibuffer-depth) 0) (abort-recursive-edit)) | |
(t (keyboard-quit)))) | |
(defun nano-kill () | |
"Delete frame or kill emacs if there is only one frame left" | |
(interactive) | |
(condition-case nil | |
(delete-frame) | |
(error (save-buffers-kill-terminal)))) | |
(bind-key "C-x k" #'kill-current-buffer) | |
(bind-key "C-x C-c" #'nano-kill) | |
(bind-key "C-x C-r" #'recentf-open) | |
(bind-key "C-g" #'nano-quit) | |
(bind-key "M-n" #'make-frame) | |
(bind-key "C-z" nil) ;; No suspend frame | |
(bind-key "C-<wheel-up>" nil) ;; No text resize via mouse scroll | |
(bind-key "C-<wheel-down>" nil) ;; No text resize via mouse scroll | |
;; --- Sane settings ---------------------------------------------------------- | |
(set-default-coding-systems 'utf-8) | |
(setq-default indent-tabs-mode nil | |
ring-bell-function 'ignore | |
select-enable-clipboard t) | |
;; --- OSX Specific ----------------------------------------------------------- | |
(when (eq system-type 'darwin) | |
(select-frame-set-input-focus (selected-frame)) | |
(setq mac-option-modifier nil | |
ns-function-modifier 'super | |
mac-right-command-modifier 'hyper | |
mac-right-option-modifier 'alt | |
mac-command-modifier 'meta)) | |
;; --- Header & mode lines ---------------------------------------------------- | |
(setq-default mode-line-format "") | |
(setq-default header-line-format | |
'(:eval | |
(let ((prefix (cond (buffer-read-only '("RO" . nano-default-i)) | |
((buffer-modified-p) '("**" . nano-critical-i)) | |
(t '("RW" . nano-faded-i)))) | |
(mode (concat "(" (downcase (cond ((consp mode-name) (car mode-name)) | |
((stringp mode-name) mode-name) | |
(t "unknow"))) | |
" mode)")) | |
(coords (format-mode-line "%c:%l "))) | |
(list | |
(propertize " " 'face (cdr prefix) 'display '(raise -0.25)) | |
(propertize (car prefix) 'face (cdr prefix)) | |
(propertize " " 'face (cdr prefix) 'display '(raise +0.25)) | |
(propertize (format-mode-line " %b ") 'face 'nano-strong) | |
(propertize mode 'face 'header-line) | |
(propertize " " 'display `(space :align-to (- right ,(length coords)))) | |
(propertize coords 'face 'nano-faded))))) | |
;; --- Minibuffer setup ------------------------------------------------------- | |
(defun nano-minibuffer--setup () | |
(set-window-margins nil 3 0) | |
(let ((inhibit-read-only t)) | |
(add-text-properties (point-min) (+ (point-min) 1) | |
`(display ((margin left-margin) | |
,(format "# %s" (substring (minibuffer-prompt) 0 1)))))) | |
(setq truncate-lines t)) | |
(add-hook 'minibuffer-setup-hook #'nano-minibuffer--setup) | |
;; --- Speed benchmarking ----------------------------------------------------- | |
(let ((init-time (float-time (time-subtract (current-time) init-start-time))) | |
(total-time (string-to-number (emacs-init-time "%f")))) | |
(message (concat | |
(propertize "Startup time: " 'face 'bold) | |
(format "%.2fs " init-time) | |
(propertize (format "(+ %.2fs system time)" | |
(- total-time init-time)) 'face 'shadow)))) |
nano-emacs repository was the first version and over the years, I added different pieces that more or less extend the initial nano-emacs. There is now a nano-theme and nano-modeline on ELPA which are the main packages for nano emacs. You can skip other stuff. This gist is a full rewrite since I now know elisp a bit more. I'll create a branch and probably make it the main branch at some point.
If I understand correctly nano256.el could replace both original nano-emacs, nano-theme and nano-modeline ? Right ? That's quite impressive 👍
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I browsed your nano-* repositories. I am quite lost. I see this gist, I see many nano-related stuff, I see a nano-theme, a whole nano thing with almost any other nano repo inside and finaly your dotemacs. Can you explain what a user should use in order to test deeply ? Individual nano-repos ? The whole nano "distribution" ? Something else ?