diff --git a/early-init.el b/early-init.el index 1bf78eb..352d4e6 100644 --- a/early-init.el +++ b/early-init.el @@ -1,10 +1,9 @@ -;;; early-init.el -*- lexical-binding: t; coding: utf-8 -*- -;; Copyright (C) 2020-2021 Case Duckworth +;;; early-init.el -*- lexical-binding: t; coding: utf-8-unix -*- ;; ;; Author: Case Duckworth ;; Created: Sometime during Covid-19, 2020 ;; Keywords: configuration -;; URL https://tildegit.org/acdw/emacs +;; URL: https://tildegit.org/acdw/emacs ;; ;; This file is NOT part of GNU Emacs. ;; @@ -23,174 +22,85 @@ ;; ;;; Code: -;; Speed up init -(setq gc-cons-threshold most-positive-fixnum - gc-cons-percentage 0.6 - comp-deferred-compilation nil) - -(defconst gc-cons-basis (* 800 1024) - "The basis value to which to return after a max jump. -800,000 (800 KB) is Emacs' default.") - -(add-hook 'after-init-hook #'(lambda () - (setq gc-cons-threshold gc-cons-basis - gc-cons-percentage 0.1))) - -(defun hook--gc-cons-maximize () - "Set `gc-cons-threshold' to the highest possible. - For memory-intensive features." - (setq gc-cons-threshold most-positive-fixnum)) - -(defun hook--gc-cons-baseline () - "Return `gc-cons-threshold' to `gc-cons-basis'. - For after memory intensive operations." - (setq gc-cons-threshold gc-cons-basis)) - -(add-hook 'minibuffer-setup-hook #'hook--gc-cons-maximize) -(add-hook 'minibuffer-exit-hook #'hook--gc-cons-baseline) - -;; From doom-emacs -(unless (daemonp) - (defvar doom--initial-file-name-handler-alist file-name-handler-alist) - (setq file-name-handler-alist nil) - (defun hook--reset-file-handler-alist () - (dolist (handler file-name-handler-alist) - (add-to-list 'doom--initial-file-name-handler-alist handler)) - (setq file-name-handler-alist doom--initial-file-name-handler-alist)) - (add-hook 'emacs-startup-hook #'hook--reset-file-handler-alist)) - -;; Where are we? +;;; Define personal-use constants (defconst acdw/system (pcase system-type ('gnu/linux :home) ((or 'msdos 'windows-nt) :work) - (_ :other))) + (_ :other)) + "Which system is currently being used.") -;; Frame initiation +(defvar acdw/dir (expand-file-name + (convert-standard-filename "var/") + user-emacs-directory) + "A directory to hold extra configuration and emacs data.") -;; Initialize frames with as little UI as possible. -(setq-default - default-frame-alist ; The default look of frames - `((tool-bar-lines . 0) ; Remove tool bar - (menu-bar-lines . 0) ; Remove menu bar - (vertical-scroll-bars) ; Remove vertical scroll bars - (horizontal-scroll-bars) ; Remove horizontal scroll bars - (width . 84) ; A /little/ wider than `fill-column' - (height . 30) ; Text characters - (left-fringe . 8) ; Width of fringes - (right-fringe . 8) ; (8 is the default) - (font . ,(pcase acdw/system ; Default font - (:home "Terminus 12") - (:work "Consolas 11"))) - ) +;;; Speed up init +;; see doom-emacs, et al. - x-underline-at-descent-line t ; underline at the descent line +(defconst gc-cons-threshold-basis (* 800 1000) + "The basis value for `gc-cons-threshold' to return to after a jump. +800 KB is Emacs's default `gc-cons-threshold'.") - scroll-margin 0 ; how many lines to show at window edge - scroll-conservatively 101 ; just enough to bring text into view - scroll-preserve-screen-position 1 ; always keep screen position - - frame-title-format ; Titles for frames - '((:eval (if (buffer-file-name) ; (cf. `mode-line-format') - (abbreviate-file-name (buffer-file-name)) - "%b")) - " " - mode-line-client - mode-line-modified - " - GNU Emacs") +(defconst gc-cons-percentage-basis 0.1 + "The basis value for `gc-cons-percentage' to return to after init. +0.1 is Emacs's default `gc-cons-percentage'.") - mode-line-format ; Mode line - `("%e" - mode-line-front-space - ;; mode-line-mule-info - mode-line-client - mode-line-modified - mode-line-remote - mode-line-frame-identification - mode-line-buffer-identification " " - mode-line-position - (vc-mode vc-mode) " " - minions-mode-line-modes - mode-line-misc-info - mode-line-end-spaces - )) +(defvar orig-file-name-handler-alist file-name-handler-alist + "The original value of `file-name-handler-alist' will be restored + after init.") -;; Set the rest of the fonts after initiation -(defun hook--setup-fonts () - (pcase acdw/system - (:home (set-face-attribute 'default nil - :family "Terminus" - :height 120) - (set-face-attribute 'fixed-pitch nil - :family "Terminus" - :height 1.0) - (set-face-attribute 'variable-pitch nil - :family "DejaVu Sans" - :height 1.0)) - (:work (set-face-attribute 'default nil - :family "Consolas" - :height 110) - (set-face-attribute 'fixed-pitch nil - :family "Consolas" - :height 1.0) - (set-face-attribute 'variable-pitch nil - :family "Cambria" - :height 1.0)))) +(setq gc-cons-threshold most-positive-fixnum + gc-cons-percentage 0.6 + file-name-handler-alist nil) -(add-hook 'after-init-hook #'hook--setup-fonts) +(defun hook--post-init-reset () + "Reset `gc-cons-threshold', `gc-cons-percentage', and + `file-name-handler-alist' to their defaults after init." + (setq gc-cons-threshold gc-cons-threshold-basis + gc-cons-percentage gc-cons-percentage-basis) + (dolist (handler file-name-handler-alist) + (add-to-list 'orig-file-name-handler-alist handler)) + (setq file-name-handler-alist orig-file-name-handler-alist)) -;; In case I do want the UI elements later, I also disable the modes -;; -- otherwise I'd have to run the mode twice to actually show the -;; thing. +(add-hook 'after-init-hook #'hook--post-init-reset) + +;; ;;; Frame settings + +(setq default-frame-alist ; Remove most UI + `((tool-bar-lines . 0) ; No tool bar + (menu-bar-lines . 0) ; No menu bar + (vertical-scroll-bars) ; No scroll bars + (horizontal-scroll-bars) ; ... at all + (width . 84) ; A /little/ wider than + ; `fill-column' (set later) + (height . 30) + (left-fringe . 8) ; Width of fringes + (right-fringe . 8) ; (8 is default) + (font . ,(pcase acdw/system + (:home "Terminus 12") + (:work "Consolas 10")))) + frame-inhibit-implied-resize t ; Don't resize randomly + frame-resize-pixelwise t ; Resize by pixels, not chars + ) (defun hook--disable-ui-modes () - (dolist (mode '(tool-bar-mode - menu-bar-mode - scroll-bar-mode - horizontal-scroll-bar-mode)) - (funcall mode -1))) + "Disable frame UI using modes, for toggling later." + (dolist (mode ;; each mode is of the form (MODE . FRAME-ALIST-VAR) + '((tool-bar-mode . tool-bar-lines) + (menu-bar-mode . menu-bar-lines) + (scroll-bar-mode . vertical-scroll-bars) + (horizontal-scroll-bar-mode . horizontal-scroll-bars) + )) + (let ((setting (alist-get (cdr mode) default-frame-alist))) + (when (or (not setting) + (= 0 setting)) + (funcall (car mode) -1))))) -;; I run it on the `after-init-hook' so it doesn't slow down init so much. (add-hook 'after-init-hook #'hook--disable-ui-modes) -;; Customize the fringe -(setq-default - indicate-empty-lines t ; show an indicator at the end of the buffer - indicate-buffer-boundaries 'right ; show buffer boundaries on the right - visual-line-fringe-indicators ; show continuation indicators on the left - '(left-curly-arrow nil)) - -(defun hook--setup-fringe-bitmaps () - (define-fringe-bitmap 'left-curly-arrow - [#b11000000 - #b01100000 - #b00110000 - #b00011000]) - (define-fringe-bitmap 'right-curly-arrow - [#b00011000 - #b00110000 - #b01100000 - #b11000000]) - (define-fringe-bitmap 'left-arrow - [#b00000000 - #b01010100 - #b01010100 - #b00000000]) - (define-fringe-bitmap 'right-arrow - [#b00000000 - #b00101010 - #b00101010 - #b00000000]) - (remove-function after-focus-change-function #'hook--setup-fringe-bitmaps)) -(add-function :after after-focus-change-function #'hook--setup-fringe-bitmaps) - -;; Resize like it's 2021 -(setq-default frame-inhibit-implied-resize t - frame-resize-pixelwise t) - -;; Bootstrap package manager (`straight') - -;; First, I need to make sure it's in the `exec-path'. +;;; Bootstrap package manager (`straight.el') +;; 1. Update `exec-path'. (let ((win-app-dir "~/Applications")) (dolist (path (list ;; Windows @@ -210,26 +120,19 @@ )) (when (file-exists-p path) (add-to-list 'exec-path path :append)))) - -;; Set $PATH back to `exec-path', for symmetry's sake. +;; 1.5. Update $PATH to reflect changes. (setenv "PATH" (mapconcat #'identity exec-path path-separator)) -;; Set some variables -(defvar acdw/etc-dir - (expand-file-name "etc/" user-emacs-directory) - "Where to put other configurations.") -(defvar acdw/var-dir - (expand-file-name "var/" user-emacs-directory) - "Where to put variable stuff.") +;; 2. Set `package' and `straight' variables. +(setq package-enable-at-startup nil ; not sure if strictly + ; necessary + package-quickstart nil ; ditto + straight-host-usernames '((github . "duckwork") + (gitlab . "acdw")) + straight-base-dir acdw/dir ; don't clutter ~/.emacs.d + ) -(setq-default package-enable-at-startup nil - package-quickstart nil - straight-use-package-by-default t - straight-host-usernames '((github . "duckwork") - (gitlab . "acdw")) - straight-base-dir acdw/var-dir) - -;; Run `straight''s bootstrap code +;; 3. Bootstrap `straight'. (defvar bootstrap-version) (let ((bootstrap-file (expand-file-name @@ -239,20 +142,17 @@ (unless (file-exists-p bootstrap-file) (with-current-buffer (url-retrieve-synchronously - "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el" + (concat "https://raw.githubusercontent.com/" + "raxod502/straight.el/develop/install.el") 'silent 'inhibit-cookies) (goto-char (point-max)) (eval-print-last-sexp))) (load bootstrap-file nil 'nomessage)) -;; `use-package' +;;; Message startup time for profiling -(straight-use-package 'use-package) -(require 'use-package) - -;; Message startup time (defun hook--message-startup-time () - "Message Emacs' startup time." + "Show Emacs's startup time in the message buffer. For profiling." (message "Emacs ready in %s with %d garbage collections." (format "%.2f seconds" (float-time (time-subtract after-init-time diff --git a/init.el b/init.el index 94e2a0e..7f6da30 100644 --- a/init.el +++ b/init.el @@ -1,10 +1,10 @@ -;;; init.el -*- lexical-binding: t; coding: utf-8 -*- -;; Copyright (C) 2020-2021 Case Duckworth +:;;; init.el -*- lexical-binding: t; coding: utf-8-unix -*- ;; ;; Author: Case Duckworth ;; Created: Sometime during Covid-19, 2020 ;; Keywords: configuration -;; URL https://tildegit.org/acdw/emacs +;; URL: https://tildegit.org/acdw/emacs +;; Bankruptcy: 5b ;; ;; This file is NOT part of GNU Emacs. ;; @@ -16,1024 +16,12 @@ ;; - Don't hurt yourself. ;; - Make good choices. ;; -;;; Comentary: -;; -;;; Research: -;; (map! :leader (:prefix "w" :desc "Toggle full screen buffer" "f" -;; #'toggle-maximize-buffer)) -;; ;;; Code: -;; User information -(setq user-full-name "Case Duckworth" - user-mail-address "acdw@acdw.net" - calendar-location-name "Baton Rouge, LA" - calendar-latitude 30.4 - calendar-longitude -91.1 - calendar-date-style 'iso - custom-file (expand-file-name "custom.el" acdw/etc-dir)) +;; Add `acdw.el' +(add-to-list 'load-path (expand-file-name "lisp/" + user-emacs-directory)) +(require 'acdw) -;; Load newer files first -(setq-default load-prefer-newer t) - -;; No littering -(use-package no-littering - :demand - :init (setq no-littering-etc-directory acdw/etc-dir - no-littering-var-directory acdw/var-dir)) - -(defun when-unfocused (func &rest args) - "Run FUNC with ARGS iff all frames are out of focus." - (when (seq-every-p #'null (mapcar #'frame-focus-state (frame-list))) - (apply func args))) - -(define-minor-mode acdw/reading-mode - "Make reading comfier." - :lighter " Read" - (if acdw/reading-mode - (progn ;; turn on - ;; (text-scale-increase +1) - (display-fill-column-indicator-mode -1) - (setq cursor-type 'hbar) - (dolist (func '(visual-fill-column-mode - iscroll-mode)) - (when (fboundp func) - (funcall func +1)))) - (progn ;; turn off - ;; (text-scale-increase 0) - (setq cursor-type 'bar) - (display-fill-column-indicator-mode +1) - (dolist (func '(visual-fill-column-mode - iscroll-mode)) - (when (fboundp func) - (funcall func -1)))))) - -(defun hook--read-only-cursor () - (setq cursor-type (if buffer-read-only 'hbar 'bar))) -(add-hook 'read-only-mode-hook #'hook--read-only-cursor) - -;; Dialogs & alerts -(setq-default use-dialog-box nil) ; Don't use a dialog box -(fset 'yes-or-no-p #'y-or-n-p) - -(defun flash-mode-line () - (ding) - (invert-face 'mode-line) - (run-with-timer 0.2 nil #'invert-face 'mode-line)) - -(setq-default visible-bell nil ; Don't use a visible bell - ring-bell-function #'flash-mode-line) - -(defun hook--gc-when-unfocused () - (when-unfocused #'garbage-collect)) - -(add-function :after after-focus-change-function - #'hook--gc-when-unfocused) - -;; Minibuffer -(setq-default - minibuffer-prompt-properties '(read-only t - cursor-intangible t - face minibuffer-prompt) - enable-recursive-minibuffers t - file-name-shadow-properties '(invisible t)) -(file-name-shadow-mode +1) -(minibuffer-depth-indicate-mode +1) - -(use-package savehist - :straight nil - :init - (setq-default - savehist-file (expand-file-name "history" acdw/var-dir) - savehist-additional-variables '(kill-ring search-ring regexp-search-ring) - history-length t - history-delete-duplicates t - savehist-autosave-interval 60) - :config (savehist-mode +1)) - -;; Backups -(setq-default backup-by-copying t - delete-old-versions -1 ; Don't delete old versions - version-control t ; Make numeric backups - vc-make-backup-files t ; Backup version-controlled files - ) - -(let ((dir (expand-file-name "backup" acdw/var-dir))) - (make-directory dir 'parents) - (setq-default backup-directory-alist - `((".*" . ,dir)))) - -;; Lockfiles -(setq-default create-lockfiles nil) ; Are these necessary? - -;; Autosaves -(setq auto-save-default nil ; Don't use `auto-save' system - ) -(use-package super-save - :defer 5 ; This package can wait - :init - (setq-default - super-save-remote-files nil ; Don't save remote files - super-save-exclude '(".gpg") ; Wouldn't work anyway - super-save-auto-save-when-idle t) - :config - (super-save-mode +1)) - -;; Auto-revert -(global-auto-revert-mode +1) ; Automatically revert a file - ; to its on-disk contents - -(use-package saveplace - :straight nil - :init - (setq-default - save-place-file (expand-file-name "places" acdw/var-dir) - save-place-forget-unreadable-files (eq acdw/system :home)) - :config (save-place-mode +1)) - -(use-package recentf - :straight nil - :init - (setq recentf-save-file (expand-file-name "recentf" acdw/var-dir) - recentf-max-menu-items 100 - recentf-max-saved-items nil - recentf-auto-cleanup 'never) - (defun maybe-save-recentf () - "Save `recentf-file' every five minutes, but only when out of focus." - (defvar recentf--last-save (time-convert nil 'integer) - "When we last saved the `recentf-save-list'.") - - (when (> (time-convert (time-since recentf--last-save) 'integer) - (* 60 5)) - (setq-default recentf--last-save (time-convert nil 'integer)) - (when-unfocused #'recentf-save-list))) - :config - (recentf-mode +1) - (add-to-list 'recentf-exclude acdw/var-dir) - (add-to-list 'recentf-exclude acdw/etc-dir) - (add-function :after after-focus-change-function - #'maybe-save-recentf)) - - -;; Uniquify -(use-package uniquify - :straight nil - :init - (setq-default - uniquify-buffer-name-style 'forward ; bubble 'up' the directory tree - uniquify-separator "/" ; separate path elements - uniquify-after-kill-buffer-p t ; hook into buffer kills - uniquify-ignore-buffers-re "^\\*" ; don't worry about special buffers - )) - -;; Scratch -(setq-default - inhibit-startup-screen t ; Don't show the splash screen - initial-buffer-choice t ; Start on *scratch* - initial-scratch-message - (concat ";; Howdy, " - (nth 0 (split-string user-full-name)) "!" - " Welcome to GNU Emacs.\n\n")) - -(defun immortal-scratch () - "Don't kill *scratch* when asked to by `kill-buffer'." - (if (not (eq (current-buffer) (get-buffer "*scratch*"))) - t - (bury-buffer) - nil)) -(add-hook 'kill-buffer-query-functions #'immortal-scratch) - -;; Easier buffer-killing -(defun kill-a-buffer (&optional prefix) - "Kill a buffer and its window, prompting only on unsaved changes. - -`kill-a-buffer' uses the PREFIX argument to determine which buffer(s) to kill: -0 => Kill THIS buffer & window -4 (C-u) => Kill OTHER buffer & window -16 (C-u C-u) => Run the default `kill-buffer'." - (interactive "P") - (pcase (or (car prefix) 0) - (0 (kill-current-buffer) - (unless (one-window-p) (delete-window))) - (4 (other-window 1) - (kill-current-buffer) - (unless (one-window-p) (delete-window))) - (16 (let ((current-prefix-arg nil)) - (kill-buffer))))) - -(bind-key "C-x k" #'kill-a-buffer) - -;; UTF-8 with LF line endings -(set-charset-priority 'unicode) -(set-language-environment "UTF-8") - -(prefer-coding-system 'utf-8-unix) -(set-default-coding-systems 'utf-8-unix) -(set-terminal-coding-system 'utf-8-unix) -(set-keyboard-coding-system 'utf-8-unix) -(set-selection-coding-system 'utf-8-unix) - -(setq-default - locale-coding-system 'utf-8-unix - coding-system-for-read 'utf-8-unix - coding-system-for-write 'utf-8-unix - buffer-file-coding-system 'utf-8-unix - - org-export-coding-system 'utf-8-unix - org-html-coding-system 'utf-8-unix ; doesn't take from above - - default-process-coding-system '(utf-8-unix . utf-8-unix) - x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING)) - -(defun ewiki/no-junk-please-were-unixish () - "Convert line endings to UNIX, dammit." - (let ((coding-str (symbol-name buffer-file-coding-system))) - (when (string-match "-\\(?:dos\\|mac\\)$" coding-str) - (set-buffer-file-coding-system 'unix)))) - -(add-hook 'find-file-hook #'ewiki/no-junk-please-were-unixish) -(add-hook 'before-save-hook #'ewiki/no-junk-please-were-unixish) - -;; Cursor -(setq-default cursor-type 'bar - cursor-in-non-selected-windows nil) -(blink-cursor-mode 0) - -;; Filling text -(setq-default fill-column 80) -(global-display-fill-column-indicator-mode +1) - -(bind-key "C-x f" #'find-file) ; I don't set `fill-column', ever - -(setq-default comment-auto-fill-only-comments t) -;; Enable `auto-fill-mode' everywhere -(add-hook 'text-mode-hook #'auto-fill-mode) -(add-hook 'prog-mode-hook #'auto-fill-mode) -;; Also enable `visual-line-mode' everywhere -(global-visual-line-mode +1) -;; "Fix" `visual-line-mode' in `org-mode' -(defun hook--visual-line-fix-org-keys () - (when (derived-mode-p 'org-mode) - (local-set-key (kbd "C-a") #'org-beginning-of-line) - (local-set-key (kbd "C-e") #'org-end-of-line) - (local-set-key (kbd "C-k") #'org-kill-line))) -(add-hook 'visual-line-mode-hook #'hook--visual-line-fix-org-keys) - -(dolist (margin '(right-margin left-margin)) - (dolist (button '(mouse-1 mouse-2 mouse-3)) - (global-set-key (vector margin button) - (global-key-binding (vector button))))) - -(mouse-wheel-mode +1) - -(when (bound-and-true-p mouse-wheel-mode) - (dolist (margin '(right-margin left-margin)) - (dolist (event '(mouse-wheel-down-event - mouse-wheel-up-event - wheel-down - wheel-up - mouse-4 - mouse-5)) - (global-set-key (vector margin event) #'mwheel-scroll)))) - -(use-package visual-fill-column - :init (setq-default visual-fill-column-center-text t) - (add-hook 'visual-fill-column-mode-hook #'visual-line-mode) - :config - (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust)) - -(when (fboundp 'global-so-long-mode) - (global-so-long-mode +1)) - -;; Whitespace -(setq-default whitespace-style '(empty ; remove blank lines at buffer edges - indentation ; clean up indentation - ;; mixed tabs & spaces - space-before-tab - space-after-tab)) -(add-hook 'before-save-hook #'whitespace-cleanup) - -(setq-default indent-tabs-mode t - tab-width 8) - -(use-package smart-tabs-mode - :config - (smart-tabs-insinuate 'c 'c++ 'java 'javascript 'cperl 'python 'ruby 'nxml)) - -;; Window layouts -(setq-default - split-width-threshold 100 ; minimum width for window splits - split-height-threshold 50 ; minimum height for window splits - display-buffer-alist ; how to display buffers - '((".*" . (display-buffer-reuse-window display-buffer-same-window))) - display-buffer-reuse-frames t ; allow reuse of frames - even-window-sizes nil ; avoid resizing windows to even them - help-window-select t ; select *Help* window when opened - ) - -(defun vsplit-other-window () - "Split the window vertically and switch to the new window." - (interactive) - (split-window-vertically) - (other-window 1 nil)) - -(defun hsplit-other-window () - "Split the window horizontally and switch to the new window." - (interactive) - (split-window-horizontally) - (other-window 1 nil)) - -(bind-key "C-x 2" #'vsplit-other-window) -(bind-key "C-x 3" #'hsplit-other-window) - -;; Theming - -(use-package form-feed - :demand - :config (global-form-feed-mode +1)) - -(use-package modus-themes - :straight (:host gitlab :repo "protesilaos/modus-themes") - :demand - :init - (setq-default modus-themes-slanted-constructs t - modus-themes-bold-constructs t - modus-themes-region 'bg-only - modus-themes-org-blocks 'grayscale - modus-themes-headings '((1 . section) - (t . no-color)) - modus-themes-scale-headings nil - modus-themes-mode-line nil) - :custom-face - (modus-theme-heading-1 - ((t (:inherit (modus-theme-heading-1 fixed-pitch bold))))) - (modus-theme-heading-2 - ((t (:inherit (modus-theme-heading-2 fixed-pitch bold))))) - (modus-theme-heading-3 - ((t (:inherit (modus-theme-heading-3 fixed-pitch bold))))) - (modus-theme-heading-4 - ((t (:inherit (modus-theme-heading-4 fixed-pitch bold))))) - (modus-theme-heading-5 - ((t (:inherit (modus-theme-heading-5 fixed-pitch bold))))) - (modus-theme-heading-6 - ((t (:inherit (modus-theme-heading-6 fixed-pitch bold))))) - (modus-theme-heading-7 - ((t (:inherit (modus-theme-heading-7 fixed-pitch bold))))) - (modus-theme-heading-8 - ((t (:inherit (modus-theme-heading-8 fixed-pitch bold)))))) - -;; Change themes based on time of day - -(defun acdw/run-with-sun (sunrise-command sunset-command) - "Run commands at sunrise and sunset." - (let* ((times-regex (rx (* nonl) - (: (any ?s ?S) "unrise") " " - (group (repeat 1 2 digit) ":" - (repeat 1 2 digit) - (: (any ?a ?A ?p ?P) (any ?m ?M))) - (* nonl) - (: (any ?s ?S) "unset") " " - (group (repeat 1 2 digit) ":" - (repeat 1 2 digit) - (: (any ?a ?A ?p ?P) (any ?m ?M))) - (* nonl))) - (ss (sunrise-sunset)) - (_m (string-match times-regex ss)) - (sunrise-time (match-string 1 ss)) - (sunset-time (match-string 2 ss))) - (run-at-time sunrise-time (* 60 60 24) sunrise-command) - (run-at-time sunset-time (* 60 60 24) sunset-command) - (run-at-time "0:00" (* 60 60 24) sunset-command))) - -(acdw/run-with-sun #'modus-themes-load-operandi - #'modus-themes-load-vivendi) - -(use-package minions - :config (minions-mode +1)) - -(use-package which-key - :config (which-key-mode +1)) - -(delete-selection-mode +1) - -(setq-default - save-interprogram-paste-before-kill t ; save existing text before replacing - yank-pop-change-selection t ; update X selection when rotating ring - x-select-enable-clipboard t ; Enable X clipboards - x-select-enable-primary t - mouse-drag-copy-region t ; Copy a region when mouse-selected - kill-do-not-save-duplicates t ; Don't append the same thing twice - ) - -(use-package smartscan - :config (global-smartscan-mode +1)) - -(when (fboundp 'global-goto-address-mode) - (global-goto-address-mode +1)) - -(use-package flyspell - :init - (setenv "LANG" "en_US") - (setq-default ispell-program-name "hunspell" - ispell-dictionary "en_US" - ispell-personal-dictionary "~/.hunspell_personal") - :hook - (text-mode . flyspell-mode) - (prog-mode . flyspell-prog-mode) - :config - (ispell-set-spellchecker-params) - (unless (file-exists-p ispell-personal-dictionary) - (write-region "" nil ispell-personal-dictionary nil 0))) - -(use-package flyspell-correct - :after flyspell - :bind (:map flyspell-mode-map - ("C-;" . flyspell-correct-wrapper))) - -(setq-default show-paren-delay 0 - show-paren-style 'mixed - show-paren-when-point-inside-paren t - show-paren-when-point-in-periphery t) -(show-paren-mode +1) - -(add-hook 'prog-mode-hook #'electric-pair-local-mode) - -(setq-default prettify-symbols-unprettify-at-point 'right-edge) -(add-hook 'prog-mode-hook #'prettify-symbols-mode) - -(add-hook 'after-save-hook - #'executable-make-buffer-file-executable-if-script-p) - -(setq-default compilation-ask-about-save nil ; just save the buffer - compilation-always-kill t ; kill the processes without asking - compilation-scroll-output 'first-error) - -(use-package reformatter - :demand) - -(setq-default smie-indent-basic 8) - -;; Shell scripts -(setq-default sh-basic-offset 8 - ;; try to indent like shfmt - sh-indent-after-case 0 - sh-indent-for-case-alt '+ - sh-indent-for-case-label 0) - -(use-package flymake-shellcheck - :when (executable-find "shellcheck") - :hook (sh-mode . flymake-shellcheck-load)) - -(when (executable-find "shfmt") - (reformatter-define sh-format - :program "shfmt" - :lighter "Shfmt") - (add-hook 'sh-mode-hook #'sh-format-on-save-mode)) - -(bind-key "M-/" #'hippie-expand) - -;; Tabs -(setq-default - tab-bar-show 1 ; show the tab bar when more than one - tab-bar-new-tab-choice "*scratch*" ; what to show on a new tab - tab-bar-tab-name-function ; how to name a new tab - #'tab-bar-tab-name-current-with-count - tab-bar-history-limit 25 ; how many tabs to save in history - ) - -(tab-bar-history-mode +1) - -;; Smart hungry delete -(use-package smart-hungry-delete - :defer nil - :bind (("" . smart-hungry-delete-backward-char) - ("C-d" . smart-hungry-delete-forward-char)) - :config (smart-hungry-delete-add-default-hooks)) - -;; Enable all commands -(setq-default disabled-command-function nil) - -;; Magit -(use-package magit - :bind ("C-x g" . magit-status) - :init (when (eq acdw/system :work) - (setenv "GIT_ASKPASS" "git-gui--askpass")) - (defun magit-display-buffer-same-window (buffer) - "Show `magit' in the same buffer, like god intended." - (display-buffer buffer '(display-buffer-same-window))) - (setq magit-display-buffer-function 'magit-display-buffer-same-window - magit-popup-display-buffer-action '((display-buffer-same-window)))) -(use-package forge - :after magit) -(use-package gitattributes-mode - :mode "\\.gitattributes\\'") -(use-package gitconfig-mode - :mode "\\.gitconfig\\'") -(use-package gitignore-mode - :mode "\\.gitignore\\'") - -;; crux -(use-package crux - :straight (:host github :repo "bbatsov/crux") - :bind - ("M-o" . crux-other-window-or-switch-buffer) - :config - (crux-with-region-or-line kill-ring-save) - (crux-with-region-or-line kill-region) - (crux-with-region-or-line comment-or-uncomment-region)) - -;; Completion and... stuff -(setq-default - completion-ignore-case t - read-buffer-completion-ignore-case t - read-file-name-completion-ignore-case t - minibuffer-eldef-shorten-default t) - -(minibuffer-electric-default-mode +1) - -(use-package icomplete-vertical - :demand - :init - (setq-default - icomplete-delay-completions-threshold 0 - icomplete-max-delay-chars 0 - icomplete-compute-delay 0 - icomplete-show-matches-on-no-input t - icomplete-hide-common-prefix nil - icomplete-with-completion-tables t - icomplete-in-buffer t) - :bind (:map icomplete-minibuffer-map - ("" . icomplete-forward-completions) - ("C-n" . icomplete-forward-completions) - ("" . icomplete-backward-completions) - ("C-p" . icomplete-backward-completions) - ("C-v" . icomplete-vertical-toggle)) - :config - (fido-mode -1) - (icomplete-mode +1) - (icomplete-vertical-mode +1)) - -(use-package orderless - :after icomplete - :init (setq-default completion-styles '(orderless))) - -(use-package marginalia - :after icomplete - :init (setq-default marginalia-annotators - '(marginalia-annotators-heavy - marginalia-annotators-light)) - :config (marginalia-mode +1)) - -(use-package consult - :after icomplete - :bind (;; C-c bindings (mode-specific-map) - ("C-c h" . consult-history) - ("C-c m" . consult-mode-command) - ("C-c b" . consult-bookmark) - ("C-c k" . consult-kmacro) - ;; C-x bindings (ctl-x-map) - ("C-x M-:" . consult-complex-command) - ("C-x b" . consult-buffer) - ("C-x 4 b" . consult-buffer-other-window) - ("C-x 5 b" . consult-buffer-other-frame) - ;; Custom M-# bindings for fast register access - ("M-#" . consult-register-load) - ("M-'" . consult-register-store) - ("C-M-#" . consult-register) - ;; Other custom bindings - ("M-y" . consult-yank-pop) - (" a" . consult-apropos) - ;; M-g bindings (goto-map) - ("M-g e" . consult-compile-error) - ("M-g g" . consult-goto-line) - ("M-g M-g" . consult-goto-line) - ("M-g o" . consult-outline) - ("M-g m" . consult-mark) - ("M-g k" . consult-global-mark) - ("M-g i" . consult-imenu) - ("M-g I" . consult-project-imenu) - ;; M-s bindings (search-map) - ("M-s f" . consult-find) - ("M-s L" . consult-locate) - ("M-s g" . consult-grep) - ("M-s G" . consult-git-grep) - ("M-s r" . consult-ripgrep) - ("M-s l" . consult-line) - ("M-s m" . consult-multi-occur) - ("M-s k" . consult-keep-lines) - ("M-s u" . consult-focus-lines) - ;; Isearch integration - ("M-s e" . consult-isearch) - :map isearch-mode-map - ("M-e" . consult-isearch) - ("M-s e" . consult-isearch) - ("M-s l" . consult-line)) - :init - (setq register-preview-delay 0 - register-preview-function #'consult-register-format) - (advice-add #'register-preview :override #'consult-register-window) - (setq xref-show-xrefs-function #'consult-xref - xref-show-definitions-function #'consult-xref) - :config - ;; (setq consult-preview-key 'any) - ;; (setq consult-preview-key (kbd "M-p")) - (setq consult-narrow-key "<")) - -;; Language: Emacs-Lisp -(setq-default eval-expression-print-length nil ; don't limit print length - eval-expression-print-level nil - ) -;; indent like common lisp -(require 'cl-lib) -(setq-default lisp-indent-function #'common-lisp-indent-function) -(put 'cl-flet 'common-lisp-indent-function - (get 'flet 'common-lisp-indent-function)) -(put 'cl-labels 'common-lisp-indent-function - (get 'labels 'common-lisp-indent-function)) -(put 'if 'common-lisp-indent-function 2) -(put 'dotimes-protect 'common-lisp-indent-function - (get 'when 'common-lisp-indent-function)) -;; eldoc mode -(use-package eldoc - :straight nil - :init (setq-default eldoc-echo-area-display-truncation-message nil - eldoc-idle-delay 0)) - -;; Dired -(use-package dired - :straight nil - :init - (setq-default dired-recursive-copies 'always - dired-recursive-deletes 'always - delete-by-moving-to-trash t - dired-listing-switches "-AFgho --group-directories-first" - dired-auto-revert-buffer t - dired-dwim-target t) - :bind ("C-x C-j" . dired-jump)) -(defun hook--dired-mode () - (hl-line-mode +1) - (dired-hide-details-mode +1)) -(add-hook 'dired-mode-hook #'hook--dired-mode) - -(use-package dired-subtree - :bind (:map dired-mode-map - ("i" . dired-subtree-toggle))) - -(use-package dired-collapse - :hook dired-mode) - -(use-package 0x0 - :straight (:repo "https://git.sr.ht/~zge/nullpointer-emacs") - :init (setq 0x0-default-service 'ttm) - :commands (0x0-upload - 0x0-upload-file - 0x0-upload-string - 0x0-upload-kill-ring - 0x0-popup)) - -(use-package elpher - :straight (:repo "git://thelambdalab.xyz/elpher.git") - :commands (elpher elpher-bookmarks) - :init (setq elpher-ipv4-always t - elpher-certificate-directory - (expand-file-name "elpher-certificates/" - acdw/var-dir)) - (add-hook 'elpher-mode-hook #'acdw/reading-mode) - :custom-face - (elpher-gemini-heading1 - ((t (:inherit (modus-theme-heading-1) - :height 1.0)))) - (elpher-gemini-heading2 - ((t (:inherit (modus-theme-heading-2) - :height 1.0)))) - (elpher-gemini-heading3 - ((t (:inherit (modus-theme-heading-3) - :height 1.0)))) - :bind (:map elpher-mode-map - ("n" . elpher-next-link) - ("p" . elpher-prev-link) - ("o" . elpher-follow-current-link) - ("G" . elpher-go-current))) - -(use-package gemini-write - :straight (:repo - "https://alexschroeder.ch/cgit/gemini-write" - :fork (:repo "https://tildegit.org/acdw/gemini-write" - :branch "main")) - :after elpher) - -(use-package gemini-mode - :straight (:repo "https://git.carcosa.net/jmcbray/gemini.el.git") - :mode "\\.\\(gemini\\|gmi\\)\\'" - :custom-face - (gemini-heading-face-1 - ((t (:inherit (elpher-gemini-heading1))))) - (gemini-heading-face2 - ((t (:inherit (elpher-gemini-heading2))))) - (gemini-heading-face3 - ((t (:inherit (elpher-gemini-heading3)))))) - -(use-package nov - :init (setq nov-text-width fill-column) - (add-hook 'nov-mode-hook #'acdw/reading-mode) - :mode ("\\.epub\\'" . nov-mode)) - -(use-package undo-fu - :bind (("C-/" . undo-fu-only-undo) - ("C-?" . undo-fu-only-redo))) -(use-package undo-fu-session - :init (setq undo-fu-session-directory (expand-file-name "undo/" - acdw/var-dir) - undo-fu-session-incompatible-files - '("/COMMIT_EDITMSG\\'" "/git-rebase-todo\\'")) - :config (global-undo-fu-session-mode)) - -(setq-default set-mark-repeat-command-pop t) ; repeat mark pops w/o prefix - -(use-package expand-region - :bind ("C-=" . er/expand-region)) - -(use-package goggles - :hook ((text-mode prog-mode) . goggles-mode)) - -(use-package isearch - :straight nil) - -(use-package anzu - :init (setq search-default-mode t) - :bind (([remap query-replace] . anzu-query-replace-regexp) - ([remap query-replace-regexp] . anzu-query-replace) - :map isearch-mode-map - ([remap isearch-query-replace] . anzu-isearch-query-replace) - ([remap isearch-query-replace-regexp] . - anzu-isearch-query-replace-regexp))) - -(use-package iscroll - :hook (text-mode . iscroll-mode)) - -(use-package ibuffer - :straight nil - :bind ([remap list-buffers] . #'ibuffer) - ;; from http://martinowen.net/blog/2010/02/03/tips-for-emacs-ibuffer.html - :init - (setq ibuffer-saved-filter-groups ; this could still be improved - '(("home" - ("emacs-config" (or (filename . ".emacs.d") - (filename . "etc/emacs"))) - ("Org" (or (mode . org-mode) - (filename . "OrgMode"))) - ("Dired" (mode . dired-mode)) - ("Magit" (name . "magit")) - ("Help" (or (name . "\*Help\*") - (name . "\*Apropos\*") - (name . "\*info\*"))))) - ibuffer-expert t ; don't ask if i wanna kill unmodifieds - ibuffer-show-empty-filter-groups nil - ) - (defun hook--ibuffer-setup () - (ibuffer-auto-mode +1) - (ibuffer-switch-to-saved-filter-groups "home")) - (add-hook 'ibuffer-mode-hook #'hook--ibuffer-setup)) - -(setq-default - browse-url-browser-function 'browse-url-firefox - browse-url-new-window-flag t - browse-url-firefox-new-window-is-tab t - shr-max-width fill-column - shr-width fill-column) - -(when (eq acdw/system :work) - (add-to-list 'exec-path "C:/Program Files/Mozilla Firefox")) - -(bind-key [remap just-one-space] #'cycle-spacing) - -;; Org mode -;; org-return-dwim (unpacakged) -(defun unpackaged/org-element-descendant-of (type element) - "Return non-nil if ELEMENT is a descendant of TYPE. - TYPE should be an element type, like `item' or `paragraph'. - ELEMENT should be a list like that returned by `org-element-context'." - ;; MAYBE: Use `org-element-lineage'. - (when-let* ((parent (org-element-property :parent element))) - (or (eq type (car parent)) - (unpackaged/org-element-descendant-of type parent)))) - -(defun unpackaged/org-return-dwim (&optional default) - "A helpful replacement for `org-return'. With prefix, call `org-return'. - - On headings, move point to position after entry content. In - lists, insert a new item or end the list, with checkbox if - appropriate. In tables, insert a new row or end the table." - ;; Inspired by John Kitchin: - ;; http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/ - (interactive "P") - (if default - (org-return) - (cond - ;; Act depending on context around point. - - ;; NOTE: I prefer RET to not follow links, but by uncommenting - ;; this block, links will be followed. - ;; FURTHER NOTE: Ideally, I would follow links unless point - ;; /appeared/ to be at the end of the line (even if it's still - ;; inside the link) -- when it would do `org-return'. That - ;; would take some /doing/, however. - - ;; ((eq 'link (car (org-element-context))) - ;; ;; Link: Open it. - ;; (org-open-at-point-global)) - - ((org-at-heading-p) - ;; Heading: Move to position after entry content. - ;; NOTE: This is probably the most interesting feature of this function. - (let ((heading-start (org-entry-beginning-position))) - (goto-char (org-entry-end-position)) - (cond ((and (org-at-heading-p) - (= heading-start (org-entry-beginning-position))) - ;; Entry ends on its heading; add newline after - (end-of-line) - (insert "\n\n")) - (t - ;; Entry ends after its heading; back up - (forward-line -1) - (end-of-line) - (when (org-at-heading-p) - ;; At the same heading - (forward-line) - (insert "\n") - (forward-line -1)) - ;; FIXME: looking-back is supposed to be called with - ;; more arguments. - (while (not (looking-back (rx - (repeat 3 - (seq (optional blank) - "\n"))) - nil)) - (insert "\n")) - (forward-line -1))))) - - ((org-at-item-checkbox-p) - ;; Checkbox: Insert new item with checkbox. - (org-insert-todo-heading nil)) - - ((org-in-item-p) - ;; Plain list. Yes, this gets a little complicated... - (let ((context (org-element-context))) - (if (or (eq 'plain-list (car context)) ; First item in list - (and (eq 'item (car context)) - (not (eq (org-element-property :contents-begin context) - (org-element-property :contents-end context)))) - ;; Element in list item, e.g. a link - (unpackaged/org-element-descendant-of 'item context)) - ;; Non-empty item: Add new item. - (org-insert-item) - ;; Empty item: Close the list. - ;; TODO: Do this with org functions rather than operating - ;; on the text. Can't seem to find the right function. - (delete-region (line-beginning-position) (line-end-position)) - (insert "\n")))) - - ((when (fboundp 'org-inlinetask-in-task-p) - (org-inlinetask-in-task-p)) - ;; Inline task: Don't insert a new heading. - (org-return)) - - ((org-at-table-p) - (cond ((save-excursion - (beginning-of-line) - ;; See `org-table-next-field'. - (cl-loop with end = (line-end-position) - for cell = (org-element-table-cell-parser) - always (equal (org-element-property :contents-begin cell) - (org-element-property :contents-end cell)) - while (re-search-forward "|" end t))) - ;; Empty row: end the table. - (delete-region (line-beginning-position) (line-end-position)) - (org-return)) - (t - ;; Non-empty row: call `org-return'. - (org-return)))) - (t - ;; All other cases: call `org-return'. - (org-return))))) - -;; org-fix-blank-lines (unpackaged) -(defun unpackaged/org-fix-blank-lines (&optional prefix) - "Ensure that blank lines exist between headings and between headings and their contents. - With prefix, operate on whole buffer. Ensures that blank lines - exist after each headings's drawers." - (interactive "P") - (org-map-entries (lambda () - (org-with-wide-buffer - ;; `org-map-entries' narrows the buffer, which prevents us - ;; from seeing newlines before the current heading, so we - ;; do this part widened. - (while (not (looking-back "\n\n" nil)) - ;; Insert blank lines before heading. - (insert "\n"))) - (let ((end (org-entry-end-position))) - ;; Insert blank lines before entry content - (forward-line) - (while (and (org-at-planning-p) - (< (point) (point-max))) - ;; Skip planning lines - (forward-line)) - (while (re-search-forward org-drawer-regexp end t) - ;; Skip drawers. You might think that `org-at-drawer-p' - ;; would suffice, but for some reason it doesn't work - ;; correctly when operating on hidden text. This - ;; works, taken from `org-agenda-get-some-entry-text'. - (re-search-forward "^[ \t]*:END:.*\n?" end t) - (goto-char (match-end 0))) - (unless (or (= (point) (point-max)) - (org-at-heading-p) - (looking-at-p "\n")) - (insert "\n")))) - t (if prefix - nil - 'tree))) - -(defun hook--org-mode-fix-blank-lines () - (when (eq major-mode 'org-mode) - (let ((current-prefix-arg 4)) ; Emulate C-u - (call-interactively 'unpackaged/org-fix-blank-lines)))) -(add-hook 'before-save-hook #'hook--org-mode-fix-blank-lines) - -(use-package org - :straight (:repo "https://code.orgmode.org/bzg/org-mode.git") - :init - (setq-default - org-directory "~/org" ; where to search for org files - ;; typesetting - org-hide-emphasis-markers t - org-fontify-whole-heading-line t - org-fontify-done-headline t - org-fontify-quote-and-verse-blocks t - org-src-fontify-natively t - org-ellipsis " ≡" - org-pretty-entities t - org-tags-column (- 0 fill-column (- (length org-ellipsis))) - ;; Source blocks - org-src-tab-acts-natively t - org-src-window-setup 'current-window - org-confirm-babel-evaluate nil - ;; Behavior - org-adapt-indentation t ; indent text after a header - org-catch-invisible-edits 'smart - org-special-ctrl-a/e t - org-special-ctrl-k t - org-imenu-depth 8 ; catch all headings - ;; Exporting - org-export-headline-levels 8 ; export all headings - org-export-with-smart-quotes t - org-export-with-sub-superscripts t - ;; Modules - org-modules '(;; default (commented if unused) - ;; bbdb - ;; bibtex - ;; docview - eww - ;; gnus - info - ;; irc - ;; mhe - ;; rmail - ;; w3m - ;; extra stuff for me - ;; habit ; track your consistency with habits - ;; inlinetask ; tasks independent of outline hierarchy - mouse ; additional mouse support - ;; protocol ; intercept calls from emacsclient - ;; man - tempo ; templates - ) - org-export-backends '(;; defaults - ascii - html - latex - odt - ;; added by me - man - md - ) - ) - :config - (require 'org-tempo) - (require 'ox-md) - :bind (:map org-mode-map - ("RET" . unpackaged/org-return-dwim))) - -(use-package goto-addr - :straight nil - :config - (goto-address-mode +1)) - -(use-package web-mode - :mode (("\\.phtml\\'" . web-mode) - ("\\.tpl\\.php\\'" . web-mode) - ("\\.[agj]sp\\'" . web-mode) - ("\\as[cp]x\\'" . web-mode) - ("\\.erb\\'" . web-mode) - ("\\.mustache\\'" . web-mode) - ("\\.djhtml\\'" . web-mode) - ("\\.html?\\'" . web-mode))) +(autoload 'ehelp-command "ehelp") +(define-key acdw/map (kbd "C-h") #'ehelp-command) diff --git a/lisp/acdw.el b/lisp/acdw.el new file mode 100644 index 0000000..03e4a62 --- /dev/null +++ b/lisp/acdw.el @@ -0,0 +1,83 @@ +:;;; acdw.el -*- lexical-binding: t; coding: utf-8-unix -*- +;; +;; Author: Case Duckworth +;; Created: Sometime during Covid-19, 2020 +;; Keywords: configuration +;; URL: https://tildegit.org/acdw/emacs +;; Bankruptcy: 5b +;; +;; This file is NOT part of GNU Emacs. +;; +;;; License: +;; +;; Everyone is permitted to do whatever with this software, without +;; limitation. This software comes without any warranty whatsoever, +;; but with two pieces of advice: +;; - Don't hurt yourself. +;; - Make good choices. +;; +;;; Commentary: +;; `acdw.el' contains `acdw/map', its mode, and assorted ease-of-life +;; functions for me, acdw. +;; +;;; Code: + +;;; Directories (think `no-littering') + +(defvar acdw/dir (expand-file-name + (convert-standard-filename "var/") + user-emacs-directory) + "A directory to hold extra configuration and emacs data.") + +(defun acdw/in-dir (file &optional make-directory) + "Expand FILE relative to `acdw/dir', optionally creating its +directory." + (let ((f (expand-file-name (convert-standard-filename file) + acdw/dir))) + (when make-directory + (make-directory (file-name-directory) 'parents)) + f)) + +;;; Settings + +(defun acdw/set (assignments) + "Perform `customize-set-variable' on each of ASSIGNMENTS. + +ASSIGNMENTS is a list where each element is of the form +(VARIABLE VALUE [COMMENT])." + (dolist (assn assignments) + (customize-set-variable (car assignment) + (cadr assignment) + (if (and (caddr assignment) + (stringp (caddr assignment))) + (caddr assignment) + "Customized by `acdw/set'.")))) + +;;; Keymap & Mode + +(defvar acdw/map (make-sparse-keymap) + "A keymap for my custom bindings.") + +(define-minor-mode acdw/mode + "A mode for `acdw/map'." + :init-value t + :lighter " acdw" + :keymap acdw/map) +(define-globalized-minor-mode acdw/global-mode acdw/mode acdw/mode) + +;; Disable `acdw/mode' in the minibuffer +(defun acdw/mode--disable () + "Disable `acdw/mode'." + (acdw/mode -1)) +(add-hook 'minibuffer-setup-hook #'acdw/mode--disable) + +;; Set up a leader key for `acdw/mode' +(defvar acdw/leader + (let ((map (make-sparse-keymap)) + (c-z (global-key-binding "\C-z"))) + (define-key acdw/map "\C-z" map) + (define-key map "\C-z" c-z) + map)) + +(provide 'acdw) +;;; acdw.el ends here