From c0cec3086fc89e3a9bbdfd50e9a7d241801b537d Mon Sep 17 00:00:00 2001 From: Raul Benencia Date: Fri, 27 Mar 2026 12:39:48 -0700 Subject: emacs: refresh rul-emacs.org --- .emacs.d/rul-emacs.org | 569 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 409 insertions(+), 160 deletions(-) (limited to '.emacs.d') diff --git a/.emacs.d/rul-emacs.org b/.emacs.d/rul-emacs.org index 1ba5aae..188fc2b 100644 --- a/.emacs.d/rul-emacs.org +++ b/.emacs.d/rul-emacs.org @@ -38,25 +38,6 @@ Customization of graphical aspects of Emacs, such as size, panels, etc. (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 @@ -345,17 +326,6 @@ together, offering a menu on the way. ("=" 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 @@ -591,7 +561,9 @@ installs =fontaine=, a software that allows defining font presets. (small :default-height 120) (medium - :default-height 140) + :default-height 150) + (wayland-medium + :default-height 320) (large :default-weight semilight :default-height 180 @@ -612,10 +584,8 @@ installs =fontaine=, a software that allows defining font presets. (t :default-family "Iosevka" :default-weight regular - :default-height 140 + :default-height 150 :variable-pitch-family "Iosevka Aile"))) - - ;; Set desired style from `fontaine-presets' (fontaine-set-preset 'medium)) (provide 'rul-fonts) @@ -650,7 +620,10 @@ Internet radio channels. It requires =mpv= to be installed. ("SomaFM - Metal" . "https://somafm.com/metal.pls") ("SomaFM - Lush" . "https://somafm.com/lush130.pls") ("KCSM Jazz 91" . "http://ice5.securenetsystems.net/KCSM") - ))) + ("KSUA 91.5 FM" . "https://stream.radio.co/se776fab22/listen") + )) + (setq empv-fd-binary "fdfind") + ) (provide 'rul-io) #+end_src @@ -678,6 +651,8 @@ Emacs can act as Mail User Agent. My preferred package for this is notmuch-show-text/html-blocked-images nil ) + (setq notmuch-draft-folder "current/Drafts") + ;; Keymaps (defun rul/capture-mail() "Capture mail to org mode." @@ -913,10 +888,14 @@ Specific to the current window's mode line.") mode-line-front-space mode-line-buffer-identification mode-line-front-space + mode-line-percent-position + mode-line-front-space rul-modeline-major-mode prot-modeline-eglot mode-line-format-right-align rul-modeline-misc-info + mode-line-front-space + mode-line-front-space )) (provide 'rul-modeline) @@ -979,8 +958,9 @@ My org mode configuration is quite big, so I split it across multiple files. (global-set-key (kbd "C-c c") #'org-capture) (global-set-key (kbd "C-c s") #'org-schedule) -(global-set-key (kbd "") 'bh/punch-in) -(global-set-key (kbd "") 'bh/punch-out) +(global-set-key (kbd "") 'org-clock-goto) +(global-set-key (kbd "") 'org-clock-in-last) +(global-set-key (kbd "") 'org-clock-out) (global-set-key (kbd "") 'org-agenda) ;; ORG STATES ;; @@ -1099,6 +1079,40 @@ My org mode configuration is quite big, so I split it across multiple files. ;; Honor ATTR_ORG attribute. Defaults to image's width if not set. (setq org-image-actual-width nil) +(setq org-clock-mode-line-total 'today) + +;; org-tempus +(unless (package-installed-p 'org-tempus) + (package-vc-install "https://github.com/rul/org-tempus.git")) + +(use-package org-tempus + :init + (org-tempus-mode 1)) + +(use-package org-remark-global-tracking + ;; It is recommended that `org-remark-global-tracking-mode' be + ;; enabled when Emacs initializes. You can set it in + ;; `after-init-hook'. + :hook after-init + :config + ;; Selectively keep or comment out the following if you want to use + ;; extensions for Info-mode, EWW, and NOV.el (EPUB) respectively. + (use-package org-remark-eww :after eww :config (org-remark-eww-mode +1)) + (use-package org-remark-nov :after nov :config (org-remark-nov-mode +1))) + +(use-package org-remark + :bind (;; :bind keyword also implicitly defers org-remark itself. + ;; Keybindings before :map is set for global-map. Adjust the keybinds + ;; as you see fit. + ("C-c n m" . org-remark-mark) + ("C-c n l" . org-remark-mark-line) + :map org-remark-mode-map + ("C-c n o" . org-remark-open) + ("C-c n ]" . org-remark-view-next) + ("C-c n [" . org-remark-view-prev) + ("C-c n r" . org-remark-remove) + ("C-c n d" . org-remark-delete))) + (provide 'rul-org) #+end_src @@ -1373,8 +1387,6 @@ Skip project and sub-project tasks, habits, and loose non-project tasks." (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 @@ -1392,22 +1404,6 @@ Skip project and sub-project tasks, habits, and loose non-project tasks." ;; 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 @@ -1419,60 +1415,6 @@ Switch projects and subprojects from NEXT back to TODO" (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 "redefine") ;; org-id-get-create - ;; https://stackoverflow.com/a/10091330 (defun zin/org-agenda-skip-tag (tag &optional others) "Skip all entries that correspond to TAG. @@ -1490,48 +1432,6 @@ If OTHERS is true, skip all entries that do not correspond to TAG." 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) - -;;; Focusing on current work - -(global-set-key (kbd "") '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 "") '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 @@ -1569,9 +1469,7 @@ on a single file. (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 @@ -1598,13 +1496,6 @@ on a single file. ;; Python (use-package blacken :ensure t :defer t) -(with-eval-after-load 'lsp-mode - (lsp-register-client - (make-lsp-client - :new-connection (lsp-stdio-connection '("ruff" "server")) - :activation-fn (lsp-activate-on "python") - :server-id 'ruff-lsp))) -(add-hook 'python-mode-hook #'lsp) ;; Terraform (use-package terraform-mode :ensure t :defer t) @@ -1622,3 +1513,361 @@ on a single file. (provide 'rul-prog) #+end_src +** The =terminals= module +TODO + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-terminals.el" +(use-package vterm + :ensure t + :init + (setq vterm-always-compile-module t + vterm-max-scrollback 100000) + :hook + (vterm-mode . goto-address-mode) + :bind + (:map vterm-mode-map + ("C-c C-t" . vterm-copy-mode) + ("C-l" . vterm-clear)) + :config + (define-key vterm-mode-map (kbd "C-c C-c") + (lambda () + (interactive) + (vterm-send-string "\C-c"))) + + (defun rul/vterm-copy-and-exit (beg end) + "Copy region and exit `vterm-copy-mode'." + (interactive "r") + (kill-ring-save beg end) + (vterm-copy-mode -1)) + + (define-key vterm-copy-mode-map (kbd "w") #'rul/vterm-copy-and-exit) + (define-key vterm-copy-mode-map (kbd "M-w") #'rul/vterm-copy-and-exit)) + +(use-package multi-vterm + :ensure t + :after vterm + :bind (("C-c t" . multi-vterm)) + :config + (setq vterm-kill-buffer-on-exit t) + + (defvar-local rul/vterm-close-tab-on-kill nil + "When non-nil, close this buffer's tab when the vterm buffer is killed.") + + (defun rul/vterm-maybe-close-tab () + "Close the current tab if this vterm buffer was opened in its own tab." + (when rul/vterm-close-tab-on-kill + (tab-close))) + + (defun rul/vterm-new-tab () + "Create a new tab and open a new vterm." + (interactive) + (tab-new) + (multi-vterm) + (setq-local rul/vterm-close-tab-on-kill t) + (add-hook 'kill-buffer-hook #'rul/vterm-maybe-close-tab nil t)) + + ;; Inside vterm buffers, make C-c t spawn a new tab + vterm + (define-key vterm-mode-map (kbd "C-c t") #'rul/vterm-new-tab) + (define-key vterm-mode-map (kbd "C-S-t") #'rul/vterm-new-tab)) + +(provide 'rul-terminals) +#+end_src + +** The =vc= module +TODO + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-vc.el" +;;; rul-vc.el --- Version control configuration -*- lexical-binding: t; -*- + +(setq vc-follow-symlinks nil) + +(use-package magit + :ensure t + :bind (("C-c g s" . magit-status) + ("C-c g F" . magit-pull-from-upstream) + ("C-c g b" . magit-blame)) + :hook (git-commit-setup . rul/git-commit-setup) + :config + (defun rul/git-commit-setup () + "Enable useful text modes for Git commit buffers." + (flyspell-mode 1) + (auto-fill-mode 1)) + + (defun rul/magit-status-save-window-config (&rest _) + "Save current window configuration before invoking `magit-status'." + (window-configuration-to-register :magit-fullscreen)) + + (defun rul/magit-status-single-window (&rest _) + "Display `magit-status' in a single window." + (delete-other-windows)) + + (advice-add 'magit-status :before #'rul/magit-status-save-window-config) + (advice-add 'magit-status :after #'rul/magit-status-single-window)) + +(with-eval-after-load 'project + (add-to-list 'project-switch-commands + '(magit-project-status "Magit" "m"))) + +(provide 'rul-vc) +;;; rul-vc.el ends here +#+end_src + +** The =wm= module +TODO + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-wm.el" +;;;; window.el +;; Inspiration: https://christiantietze.de/posts/2022/12/updated-org-mode-agenda-display-buffer-alist/ +(defun rul/display-buffer-org-agenda-managed-p (buffer-name action) + "Determine whether BUFFER-NAME is an org-agenda managed buffer." + (with-current-buffer buffer-name + (or (derived-mode-p 'org-mode 'org-agenda-mode) + (member (buffer-file-name) (org-agenda-files))))) + +;; Side window for dictionary +(setq switch-to-buffer-obey-display-actions t) +(add-to-list 'display-buffer-alist + '("^\\*Dictionary\\*" display-buffer-in-side-window + (side . bottom) + (window-height . 12) + )) + +;;;; tab-bar.el +(let ((map global-map)) +(define-key map (kbd "C-") 'tab-bar-switch-to-next-tab) +(define-key map (kbd "C-") 'tab-bar-switch-to-prev-tab) +(define-key map (kbd "") 'tab-bar-mode)) + +(setq tab-bar-format + '(tab-bar-format-tabs + ;; tab-bar-format-align-right + ;; tab-bar-format-global + )) + +(setq tab-bar-new-tab-to 'rightmost) +(setq tab-bar-close-button-show nil) +(set-face-attribute 'tab-bar nil :height 0.8) + +;; 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)) + +(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 "") #'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))) + +(defun kill-project-buffers-and-close-frame () + (interactive) + (project-kill-buffers) + (delete-frame (selected-frame))) + +(define-key global-map (kbd "C-x p K") 'kill-project-buffers-and-close-frame) + +(add-hook 'text-mode-hook 'context-menu-mode) + +(defun my-context-menu (menu click) + "My context menu" + (define-key-after menu [dictionary-lookup] + '(menu-item "Dict" dictionary-search-word-at-mouse + :help "Look up in dictionary")) + menu) + +;; hook into context menu +(add-hook 'context-menu-functions #'my-context-menu) + +(provide 'rul-wm) +#+end_src + +** The =write= module +TODO + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-write.el" +;;;; `dictionary' +(setq dictionary-server "localhost" + dictionary-default-popup-strategy "lev" + dictionary-create-buttons nil + dictionary-use-single-buffer t) +(define-key global-map (kbd "C-c d") #'dictionary-lookup-definition) + +(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) + (setq denote-excluded-directories-regexp nil) + (setq denote-allow-multi-word-keywords nil) + (setq denote-link-fontify-backlinks t) + (setq denote-rename-no-confirm t) + + (let ((map global-map)) + (define-key map (kbd "C-c n j") #'denote-journal-new-or-existing-entry) + (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) + ) +) + +(use-package electric + :init + (setq electric-quote-replace-double t) + :hook + (message-mode . electric-quote-local-mode)) + +(use-package message + :hook + (message-mode . my/message-mode-setup)) + +(defun my/message-mode-setup () + (setq fill-column 72 + sentence-end-double-space nil) + (auto-fill-mode 1)) + +;; 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) +#+end_src + -- cgit v1.2.3