;;; init.el -*- lexical-binding: t; coding: utf-8 -*- ;; Copyright (C) 2020-2021 Case Duckworth ;; ;; Author: Case Duckworth ;; Created: Sometime during Covid-19, 2020 ;; Keywords: configuration ;; URL https://tildegit.org/acdw/emacs ;; ;; 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. ;; ;;; 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)) ;; 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)))