diff options
47 files changed, 2235 insertions, 682 deletions
diff --git a/.alias.d/00-general b/.alias.d/00-general index c9a4668..ab57817 100644 --- a/.alias.d/00-general +++ b/.alias.d/00-general @@ -17,4 +17,4 @@ alias e=$EDITOR alias mtr='mtr --curses' alias t='cd ~/src/' -alias by='bat -l yaml'
\ No newline at end of file +alias by='batcat -l yaml'
\ No newline at end of file diff --git a/.config/rofi/config b/.config/rofi/config deleted file mode 100644 index 0406648..0000000 --- a/.config/rofi/config +++ /dev/null @@ -1 +0,0 @@ -rofi.theme: /usr/share/rofi/themes/Arc-Dark.rasi diff --git a/.config/rofi/config.rasi b/.config/rofi/config.rasi new file mode 100644 index 0000000..3b108ba --- /dev/null +++ b/.config/rofi/config.rasi @@ -0,0 +1,7 @@ +configuration { + font: "Iosevka 20"; + modes: [ combi ]; + combi-modes: [ window, drun, run ]; +} + +@theme "Arc-Dark" diff --git a/.emacs.d/early-init.el b/.emacs.d/early-init.el index 495c277..5a34cb3 100644 --- a/.emacs.d/early-init.el +++ b/.emacs.d/early-init.el @@ -1,20 +1,44 @@ +;; I don't use any of these (menu-bar-mode -1) (tool-bar-mode -1) (scroll-bar-mode -1) -;; Initialise installed packages -(setq package-enable-at-startup t) +;; Avoid initial flash of light. +(defun prot-emacs-re-enable-frame-theme (_frame) + "Re-enable active theme, if any, upon FRAME creation. +Add this to `after-make-frame-functions' so that new frames do +not retain the generic background set by the function +`prot-emacs-avoid-initial-flash-of-light'." + (when-let* ((theme (car custom-enabled-themes))) + (enable-theme theme))) + +(defun prot-emacs-avoid-initial-flash-of-light () + "Avoid flash of light when starting Emacs, if needed. +New frames are instructed to call `prot-emacs-re-enable-frame-theme'." + (setq mode-line-format nil) + (set-face-attribute 'default nil :background "#000000" :foreground "#ffffff") + (set-face-attribute 'mode-line nil :background "#000000" :foreground "#ffffff" :box 'unspecified) + (add-hook 'after-make-frame-functions #'prot-emacs-re-enable-frame-theme)) -;; Do not report warning errors -(setq native-comp-async-report-warnings-errors 'silent) +(prot-emacs-avoid-initial-flash-of-light) -;; Truly maximize screen +;; Do not resize when font size changes (setq frame-resize-pixelwise t) -;; Start maximized +;; By default, start maximized, undecorated (add-to-list 'default-frame-alist '(fullscreen . maximized)) +(add-to-list 'default-frame-alist '(undecorated . t)) + +;; Name frames to ease switching between them +(add-hook 'after-init-hook (lambda () (set-frame-name "main"))) + +;; Initialise installed packages, otherwise, basic functions are not +;; available during the initialization stage. +(setq package-enable-at-startup t) -;; No need for titlebar -(modify-frame-parameters nil '((undecorated . t))) +;; Do not report warnings. It's too noisy. +(setq native-comp-async-report-warnings-errors 'silent) -;;; early-init.el ends here +;; Keep things minimal +(setq inhibit-startup-screen t) +(setq inhibit-startup-echo-area-message user-login-name) diff --git a/.emacs.d/init.el b/.emacs.d/init.el index 248abcb..a07d86f 100644 --- a/.emacs.d/init.el +++ b/.emacs.d/init.el @@ -1,110 +1,35 @@ +;; package.el +(require 'package) (add-to-list 'package-archives '("melpa-stable" . "https://stable.melpa.org/packages/") t) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) +(let ((backup-dir "~/.backup")) + (unless (file-directory-p backup-dir) + (make-directory backup-dir t)) + (setq backup-directory-alist `(("." . ,backup-dir)))) + +(setq + backup-by-copying t ; Don't delink hardlinks + delete-old-versions t ; Clean up the backups + kept-new-versions 3 ; keep some new versions + kept-old-versions 2 ; and some old ones, too + version-control t) ; Use version numbers on backups + ;; Do not persist customizations (setq custom-file (make-temp-file "emacs-custom-")) -;; Place backups in ~/.backups/ directory, like a civilized program. -;; ------ -(if (file-directory-p "~/.backup") - (setq backup-directory-alist '(("." . "~/.backup"))) - (message "Directory does not exist: ~/.backup")) - -(filesets-init) - -(setq backup-by-copying t ; Don't delink hardlinks - delete-old-versions t ; Clean up the backups - version-control t ; Use version numbers on backups, - kept-new-versions 3 ; keep some new versions - kept-old-versions 2) ; and some old ones, too - -;; --------- -;; Generic keybindings -;; --------- -(global-set-key (kbd "C-c d") 'diff-buffer-with-file) -(global-set-key (kbd "C-c R") 'revert-buffer) -(global-set-key (kbd "C-c w") 'whitespace-cleanup) - -(global-set-key (kbd "C-c b s") 'bookmark-set) -(global-set-key (kbd "C-c b j") 'bookmark-jump) - -(global-set-key (kbd "<f12>") 'compile) - -(defun help/insert-em-dash () - "Inserts an EM-DASH (not a HYPEN, not an N-DASH)" - (interactive) - (insert "—")) - -(global-set-key (kbd "C--") #'help/insert-em-dash) - -;; ------ -;; General config -;; ------ - -(setq fill-column 79) - -;; Make sure that pressing middle mouse button pastes right at point, -;; not where the mouse cursor is. -(setq mouse-yank-at-point t) - -(setq column-number-mode 1) -(setq line-number-mode 1) -(setq-default indent-tabs-mode nil) -(setq-default tab-width 4) - -(setq-default c-basic-offset 4) - -;; ------ -;; Initialize environment -;; ------ - -(setenv "TMPDIR" (concat (getenv "HOME") "/tmp")) -(add-hook 'after-init-hook #'server-start) - -;; ------ -;; Helper for compilation. -;; ------ -;; Close the compilation window if there was no error at all. -(defun compilation-exit-autoclose (status code msg) - ;; If M-x compile exists with a 0 - (when (and (eq status 'exit) (zerop code)) - ;; then bury the *compilation* buffer, so that C-x b doesn't go there - (bury-buffer) - ;; and delete the *compilation* window - (delete-window (get-buffer-window (get-buffer "*compilation*")))) - ;; Always return the anticipated result of compilation-exit-message-function - (cons msg code)) - -;; Specify my function (maybe I should have done a lambda function) -(setq compilation-exit-message-function 'compilation-exit-autoclose) -(setq compilation-read-command nil) - -;; Themes -(add-to-list 'custom-theme-load-path "~/.emacs.d/themes/") - -;; Remove scratch message -(setq initial-scratch-message "") +(setq fill-column 79) ; Wrap lines +(setq mouse-yank-at-point t) ; Do not follow mouse curors when mouse-yanking -;; Ask y or n instead of yes or no -(defalias 'yes-or-no-p 'y-or-n-p) +(setq-default indent-tabs-mode nil) ; No tabs when indenting +(setq-default tab-width 4) ; How many spaces a tab represents -;; Fancier buffer selection -(global-set-key (kbd "C-x C-b") 'bs-show) - -;;; Stefan Monnier <foo at acm.org>. It is the opposite of fill-paragraph -(defun unfill-paragraph (&optional region) - "Takes a multi-line paragraph and makes it into a single line of text." - (interactive (progn (barf-if-buffer-read-only) '(t))) - (let ((fill-column (point-max)) - ;; This would override `fill-column' if it's an integer. - (emacs-lisp-docstring-fill-column t)) - (fill-paragraph nil region))) +(setq initial-scratch-message "") -;; Handy key definition -(define-key global-map "\M-Q" 'unfill-paragraph) +(defalias 'yes-or-no-p 'y-or-n-p) ;; Only flash the mode line (setq ring-bell-function @@ -119,87 +44,52 @@ (setq show-paren-delay 0) (show-paren-mode 1) -;; Save what you enter into minibuffer prompts -(setq history-length 25) -(savehist-mode 1) +(savehist-mode 1) ; Save histories, including minibuffer -;; Remember and restore cursor information -(save-place-mode 1) +(save-place-mode 1) ; Remember and restore cursor information -;; Desktop -(setq desktop-path `(,user-emacs-directory)) -(setq desktop-base-file-name "desktop") -(desktop-save-mode 1) +(setq auto-save-no-message t) ; Do not print a message when auto-saving -;; Set this to t if you don't understand what it means -(setq vc-follow-symlinks nil) +(pixel-scroll-precision-mode 1) ; Precision scrolling -;; dired -(use-package dired-preview - :ensure t - :config - (setq dired-preview-delay 0.3) - (setq dired-preview-max-size (expt 2 20)) - (setq dired-preview-ignored-extensions-regexp - (concat "\\." - "\\(mkv\\|webm\\|mp4\\|mp3\\|ogg\\|m4a" - "\\|gz\\|zst\\|tar\\|xz\\|rar\\|zip" - "\\|iso\\|epub\\|pdf\\)")) +;; Source: https://protesilaos.com/codelog/2024-12-11-emacs-diff-save-some-buffers/ +(add-to-list 'save-some-buffers-action-alist + (list "d" + (lambda (buffer) (diff-buffer-with-file (buffer-file-name buffer))) + "show diff between the buffer and its file")) - (dired-preview-global-mode 1)) +;; Server +(require 'server) +(setq server-client-instructions nil) ; Keep it quiet when opening an ec -(add-hook 'dired-mode-hook #'dired-hide-details-mode) +(unless (server-running-p) + (server-start)) -(setq dired-guess-shell-alist-user - '(("\\.\\(png\\|jpe?g\\|tiff\\)" "feh" "xdg-open") - ("\\.\\(mp[34]\\|m4a\\|ogg\\|flac\\|webm\\|mkv\\)" "mpv" "xdg-open") - (".*" "xdg-open"))) - -;;; Icons -(use-package nerd-icons :ensure t ) -(use-package nerd-icons-dired :ensure t - :config - (add-hook 'dired-mode-hook #'nerd-icons-dired-mode) - ) - -(dolist (path '("~/.emacs.d/rul-lisp/config" "~/.emacs.d/rul-lisp/packages")) +(dolist (path '("~/.emacs.d/rul-lisp/packages")) (add-to-list 'load-path path)) -(pixel-scroll-precision-mode 1) +(when-let* ((file (locate-user-emacs-file "rul-pre-init.el")) + ((file-exists-p file))) + (load-file file)) +(require 'rul-themes) +(require 'rul-bindings) (require 'rul-completion) -(require 'rul-prog) -(require 'rul-elfeed) +(require 'rul-fm) +(require 'rul-fonts) +(require 'rul-io) +(require 'rul-mail) (require 'rul-modeline) -(require 'rul-media) (require 'rul-org) +(require 'rul-prog) +(require 'rul-terminals) +(require 'rul-vc) (require 'rul-wm) (require 'rul-write) -(load-file "~/.emacs.d/rul-init.d/fonts.el") -(load-file "~/.emacs.d/rul-init.d/themes.el") - -;; Init parts (will be deprecated in favor of packages) -(load-file "~/.emacs.d/rul-init.d/auto-fill.el") -(load-file "~/.emacs.d/rul-init.d/flycheck.el") -(load-file "~/.emacs.d/rul-init.d/flyspell.el") -(load-file "~/.emacs.d/rul-init.d/go-lang.el") -(load-file "~/.emacs.d/rul-init.d/hydra.el") -(load-file "~/.emacs.d/rul-init.d/ibuffer.el") -(load-file "~/.emacs.d/rul-init.d/imenu.el") -(load-file "~/.emacs.d/rul-init.d/latex.el") -(load-file "~/.emacs.d/rul-init.d/logos.el") -(load-file "~/.emacs.d/rul-init.d/magit.el") -(load-file "~/.emacs.d/rul-init.d/mail-mode.el") -(load-file "~/.emacs.d/rul-init.d/markdown.el") -(load-file "~/.emacs.d/rul-init.d/notmuch.el") -(load-file "~/.emacs.d/rul-init.d/vterm.el") -(load-file "~/.emacs.d/rul-init.d/which-key.el") - (when-let* ((file (locate-user-emacs-file "rul-post-init.el")) ((file-exists-p file))) (load-file file)) ;; init.el ends here -(put 'dired-find-alternate-file 'disabled nil) diff --git a/.emacs.d/rul-emacs.org b/.emacs.d/rul-emacs.org new file mode 100644 index 0000000..e7162c8 --- /dev/null +++ b/.emacs.d/rul-emacs.org @@ -0,0 +1,1553 @@ +This is (will be) my Emacs literate configuration file. A self +contained file with all my configuration is useful for documentation +purposes. It will be modeled using the technique described by +Protesilaos for his own Emacs config file: +<https://protesilaos.com/emacs/dotemacs>. + +This method consists in generating all files /a priori/, after modifying +this file, and *not* at load time, as that would be too slow. + +#+begin_src emacs-lisp :tangle no :results none +(org-babel-tangle) +#+end_src + +* Overview of files and directories + +- =early-init.el=: quoting the [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Early-Init-File.html][Emacs documentation]], this file is "loaded + before the package system and GUI is initialized, so in it you can + customize variables that affect the package initialization process" +- =init.el=: the skeleton of my configuration framework. It will load + the rest of the modules. +- =rul-emacs-modules/=: a directory with Emacs modules specific to my + configuration. Modules group code related to a topic or theme of + configuration. For example, =rul-prog.el= contains code related to + programming, and =rul-org.el= contains code related to org-mode. If a + module gets too big, I can create a smaller module under the same + topic; for example, =rul-org-agenda.el=. +- =rul-post-init.el=: this file will be loaded after =init.el=, and will + normally live in other git repository. Here I normally add overrides + needed in my work computer. +- =rul-emacs.org=: this file. It (will) generate the rest of the structure. + +* Early configuration file (=early-init.el=) +** Graphical aspects +Customization of graphical aspects of Emacs, such as size, panels, etc. + +#+begin_src emacs-lisp :tangle "early-init.el" +;; I don't use any of these +(menu-bar-mode -1) +(tool-bar-mode -1) +(scroll-bar-mode -1) + +;; Avoid initial flash of light. +(defun prot-emacs-re-enable-frame-theme (_frame) + "Re-enable active theme, if any, upon FRAME creation. +Add this to `after-make-frame-functions' so that new frames do +not retain the generic background set by the function +`prot-emacs-avoid-initial-flash-of-light'." + (when-let* ((theme (car custom-enabled-themes))) + (enable-theme theme))) + +(defun prot-emacs-avoid-initial-flash-of-light () + "Avoid flash of light when starting Emacs, if needed. +New frames are instructed to call `prot-emacs-re-enable-frame-theme'." + (setq mode-line-format nil) + (set-face-attribute 'default nil :background "#000000" :foreground "#ffffff") + (set-face-attribute 'mode-line nil :background "#000000" :foreground "#ffffff" :box 'unspecified) + (add-hook 'after-make-frame-functions #'prot-emacs-re-enable-frame-theme)) + +(prot-emacs-avoid-initial-flash-of-light) +#+end_src + +** Frame configuration +I like to keep a few frames open all the time. A main frame, where I +open my org files, code, etc. A frame for communication and reading, +such as email and feeds, and a frame for terminals. + +Currently, the frames are all the same, but I will add configuration +to distinguish them so I can automate their placement in my desktop +environment. + +#+begin_src emacs-lisp :tangle "early-init.el" +;; Do not resize when font size changes +(setq frame-resize-pixelwise t) + +;; By default, start maximized, undecorated +(add-to-list 'default-frame-alist '(fullscreen . maximized)) +(add-to-list 'default-frame-alist '(undecorated . t)) + +;; Name frames to ease switching between them +(add-hook 'after-init-hook (lambda () (set-frame-name "main"))) +#+end_src + +** Miscellany +#+begin_src emacs-lisp :tangle "early-init.el" +;; Initialise installed packages, otherwise, basic functions are not +;; available during the initialization stage. +(setq package-enable-at-startup t) + +;; Do not report warnings. It's too noisy. +(setq native-comp-async-report-warnings-errors 'silent) + +;; Keep things minimal +(setq inhibit-startup-screen t) +(setq inhibit-startup-echo-area-message user-login-name) +#+end_src + +* Main configuration file (=init.el=) +** Package matters + +I use package from both stable and bleeding-edge Melpa. + +#+begin_src emacs-lisp :tangle "init.el" +;; package.el +(require 'package) +(add-to-list 'package-archives + '("melpa-stable" . "https://stable.melpa.org/packages/") t) + +(add-to-list 'package-archives + '("melpa" . "https://melpa.org/packages/") t) +#+end_src +** Backups +Emacs tends to clutter the filesystem with backup files. A backup file is normally the filename with a =~= suffix. I rather have my filesystem clean, and centralize all backups in a single directory. + +#+begin_src emacs-lisp :tangle "init.el" +(let ((backup-dir "~/.backup")) + (unless (file-directory-p backup-dir) + (make-directory backup-dir t)) + (setq backup-directory-alist `(("." . ,backup-dir)))) + +(setq + backup-by-copying t ; Don't delink hardlinks + delete-old-versions t ; Clean up the backups + kept-new-versions 3 ; keep some new versions + kept-old-versions 2 ; and some old ones, too + version-control t) ; Use version numbers on backups +#+end_src +** Customizations +Customizations don't place nicely with version control, so I do them in a random file that won't get persisted. + +Configurations that need persisting will be added to =custom-set-variables= and =custom-set-faces=. + +#+begin_src emacs-lisp :tangle "init.el" +;; Do not persist customizations +(setq custom-file (make-temp-file "emacs-custom-")) +#+end_src + +** Editor interface +General configurations related to text editing across all modes. + +#+begin_src emacs-lisp :tangle "init.el" +(setq fill-column 79) ; Wrap lines +(setq mouse-yank-at-point t) ; Do not follow mouse curors when mouse-yanking + +(setq-default indent-tabs-mode nil) ; No tabs when indenting +(setq-default tab-width 4) ; How many spaces a tab represents + +(setq initial-scratch-message "") + +(defalias 'yes-or-no-p 'y-or-n-p) + +;; Only flash the mode line +(setq ring-bell-function + (lambda () + (let ((orig-fg (face-foreground 'mode-line))) + (set-face-foreground 'mode-line "#F2804F") + (run-with-idle-timer 0.1 nil + (lambda (fg) (set-face-foreground 'mode-line fg)) + orig-fg)))) + +;; Highlight parens +(setq show-paren-delay 0) +(show-paren-mode 1) + +(savehist-mode 1) ; Save histories, including minibuffer + +(save-place-mode 1) ; Remember and restore cursor information + +(setq auto-save-no-message t) ; Do not print a message when auto-saving + +(pixel-scroll-precision-mode 1) ; Precision scrolling + + +;; Source: https://protesilaos.com/codelog/2024-12-11-emacs-diff-save-some-buffers/ +(add-to-list 'save-some-buffers-action-alist + (list "d" + (lambda (buffer) (diff-buffer-with-file (buffer-file-name buffer))) + "show diff between the buffer and its file")) +#+end_src +** Emacs server +I used to run Emacs as a systemd daemon, but it was not too deterministic as sometimes it would break. + + https://rbenencia.name/blog/emacs-daemon-as-a-systemd-service/ + +Now, I simply start it from Emacs itself. This approach works well for me. + +#+begin_src emacs-lisp :tangle "init.el" +;; Server +(require 'server) +(setq server-client-instructions nil) ; Keep it quiet when opening an ec + +(unless (server-running-p) + (server-start)) +#+end_src +** Modules machinery +#+begin_src emacs-lisp :tangle "init.el" +(dolist (path '("~/.emacs.d/rul-lisp/packages")) + (add-to-list 'load-path path)) + +(when-let* ((file (locate-user-emacs-file "rul-pre-init.el")) + ((file-exists-p file))) + (load-file file)) + +(require 'rul-themes) +(require 'rul-bindings) +(require 'rul-completion) +(require 'rul-fm) +(require 'rul-fonts) +(require 'rul-io) +(require 'rul-mail) +(require 'rul-modeline) +(require 'rul-org) +(require 'rul-prog) +(require 'rul-terminals) +(require 'rul-vc) +(require 'rul-wm) +(require 'rul-write) + +(when-let* ((file (locate-user-emacs-file "rul-post-init.el")) + ((file-exists-p file))) + (load-file file)) + +;; init.el ends here +#+end_src + +* Modules +I group my configuration in logical modules. In general, a module +contains configuration for more than one package. + +** The =themes= module +The =themes= module contains code pertaining to Emacs themes. + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-themes.el" +(use-package ef-themes :ensure t) +(use-package modus-themes + :ensure t + :config + (setq + modus-themes-mode-line '(accented borderless padded) + modus-themes-region '(bg-only) + modus-themes-bold-constructs t + modus-themes-italic-constructs t + modus-themes-paren-match '(bold intense) + modus-themes-headings (quote ((1 . (rainbow variable-pitch 1.3)) + (2 . (rainbow 1.1)) + (t . (rainbow)))) + modus-themes-org-blocks 'tinted)) +#+end_src + + +Additionally, this module subscribes to =org.freedesktop.appearance color-theme= +to detect what color theme is preferred, and set our Emacs theme accordingly. + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-themes.el" +(use-package dbus) +(defun mf/set-theme-from-dbus-value (value) + "Set the appropiate theme according to the color-scheme setting value." + (message "value is %s" value) + (if (equal value '1) + (progn (message "Switch to dark theme") + (modus-themes-select 'modus-vivendi)) + (progn (message "Switch to light theme") + (modus-themes-select 'modus-operandi)))) + +(defun mf/color-scheme-changed (path var value) + "DBus handler to detect when the color-scheme has changed." + (when (and (string-equal path "org.freedesktop.appearance") + (string-equal var "color-scheme")) + (mf/set-theme-from-dbus-value (car value)) + )) + +;; Register for future changes +(dbus-register-signal + :session "org.freedesktop.portal.Desktop" + "/org/freedesktop/portal/desktop" "org.freedesktop.portal.Settings" + "SettingChanged" + #'mf/color-scheme-changed) + +;; Request the current color-scheme +(dbus-call-method-asynchronously + :session "org.freedesktop.portal.Desktop" + "/org/freedesktop/portal/desktop" "org.freedesktop.portal.Settings" + "Read" + (lambda (value) (mf/set-theme-from-dbus-value (caar value))) + "org.freedesktop.appearance" + "color-scheme" + ) + + +(provide 'rul-themes) +#+end_src + +** The =bindings= module +This module contains code pertaining to keybindings. It starts by +defining a set global keys. + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-bindings.el" +;; Global keybindings +(global-set-key (kbd "C-c R") 'revert-buffer) +(global-set-key (kbd "C-c w") 'whitespace-cleanup) + +(defun help/insert-em-dash () + "Inserts an EM-DASH (not a HYPEN, not an N-DASH)" + (interactive) + (insert "—")) + +(global-set-key (kbd "C--") #'help/insert-em-dash) +#+end_src + +Next, we define a few /hydras/. /Hydras/ are a way of grouping keybindings +together, offering a menu on the way. + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-bindings.el" +(use-package hydra + :ensure t + :defer 1) + +;; tab-bar +(defhydra hydra-tab-bar (:color amaranth) + "Tab Bar Operations" + ("t" tab-new "Create a new tab" :column "Creation" :exit t) + ("d" dired-other-tab "Open Dired in another tab") + ("f" find-file-other-tab "Find file in another tab") + ("x" tab-close "Close current tab") + ("m" tab-move "Move current tab" :column "Management") + ("r" tab-rename "Rename Tab") + ("<return>" tab-bar-select-tab-by-name "Select tab by name" :column "Navigation") + ("l" tab-next "Next Tab") + ("j" tab-previous "Previous Tab") + ("q" nil "Exit" :exit t)) + +(global-set-key (kbd "C-x t") 'hydra-tab-bar/body) + +;; Zoom +(defhydra hydra-zoom () + "zoom" + ("g" text-scale-increase "in") + ("l" text-scale-decrease "out")) + +(global-set-key (kbd "C-c z") 'hydra-zoom/body) + +;; Go +(defhydra hydra-go () + "zoom" + ("=" gofmt :exit t) + ("c" go-coverage :exit t)) + +;; vterm +(defhydra hydra-vterm () + "zoom" + ("t" multi-vterm "Open a terminal" :exit t) + ("d" multi-vterm-dedicated-open "Dedicated" :exit t) + ("p" multi-vterm-prev "Previous terminal") + ("n" multi-vterm-next "Next terminal") + ("r" multi-vterm-rename-buffer "Rename buffer" :exit t) + ) + +(global-set-key (kbd "C-c t") 'hydra-vterm/body) +(global-set-key (kbd "C-c m") 'hydra-go/body) +#+end_src + +Finally, we make use of =which-key=, which will show a menu with all +keybinding options after a prefix is pressed. I think this package has +the potential to obsolete =hydra=, so I'll have to revisit that code. + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-bindings.el" +(use-package which-key + :ensure t + :config + (which-key-mode)) + +(provide 'rul-bindings) +#+end_src + +** The =completions= module +This module contains code pertaining to completion and the minibuffer. + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-completion.el" +(use-package orderless :ensure t) + +(setq completion-styles '(basic substring initials orderless)) +(setq completion-category-overrides + '( + (file (styles . (basic partial-completion orderless))) + (project-file (styles . (flex basic substring partial-completion orderless))) + )) + +(setq completion-ignore-case t) +#+end_src + +The =vertico= package provides a vertical completion UI based on the default completion +system. + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-completion.el" +;; Enable vertico +(use-package vertico + :ensure t + :init + (vertico-mode) + + :config + (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy)) +#+end_src + +The =marginalia= package annotates the completion candidates with useful contextual +information. + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-completion.el" +;; Enable rich annotations using the Marginalia package +(use-package marginalia + :ensure t + :bind (:map minibuffer-local-map + ("M-A" . marginalia-cycle)) + :init + (marginalia-mode)) +#+end_src + +The =consult= package replaces most of Emacs core functions with +completion-friendly alternatives that integrates well with =vertico= and +=marginalia=. + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-completion.el" +(use-package consult + :ensure t + :bind (;; C-c bindings in `mode-specific-map' + ("C-c M-x" . consult-mode-command) + ("C-c h" . consult-history) + ("C-c k" . consult-kmacro) + ("C-c m" . consult-man) + ("C-c i" . consult-info) + ([remap Info-search] . consult-info) + + ;; C-x bindings in `ctl-x-map' + ("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command + ("C-x b" . consult-buffer) ;; orig. switch-to-buffer + ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window + ("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame + ("C-x r b" . consult-bookmark) ;; orig. bookmark-jump + ("C-x p b" . consult-project-buffer) ;; orig. project-switch-to-buffer + + ;; Custom M-# bindings for fast register access + ("M-#" . consult-register-load) + ("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated) + ("C-M-#" . consult-register) + + ;; Other custom bindings + ("M-y" . consult-yank-pop) ;; orig. yank-pop + + ;; M-g bindings in `goto-map' + ("M-g e" . consult-compile-error) + ("M-g f" . consult-flymake) ;; Alternative: consult-flycheck + ("M-g g" . consult-goto-line) ;; orig. goto-line + ("M-g M-g" . consult-goto-line) ;; orig. goto-line + ("M-g o" . consult-outline) ;; Alternative: consult-org-heading + ("M-g m" . consult-mark) + ("M-g k" . consult-global-mark) + ("M-g i" . consult-imenu) + ("M-g I" . consult-imenu-multi) + + ;; M-s bindings in `search-map' + ("M-s d" . consult-find) + ("M-s D" . 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 L" . consult-line-multi) + ("M-s k" . consult-keep-lines) + ("M-s u" . consult-focus-lines) + + ;; Isearch integration + ("M-s e" . consult-isearch-history) + + :map isearch-mode-map + ("M-e" . consult-isearch-history) ;; orig. isearch-edit-string + ("M-s e" . consult-isearch-history) ;; orig. isearch-edit-string + ("M-s l" . consult-line) ;; needed by consult-line to detect isearch + ("M-s L" . consult-line-multi) ;; needed by consult-line to detect isearch + + ;; Minibuffer history + :map minibuffer-local-map + ("M-s" . consult-history) ;; orig. next-matching-history-element + ("M-r" . consult-history)) ;; orig. previous-matching-history-element + + :init + (setq xref-show-xrefs-function #'consult-xref) + (setq xref-show-definitions-function #'consult-xref) + (add-hook 'completion-list-mode-hook #'consult-preview-at-point-mode) + + :config + (setq consult-preview-key 'any) + (setq consult-narrow-key "<") +) +#+end_src + +The next piece of code corresponds to =embark=, a package that enables +context-specific actions in the minibuffer, or common buffers. + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-completion.el" +(use-package embark + :ensure t + + :bind + (("C-." . embark-act) ;; pick some comfortable binding + ("M-." . embark-dwim) ;; good alternative: M-. + ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' + + :init + (setq prefix-help-command #'embark-prefix-help-command) + + :config + ;; Hide the mode line of the Embark live/completions buffers + (add-to-list 'display-buffer-alist + '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" + nil + (window-parameters (mode-line-format . none))))) + +(use-package embark-consult + :ensure t + :hook + (embark-collect-mode . consult-preview-at-point-mode)) + +(provide 'rul-completion) +#+end_src + +** The =fm= module +The =fm= module contains code pertaining to file management. In +particular, it's the module that configures =dired= and adds a few extra +packages. + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-fm.el" +;;; rul-fm.el --- File management + +;; dired +(add-hook 'dired-mode-hook #'dired-hide-details-mode) +(setq dired-guess-shell-alist-user + '(("\\.\\(png\\|jpe?g\\|tiff\\)" "feh" "xdg-open") + ("\\.\\(mp[34]\\|m4a\\|ogg\\|flac\\|webm\\|mkv\\)" "mpv" "xdg-open") + (".*" "xdg-open"))) + +(setq dired-kill-when-opening-new-dired-buffer t) +(put 'dired-find-alternate-file 'disabled nil) + +;;; Icons +(use-package nerd-icons :ensure t ) +(use-package nerd-icons-dired :ensure t + :config + (add-hook 'dired-mode-hook #'nerd-icons-dired-mode)) + +(provide 'rul-fm) +#+end_src + +** The =fonts= module +The =fonts= module contains code pertaining to fonts. In particular, it +installs =fontaine=, a software that allows defining font presets. + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-fonts.el" +;;; rul-fonts.el --- Fonts configuration + +(use-package fontaine + :ensure t + :config + (setq fontaine-presets + '((tiny + :default-height 100) + (small + :default-height 120) + (medium + :default-height 140) + (large + :default-weight semilight + :default-height 180 + :bold-weight extrabold) + (presentation + :default-weight semilight + :default-height 200 + :bold-weight extrabold) + (jumbo + :default-weight semilight + :default-height 230 + :bold-weight extrabold) + (t + :default-family "Iosevka" + :default-weight regular + :default-height 140 + :variable-pitch-family "Iosevka Aile"))) + + ;; Set desired style from `fontaine-presets' + (fontaine-set-preset 'medium)) + +(provide 'rul-fonts) +#+end_src + +** The =io= module +The =io= module contains configurations for packages related to Internet +services and media. I don't have excessive costumizations in these +packages, so they're somewhat unrelated fragments of code grouped in +the same file. + +We install =elfeed= to browse RSS and Atom feeds. +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-io.el" +;;; rul-io.el --- Configuration for Internet and media packages + +(use-package elfeed :ensure t) +(provide 'rul-feeds) +#+end_src + +The =empv= package allow us to use the =mpv= player from within +Emacs. Here we're simply installing it and configuring it with some +Internet radio channels. It requires =mpv= to be installed. +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-io.el" +(use-package empv +:ensure t +:config + (bind-key "C-x m" empv-map) + (setq empv-radio-channels + '( + ("SomaFM - Groove Salad" . "http://www.somafm.com/groovesalad.pls") + ("SomaFM - DEFCON" . "https://somafm.com/defcon256.pls") + ("SomaFM - Metal" . "https://somafm.com/metal.pls") + ("SomaFM - Lush" . "https://somafm.com/lush130.pls") + ("KCSM Jazz 91" . "http://ice5.securenetsystems.net/KCSM") + ))) + +(provide 'rul-io) +#+end_src +** The =mail= module +Emacs can act as Mail User Agent. My preferred package for this is +=notmuch=. + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-mail.el" +;;; rul-mail.el --- Email configuration + +;; mml-sec.el +;; Use sender to find GPG key. +(setq mml-secure-openpgp-sign-with-sender t) + +(use-package notmuch + :ensure t + :config + ;; UI + (setq notmuch-show-logo nil + notmuch-column-control 1.0 + notmuch-hello-auto-refresh t + notmuch-hello-recent-searches-max 20 + notmuch-hello-thousands-separator "" + notmuch-show-all-tags-list t) + + ;; Keymaps + (defun rul/capture-mail() + "Capture mail to org mode." + (interactive) + (org-store-link nil) + (org-capture nil "m") + ) + + (bind-key "c" 'rul/capture-mail notmuch-show-mode-map) + + (define-key notmuch-show-mode-map "R" 'notmuch-show-reply) + (define-key notmuch-search-mode-map "R" 'notmuch-search-reply-to-thread) + + ;; Spam + (define-key notmuch-show-mode-map "S" + (lambda () + "mark message as spam" + (interactive) + (notmuch-show-tag (list "+spam" "-inbox" "-unread")))) + + (define-key notmuch-search-mode-map "S" + (lambda (&optional beg end) + "mark thread as spam" + (interactive (notmuch-search-interactive-region)) + (notmuch-search-tag (list "+spam" "-inbox" "-unread") beg end))) + + ;; Archive + (setq notmuch-archive-tags (list "-inbox" "+archive")) + (define-key notmuch-show-mode-map "A" + (lambda () + "archive" + (interactive) + (notmuch-show-tag (list "+archive" "-inbox" "-unread")) + (notmuch-refresh-this-buffer))) + + (define-key notmuch-search-mode-map "A" + (lambda (&optional beg end) + "archive thread" + (interactive (notmuch-search-interactive-region)) + (notmuch-search-tag (list "+archive" "-inbox" "-unread") beg end) + (notmuch-refresh-this-buffer))) + + ;; Mark as read + (define-key notmuch-search-mode-map "r" + (lambda (&optional beg end) + "mark thread as read" + (interactive (notmuch-search-interactive-region)) + (notmuch-search-tag (list "-unread") beg end) + (notmuch-search-next-thread))) + + (define-key notmuch-search-mode-map (kbd "RET") + (lambda () + "Show the selected thread with notmuch-tree if it has more +than one email. Use notmuch-show otherwise." + (interactive) + (if (= (plist-get (notmuch-search-get-result) :total) 1) + (notmuch-search-show-thread) + (notmuch-tree (notmuch-search-find-thread-id) + notmuch-search-query-string + nil + (notmuch-prettify-subject (notmuch-search-find-subject)))))) + + (defun color-inbox-if-unread () (interactive) + (save-excursion + (goto-char (point-min)) + (let ((cnt (car (process-lines "notmuch" "count" "tag:inbox and tag:unread")))) + (when (> (string-to-number cnt) 0) + (save-excursion + (when (search-forward "inbox" (point-max) t) + (let* ((overlays (overlays-in (match-beginning 0) (match-end 0))) + (overlay (car overlays))) + (when overlay + (overlay-put overlay 'face '((:inherit bold) (:foreground "green"))))))))))) + + (defvar notmuch-hello-refresh-count 0) + (defun notmuch-hello-refresh-status-message () + (let* ((new-count + (string-to-number + (car (process-lines notmuch-command "count")))) + (diff-count (- new-count notmuch-hello-refresh-count))) + (cond + ((= notmuch-hello-refresh-count 0) + (message "You have %s messages." + (notmuch-hello-nice-number new-count))) + ((> diff-count 0) + (message "You have %s more messages since last refresh." + (notmuch-hello-nice-number diff-count))) + ((< diff-count 0) + (message "You have %s fewer messages since last refresh." + (notmuch-hello-nice-number (- diff-count))))) + (setq notmuch-hello-refresh-count new-count))) + + (add-hook 'notmuch-hello-refresh-hook 'color-inbox-if-unread) + (add-hook 'notmuch-hello-refresh-hook 'notmuch-hello-refresh-status-message) + + (setq notmuch-hello-sections '(notmuch-hello-insert-saved-searches + notmuch-hello-insert-search + notmuch-hello-insert-recent-searches + notmuch-hello-insert-alltags + )) + + ;; https://git.sr.ht/~tslil/dotfiles/tree/4e51afbb/emacs/notmuch-config.el#L76-82 + (defmacro make-binds (mode-map binds argfunc &rest body) + "Create keybindings in `mode-map' using a list of (keystr . arg) +pairs in `binds' of the form ( ... (argfunc arg) body)." + `(progn ,@(mapcar (lambda (pair) + `(define-key ,mode-map (kbd ,(car pair)) + (lambda () (interactive) (,argfunc ,(cdr pair)) ,@body))) + (eval binds)))) + + (defvar notmuch-hello-tree-searches '(("u" . "tag:unread") + ("i" . "tag:inbox") + ("*" . "*")) + "List of (key . query) pairs to bind in notmuch-hello.") + + (make-binds notmuch-hello-mode-map + notmuch-hello-tree-searches + notmuch-search) +) ;; ends use-package notmuch + +(use-package notmuch-indicator :ensure t) + +(provide 'rul-mail) +#+end_src +** The =modeline= module +The =modeline= module contains code pertaining to Emacs modeline. + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-modeline.el" +;;; rul-modeline.el --- Modeline configuration + +;; Most of the code in this file is based on: +;; https://git.sr.ht/~protesilaos/dotfiles/tree/cf26bc34/item/emacs/.emacs.d/prot-lisp/prot-modeline.el +;; +;; All Kudos to Prot. + +;;;; Faces +(defface rul-modeline-indicator-red + '((default :inherit bold) + (((class color) (min-colors 88) (background light)) + :foreground "#880000") + (((class color) (min-colors 88) (background dark)) + :foreground "#ff9f9f") + (t :foreground "red")) + "Face for modeline indicators.") + +;;;; Common helper functions +(defcustom rul-modeline-string-truncate-length 9 + "String length after which truncation should be done in small windows." + :type 'natnum) + +(defun rul-modeline--string-truncate-p (str) + "Return non-nil if STR should be truncated." + (and (< (window-total-width) split-width-threshold) + (> (length str) rul-modeline-string-truncate-length) + (not (one-window-p :no-minibuffer)))) + +(defun rul-modeline-string-truncate (str) + "Return truncated STR, if appropriate, else return STR. +Truncation is done up to `rul-modeline-string-truncate-length'." + (if (rul-modeline--string-truncate-p str) + (concat (substring str 0 rul-modeline-string-truncate-length) "...") + str)) + +;;;; Major mode +(defun rul-modeline-major-mode-indicator () + "Return appropriate propertized mode line indicator for the major mode." + (let ((indicator (cond + ((derived-mode-p 'text-mode) "§") + ((derived-mode-p 'prog-mode) "λ") + ((derived-mode-p 'comint-mode) ">_") + (t "◦")))) + (propertize indicator 'face 'shadow))) + +(defun rul-modeline-major-mode-name () + "Return capitalized `major-mode' without the -mode suffix." + (capitalize (string-replace "-mode" "" (symbol-name major-mode)))) + +(defun rul-modeline-major-mode-help-echo () + "Return `help-echo' value for `rul-modeline-major-mode'." + (if-let ((parent (get major-mode 'derived-mode-parent))) + (format "Symbol: `%s'. Derived from: `%s'" major-mode parent) + (format "Symbol: `%s'." major-mode))) + +(defvar-local rul-modeline-major-mode + (list + (propertize "%[" 'face 'rul-modeline-indicator-red) + '(:eval + (concat + (rul-modeline-major-mode-indicator) + " " + (propertize + (rul-modeline-string-truncate + (rul-modeline-major-mode-name)) + 'mouse-face 'mode-line-highlight + 'help-echo (rul-modeline-major-mode-help-echo)))) + (propertize "%]" 'face 'rul-modeline-indicator-red)) + "Mode line construct for displaying major modes.") + +(with-eval-after-load 'eglot + (setq mode-line-misc-info + (delete '(eglot--managed-mode (" [" eglot--mode-line-format "] ")) mode-line-misc-info))) + +(defvar-local prot-modeline-eglot + `(:eval + (when (and (featurep 'eglot) (mode-line-window-selected-p)) + '(eglot--managed-mode eglot--mode-line-format))) + "Mode line construct displaying Eglot information. +Specific to the current window's mode line.") + +;;;; Miscellaneous +(defvar-local rul-modeline-misc-info + '(:eval + (when (mode-line-window-selected-p) + mode-line-misc-info)) + "Mode line construct displaying `mode-line-misc-info'. +Specific to the current window's mode line.") + +;;;; Display current time +(setq display-time-format " %a %e %b, %H:%M ") +(setq display-time-default-load-average nil) +(setq display-time-mail-string "") + +;;;; Variables used in the modeline need to be in `risky-local-variable'. +(dolist (construct '( + rul-modeline-major-mode + rul-modeline-misc-info + )) + (put construct 'risky-local-variable t)) + +;;;; Finally, define the modeline format +(setq-default mode-line-format + '("%e" + mode-line-front-space + mode-line-buffer-identification + mode-line-front-space + rul-modeline-major-mode + prot-modeline-eglot + mode-line-format-right-align + rul-modeline-misc-info + )) + +(provide 'rul-modeline) +#+end_src +** The =org= module + +My org mode configuration is quite big, so I split it across multiple files. + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-org.el" +;;; rul-org.el --- Org configuration +(require 'org) +(require 'org-capture) +(require 'org-protocol) +(require 'org-habit) + +(require 'rul-org-agenda) + +(setq org-attach-use-inheritance t) +(setq org-cycle-separator-lines 0) +(setq org-hide-leading-stars nil) +(setq org-startup-indented t) +(setq org-edit-src-content-indentation 0) + +(use-package org-modern :ensure t) +(use-package org-pomodoro + :ensure t + :config + (defun rul/disable-notifications () + "Disable GNOME notifications." + (shell-command "gsettings set org.gnome.desktop.notifications show-banners false")) + + (defun rul/enable-notifications () + "Enable GNOME notifications." + (shell-command "gsettings set org.gnome.desktop.notifications show-banners true")) + + ;; Add hooks for Pomodoro start and finish + (add-hook 'org-pomodoro-started-hook #'rul/disable-notifications) + (add-hook 'org-pomodoro-finished-hook #'rul/enable-notifications) + (add-hook 'org-pomodoro-killed-hook #'rul/enable-notifications)) + +;; (add-hook 'org-mode-hook 'turn-off-auto-fill) +;; (add-hook 'auto-save-hook 'org-save-all-org-buffers) +(add-hook 'org-mode-hook 'visual-line-mode) + +(use-package org-download + :ensure t + :config + (add-hook 'dired-mode-hook 'org-download-enable)) + +(setq org-startup-indented t + org-pretty-entities nil + org-hide-emphasis-markers t + ;; show actually italicized text instead of /italicized text/ + org-fontify-whole-heading-line t + org-fontify-done-headline t + org-fontify-quote-and-verse-blocks t) + +;; ORG BINDINGS ;; +(global-set-key (kbd "C-c l") #'org-store-link) +(global-set-key (kbd "C-c c") #'org-capture) +(global-set-key (kbd "C-c s") #'org-schedule) + +;; ORG STATES ;; +(setq org-todo-keywords + (quote ((sequence "TODO(t)" "MAYBE(m)" "NEXT(n)" "|" "DONE(d)") + (sequence "WAITING(w@/!)" "HOLD(h@/!)" "|" "CANCELLED(c@/!)" "MEETING")))) + +(setq org-use-fast-todo-selection t) + +(setq org-todo-state-tags-triggers + (quote (("CANCELLED" ("CANCELLED" . t)) + ("WAITING" ("WAITING" . t)) + ("HOLD" ("WAITING") ("HOLD" . t)) + (done ("WAITING") ("HOLD")) + ("TODO" ("WAITING") ("CANCELLED") ("HOLD")) + ("NEXT" ("WAITING") ("CANCELLED") ("HOLD")) + ("DONE" ("WAITING") ("CANCELLED") ("HOLD"))))) + +(setq org-enforce-todo-dependencies t) +(setq org-log-done (quote time)) +(setq org-log-redeadline (quote time)) +(setq org-log-reschedule (quote time)) + +;; CAPTURE ;; +(setq org-capture-templates + (quote + ( + + ("w" "Todo" entry + (file+headline org-refile-path "Tasks") + "* TODO %?" + :empty-lines 1) + + ("m" + "Capture incoming email" + entry + (file+headline org-refile-path "Incoming") + "* TODO Re: %:description\n\n Source: %u, %a\n" + :empty-lines 1) + + ("e" "Elfeed entry" entry + (file+headline org-refile-path "Read later") + "* %? [[%:link][%:description]]\n %U\n %:description\n") + + ("L" "Web Link" entry + (file+headline org-refile-path "Read later") + "* %?[[%:link][%:description]] \"\")\n %:initial\n \nCaptured On: %U" + ) + + ("l" "Web Link with Selection" entry + (file+headline org-refile-path "Read later") + "* [[%:link][%:description]] \n %:initial\n \nCaptured On: %U") + + ))) + +;; REFILE ;; + +; Targets include this file and any file contributing to the agenda - up to 3 levels deep +(setq org-refile-targets + '((nil :maxlevel . 3) + (org-agenda-files :maxlevel . 3))) + +; Targets complete directly with IDO +(setq org-outline-path-complete-in-steps nil) + +; Allow refile to create parent tasks with confirmation +(setq org-refile-allow-creating-parent-nodes (quote confirm)) + + + +;; ORG REPORTS ;; +; Set default column view headings: Task Effort Clock_Summary +(setq org-columns-default-format "%80ITEM(Task) %10Effort(Effort){:} %10CLOCKSUM") + +(defun my-org-clocktable-indent-string (level) + (if (= level 1) + "" + (let ((str "^")) + (while (> level 2) + (setq level (1- level) + str (concat str "--"))) + (concat str "-> ")))) + +(advice-add 'org-clocktable-indent-string :override #'my-org-clocktable-indent-string) + +(setq org-clock-clocktable-default-properties '(:maxlevel 4 :scope file :formula %)) + +; global Effort estimate values +; global STYLE property values for completion +(setq org-global-properties (quote (("Effort_ALL" . "0:15 0:30 0:45 1:00 2:00 3:00 4:00 5:00 6:00 0:00") + ("STYLE_ALL" . "habit")))) + +;; TAGS ;; +; Tags with fast selection keys +(setq org-tag-alist (quote ((:startgroup) + ("@errand" . ?e) + ("@office" . ?o) + ("@home" . ?H) + (:endgroup) + ("WAITING" . ?w) + ("HOLD" . ?h) + ("CANCELLED" . ?c) + ("FLAGGED" . ??)))) + +(setq org-stuck-projects + '("+LEVEL=2+PROJECT/-MAYBE-DONE" ("NEXT") ("@shop") + "\\<IGNORE\\>")) + +; Allow setting single tags without the menu +(setq org-fast-tag-selection-single-key (quote expert)) + +;; org-modern +(add-hook 'org-mode-hook 'org-modern-mode) +(add-hook 'org-agenda-finalize-hook #'org-modern-agenda) + +;; Honor ATTR_ORG attribute. Defaults to image's width if not set. +(setq org-image-actual-width nil) + +(provide 'rul-org) +#+end_src + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-org-agenda.el" +;;; rul-org-agenda.el --- Org agenda configuration +(require 'org) + +(global-set-key (kbd "<f12>") #'org-agenda) +(global-set-key (kbd "C-c a") #'org-agenda) + +(defun bh/is-project-p () + "Any task with a todo keyword subtask" + (save-restriction + (widen) + (let ((has-subtask) + (subtree-end (save-excursion (org-end-of-subtree t))) + (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1))) + (save-excursion + (forward-line 1) + (while (and (not has-subtask) + (< (point) subtree-end) + (re-search-forward "^\*+ " subtree-end t)) + (when (member (org-get-todo-state) org-todo-keywords-1) + (setq has-subtask t)))) + (and is-a-task has-subtask)))) + +(defun bh/is-project-subtree-p () + "Any task with a todo keyword that is in a project subtree. +Callers of this function already widen the buffer view." + (let ((task (save-excursion (org-back-to-heading 'invisible-ok) + (point)))) + (save-excursion + (bh/find-project-task) + (if (equal (point) task) + nil + t)))) + +(defun bh/is-task-p () + "Any task with a todo keyword and no subtask" + (save-restriction + (widen) + (let ((has-subtask) + (subtree-end (save-excursion (org-end-of-subtree t))) + (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1))) + (save-excursion + (forward-line 1) + (while (and (not has-subtask) + (< (point) subtree-end) + (re-search-forward "^\*+ " subtree-end t)) + (when (member (org-get-todo-state) org-todo-keywords-1) + (setq has-subtask t)))) + (and is-a-task (not has-subtask))))) + +(defun bh/is-subproject-p () + "Any task which is a subtask of another project" + (let ((is-subproject) + (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1))) + (save-excursion + (while (and (not is-subproject) (org-up-heading-safe)) + (when (member (nth 2 (org-heading-components)) org-todo-keywords-1) + (setq is-subproject t)))) + (and is-a-task is-subproject))) + +(defun bh/list-sublevels-for-projects-indented () + "Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks. + This is normally used by skipping functions where this variable is already local to the agenda." + (if (marker-buffer org-agenda-restrict-begin) + (setq org-tags-match-list-sublevels 'indented) + (setq org-tags-match-list-sublevels nil)) + nil) + +(defun bh/list-sublevels-for-projects () + "Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks. + This is normally used by skipping functions where this variable is already local to the agenda." + (if (marker-buffer org-agenda-restrict-begin) + (setq org-tags-match-list-sublevels t) + (setq org-tags-match-list-sublevels nil)) + nil) + +(defvar bh/hide-scheduled-and-waiting-next-tasks t) + +(defun bh/toggle-next-task-display () + (interactive) + (setq bh/hide-scheduled-and-waiting-next-tasks (not bh/hide-scheduled-and-waiting-next-tasks)) + (when (equal major-mode 'org-agenda-mode) + (org-agenda-redo)) + (message "%s WAITING and SCHEDULED NEXT Tasks" (if bh/hide-scheduled-and-waiting-next-tasks "Hide" "Show"))) + +(defun bh/skip-stuck-projects () + "Skip trees that are not stuck projects" + (save-restriction + (widen) + (let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))) + (if (bh/is-project-p) + (let* ((subtree-end (save-excursion (org-end-of-subtree t))) + (has-next )) + (save-excursion + (forward-line 1) + (while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t)) + (unless (member "WAITING" (org-get-tags-at)) + (setq has-next t)))) + (if has-next + nil + next-headline)) ; a stuck project, has subtasks but no next task + nil)))) + +(defun bh/skip-non-stuck-projects () + "Skip trees that are not stuck projects" + ;; (bh/list-sublevels-for-projects-indented) + (save-restriction + (widen) + (let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))) + (if (bh/is-project-p) + (let* ((subtree-end (save-excursion (org-end-of-subtree t))) + (has-next )) + (save-excursion + (forward-line 1) + (while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t)) + (unless (member "WAITING" (org-get-tags-at)) + (setq has-next t)))) + (if has-next + next-headline + nil)) ; a stuck project, has subtasks but no next task + next-headline)))) + +(defun bh/skip-non-projects () + "Skip trees that are not projects" + ;; (bh/list-sublevels-for-projects-indented) + (if (save-excursion (bh/skip-non-stuck-projects)) + (save-restriction + (widen) + (let ((subtree-end (save-excursion (org-end-of-subtree t)))) + (cond + ((bh/is-project-p) + nil) + ((and (bh/is-project-subtree-p) (not (bh/is-task-p))) + nil) + (t + subtree-end)))) + (save-excursion (org-end-of-subtree t)))) + +(defun bh/skip-non-tasks () + "Show non-project tasks. +Skip project and sub-project tasks, habits, and project related tasks." + (save-restriction + (widen) + (let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))) + (cond + ((bh/is-task-p) + nil) + (t + next-headline))))) + +(defun bh/skip-project-trees-and-habits () + "Skip trees that are projects" + (save-restriction + (widen) + (let ((subtree-end (save-excursion (org-end-of-subtree t)))) + (cond + ((bh/is-project-p) + subtree-end) + ((org-is-habit-p) + subtree-end) + (t + nil))))) + +(defun bh/skip-projects-and-habits-and-single-tasks () + "Skip trees that are projects, tasks that are habits, single non-project tasks" + (save-restriction + (widen) + (let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))) + (cond + ((org-is-habit-p) + next-headline) + ((and bh/hide-scheduled-and-waiting-next-tasks + (member "WAITING" (org-get-tags-at))) + next-headline) + ((bh/is-project-p) + next-headline) + ((and (bh/is-task-p) (not (bh/is-project-subtree-p))) + next-headline) + (t + nil))))) + +(defun bh/skip-project-tasks-maybe () + "Show tasks related to the current restriction. +When restricted to a project, skip project and sub project tasks, habits, NEXT tasks, and loose tasks. +When not restricted, skip project and sub-project tasks, habits, and project related tasks." + (save-restriction + (widen) + (let* ((subtree-end (save-excursion (org-end-of-subtree t))) + (next-headline (save-excursion (or (outline-next-heading) (point-max)))) + (limit-to-project (marker-buffer org-agenda-restrict-begin))) + (cond + ((bh/is-project-p) + next-headline) + ((org-is-habit-p) + subtree-end) + ((and (not limit-to-project) + (bh/is-project-subtree-p)) + subtree-end) + ((and limit-to-project + (bh/is-project-subtree-p) + (member (org-get-todo-state) (list "NEXT"))) + subtree-end) + (t + nil))))) + +(defun bh/skip-project-tasks () + "Show non-project tasks. +Skip project and sub-project tasks, habits, and project related tasks." + (save-restriction + (widen) + (let* ((subtree-end (save-excursion (org-end-of-subtree t)))) + (cond + ((bh/is-project-p) + subtree-end) + ((org-is-habit-p) + subtree-end) + ((bh/is-project-subtree-p) + subtree-end) + ((not (org-entry-is-todo-p)) + subtree-end) + (t + nil))))) + +(defun bh/skip-non-project-tasks () + "Show project tasks. +Skip project and sub-project tasks, habits, and loose non-project tasks." + (save-restriction + (widen) + (let* ((subtree-end (save-excursion (org-end-of-subtree t))) + (next-headline (save-excursion (or (outline-next-heading) (point-max))))) + (cond + ((bh/is-project-p) + next-headline) + ((org-is-habit-p) + subtree-end) + ((and (bh/is-project-subtree-p) + (member (org-get-todo-state) (list "NEXT"))) + subtree-end) + ((not (bh/is-project-subtree-p)) + subtree-end) + (t + nil))))) + +(defun bh/skip-projects-and-habits () + "Skip trees that are projects and tasks that are habits" + (save-restriction + (widen) + (let ((subtree-end (save-excursion (org-end-of-subtree t)))) + (cond + ((bh/is-project-p) + subtree-end) + ((org-is-habit-p) + subtree-end) + (t + nil))))) + +(defun bh/skip-non-subprojects () + "Skip trees that are not projects" + (let ((next-headline (save-excursion (outline-next-heading)))) + (if (bh/is-subproject-p) + nil + next-headline))) + +;; CLOCKING ;; +;; Resume clocking task when emacs is restarted +(org-clock-persistence-insinuate) +;; +;; Show lot of clocking history so it's easy to pick items off the C-F11 list +(setq org-clock-history-length 23) +;; Resume clocking task on clock-in if the clock is open +(setq org-clock-in-resume t) +;; Change tasks to NEXT when clocking in +(setq org-clock-in-switch-to-state 'bh/clock-in-to-next) +;; Separate drawers for clocking and logs +(setq org-drawers (quote ("PROPERTIES" "LOGBOOK"))) +;; Save clock data and state changes and notes in the LOGBOOK drawer +(setq org-clock-into-drawer t) +;; Sometimes I change tasks I'm clocking quickly - this removes clocked tasks with 0:00 duration +(setq org-clock-out-remove-zero-time-clocks t) +;; Clock out when moving task to a done state +(setq org-clock-out-when-done t) +;; Save the running clock and all clock history when exiting Emacs, load it on startup +(setq org-clock-persist t) +;; Do not prompt to resume an active clock +(setq org-clock-persist-query-resume nil) +;; Enable auto clock resolution for finding open clocks +(setq org-clock-auto-clock-resolution (quote when-no-clock-is-running)) +;; Include current clocking task in clock reports +(setq org-clock-report-include-clocking-task t) + + +(setq bh/keep-clock-running nil) + +(defun bh/clock-in-to-next (kw) + "Switch a task from TODO to NEXT when clocking in. +Skips capture tasks, projects, and subprojects. +Switch projects and subprojects from NEXT back to TODO" + (when (not (and (boundp 'org-capture-mode) org-capture-mode)) + (cond + ((and (member (org-get-todo-state) (list "TODO")) + (bh/is-task-p)) + "NEXT") + ((and (member (org-get-todo-state) (list "NEXT")) + (bh/is-project-p)) + "TODO")))) + +(defun bh/find-project-task () + "Move point to the parent (project) task if any" + (save-restriction + (widen) + (let ((parent-task (save-excursion (org-back-to-heading 'invisible-ok) (point)))) + (while (org-up-heading-safe) + (when (member (nth 2 (org-heading-components)) org-todo-keywords-1) + (setq parent-task (point)))) + (goto-char parent-task) + parent-task))) + +(defun bh/punch-in (arg) + "Start continuous clocking and set the default task to the +selected task. If no task is selected set the Organization task +as the default task." + (interactive "p") + (setq bh/keep-clock-running t) + (if (equal major-mode 'org-agenda-mode) + ;; + ;; We're in the agenda + ;; + (let* ((marker (org-get-at-bol 'org-hd-marker)) + (tags (org-with-point-at marker (org-get-tags-at)))) + (if (and (eq arg 4) tags) + (org-agenda-clock-in '(16)) + (bh/clock-in-organization-task-as-default))) + ;; + ;; We are not in the agenda + ;; + (save-restriction + (widen) + ; Find the tags on the current task + (if (and (equal major-mode 'org-mode) (not (org-before-first-heading-p)) (eq arg 4)) + (org-clock-in '(16)) + (bh/clock-in-organization-task-as-default))))) + +(defun bh/punch-out () + (interactive) + (setq bh/keep-clock-running nil) + (when (org-clock-is-active) + (org-clock-out)) + (org-agenda-remove-restriction-lock)) + +(defun bh/clock-in-default-task () + (save-excursion + (org-with-point-at org-clock-default-task + (org-clock-in)))) + +(defun bh/clock-in-parent-task () + "Move point to the parent (project) task if any and clock in" + (let ((parent-task)) + (save-excursion + (save-restriction + (widen) + (while (and (not parent-task) (org-up-heading-safe)) + (when (member (nth 2 (org-heading-components)) org-todo-keywords-1) + (setq parent-task (point)))) + (if parent-task + (org-with-point-at parent-task + (org-clock-in)) + (when bh/keep-clock-running + (bh/clock-in-default-task))))))) + +(defvar bh/organization-task-id "eb155a82-92b2-4f25-a3c6-0304591af2f9") + +;; https://stackoverflow.com/a/10091330 +(defun zin/org-agenda-skip-tag (tag &optional others) + "Skip all entries that correspond to TAG. + +If OTHERS is true, skip all entries that do not correspond to TAG." + (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))) + (current-headline (or (and (org-at-heading-p) + (point)) + (save-excursion (org-back-to-heading))))) + (if others + (if (not (member tag (org-get-tags-at current-headline))) + next-headline + nil) + (if (member tag (org-get-tags-at current-headline)) + next-headline + nil)))) + +(defun bh/clock-in-organization-task-as-default () + (interactive) + (org-with-point-at (org-id-find bh/organization-task-id 'marker) + (org-clock-in '(16)))) + +(defun bh/clock-out-maybe () + (when (and bh/keep-clock-running + (not org-clock-clocking-in) + (marker-buffer org-clock-default-task) + (not org-clock-resolving-clocks-due-to-idleness)) + (bh/clock-in-parent-task))) + +(add-hook 'org-clock-out-hook 'bh/clock-out-maybe 'append) + +;; AGENDA VIEW ;; + +;; Do not dim blocked tasks +(setq org-agenda-compact-blocks nil) +(setq org-agenda-dim-blocked-tasks nil) +(setq org-agenda-block-separator 61) + +;; Agenda log mode items to display (closed and state changes by default) +(setq org-agenda-log-mode-items (quote (closed state))) + +; For tag searches ignore tasks with scheduled and deadline dates +(setq org-agenda-tags-todo-honor-ignore-options t) + +(setq org-icalendar-include-body nil) +(setq org-icalendar-include-bbdb-anniversaries t) +(setq org-icalendar-include-todo t) +(setq org-icalendar-use-scheduled '(todo-start event-if-not-todo event-if-todo-not-done)) + +(provide 'rul-org-agenda) +#+end_src +** The =prog= module +This package contains code related to programming or markup languages +modes. As my configurations are generally small, I prefer to have them +on a single file. + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-prog.el" +;;; rul-prog.el --- Configuration related to programming and markup +;;; languages +(use-package eglot :ensure t) + +;; Go +(use-package go-mode + :ensure t + :init + (progn + (bind-key [remap find-tag] #'godef-jump)) + :config + (add-hook 'go-mode-hook #'yas-minor-mode) + (add-hook 'go-mode-hook 'electric-pair-mode) + (add-hook 'go-mode-hook 'my-go-mode-hook) + (add-hook 'before-save-hook 'gofmt-before-save)) + +(use-package go-eldoc + :ensure t + :init + (add-hook 'go-mode-hook 'go-eldoc-setup)) + +;; Latex +(add-hook 'latex-mode-hook 'flyspell-mode) +(setq TeX-PDF-mode t) + +(defun pdfevince () + (add-to-list 'TeX-output-view-style + '("^pdf$" "." "evince %o %(outpage)"))) + +(add-hook 'LaTeX-mode-hook 'pdfevince t) ; AUCTeX LaTeX mode + +;; Markdown +(use-package markdown-mode + :ensure t + :config + (setq auto-mode-alist + (cons '("\\.mdwn" . markdown-mode) auto-mode-alist))) + +;; Python +(use-package blacken :ensure t :defer t) +(add-hook 'python-mode-hook 'py-autopep8-enable-on-save) + +;; Terraform +(use-package terraform-mode :ensure t :defer t) + +;; YAML +(use-package yaml-mode :ensure t :defer t) + +;; Rust +(use-package rust-mode + :defer t + :init + (setq rust-mode-treesitter-derive t) + :config + (add-hook 'rust-mode-hook 'eglot-ensure)) + +(provide 'rul-prog) +#+end_src diff --git a/.emacs.d/rul-init.d/auto-fill.el b/.emacs.d/rul-init.d/auto-fill.el deleted file mode 100644 index dad2831..0000000 --- a/.emacs.d/rul-init.d/auto-fill.el +++ /dev/null @@ -1,4 +0,0 @@ -;; auto-fill mode -(add-hook 'text-mode-hook 'turn-on-auto-fill) -(global-set-key (kbd "C-c q") 'auto-fill-mode) - diff --git a/.emacs.d/rul-init.d/flycheck.el b/.emacs.d/rul-init.d/flycheck.el deleted file mode 100644 index 006081e..0000000 --- a/.emacs.d/rul-init.d/flycheck.el +++ /dev/null @@ -1,22 +0,0 @@ -(use-package flycheck - :ensure t - :config - -(flycheck-define-checker proselint - "A linter for prose." - :command ("proselint" source-inplace) - :error-patterns - ((warning line-start (file-name) ":" line ":" column ": " - (id (one-or-more (not (any " ")))) - (message) line-end)) - :modes (text-mode markdown-mode gfm-mode org-mode)) - -(add-to-list 'flycheck-checkers 'proselint) - -;; TODO: docker run --rm -p 8010:8010 erikvl87/languagetool -(use-package flycheck-languagetool - :ensure t - :hook (message-mode . flycheck-languagetool-setup) - :init - (setq flycheck-languagetool-url "http://localhost:8010") -)) diff --git a/.emacs.d/rul-init.d/flyspell.el b/.emacs.d/rul-init.d/flyspell.el deleted file mode 100644 index 8cf27b8..0000000 --- a/.emacs.d/rul-init.d/flyspell.el +++ /dev/null @@ -1,12 +0,0 @@ -(defcustom flyspell-delayed-commands nil - "List of commands that are \"delayed\" for Flyspell mode. -After these commands, Flyspell checking is delayed for a short time, -whose length is specified by `flyspell-delay'." - :group 'flyspell - :type '(repeat (symbol))) - -(setq ispell-dictionary "en") -(setq flyspell-default-dictionary "en") - -(setq flyspell-issue-welcome-flag nil) -(setq-default ispell-list-command "list") diff --git a/.emacs.d/rul-init.d/go-lang.el b/.emacs.d/rul-init.d/go-lang.el deleted file mode 100644 index 8ec678b..0000000 --- a/.emacs.d/rul-init.d/go-lang.el +++ /dev/null @@ -1,23 +0,0 @@ -;; Debian packages: elpa-go-mode -;; Elpa packages: go-eldoc - -(use-package go-mode - :ensure t - :init - (progn - (bind-key [remap find-tag] #'godef-jump)) - :config - (add-hook 'go-mode-hook #'yas-minor-mode) - (add-hook 'go-mode-hook 'electric-pair-mode) - (add-hook 'go-mode-hook 'my-go-mode-hook) - (add-hook 'before-save-hook 'gofmt-before-save) -) - -(use-package go-eldoc - :ensure t - :init - (add-hook 'go-mode-hook 'go-eldoc-setup)) - -;; Define function to call when go-mode loads -(defun my-go-mode-hook () - (set 'compile-command "go build -v && go test -v && go vet")) diff --git a/.emacs.d/rul-init.d/ibuffer.el b/.emacs.d/rul-init.d/ibuffer.el deleted file mode 100644 index d5198d8..0000000 --- a/.emacs.d/rul-init.d/ibuffer.el +++ /dev/null @@ -1,35 +0,0 @@ -;; Debian packages: elpa-ibuffer-vc - -(use-package ibuffer ; Better buffer list - :bind (([remap list-buffers] . ibuffer)) - ;; Show VC Status in ibuffer - :config (setq ibuffer-formats - '((mark modified read-only vc-status-mini " " - (name 18 18 :left :elide) - " " - (size 9 -1 :right) - " " - (mode 16 16 :left :elide) - " " - (vc-status 16 16 :left) - " " - filename-and-process) - (mark modified read-only " " - (name 18 18 :left :elide) - " " - (size 9 -1 :right) - " " - (mode 16 16 :left :elide) - " " filename-and-process) - (mark " " - (name 16 -1) - " " filename)))) - -(use-package ibuffer-vc ; Group buffers by VC project and status - :ensure t - :defer t - :init (add-hook 'ibuffer-hook - (lambda () - (ibuffer-vc-set-filter-groups-by-vc-root) - (unless (eq ibuffer-sorting-mode 'alphabetic) - (ibuffer-do-sort-by-alphabetic))))) diff --git a/.emacs.d/rul-init.d/imenu.el b/.emacs.d/rul-init.d/imenu.el deleted file mode 100644 index 1a2b29b..0000000 --- a/.emacs.d/rul-init.d/imenu.el +++ /dev/null @@ -1,12 +0,0 @@ -;; Debian packages: elpa-imenu-list -(use-package imenu-list - :ensure t - :bind ("C-." . imenu-list-minor-mode) - :config - (setq imenu-list-focus-after-activation t) - (setq imenu-list-size 0.2) - (setq imenu-list-position 'left) - (add-hook 'go-mode-hook #'imenu-list-minor-mode)) - - - diff --git a/.emacs.d/rul-init.d/latex.el b/.emacs.d/rul-init.d/latex.el deleted file mode 100644 index de4de1f..0000000 --- a/.emacs.d/rul-init.d/latex.el +++ /dev/null @@ -1,9 +0,0 @@ -(add-hook 'latex-mode-hook 'flyspell-mode) -(setq TeX-PDF-mode t) - -(defun pdfevince () - (add-to-list 'TeX-output-view-style - '("^pdf$" "." "evince %o %(outpage)"))) - -(add-hook 'LaTeX-mode-hook 'pdfevince t) ; AUCTeX LaTeX mode - diff --git a/.emacs.d/rul-init.d/logos.el b/.emacs.d/rul-init.d/logos.el deleted file mode 100644 index 0ea1244..0000000 --- a/.emacs.d/rul-init.d/logos.el +++ /dev/null @@ -1,31 +0,0 @@ -(use-package logos -:ensure t -:config - -;; If you want to use outlines instead of page breaks (the ^L) -(setq logos-outlines-are-pages t) -(setq logos-outline-regexp-alist - `((emacs-lisp-mode . "^;;;+ ") - (org-mode . "^\\*+ +") - (markdown-mode . "^\\#+ +") - )) - -;; These apply when `logos-focus-mode' is enabled. Their value is -;; buffer-local. -(setq-default logos-hide-mode-line t - logos-hide-buffer-boundaries t - logos-hide-fringe t - logos-variable-pitch nil - logos-buffer-read-only nil - logos-scroll-lock nil - logos-olivetti t - olivetti-body-width 100 - ) - - -(let ((map global-map)) - (define-key map [remap narrow-to-region] #'logos-narrow-dwim) - (define-key map [remap forward-page] #'logos-forward-page-dwim) - (define-key map [remap backward-page] #'logos-backward-page-dwim) - (define-key map (kbd "<f9>") #'logos-focus-mode)) -) diff --git a/.emacs.d/rul-init.d/mail-mode.el b/.emacs.d/rul-init.d/mail-mode.el deleted file mode 100644 index 09b04f9..0000000 --- a/.emacs.d/rul-init.d/mail-mode.el +++ /dev/null @@ -1,15 +0,0 @@ -(setq auto-mode-alist (append '((".*tmp/mutt.*" . message-mode)) auto-mode-alist)) -(setq auto-mode-alist (append '((".*tmp/neomutt.*" . message-mode)) auto-mode-alist)) -(add-to-list 'auto-mode-alist '("/mutt" . mail-mode)) - -(setq mml-secure-openpgp-sign-with-sender t) - -(add-hook 'mail-mode-hook - (lambda () - (font-lock-add-keywords nil - '(("^[ \t]*>[ \t]*>[ \t]*>.*$" - (0 'compilation-error)) - ("^[ \t]*>[ \t]*>.*$" - (0 'compilation-column-number)) - ("^[ \t]*>.*$" - (0 'comint-highlight-prompt)))))) diff --git a/.emacs.d/rul-init.d/markdown.el b/.emacs.d/rul-init.d/markdown.el deleted file mode 100644 index f035509..0000000 --- a/.emacs.d/rul-init.d/markdown.el +++ /dev/null @@ -1,5 +0,0 @@ -(autoload 'markdown-mode "markdown-mode.el" - "Major mode for editing Markdown files" t) - -(setq auto-mode-alist - (cons '("\\.mdwn" . markdown-mode) auto-mode-alist)) diff --git a/.emacs.d/rul-init.d/notmuch.el b/.emacs.d/rul-init.d/notmuch.el deleted file mode 100644 index e8e4d9d..0000000 --- a/.emacs.d/rul-init.d/notmuch.el +++ /dev/null @@ -1,136 +0,0 @@ -;; -------- -;; notmuch mode -;; -------- -(require 'rul-config-mail) -(use-package notmuch -:ensure t -:config -;;;; General UI -(setq notmuch-show-logo nil - notmuch-column-control 1.0 - notmuch-hello-auto-refresh t - notmuch-hello-recent-searches-max 20 - notmuch-hello-thousands-separator "" - notmuch-show-all-tags-list t) - -;; Keymaps -(defun rul/capture-mail() - "Capture mail to org mode." - (interactive) - (org-store-link nil) - (org-capture nil "m") - ) - -(bind-key "c" 'rul/capture-mail notmuch-show-mode-map) - -(define-key notmuch-show-mode-map "R" 'notmuch-show-reply) -(define-key notmuch-search-mode-map "R" 'notmuch-search-reply-to-thread) - -; Spam -(define-key notmuch-show-mode-map "S" - (lambda () - "mark message as spam" - (interactive) - (notmuch-show-tag (list "+spam" "-inbox" "-unread")))) - -(define-key notmuch-search-mode-map "S" - (lambda (&optional beg end) - "mark thread as spam" - (interactive (notmuch-search-interactive-region)) - (notmuch-search-tag (list "+spam" "-inbox" "-unread") beg end))) - -; Archive -(setq notmuch-archive-tags (list "-inbox" "+archive")) -(define-key notmuch-show-mode-map "A" - (lambda () - "archive" - (interactive) - (notmuch-show-tag (list "+archive" "-inbox" "-unread")) - (notmuch-refresh-this-buffer))) - -(define-key notmuch-search-mode-map "A" - (lambda (&optional beg end) - "archive thread" - (interactive (notmuch-search-interactive-region)) - (notmuch-search-tag (list "+archive" "-inbox" "-unread") beg end) - (notmuch-refresh-this-buffer))) - -; Mark as read -(define-key notmuch-search-mode-map "r" - (lambda (&optional beg end) - "mark thread as read" - (interactive (notmuch-search-interactive-region)) - (notmuch-search-tag (list "-unread") beg end) - (notmuch-search-next-thread))) - -(define-key notmuch-search-mode-map (kbd "RET") - (lambda () - "Show the selected thread with notmuch-tree if it has more -than one email. Use notmuch-show otherwise." - (interactive) - (if (= (plist-get (notmuch-search-get-result) :total) 1) - (notmuch-search-show-thread) - (notmuch-tree (notmuch-search-find-thread-id) - notmuch-search-query-string - nil - (notmuch-prettify-subject (notmuch-search-find-subject)))))) - -(defun color-inbox-if-unread () (interactive) - (save-excursion - (goto-char (point-min)) - (let ((cnt (car (process-lines "notmuch" "count" "tag:inbox and tag:unread")))) - (when (> (string-to-number cnt) 0) - (save-excursion - (when (search-forward "inbox" (point-max) t) - (let* ((overlays (overlays-in (match-beginning 0) (match-end 0))) - (overlay (car overlays))) - (when overlay - (overlay-put overlay 'face '((:inherit bold) (:foreground "green"))))))))))) - -(defvar notmuch-hello-refresh-count 0) -(defun notmuch-hello-refresh-status-message () - (let* ((new-count - (string-to-number - (car (process-lines notmuch-command "count")))) - (diff-count (- new-count notmuch-hello-refresh-count))) - (cond - ((= notmuch-hello-refresh-count 0) - (message "You have %s messages." - (notmuch-hello-nice-number new-count))) - ((> diff-count 0) - (message "You have %s more messages since last refresh." - (notmuch-hello-nice-number diff-count))) - ((< diff-count 0) - (message "You have %s fewer messages since last refresh." - (notmuch-hello-nice-number (- diff-count))))) - (setq notmuch-hello-refresh-count new-count))) - -(add-hook 'notmuch-hello-refresh-hook 'color-inbox-if-unread) -(add-hook 'notmuch-hello-refresh-hook 'notmuch-hello-refresh-status-message) - -(setq notmuch-hello-sections '(notmuch-hello-insert-saved-searches - notmuch-hello-insert-search - notmuch-hello-insert-recent-searches - notmuch-hello-insert-alltags - )) - -;; https://git.sr.ht/~tslil/dotfiles/tree/4e51afbb/emacs/notmuch-config.el#L76-82 -(defmacro make-binds (mode-map binds argfunc &rest body) - "Create keybindings in `mode-map' using a list of (keystr . arg) -pairs in `binds' of the form ( ... (argfunc arg) body)." - `(progn ,@(mapcar (lambda (pair) - `(define-key ,mode-map (kbd ,(car pair)) - (lambda () (interactive) (,argfunc ,(cdr pair)) ,@body))) - (eval binds)))) - -(defvar notmuch-hello-tree-searches '(("u" . "tag:unread") - ("i" . "tag:inbox") - ("*" . "*")) - "List of (key . query) pairs to bind in notmuch-hello.") - -(make-binds notmuch-hello-mode-map - notmuch-hello-tree-searches - notmuch-search) -) - -(use-package notmuch-indicator :ensure t) diff --git a/.emacs.d/rul-init.d/python.el b/.emacs.d/rul-init.d/python.el deleted file mode 100644 index 3186c72..0000000 --- a/.emacs.d/rul-init.d/python.el +++ /dev/null @@ -1 +0,0 @@ -(add-hook 'python-mode-hook 'py-autopep8-enable-on-save) diff --git a/.emacs.d/rul-init.d/vterm.el b/.emacs.d/rul-init.d/vterm.el deleted file mode 100644 index b3bed95..0000000 --- a/.emacs.d/rul-init.d/vterm.el +++ /dev/null @@ -1,35 +0,0 @@ -(use-package multi-vterm -:ensure t -:after (consult) -:config - -(setq vterm-source - `(:name "VTerm Buffer" - :category buffer - :narrow ?t - :face 'consult-buffer - :action ,#'consult--buffer-action - :items - (lambda () - (mapcar #'buffer-name - (seq-filter - (lambda (x) - (eq (buffer-local-value 'major-mode x) 'vterm-mode)) - (buffer-list)))))) - -(setq consult-vterm-buffer-sources - (list - `(:hidden nil :narrow ?t ,@vterm-source))) - -(setq vterm-max-scrollback 100000) - -(defun consult-vterm-buffer () - "Switch to a vterm buffer" - (interactive) - (consult-buffer consult-vterm-buffer-sources)) - -(bind-key "C-x b" 'consult-vterm-buffer vterm-mode-map) - -(add-to-list 'consult-buffer-sources 'vterm-source 'append) -(add-hook 'vterm-mode-hook 'goto-address-mode) -) diff --git a/.emacs.d/rul-init.d/which-key.el b/.emacs.d/rul-init.d/which-key.el deleted file mode 100644 index 1d8fd8d..0000000 --- a/.emacs.d/rul-init.d/which-key.el +++ /dev/null @@ -1,5 +0,0 @@ -(use-package which-key - :ensure t - :config - (which-key-mode) -) diff --git a/.emacs.d/rul-lisp/config/rul-config-elfeed.el b/.emacs.d/rul-lisp/config/rul-config-elfeed.el deleted file mode 100644 index 026c325..0000000 --- a/.emacs.d/rul-lisp/config/rul-config-elfeed.el +++ /dev/null @@ -1,2 +0,0 @@ -(setq elfeed-feeds '("https://planet.debian.org/rss10.xml")) -(provide 'rul-config-elfeed) diff --git a/.emacs.d/rul-lisp/config/rul-config-mail.el b/.emacs.d/rul-lisp/config/rul-config-mail.el deleted file mode 100644 index 240f8be..0000000 --- a/.emacs.d/rul-lisp/config/rul-config-mail.el +++ /dev/null @@ -1 +0,0 @@ -(provide 'rul-config-mail) diff --git a/.emacs.d/rul-lisp/config/rul-config-org.el b/.emacs.d/rul-lisp/config/rul-config-org.el deleted file mode 100644 index bf538dc..0000000 --- a/.emacs.d/rul-lisp/config/rul-config-org.el +++ /dev/null @@ -1,22 +0,0 @@ -(setq - org-agenda-files '("~/org/") - org-agenda-custom-commands - '(("x" agenda) - ("y" agenda*) - ("w" todo "WAITING") - ("W" todo-tree "WAITING") - ) - org-journal-file-type 'yearly - org-journal-dir "~/org/journal/" - org-journal-file-format "%Y.org" - org-journal-time-prefix "* " - org-journal-time-format "" - org-refile-path "~/refile.org" - org-roam-directory "~/org/roam/" - - org-agenda-private-local-path "/tmp/example.ics" - org-agenda-private-remote-path "/sshx:user@host:example.ics" - ) - -(provide 'rul-config-org) - diff --git a/.emacs.d/rul-init.d/hydra.el b/.emacs.d/rul-lisp/packages/rul-bindings.el index 525162e..96d14b0 100644 --- a/.emacs.d/rul-init.d/hydra.el +++ b/.emacs.d/rul-lisp/packages/rul-bindings.el @@ -1,3 +1,14 @@ +;; Global keybindings +(global-set-key (kbd "C-c R") 'revert-buffer) +(global-set-key (kbd "C-c w") 'whitespace-cleanup) + +(defun help/insert-em-dash () + "Inserts an EM-DASH (not a HYPEN, not an N-DASH)" + (interactive) + (insert "—")) + +(global-set-key (kbd "C--") #'help/insert-em-dash) + (use-package hydra :ensure t :defer 1) @@ -43,6 +54,11 @@ ) (global-set-key (kbd "C-c t") 'hydra-vterm/body) +(global-set-key (kbd "C-c m") 'hydra-go/body) +(use-package which-key + :ensure t + :config + (which-key-mode)) -(global-set-key (kbd "C-c m") 'hydra-go/body) +(provide 'rul-bindings) diff --git a/.emacs.d/rul-lisp/packages/rul-completion.el b/.emacs.d/rul-lisp/packages/rul-completion.el index 2b3afa1..3412354 100644 --- a/.emacs.d/rul-lisp/packages/rul-completion.el +++ b/.emacs.d/rul-lisp/packages/rul-completion.el @@ -1,5 +1,14 @@ (use-package orderless :ensure t) +(setq completion-styles '(basic substring initials orderless)) +(setq completion-category-overrides + '( + (file (styles . (basic partial-completion orderless))) + (project-file (styles . (flex basic substring partial-completion orderless))) + )) + +(setq completion-ignore-case t) + ;; Enable vertico (use-package vertico :ensure t @@ -7,50 +16,18 @@ (vertico-mode) :config - (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy) - - ;; Different scroll margin - ;; (setq vertico-scroll-margin 0) - - ;; Show more candidates - ;; (setq vertico-count 20) - - ;; Grow and shrink the Vertico minibuffer - ;; (setq vertico-resize t) - - ;; Optionally enable cycling for `vertico-next' and `vertico-previous'. - ;; (setq vertico-cycle t) - ) + (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy)) ;; Enable rich annotations using the Marginalia package (use-package marginalia :ensure t - ;; Bind `marginalia-cycle' locally in the minibuffer. To make the binding - ;; available in the *Completions* buffer, add it to the - ;; `completion-list-mode-map'. :bind (:map minibuffer-local-map ("M-A" . marginalia-cycle)) - - ;; The :init section is always executed. :init - - ;; Marginalia must be actived in the :init section of use-package such that - ;; the mode gets enabled right away. Note that this forces loading the - ;; package. (marginalia-mode)) -(setq completion-styles '(basic substring initials orderless)) -(setq completion-category-overrides - '( - (file (styles . (basic partial-completion orderless))) - (project-file (styles . (flex basic substring partial-completion orderless))) - )) - -(setq completion-ignore-case t) - (use-package consult :ensure t - ;; Replace bindings. Lazily loaded due by `use-package'. :bind (;; C-c bindings in `mode-specific-map' ("C-c M-x" . consult-mode-command) ("C-c h" . consult-history) @@ -126,7 +103,7 @@ :bind (("C-." . embark-act) ;; pick some comfortable binding - ("C-;" . embark-dwim) ;; good alternative: M-. + ("M-." . embark-dwim) ;; good alternative: M-. ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' :init @@ -139,9 +116,8 @@ nil (window-parameters (mode-line-format . none))))) -;; Consult users will also want the embark-consult package. (use-package embark-consult - :ensure t ; only need to install it, embark loads it after consult if found + :ensure t :hook (embark-collect-mode . consult-preview-at-point-mode)) diff --git a/.emacs.d/rul-lisp/packages/rul-elfeed.el b/.emacs.d/rul-lisp/packages/rul-elfeed.el deleted file mode 100644 index 22aacdd..0000000 --- a/.emacs.d/rul-lisp/packages/rul-elfeed.el +++ /dev/null @@ -1,6 +0,0 @@ -(use-package elfeed -:ensure t -:config -(require 'rul-config-elfeed)) - -(provide 'rul-elfeed) diff --git a/.emacs.d/rul-lisp/packages/rul-fm.el b/.emacs.d/rul-lisp/packages/rul-fm.el new file mode 100644 index 0000000..83aaf37 --- /dev/null +++ b/.emacs.d/rul-lisp/packages/rul-fm.el @@ -0,0 +1,19 @@ +;;; rul-fm.el --- File management + +;; dired +(add-hook 'dired-mode-hook #'dired-hide-details-mode) +(setq dired-guess-shell-alist-user + '(("\\.\\(png\\|jpe?g\\|tiff\\)" "feh" "xdg-open") + ("\\.\\(mp[34]\\|m4a\\|ogg\\|flac\\|webm\\|mkv\\)" "mpv" "xdg-open") + (".*" "xdg-open"))) + +(setq dired-kill-when-opening-new-dired-buffer t) +(put 'dired-find-alternate-file 'disabled nil) + +;;; Icons +(use-package nerd-icons :ensure t ) +(use-package nerd-icons-dired :ensure t + :config + (add-hook 'dired-mode-hook #'nerd-icons-dired-mode)) + +(provide 'rul-fm) diff --git a/.emacs.d/rul-init.d/fonts.el b/.emacs.d/rul-lisp/packages/rul-fonts.el index a7cb255..8395292 100644 --- a/.emacs.d/rul-init.d/fonts.el +++ b/.emacs.d/rul-lisp/packages/rul-fonts.el @@ -1,3 +1,5 @@ +;;; rul-fonts.el --- Fonts configuration + (use-package fontaine :ensure t :config @@ -28,3 +30,5 @@ ;; Set desired style from `fontaine-presets' (fontaine-set-preset 'medium)) + +(provide 'rul-fonts) diff --git a/.emacs.d/rul-lisp/packages/rul-media.el b/.emacs.d/rul-lisp/packages/rul-io.el index b2f8db1..eb2c1c7 100644 --- a/.emacs.d/rul-lisp/packages/rul-media.el +++ b/.emacs.d/rul-lisp/packages/rul-io.el @@ -1,3 +1,8 @@ +;;; rul-io.el --- Configuration for Internet and media packages + +(use-package elfeed :ensure t) +(provide 'rul-feeds) + (use-package empv :ensure t :config @@ -9,7 +14,6 @@ ("SomaFM - Metal" . "https://somafm.com/metal.pls") ("SomaFM - Lush" . "https://somafm.com/lush130.pls") ("KCSM Jazz 91" . "http://ice5.securenetsystems.net/KCSM") - )) - ) + ))) -(provide 'rul-media) +(provide 'rul-io) diff --git a/.emacs.d/rul-lisp/packages/rul-mail.el b/.emacs.d/rul-lisp/packages/rul-mail.el new file mode 100644 index 0000000..dbf9c9b --- /dev/null +++ b/.emacs.d/rul-lisp/packages/rul-mail.el @@ -0,0 +1,140 @@ +;;; rul-mail.el --- Email configuration + +;; mml-sec.el +;; Use sender to find GPG key. +(setq mml-secure-openpgp-sign-with-sender t) + +(use-package notmuch + :ensure t + :config + ;; UI + (setq notmuch-show-logo nil + notmuch-column-control 1.0 + notmuch-hello-auto-refresh t + notmuch-hello-recent-searches-max 20 + notmuch-hello-thousands-separator "" + notmuch-show-all-tags-list t) + + ;; Keymaps + (defun rul/capture-mail() + "Capture mail to org mode." + (interactive) + (org-store-link nil) + (org-capture nil "m") + ) + + (bind-key "c" 'rul/capture-mail notmuch-show-mode-map) + + (define-key notmuch-show-mode-map "R" 'notmuch-show-reply) + (define-key notmuch-search-mode-map "R" 'notmuch-search-reply-to-thread) + + ;; Spam + (define-key notmuch-show-mode-map "S" + (lambda () + "mark message as spam" + (interactive) + (notmuch-show-tag (list "+spam" "-inbox" "-unread")))) + + (define-key notmuch-search-mode-map "S" + (lambda (&optional beg end) + "mark thread as spam" + (interactive (notmuch-search-interactive-region)) + (notmuch-search-tag (list "+spam" "-inbox" "-unread") beg end))) + + ;; Archive + (setq notmuch-archive-tags (list "-inbox" "+archive")) + (define-key notmuch-show-mode-map "A" + (lambda () + "archive" + (interactive) + (notmuch-show-tag (list "+archive" "-inbox" "-unread")) + (notmuch-refresh-this-buffer))) + + (define-key notmuch-search-mode-map "A" + (lambda (&optional beg end) + "archive thread" + (interactive (notmuch-search-interactive-region)) + (notmuch-search-tag (list "+archive" "-inbox" "-unread") beg end) + (notmuch-refresh-this-buffer))) + + ;; Mark as read + (define-key notmuch-search-mode-map "r" + (lambda (&optional beg end) + "mark thread as read" + (interactive (notmuch-search-interactive-region)) + (notmuch-search-tag (list "-unread") beg end) + (notmuch-search-next-thread))) + + (define-key notmuch-search-mode-map (kbd "RET") + (lambda () + "Show the selected thread with notmuch-tree if it has more +than one email. Use notmuch-show otherwise." + (interactive) + (if (= (plist-get (notmuch-search-get-result) :total) 1) + (notmuch-search-show-thread) + (notmuch-tree (notmuch-search-find-thread-id) + notmuch-search-query-string + nil + (notmuch-prettify-subject (notmuch-search-find-subject)))))) + + (defun color-inbox-if-unread () (interactive) + (save-excursion + (goto-char (point-min)) + (let ((cnt (car (process-lines "notmuch" "count" "tag:inbox and tag:unread")))) + (when (> (string-to-number cnt) 0) + (save-excursion + (when (search-forward "inbox" (point-max) t) + (let* ((overlays (overlays-in (match-beginning 0) (match-end 0))) + (overlay (car overlays))) + (when overlay + (overlay-put overlay 'face '((:inherit bold) (:foreground "green"))))))))))) + + (defvar notmuch-hello-refresh-count 0) + (defun notmuch-hello-refresh-status-message () + (let* ((new-count + (string-to-number + (car (process-lines notmuch-command "count")))) + (diff-count (- new-count notmuch-hello-refresh-count))) + (cond + ((= notmuch-hello-refresh-count 0) + (message "You have %s messages." + (notmuch-hello-nice-number new-count))) + ((> diff-count 0) + (message "You have %s more messages since last refresh." + (notmuch-hello-nice-number diff-count))) + ((< diff-count 0) + (message "You have %s fewer messages since last refresh." + (notmuch-hello-nice-number (- diff-count))))) + (setq notmuch-hello-refresh-count new-count))) + + (add-hook 'notmuch-hello-refresh-hook 'color-inbox-if-unread) + (add-hook 'notmuch-hello-refresh-hook 'notmuch-hello-refresh-status-message) + + (setq notmuch-hello-sections '(notmuch-hello-insert-saved-searches + notmuch-hello-insert-search + notmuch-hello-insert-recent-searches + notmuch-hello-insert-alltags + )) + + ;; https://git.sr.ht/~tslil/dotfiles/tree/4e51afbb/emacs/notmuch-config.el#L76-82 + (defmacro make-binds (mode-map binds argfunc &rest body) + "Create keybindings in `mode-map' using a list of (keystr . arg) +pairs in `binds' of the form ( ... (argfunc arg) body)." + `(progn ,@(mapcar (lambda (pair) + `(define-key ,mode-map (kbd ,(car pair)) + (lambda () (interactive) (,argfunc ,(cdr pair)) ,@body))) + (eval binds)))) + + (defvar notmuch-hello-tree-searches '(("u" . "tag:unread") + ("i" . "tag:inbox") + ("*" . "*")) + "List of (key . query) pairs to bind in notmuch-hello.") + + (make-binds notmuch-hello-mode-map + notmuch-hello-tree-searches + notmuch-search) +) ;; ends use-package notmuch + +(use-package notmuch-indicator :ensure t) + +(provide 'rul-mail) diff --git a/.emacs.d/rul-lisp/packages/rul-modeline.el b/.emacs.d/rul-lisp/packages/rul-modeline.el index ae250c4..e20bb2c 100644 --- a/.emacs.d/rul-lisp/packages/rul-modeline.el +++ b/.emacs.d/rul-lisp/packages/rul-modeline.el @@ -1,3 +1,5 @@ +;;; rul-modeline.el --- Modeline configuration + ;; Most of the code in this file is based on: ;; https://git.sr.ht/~protesilaos/dotfiles/tree/cf26bc34/item/emacs/.emacs.d/prot-lisp/prot-modeline.el ;; diff --git a/.emacs.d/rul-lisp/packages/rul-org-agenda.el b/.emacs.d/rul-lisp/packages/rul-org-agenda.el index bfdc8d3..ddfbd7e 100644 --- a/.emacs.d/rul-lisp/packages/rul-org-agenda.el +++ b/.emacs.d/rul-lisp/packages/rul-org-agenda.el @@ -1,3 +1,4 @@ +;;; rul-org-agenda.el --- Org agenda configuration (require 'org) (global-set-key (kbd "<f12>") #'org-agenda) @@ -365,7 +366,7 @@ as the default task." (when bh/keep-clock-running (bh/clock-in-default-task))))))) -(defvar bh/organization-task-id "eb155a82-92b2-4f25-a3c6-0304591af2f9") +(defvar bh/organization-task-id "redefine") ;; org-id-get-create ;; https://stackoverflow.com/a/10091330 (defun zin/org-agenda-skip-tag (tag &optional others) @@ -398,6 +399,34 @@ If OTHERS is true, skip all entries that do not correspond to TAG." (add-hook 'org-clock-out-hook 'bh/clock-out-maybe 'append) +;;; Focusing on current work + +(global-set-key (kbd "<f5>") 'bh/org-todo) +(defun bh/org-todo (arg) + (interactive "p") + (if (equal arg 4) + (save-restriction + (bh/narrow-to-org-subtree) + (org-show-todo-tree nil)) + (bh/narrow-to-org-subtree) + (org-show-todo-tree nil))) + +(global-set-key (kbd "<S-f5>") 'bh/widen) +(defun bh/widen () + (interactive) + (if (equal major-mode 'org-agenda-mode) + (progn + (org-agenda-remove-restriction-lock) + (when org-agenda-sticky + (org-agenda-redo))) + (widen))) + +(defun bh/narrow-to-org-subtree () + (widen) + (org-narrow-to-subtree) + (save-restriction + (org-agenda-set-restriction-lock))) + ;; AGENDA VIEW ;; ;; Do not dim blocked tasks diff --git a/.emacs.d/rul-lisp/packages/rul-org-journal.el b/.emacs.d/rul-lisp/packages/rul-org-journal.el deleted file mode 100644 index 46fcebf..0000000 --- a/.emacs.d/rul-lisp/packages/rul-org-journal.el +++ /dev/null @@ -1,19 +0,0 @@ -(use-package org-journal - :ensure t - :init - ;; Change default prefix key; needs to be set before loading org-journal - (setq org-journal-prefix-key "C-c j ") - :config - (require 'rul-config-org) - (setq org-journal-date-format "%A, %d %B %Y") - - (global-set-key (kbd "C-c j o") 'org-journal-open-current-journal-file) - (global-set-key (kbd "C-c j j") 'org-journal-new-entry) - (global-set-key (kbd "C-c j J") 'org-journal-new-scheduled-entry) - - (define-key org-journal-mode-map (kbd "C-c j n") 'org-journal-next-entry) - (define-key org-journal-mode-map (kbd "C-c j p") 'org-journal-previous-entry) - (define-key org-journal-mode-map (kbd "C-c j r") 'org-journal-reschedule-scheduled-entry) - ) - -(provide 'rul-org-journal) diff --git a/.emacs.d/rul-lisp/packages/rul-org-notify.el b/.emacs.d/rul-lisp/packages/rul-org-notify.el deleted file mode 100644 index 50c35a0..0000000 --- a/.emacs.d/rul-lisp/packages/rul-org-notify.el +++ /dev/null @@ -1,9 +0,0 @@ -(use-package org-notify - :ensure t - :config - (org-notify-start) - (org-notify-add 'default - '(:time "-1s" :period "20s" :duration 10 - :actions (-message -ding)) - '(:time "1d" :actions -notify/window - :duration 60))) diff --git a/.emacs.d/rul-lisp/packages/rul-org.el b/.emacs.d/rul-lisp/packages/rul-org.el index d076948..9439b86 100644 --- a/.emacs.d/rul-lisp/packages/rul-org.el +++ b/.emacs.d/rul-lisp/packages/rul-org.el @@ -1,24 +1,36 @@ -;; Debian packages: elpa-org -;; Elpa packages: org-modern +;;; rul-org.el --- Org configuration (require 'org) (require 'org-capture) (require 'org-protocol) (require 'org-habit) -(require 'rul-config-org) - (require 'rul-org-agenda) -(require 'rul-org-journal) +(setq org-attach-use-inheritance t) (setq org-cycle-separator-lines 0) -(setq org-startup-indented t) (setq org-hide-leading-stars nil) +(setq org-startup-indented t) +(setq org-edit-src-content-indentation 0) (use-package org-modern :ensure t) -(use-package org-pomodoro :ensure t) +(use-package org-pomodoro + :ensure t + :config + (defun rul/disable-notifications () + "Disable GNOME notifications." + (shell-command "gsettings set org.gnome.desktop.notifications show-banners false")) -(add-hook 'org-mode-hook 'turn-off-auto-fill) -(add-hook 'auto-save-hook 'org-save-all-org-buffers) + (defun rul/enable-notifications () + "Enable GNOME notifications." + (shell-command "gsettings set org.gnome.desktop.notifications show-banners true")) + + ;; Add hooks for Pomodoro start and finish + (add-hook 'org-pomodoro-started-hook #'rul/disable-notifications) + (add-hook 'org-pomodoro-finished-hook #'rul/enable-notifications) + (add-hook 'org-pomodoro-killed-hook #'rul/enable-notifications)) + +;; (add-hook 'org-mode-hook 'turn-off-auto-fill) +;; (add-hook 'auto-save-hook 'org-save-all-org-buffers) (add-hook 'org-mode-hook 'visual-line-mode) (use-package org-download @@ -39,6 +51,12 @@ (global-set-key (kbd "C-c c") #'org-capture) (global-set-key (kbd "C-c s") #'org-schedule) + +(global-set-key (kbd "<f9>") 'bh/punch-in) +(global-set-key (kbd "<f10>") 'bh/punch-out) +(global-set-key (kbd "<f11>") 'org-clock-goto) +(global-set-key (kbd "<f12>") 'org-agenda) + ;; ORG STATES ;; (setq org-todo-keywords (quote ((sequence "TODO(t)" "MAYBE(m)" "NEXT(n)" "|" "DONE(d)") @@ -61,14 +79,13 @@ (setq org-log-reschedule (quote time)) ;; CAPTURE ;; -(setq org-default-notes-file org-refile-path) (setq org-capture-templates (quote ( ("w" "Todo" entry (file+headline org-refile-path "Tasks") - "* TODO " + "* TODO %?" :empty-lines 1) ("m" @@ -84,39 +101,15 @@ ("L" "Web Link" entry (file+headline org-refile-path "Read later") - "* %?[[%:link][%:description]] %(progn (setq rul/delete-frame-after-capture 1) \"\")\n %:initial\n \nCaptured On: %U" + "* %?[[%:link][%:description]] \"\")\n %:initial\n \nCaptured On: %U" ) ("l" "Web Link with Selection" entry (file+headline org-refile-path "Read later") - "* [[%:link][%:description]] %(progn (setq rul/delete-frame-after-capture 1) \"\")\n %:initial\n \nCaptured On: %U") + "* [[%:link][%:description]] \n %:initial\n \nCaptured On: %U") ))) -(defvar rul/delete-frame-after-capture 0 "Whether to delete the last frame after the current capture") - -(defun rul/delete-frame-if-necessary () - "Delete the last frame if necessary." - (cond - ((= rul/delete-frame-after-capture 0) nil) - ((> rul/delete-frame-after-capture 1) - (setq rul/delete-frame-after-capture (- rul/delete-frame-after-capture 1))) - (t - (setq rul/delete-frame-after-capture 0) - (delete-frame)))) - -(defun rul/org-capture-before () - "Function to run before org capture." - (setq rul/delete-frame-after-capture (1+ rul/delete-frame-after-capture))) - -(defun rul/org-capture-after () - "Function to run after org capture." - (rul/delete-frame-if-necessary)) - -(advice-add 'org-capture-finalize :after 'rul/delete-frame-if-necessary) -(advice-add 'org-capture-kill :after 'rul/delete-frame-if-necessary) -(advice-add 'org-capture-refile :after 'rul/delete-frame-if-necessary) - ;; REFILE ;; ; Targets include this file and any file contributing to the agenda - up to 3 levels deep diff --git a/.emacs.d/rul-lisp/packages/rul-prog.el b/.emacs.d/rul-lisp/packages/rul-prog.el index eb9c676..581dbc1 100644 --- a/.emacs.d/rul-lisp/packages/rul-prog.el +++ b/.emacs.d/rul-lisp/packages/rul-prog.el @@ -1,14 +1,44 @@ +;;; rul-prog.el --- Configuration related to programming and markup +;;; languages (use-package eglot :ensure t) -;; Dart -(setq dart-server-format-on-save t) -(add-hook 'dart-mode-hook 'lsp) +;; Go +(use-package go-mode + :ensure t + :init + (progn + (bind-key [remap find-tag] #'godef-jump)) + :config + (add-hook 'go-mode-hook #'yas-minor-mode) + (add-hook 'go-mode-hook 'electric-pair-mode) + (add-hook 'go-mode-hook 'my-go-mode-hook) + (add-hook 'before-save-hook 'gofmt-before-save)) -(setq gc-cons-threshold (* 100 1024 1024) - read-process-output-max (* 1024 1024)) +(use-package go-eldoc + :ensure t + :init + (add-hook 'go-mode-hook 'go-eldoc-setup)) + +;; Latex +(add-hook 'latex-mode-hook 'flyspell-mode) +(setq TeX-PDF-mode t) + +(defun pdfevince () + (add-to-list 'TeX-output-view-style + '("^pdf$" "." "evince %o %(outpage)"))) + +(add-hook 'LaTeX-mode-hook 'pdfevince t) ; AUCTeX LaTeX mode + +;; Markdown +(use-package markdown-mode + :ensure t + :config + (setq auto-mode-alist + (cons '("\\.mdwn" . markdown-mode) auto-mode-alist))) ;; Python -(use-package blacken :ensure t) +(use-package blacken :ensure t :defer t) +(add-hook 'python-mode-hook 'py-autopep8-enable-on-save) ;; Terraform (use-package terraform-mode :ensure t :defer t) @@ -16,4 +46,12 @@ ;; YAML (use-package yaml-mode :ensure t :defer t) +;; Rust +(use-package rust-mode + :defer t + :init + (setq rust-mode-treesitter-derive t) + :config + (add-hook 'rust-mode-hook 'eglot-ensure)) + (provide 'rul-prog) diff --git a/.emacs.d/rul-lisp/packages/rul-terminals.el b/.emacs.d/rul-lisp/packages/rul-terminals.el new file mode 100644 index 0000000..9678b55 --- /dev/null +++ b/.emacs.d/rul-lisp/packages/rul-terminals.el @@ -0,0 +1,9 @@ +(use-package multi-vterm + :ensure t + :init + (setq vterm-always-compile-module t) + :config + (setq vterm-max-scrollback 100000) + (add-hook 'vterm-mode-hook 'goto-address-mode)) + +(provide 'rul-terminals) diff --git a/.emacs.d/rul-init.d/themes.el b/.emacs.d/rul-lisp/packages/rul-themes.el index d5c591a..2c696ec 100644 --- a/.emacs.d/rul-init.d/themes.el +++ b/.emacs.d/rul-lisp/packages/rul-themes.el @@ -1,17 +1,17 @@ -(use-package modus-themes :ensure t) (use-package ef-themes :ensure t) - -(setq - modus-themes-mode-line '(accented borderless padded) - modus-themes-region '(bg-only) - modus-themes-bold-constructs t - modus-themes-italic-constructs t - modus-themes-paren-match '(bold intense) - modus-themes-headings (quote ((1 . (rainbow variable-pitch 1.3)) - (2 . (rainbow 1.1)) - (t . (rainbow)))) - modus-themes-org-blocks 'tinted - ) +(use-package modus-themes + :ensure t + :config + (setq + modus-themes-mode-line '(accented borderless padded) + modus-themes-region '(bg-only) + modus-themes-bold-constructs t + modus-themes-italic-constructs t + modus-themes-paren-match '(bold intense) + modus-themes-headings (quote ((1 . (rainbow variable-pitch 1.3)) + (2 . (rainbow 1.1)) + (t . (rainbow)))) + modus-themes-org-blocks 'tinted)) (use-package dbus) (defun mf/set-theme-from-dbus-value (value) @@ -19,9 +19,9 @@ (message "value is %s" value) (if (equal value '1) (progn (message "Switch to dark theme") - (modus-themes-select 'modus-vivendi-tinted)) + (modus-themes-select 'modus-vivendi)) (progn (message "Switch to light theme") - (modus-themes-select 'modus-operandi-tinted)))) + (modus-themes-select 'modus-operandi)))) (defun mf/color-scheme-changed (path var value) "DBus handler to detect when the color-scheme has changed." @@ -46,3 +46,6 @@ "org.freedesktop.appearance" "color-scheme" ) + + +(provide 'rul-themes) diff --git a/.emacs.d/rul-init.d/magit.el b/.emacs.d/rul-lisp/packages/rul-vc.el index 20ba99d..a2ab563 100644 --- a/.emacs.d/rul-init.d/magit.el +++ b/.emacs.d/rul-lisp/packages/rul-vc.el @@ -1,10 +1,8 @@ -;; Debian packages: elpa-magit - (use-package magit :ensure t - :defer t :bind (("C-c g s" . magit-status) + ("C-c g F" . magit-pull-from-upstream) ("C-c g b" . magit-blame)) :config (progn @@ -15,3 +13,7 @@ (window-configuration-to-register :magit-fullscreen) ad-do-it (delete-other-windows)))) + +(add-to-list 'project-switch-commands '(magit-project-status "Magit" "m")) + +(provide 'rul-vc) diff --git a/.emacs.d/rul-lisp/packages/rul-wm.el b/.emacs.d/rul-lisp/packages/rul-wm.el index cea3a76..5a2293c 100644 --- a/.emacs.d/rul-lisp/packages/rul-wm.el +++ b/.emacs.d/rul-lisp/packages/rul-wm.el @@ -21,14 +21,112 @@ (setq tab-bar-new-tab-to 'rightmost) (setq tab-bar-close-button-show nil) (set-face-attribute 'tab-bar nil :height 0.8) -(tab-bar-mode 1) + +;; I've moved to a frame oriented workflow, so I no longer use tabs. +;; (tab-bar-mode 1) + +;; Pop-up buffers +;; https://protesilaos.com/codelog/2024-09-19-emacs-command-popup-frame-emacsclient/ +(defun prot-window-delete-popup-frame (&rest _) + "Kill selected selected frame if it has parameter `prot-window-popup-frame'. +Use this function via a hook." + (when (frame-parameter nil 'prot-window-popup-frame) + (delete-frame))) + +(defmacro prot-window-define-with-popup-frame (command) + "Define interactive function which calls COMMAND in a new frame. +Make the new frame have the `prot-window-popup-frame' parameter." + `(defun ,(intern (format "prot-window-popup-%s" command)) () + ,(format "Run `%s' in a popup frame with `prot-window-popup-frame' parameter. +Also see `prot-window-delete-popup-frame'." command) + (interactive) + (let ((frame (make-frame '((prot-window-popup-frame . t))))) + (select-frame frame) + ;; Placeholder for frame, otherwise it'll get autoclosed. + (switch-to-buffer " prot-window-hidden-buffer-for-popup-frame") + (condition-case nil + (call-interactively ',command) + ((quit error user-error) + (delete-frame frame)))))) + +(declare-function org-capture "org-capture" (&optional goto keys)) +(defvar org-capture-after-finalize-hook) + +;;;###autoload (autoload 'prot-window-popup-org-capture "prot-window") +(prot-window-define-with-popup-frame org-capture) + +(add-hook 'org-capture-after-finalize-hook #'prot-window-delete-popup-frame) (use-package olivetti :ensure t :defer t :config - (setq - olivetti-body-width 100 - )) + (setq olivetti-body-width 100)) + +(use-package logos +:ensure t +:config + +;; If you want to use outlines instead of page breaks (the ^L) +(setq logos-outlines-are-pages t) +(setq logos-outline-regexp-alist + `((emacs-lisp-mode . "^;;;+ ") + (org-mode . "^\\*+ +") + (markdown-mode . "^\\#+ +") + )) + +;; These apply when `logos-focus-mode' is enabled. Their value is +;; buffer-local. +(setq-default logos-hide-mode-line t + logos-hide-buffer-boundaries t + logos-hide-fringe t + logos-variable-pitch nil + logos-buffer-read-only nil + logos-scroll-lock nil + logos-olivetti t + olivetti-body-width 100 + ) + + +(let ((map global-map)) + (define-key map [remap narrow-to-region] #'logos-narrow-dwim) + (define-key map [remap forward-page] #'logos-forward-page-dwim) + (define-key map [remap backward-page] #'logos-backward-page-dwim) + (define-key map (kbd "<f7>") #'logos-focus-mode)) +) + +(use-package beframe + :ensure t + :hook (after-init . beframe-mode) + :config + (setq beframe-functions-in-frames '(project-prompt-project-dir)) + (setq beframe-global-buffers nil) + (define-key global-map (kbd "C-c b") beframe-prefix-map) + + ;;Integration with Consult + (defvar consult-buffer-sources) + (declare-function consult--buffer-state "consult") + + (with-eval-after-load 'consult + (defface beframe-buffer + '((t :inherit font-lock-string-face)) + "Face for `consult' framed buffers.") + + (defun my-beframe-buffer-names-sorted (&optional frame) + "Return the list of buffers from `beframe-buffer-names' sorted by visibility. +With optional argument FRAME, return the list of buffers of FRAME." + (beframe-buffer-names frame :sort #'beframe-buffer-sort-visibility)) + + (defvar beframe-consult-source + `( :name "Frame-specific buffers (current frame)" + :narrow ?F + :category buffer + :face beframe-buffer + :history beframe-history + :items ,#'my-beframe-buffer-names-sorted + :action ,#'switch-to-buffer + :state ,#'consult--buffer-state)) + + (add-to-list 'consult-buffer-sources 'beframe-consult-source))) (provide 'rul-wm) diff --git a/.emacs.d/rul-lisp/packages/rul-write.el b/.emacs.d/rul-lisp/packages/rul-write.el index 16eae84..719baaf 100644 --- a/.emacs.d/rul-lisp/packages/rul-write.el +++ b/.emacs.d/rul-lisp/packages/rul-write.el @@ -7,7 +7,14 @@ (use-package denote :ensure t + :hook (dired-mode . denote-dired-mode) + :bind + (("C-c n n" . denote) + ("C-c n r" . denote-rename-file) + ("C-c n l" . denote-link) + ("C-c n b" . denote-backlinks)) :config + (denote-rename-buffer-mode 1) (setq denote-infer-keywords t) (setq denote-sort-keywords t) (setq denote-file-type 'org) @@ -16,16 +23,14 @@ (setq denote-link-fontify-backlinks t) (setq denote-rename-no-confirm t) - (add-hook 'find-file-hook #'denote-link-buttonize-buffer) - (add-hook 'dired-mode-hook #'denote-dired-mode-in-directories) - (let ((map global-map)) (define-key map (kbd "C-c n j") #'rul/denote-journal) (define-key map (kbd "C-c n n") #'denote) (define-key map (kbd "C-c n f") #'denote-open-or-create) (define-key map (kbd "C-c n i") #'denote-link) (define-key map (kbd "C-c n r") #'denote-rename-file) - )) + ) +) (defun rul/denote-journal () "Create an entry tagged 'journal' with the date as its title. @@ -46,4 +51,45 @@ Else create a new file." today '("journal")))))) +;; auto-fill mode +(add-hook 'text-mode-hook 'turn-on-auto-fill) + +;; Flycheck +(use-package flycheck + :ensure t + :config + +(flycheck-define-checker proselint + "A linter for prose." + :command ("proselint" source-inplace) + :error-patterns + ((warning line-start (file-name) ":" line ":" column ": " + (id (one-or-more (not (any " ")))) + (message) line-end)) + :modes (text-mode markdown-mode gfm-mode org-mode)) + +(add-to-list 'flycheck-checkers 'proselint) + +;; TODO: docker run --rm -p 8010:8010 erikvl87/languagetool +(use-package flycheck-languagetool + :ensure t + :hook (message-mode . flycheck-languagetool-setup) + :init + (setq flycheck-languagetool-url "http://localhost:8010") +)) + +;; Flyspell +(defcustom flyspell-delayed-commands nil + "List of commands that are \"delayed\" for Flyspell mode. +After these commands, Flyspell checking is delayed for a short time, +whose length is specified by `flyspell-delay'." + :group 'flyspell + :type '(repeat (symbol))) + +(setq ispell-dictionary "en") +(setq flyspell-default-dictionary "en") + +(setq flyspell-issue-welcome-flag nil) +(setq-default ispell-list-command "list") + (provide 'rul-write) diff --git a/.xprofile b/.xprofile new file mode 100644 index 0000000..d1606ec --- /dev/null +++ b/.xprofile @@ -0,0 +1 @@ +export PATH="$HOME/bin:$PATH"
\ No newline at end of file @@ -8,3 +8,6 @@ bin_file_name := 10-$(cur_dir) all: $(bin_dir)/pycombine -e '(Makefile|README.md|debian)' "$(HOME)" "$(repo_dir)" xdg-settings set default-url-scheme-handler org-protocol org-protocol.desktop + +fonts: + $(bin_dir)/setup-install-fonts diff --git a/bin/gnome-move-windows b/bin/gnome-move-windows new file mode 100755 index 0000000..0b6c0cc --- /dev/null +++ b/bin/gnome-move-windows @@ -0,0 +1,36 @@ +#!/bin/sh +# Move windows according to my workflow. Check bin/gnome-set-config to +# see its key-binding. Needs wmctrl. + +# Move all windows to the primary display. If they're on the secondary +# display, and we try to move them to a workspace, it won't work. +for window_id in $(wmctrl -l | awk '{print $1}'); do + wmctrl -i -r $window_id -e 0,0,0,-1,-1 +done + +# Assign windows to predetermined workplaces +misc=$(wmctrl -l | awk '/isco|eepa/ {print $1}') +main="$(wmctrl -xl | awk '/ emacs/ {print $1}')" +communications="$(wmctrl -l | awk '/ebex|lack|communications|notmuch|elfeed/ {print $1}')" +terminals="$(wmctrl -l | awk '/Alacritty|kitty|terminal/ {print $1}')" +browsers="$(wmctrl -xl | awk '/irefox|hrom/ {print $1}')" + +for window_id in $misc; do + wmctrl -i -r $window_id -t 4 +done + +for window_id in $main; do + wmctrl -i -r $window_id -t 0 +done + +for window_id in $browsers; do + wmctrl -i -r $window_id -t 1 +done + +for window_id in $communications; do + wmctrl -i -r $window_id -t 2 +done + +for window_id in $terminals; do + wmctrl -i -r $window_id -t 3 +done diff --git a/bin/gnome-set-config b/bin/gnome-set-config index 5bfb476..15771d7 100755 --- a/bin/gnome-set-config +++ b/bin/gnome-set-config @@ -1,12 +1,46 @@ #!/bin/sh +# Sets my preferred Gnome config. +# Find existing bindings with: +# for e in $(gsettings list-schemas | grep bind); do gsettings list-recursively $e; done NUM_WORKSPACES=9 gsettings set org.gnome.mutter dynamic-workspaces false gsettings set org.gnome.desktop.wm.preferences num-workspaces $NUM_WORKSPACES +# Disable the default <Super>p. I don't use it, and it's disruptive when I accidentally trigger it. +gsettings set org.gnome.mutter.keybindings switch-monitor '[]' + for i in $(seq 1 $NUM_WORKSPACES); do gsettings set org.gnome.shell.keybindings switch-to-application-$i '[]' gsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-$i "['<Super>$i']" gsettings set org.gnome.desktop.wm.keybindings move-to-workspace-$i "['<Shift><Super>$i']" done + +# This configuration is not present in gsettings; we need to fall back to dconf +bindings="emacs org-mode move-windows rofi" +keybindings_key="/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings" +keybindings=$(echo $bindings | awk -v key="$keybindings_key" '{for(i=1;i<=NF;i++) printf("'\''" key "/" $i "/'\''%s", (i==NF ? "" : ","))}') +keybindings="[$keybindings]" + +dconf write "$keybindings_key" "$keybindings" + +dconf write /org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/emacs/binding "'<Shift><Super>e'" +dconf write /org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/emacs/command "'emacsclient -c'" +dconf write /org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/emacs/name "'Emacs'" + +dconf write /org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/org-mode/binding "'<Shift><Super>o'" +dconf write /org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/org-mode/command "'emacsclient -e \"(prot-window-popup-org-capture)\"'" +dconf write /org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/org-mode/name "'org-capture'" + +dconf write /org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/move-windows/binding "'<Shift><Super>m'" +dconf write /org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/move-windows/command "'gnome-move-windows'" +dconf write /org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/move-windows/name "'move-windows'" + +# Disable default for <Super>space. +gsettings set org.gnome.desktop.wm.keybindings switch-input-source "[]" +gsettings set org.gnome.desktop.wm.keybindings switch-input-source-backward '[]' + +dconf write /org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/rofi/binding "'<Super>space'" +dconf write /org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/rofi/command "'rofi -show window'" +dconf write /org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/rofi/name "'rofi'" diff --git a/bin/setup-install-fonts b/bin/setup-install-fonts new file mode 100755 index 0000000..ea96873 --- /dev/null +++ b/bin/setup-install-fonts @@ -0,0 +1,26 @@ +#!/bin/bash + +set -e + +FONT_DIR="$HOME/.local/share/fonts" +TEMP_DIR=$(mktemp -d) + +mkdir -p "$FONT_DIR" + +pushd $TEMP_DIR +curl -s 'https://api.github.com/repos/be5invis/Iosevka/releases/latest' | \ + jq -r ".assets[] | .browser_download_url" | \ + grep PkgTTC-Iosevka | \ + xargs -n 1 curl -L -O --fail --silent --show-error + +for f in *.zip; do + unzip "$f" +done + +mv *.ttc "$FONT_DIR/" +fc-cache -f -v "$FONT_DIR" + +popd +rm -rf "$TEMP_DIR" + +echo "Iosevka fonts have been installed." diff --git a/debian/control b/debian/control index 9ff4f63..33513e5 100644 --- a/debian/control +++ b/debian/control @@ -6,8 +6,13 @@ Build-Depends: fzf, make, cmake, + jq, + libtool-bin, mpv, python3-proselint, + ripgrep, + rofi, + vim, Standards-Version: 4.5.1 Rules-Requires-Root: no |