From: rush Date: Tue, 30 Jun 2026 15:55:19 +0000 (-0400) Subject: Move away from literate config X-Git-Url: https://git.earman.xyz/?a=commitdiff_plain;h=1a4fde72d1903ff9a9043f0eea759cf99a72b3ef;p=emacsinit.git Move away from literate config Tangle config.org into init.el, update comment levels, add page-breaks --- diff --git a/config.org b/config.org deleted file mode 100644 index 99c2328..0000000 --- a/config.org +++ /dev/null @@ -1,1424 +0,0 @@ -#+TITLE: Emacs literate config -#+PROPERTY: header-args :comments org :tangle yes - -* Header -Set up the correct lexical binding to avoid screaming compiler -#+BEGIN_SRC emacs-lisp :comments no -;;; config.el --- Emacs Configuration -*- lexical-binding: t; -*- -#+END_SRC -* OS specific stuff -#+BEGIN_SRC emacs-lisp -(setq w32-recognize-altgr 'nil) - -(unless (equal system-type 'windows-nt) - (setq trash-directory "~/Trash/") - (defun doas () - "Use TRAMP to reopen the current buffer as root using doas." - (interactive) - (when buffer-file-name - (find-alternate-file (concat "/doas:root@localhost:" buffer-file-name)))) - (defun sudo () - "Use TRAMP to reopen the current buffer as root using sudo." - (interactive) - (when buffer-file-name - (find-alternate-file (concat "/sudo:root@localhost:" buffer-file-name))))) -#+END_SRC -* Packages -** Add melpa, initialize -#+BEGIN_SRC emacs-lisp -(require 'package) - -(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) -(package-initialize) -#+END_SRC -** Install all packages that are not included -#+BEGIN_SRC emacs-lisp -(defvar *re/packages* - '(corfu - expand-region - page-break-lines - pdf-tools - auctex - csv-mode - markdown-mode - edit-indirect - sly - geiser - geiser-chicken - forth-mode - trashed - org-transclusion - vc-jj - minions)) - -(dolist (pkg *re/packages*) - (unless (package-installed-p pkg) - (package-refresh-contents) - (package-install pkg))) -#+END_SRC -** Corfu -#+BEGIN_SRC emacs-lisp -(require 'corfu) - -(setq corfu-auto t - corfu-auto-delay 0 - corfu-auto-prefix 1) - -(add-hook 'corfu-mode-hook #'corfu-popupinfo-mode) - -(setq corfu-popupinfo-delay '(1.0 . 0.01) ; start after one second, fast refresh once on - corfu-popupinfo-hide nil) - -(global-corfu-mode) -#+END_SRC -** Expand Region -#+BEGIN_SRC emacs-lisp -(require 'expand-region) - -(keymap-global-set "C-=" #'er/expand-region) -#+END_SRC -** Page Break Lines -#+BEGIN_SRC emacs-lisp -(require 'page-break-lines) -(global-page-break-lines-mode) -#+END_SRC -** PDFtools -Be sure to run =pdf-tools-install= before first run -#+BEGIN_SRC emacs-lisp -(add-to-list 'auto-mode-alist '("\\.pdf\\'" . pdf-tools-install)) - -(setq-default pdf-view-display-size 'fit-page) -#+END_SRC -** Auctex -#+BEGIN_SRC emacs-lisp -(setq-default TeX-master nil) - -(setq TeX-auto-save t - TeX-parse-self t - TeX-PDF-mode t - TeX-view-program-selection '((output-pdf "PDF Tools")) - TeX-source-correlate-start-server t) - -(add-hook 'TeX-after-compilation-finished-functions - #'TeX-revert-document-buffer) -#+END_SRC -** CSV Mode -#+BEGIN_SRC emacs-lisp -(add-hook 'csv-mode-hook 'csv-align-mode) -#+END_SRC -** Markdown Mode -Make markdown act more like org when I'm forced to use it -#+BEGIN_SRC emacs-lisp -(setq markdown-special-ctrl-a/e t) - -(with-eval-after-load 'markdown-mode - (keymap-set markdown-mode-map "M-" #'markdown-demote) - (keymap-set markdown-mode-map "M-" #'markdown-promote) - (keymap-set markdown-mode-map "M-" #'markdown-move-up) - (keymap-set markdown-mode-map "M-" #'markdown-move-down) - (keymap-set markdown-mode-map "C-M-u" #'markdown-up-heading) - (keymap-set markdown-mode-map "C-M-b" #'markdown-outline-previous-same-level) - (keymap-set markdown-mode-map "C-M-f" #'markdown-outline-next-same-level) - (keymap-set markdown-mode-map "C-M-p" #'markdown-outline-previous) - (keymap-set markdown-mode-map "C-M-n" #'markdown-outline-next)) -#+END_SRC -** Sly -#+BEGIN_SRC emacs-lisp -(defun re/sly-repl-new-frame (&optional arg) - "Start a SLY repl in a new frame." - (interactive "P") - (re/open-with-new-frame "SLY" #'sly)) - -(defun re/sly-quit-current-lisp () - "Quit the current lisp repl connection without prompting." - (interactive) - (sly-quit-lisp nil nil)) - -(defun re/sly-connect-stump (&optional arg) - "Connect to my typical stumpwm instance." - (interactive "P") - (sly-connect "localhost" 4004)) - -(setq inferior-lisp-program "sbcl" - sly-net-coding-system 'utf-8-unix - sly-command-switch-to-existing-lisp 'always) - -(let ((core (expand-file-name "~/sbcl.core-for-sly"))) - (when (file-exists-p core) - (setq sly-lisp-implementations (list `(sbcl ("sbcl" "--core" ,core)))))) - -(with-eval-after-load 'sly-mrepl - (keymap-set sly-mrepl-mode-map ")" (re/send-on-close-paren #'sly-mrepl-return)) - (keymap-set sly-mrepl-mode-map "]" (re/balance-and-eval-sexp #'sly-mrepl-return)) - (keymap-set sly-mrepl-mode-map "C-c q" #'re/sly-quit-current-lisp)) -#+END_SRC -*** Eldoc settings -Sly really doesn't play well with elodc in the minibuffer, so here are some settings to make it better. -#+BEGIN_SRC emacs-lisp -(add-to-list 'display-buffer-alist - '("^\\*eldoc" display-buffer-in-direction - (direction . 'down) - (window-height . 4))) -(keymap-global-set "C-c d" #'eldoc) ; force eldoc to show in minibuffer -(keymap-global-set "C-c D" #'eldoc-doc-buffer) ; open eldoc buffer when it's getting spammed -#+END_SRC -** Geiser -#+BEGIN_SRC emacs-lisp -(if (equal system-type 'windows-nt) - (setq geiser-chicken-binary '("C:/tools/chicken/bin/csi.exe" "-:c")) - (setq geiser-chicken-binary "csi -:c")) - -(setq scheme-program-name geiser-chicken-binary - geiser-active-implementations '(chicken)) - -(add-hook 'scheme-mode-hook 'geiser-mode) - -(with-eval-after-load 'geiser-repl - (keymap-set geiser-repl-mode-map ")" (re/send-on-close-paren #'geiser-repl-maybe-send)) - (keymap-set geiser-repl-mode-map "]" (re/balance-and-eval-sexp #'geiser-repl-maybe-send))) -#+END_SRC -** ORG -*** Set bindings, things that have to happen after loading -#+BEGIN_SRC emacs-lisp -(with-eval-after-load 'org - (require 'org-mouse) - (add-to-list 'org-export-backends 'md) - (keymap-set org-mode-map "C-M-h" #'org-mark-subtree) - (keymap-set org-mode-map "C-M-u" #'org-up-element) - (keymap-set org-mode-map "C-M-d" #'org-down-element) - (keymap-set org-mode-map "C-M-b" #'org-backward-heading-same-level) - (keymap-set org-mode-map "C-M-f" #'org-forward-heading-same-level) - (keymap-set org-mode-map "C-M-p" #'org-previous-visible-heading) - (keymap-set org-mode-map "C-M-n" #'org-next-visible-heading) - (keymap-set org-mode-map "C-c t" #'org-transclusion-mode)) - -(keymap-global-set "C-c l" #'org-store-link) -(keymap-global-set "C-c a" #'org-agenda) -(keymap-global-set "C-c c" #'org-capture) -#+END_SRC -*** General options -#+BEGIN_SRC emacs-lisp -(setq org-M-RET-may-split-line nil - org-cycle-inline-images-display t - org-cycle-separator-lines -1 - org-ellipsis "->" - org-enforce-todo-checkbox-dependencies t - org-enforce-todo-dependencies t - org-goto-interface 'outline-path-completion - org-outline-path-complete-in-steps nil - org-src-fontify-natively nil - org-return-follows-link t - org-reverse-note-order t - org-special-ctrl-a/e t - org-special-ctrl-k t - org-src-preserve-indentation t - org-startup-folded 'fold - org-tags-column 0 - org-yank-adjusted-subtrees t - org-refile-use-outline-path 'file - org-refile-allow-creating-parent-nodes 'confirm) -#+END_SRC -**** Testing without these setq options -#+BEGIN_SRC emacs-lisp :tangle no - org-fontify-done-headline nil - org-goto-auto-isearch nil - org-list-allow-alphabetical t - org-src-window-setup 'current-window) -#+END_SRC -*** Time Clocking -#+BEGIN_SRC emacs-lisp :tangle no -(org-clock-persistence-insinuate) -(setq org-clock-in-resume t - org-clock-out-remove-zero-time-clocks t - org-clock-persist t - org-clock-report-include-clocking-task t - org-time-stamp-rounding-minutes '(1 1)) -#+END_SRC -*** Agenda and Capture -**** Agenda settings -#+BEGIN_SRC emacs-lisp - (setq org-agenda-files (append '("~/org/") - (file-expand-wildcards "~/projects/*/")) - org-refile-targets '((nil :maxlevel . 9) - (org-agenda-files :maxlevel . 9)) - org-todo-keywords '((sequence "TODO(t)" "WAITING(w@)" "|" "DONE(d)" "BELAYED(b@)" "GIVEN(g@)")) - org-todo-keyword-faces '(("WAITING" :foreground "#e85400" :weight bold)) ; alert orange - org-agenda-prefix-format '((agenda . " %?-12t%? s") - (todo . " ") - (tags . " ") - (search . " ")) - org-agenda-start-on-weekday nil - org-read-date-prefer-future 'time - org-log-done 'time - org-log-redeadline 'time - org-log-reschedule 'time - org-log-state-notes-insert-after-drawers t - org-agenda-window-setup 'current-window) -#+END_SRC -***** Testing without these options -#+BEGIN_SRC emacs-lisp :tangle no - org-tag-alist '((:startgroup) - ("call" . ?c) - ("net" . ?n) - (:endgroup) - (:startgroup) - ("home" . ?h) - ("office" . ?o) - ("errand" . ?e) - ("vps" . ?v) - (:endgroup)) - org-agenda-span 'day - org-agenda-restore-windows-after-quit t - org-agenda-time-grid '((daily today require-timed) - (800 1000 1200 1400 1600 1800 2000) - " -----" - "") - org-agenda-scheduled-leaders '("Booked: " "Booked %dd. ago: ") - org-agenda-deadline-leaders '("Deadline: " "In %dd.: " "Died %dd. ago: ") - org-agenda-start-with-log-mode t - org-agenda-log-mode-items '(closed clock state)) -#+END_SRC -**** Stuck Projects -#+BEGIN_SRC emacs-lisp -(setq org-stuck-projects '("CATEGORY=\"undertakings\"+LEVEL=1/-DONE-GIVEN-BELAYED" ("TODO" "WAITING") nil "")) -#+END_SRC -**** Custom Agendas -#+BEGIN_SRC emacs-lisp - (defvar *re/org-agenda-inbox* - '(tags "CATEGORY=\"inbox\"" ((org-agenda-overriding-header "")))) - - (defvar *re/org-agenda-todo* - '(tags-todo "-recurring/TODO" ((org-agenda-overriding-header "")))) - - (defvar *re/org-agenda-undertakings* - '(tags "CATEGORY=\"undertakings\"+LEVEL=1" ((org-agenda-overriding-header "")))) - - (defvar *re/org-agenda-waiting* - '(todo "WAITING" ((org-agenda-overriding-header "")))) - -(defvar *re/org-agenda-done* - '(tags "-recurring+LEVEL=1/DONE|BELAYED|GIVEN")) - - (setq org-agenda-custom-commands `(("i" "Inbox" ,@*re/org-agenda-inbox*) - ("n" "Next Actions" ,@*re/org-agenda-todo*) - ("u" "Undertakings" ,@*re/org-agenda-undertakings*) - ("w" "Waiting" ,@*re/org-agenda-waiting*) - ("d" "Done" ,@*re/org-agenda-done*))) -#+END_SRC -**** Capture Templates -#+BEGIN_SRC emacs-lisp - (setq org-capture-templates '(("i" "Inbox" entry - (file "inbox.org") - "* %^{Description}\n%U\n%?" - :prepend t) - ("n" "Next Action" entry - (file "next.org") - "* TODO %^{Description}\nTaken: %T\n%?" - :prepend t) - ("u" "Undertaking" entry - (file "undertakings.org") - "* %^{Description} [/]\nTaken: %T\n%?" - :prepend t) - ("m" "Meeting" entry - (file "meetings.org") - "* %^{Description}\n%^{Scheduled:}T\n%?" - :prepend t) - ("p" "Phone Call" entry - (file "inbox.org") - "* %^{Description} :call:\n%T\n%?" - :prepend t) - ("t" "Note" entry - (file "notes.org") - "* %^{Description}\n%U\n%?" - :prepend t))) -#+END_SRC -*** IDs -#+BEGIN_SRC emacs-lisp -(setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id - org-id-method 'ts - org-clone-delete-id t - org-attach-id-to-path-function-list '(org-attach-id-ts-folder-format - org-attach-id-uuid-folder-format)) - -(defun re/org-save-all () - "Save all `org-agenda-files' without user confirmation." - (interactive) - (message "Saving all org-agenda-files buffers...") - (save-some-buffers t - (lambda () - (when (member (buffer-file-name) org-agenda-files) t))) - (message "Saving all org-agenda-files buffers... done")) - -(advice-add 'org-refile :after (lambda (&rest _) (re/org-save-all))) -#+END_SRC -*** Archiving -#+BEGIN_SRC emacs-lisp -(setq org-archive-location (concat "~/org/old.org_keep::datetree/") - org-agenda-text-search-extra-files '(agenda-archives)) - -(add-to-list 'auto-mode-alist '("\\.org_keep\\'" . org-mode)) -#+END_SRC -*** Converting markdown to org -#+BEGIN_SRC emacs-lisp -(defun re/markdown-to-org-region (start end) - "Convert Markdown formatted text in region (START, END) to Org. -This command requires that pandoc (man page `pandoc(1)') be -installed." - (interactive "r") - (shell-command-on-region - start end - "pandoc -f markdown -t org --wrap=preserve" t t)) -#+END_SRC -*** Converting org to markdown -#+BEGIN_SRC emacs-lisp -(defun re/org-to-markdown-region (start end) - "Convert Org formatted text in region (START, END) to Markdown. -This command requires that pandoc (man page `pandoc(1)') be -installed." - (interactive "r") - (shell-command-on-region - start end - "pandoc -f org -t markdown --wrap=preserve" t t)) -#+END_SRC -** Trashed -Shows the trash file as a dired buffer -#+BEGIN_SRC emacs-lisp -(setq trashed-action-confirmer 'y-or-n-p - trashed-use-header-line t - trashed-sort-key '("Date deleted" . t) - trashed-date-format "%Y-%m-%d %H:%M:%S") -#+END_SRC -** Use mu4e on my home pcs -#+BEGIN_SRC emacs-lisp -(when (eq system-type 'gnu/linux) - (with-eval-after-load 'mu4e - (setq mail-user-agent 'message-user-agent - message-send-mail-function 'smtpmail-send-it - message-citation-line-format "Quoth %f :\n" - smtpmail-default-smtp-server "mail.earman.xyz" - smtpmail-smtp-server "mail.earman.xyz" - smtpmail-smtp-service 587 - smtpmail-stream-type 'starttls - smtpmail-local-domain "earman.xyz" - smtpmail-queue-mail nil - smtpmail-queue-dir "~/mail/Outbox/cur" - user-mail-address "iv@earman.xyz" - user-full-name "Rush N. Earman IV" - message-signature "Rush Earman IV, PE" - message-kill-buffer-on-exit t - mu4e-get-mail-command "mbsync -a" - mu4e-update-interval (* 10 60) - mu4e-change-filenames-when-moving t - mu4e-use-fancy-chars t - mu4e-attachment-dir "~/downloads" - mu4e-compose-reply-to-address "iv@earman.xyz" - mu4e-maildir "~/mail" - mu4e-drafts-folder "/Drafts" - mu4e-sent-folder "/Sent" - mu4e-trash-folder "/Trash" - mu4e-maildir-shortcuts '((:maildir "/Inbox" :key ?i) - (:maildir "/Sent" :key ?s) - (:maildir "/Trash" :key ?t) - (:maildir "/Drafts" :key ?d))))) -#+END_SRC -* Load libraries -Libraries Not from the package manager -#+BEGIN_SRC emacs-lisp -(dolist (path '("sedit-mouse/" "themes/")) - (add-to-list 'load-path (concat user-emacs-directory path))) - -(dolist (file '(eng-paper-theme sedit-mouse)) - (require file)) -#+END_SRC -* UNIX/vi-like bindings -=C-h=, =C-w=, =C-u=, et. al to work as I expect (like acme). Some functions get re-bound when these take over a default. -#+BEGIN_SRC emacs-lisp -(defun re/open-line-below (n) - "Creates a new empty line below the current line and moves to it." - (interactive "*p") - (end-of-line) - (open-line n) - (next-line 1) - (indent-for-tab-command)) - -(keymap-global-set "C-o" #'re/open-line-below) - -(defun re/open-line-above (n) - "Creates a new empty line above the current line." - (interactive "*p") - (beginning-of-line) - (open-line n) - (indent-for-tab-command)) - -(keymap-global-set "M-o" #'re/open-line-above) - -(defun re/kill-bword-or-region (&optional arg) - "Kill region if active, otherwise kill back one word." - (interactive "p") - (if (use-region-p) - (kill-region (region-beginning) (region-end)) - (backward-kill-word arg))) - -(keymap-global-set "C-w" #'re/kill-bword-or-region) -(keymap-global-set "C-u" (lambda () (interactive) (kill-line 0))) ; kill-line backwards -(keymap-global-set "C-S-U" #'universal-argument) -(keymap-set key-translation-map "C-h" "") ; Rather F1 for help, also C-S-H works -(keymap-global-set "M-j" (lambda () (interactive) (join-line 0))) ; join-line other way -(keymap-global-set "" #'beginning-of-buffer) -(keymap-global-set "" #'end-of-buffer) -#+END_SRC -* Commenting and defun-level bindings -#+BEGIN_SRC emacs-lisp -(defun re/comment-dwim (&optional arg) - "Comment region if active, otherwise comment line. - Stolen from https://depp.brause.cc/dotemacs" - (interactive "P") - (if (use-region-p) - (comment-or-uncomment-region (region-beginning) (region-end)) - (comment-line arg))) - -(keymap-global-set "M-;" #'re/comment-dwim) - -(defun re/comment-defun (&optional arg) - "Mark a defun and comment it out. - Return to initial position after. Arg works on `mark-defun'." - (interactive "P") - (save-excursion (mark-defun arg) - (comment-region (region-beginning) (region-end)))) - -(keymap-global-set "C-M-;" #'re/comment-defun) - -(defun re/kill-defun (&optional arg) - "Mark a defun and kill it. - Arg works on `mark-defun'." - (interactive "P") - (mark-defun arg) - (kill-region (region-beginning) (region-end))) - -(keymap-global-set "C-M-S-K" #'re/kill-defun) -#+END_SRC -* Misc QOL bindings -#+BEGIN_SRC emacs-lisp -(keymap-global-set "" nil) ; I keep toggliŋ overwrite-mode haply -(keymap-global-set "C-\\" #'undo-redo) ; issues with C-? sometimes -(keymap-global-set "C-S-K" #'kill-whole-line) -(keymap-global-set "C-S-d" #'delete-pair) -(keymap-global-set "C-z" #'zap-up-to-char) -(keymap-global-set "C-c s" #'sort-lines) -(keymap-global-set " " #'capitalize-dwim) ; M-c -(keymap-global-set " " #'downcase-dwim) ; M-l -(keymap-global-set " " #'upcase-dwim) ; M-u -#+END_SRC -* Tabs -I want *TABS*, not spaces, dammit. Globally overwrite the tab key to insert the tab character. This makes it so that many things (completion) will have to be triggered with =C-i= instead, but I don't think it's necessarily bad to have things like that on a control combo instead of overloading the tab key at the expense of being able to actually insert tabs. -#+BEGIN_SRC emacs-lisp -;; Tabs -(setq-default indent-tabs-mode t - tab-width 8 - backward-delete-char-untabify-method nil) - -(advice-add 'indent-to :around - (lambda (orig-fun column &rest args) - (when indent-tabs-mode - (setq column (* tab-width (round column tab-width)))) - (apply orig-fun column args))) - -(dolist (indent-level '(c-basic-offset cperl-indent-level LaTeX-indent-level)) - (defvaralias indent-level 'tab-width)) - -(defun Tab (width) - "Give me a function analagous to the Acme `Tab' command" - (interactive) - (setq-local tab-width width)) - -(keymap-global-set "" (lambda () (interactive) (insert "\t"))) -#+END_SRC -* Buffer dealiŋ -#+BEGIN_SRC emacs-lisp -(setq-default indicate-empty-lines t - truncate-lines t - fill-column 78) - -(dolist (hook '(text-mode-hook LaTeX-mode-hook)) - (add-hook hook #'visual-line-mode)) - -(blink-cursor-mode -1) -(delete-selection-mode t) - -(setq delete-by-moving-to-trash t - delete-pair-blink-delay 0 - use-short-answers t - use-file-dialog nil - echo-keystrokes 0.1 - eldoc-idle-delay 0 - read-buffer-completion-ignore-case t - history-delete-duplicates t - disabled-command-function nil - save-interprogram-paste-before-kill t - confirm-kill-processes nil) - -(keymap-global-set "C-x M-f" #'find-file-at-point) -(keymap-global-set "C-x C-b" #'buffer-menu) -(keymap-global-set "C-x M-b" #'buffer-menu-other-window) -(keymap-global-set "C-x k" #'kill-current-buffer) -(keymap-global-set "C-x K" #'kill-buffer) -#+END_SRC -* Recentering and page-by-page navigation -#+BEGIN_SRC emacs-lisp -(setq recenter-positions '(2 middle -3)) - -(defun re/recenter-advice (&rest _) - "Recenter to page start." - (when (called-interactively-p 'any) (recenter 2))) - -(dolist (binding '(backward-page forward-page)) - (advice-add binding :after #'re/recenter-advice)) - -(keymap-global-set "C-x C-n" #'forward-page) -(keymap-global-set "C-x C-p" #'backward-page) -(keymap-global-set "" #'forward-page) -(keymap-global-set "" #'backward-page) -#+END_SRC -* Macro to open something in its own named frame -Start a repl in a new frame, open dired in a new frame, etc. etc. -#+BEGIN_SRC emacs-lisp -(defmacro re/open-with-new-frame (frname function) - "Run the function in its own named frame." - `(progn - (select-frame (make-frame '((name . ,(eval frname))))) - (call-interactively ,function))) -#+END_SRC -* Scratch buffer easy access -#+BEGIN_SRC emacs-lisp -(defun re/scratch-only () - "Bring up the scratch buffer as the only visible buffer." - (interactive) - (scratch-buffer) - (delete-other-windows)) - -(keymap-global-set "" #'scratch-buffer) -(keymap-global-set "S-" #'re/scratch-only) -#+END_SRC -* Switch or split window -Game changer. Auto choose how to split, or just switch to the next, all on the same binding. Ripped from an early version of the lem editor. -#+BEGIN_SRC emacs-lisp -(defun re/other-window-or-split-window (&optional window) - "Go to other window or split sensibly." - (interactive) - (if (= (count-windows) 1) - (funcall split-window-preferred-function window) - (other-window 1))) - -(keymap-global-set "C-M-o" #'re/other-window-or-split-window) -#+END_SRC -* Smart End/Beginning of line -Similar to what org does, and indeed calls the org function if in org-mode -#+BEGIN_SRC emacs-lisp -(defun re/smart-beginning-of-line () - "Move point to first non-whitespace character or `beginning-of-visual-line'. -If point was already at that position, call `back-to-indentation'. -Called a third time, move point back to `beginning-of-line'. -If in org-mode, call `org-beginning-of-line' first." - (interactive "^") - (let ((oldpos (point))) - (if (equal major-mode 'org-mode) - (org-beginning-of-line) - (progn - (when visual-line-mode (beginning-of-visual-line 1)) - (skip-syntax-forward " " (line-end-position)) - (backward-prefix-chars))) - (and (= oldpos (point)) - (let ((newpos (point))) - (back-to-indentation) - (and (= newpos (point)) (beginning-of-line)))))) - -(keymap-global-set " " #'re/smart-beginning-of-line) -(keymap-set visual-line-mode-map "C-a" #'re/smart-beginning-of-line) - -(defun re/smart-end-of-line () - "Move point to the end of the visual line. - If point was already at that position, call `move-end-of-line'. -If in org-mode, call `org-end-of-line' first." - (interactive "^") - (let ((oldpos (point))) - (if (equal major-mode 'org-mode) - (org-end-of-line) - (when visual-line-mode - (end-of-visual-line 1))) - (and (= oldpos (point)) (move-end-of-line nil)))) - -(keymap-global-set " " #'re/smart-end-of-line) -(keymap-set visual-line-mode-map "C-e" #'re/smart-end-of-line) -#+END_SRC -* Goto line with numbers -I only care about line numbers when I want to jump, so only show them then -#+BEGIN_SRC emacs-lisp -(defun re/goto-line () - "Show line numbers before going to line, then hide them again." - (interactive) - (display-line-numbers-mode 1) - (call-interactively 'goto-line) - (display-line-numbers-mode -1)) - -(keymap-global-set " " #'re/goto-line) -#+END_SRC -* Switching between buffers -#+BEGIN_SRC emacs-lisp -(keymap-global-set "C-." #'next-buffer) -(keymap-global-set "C-," #'previous-buffer) -(keymap-global-set "C-" (lambda () (interactive) (switch-to-buffer nil))) ; toggle last two buffers -(keymap-global-set "" #'previous-buffer) ; Back button -(keymap-global-set "" #'next-buffer) ; Forward button -#+END_SRC -* Disable M-mouse for window manager bindings -#+BEGIN_SRC emacs-lisp -(keymap-global-unset "M-") -(keymap-global-unset "M-") -(keymap-global-unset "M-") -(keymap-global-unset "M-") -(keymap-global-unset "M-") -#+END_SRC -* Search -#+BEGIN_SRC emacs-lisp -;; Search -(defun re/add-region-to-search-ring () - (when (use-region-p) - (add-to-history 'search-ring - (buffer-substring (region-beginning) (region-end))) - (deactivate-mark))) - -(defun re/isearch-forward-use-region () - "Search text in region if active, otherwise normal forward search." - (interactive) - (re/add-region-to-search-ring) - (isearch-forward)) - -(keymap-global-set "C-s" #'re/isearch-forward-use-region) - -(defun re/isearch-backward-use-region () - "Search text in region if active, otherwise normal backward search." - (interactive) - (re/add-region-to-search-ring) - (isearch-backward)) - -(keymap-global-set "C-r" #'re/isearch-backward-use-region) - -(defun re/exchange-point-and-mark-maybe-activate (arg) - "Call `exchange-point-and-mark' but don't activate the region. -Stolen from https://www.masteringemacs.org/article/fixing-mark-commands-transient-mark-mode" - (interactive "P") - (exchange-point-and-mark (if (and transient-mark-mode (not mark-active)) - (not arg) - arg))) - -(keymap-global-set " " #'re/exchange-point-and-mark-maybe-activate) - -(keymap-global-set "M-s ." #'isearch-forward-thing-at-point) -(keymap-global-set "M-s M-." #'isearch-forward-symbol-at-point) -(keymap-global-set "M-%" #'replace-regexp) -(keymap-global-set "C-%" #'replace-string) -(keymap-global-set "C-M-S-R" #'replace-regexp-as-diff) -(keymap-global-set "C-S-S" #'isearch-forward-regexp) -(keymap-global-set "C-S-R" #'isearch-backward-regexp) -#+END_SRC -* Recent Files/Buffers -#+BEGIN_SRC emacs-lisp -(midnight-mode t) -(setq clean-buffer-list-delay-general 1) -(dolist (never-re '("\\` \\*tramp/.*\\'" - "\\` \\*eshell.*\\'")) - (add-to-list 'clean-buffer-list-kill-never-regexps never-re)) -(save-place-mode 1) -(recentf-mode 1) -#+END_SRC -* File Handling -#+BEGIN_SRC emacs-lisp -(defun re/rename-current-buffer-file nil - "Renames the current buffer and the file it is visiting." - (interactive) - (let ((name (buffer-name)) - (filename (buffer-file-name))) - (if (not (and filename (file-exists-p filename))) - (error "Buffer '%s' is not visiting a file!" name) - (let ((new-name (read-file-name "New name: " filename))) - (if (get-buffer new-name) - (error "A buffer named '%s' already exists!" new-name) - (rename-file filename new-name 1) - (rename-buffer new-name) - (set-visited-file-name new-name) - (set-buffer-modified-p nil) - (message "File '%s' successfully renamed to '%s'" - name - (file-name-nondirectory new-name))))))) - -(keymap-global-set "C-x C-r" #'re/rename-current-buffer-file) - -(defun re/get-file-modified-time (filepath) - (interactive) - (format-time-string "%Y-%m-%d-%H%M" - (file-attribute-modification-time (file-attributes filepath)))) - -(defun re/filename-inject-mod-time (filepath) - (interactive) - (concat (file-name-sans-extension filepath) - "_" - (re/get-file-modified-time filepath) - (file-name-extension filepath t))) - -(defun re/copy-with-mod-time (&optional filepath) - (interactive) - (let ((filepath (if (eq major-mode 'dired-mode) - (dired-get-filename t) - (when (null filepath) - (read-file-name "File to copy with datestring: " - default-directory - nil - t - nil))))) - (copy-file filepath (re/filename-inject-mod-time filepath ) nil t t t))) -#+END_SRC -* Dired -#+BEGIN_SRC emacs-lisp -(defun re/dired-display-direction () - "In dired mode, visit the file at the cursor in the right/below/left/above window." - (interactive) - (let* ((file-or-dir (dired-get-file-for-visit)) ;; get the file at cursor - (buffer (find-file-noselect file-or-dir))) ;; load the file into a buffer - (let ((window ;; figure out the window to use - (cond ((get-buffer-window buffer (selected-frame))) - ((window-in-direction 'right)) ;; try window in each direction - ((window-in-direction 'below)) ;; and default to right - ((window-in-direction 'left)) ;; if no window found. - ((window-in-direction 'above)) - (t (split-window-sensibly (selected-window)))))) - (window--display-buffer buffer window 'window nil) - window))) - -(defun re/dired-new-frame () - "Open dired in a new frame." - (interactive) - (re/open-with-new-frame "DIRED" - #'(lambda () (interactive) (dired default-directory)))) - -(keymap-global-set "" #'re/dired-new-frame) - -(defun re/dired-find-marked-files () - "Open all marked files from `dired'." - (interactive) - (mapc #'find-file (reverse (dired-get-marked-files)))) - -(defun re/dired-sort () - "Change sort order of dired dir." - (interactive) - (let ((smenu '(("name" . "-Ahl") - ("mdate" . "-Ahlt") - ("adate" . "-Ahltu") - ("size" . "-AhlS") - ("dir" . "-Ahl --group-directories-first"))) - ssortBy) - (setq ssortBy (completing-read "Sort by (default name):" - smenu - nil - t - nil - nil - (caar smenu))) - (dired-sort-other (cdr (assoc ssortBy smenu))))) - -(defun re/open-file-external (file) - "Open a file in an external program." - (interactive) - (let ((process-connection-type nil)) - (cond ((eq system-type 'windows-nt) - (w32-shell-execute "open" - (replace-regexp-in-string "/" "\\\\" file t t))) - ((eq system-type 'gnu/linux) - (start-process "" nil "xdg-open" file)) - (t (error "Unable to automatically open this file"))))) - -(defun re/dired-open-externally () - "Open the file at point in an external program." - (interactive) - (re/open-file-external (dired-get-filename))) - -(defun re/dired-open-marked-externally () - "Open all marked files from `dired' in an external program." - (interactive) - (mapc #'re/open-file-external (reverse (dired-get-marked-files)))) - -(setq dired-listing-switches "-Ahl" - dired-vc-rename-file t - dired-auto-revert-buffer t - dired-hide-details-hide-symlink-targets nil - dired-kill-when-opening-new-dired-buffer t) - -(add-hook 'dired-mode-hook - (lambda () - (keymap-set dired-mode-map "o" #'re/dired-display-direction) - (keymap-set dired-mode-map "z" #'dired-do-compress-to) - (keymap-set dired-mode-map "c" #'dired-do-chmod) - (keymap-set dired-mode-map "r" #'dired-do-rename) - (keymap-set dired-mode-map "M" #'dired-do-rename) - (keymap-set dired-mode-map "s" #'re/dired-sort) - (keymap-set dired-mode-map "F" #'re/dired-find-marked-files) - (keymap-set dired-mode-map "M-C" #'re/copy-with-mod-time) - (keymap-set dired-mode-map "b" #'re/dired-open-externally) - (keymap-set dired-mode-map "B" #'re/dired-open-marked-externally) - (dired-hide-details-mode t) - (hl-line-mode))) -#+END_SRC -* Bookmarks -#+BEGIN_SRC emacs-lisp -(setq bookmark-save-flag 1) -(keymap-global-set "" #'recentf-open-files) -(keymap-global-set "C-x r B" #'bookmark-jump-other-window) -#+END_SRC -* GUI -#+BEGIN_SRC emacs-lisp -(load-theme 'eng-paper t) - -(if (equal system-type 'android) - (progn - (setq touch-screen-display-keyboard t) - (set-frame-parameter nil 'tool-bar-position 'bottom) - (define-key key-translation-map - (kbd "") - (kbd "C-i")) - (define-key key-translation-map - (kbd "") - 'event-apply-meta-modifier) - (modifier-bar-mode 1) - (tool-bar-mode 1)) - (progn - (menu-bar-mode -1) - (tool-bar-mode -1))) -(set-scroll-bar-mode (if (eq system-type 'windows-nt) nil 'left)) - -(setq frame-title-format "%b" - mouse-prefer-closest-glyph t - mouse-1-click-follows-link nil - focus-follows-mouse t - mouse-autoselect-window -0.25) - -(setq-default cursor-type 'hbar - cursor-in-non-selected-windows nil) -#+END_SRC -** Modeline -*** Minions -Remove all minor modes from the mode-line, and show /all/ of them in the minor mode menu. Must come before the modeline customization to ensure =minions-mode-line-modes= is defined. -#+BEGIN_SRC emacs-lisp -(require 'minions) -(minions-mode) - -(setq minions-mode-line-lighter "+") -#+END_SRC -*** Format -#+BEGIN_SRC emacs-lisp -(setq-default mode-line-format '(" " - (:eval (propertize "%b" - 'face - (when (buffer-modified-p) - 'mode-line-highlight))) - " " - mode-line-remote - " " - minions-mode-line-modes - (vc-mode vc-mode) - mode-line-format-right-align - (:eval (number-to-string (line-number-at-pos (point-max)))) - "L/%Ib (%l,%C) ")) -#+END_SRC -** Pretty Symbols -#+BEGIN_SRC emacs-lisp -(setq prettify-symbols-alist '(("lambda" . 955) - ("delta" . 120517) - ("epsilon" . 120518) - ("->" . 8594) - ("<=" . 8804) - (">=" . 8805))) - -(global-prettify-symbols-mode t) -#+END_SRC -* Parens -Must be set after loading any theme -#+BEGIN_SRC emacs-lisp -(setq show-paren-delay 0 - show-paren-style 'expression) - -(setq electric-pair-pairs '((?\" . ?\") - (?\‘ . ?\’) - (?\“ . ?\”) - (?\{ . ?\}) - (?\< . ?\>))) -#+END_SRC -* Help -#+BEGIN_SRC emacs-lisp -(setq apropos-do-all t) -#+END_SRC -* UTF-8 -#+BEGIN_SRC emacs-lisp -(prefer-coding-system 'utf-8-unix) -(set-language-environment 'utf-8) -#+END_SRC -* Cleanup on write -#+BEGIN_SRC emacs-lisp -(defun re/flush-extra-lines () - "Change all groups of blank lines to 1 blank line." - (interactive) - (save-excursion - (replace-regexp "^\n+" - "\n" - nil - (point-min) - (point-max)))) - -(setq require-final-newline t) -(add-hook 'before-save-hook #'re/flush-extra-lines) -(add-hook 'before-save-hook #'whitespace-cleanup) -(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p) -#+END_SRC -* Pass-alike -** Password generation -#+BEGIN_SRC emacs-lisp -(defconst *alphanumerics* - (mapcar #'char-to-string - (append (number-sequence ?a ?z) - (number-sequence ?A ?Z) - (number-sequence ?0 ?9))) - "List of strings of the upper/lower-case English alphabet, and the single-digit numbers.") - -(defconst *special-characters* - '("!" "?" "$" "%" "&" "'" "(" ")" "*" "+" "," "-" "." - "/" ":" ";" "<" "=" ">" "?" "@" "[" "]" "^" "_" "{" - "|" "}" "~") - "List of strings containing the special visible characters excluding \\ and \" .") - -(defun pw-get-char (&optional no-special-chars) - "Select a single character at random to be used in a password. -If `no-special-chars' is t, use only alpha-numeric characters." - (let ((selected-charset (append *alphanumerics* - (when (null no-special-chars) - ,*special-characters*)))) - (elt selected-charset (random (length selected-charset))))) - -(defun pw-get-chars (password-length &optional no-special-chars) - "Generate a list of characters as strings to be made into a password. -If `no-special-chars' is t, use only alpha-numeric characters." - (append (list (pw-get-char no-special-chars)) - (when (> password-length 1) - (pw-get-chars (- password-length 1) no-special-chars)))) - -(defun generate-password (password-length &optional no-special-chars) - "Combine a list of randomly-selected characters into a single string of length given. -calls `pw-get-chars'. If `no-special-chars' is `t', use only alpha-numeric characters." - (apply #'concat (pw-get-chars password-length no-special-chars))) - -(defun insert-generated-password (&optional password-length) - "Insert a generated password into the current buffer. -Negative password-length (or negative arg) sets `no-special-chars' for `generate-password'." - (interactive "p") - (let ((len (if (or (null password-length) (= 1 (abs password-length))) - 16 - (abs password-length))) - (no-special-p (and (numberp password-length) (< password-length 0)))) - (insert (generate-password len no-special-p)))) -#+END_SRC -** Get a password from a pass(1) entry -Assume the password is on the first line. -#+BEGIN_SRC emacs-lisp -(defun re/get-line-from-file (file-path line-number) - "Return the specified LINE-NUMBER from FILE-PATH as a string. -Start from bottom if given a negative line number." - (with-temp-buffer - (insert-file-contents file-path) - (if (< line-number 0) - (progn - (goto-char (point-max)) - (forward-line line-number)) - (progn - (goto-char (point-min)) - (forward-line (1- line-number)))) - (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) - -(defun get-password (&optional filename) - "Return a password given a file where it is stored. -Assumes same structure as `pass(1)'." - (re/get-line-from-file (keyfile-string filename) 1)) - -(defun password-to-clipboard (&optional filename) - "Copy the returned password to the clipboard." - (interactive) - (kill-new (get-password filename))) -#+END_SRC -** TOTP -Generate TOTP tokens, generate the codes, put them on the clipboard. Assumes the format of pass(1), where the TOTP token is the last line in the file. -#+BEGIN_SRC emacs-lisp -(dolist (requirement '(bindat gnutls hexl auth-source)) - (require requirement)) - -(defun totp--hex-decode-string (string) - "Hex-decode STRING and return the result as a unibyte string." - (apply #'unibyte-string - (seq-map (lambda (s) (hexl-htoi (aref s 0) (aref s 1))) - (seq-partition string 2)))) - -(defun totp (string &optional time digits) - "Return a TOTP token using the secret hex STRING and current time. -TIME is used as counter value instead of current time, if non-nil. -DIGITS is the number of pin digits and defaults to 6." - (let* ((key-bytes (totp--hex-decode-string (upcase string))) - (counter (truncate (/ (or time (time-to-seconds)) 30))) - (digits (or digits 6)) - (format-string (format "%%0%dd" digits)) - ;; we have to manually split the 64 bit number (u64 not supported in Emacs 27.2) - (counter-bytes (bindat-pack '((:high u32) (:low u32)) - `((:high . ,(ash counter -32)) (:low . ,(logand counter #xffffffff))))) - (mac (gnutls-hash-mac 'SHA1 key-bytes counter-bytes)) - (offset (logand (bindat-get-field (bindat-unpack '((:offset u8)) mac 19) :offset) #xf))) - (format format-string - (mod - (logand (bindat-get-field (bindat-unpack '((:totp-pin u32)) mac offset) :totp-pin) - #x7fffffff) - (expt 10 digits))))) - -(defconst base32-alphabet - (let ((tbl (make-char-table nil))) - (dolist (mapping '(("A" . 0) ("B" . 1) ("C" . 2) ("D" . 3) - ("E" . 4) ("F" . 5) ("G" . 6) - ("H" . 7) ("I" . 8) ("J" . 9) ("K" . 10) - ("L" . 11) ("M" . 12) ("N" . 13) - ("O" . 14) ("P" . 15) ("Q" . 16) ("R" . 17) - ("S" . 18) ("T" . 19) ("U" . 20) - ("V" . 21) ("W" . 22) ("X" . 23) ("Y" . 24) - ("Z" . 25) ("2" . 26) ("3" . 27) - ("4" . 28) ("5" . 29) ("6" . 30) ("7" . 31))) - (aset tbl (string-to-char (car mapping)) (cdr mapping))) - tbl) - "Base-32 mapping table, as defined in RFC 4648.") - -(defun base32-hex-decode (string) - "The cheats' version of base-32 decode. - -This is not a 100% faithful implementation of RFC 4648. The -concept of encoding partial quanta is not implemented fully. - -No attempt is made to pad the output either as that is not -required for HMAC-TOTP." - (unless (mod (length string) 8) - (error "Padding is incorrect")) - (setq string (upcase string)) - (let ((trimmed-array (append (string-trim-right string "=+") nil))) - (format "%X" (seq-reduce - (lambda (acc char) (+ (ash acc 5) (aref base32-alphabet char))) - trimmed-array 0)))) - -(defun keyfile-string (filename) - "Return a string which is the filepath to a `pass(1)' file." - (let ((directory "~/.password-store/")) - (if (null filename) - (read-file-name "Select a password: " directory nil t nil) - (concat directory - (if (stringp filename) - filename - (symbol-name filename)) - ".gpg")))) - -(defun get-totp (&optional filename) - "Generate a totp code given a file with the secret hex string. -If no filename given, prompt for one. -Assumes the file has the string by itself on the last line of the file, -similar to what pass-otp does, but without the full uri. -Also assumes the file is in `~/.password-store' and has the `.gpg' extension." - (totp (base32-hex-decode (re/get-line-from-file (keyfile-string filename) -1)))) - -(defun otp-to-clipboard (&optional filename) - "Copy the returned totp code to the clipboard." - (interactive) - (kill-new (get-totp filename))) -#+END_SRC -* Control -#+BEGIN_SRC emacs-lisp -(defun re/kill-emacs () - "Save all open files and kill emacs." - (interactive) - (save-some-buffers t) - (kill-emacs)) - -(keymap-global-set "C-x M-c" #'re/kill-emacs) - -(defun re/restart-emacs () - "Save all open files and restart emacs." - (interactive) - (save-some-buffers t) - (kill-emacs nil t)) - -(keymap-global-set "C-x M-r" #'re/restart-emacs) - -(defun re/keyboard-quit () - "Smarter version of `keyboard-quit'. -Close the minibuffer if not focused when hit. -Stolen from emacsredux.com." - (interactive) - (if (active-minibuffer-window) - (if (minibufferp) - (minibuffer-keyboard-quit) - (abort-recursive-edit)) - (keyboard-quit))) - -(keymap-global-set " " #'re/keyboard-quit) ; Make C-g better -(keymap-global-set "C-x S" (lambda () (interactive) (save-some-buffers t))) ; don't ask -(keymap-global-set "C-x c" #'delete-frame) ; delete a frame without asking about saving files -#+END_SRC -* Version-control mode -#+BEGIN_SRC emacs-lisp -(defun re/vc-clone () - "Interactively clone with vc-mode. -Prompt for url and local dir." - (interactive) - (let* ((url (read-string "Repository URL: ")) - (dir (read-string "Local Dir: " (file-name-base url)))) - (vc-git-clone url dir nil))) - -(keymap-global-set "C-x v C" #'re/vc-clone) - -(defun re/vc-show-branches (&optional arg) - "Display all Git branches in a separate buffer. -Remotes as well when arg." - (interactive "P") - (let ((default-directory (if (boundp 'vc-dir-directory) - vc-dir-directory - default-directory))) - (vc-git-command "*git-branches*" - nil - nil - "branch" - "--verbose" - (when arg "--remotes")) - (pop-to-buffer "*git-branches*") - (goto-char (point-min)) - (special-mode))) - -(keymap-global-set "C-x v b b" #'re/vc-show-branches) - -(defun re/vc-fetch (&optional arg) - "Interactively fetch with vc-mode. -Allows separate fetch in addition to pull. Only care about git. -With arg, ask for remote, otherwise fetch all." - (interactive "P") - (let* ((default-directory (if (boundp 'vc-dir-directory) - vc-dir-directory - default-directory))) - (vc-git-command "*vc-fetch*" - nil - nil - "fetch" - (if arg - (read-string "Fetch From: " "origin") - "--all") - "--verbose") - (pop-to-buffer "*vc-fetch*") - (goto-char (point-min)) - (special-mode))) - -(keymap-global-set "C-x v f" #'re/vc-fetch) - -(add-hook 'vc-dir-mode-hook - (lambda () - (keymap-set vc-dir-mode-map "b b" #'re/vc-show-branches) - (keymap-set vc-dir-mode-map "f" #'re/vc-fetch) - (keymap-set vc-dir-mode-map "k" #'vc-revert))) - -(setq vc-make-backup-files nil - vc-handled-backends '(jj Git) - vc-git-log-switches '("--oneline" "--graph" "--decorate" "--all") - vc-git-log-edit-summary-target-len 50 - vc-find-revision-no-save t) -#+END_SRC -* Backups -#+BEGIN_SRC emacs-lisp -(setq backup-directory-alist `((".*" . "~/emacs-backups/")) - backup-by-copying t - delete-old-versions t - kept-new-versions 5 - kept-old-versions 5) -#+END_SRC -* Any Lisp Repl -#+BEGIN_SRC emacs-lisp -(defmacro re/send-on-close-paren (executor) - "Generalizes sending an execute on close paren. - Interactively call `executor'. For `executor', use whatever - function is called by `' in the applicable REPL. For a - non-repl, use `newline' or similar." - `(lambda (&optional arg) - (interactive "p") - (insert-char 41 arg) - (call-interactively ,executor))) - -(defun re/sexp-drop-paren-p () - "Returns t if sexp needs fewer parens to balance." - (< (car (syntax-ppss)) 0)) - -(defun re/sexp-need-paren-p () - "Returns t if sexp needs more parens to balance." - (> (car (syntax-ppss)) 0)) - -(defun re/sexp-unbalanced-count () - "Returns distance from balanced sexp." - (abs (car (syntax-ppss)))) - -(defun re/balance-sexp () - "Balance the sexp before point. -Either delete, move forward, or add as many ')' as needed." - (let ((distance (re/sexp-unbalanced-count))) - (cond ((re/sexp-need-paren-p) - (if (looking-at ")") - (progn - (forward-char) - (re/balance-sexp)) - (insert-char 41 distance))) - ((re/sexp-drop-paren-p) - (backward-delete-char distance))))) - -(defmacro re/balance-and-eval-sexp (executor) - "Balance the sexp before point, then call `executor'. -Call `re/balance-sexp', then interactively call `executor'. Bind -this to ']' for the interlisp experience. For `executor', use -whatever function is called by `' in the applicable REPL. -For a non-repl, use `newline' or similar." - `(lambda () (interactive) (re/balance-sexp) (call-interactively ,executor))) - -(defvar *re/lisp-mode-hooks* - '(emacs-lisp-mode-hook - lisp-mode-hook - sly-mode-hook - scheme-mode-hook - geiser-mode-hook - lisp-interaction-mode-hook)) - -(dolist (hook *re/lisp-mode-hooks*) - (add-hook hook - (lambda () - (when (bound-and-true-p acme-mouse-mode) (acme-mouse-mode -1)) - (sedit-mouse-mode 1) - (keymap-set lisp-mode-shared-map "C-M-z" #'eval-region) - (keymap-set lisp-mode-shared-map - "C-M-S-q" - #'sedit-auto-prettify-sexp) - (keymap-set lisp-mode-shared-map - "]" - (re/balance-and-eval-sexp #'sedit-auto-prettify-sexp)) - (keymap-set lisp-interaction-mode-map - "]" - (re/balance-and-eval-sexp #'eval-print-last-sexp))))) -#+END_SRC -* IELM -Rarely used, but nice to have when I want it. -#+BEGIN_SRC emacs-lisp -(add-hook 'ielm-mode-hook - (lambda () - (keymap-set inferior-emacs-lisp-mode-map - "C-l" - #'comint-clear-buffer) - (let ((eval-function #'ielm-send-input)) - (keymap-set inferior-emacs-lisp-mode-map - ")" - (re/send-on-close-paren eval-function)) - (keymap-set inferior-emacs-lisp-mode-map - "]" - (re/balance-and-eval-sexp eval-function))))) -#+END_SRC -* Eshell -#+BEGIN_SRC emacs-lisp -(keymap-global-set "C-c e" #'eshell) - -(defun eshell-quit-or-delete-char (arg) - "Delete char if at one, quit eshell if on empty prompt. -Stolen from https://depp.brause.cc/dotemacs" - (interactive "p") - (if (and (eolp) (looking-back eshell-prompt-regexp 0 t)) - (eshell-life-is-too-much) - (delete-char arg))) - -(add-hook 'eshell-mode-hook - (lambda () - (keymap-set eshell-mode-map "C-d" #'eshell-quit-or-delete-char) - (let ((eval-function #'eshell-send-input)) - (keymap-set eshell-mode-map - ")" - (re/send-on-close-paren eval-function)) - (keymap-set eshell-mode-map - "]" - (re/balance-and-eval-sexp eval-function))))) -#+END_SRC -* C-mode -#+BEGIN_SRC emacs-lisp -(dolist (mode-maps '(("\\.keymap\\'" . c-mode) ("\\.dtsi\\'" . c-mode))) - (add-to-list 'auto-mode-alist mode-maps t)) - -(defun re/c-lineup-arglist-tabs-only (ignored) - "Line up argument lists by tabs, not spaces. - Stolen from https://kernel.org/doc/html/v4.10/process/coding-style.html" - (let* ((anchor (c-langelem-pos c-syntactic-element)) - (column (c-langelem-2nd-pos c-syntactic-element)) - (offset (- (1+ column) anchor)) - (steps (floor offset c-basic-offset))) - (* (max steps 1) c-basic-offset))) - -(add-hook 'c-mode-common-hook - (lambda () - (c-add-style "linux-tabs-only" - '("linux" (c-offsets-alist - (arglist-cont-nonempty - c-lineup-gcc-asm-reg - re/c-lineup-arglist-tabs-only)))))) - -(add-hook 'c-mode-hook - (lambda () - (setq c-backspace-function 'backward-delete-char) - (c-set-style "linux-tabs-only"))) -#+END_SRC -* Auto-Revert -#+BEGIN_SRC emacs-lisp -(global-auto-revert-mode) -(setq global-auto-revert-non-file-buffers nil) -#+END_SRC -* Crash Handliŋ -#+BEGIN_SRC emacs-lisp -(setq attempt-stack-overflow-recovery nil - attempt-orderly-shutdown-on-fatal-signal nil) -#+END_SRC -* Abbrev-mode and skeletons -#+BEGIN_SRC emacs-lisp -(define-skeleton workorder-link - "part of a link to a workorder, just enter the number." - "" - "https://eservice.r717.net/index.php/PLC_workorders/store/" - _ -) - -(define-skeleton skel-org-block - "Add a source block to an org file." - "" - "#+BEGIN_SRC" - \n - _ - - \n - "#+END_SRC") - -(define-skeleton skel-org-block-elisp - "Add an elisp source block to an org file." - "" - "#+BEGIN_SRC emacs-lisp" - \n - _ - - \n - "#+END_SRC") - -(setq save-abbrevs 'silently) - -(add-hook 'org-mode-hook #'abbrev-mode) -#+END_SRC -* Customization Framework -Disable it by making it save to a temp file (stolen from prot) -#+BEGIN_SRC emacs-lisp -(setq custom-file (make-temp-file "emacs-custom-")) -#+END_SRC diff --git a/init.el b/init.el index 0b4d640..0c968f9 100644 --- a/init.el +++ b/init.el @@ -1,9 +1,1385 @@ -;;; init.el --- Emacs Configuration -*- lexical-binding: t; -*- - -;; This file is not part of GNU emacs - -;;; Code: - -(org-babel-load-file "~/.emacs.d/config.org") - -;;; init.el ends here +;;;; init.el --- Emacs Configuration -*- lexical-binding: t; -*- + +;;; OS specific stuff + +(setq w32-recognize-altgr 'nil) + +(unless (equal system-type 'windows-nt) + (setq trash-directory "~/Trash/") + (defun doas () + "Use TRAMP to reopen the current buffer as root using doas." + (interactive) + (when buffer-file-name + (find-alternate-file (concat "/doas:root@localhost:" buffer-file-name)))) + (defun sudo () + "Use TRAMP to reopen the current buffer as root using sudo." + (interactive) + (when buffer-file-name + (find-alternate-file (concat "/sudo:root@localhost:" buffer-file-name))))) + +;;; PACKAGES +;; Add melpa, initialize + +(require 'package) + +(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) +(package-initialize) + +;; Install all packages that are not included + +(defvar *re/packages* + '(corfu + expand-region + page-break-lines + pdf-tools + auctex + csv-mode + markdown-mode + edit-indirect + sly + geiser + geiser-chicken + forth-mode + trashed + org-transclusion + vc-jj + minions) + "The list of packages I want to always install") + +(dolist (pkg *re/packages*) + (unless (package-installed-p pkg) + (package-refresh-contents) + (package-install pkg))) + +;; Corfu + +(require 'corfu) + +(setq corfu-auto t + corfu-auto-delay 0 + corfu-auto-prefix 1) + +(add-hook 'corfu-mode-hook #'corfu-popupinfo-mode) + +(setq corfu-popupinfo-delay '(1.0 . 0.01) ; start after one second, fast refresh once on + corfu-popupinfo-hide nil) + +(global-corfu-mode) + +;; Expand Region + +(require 'expand-region) + +(keymap-global-set "C-=" #'er/expand-region) + +;; Page Break Lines + +(require 'page-break-lines) +(global-page-break-lines-mode) + +;; PDFtools +;; Be sure to run `pdf-tools-install' before first run + +(add-to-list 'auto-mode-alist '("\\.pdf\\'" . pdf-tools-install)) + +(setq-default pdf-view-display-size 'fit-page) + +;; Auctex + +(setq-default TeX-master nil) + +(setq TeX-auto-save t + TeX-parse-self t + TeX-PDF-mode t + TeX-view-program-selection '((output-pdf "PDF Tools")) + TeX-source-correlate-start-server t) + +(add-hook 'TeX-after-compilation-finished-functions + #'TeX-revert-document-buffer) + +;; CSV Mode + +(add-hook 'csv-mode-hook 'csv-align-mode) + +;; Markdown Mode +;; Make markdown act more like org when I'm forced to use it + +(setq markdown-special-ctrl-a/e t) + +(with-eval-after-load 'markdown-mode + (keymap-set markdown-mode-map "M-" #'markdown-demote) + (keymap-set markdown-mode-map "M-" #'markdown-promote) + (keymap-set markdown-mode-map "M-" #'markdown-move-up) + (keymap-set markdown-mode-map "M-" #'markdown-move-down) + (keymap-set markdown-mode-map "C-M-u" #'markdown-up-heading) + (keymap-set markdown-mode-map "C-M-b" #'markdown-outline-previous-same-level) + (keymap-set markdown-mode-map "C-M-f" #'markdown-outline-next-same-level) + (keymap-set markdown-mode-map "C-M-p" #'markdown-outline-previous) + (keymap-set markdown-mode-map "C-M-n" #'markdown-outline-next)) + +;; Sly + +(defun re/sly-repl-new-frame (&optional arg) + "Start a SLY repl in a new frame." + (interactive "P") + (re/open-with-new-frame "SLY" #'sly)) + +(defun re/sly-quit-current-lisp () + "Quit the current lisp repl connection without prompting." + (interactive) + (sly-quit-lisp nil nil)) + +(defun re/sly-connect-stump (&optional arg) + "Connect to my typical stumpwm instance." + (interactive "P") + (sly-connect "localhost" 4004)) + +(setq inferior-lisp-program "sbcl" + sly-net-coding-system 'utf-8-unix + sly-command-switch-to-existing-lisp 'always) + +(let ((core (expand-file-name "~/sbcl.core-for-sly"))) + (when (file-exists-p core) + (setq sly-lisp-implementations (list `(sbcl ("sbcl" "--core" ,core)))))) + +(with-eval-after-load 'sly-mrepl + (keymap-set sly-mrepl-mode-map ")" (re/send-on-close-paren #'sly-mrepl-return)) + (keymap-set sly-mrepl-mode-map "]" (re/balance-and-eval-sexp #'sly-mrepl-return)) + (keymap-set sly-mrepl-mode-map "C-c q" #'re/sly-quit-current-lisp)) + +;; Eldoc settings +;; Sly really doesn't play well with elodc in the minibuffer, +;; so here are some settings to make it better. + +(add-to-list 'display-buffer-alist + '("^\\*eldoc" display-buffer-in-direction + (direction . 'down) + (window-height . 4))) +(keymap-global-set "C-c d" #'eldoc) ; force eldoc to show in minibuffer +(keymap-global-set "C-c D" #'eldoc-doc-buffer) ; open eldoc buffer when it's getting spammed + +;; Geiser + +(if (equal system-type 'windows-nt) + (setq geiser-chicken-binary '("C:/tools/chicken/bin/csi.exe" "-:c")) + (setq geiser-chicken-binary "csi -:c")) + +(setq scheme-program-name geiser-chicken-binary + geiser-active-implementations '(chicken)) + +(add-hook 'scheme-mode-hook 'geiser-mode) + +(with-eval-after-load 'geiser-repl + (keymap-set geiser-repl-mode-map ")" (re/send-on-close-paren #'geiser-repl-maybe-send)) + (keymap-set geiser-repl-mode-map "]" (re/balance-and-eval-sexp #'geiser-repl-maybe-send))) + +;; Set bindings, things that have to happen after loading + +(with-eval-after-load 'org + (require 'org-mouse) + (add-to-list 'org-export-backends 'md) + (keymap-set org-mode-map "C-M-h" #'org-mark-subtree) + (keymap-set org-mode-map "C-M-u" #'org-up-element) + (keymap-set org-mode-map "C-M-d" #'org-down-element) + (keymap-set org-mode-map "C-M-b" #'org-backward-heading-same-level) + (keymap-set org-mode-map "C-M-f" #'org-forward-heading-same-level) + (keymap-set org-mode-map "C-M-p" #'org-previous-visible-heading) + (keymap-set org-mode-map "C-M-n" #'org-next-visible-heading) + (keymap-set org-mode-map "C-c t" #'org-transclusion-mode)) + +(keymap-global-set "C-c l" #'org-store-link) +(keymap-global-set "C-c a" #'org-agenda) +(keymap-global-set "C-c c" #'org-capture) + +;; Org-Mode +;; General options + +(setq org-M-RET-may-split-line nil + org-cycle-inline-images-display t + org-cycle-separator-lines -1 + org-ellipsis "->" + org-enforce-todo-checkbox-dependencies t + org-enforce-todo-dependencies t + org-goto-interface 'outline-path-completion + org-outline-path-complete-in-steps nil + org-src-fontify-natively nil + org-return-follows-link t + org-reverse-note-order t + org-special-ctrl-a/e t + org-special-ctrl-k t + org-src-preserve-indentation t + org-startup-folded 'fold + org-tags-column 0 + org-yank-adjusted-subtrees t + org-refile-use-outline-path 'file + org-refile-allow-creating-parent-nodes 'confirm) + +;; Agenda settings + + (setq org-agenda-files (append '("~/org/") + (file-expand-wildcards "~/projects/*/")) + org-refile-targets '((nil :maxlevel . 9) + (org-agenda-files :maxlevel . 9)) + org-todo-keywords '((sequence "TODO(t)" "WAITING(w@)" "|" "DONE(d)" "BELAYED(b@)" "GIVEN(g@)")) + org-todo-keyword-faces '(("WAITING" :foreground "#e85400" :weight bold)) ; alert orange + org-agenda-prefix-format '((agenda . " %?-12t%? s") + (todo . " ") + (tags . " ") + (search . " ")) + org-agenda-start-on-weekday nil + org-read-date-prefer-future 'time + org-log-done 'time + org-log-redeadline 'time + org-log-reschedule 'time + org-log-state-notes-insert-after-drawers t + org-agenda-window-setup 'current-window) + +;; Stuck Projects + +(setq org-stuck-projects '("CATEGORY=\"undertakings\"+LEVEL=1/-DONE-GIVEN-BELAYED" ("TODO" "WAITING") nil "")) + +;; Custom Agendas + + (defvar *re/org-agenda-inbox* + '(tags "CATEGORY=\"inbox\"" ((org-agenda-overriding-header "")))) + + (defvar *re/org-agenda-todo* + '(tags-todo "-recurring/TODO" ((org-agenda-overriding-header "")))) + + (defvar *re/org-agenda-undertakings* + '(tags "CATEGORY=\"undertakings\"+LEVEL=1" ((org-agenda-overriding-header "")))) + + (defvar *re/org-agenda-waiting* + '(todo "WAITING" ((org-agenda-overriding-header "")))) + +(defvar *re/org-agenda-done* + '(tags "-recurring+LEVEL=1/DONE|BELAYED|GIVEN")) + + (setq org-agenda-custom-commands `(("i" "Inbox" ,@*re/org-agenda-inbox*) + ("n" "Next Actions" ,@*re/org-agenda-todo*) + ("u" "Undertakings" ,@*re/org-agenda-undertakings*) + ("w" "Waiting" ,@*re/org-agenda-waiting*) + ("d" "Done" ,@*re/org-agenda-done*))) + +;; Capture Templates + + (setq org-capture-templates '(("i" "Inbox" entry + (file "inbox.org") + "* %^{Description}\n%U\n%?" + :prepend t) + ("n" "Next Action" entry + (file "next.org") + "* TODO %^{Description}\nTaken: %T\n%?" + :prepend t) + ("u" "Undertaking" entry + (file "undertakings.org") + "* %^{Description} [/]\nTaken: %T\n%?" + :prepend t) + ("m" "Meeting" entry + (file "meetings.org") + "* %^{Description}\n%^{Scheduled:}T\n%?" + :prepend t) + ("p" "Phone Call" entry + (file "inbox.org") + "* %^{Description} :call:\n%T\n%?" + :prepend t) + ("t" "Note" entry + (file "notes.org") + "* %^{Description}\n%U\n%?" + :prepend t))) + +;; IDs + +(setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id + org-id-method 'ts + org-clone-delete-id t + org-attach-id-to-path-function-list '(org-attach-id-ts-folder-format + org-attach-id-uuid-folder-format)) + +(defun re/org-save-all () + "Save all `org-agenda-files' without user confirmation." + (interactive) + (message "Saving all org-agenda-files buffers...") + (save-some-buffers t + (lambda () + (when (member (buffer-file-name) org-agenda-files) t))) + (message "Saving all org-agenda-files buffers... done")) + +(advice-add 'org-refile :after (lambda (&rest _) (re/org-save-all))) + +;; Archiving + +(setq org-archive-location (concat "~/org/old.org_keep::datetree/") + org-agenda-text-search-extra-files '(agenda-archives)) + +(add-to-list 'auto-mode-alist '("\\.org_keep\\'" . org-mode)) + +;; Converting markdown to org + +(defun re/markdown-to-org-region (start end) + "Convert Markdown formatted text in region (START, END) to Org. +This command requires that pandoc (man page `pandoc(1)') be +installed." + (interactive "r") + (shell-command-on-region + start end + "pandoc -f markdown -t org --wrap=preserve" t t)) + +;; Converting org to markdown + +(defun re/org-to-markdown-region (start end) + "Convert Org formatted text in region (START, END) to Markdown. +This command requires that pandoc (man page `pandoc(1)') be +installed." + (interactive "r") + (shell-command-on-region + start end + "pandoc -f org -t markdown --wrap=preserve" t t)) + +;; Trashed +;; Shows the trash file as a dired buffer + +(setq trashed-action-confirmer 'y-or-n-p + trashed-use-header-line t + trashed-sort-key '("Date deleted" . t) + trashed-date-format "%Y-%m-%d %H:%M:%S") + +;; Use mu4e on my home pcs + +(when (eq system-type 'gnu/linux) + (with-eval-after-load 'mu4e + (setq mail-user-agent 'message-user-agent + message-send-mail-function 'smtpmail-send-it + message-citation-line-format "Quoth %f :\n" + smtpmail-default-smtp-server "mail.earman.xyz" + smtpmail-smtp-server "mail.earman.xyz" + smtpmail-smtp-service 587 + smtpmail-stream-type 'starttls + smtpmail-local-domain "earman.xyz" + smtpmail-queue-mail nil + smtpmail-queue-dir "~/mail/Outbox/cur" + user-mail-address "iv@earman.xyz" + user-full-name "Rush N. Earman IV" + message-signature "Rush Earman IV, PE" + message-kill-buffer-on-exit t + mu4e-get-mail-command "mbsync -a" + mu4e-update-interval (* 10 60) + mu4e-change-filenames-when-moving t + mu4e-use-fancy-chars t + mu4e-attachment-dir "~/downloads" + mu4e-compose-reply-to-address "iv@earman.xyz" + mu4e-maildir "~/mail" + mu4e-drafts-folder "/Drafts" + mu4e-sent-folder "/Sent" + mu4e-trash-folder "/Trash" + mu4e-maildir-shortcuts '((:maildir "/Inbox" :key ?i) + (:maildir "/Sent" :key ?s) + (:maildir "/Trash" :key ?t) + (:maildir "/Drafts" :key ?d))))) + +;;; Load libraries Not from the package manager + +(dolist (path '("sedit-mouse/" "themes/")) + (add-to-list 'load-path (concat user-emacs-directory path))) + +(dolist (file '(eng-paper-theme sedit-mouse)) + (require file)) + +;;; UNIX/vi-like bindings +;; =C-h=, =C-w=, =C-u=, et. al to work as I expect (like acme). +;; Some functions get re-bound when these take over a default. + +(defun re/open-line-below (n) + "Creates a new empty line below the current line and moves to it." + (interactive "*p") + (end-of-line) + (open-line n) + (next-line 1) + (indent-for-tab-command)) + +(keymap-global-set "C-o" #'re/open-line-below) + +(defun re/open-line-above (n) + "Creates a new empty line above the current line." + (interactive "*p") + (beginning-of-line) + (open-line n) + (indent-for-tab-command)) + +(keymap-global-set "M-o" #'re/open-line-above) + +(defun re/kill-bword-or-region (&optional arg) + "Kill region if active, otherwise kill back one word." + (interactive "p") + (if (use-region-p) + (kill-region (region-beginning) (region-end)) + (backward-kill-word arg))) + +(keymap-global-set "C-w" #'re/kill-bword-or-region) +(keymap-global-set "C-u" (lambda () (interactive) (kill-line 0))) ; kill-line backwards +(keymap-global-set "C-S-U" #'universal-argument) +(keymap-set key-translation-map "C-h" "") ; Rather F1 for help, also C-S-H works +(keymap-global-set "M-j" (lambda () (interactive) (join-line 0))) ; join-line other way +(keymap-global-set "" #'beginning-of-buffer) +(keymap-global-set "" #'end-of-buffer) + +;;; Commenting and defun-level bindings + +(defun re/comment-dwim (&optional arg) + "Comment region if active, otherwise comment line. + Stolen from https://depp.brause.cc/dotemacs" + (interactive "P") + (if (use-region-p) + (comment-or-uncomment-region (region-beginning) (region-end)) + (comment-line arg))) + +(keymap-global-set "M-;" #'re/comment-dwim) + +(defun re/comment-defun (&optional arg) + "Mark a defun and comment it out. + Return to initial position after. Arg works on `mark-defun'." + (interactive "P") + (save-excursion (mark-defun arg) + (comment-region (region-beginning) (region-end)))) + +(keymap-global-set "C-M-;" #'re/comment-defun) + +(defun re/kill-defun (&optional arg) + "Mark a defun and kill it. + Arg works on `mark-defun'." + (interactive "P") + (mark-defun arg) + (kill-region (region-beginning) (region-end))) + +(keymap-global-set "C-M-S-K" #'re/kill-defun) + +;;; Misc QOL bindings + +(keymap-global-set "" nil) ; I keep toggliŋ overwrite-mode haply +(keymap-global-set "C-\\" #'undo-redo) ; issues with C-? sometimes +(keymap-global-set "C-S-K" #'kill-whole-line) +(keymap-global-set "C-S-d" #'delete-pair) +(keymap-global-set "C-z" #'zap-up-to-char) +(keymap-global-set "C-c s" #'sort-lines) +(keymap-global-set " " #'capitalize-dwim) ; M-c +(keymap-global-set " " #'downcase-dwim) ; M-l +(keymap-global-set " " #'upcase-dwim) ; M-u + +;;; Tabs +;; I want *TABS*, not spaces, dammit. Globally overwrite the tab key to +;; insert the tab character. This makes it so that many things (completion) +;; will have to be triggered with =C-i= instead, but I don't think it's +;; necessarily bad to have things like that on a control combo instead of +;; overloading the tab key at the expense of being able to actually insert +;; tabs. + +(setq-default indent-tabs-mode t + tab-width 8 + backward-delete-char-untabify-method nil) + +(advice-add 'indent-to :around + (lambda (orig-fun column &rest args) + (when indent-tabs-mode + (setq column (* tab-width (round column tab-width)))) + (apply orig-fun column args))) + +(dolist (indent-level '(c-basic-offset cperl-indent-level LaTeX-indent-level)) + (defvaralias indent-level 'tab-width)) + +(defun Tab (width) + "Give me a function analagous to the Acme `Tab' command" + (interactive) + (setq-local tab-width width)) + +(keymap-global-set "" (lambda () (interactive) (insert "\t"))) + +;;; Buffer dealiŋ + +(setq-default indicate-empty-lines t + truncate-lines t + fill-column 78) + +(dolist (hook '(text-mode-hook LaTeX-mode-hook)) + (add-hook hook #'visual-line-mode)) + +(blink-cursor-mode -1) +(delete-selection-mode t) + +(setq delete-by-moving-to-trash t + delete-pair-blink-delay 0 + use-short-answers t + use-file-dialog nil + echo-keystrokes 0.1 + eldoc-idle-delay 0 + read-buffer-completion-ignore-case t + history-delete-duplicates t + disabled-command-function nil + save-interprogram-paste-before-kill t + confirm-kill-processes nil) + +(keymap-global-set "C-x M-f" #'find-file-at-point) +(keymap-global-set "C-x C-b" #'buffer-menu) +(keymap-global-set "C-x M-b" #'buffer-menu-other-window) +(keymap-global-set "C-x k" #'kill-current-buffer) +(keymap-global-set "C-x K" #'kill-buffer) + +;;; Recentering and page-by-page navigation + +(setq recenter-positions '(2 middle -3)) + +(defun re/recenter-advice (&rest _) + "Recenter to page start." + (when (called-interactively-p 'any) (recenter 2))) + +(dolist (binding '(backward-page forward-page)) + (advice-add binding :after #'re/recenter-advice)) + +(keymap-global-set "C-x C-n" #'forward-page) +(keymap-global-set "C-x C-p" #'backward-page) +(keymap-global-set "" #'forward-page) +(keymap-global-set "" #'backward-page) + +;;; Macro to open something in its own named frame +;; Start a repl in a new frame, open dired in a new frame, etc. etc. + +(defmacro re/open-with-new-frame (frname function) + "Run the function in its own named frame." + `(progn + (select-frame (make-frame '((name . ,(eval frname))))) + (call-interactively ,function))) + +;;; Scratch buffer easy access + +(defun re/scratch-only () + "Bring up the scratch buffer as the only visible buffer." + (interactive) + (scratch-buffer) + (delete-other-windows)) + +(keymap-global-set "" #'scratch-buffer) +(keymap-global-set "S-" #'re/scratch-only) + +;;; Switch or split window +;; Game changer. Auto choose how to split, or just switch to the next, all on +;; the same binding. Ripped from an early version of the lem editor. + +(defun re/other-window-or-split-window (&optional window) + "Go to other window or split sensibly." + (interactive) + (if (= (count-windows) 1) + (funcall split-window-preferred-function window) + (other-window 1))) + +(keymap-global-set "C-M-o" #'re/other-window-or-split-window) + +;;; Smart End/Beginning of line +;; Similar to what org does, and indeed calls the org function if in org-mode + +(defun re/smart-beginning-of-line () + "Move point to first non-whitespace character or `beginning-of-visual-line'. +If point was already at that position, call `back-to-indentation'. +Called a third time, move point back to `beginning-of-line'. +If in org-mode, call `org-beginning-of-line' first." + (interactive "^") + (let ((oldpos (point))) + (if (equal major-mode 'org-mode) + (org-beginning-of-line) + (progn + (when visual-line-mode (beginning-of-visual-line 1)) + (skip-syntax-forward " " (line-end-position)) + (backward-prefix-chars))) + (and (= oldpos (point)) + (let ((newpos (point))) + (back-to-indentation) + (and (= newpos (point)) (beginning-of-line)))))) + +(keymap-global-set " " #'re/smart-beginning-of-line) +(keymap-set visual-line-mode-map "C-a" #'re/smart-beginning-of-line) + +(defun re/smart-end-of-line () + "Move point to the end of the visual line. + If point was already at that position, call `move-end-of-line'. +If in org-mode, call `org-end-of-line' first." + (interactive "^") + (let ((oldpos (point))) + (if (equal major-mode 'org-mode) + (org-end-of-line) + (when visual-line-mode + (end-of-visual-line 1))) + (and (= oldpos (point)) (move-end-of-line nil)))) + +(keymap-global-set " " #'re/smart-end-of-line) +(keymap-set visual-line-mode-map "C-e" #'re/smart-end-of-line) + +;;; Goto line with numbers +;; I only care about line numbers when I want to jump, so only show them then + +(defun re/goto-line () + "Show line numbers before going to line, then hide them again." + (interactive) + (display-line-numbers-mode 1) + (call-interactively 'goto-line) + (display-line-numbers-mode -1)) + +(keymap-global-set " " #'re/goto-line) + +;;; Switching between buffers + +(keymap-global-set "C-." #'next-buffer) +(keymap-global-set "C-," #'previous-buffer) +(keymap-global-set "C-" (lambda () (interactive) (switch-to-buffer nil))) ; toggle last two buffers +(keymap-global-set "" #'previous-buffer) ; Back button +(keymap-global-set "" #'next-buffer) ; Forward button + +;;; Disable M-mouse for window manager bindings + +(keymap-global-unset "M-") +(keymap-global-unset "M-") +(keymap-global-unset "M-") +(keymap-global-unset "M-") +(keymap-global-unset "M-") + +;;; Search +(defun re/add-region-to-search-ring () + (when (use-region-p) + (add-to-history 'search-ring + (buffer-substring (region-beginning) (region-end))) + (deactivate-mark))) + +(defun re/isearch-forward-use-region () + "Search text in region if active, otherwise normal forward search." + (interactive) + (re/add-region-to-search-ring) + (isearch-forward)) + +(keymap-global-set "C-s" #'re/isearch-forward-use-region) + +(defun re/isearch-backward-use-region () + "Search text in region if active, otherwise normal backward search." + (interactive) + (re/add-region-to-search-ring) + (isearch-backward)) + +(keymap-global-set "C-r" #'re/isearch-backward-use-region) + +(keymap-global-set "M-s ." #'isearch-forward-thing-at-point) +(keymap-global-set "M-s M-." #'isearch-forward-symbol-at-point) +(keymap-global-set "M-%" #'replace-regexp) +(keymap-global-set "C-%" #'replace-string) +(keymap-global-set "C-M-S-R" #'replace-regexp-as-diff) +(keymap-global-set "C-S-S" #'isearch-forward-regexp) +(keymap-global-set "C-S-R" #'isearch-backward-regexp) + +;;; Recent Files/Buffers + +(midnight-mode t) +(setq clean-buffer-list-delay-general 1) +(dolist (never-re '("\\` \\*tramp/.*\\'" + "\\` \\*eshell.*\\'")) + (add-to-list 'clean-buffer-list-kill-never-regexps never-re)) +(save-place-mode 1) +(recentf-mode 1) + +;;; File Handling +;; Rename visited file + +(defun re/rename-current-buffer-file nil + "Renames the current buffer and the file it is visiting." + (interactive) + (let ((name (buffer-name)) + (filename (buffer-file-name))) + (if (not (and filename (file-exists-p filename))) + (error "Buffer '%s' is not visiting a file!" name) + (let ((new-name (read-file-name "New name: " filename))) + (if (get-buffer new-name) + (error "A buffer named '%s' already exists!" new-name) + (rename-file filename new-name 1) + (rename-buffer new-name) + (set-visited-file-name new-name) + (set-buffer-modified-p nil) + (message "File '%s' successfully renamed to '%s'" + name + (file-name-nondirectory new-name))))))) + +(keymap-global-set "C-x C-r" #'re/rename-current-buffer-file) + +;; Copy file with datestamp appended + +(defun re/get-file-modified-time (filepath) + (interactive) + (format-time-string "%Y-%m-%d-%H%M" + (file-attribute-modification-time (file-attributes filepath)))) + +(defun re/filename-inject-mod-time (filepath) + (interactive) + (concat (file-name-sans-extension filepath) + "_" + (re/get-file-modified-time filepath) + (file-name-extension filepath t))) + +(defun re/copy-with-mod-time (&optional filepath) + (interactive) + (let ((filepath (if (eq major-mode 'dired-mode) + (dired-get-filename t) + (when (null filepath) + (read-file-name "File to copy with datestring: " + default-directory + nil + t + nil))))) + (copy-file filepath (re/filename-inject-mod-time filepath ) nil t t t))) + +;;; Dired +;; General Settings + +(setq dired-listing-switches "-Ahl" + dired-vc-rename-file t + dired-auto-revert-buffer t + dired-hide-details-hide-symlink-targets nil + dired-kill-when-opening-new-dired-buffer t) + +;; Open file next to dir buffer + +(defun re/dired-display-direction () + "In dired mode, visit the file at the cursor in the right/below/left/above window." + (interactive) + (let* ((file-or-dir (dired-get-file-for-visit)) ;; get the file at cursor + (buffer (find-file-noselect file-or-dir))) ;; load the file into a buffer + (let ((window ;; figure out the window to use + (cond ((get-buffer-window buffer (selected-frame))) + ((window-in-direction 'right)) ;; try window in each direction + ((window-in-direction 'below)) ;; and default to right + ((window-in-direction 'left)) ;; if no window found. + ((window-in-direction 'above)) + (t (split-window-sensibly (selected-window)))))) + (window--display-buffer buffer window 'window nil) + window))) +;; Open in new frame + +(defun re/dired-new-frame () + "Open dired in a new frame." + (interactive) + (re/open-with-new-frame "DIRED" + #'(lambda () (interactive) (dired default-directory)))) +;; Multiple Files + +(keymap-global-set "" #'re/dired-new-frame) + +(defun re/dired-find-marked-files () + "Open all marked files from `dired'." + (interactive) + (mapc #'find-file (reverse (dired-get-marked-files)))) +;; Open file in external program + +(defun re/dired-sort () + "Change sort order of dired dir." + (interactive) + (let ((smenu '(("name" . "-Ahl") + ("mdate" . "-Ahlt") + ("adate" . "-Ahltu") + ("size" . "-AhlS") + ("dir" . "-Ahl --group-directories-first"))) + ssortBy) + (setq ssortBy (completing-read "Sort by (default name):" + smenu + nil + t + nil + nil + (caar smenu))) + (dired-sort-other (cdr (assoc ssortBy smenu))))) +;; From dired + +(defun re/open-file-external (file) + "Open a file in an external program." + (interactive) + (let ((process-connection-type nil)) + (cond ((eq system-type 'windows-nt) + (w32-shell-execute "open" + (replace-regexp-in-string "/" "\\\\" file t t))) + ((eq system-type 'gnu/linux) + (start-process "" nil "xdg-open" file)) + (t (error "Unable to automatically open this file"))))) +;; Multiple files + +(defun re/dired-open-externally () + "Open the file at point in an external program." + (interactive) + (re/open-file-external (dired-get-filename))) + +(defun re/dired-open-marked-externally () + "Open all marked files from `dired' in an external program." + (interactive) + (mapc #'re/open-file-external (reverse (dired-get-marked-files)))) + +(add-hook 'dired-mode-hook + (lambda () + (keymap-set dired-mode-map "o" #'re/dired-display-direction) + (keymap-set dired-mode-map "z" #'dired-do-compress-to) + (keymap-set dired-mode-map "c" #'dired-do-chmod) + (keymap-set dired-mode-map "r" #'dired-do-rename) + (keymap-set dired-mode-map "M" #'dired-do-rename) + (keymap-set dired-mode-map "s" #'re/dired-sort) + (keymap-set dired-mode-map "F" #'re/dired-find-marked-files) + (keymap-set dired-mode-map "M-C" #'re/copy-with-mod-time) + (keymap-set dired-mode-map "b" #'re/dired-open-externally) + (keymap-set dired-mode-map "B" #'re/dired-open-marked-externally) + (dired-hide-details-mode t) + (hl-line-mode))) + +;;; Bookmarks + +(setq bookmark-save-flag 1) +(keymap-global-set "" #'recentf-open-files) +(keymap-global-set "C-x r B" #'bookmark-jump-other-window) + +;;; GUI + +(load-theme 'eng-paper t) + +(if (equal system-type 'android) + (progn + (setq touch-screen-display-keyboard t) + (set-frame-parameter nil 'tool-bar-position 'bottom) + (modifier-bar-mode 1) + (tool-bar-mode 1) + (define-key key-translation-map + (kbd "") + (kbd "C-i")) + (define-key key-translation-map + (kbd "") + 'event-apply-meta-modifier)) + (progn + (menu-bar-mode -1) + (tool-bar-mode -1))) +(set-scroll-bar-mode (if (eq system-type 'windows-nt) nil 'left)) + +(setq frame-title-format "%b" + mouse-prefer-closest-glyph t + mouse-1-click-follows-link nil + focus-follows-mouse t + mouse-autoselect-window -0.25) + +(setq-default cursor-type 'hbar + cursor-in-non-selected-windows nil) + +;;; Mode Line +;; Remove all minor modes from the mode-line, and show /all/ of them in the +;; minor mode menu. Must come before the modeline customization to ensure +;; =minions-mode-line-modes= is defined. + +(require 'minions) +(minions-mode) + +(setq minions-mode-line-lighter "+") + +;; Format + +(setq-default mode-line-format '(" " + (:eval (propertize "%b" + 'face + (when (buffer-modified-p) + 'mode-line-highlight))) + " " + mode-line-remote + " " + minions-mode-line-modes + (vc-mode vc-mode) + mode-line-format-right-align + (:eval (number-to-string (line-number-at-pos (point-max)))) + "L/%Ib (%l,%C) ")) + +;;; Pretty Symbols + +(setq prettify-symbols-alist '(("lambda" . 955) + ("delta" . 120517) + ("epsilon" . 120518) + ("->" . 8594) + ("<=" . 8804) + (">=" . 8805))) + +(global-prettify-symbols-mode t) + +;;; Parens +;; Must be set after loading any theme + +(setq show-paren-delay 0 + show-paren-style 'expression) + +(setq electric-pair-pairs '((?\" . ?\") + (?\‘ . ?\’) + (?\“ . ?\”) + (?\{ . ?\}) + (?\< . ?\>))) + +;;; Help + +(setq apropos-do-all t) + +;;; UTF-8 + +(prefer-coding-system 'utf-8-unix) +(set-language-environment 'utf-8) + +;;; Cleanup on write + +(defun re/flush-extra-lines () + "Change all groups of blank lines to 1 blank line." + (interactive) + (save-excursion + (replace-regexp "^\n+" + "\n" + nil + (point-min) + (point-max)))) + +(setq require-final-newline t) +(add-hook 'before-save-hook #'re/flush-extra-lines) +(add-hook 'before-save-hook #'whitespace-cleanup) +(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p) + +;;; Password generation + +(defconst *alphanumerics* + (mapcar #'char-to-string + (append (number-sequence ?a ?z) + (number-sequence ?A ?Z) + (number-sequence ?0 ?9))) + "List of strings of the upper/lower-case English alphabet, and the single-digit numbers.") + +(defconst *special-characters* + '("!" "?" "$" "%" "&" "'" "(" ")" "*" "+" "," "-" "." + "/" ":" ";" "<" "=" ">" "?" "@" "[" "]" "^" "_" "{" + "|" "}" "~") + "List of strings containing the special visible characters excluding \\ and \" .") + +(defun pw-get-char (&optional no-special-chars) + "Select a single character at random to be used in a password. +If `no-special-chars' is t, use only alpha-numeric characters." + (let ((selected-charset (append *alphanumerics* + (when (null no-special-chars) + *special-characters*)))) + (elt selected-charset (random (length selected-charset))))) + +(defun pw-get-chars (password-length &optional no-special-chars) + "Generate a list of characters as strings to be made into a password. +If `no-special-chars' is t, use only alpha-numeric characters." + (append (list (pw-get-char no-special-chars)) + (when (> password-length 1) + (pw-get-chars (- password-length 1) no-special-chars)))) + +(defun generate-password (password-length &optional no-special-chars) + "Combine a list of randomly-selected characters into a single string of length given. +calls `pw-get-chars'. If `no-special-chars' is `t', use only alpha-numeric characters." + (apply #'concat (pw-get-chars password-length no-special-chars))) + +(defun insert-generated-password (&optional password-length) + "Insert a generated password into the current buffer. +Negative password-length (or negative arg) sets `no-special-chars' for `generate-password'." + (interactive "p") + (let ((len (if (or (null password-length) (= 1 (abs password-length))) + 16 + (abs password-length))) + (no-special-p (and (numberp password-length) (< password-length 0)))) + (insert (generate-password len no-special-p)))) + +;; Get a password from a pass(1) entry +;; Assume the password is on the first line. + +(defun re/get-line-from-file (file-path line-number) + "Return the specified LINE-NUMBER from FILE-PATH as a string. +Start from bottom if given a negative line number." + (with-temp-buffer + (insert-file-contents file-path) + (if (< line-number 0) + (progn + (goto-char (point-max)) + (forward-line line-number)) + (progn + (goto-char (point-min)) + (forward-line (1- line-number)))) + (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) + +(defun get-password (&optional filename) + "Return a password given a file where it is stored. +Assumes same structure as `pass(1)'." + (re/get-line-from-file (keyfile-string filename) 1)) + +(defun password-to-clipboard (&optional filename) + "Copy the returned password to the clipboard." + (interactive) + (kill-new (get-password filename))) + +;;; TOTP +;; Generate TOTP tokens, generate the codes, put them on the clipboard. +;; Assumes the format of pass(1), where the TOTP token is the last line in the +;; file. + +(dolist (requirement '(bindat gnutls hexl auth-source)) + (require requirement)) + +(defun totp--hex-decode-string (string) + "Hex-decode STRING and return the result as a unibyte string." + (apply #'unibyte-string + (seq-map (lambda (s) (hexl-htoi (aref s 0) (aref s 1))) + (seq-partition string 2)))) + +(defun totp (string &optional time digits) + "Return a TOTP token using the secret hex STRING and current time. +TIME is used as counter value instead of current time, if non-nil. +DIGITS is the number of pin digits and defaults to 6." + (let* ((key-bytes (totp--hex-decode-string (upcase string))) + (counter (truncate (/ (or time (time-to-seconds)) 30))) + (digits (or digits 6)) + (format-string (format "%%0%dd" digits)) + ;; we have to manually split the 64 bit number (u64 not supported in Emacs 27.2) + (counter-bytes (bindat-pack '((:high u32) (:low u32)) + `((:high . ,(ash counter -32)) (:low . ,(logand counter #xffffffff))))) + (mac (gnutls-hash-mac 'SHA1 key-bytes counter-bytes)) + (offset (logand (bindat-get-field (bindat-unpack '((:offset u8)) mac 19) :offset) #xf))) + (format format-string + (mod + (logand (bindat-get-field (bindat-unpack '((:totp-pin u32)) mac offset) :totp-pin) + #x7fffffff) + (expt 10 digits))))) + +(defconst base32-alphabet + (let ((tbl (make-char-table nil))) + (dolist (mapping '(("A" . 0) ("B" . 1) ("C" . 2) ("D" . 3) + ("E" . 4) ("F" . 5) ("G" . 6) + ("H" . 7) ("I" . 8) ("J" . 9) ("K" . 10) + ("L" . 11) ("M" . 12) ("N" . 13) + ("O" . 14) ("P" . 15) ("Q" . 16) ("R" . 17) + ("S" . 18) ("T" . 19) ("U" . 20) + ("V" . 21) ("W" . 22) ("X" . 23) ("Y" . 24) + ("Z" . 25) ("2" . 26) ("3" . 27) + ("4" . 28) ("5" . 29) ("6" . 30) ("7" . 31))) + (aset tbl (string-to-char (car mapping)) (cdr mapping))) + tbl) + "Base-32 mapping table, as defined in RFC 4648.") + +(defun base32-hex-decode (string) + "The cheats' version of base-32 decode. + +This is not a 100% faithful implementation of RFC 4648. The +concept of encoding partial quanta is not implemented fully. + +No attempt is made to pad the output either as that is not +required for HMAC-TOTP." + (unless (mod (length string) 8) + (error "Padding is incorrect")) + (setq string (upcase string)) + (let ((trimmed-array (append (string-trim-right string "=+") nil))) + (format "%X" (seq-reduce + (lambda (acc char) (+ (ash acc 5) (aref base32-alphabet char))) + trimmed-array 0)))) + +(defun keyfile-string (filename) + "Return a string which is the filepath to a `pass(1)' file." + (let ((directory "~/.password-store/")) + (if (null filename) + (read-file-name "Select a password: " directory nil t nil) + (concat directory + (if (stringp filename) + filename + (symbol-name filename)) + ".gpg")))) + +(defun get-totp (&optional filename) + "Generate a totp code given a file with the secret hex string. +If no filename given, prompt for one. +Assumes the file has the string by itself on the last line of the file, +similar to what pass-otp does, but without the full uri. +Also assumes the file is in `~/.password-store' and has the `.gpg' extension." + (totp (base32-hex-decode (re/get-line-from-file (keyfile-string filename) -1)))) + +(defun otp-to-clipboard (&optional filename) + "Copy the returned totp code to the clipboard." + (interactive) + (kill-new (get-totp filename))) + +;;; Emacs Control + +(defun re/kill-emacs () + "Save all open files and kill emacs." + (interactive) + (save-some-buffers t) + (kill-emacs)) + +(keymap-global-set "C-x M-c" #'re/kill-emacs) + +(defun re/restart-emacs () + "Save all open files and restart emacs." + (interactive) + (save-some-buffers t) + (kill-emacs nil t)) + +(keymap-global-set "C-x M-r" #'re/restart-emacs) + +(defun re/keyboard-quit () + "Smarter version of `keyboard-quit'. +Close the minibuffer if not focused when hit. +Stolen from emacsredux.com." + (interactive) + (if (active-minibuffer-window) + (if (minibufferp) + (minibuffer-keyboard-quit) + (abort-recursive-edit)) + (keyboard-quit))) + +(keymap-global-set " " #'re/keyboard-quit) ; Make C-g better +(keymap-global-set "C-x S" (lambda () (interactive) (save-some-buffers t))) ; don't ask +(keymap-global-set "C-x c" #'delete-frame) ; delete a frame without asking about saving files + +;;; Version-control mode + +(defun re/vc-clone () + "Interactively clone with vc-mode. +Prompt for url and local dir." + (interactive) + (let* ((url (read-string "Repository URL: ")) + (dir (read-string "Local Dir: " (file-name-base url)))) + (vc-git-clone url dir nil))) + +(keymap-global-set "C-x v C" #'re/vc-clone) + +(defun re/vc-show-branches (&optional arg) + "Display all Git branches in a separate buffer. +Remotes as well when arg." + (interactive "P") + (let ((default-directory (if (boundp 'vc-dir-directory) + vc-dir-directory + default-directory))) + (vc-git-command "*git-branches*" + nil + nil + "branch" + "--verbose" + (when arg "--remotes")) + (pop-to-buffer "*git-branches*") + (goto-char (point-min)) + (special-mode))) + +(keymap-global-set "C-x v b b" #'re/vc-show-branches) + +(defun re/vc-fetch (&optional arg) + "Interactively fetch with vc-mode. +Allows separate fetch in addition to pull. Only care about git. +With arg, ask for remote, otherwise fetch all." + (interactive "P") + (let* ((default-directory (if (boundp 'vc-dir-directory) + vc-dir-directory + default-directory))) + (vc-git-command "*vc-fetch*" + nil + nil + "fetch" + (if arg + (read-string "Fetch From: " "origin") + "--all") + "--verbose") + (pop-to-buffer "*vc-fetch*") + (goto-char (point-min)) + (special-mode))) + +(keymap-global-set "C-x v f" #'re/vc-fetch) + +(add-hook 'vc-dir-mode-hook + (lambda () + (keymap-set vc-dir-mode-map "b b" #'re/vc-show-branches) + (keymap-set vc-dir-mode-map "f" #'re/vc-fetch) + (keymap-set vc-dir-mode-map "k" #'vc-revert))) + +(setq vc-make-backup-files nil + vc-handled-backends '(jj Git) + vc-git-log-switches '("--oneline" "--graph" "--decorate" "--all") + vc-git-log-edit-summary-target-len 50 + vc-find-revision-no-save t) + +;;; Backups + +(setq backup-directory-alist `((".*" . "~/emacs-backups/")) + backup-by-copying t + delete-old-versions t + kept-new-versions 5 + kept-old-versions 5) + +;;; Any Lisp Repl + +(defmacro re/send-on-close-paren (executor) + "Generalizes sending an execute on close paren. + Interactively call `executor'. For `executor', use whatever + function is called by `' in the applicable REPL. For a + non-repl, use `newline' or similar." + `(lambda (&optional arg) + (interactive "p") + (insert-char 41 arg) + (call-interactively ,executor))) + +(defun re/sexp-drop-paren-p () + "Returns t if sexp needs fewer parens to balance." + (< (car (syntax-ppss)) 0)) + +(defun re/sexp-need-paren-p () + "Returns t if sexp needs more parens to balance." + (> (car (syntax-ppss)) 0)) + +(defun re/sexp-unbalanced-count () + "Returns distance from balanced sexp." + (abs (car (syntax-ppss)))) + +(defun re/balance-sexp () + "Balance the sexp before point. +Either delete, move forward, or add as many ')' as needed." + (let ((distance (re/sexp-unbalanced-count))) + (cond ((re/sexp-need-paren-p) + (if (looking-at ")") + (progn + (forward-char) + (re/balance-sexp)) + (insert-char 41 distance))) + ((re/sexp-drop-paren-p) + (backward-delete-char distance))))) + +(defmacro re/balance-and-eval-sexp (executor) + "Balance the sexp before point, then call `executor'. +Call `re/balance-sexp', then interactively call `executor'. Bind +this to ']' for the interlisp experience. For `executor', use +whatever function is called by `' in the applicable REPL. +For a non-repl, use `newline' or similar." + `(lambda () (interactive) (re/balance-sexp) (call-interactively ,executor))) + +(defvar *re/lisp-mode-hooks* + '(emacs-lisp-mode-hook + lisp-mode-hook + sly-mode-hook + scheme-mode-hook + geiser-mode-hook + lisp-interaction-mode-hook)) + +(dolist (hook *re/lisp-mode-hooks*) + (add-hook hook + (lambda () + (when (bound-and-true-p acme-mouse-mode) (acme-mouse-mode -1)) + (sedit-mouse-mode 1) + (keymap-set lisp-mode-shared-map "C-M-z" #'eval-region) + (keymap-set lisp-mode-shared-map + "C-M-S-q" + #'sedit-auto-prettify-sexp) + (keymap-set lisp-mode-shared-map + "]" + (re/balance-and-eval-sexp #'sedit-auto-prettify-sexp)) + (keymap-set lisp-interaction-mode-map + "]" + (re/balance-and-eval-sexp #'eval-print-last-sexp))))) + +;;; IELM +;; Rarely used, but nice to have when I want it. + +(add-hook 'ielm-mode-hook + (lambda () + (keymap-set inferior-emacs-lisp-mode-map + "C-l" + #'comint-clear-buffer) + (let ((eval-function #'ielm-send-input)) + (keymap-set inferior-emacs-lisp-mode-map + ")" + (re/send-on-close-paren eval-function)) + (keymap-set inferior-emacs-lisp-mode-map + "]" + (re/balance-and-eval-sexp eval-function))))) + +;;; Eshell + +(keymap-global-set "C-c e" #'eshell) + +(defun eshell-quit-or-delete-char (arg) + "Delete char if at one, quit eshell if on empty prompt. +Stolen from https://depp.brause.cc/dotemacs" + (interactive "p") + (if (and (eolp) (looking-back eshell-prompt-regexp 0 t)) + (eshell-life-is-too-much) + (delete-char arg))) + +(add-hook 'eshell-mode-hook + (lambda () + (keymap-set eshell-mode-map "C-d" #'eshell-quit-or-delete-char) + (let ((eval-function #'eshell-send-input)) + (keymap-set eshell-mode-map + ")" + (re/send-on-close-paren eval-function)) + (keymap-set eshell-mode-map + "]" + (re/balance-and-eval-sexp eval-function))))) + +;;; C-mode + +(dolist (mode-maps '(("\\.keymap\\'" . c-mode) ("\\.dtsi\\'" . c-mode))) + (add-to-list 'auto-mode-alist mode-maps t)) + +(defun re/c-lineup-arglist-tabs-only (ignored) + "Line up argument lists by tabs, not spaces. + Stolen from https://kernel.org/doc/html/v4.10/process/coding-style.html" + (let* ((anchor (c-langelem-pos c-syntactic-element)) + (column (c-langelem-2nd-pos c-syntactic-element)) + (offset (- (1+ column) anchor)) + (steps (floor offset c-basic-offset))) + (* (max steps 1) c-basic-offset))) + +(add-hook 'c-mode-common-hook + (lambda () + (c-add-style "linux-tabs-only" + '("linux" (c-offsets-alist + (arglist-cont-nonempty + c-lineup-gcc-asm-reg + re/c-lineup-arglist-tabs-only)))))) + +(add-hook 'c-mode-hook + (lambda () + (setq c-backspace-function 'backward-delete-char) + (c-set-style "linux-tabs-only"))) + +;;; Auto-Revert + +(global-auto-revert-mode) +(setq global-auto-revert-non-file-buffers nil) + +;;; Crash Handliŋ + +(setq attempt-stack-overflow-recovery nil + attempt-orderly-shutdown-on-fatal-signal nil) + +;;; Abbrev-mode and skeletons + +(define-skeleton workorder-link + "part of a link to a workorder, just enter the number." + "" + "https://eservice.r717.net/index.php/PLC_workorders/store/" + _ -) + +(define-skeleton skel-org-block + "Add a source block to an org file." + "" + "#+BEGIN_SRC" + \n + _ - + \n + "#+END_SRC") + +(define-skeleton skel-org-block-elisp + "Add an elisp source block to an org file." + "" + "#+BEGIN_SRC emacs-lisp" + \n + _ - + \n + "#+END_SRC") + +(setq save-abbrevs 'silently) + +(add-hook 'org-mode-hook #'abbrev-mode) + +;;; Customization Framework +;; Disable it by making it save to a temp file (stolen from prot) + +(setq custom-file (make-temp-file "emacs-custom-"))