aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.alias.d/00-general2
-rw-r--r--.config/rofi/config1
-rw-r--r--.config/rofi/config.rasi7
-rw-r--r--.emacs.d/early-init.el42
-rw-r--r--.emacs.d/init.el204
-rw-r--r--.emacs.d/rul-emacs.org1553
-rw-r--r--.emacs.d/rul-init.d/auto-fill.el4
-rw-r--r--.emacs.d/rul-init.d/flycheck.el22
-rw-r--r--.emacs.d/rul-init.d/flyspell.el12
-rw-r--r--.emacs.d/rul-init.d/go-lang.el23
-rw-r--r--.emacs.d/rul-init.d/ibuffer.el35
-rw-r--r--.emacs.d/rul-init.d/imenu.el12
-rw-r--r--.emacs.d/rul-init.d/latex.el9
-rw-r--r--.emacs.d/rul-init.d/logos.el31
-rw-r--r--.emacs.d/rul-init.d/mail-mode.el15
-rw-r--r--.emacs.d/rul-init.d/markdown.el5
-rw-r--r--.emacs.d/rul-init.d/notmuch.el136
-rw-r--r--.emacs.d/rul-init.d/python.el1
-rw-r--r--.emacs.d/rul-init.d/vterm.el35
-rw-r--r--.emacs.d/rul-init.d/which-key.el5
-rw-r--r--.emacs.d/rul-lisp/config/rul-config-elfeed.el2
-rw-r--r--.emacs.d/rul-lisp/config/rul-config-mail.el1
-rw-r--r--.emacs.d/rul-lisp/config/rul-config-org.el22
-rw-r--r--.emacs.d/rul-lisp/packages/rul-bindings.el (renamed from .emacs.d/rul-init.d/hydra.el)18
-rw-r--r--.emacs.d/rul-lisp/packages/rul-completion.el48
-rw-r--r--.emacs.d/rul-lisp/packages/rul-elfeed.el6
-rw-r--r--.emacs.d/rul-lisp/packages/rul-fm.el19
-rw-r--r--.emacs.d/rul-lisp/packages/rul-fonts.el (renamed from .emacs.d/rul-init.d/fonts.el)4
-rw-r--r--.emacs.d/rul-lisp/packages/rul-io.el (renamed from .emacs.d/rul-lisp/packages/rul-media.el)10
-rw-r--r--.emacs.d/rul-lisp/packages/rul-mail.el140
-rw-r--r--.emacs.d/rul-lisp/packages/rul-modeline.el2
-rw-r--r--.emacs.d/rul-lisp/packages/rul-org-agenda.el31
-rw-r--r--.emacs.d/rul-lisp/packages/rul-org-journal.el19
-rw-r--r--.emacs.d/rul-lisp/packages/rul-org-notify.el9
-rw-r--r--.emacs.d/rul-lisp/packages/rul-org.el67
-rw-r--r--.emacs.d/rul-lisp/packages/rul-prog.el50
-rw-r--r--.emacs.d/rul-lisp/packages/rul-terminals.el9
-rw-r--r--.emacs.d/rul-lisp/packages/rul-themes.el (renamed from .emacs.d/rul-init.d/themes.el)33
-rw-r--r--.emacs.d/rul-lisp/packages/rul-vc.el (renamed from .emacs.d/rul-init.d/magit.el)8
-rw-r--r--.emacs.d/rul-lisp/packages/rul-wm.el106
-rw-r--r--.emacs.d/rul-lisp/packages/rul-write.el54
-rw-r--r--.xprofile1
-rw-r--r--Makefile3
-rwxr-xr-xbin/gnome-move-windows36
-rwxr-xr-xbin/gnome-set-config34
-rwxr-xr-xbin/setup-install-fonts26
-rw-r--r--debian/control5
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
diff --git a/Makefile b/Makefile
index f77b891..0aa85f9 100644
--- a/Makefile
+++ b/Makefile
@@ -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
nihil fit ex nihilo