From 21794a4dfa857a770155b9e0c59af885e0d5756c Mon Sep 17 00:00:00 2001 From: BardofSprites <89086143+BardofSprites@users.noreply.github.com> Date: Fri, 26 Dec 2025 19:40:49 -0500 Subject: literate configuration --- bard-elisp/bard-ryo.el | 49 - bard-elisp/bard-theme.el | 68 +- bard-emacs-modules/bard-emacs-calendar.el | 46 +- bard-emacs-modules/bard-emacs-completion.el | 92 +- bard-emacs-modules/bard-emacs-dired.el | 17 +- bard-emacs-modules/bard-emacs-email.el | 99 +- bard-emacs-modules/bard-emacs-emms.el | 0 bard-emacs-modules/bard-emacs-essentials.el | 26 +- bard-emacs-modules/bard-emacs-keyboard.el | 12 +- bard-emacs-modules/bard-emacs-media.el | 2 - bard-emacs-modules/bard-emacs-modeline.el | 3 +- bard-emacs-modules/bard-emacs-org.el | 10 +- bard-emacs-modules/bard-emacs-prog.el | 6 + bard-emacs-modules/bard-emacs-theme.el | 95 +- config.org | 4702 +++++++++++++++++++++++++++ snippets/org-mode/time | 2 +- 16 files changed, 4992 insertions(+), 237 deletions(-) delete mode 100644 bard-elisp/bard-ryo.el delete mode 100644 bard-emacs-modules/bard-emacs-emms.el create mode 100644 config.org diff --git a/bard-elisp/bard-ryo.el b/bard-elisp/bard-ryo.el deleted file mode 100644 index 782aab8..0000000 --- a/bard-elisp/bard-ryo.el +++ /dev/null @@ -1,49 +0,0 @@ -(defun bard/ryo-insert-p () - "Return t if `ryo-modal-mode` is not bound or not active." - (not (bound-and-true-p ryo-modal-mode))) - -(defun xah-beginning-of-line-or-block () - "Move cursor to beginning of indent or line, end of previous block, in that order. - -If `visual-line-mode' is on, beginning of line means visual line. - -URL `http://xahlee.info/emacs/emacs/emacs_move_by_paragraph.html' -Created: 2018-06-04 -Version: 2024-10-30" - (interactive) - (let ((xp (point))) - (if (or (eq (point) (line-beginning-position)) - (eq last-command this-command)) - (when (re-search-backward "\n[\t\n ]*\n+" nil :move) - (skip-chars-backward "\n\t ") - ;; (forward-char) - ) - (if visual-line-mode - (beginning-of-visual-line) - (if (eq major-mode 'eshell-mode) - (progn - (declare-function eshell-bol "esh-mode.el" ()) - (eshell-bol)) - (back-to-indentation) - (when (eq xp (point)) - (beginning-of-line))))))) - -(defun xah-end-of-line-or-block () - "Move cursor to end of line or next block. - -• When called first time, move cursor to end of line. -• When called again, move cursor forward by jumping over any sequence of whitespaces containing 2 blank lines. -• if `visual-line-mode' is on, end of line means visual line. - -URL `http://xahlee.info/emacs/emacs/emacs_move_by_paragraph.html' -Created: 2018-06-04 -Version: 2024-10-30" - (interactive) - (if (or (eq (point) (line-end-position)) - (eq last-command this-command)) - (re-search-forward "\n[\t\n ]*\n+" nil :move) - (if visual-line-mode - (end-of-visual-line) - (end-of-line)))) - -(provide 'bard-ryo) diff --git a/bard-elisp/bard-theme.el b/bard-elisp/bard-theme.el index 98f420a..da33997 100644 --- a/bard-elisp/bard-theme.el +++ b/bard-elisp/bard-theme.el @@ -2,7 +2,7 @@ '((default :default-height 140 :default-family "Iosevka Comfy" - :variable-pitch-family "Iosevka Comfy Motion" + :variable-pitch-family "Iosevka Comfy" :variable-pitch-height 1.0 :fixed-pitch-family "Iosevka Comfy" :fixed-pitch-height 1.0 @@ -12,10 +12,32 @@ :inherit default :default-height 135) (wide - :inherit default + :default-height 135 :default-family "Iosevka Comfy Wide" :fixed-pitch-family "Iosevka Comfy Wide" :variable-pitch-family "Iosevka Comfy Wide Motion Duo") + (prot + :default-family "Iosevka Comfy Wide Motion" + :default-height 130 + :default-weight medium + :fixed-pitch-family "Iosevka Comfy Wide Motion" + :variable-pitch-family "Iosevka Comfy Wide Duo" + :bold-weight extrabold) + (mono + :default-height 130 + :default-family "monospace" + :fixed-pitch-family "monospace" + :variable-pitch-family "Baskerville" + :variable-pitch-height 140) + (mono-large + :inherit mono + :default-height 150 + :variable-pitch-height 160) + (mac + :default-height 130 + :default-family "Monaco" + :variable-pitch-family "Monaco" + :fixed-pitch-family "Monaco") (large :inherit default :default-height 160 @@ -44,8 +66,7 @@ (dolist (hook '(bard/after-theme-load-hook)) (add-hook hook #'fontaine-apply-current-preset) - (add-hook hook #'logos-update-fringe-in-buffers) - (add-hook hook #'bard/update-ryo-cursor-color)) + (add-hook hook #'logos-update-fringe-in-buffers)) (defun bard/select-theme (&optional theme) "Enable the specified THEME, or prompt the user to select one if THEME is nil." @@ -96,4 +117,43 @@ leave it alone in pdf-view-mode." (add-hook 'post-command-hook #'bard/update-cursor-type) +(defun bard/outline-heading-faces () + (set-face-attribute 'org-level-1 nil + :inherit '(outline-1 variable-pitch) + :weight 'light + :height 1.5) + + (set-face-attribute 'org-level-2 nil + :inherit '(outline-2 variable-pitch) + :weight 'light + :height 1.3) + + (set-face-attribute 'org-level-3 nil + :inherit 'outline-3 + :height 1.2) + + (set-face-attribute 'org-level-4 nil + :inherit '(outline-4 variable-pitch) + :height 1.1) + + (set-face-attribute 'org-level-5 nil + :inherit '(outline-5 variable-pitch) + :height 1.1) + + (set-face-attribute 'org-level-6 nil + :inherit '(outline-6 variable-pitch) + :height 1.1) + + (set-face-attribute 'org-agenda-date nil + :inherit 'variable-pitch + :weight 'bold + :height 1.3) + + (set-face-attribute 'org-agenda-structure nil + :inherit 'variable-pitch + :weight 'bold + :height 1.5)) + +(add-hook 'bard/after-theme-load-hook #'bard/outline-heading-faces) + (provide 'bard-theme) diff --git a/bard-emacs-modules/bard-emacs-calendar.el b/bard-emacs-modules/bard-emacs-calendar.el index 54e7795..25a5c8d 100644 --- a/bard-emacs-modules/bard-emacs-calendar.el +++ b/bard-emacs-modules/bard-emacs-calendar.el @@ -14,6 +14,15 @@ (calendar-mode . denote-journal-calendar-mode) (calendar-today-visible . calendar-mark-holidays)) +(use-package orthodox-christian-new-calendar-holidays + :ensure t + :config + (setq holiday-other-holidays (append holiday-other-holidays orthodox-christian-new-calendar-holidays)) + + (setq holiday-bahai-holidays nil + holiday-christian-holidays nil + holiday-islamic-holidays nil)) + ;; Org todo keywords - changed to using hl-todo faces fixed by modus/ef themes (setq org-todo-keywords '((sequence "TODO(t)" "EXTRA(e)" "INPROG(i)" "|" "DONE(d)" "KILLED(k)") @@ -30,8 +39,6 @@ ;; Automatically clock in (add-hook 'org-after-todo-state-change-hook #'bard/auto-clock-in) -;;; Org Agenda - ;; clock tables (setq org-clock-clocktable-default-properties '(:maxlevel 7 :scope agenda) org-clock-persist 'history @@ -43,20 +50,15 @@ :hook ((org-clock-out . bard/org-clock-update-mode-line))) -(global-set-key (kbd "M-") 'bard/choose-agenda) -(global-set-key (kbd "C-c a c") 'bard/choose-agenda) - (global-set-key (kbd "") 'bard/default-agenda) -(global-set-key (kbd "C-c a a") 'bard/default-agenda) +(global-set-key (kbd "M-") 'bard/choose-agenda) -;; Org Agenda (setq org-agenda-include-diary t) (setq org-agenda-custom-commands `(("D" "Daily agenda and top priority tasks" ((tags-todo "!TODO" ((org-agenda-overriding-header "Unscheduled Tasks \n") (org-agenda-skip-function '(org-agenda-skip-entry-if 'timestamp)))) - (agenda "" ((org-agenda-span 1) (org-agenda-start-day nil) (org-deadline-warning-days 14) @@ -88,32 +90,4 @@ org-habit-show-all-today t) ) -(use-package orthodox-christian-new-calendar-holidays - :ensure t - :config - (setq holiday-other-holidays (append holiday-other-holidays orthodox-christian-new-calendar-holidays)) - - (setq holiday-bahai-holidays nil - holiday-christian-holidays nil - holiday-islamic-holidays nil)) - -(defun bard/export-agenda-to-file () - (interactive) - (org-eval-in-environment (org-make-parameter-alist - '(org-agenda-span - 'week - org-agenda-use-time-grid t - org-agenda-remove-tags t - org-agenda-window-setup 'nope)) - (let* ((wins (current-window-configuration)) - org-agenda-sticky) - (save-excursion - (with-current-buffer - (get-buffer-create org-agenda-buffer-name) - (pop-to-buffer (current-buffer)) - (org-agenda nil "a") - (let ((result (buffer-string))) - (with-temp-file "~/.cache/agenda" (insert result))))) - (set-window-configuration wins)))) - (provide 'bard-emacs-calendar) diff --git a/bard-emacs-modules/bard-emacs-completion.el b/bard-emacs-modules/bard-emacs-completion.el index fda5397..b8bfce7 100644 --- a/bard-emacs-modules/bard-emacs-completion.el +++ b/bard-emacs-modules/bard-emacs-completion.el @@ -4,7 +4,7 @@ :config (setq vertico-scroll-margin 0) (setq vertico-cycle nil) - + (setq vertico-resize nil) (vertico-mode t) (with-eval-after-load 'rfn-eshadow @@ -30,17 +30,17 @@ '(read-only t cursor-intangible t face minibuffer-prompt)) ;; MCT has a variant of this built-in. - (defun crm-indicator (args) - (cons (format "[`completing-read-multiple': %s] %s" - (propertize - (replace-regexp-in-string - "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" "" - crm-separator) - 'face 'error) - (car args)) - (cdr args))) - - (advice-add #'completing-read-multiple :filter-args #'crm-indicator) + (defun crm-indicator (args) + (cons (format "[`completing-read-multiple': %s] %s" + (propertize + (replace-regexp-in-string + "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" "" + crm-separator) + 'face 'error) + (car args)) + (cdr args))) + + (advice-add #'completing-read-multiple :filter-args #'crm-indicator) (file-name-shadow-mode 1)) @@ -176,7 +176,6 @@ (setq embark-verbose-indicator-excluded-actions '(embark-cycle embark-act-all embark-collect embark-export embark-insert))) -;; Savehist (setq savehist-file (locate-user-emacs-file "savehist")) (setq history-length 100) (setq history-delete-duplicates t) @@ -184,15 +183,12 @@ (setq savehist-additional-variables '(register-alist kill-ring)) (savehist-mode t) -;; recentf (recentf-mode t) (setq recentf-save-file (locate-user-emacs-file "recentf")) ;; save cursor place for code files (add-hook 'prog-mode-hook #'save-place-local-mode) - -;;; abbrev-mode (setq abbrev-file-name (locate-user-emacs-file "abbrevs")) (setq only-global-abbrevs nil) @@ -203,38 +199,38 @@ "protweb" "https://protesilaos.com/") (bard/make-abbrev text-mode-abbrev-table - "asciidoc" "AsciiDoc" - "auctex" "AUCTeX" - "cafe" "café" - "cliche" "cliché" - "clojurescript" "ClojureScript" - "emacsconf" "EmacsConf" - "github" "GitHub" - "gitlab" "GitLab" - "javascript" "JavaScript" - "latex" "LaTeX" - "libreplanet" "LibrePlanet" - "linkedin" "LinkedIn" - "paypal" "PayPal" - "sourcehut" "SourceHut" - "texmacs" "TeXmacs" - "typescript" "TypeScript" - "visavis" "vis-à-vis" - "vscode" "Visual Studio Code" - "youtube" "YouTube" - "Результат" "=Результат Сегодняшний Битвый=" - "asf" "and so on and so forth" - "paragraph" "¶" - "em" "—" - "ua" "↑" - "da" "↓" - "ra" "→" - "la" "←" - "iff" "⇔" - "imp" "⇒" - "tf" "∴" - "xmonad" "XMonad" - "xmobar" "XMobar") + "asciidoc" "AsciiDoc" + "auctex" "AUCTeX" + "cafe" "café" + "cliche" "cliché" + "clojurescript" "ClojureScript" + "emacsconf" "EmacsConf" + "github" "GitHub" + "gitlab" "GitLab" + "javascript" "JavaScript" + "latex" "LaTeX" + "libreplanet" "LibrePlanet" + "linkedin" "LinkedIn" + "paypal" "PayPal" + "sourcehut" "SourceHut" + "texmacs" "TeXmacs" + "typescript" "TypeScript" + "visavis" "vis-à-vis" + "vscode" "Visual Studio Code" + "youtube" "YouTube" + "Результат" "=Результат Сегодняшний Битвый=" + "asf" "and so on and so forth" + "paragraph" "¶" + "em" "—" + "ua" "↑" + "da" "↓" + "ra" "→" + "la" "←" + "iff" "⇔" + "imp" "⇒" + "tf" "∴" + "xmonad" "XMonad" + "xmobar" "XMobar") (dolist (hook '(text-mode-hook prog-mode-hook git-commit-mode-hook)) (add-hook hook #'abbrev-mode)) diff --git a/bard-emacs-modules/bard-emacs-dired.el b/bard-emacs-modules/bard-emacs-dired.el index e233f52..f4470fa 100644 --- a/bard-emacs-modules/bard-emacs-dired.el +++ b/bard-emacs-modules/bard-emacs-dired.el @@ -24,22 +24,14 @@ ("\\.\\(mp[34]\\|m4a\\|ogg\\|flac\\|webm\\|mkv\\)" "mpv" "xdg-open") (".gif" "mpv --loop=inf") (".*" "xdg-open"))) + (setq dired-dwim-target t) + (setq dired-listing-switches + "-AgGFhlv --group-directories-first --time-style=long-iso") :hook ((dired-mode . dired-hide-details-mode) ;; attachments for email through dired (dired-mode . turn-on-gnus-dired-mode))) -(use-package dired - :ensure nil - :defer 1 - :config - (setq dired-dwim-target t) - ;; (setq dired-listing-switches "-lXGh --group-directories-first") - (setq dired-listing-switches - "-AgGFhlv --group-directories-first --time-style=long-iso") - ) - -;; Image dired (use-package image-dired :bind (:map dired-mode-map @@ -89,7 +81,6 @@ (start-process "feh" nil "feh" "--bg-fill" image-file) (message "Background set to %s" image-file)))) - ;; Taken from https://superuser.com/a/176629 (defun bard/dired-do-command (command) "Run COMMAND on marked files. Any files not already open will be opened. @@ -105,7 +96,7 @@ open and unsaved." (use-package dired-video-thumbnail :ensure t :vc (:url "https://github.com/captainflasmr/dired-video-thumbnail" - :rev :newest) + :rev :newest) :bind (:map dired-mode-map ("C-t v" . dired-video-thumbnail))) diff --git a/bard-emacs-modules/bard-emacs-email.el b/bard-emacs-modules/bard-emacs-email.el index 68605a7..797f01b 100644 --- a/bard-emacs-modules/bard-emacs-email.el +++ b/bard-emacs-modules/bard-emacs-email.el @@ -1,57 +1,26 @@ (require 'bard-email) -;; General `notmuch' ui configuration - (use-package notmuch :ensure t :config - (define-key global-map (kbd "C-c m") #'notmuch)) - -(use-package notmuch-indicator - :ensure t - :after notmuch - :config - (setq notmuch-indicator-args - '(( :terms "tag:unread and tag:inbox" - :label "[U] " - :label-face prot-modeline-indicator-green - :counter-face prot-modeline-indicator-green) - ( :terms "tag:unread and tag:linux" - :label "[L] " - :label-face prot-modeline-indicator-cyan - :counter-face prot-modeline-indicator-cyan) - ( :terms "tag:unread and tag:emacs" - :label "[E] " - :label-face prot-modeline-indicator-blue - :counter-face prot-modeline-indicator-blue)) - - notmuch-indicator-refresh-count (* 60 3) - notmuch-indicator-hide-empty-counters t - notmuch-indicator-force-refresh-commands '(notmuch-refresh-this-buffer)) - (setq notmuch-indicator-add-to-mode-line-misc-info nil) - (notmuch-indicator-mode t)) - -;; use msmtp -(setq message-send-mail-function 'message-send-mail-with-sendmail) -(setq sendmail-program "/usr/bin/msmtp") - -(setq notmuch-show-logo t - notmuch-column-control 1.0 - notmuch-hello-auto-refresh t - notmuch-hello-recent-searches-max 20 - notmuch-hello-thousands-separator "" - notmuch-hello-sections '(notmuch-hello-insert-header notmuch-hello-insert-saved-searches notmuch-hello-insert-search notmuch-hello-insert-alltags) - notmuch-show-all-tags-list t) + (define-key global-map (kbd "C-c m") #'notmuch) + (setq notmuch-show-logo t + notmuch-column-control 1.0 + notmuch-hello-auto-refresh t + notmuch-hello-recent-searches-max 20 + notmuch-hello-thousands-separator "" + notmuch-hello-sections '(notmuch-hello-insert-header notmuch-hello-insert-saved-searches notmuch-hello-insert-search notmuch-hello-insert-alltags) + notmuch-show-all-tags-list t) -(setq notmuch-search-oldest-first nil) -(setq notmuch-show-seen-current-message t) + (setq notmuch-search-oldest-first nil) + (setq notmuch-show-seen-current-message t) -(defun bard/notmuch-mua-empty-subject-check () - "Request confirmation before sending a message with empty subject." - (when (and (null (message-field-value "Subject")) - (not (y-or-n-p "Subject is empty, send anyway? "))) - (error "Sending message cancelled: empty subject"))) -(add-hook 'message-send-hook 'bard/notmuch-mua-empty-subject-check) + (defun bard/notmuch-mua-empty-subject-check () + "Request confirmation before sending a message with empty subject." + (when (and (null (message-field-value "Subject")) + (not (y-or-n-p "Subject is empty, send anyway? "))) + (error "Sending message cancelled: empty subject"))) + (add-hook 'message-send-hook 'bard/notmuch-mua-empty-subject-check)) (setq notmuch-show-empty-saved-searches t) (setq notmuch-saved-searches @@ -107,4 +76,40 @@ notmuch-draft-folder "drafts" notmuch-draft-save-plaintext 'ask) +(use-package notmuch-indicator + :ensure t + :after notmuch + :config + (setq notmuch-indicator-args + '(( :terms "tag:unread and tag:inbox" + :label "[U] " + :label-face prot-modeline-indicator-green + :counter-face prot-modeline-indicator-green) + ( :terms "tag:unread and tag:linux" + :label "[L] " + :label-face prot-modeline-indicator-cyan + :counter-face prot-modeline-indicator-cyan) + ( :terms "tag:unread and tag:emacs" + :label "[E] " + :label-face prot-modeline-indicator-blue + :counter-face prot-modeline-indicator-blue)) + + notmuch-indicator-refresh-count (* 60 3) + notmuch-indicator-hide-empty-counters t + notmuch-indicator-force-refresh-commands '(notmuch-refresh-this-buffer)) + (setq notmuch-indicator-add-to-mode-line-misc-info nil) + (notmuch-indicator-mode t)) + +(use-package ol-notmuch + :ensure t + :after notmuch) + +;; use msmtp +(setq sendmail-program "/usr/bin/msmtp" + message-send-mail-function 'message-send-mail-with-sendmail + message-sendmail-f-is-evil nil + mail-specify-envelope-from t + message-sendmail-envelope-from 'header + mail-envelope-from t) + (provide 'bard-emacs-email) diff --git a/bard-emacs-modules/bard-emacs-emms.el b/bard-emacs-modules/bard-emacs-emms.el deleted file mode 100644 index e69de29..0000000 diff --git a/bard-emacs-modules/bard-emacs-essentials.el b/bard-emacs-modules/bard-emacs-essentials.el index 05c6654..e8a0f17 100644 --- a/bard-emacs-modules/bard-emacs-essentials.el +++ b/bard-emacs-modules/bard-emacs-essentials.el @@ -1,6 +1,3 @@ -;;; Editing niceties - -;; writeable grep buffers (use-package wgrep :ensure t :bind @@ -12,7 +9,6 @@ (:map grep-mode-map ("e" . wgrep-change-to-wgrep-mode))) -;; preview replace (use-package multiple-cursors :ensure t :config @@ -46,18 +42,10 @@ desktop-restore-=frams t desktop-restory-in-current-display t desktop-files-not-to-save "\(^$\\|\\*scratch\\*\\|\\*Messages\\*\\|\\*dashboard\\*\\|\\*Async-native-compile-log\\*|\\*Music\\*)") - -;;; General Keybinds - -;; Buffer switching -(global-set-key (kbd "C-x C-b") 'ibuffer) - -;; Desktop/session save ;; (desktop-save-mode t) (global-set-key (kbd "C-z s") 'desktop-save-in-desktop-dir) (global-set-key (kbd "C-z r") 'desktop-read) -;;; Scratch buffers ;; Text Scratch buffers (defun bard/new-org-buffer () (interactive) @@ -87,7 +75,6 @@ (text-scale-increase 1.5) xbuf)) -;;; Terminals (defun bard/open-terminal-in-current-directory () "Open a terminal in the current working directory." (interactive) @@ -107,7 +94,6 @@ (define-key global-map (kbd "C-z C-s") #'bard/new-elisp-buffer) -;;; Time Management ;; Modeline (setq display-time-format "%Y-%m-%d (%a) %H:%M") (setq display-time-interval 60) @@ -138,8 +124,11 @@ (use-package tmr :ensure t :config - (setq tmr-sound-file "/home/bard/.local/bin/scripts/bell.mp3") (setq tmr-notification-urgency 'normal) + (setq tmr-sound-file nil) + (setq tmr-timer-finished-functions '(tmr-notification-notify + tmr-print-message-for-finished-timer + tmr-acknowledge-minibuffer)) (setq tmr-descriptions-list 'tmr-description-history) (define-key global-map (kbd "C-c t l") 'tmr-tabulated-view) (define-key global-map (kbd "C-c t t") #'tmr) @@ -152,16 +141,9 @@ (define-key global-map (kbd "C-c t r") #'tmr-remove) (define-key global-map (kbd "C-c t R") #'tmr-remove-finished)) -;; running emacs as server (require 'server) (setq server-client-instructions nil) (unless (server-running-p) (server-start)) -(use-package treemacs - :ensure t - :bind - ("M-`" . treemacs) ; uses old text-based menu keybind - ) - (provide 'bard-emacs-essentials) diff --git a/bard-emacs-modules/bard-emacs-keyboard.el b/bard-emacs-modules/bard-emacs-keyboard.el index 061907f..95a7b30 100644 --- a/bard-emacs-modules/bard-emacs-keyboard.el +++ b/bard-emacs-modules/bard-emacs-keyboard.el @@ -9,6 +9,7 @@ (setq evil-want-keybinding nil) ;; no vim insert bindings (setq evil-undo-system 'undo-fu) + (setq evil-respect-visual-line-mode t) :config (evil-mode 1)) @@ -30,7 +31,7 @@ (general-create-definer bard/leader-keys :states '(normal insert visual emacs) :prefix "SPC" - :global-prefix "C-SPC") + :global-prefix "C-S-SPC") (bard/leader-keys @@ -41,7 +42,7 @@ ;; buffers "b" '(:ignore t :which-key "buffers") "bb" '(consult-buffer :which-key "switch buffer") - "bd" '(kill-this-buffer :which-key "kill buffer") + "bk" '(kill-buffer :which-key "kill buffer") "bs" '(save-buffer :which-key "save buffer") ;; windows @@ -94,9 +95,14 @@ "t" '(:ignore t :which-key "toggle") "tf" '(toggle-frame-fullscreen :which-key "fullscreen") "tf" '(toggle-frame-fullscreen :which-key "fullscreen") + + ;; help + "h" '(:ignore t :which-key "help") + "hr" '(info-emacs-manual :which-key "emacs manual") + "hR" '(info-display-manual :which-key "emacs search manual") + "hi" '(info :which-key "info") ) ) (provide 'bard-emacs-keyboard) - diff --git a/bard-emacs-modules/bard-emacs-media.el b/bard-emacs-modules/bard-emacs-media.el index 77c3556..7d15fa4 100644 --- a/bard-emacs-modules/bard-emacs-media.el +++ b/bard-emacs-modules/bard-emacs-media.el @@ -1,4 +1,3 @@ -;;;; EMMS (use-package emms :ensure t ;; :demand t @@ -47,7 +46,6 @@ (setq bard/watch-later-file "~/Videos/watch-later.m3u") ) -;;;; PDFS (use-package pdf-tools :ensure t :config diff --git a/bard-emacs-modules/bard-emacs-modeline.el b/bard-emacs-modules/bard-emacs-modeline.el index f674b04..2b10722 100644 --- a/bard-emacs-modules/bard-emacs-modeline.el +++ b/bard-emacs-modules/bard-emacs-modeline.el @@ -26,7 +26,8 @@ mode-line-format-right-align prot-modeline-notmuch-indicator " " - prot-modeline-misc-info)) + prot-modeline-misc-info + " ")) (with-eval-after-load 'spacious-padding (defun prot/modeline-spacious-indicators () diff --git a/bard-emacs-modules/bard-emacs-org.el b/bard-emacs-modules/bard-emacs-org.el index 5b74ff4..a7a9666 100644 --- a/bard-emacs-modules/bard-emacs-org.el +++ b/bard-emacs-modules/bard-emacs-org.el @@ -20,10 +20,7 @@ (("C-c c" . org-capture)) :config (setq org-goto-interface 'outline-path-completion) - (setq safe-local-variable-values '((org-refile-targets (nil :maxlevel . 3)))) - ;; :hook - ;; ((org-mode . org-num-mode)) - ) + (setq safe-local-variable-values '((org-refile-targets (nil :maxlevel . 3))))) ;; Org Variables (setq bard/org-anki-file "~/Notes/denote/20240729T171836--anki-flashcards__cards_meta.org") @@ -205,7 +202,8 @@ :hook (org-mode . org-bullets-mode) :config (setq org-bullets-bullet-list '("◉" "○" "●" "🞛" "◇" "◆")) - (set-fontset-font t 'symbol (font-spec :family "Iosevka Comfy") nil 'append) - (set-fontset-font t 'unicode (font-spec :family "Iosevka Comfy") nil 'append)) + ;; (set-fontset-font t 'symbol (font-spec :family "Iosevka Comfy") nil 'append) + ;; (set-fontset-font t 'unicode (font-spec :family "Iosevka Comfy") nil 'append) + ) (provide 'bard-emacs-org) diff --git a/bard-emacs-modules/bard-emacs-prog.el b/bard-emacs-modules/bard-emacs-prog.el index dddac09..98b2c18 100644 --- a/bard-emacs-modules/bard-emacs-prog.el +++ b/bard-emacs-modules/bard-emacs-prog.el @@ -24,6 +24,12 @@ (add-to-list 'exec-path bard/ghcup-path))) ;; Lisp +(use-package clojure-mode + :ensure t) + +(use-package cider + :ensure t) + (use-package sly :ensure t :config diff --git a/bard-emacs-modules/bard-emacs-theme.el b/bard-emacs-modules/bard-emacs-theme.el index c729379..969adee 100644 --- a/bard-emacs-modules/bard-emacs-theme.el +++ b/bard-emacs-modules/bard-emacs-theme.el @@ -3,6 +3,79 @@ ;; declare all themes as safe (i trust developers) (setq custom-safe-themes t) +(use-package ef-themes + :ensure t + :demand t + :hook + ((ef-themes-post-load . bard/ef-themes-hl-todo-faces) + (ef-themes-post-load . logos-update-fringe-in-buffers)) + :config + (defun bard/ef-themes-hl-todo-faces () + "Configure `hl-todo-keyword-faces' with Ef themes colors. +The exact color values are taken from the active Ef theme." + (ef-themes-with-colors + (setq hl-todo-keyword-faces + `(("WAIT" . ,yellow) + ("TODO" . ,red) + ("NEXT" . ,blue) + ("THEM" . ,magenta) + ("PROG" . ,cyan-warmer) + ("OKAY" . ,green-warmer) + ("DONT" . ,yellow-warmer) + ("FAIL" . ,red-warmer) + ("BUG" . ,red-warmer) + ("DONE" . ,green) + ("NOTE" . ,blue-warmer) + ("KLUDGE" . ,cyan) + ("HACK" . ,cyan) + ("TEMP" . ,red) + ("FIXME" . ,red-warmer) + ("XXX+" . ,red-warmer) + ("KILLED" . ,cyan) + ("REVIEW" . ,red) + ("DEPRECATED" . ,yellow))))) + + (setq org-priority-faces + '((?A . (:inherit (bold next-error))) + (?B . org-priority) + (?C . (:inherit (shadow org-priority))))) + + ;; (setq ef-themes-headings + ;; '((0 variable-pitch bold 1.5) + ;; (1 variable-pitch bold 1.3) + ;; (2 regular 1.2) + ;; (3 1.1) + ;; (agenda-date . (variable-pitch bold 1.3)) + ;; (agenda-structure . (variable-pitch bold 1.5)) + ;; (t . 1.1))) + + (setq ef-themes-headings + '((0 . (variable-pitch light 1.5)) + (1 . (variable-pitch light 1.3)) + (2 . (regular 1.2)) + (t . (variable-pitch 1.1)) + (agenda-date . (variable-pitch bold 1.3)) + (agenda-structure . (variable-pitch bold 1.5)))) + + ;; better faces in overrides + (setq ef-themes-common-palette-overrides + `((fringe unspecified) + (bg-line-number-active bg-hl-line) + (bg-line-number-inactive unspecified) + (fg-line-number-active fg-main) + (bg-tab-bar bg-dim))) + + ;; verbatim need higher contrast for org mode + (setq ef-elea-dark-palette-overrides + '((prose-verbatim yellow-cooler))) + + (setq ef-elea-light-palette-overrides + '((prose-verbatim yellow-cooler))) + + (define-key global-map (kbd "M-") #'ef-themes-select) + (setq ef-themes-variable-pitch-ui t + ef-themes-mixed-fonts t)) + (use-package doom-themes :ensure t :config @@ -10,12 +83,10 @@ (bard/select-theme 'doom-gruvbox) ) -(use-package doom-modeline +(use-package gruber-darker-theme :ensure t - :config - (doom-modeline-mode t) - (setq doom-modeline-buffer-file-name-style 'buffer-name) - ) + :vc (:url "https://github.com/BardofSprites/gruber-darker-theme" + :rev newest)) ;;;; Fonts (use-package fontaine @@ -48,6 +119,20 @@ (org-mode . mixed-pitch-mode) ) +(use-package spacious-padding + :ensure t + :config + (setq spacious-padding-widths + '( :internal-border-width 10 + :header-line-width 4 + :mode-line-width 6 + :tab-width 4 + :right-divider-width 1 + :left-fringe-width 0 + :right-fringe-width 0 + :scroll-bar-width 0)) + (spacious-padding-mode t)) + (provide 'bard-emacs-theme) ;;; bard-emacs-theme.el ends here diff --git a/config.org b/config.org new file mode 100644 index 0000000..d86cdfb --- /dev/null +++ b/config.org @@ -0,0 +1,4702 @@ +* Startup +** Early initialization +#+begin_src emacs-lisp :tangle early-init.el +(setq inhibit-startup-message t) +(setq inhibit-startup-screen t) + +(setq frame-resize-pixelwise t + frame-inhibit-implied-resize t + use-dialog-box t ; only for mouse events, which I seldom use + use-file-dialog nil + use-short-answers t + inhibit-x-resources t + inhibit-startup-echo-area-message user-login-name + inhibit-startup-buffer-menu t) + +(setq mode-line-misc-info + (delete (assoc 'minor-mode-alist mode-line-misc-info) mode-line-misc-info)) + +(add-hook 'after-init-hook (lambda () (set-frame-name "home"))) + +;; Modes +(scroll-bar-mode -1) +(tool-bar-mode -1) +(tooltip-mode -1) +(set-fringe-mode 10) +(menu-bar-mode -1) +(tool-bar-mode 0) + +;; settings for windows +(setq focus-follows-mouse t) + +(add-to-list 'default-frame-alist '(alpha . (95 . 95))) + +;; garbage collection +(setq gc-cons-threshold most-positive-fixnum + gc-cons-percentage 0.5) + +(defvar bard-emacs--file-name-handler-alist file-name-handler-alist) +(defvar bard-emacs--vc-handled-backends vc-handled-backends) + +(add-hook 'emacs-startup-hook + (lambda () + (setq gc-cons-threshold (* 1000 1000 8) + gc-cons-percentage 0.1 + file-name-handler-alist bard-emacs--file-name-handler-alist + vc-handled-backends bard-emacs--vc-handled-backends))) + +;; Package cache +(setq package-enable-at-startup t) + +#+end_src + +** Initialization +#+begin_src emacs-lisp :tangle init.el +;;; init.el --- init.el -*- lexical-binding: t -*- + +;; Author: BardofSprites +;; Maintainer: BardofSprites +;; Version: 0.1.0 +;; Package-Requires: ((emacs "28.2")) + +;; This file is not part of GNU Emacs + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + + +;;; Commentary: + +;;; Code: + +;; fix keymap +(repeat-mode t) +(global-set-key (kbd "C-z") nil) +(global-set-key (kbd "M-z") nil) + +(global-set-key (kbd "C-z C-z") #'suspend-emacs) + + +;; Add the directories to the load path +(mapc + (lambda (string) + (add-to-list 'load-path (locate-user-emacs-file string))) + '("bard-elisp" "bard-emacs-modules" "old-ada")) + +;; Clipboard saving +(setq select-enable-clipboard t) + +;; custom file is evil +(setq custom-file (make-temp-file "emacs-custom-")) + +;; No Backups +(setq auto-save-default nil) +(setq make-backup-files nil) +(setq confirm-kill-emacs 'y-or-n-p) + +;; native comp enabled +(when (native-comp-available-p) + (setq native-compile-prune-cache t)) + +;; enable/disable commands +(mapc + (lambda (command) + (put command 'disabled nil)) + '(list-timers narrow-to-region narrow-to-page upcase-region downcase-region)) + +(mapc + (lambda (command) + (put command 'disabled t)) + '(eshell project-eshell overwrite-mode iconify-frame diary)) + +;;; Packages +(setq package-archives '(("elpa" . "https://elpa.gnu.org/packages/") + ("nongnu" . "https://elpa.nongnu.org/nongnu/") + ("melpa" . "https://melpa.org/packages/"))) + +;;; MACROS + +(defmacro bard/make-abbrev (table &rest definitions) + "Expand abbrev DEFINITIONS for the given TABLE. +DEFINITIONS is a sequence of (i) string pairs mapping the +abbreviation to its expansion or (ii) a string and symbol pair +making an abbreviation to a function." + (declare (indent 1)) + (unless (zerop (% (length definitions) 2)) + (error "Uneven number of key+command pairs")) + `(if (abbrev-table-p ,table) + (progn + ,@(mapcar + (lambda (pair) + (let ((abbrev (nth 0 pair)) + (expansion (nth 1 pair))) + (if (stringp expansion) + `(define-abbrev ,table ,abbrev ,expansion) + `(define-abbrev ,table ,abbrev "" ,expansion)))) + (seq-split definitions 2))) + (error "%s is not an abbrev table" ,table))) + +(require 'bard-emacs-anki) +(require 'bard-emacs-calendar) +(require 'bard-emacs-completion) +(require 'bard-emacs-dired) +;; (require 'bard-emacs-email) +(require 'bard-emacs-media) +(require 'bard-emacs-eshell) +(require 'bard-emacs-essentials) +;; (require 'bard-emacs-keyboard) +(require 'bard-emacs-modeline) +(require 'bard-emacs-org) +(require 'bard-emacs-prog) +(require 'bard-emacs-theme) +(require 'bard-emacs-ui) +(require 'bard-emacs-web) +(require 'bard-emacs-window) +(require 'bard-emacs-writing) + +(provide 'init) +(put 'eshell 'disabled nil) + +#+end_src +* Modules +** bard-emacs-anki +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-anki.el :mkdirp yes +(use-package anki-editor + :ensure t + :after org + :bind (:map org-mode-map + ("C-c M-i" . bard/anki-editor-cloze-region-auto-incr) + ("C-c M-I" . bard/anki-editor-cloze-region-dont-incr) + ("C-c M-r" . bard/anki-editor-reset-cloze-number) + ("C-c M-p" . anki-editor-push-new-notes) + ("C-c M-P" . anki-editor-push-notes)) + + :hook (org-capture-after-finalize . bard/anki-editor-reset-cloze-number) ; Reset cloze-number after each capture. + :config + (setq anki-editor-create-decks t ;; Allow anki-editor to create a new deck if it doesn't exist + anki-editor-org-tags-as-anki-tags t) + + (defun bard/anki-editor-cloze-region-auto-incr (&optional arg) + "Cloze region without hint and increase card number." + (interactive) + (anki-editor-cloze-region my-anki-editor-cloze-number "") + (setq my-anki-editor-cloze-number (1+ my-anki-editor-cloze-number)) + (forward-sexp)) + (defun bard/anki-editor-cloze-region-dont-incr (&optional arg) + "Cloze region without hint using the previous card number." + (interactive) + (anki-editor-cloze-region (1- my-anki-editor-cloze-number) "") + (forward-sexp)) + (defun bard/anki-editor-reset-cloze-number (&optional arg) + "Reset cloze number to ARG or 1" + (interactive) + (setq my-anki-editor-cloze-number (or arg 1))) + ;; Initialize + (bard/anki-editor-reset-cloze-number) + ) + +(provide 'bard-emacs-anki) +#+end_src + +** bard-emacs-calendar +*** Load required libraries +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-calendar.el :mkdirp yes + (require 'bard-calendar) + (require 'hi-lock) +#+end_src + +*** Org directory and agenda files +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-calendar.el :mkdirp yes + (setq org-directory "~/Notes/denote/") + ;; symlinked file to shorten denote file name in agenda buffers + (setq org-agenda-files (list "~/Notes/denote/todo.org" "~/Notes/denote/uni.org")) +#+end_src + +*** Org Calendar +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-calendar.el :mkdirp yes + ;; Calendar + (use-package calendar-mode + :config + (setq calendar-holidays (append calendar-holidays russian-holidays)) + :hook + (calendar-today-visible . calendar-mark-today) + (calendar-mode . denote-journal-calendar-mode) + (calendar-today-visible . calendar-mark-holidays)) + + (use-package orthodox-christian-new-calendar-holidays + :ensure t + :config + (setq holiday-other-holidays (append holiday-other-holidays orthodox-christian-new-calendar-holidays)) + + (setq holiday-bahai-holidays nil + holiday-christian-holidays nil + holiday-islamic-holidays nil)) +#+end_src + +*** Org todo keywords +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-calendar.el :mkdirp yes + ;; Org todo keywords - changed to using hl-todo faces fixed by modus/ef themes + (setq org-todo-keywords + '((sequence "TODO(t)" "EXTRA(e)" "INPROG(i)" "|" "DONE(d)" "KILLED(k)") + (sequence "MEET(m)" "TENT(T)" "|" "MET(M)" "NOGO(n)"))) + + (setq org-todo-keyword-faces + '(("EXTRA" . (:inherit warning)) + ("TENT" . (:inherit warning)) + ("MEET" . (:inherit warning underline bold)) + ("INPROG" . (:inherit hi-yellow :weight bold)))) + + (setq org-enforce-todo-dependencies t) + + ;; Automatically clock in + (add-hook 'org-after-todo-state-change-hook #'bard/auto-clock-in) +#+end_src + +*** Org clocking +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-calendar.el :mkdirp yes + ;; clock tables + (setq org-clock-clocktable-default-properties '(:maxlevel 7 :scope agenda) + org-clock-persist 'history + org-clock-mode-line-total 'current) + (org-clock-persistence-insinuate) + + (use-package org + :demand t + :hook + ((org-clock-out . bard/org-clock-update-mode-line))) +#+end_src + +*** Org agenda settings +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-calendar.el :mkdirp yes + (global-set-key (kbd "") 'bard/default-agenda) + (global-set-key (kbd "M-") 'bard/choose-agenda) + + (setq org-agenda-include-diary t) + (setq org-agenda-custom-commands + `(("D" "Daily agenda and top priority tasks" + ((tags-todo "!TODO" + ((org-agenda-overriding-header "Unscheduled Tasks \n") + (org-agenda-skip-function '(org-agenda-skip-entry-if 'timestamp)))) + (agenda "" ((org-agenda-span 1) + (org-agenda-start-day nil) + (org-deadline-warning-days 14) + ;; (org-scheduled-past-days 0) + (org-agenda-day-face-function (lambda (date) 'org-agenda-date)) + (org-agenda-format-date "%A %-e %B %Y") + (org-agenda-overriding-header "Today's agenda \n"))) + (agenda "" ((org-agenda-span 8) + (org-calendar-holiday) + (org-deadline-warning-days 0) + (org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done)) + (org-agenda-overriding-header "Upcoming this week \n"))))) + ("Y" "Yearly view for all tasks" + ((agenda "" ((org-agenda-span 365) + (org-deadline-warning-days 2) + (org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done)) + (org-agenda-overriding-header "Upcoming this Year\n"))))) + ("M" "Monthly view for all tasks" + ((agenda "" ((org-agenda-span 31) + (org-deadline-warning-days 2) + (org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done)) + (org-agenda-overriding-header "Upcoming this month\n"))))))) +#+end_src + +*** Org habit +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-calendar.el :mkdirp yes + (use-package org-habit + :after org-agenda + :config + (setq org-habit-show-done-always-green t + org-habit-show-habits t + org-habit-show-all-today t) + ) +#+end_src + +*** Provide module +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-calendar.el :mkdirp yes + (provide 'bard-emacs-calendar) +#+end_src + +** bard-emacs-completion +*** Vertico completion framework +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-completion.el :mkdirp yes + ;; minibuffer completion + (use-package vertico + :ensure t + :config + (setq vertico-scroll-margin 0) + (setq vertico-cycle nil) + (setq vertico-resize nil) + (vertico-mode t) + + (with-eval-after-load 'rfn-eshadow + ;; This works with `file-name-shadow-mode' enabled. When you are in + ;; a sub-directory and use, say, `find-file' to go to your home '~/' + ;; or root '/' directory, Vertico will clear the old path to keep + ;; only your current input. + (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy)) + ) +#+end_src + +*** Rfn eshadow +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-completion.el :mkdirp yes + (use-package rfn-eshadow + :ensure nil + :hook (minibuffer-setup . cursor-intangible-mode) + :config + (setq resize-mini-windows t) + (setq read-answer-short t) ; also check `use-short-answers' for Emacs28 + (setq echo-keystrokes 0.25) + + ;; Do not allow the cursor to move inside the minibuffer prompt. I + ;; got this from the documentation of Daniel Mendler's Vertico + ;; package: . + (setq minibuffer-prompt-properties + '(read-only t cursor-intangible t face minibuffer-prompt)) + + ;; MCT has a variant of this built-in. + (defun crm-indicator (args) + (cons (format "[`completing-read-multiple': %s] %s" + (propertize + (replace-regexp-in-string + "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" "" + crm-separator) + 'face 'error) + (car args)) + (cdr args))) + + (advice-add #'completing-read-multiple :filter-args #'crm-indicator) + + (file-name-shadow-mode 1)) +#+end_src + +*** Marginalia +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-completion.el :mkdirp yes + (use-package marginalia + :ensure t + :init + (marginalia-mode 1)) +#+end_src + +*** Orderless +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-completion.el :mkdirp yes + (use-package orderless + :ensure t + :config + (setq completion-styles '(orderless basic))) +#+end_src + +*** Tab completion +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-completion.el :mkdirp yes + (use-package corfu + :ensure t + :init (global-corfu-mode) + ;; I also have (setq tab-always-indent 'complete) for TAB to complete + ;; when it does not need to perform an indentation change. + :bind (:map corfu-map ("" . corfu-complete)) + :config + (setq corfu-preview-current nil) + (setq corfu-min-width 20) + + (setq corfu-popupinfo-delay nil) + (corfu-popupinfo-mode 1) ; shows documentation after `corfu-popupinfo-delay' + + ;; Sort by input history (no need to modify `corfu-sort-function'). + (with-eval-after-load 'savehist + (corfu-history-mode 1) + (add-to-list 'savehist-additional-variables 'corfu-history))) +#+end_src + +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-completion.el :mkdirp yes + (use-package cape + :ensure t + :init + (add-to-list 'completion-at-point-functions #'cape-file) + (add-to-list 'completion-at-point-functions #'cape-dabbrev) + (add-to-list 'completion-at-point-functions #'cape-keyword) + (add-to-list 'completion-at-point-functions #'cape-abbrev)) + + (use-package clang-capf + :ensure t + :config + (defun bard/clang-capf-init () + "Add `clang-capf' to `completion-at-point-functions'." + (add-hook 'completion-at-point-functions #'clang-capf nil t)) + + (add-hook 'c-mode-hook #'bard/clang-capf-init)) +#+end_src + +**** Completion styles +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-completion.el :mkdirp yes + (use-package minibuffer + :config + ;;;; Completion styles + (setq completion-styles '(basic substring initials flex orderless)) + + (setq completion-category-defaults nil) + + (setq completion-category-overrides + '((file (styles . (basic partial-completion orderless))) + (bookmark (styles . (basic substring))) + (library (styles . (basic substring))) + (embark-keybinding (styles . (basic substring))) + (imenu (styles . (basic substring orderless))) + (consult-location (styles . (basic substring orderless))) + (kill-ring (styles . (emacs22 orderless))) + (eglot (styles . (emacs22 substring orderless)))))) +#+end_src + +*** Minibuffer enhancements +**** Consult +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-completion.el :mkdirp yes + (use-package consult + :ensure t + :defer 2 + :bind* + ("C-x r b" . consult-bookmark) + ("M-g M-g" . consult-goto-line) + ("C-x b" . consult-buffer) + ("M-s M-f" . consult-find) + ("M-s M-g" . consult-grep) + ("M-s M-h" . consult-history) + ("M-s M-y" . consult-yank-pop) + ("M-s M-o" . consult-outline) + ("M-s M-l" . consult-line) + ("M-s M-k" . consult-kmacro) + ("M-s M-r" . consult-register) + :config + (setq consult-find-args + (concat "find . -not ( " + "-path */.git* -prune " + "-or -path */.cache* -prune )"))) +#+end_src + +**** Embark +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-completion.el :mkdirp yes + (use-package embark + :ensure t + :bind* + (("C-," . bard-embark-act-no-quit) + ("C-." . bard-embark-act-quit)) + :config + (require 'bard-embark) + (setq embark-keymap-alist + '((buffer bard-embark-buffer-map) + (command bard-embark-command-map) + (expression bard-embark-expression-map) + (file bard-embark-file-map) + (function bard-embark-function-map) + (identifier bard-embark-identifier-map) + (package bard-embark-package-map) + (region bard-embark-region-map) + (symbol bard-embark-symbol-map) + (url bard-embark-url-map) + (variable bard-embark-variable-map) + (t embark-general-map))) + + (defun bard-embark-act-no-quit () + "Call `embark-act' but do not quit after the action." + (interactive) + (let ((embark-quit-after-action nil)) + (call-interactively #'embark-act))) + + (defun bard-embark-act-quit () + "Call `embark-act' and quit after the action." + (interactive) + (let ((embark-quit-after-action t)) + (call-interactively #'embark-act)) + (when (and (> (minibuffer-depth) 0) + (derived-mode-p 'completion-list-mode)) + (abort-recursive-edit))) + + + (setq embark-confirm-act-all nil) + ;; The prot-embark.el has an advice to further simplify the + ;; minimal indicator. It shows cycling, which I never want to see + ;; or do. + (setq embark-mixed-indicator-both nil) + (setq embark-mixed-indicator-delay 0.1) + (setq embark-indicators '(embark-mixed-indicator embark-highlight-indicator)) + (setq embark-verbose-indicator-nested nil) ; I think I don't have them, but I do not want them either + (setq embark-verbose-indicator-buffer-sections '(bindings)) + (setq embark-verbose-indicator-excluded-actions + '(embark-cycle embark-act-all embark-collect embark-export embark-insert))) +#+end_src + +*** Save minibuffer history (~savehist~) +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-completion.el :mkdirp yes + (setq savehist-file (locate-user-emacs-file "savehist")) + (setq history-length 100) + (setq history-delete-duplicates t) + (setq savehist-save-minibuffer-history t) + (setq savehist-additional-variables '(register-alist kill-ring)) + (savehist-mode t) +#+end_src + +*** Recent files (~recentf~) +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-completion.el :mkdirp yes + (recentf-mode t) + (setq recentf-save-file (locate-user-emacs-file "recentf")) + + ;; save cursor place for code files + (add-hook 'prog-mode-hook #'save-place-local-mode) +#+end_src + +*** Abbreviations (~(d)abbrev-mode~) +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-completion.el :mkdirp yes + (setq abbrev-file-name (locate-user-emacs-file "abbrevs")) + (setq only-global-abbrevs nil) + + (bard/make-abbrev global-abbrev-table + "meweb" "https://bardman.dev" + "megit" "https://github.com/BardofSprites" + "meyt" "https://www.youtube.com/@bardmandev" + "protweb" "https://protesilaos.com/") + + (bard/make-abbrev text-mode-abbrev-table + "asciidoc" "AsciiDoc" + "auctex" "AUCTeX" + "cafe" "café" + "cliche" "cliché" + "clojurescript" "ClojureScript" + "emacsconf" "EmacsConf" + "github" "GitHub" + "gitlab" "GitLab" + "javascript" "JavaScript" + "latex" "LaTeX" + "libreplanet" "LibrePlanet" + "linkedin" "LinkedIn" + "paypal" "PayPal" + "sourcehut" "SourceHut" + "texmacs" "TeXmacs" + "typescript" "TypeScript" + "visavis" "vis-à-vis" + "vscode" "Visual Studio Code" + "youtube" "YouTube" + "Результат" "=Результат Сегодняшний Битвый=" + "asf" "and so on and so forth" + "paragraph" "¶" + "em" "—" + "ua" "↑" + "da" "↓" + "ra" "→" + "la" "←" + "iff" "⇔" + "imp" "⇒" + "tf" "∴" + "xmonad" "XMonad" + "xmobar" "XMobar") + + (dolist (hook '(text-mode-hook prog-mode-hook git-commit-mode-hook)) + (add-hook hook #'abbrev-mode)) + + (remove-hook 'save-some-buffers-functions #'abbrev--possibly-save) +#+end_src + +*** Provide module +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-completion.el :mkdirp yes + (provide 'bard-emacs-completion) + ;;; bard-emacs-completion.el ends here +#+end_src + +** bard-emacs-dired +*** Dired subtree +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-dired.el :mkdirp yes + (use-package dired-subtree + :ensure t + :config + (setq dired-subtree-use-backgrounds nil)) +#+end_src + +*** Dired file preview +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-dired.el :mkdirp yes + (use-package dired-preview + :ensure t + :bind + (:map dired-mode-map + ("P" . dired-preview-mode)) + :config + (setq dired-preview-delay 0.1)) +#+end_src + +*** General dired settings +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-dired.el :mkdirp yes + (use-package dired + :bind* + (("C-j" . dired-jump)) + :bind (:map dired-mode-map + (("E" . emms-add-dired) + ("" . dired-subtree-toggle) + ("" . dired-subtree-cycle))) + :config + (setq dired-guess-shell-alist-user ; those are the suggestions for ! and & in Dired + '(("\\.\\(png\\|jpe?g\\|tiff\\)" "nsxiv" "feh" "xdg-open") + ("\\.\\(mp[34]\\|m4a\\|ogg\\|flac\\|webm\\|mkv\\)" "mpv" "xdg-open") + (".gif" "mpv --loop=inf") + (".*" "xdg-open"))) + (setq dired-dwim-target t) + (setq dired-listing-switches + "-AgGFhlv --group-directories-first --time-style=long-iso") + :hook + ((dired-mode . dired-hide-details-mode) + ;; attachments for email through dired + (dired-mode . turn-on-gnus-dired-mode))) +#+end_src + +*** Image dired +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-dired.el :mkdirp yes + (use-package image-dired + :bind + (:map dired-mode-map + ((")" . image-dired-dired-display-external) + ("B" . bard/dired-set-background-with-feh))) + :bind + ("C-x C-d" . image-dired) + :bind + (:map image-dired-thumbnail-mode-map ("B" . bard/image-dired-set-background-with-feh)) + :config + (define-advice image-dired-display-image (:override (file &optional _ignored)) + (setq file (expand-file-name file)) + (when (not (file-exists-p file)) + (error "No such file: %s" file)) + (let ((buf (get-buffer image-dired-display-image-buffer)) + (cur-win (selected-window))) + (when buf + (kill-buffer buf)) + (when-let ((buf (find-file-noselect file nil t))) + (with-current-buffer buf + (rename-buffer image-dired-display-image-buffer) + (if (string-match (image-file-name-regexp) file) + (image-dired-image-mode) + ;; Support visiting PDF files. + (normal-mode)) + (display-buffer buf)) + (select-window cur-win)))) + + (setq image-dired-thumbnail-storage 'standard) + (setq image-dired-external-viewer "nsxiv") + (setq image-dired-thumb-size 80) + (setq image-dired-thumb-margin 2) + (setq image-dired-thumb-relief 0) + (setq image-dired-thumbs-per-row 4) + + (defun bard/dired-set-background-with-feh () + "Set the selected image as the background using feh." + (interactive) + (let ((image-file (dired-get-file-for-visit))) + (start-process "feh" nil "feh" "--bg-fill" image-file) + (message "Background set to %s" image-file))) + + (defun bard/image-dired-set-background-with-feh () + "Set the selected image as the background using feh." + (interactive) + (let ((image-file (image-dired-original-file-name))) + (start-process "feh" nil "feh" "--bg-fill" image-file) + (message "Background set to %s" image-file)))) +#+end_src + +*** Running commands on dired files +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-dired.el :mkdirp yes + ;; Taken from https://superuser.com/a/176629 + (defun bard/dired-do-command (command) + "Run COMMAND on marked files. Any files not already open will be opened. + After this command has been run, any buffers it's modified will remain + open and unsaved." + (interactive "CRun on marked files M-x ") + (save-window-excursion + (mapc (lambda (filename) + (find-file filename) + (call-interactively command)) + (dired-get-marked-files)))) +#+end_src + +*** Dired video thumbnails +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-dired.el :mkdirp yes + (use-package dired-video-thumbnail + :ensure t + :vc (:url "https://github.com/captainflasmr/dired-video-thumbnail" + :rev :newest) + :bind (:map dired-mode-map + ("C-t v" . dired-video-thumbnail))) +#+end_src + +*** Provide module +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-dired.el :mkdirp yes + (provide 'bard-emacs-dired) +#+end_src + +** bard-emacs-email +*** Require email library +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-email.el :mkdirp yes + (require 'bard-email) +#+end_src + +*** Notmuch mail client settings +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-email.el :mkdirp yes + (use-package notmuch + :ensure t + :config + (define-key global-map (kbd "C-c m") #'notmuch) + (setq notmuch-show-logo t + notmuch-column-control 1.0 + notmuch-hello-auto-refresh t + notmuch-hello-recent-searches-max 20 + notmuch-hello-thousands-separator "" + notmuch-hello-sections '(notmuch-hello-insert-header notmuch-hello-insert-saved-searches notmuch-hello-insert-search notmuch-hello-insert-alltags) + notmuch-show-all-tags-list t) + + (setq notmuch-search-oldest-first nil) + (setq notmuch-show-seen-current-message t) + + (defun bard/notmuch-mua-empty-subject-check () + "Request confirmation before sending a message with empty subject." + (when (and (null (message-field-value "Subject")) + (not (y-or-n-p "Subject is empty, send anyway? "))) + (error "Sending message cancelled: empty subject"))) + (add-hook 'message-send-hook 'bard/notmuch-mua-empty-subject-check)) +#+end_src + +**** Notmuch saved searches +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-email.el :mkdirp yes + (setq notmuch-show-empty-saved-searches t) + (setq notmuch-saved-searches + `(( :name "📥 inbox (all-mail)" + :query "tag:inbox" + :sort-order newest-first + :key ,(kbd "i")) + ( :name "💬 unread (inbox)" + :query "tag:unread and tag:inbox" + :sort-order newest-first + :key ,(kbd "u")) + ( :name "📝 To Do" + :query "tag:todo" + :sort-order oldest-first + :key ,(kbd "t")) + ( :name "🚩 flagged" + :query "tag:flag" + :sort-order newest-first + :key ,(kbd "f")) + ( :name "🐃 contributions" + :query "tag:unread and tag:contrib" + :sort-order newest-first + :key ,(kbd "c")) + ( :name "🐧 linux-related" + :query "tag:unread and tag:linux" + :sort-order newest-first + :key ,(kbd "l")) + ( :name "🚂 emacs developement" + :query "tag:unread and tag:contrib" + :sort-order newest-first + :key ,(kbd "ed")) + ( :name "🎨 emacs humanities" + :query "tag:unread and tag:emacs-humanities" + :sort-order newest-first + :key ,(kbd "eh")) + ( :name "🦄 emacs org-mode" + :query "tag:unread and tag:emacs-org" + :sort-order newest-first + :key ,(kbd "eo")))) +#+end_src + +**** Tagging keys +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-email.el :mkdirp yes + (setq notmuch-tagging-keys + `((,(kbd "d") prot-notmuch-mark-delete-tags "💥 Mark for deletion") + (,(kbd "f") prot-notmuch-mark-flag-tags "🚩 Flag as important") + (,(kbd "s") prot-notmuch-mark-spam-tags "🔥 Mark as spam") + (,(kbd "r") ("-unread") "👁️‍🗨️ Mark as read") + (,(kbd "u") ("+unread") "🗨️ Mark as unread"))) + + (setq notmuch-archive-tags '("+archive") + notmuch-message-replied-tags '("+replied") + notmuch-message-forwarded-tags '("+forwarded") + notmuch-show-mark-read-tags '("-unread") + notmuch-draft-tags '("+draft") + notmuch-draft-folder "drafts" + notmuch-draft-save-plaintext 'ask) +#+end_src + +*** Notmuch modeline indicator +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-email.el :mkdirp yes + (use-package notmuch-indicator + :ensure t + :after notmuch + :config + (setq notmuch-indicator-args + '(( :terms "tag:unread and tag:inbox" + :label "[U] " + :label-face prot-modeline-indicator-green + :counter-face prot-modeline-indicator-green) + ( :terms "tag:unread and tag:linux" + :label "[L] " + :label-face prot-modeline-indicator-cyan + :counter-face prot-modeline-indicator-cyan) + ( :terms "tag:unread and tag:emacs" + :label "[E] " + :label-face prot-modeline-indicator-blue + :counter-face prot-modeline-indicator-blue)) + + notmuch-indicator-refresh-count (* 60 3) + notmuch-indicator-hide-empty-counters t + notmuch-indicator-force-refresh-commands '(notmuch-refresh-this-buffer)) + (setq notmuch-indicator-add-to-mode-line-misc-info nil) + (notmuch-indicator-mode t)) +#+end_src + +*** Org links for notmuch +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-email.el :mkdirp yes + (use-package ol-notmuch + :ensure t + :after notmuch) +#+end_src + +*** Send mail and smtp settings +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-email.el :mkdirp yes + ;; use msmtp + (setq sendmail-program "/usr/bin/msmtp" + message-send-mail-function 'message-send-mail-with-sendmail + message-sendmail-f-is-evil nil + mail-specify-envelope-from t + message-sendmail-envelope-from 'header + mail-envelope-from t) +#+end_src + +*** Provide module +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-email.el :mkdirp yes + (provide 'bard-emacs-email) +#+end_src + +** bard-emacs-eshell +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-eshell.el :mkdirp yes +(use-package eshell + :ensure nil + :bind + (("C-z e" . eshell-switcher)) + :config + (require 'bard-eshell) + (require 'ffap) + ;; (setq eshell-banner-message "Time for another recreational programming session.\n\n") + (setq eshell-banner-message + '(format "%s %s\n %s\n" + (propertize (format " %s " (string-trim (buffer-name))) + 'face 'mode-line-highlight) + (propertize (current-time-string) + 'face 'font-lock-keyword-face) + (propertize "Time for another recreational programming session." + 'face 'warning))) + (setq bard/eshell-aliases + '((g . magit) + (gl . magit-log) + (d . dired) + (o . find-file) + (oo . find-file-other-window) + (vim . find-file) + (l . (lambda () (eshell/ls '-la))) + (eshell/clear . eshell/clear-scrollback))) + + (mapc (lambda (alias) + (defalias (car alias) (cdr alias))) + bard/eshell-aliases)) + +(use-package eshell + :ensure nil + :after esh-mode + :bind + (:map eshell-mode-map + ("C-c C-e" . prot-eshell-export) + ("M-k" . eshell-kill-input) + ("C-c C-d" . prot-eshell-complete-recent-dir) + ("C-c C-h" . prot-eshell-narrow-output-highlight-regexp) + ("C-c C-f" . bard/eshell-find-file-at-point))) + +(provide 'bard-emacs-eshell) +#+end_src + +** bard-emacs-essentials +*** Writable grep buffers (wgrep) +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-essentials.el :mkdirp yes + + (use-package wgrep + :ensure t + :bind + (:map wgrep-mode-map + ("C-x C-s" . wgrep-save-all-buffers) + ("C-x C-q" . wgrep-change-to-wgrep-mode) + ("C-c C-c" . wgrep-finish-edit)) + :bind + (:map grep-mode-map + ("e" . wgrep-change-to-wgrep-mode))) +#+end_src + +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-essentials.el :mkdirp yes + (use-package multiple-cursors + :ensure t + :config + (setq mc/always-run-for-all t) + :bind + (("C-S-c C-S-c" . mc/edit-lines) + ("C->" . mc/mark-next-like-this) + ("C-<" . mc/mark-previous-like-this) + ("C-c C" . mc/mark-all-like-this) + ("C-\"". mc/skip-to-next-like-this) + ("C-;" . mc/skip-to-previous-like-this))) +#+end_src + +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-essentials.el :mkdirp yes + (use-package expand-region + :ensure t + :bind ("C-=" . er/expand-region)) +#+end_src + +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-essentials.el :mkdirp yes + (use-package substitute + :ensure t + :bind + (("C-c s b" . substitute-target-below-point) + ("C-c s a" . substitute-target-above-point) + ("C-c s d" . substitute-target-in-defun) + ("C-c s s" . substitute-target-in-buffer))) +#+end_src + +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-essentials.el :mkdirp yes + ;; Desktop mode/session saving + (setq desktop-path '("~/.emacs.d/desktop") + desktop-dirname "~/.emacs.d/desktop/" + desktop-base-file-name "emacs-desktop" + desktop-save t + desktop-restore-eager t + desktop-restore-=frams t + desktop-restory-in-current-display t + desktop-files-not-to-save "\(^$\\|\\*scratch\\*\\|\\*Messages\\*\\|\\*dashboard\\*\\|\\*Async-native-compile-log\\*|\\*Music\\*)") + ;; (desktop-save-mode t) + (global-set-key (kbd "C-z s") 'desktop-save-in-desktop-dir) + (global-set-key (kbd "C-z r") 'desktop-read) + +#+end_src + +*** Scratch buffers +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-essentials.el :mkdirp yes + ;; Text Scratch buffers + (defun bard/new-org-buffer () + (interactive) + (let ((xbuf (generate-new-buffer "*org*"))) + (switch-to-buffer xbuf) + (funcall (quote org-mode)) + (text-scale-increase 1.5) + xbuf)) + (define-key global-map (kbd "M-=") #'bard/new-org-buffer) + + (defun bard/new-plain-buffer () + (interactive) + (let ((xbuf (generate-new-buffer "*plain*"))) + (switch-to-buffer xbuf) + (text-scale-increase 1.5) + xbuf)) + + (define-key global-map (kbd "M--") #'bard/new-plain-buffer) + + ;; elisp scratch buffer + + (defun bard/new-elisp-buffer () + (interactive) + (let ((xbuf (generate-new-buffer "*elisp*"))) + (switch-to-buffer xbuf) + (funcall (quote emacs-lisp-mode)) + (text-scale-increase 1.5) + xbuf)) +#+end_src + +*** Terminals +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-essentials.el :mkdirp yes + (defun bard/open-terminal-in-current-directory () + "Open a terminal in the current working directory." + (interactive) + (let ((default-directory default-directory)) + (term "/usr/bin/env bash"))) + + ;; (define-key global-map (kbd "C-t") #'bard/open-terminal-in-current-directory) + (define-key global-map (kbd "C-z t") #'bard/open-terminal-in-current-directory) + + (defun bard/open-terminal-emulator () + "Open a terminal in the current working directory." + (interactive) + (let ((default-directory default-directory)) + (start-process "st terminal" nil "st"))) + + (define-key global-map (kbd "C-z C-t") 'bard/open-terminal-emulator) + + (define-key global-map (kbd "C-z C-s") #'bard/new-elisp-buffer) +#+end_src + +*** Time Management +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-essentials.el :mkdirp yes + ;; Modeline + (setq display-time-format "%Y-%m-%d (%a) %H:%M") + (setq display-time-interval 60) + (setq display-time-default-load-average nil) + (setq display-time-mail-directory nil) + (setq display-time-mail-function nil) + (setq display-time-use-mail-icon nil) + (setq display-time-mail-string nil) + (setq display-time-mail-face nil) + (setq display-time-string-forms + '((propertize + (format-time-string display-time-format now) + 'face 'display-time-date-and-time + 'help-echo (format-time-string "%a %b %e, %Y" now)) + " ")) + (display-time-mode 1) + + ;; world clock + (setq world-clock-list + '(("America/New_York" "New York") + ("Europe/Moscow" "Moscow") + ("Europe/London" "London") + ("Asia/Tokyo" "Tokyo"))) + + (setq world-clock-time-format "%Y-%m-%d %B (%A) %R %Z") + + ;; timer package + (use-package tmr + :ensure t + :config + (setq tmr-notification-urgency 'normal) + (setq tmr-sound-file nil) + (setq tmr-timer-finished-functions '(tmr-notification-notify + tmr-print-message-for-finished-timer + tmr-acknowledge-minibuffer)) + (setq tmr-descriptions-list 'tmr-description-history) + (define-key global-map (kbd "C-c t l") 'tmr-tabulated-view) + (define-key global-map (kbd "C-c t t") #'tmr) + (define-key global-map (kbd "C-c t T") #'tmr-with-description) + (define-key global-map (kbd "C-c t l") #'tmr-tabulated-view) + (define-key global-map (kbd "C-c t c") #'tmr-clone) + (define-key global-map (kbd "C-c t k") #'tmr-cancel) + (define-key global-map (kbd "C-c t s") #'tmr-reschedule) + (define-key global-map (kbd "C-c t e") #'tmr-edit-description) + (define-key global-map (kbd "C-c t r") #'tmr-remove) + (define-key global-map (kbd "C-c t R") #'tmr-remove-finished)) +#+end_src + +*** Running emacs as server +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-essentials.el :mkdirp yes + (require 'server) + (setq server-client-instructions nil) + (unless (server-running-p) + (server-start)) +#+end_src + +*** Provide module +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-essentials.el :mkdirp yes + (provide 'bard-emacs-essentials) +#+end_src + +** bard-emacs-media +*** EMMS music player +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-media.el :mkdirp yes + (use-package emms + :ensure t + ;; :demand t + :bind + (:map emms-playlist-mode-map + ("A" . emms-add-directory) + ("l" . emms-add-playlist) + ("T" . emms-add-directory-tree) + ("F" . emms-add-file) + ("U" . emms-add-url) + ("L" . emms-toggle-repeat-track) + ("" . emms-pause) + ("" . emms-pause) + ("c" . bard/emms-recenter) + ("P" . emms-playlist-mode-shift-track-up) + ("N" . emms-playlist-mode-shift-track-down) + ("Z" . bard/save-emms-watch-later) + ("Y" . bard/emms-download-current-video)) + :bind (("" . emms) + ("M-" . emms-browser)) + :hook + (emms-playlist-mode . hl-line-mode) + :config + (emms-all) + (emms-default-players) + (emms-mpris-enable) + (setq emms-player-list '(emms-player-mpv)) + ;; emms-info-functions '(emms-info-native) + + ;; (setq emms-browser-covers 'emms-browser-cache-thumbnail) + + (setq emms-volume-amixer-card 0) + + ;; center line function + (defun bard/emms-recenter () + (interactive) + (recenter-top-bottom) + (emms-playlist-mode-center-current)) + + ;; modeline + (emms-mode-line-disable) + (emms-playing-time-disable-display) + + ;; playlist saving + (setq bard/emms-playlist-format 'm3u) + (setq bard/watch-later-file "~/Videos/watch-later.m3u") + ) +#+end_src + +*** PDF viewer (~pdf-tools~) +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-media.el :mkdirp yes + (use-package pdf-tools + :ensure t + :config + (pdf-tools-install) + (add-to-list 'pdf-tools-enabled-modes #'pdf-view-themed-minor-mode)) +#+end_src + +*** Custom functions +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-media.el :mkdirp yes + (use-package bard-media + :ensure nil + :config + (require 'bard-media) + :bind + (("C-c o p" . bard/play-youtube-video) + ("C-c o i" . bard/image-browser)) + ) +#+end_src + +*** Provide module +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-media.el :mkdirp yes + (provide 'bard-emacs-media) +#+end_src + +** bard-emacs-modeline +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-modeline.el :mkdirp yes +(require 'bard-modeline) + +;;; Mode line +(setq mode-line-compact nil) ; Emacs 28 +(setq mode-line-right-align-edge 'right-margin) +(setq-default mode-line-format + '("%e" + prot-modeline-kbd-macro + prot-modeline-narrow + bard-modeline-centered-cursor + prot-modeline-input-method + prot-modeline-buffer-status + prot-modeline-window-dedicated-status + bard-evil-state-indicator + " " + prot-modeline-buffer-identification + " " + prot-modeline-major-mode + prot-modeline-process + " " + prot-modeline-vc-branch + " " + prot-modeline-flymake + prot-modeline-eglot + " " + mode-line-format-right-align + prot-modeline-notmuch-indicator + " " + prot-modeline-misc-info + " ")) + +(with-eval-after-load 'spacious-padding + (defun prot/modeline-spacious-indicators () + "Set box attribute to `'prot-modeline-indicator-button' if spacious-padding is enabled." + (if (bound-and-true-p spacious-padding-mode) + (set-face-attribute 'prot-modeline-indicator-button nil :box t) + (set-face-attribute 'prot-modeline-indicator-button nil :box 'unspecified))) + + ;; Run it at startup and then afterwards whenever + ;; `spacious-padding-mode' is toggled on/off. + (prot/modeline-spacious-indicators) + + (add-hook 'spacious-padding-mode-hook #'prot/modeline-spacious-indicators)) + +(setq mode-line-right-align-edge 'window) + +(provide 'bard-emacs-modeline) + +;;; bard-emacs-modeline.el ends here + +#+end_src + +** bard-emacs-org +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-org.el :mkdirp yes +;; |------------------------------------| +;; | Org Config | +;; |------------------------------------| +(require 'org) +(require 'ox) +(require 'org-habit) + +(use-package org + :defer nil + :bind + (:map org-mode-map + ("C-M-a" . backward-paragraph) + ("C-M-e" . forward-paragraph) + ("C-c M-c" . count-words-region) + ("C-c C-M-c" . count-words) + ("C-c l" . org-id-get-create) + ("C-c j" . org-goto) + ) + :bind + (("C-c c" . org-capture)) + :config + (setq org-goto-interface 'outline-path-completion) + (setq safe-local-variable-values '((org-refile-targets (nil :maxlevel . 3))))) + +;; Org Variables +(setq bard/org-anki-file "~/Notes/denote/20240729T171836--anki-flashcards__cards_meta.org") +(setq org-archive-location "~/Notes/denote/20240328T215840--archive__self.org::* Archive") +(setq org-log-done 'time) +(setq org-icalendar-include-todo t + org-icalendar-include-body t + org-icalendar-with-timestamps t + org-icalendar-use-scheduled '(event-if-todo-not-done) + org-icalendar-scheduled-summary-prefix "SCHEDULED: " + org-icalendar-use-deadline '(event-if-todo-not-done) + org-icalendar-deadline-summary-prefix "DEADLINE: ") + +(setq org-habit-show-all-today nil) + +(setq org-structure-template-alist + '(("c" . "center") + ("x" . "example") + ("d" . "definition") + ("t" . "theorem") + ("q" . "quote") + ("v" . "verse") + ("s" . "src") + ("E" . "src emacs-lisp :results value code :lexical t") ; for code examples in notes + ("z" . "src emacs-lisp :tangle FILENAME") ; tangle without making dir, below makes dir + ("Z" . "src emacs-lisp :tangle FILENAME :mkdirp yes"))) +(setq org-ellipsis " ⤶") + +;; mainly for denote, org throws away a link that i might reuse later +(setq org-id-link-to-org-use-id t) +(setq org-link-keep-stored-after-insertion nil) + +;; Making org mode look nice +(setq org-startup-indented t + org-startup-folded 'showeverything + org-hide-emphasis-markers t + org-startup-with-inline-images t + org-image-actual-width '(600) + org-list-allow-alphabetical t + org-insert-heading-respect-content t + org-special-ctrl-a/e t) + +(setq org-format-latex-options (plist-put org-format-latex-options :scale 1.5)) + +(defun bard/org-export-on-save () + "Export current Org buffer to PDF and open it with auto-revert enabled." + (when (derived-mode-p 'org-mode) + (org-latex-export-to-pdf))) + +(define-minor-mode bard/org-auto-export-pdf-mode + "Automatically export Org buffer to PDF on save." + :lighter " AutoPDF" + :group 'org + (if bard/org-auto-export-pdf-mode + (add-hook 'after-save-hook #'bard/org-export-on-save) + (remove-hook 'after-save-hook #'bard/org-export-on-save))) + +(use-package auctex + :ensure t) + +(use-package cdlatex + :ensure t + ) + +;; (use-package org-mode +;; :config + +;; ;; (setq org-latex-to-pdf-process +;; ;; '("xelatex -interaction nonstopmode %f" +;; ;; "xelatex -interaction nonstopmode %f")) +;; ;; (add-to-list 'org-latex-packages-alist +;; ;; '("AUTO" "babel" t ("pdflatex" "xelatex" "lualatex"))) +;; ;; (add-to-list 'org-latex-packages-alist +;; ;; '("AUTO" "polyglossia" t ("xelatex" "lualatex"))) +;; ) + +(with-eval-after-load 'ox-latex + (add-to-list 'org-latex-classes + '("org-plain-latex" + "\\documentclass{article} + [NO-DEFAULT-PACKAGES] + [PACKAGES] + [EXTRA]" + ("\\section{%s}" . "\\section*{%s}") + ("\\subsection{%s}" . "\\subsection*{%s}") + ("\\subsubsection{%s}" . "\\subsubsection*{%s}") + ("\\paragraph{%s}" . "\\paragraph*{%s}") + ("\\subparagraph{%s}" . "\\subparagraph*{%s}")))) + +;; latex editing niceness +(use-package org-fragtog + :ensure t) + +;;; Org capture +(setq org-capture-bookmark nil + org-id-link-to-org-use-id nil) + +(require 'org-protocol) +(setq org-capture-templates + '(("t" "task" entry + (file+olp + "~/Notes/denote/20240328T215727--todo.org" + "Inbox" "General tasks") + "* TODO %?") + ;; ("s" "Basic Statistics" entry + ;; (file+headline + ;; "~/Notes/denote/20240830T215644--statistics-flashcards__anki_stats.org" "Unsorted") + ;; "** %U %^g\n:PROPERTIES:\n:ANKI_NOTE_TYPE: Basic\n:ANKI_DECK: Statistics\n:END:\n*** Front\n %?\n*** Back\n\n") + ;; ("S" "Cloze Statistics" entry + ;; (file+headline + ;; "~/Notes/denote/20240830T215644--statistics-flashcards__anki_stats.org" "Unsorted") + ;; "** %U %^g\n:PROPERTIES:\n:ANKI_NOTE_TYPE: Cloze\n:ANKI_DECK: Statistics\n:END:\n*** Text\n %?\n*** Hooray\n\n") + ("c" "Basic Chemistry" entry + (file+headline + "~/Notes/denote/20251019T175402--chemistry-flashcards__anki_chem.org" "Unsorted") + "** %U %^g\n:PROPERTIES:\n:ANKI_NOTE_TYPE: Basic\n:ANKI_DECK: Chemistry\n:END:\n*** Front\n%?\n*** Back\n\n") + ("n" "common place note" entry + (file "~/Notes/denote/20251023T182240--common-place-notes__topic.org") + "* %^{Source}\n#+BEGIN_QUOTE\n%?\n#+END_QUOTE") + ("z" "Protocol" entry + (file+olp + "~/Notes/denote/20240328T220037--media-tracker__media_topic.org" "Quotes") + "* Source: [[%:link][%:description]]\n#+BEGIN_QUOTE\n%i\n#+END_QUOTE\n%?") + ("Z" "Protocol Link" entry + (file+olp + "~/Notes/denote/20240328T220037--media-tracker__media_topic.org" "Watch/Read List") + "* [[%:link][%:description]] \nCaptured On: %U \n%?") + ("w" "Class outline" entry + (file + "~/Notes/denote/20240328T215727--todo.org") + (file + "~/Notes/denote/templates/class-template.org")) + ("p" "project idea" entry + (file + "~/Notes/denote/20250201T165619--project-ideas__idea_programming.org") + "* %^{Project description}\n%?"))) + +;;; Org Publish +(setq org-html-scripts nil) +(setq org-publish-project-alist + '(("org-blog" + :base-directory "~/Code/org-blog/" + :base-extension "org" + :publishing-directory "~/Code/org-site/" + :recursive t + :publishing-function org-html-publish-to-html + :headline-levels 4 + :html-preamble "

Go back to note index

Page last updated: %d


" + :html-postamble nil))) + +;;; Managing media +;; inspired by https://zzamboni.org/post/how-to-insert-screenshots-in-org-documents-on-macos/ + +(use-package org-download + :after org + :defer nil + :ensure t + :custom + (org-download-method 'directory) + (org-download-image-dir "~/Notes/denote/Images") + (org-download-heading-lvl 0) + (org-download-timestamp "org_%Y%m%d-%H%M%S_") + (org-download-screenshot-method "xclip -selection clipboard -t image/png -o > '%s'") + :bind + ("C-M-y" . org-download-screenshot) + :config + (require 'org-download)) + +(use-package org-noter + :ensure t) + +(use-package org-cliplink + :ensure t + :bind + ("C-c p" . org-cliplink)) + +(use-package org-bullets + :ensure t + :hook (org-mode . org-bullets-mode) + :config + (setq org-bullets-bullet-list '("◉" "○" "●" "🞛" "◇" "◆")) + ;; (set-fontset-font t 'symbol (font-spec :family "Iosevka Comfy") nil 'append) + ;; (set-fontset-font t 'unicode (font-spec :family "Iosevka Comfy") nil 'append) + ) + +(provide 'bard-emacs-org) + +#+end_src + +** bard-emacs-prog +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-prog.el :mkdirp yes +(use-package haskell-mode + :ensure t + :config + (setq haskell-interactive-popup-errors nil)) + +;; CPP Mode +(use-package emacs + :config + (setq-default c-basic-offset 4) + (setq c-default-style '((c-mode . "gnu") + (java-mode . "java") + (awk-mode . "awk")))) + +;; Haskell + +(use-package emacs + :config + (add-to-list 'exec-path "$HOME/.ghcup/bin") + (add-to-list 'exec-path "/home/bard/.cabal/bin") + (add-to-list 'exec-path "/home/bard/.local/bin") + (add-to-list 'exec-path "/home/bard/opt/") + (let ((bard/ghcup-path (expand-file-name "~/.ghcup/bin"))) + (setenv "PATH" (concat bard/ghcup-path ":" (getenv "PATH"))) + (add-to-list 'exec-path bard/ghcup-path))) + +;; Lisp +(use-package clojure-mode + :ensure t) + +(use-package cider + :ensure t) + +(use-package sly + :ensure t + :config + (setq inferior-lisp-program (executable-find "sbcl"))) + +(use-package geiser + :ensure t) + +(use-package geiser-gauche + :ensure t) + +;; parens packages + +(use-package c++-mode + :ensure nil + :bind + (:map c++-mode-map + ("C-c C-c" . project-compile)) + :config + (defun bard/c++-completion-or-indent () + "Complete if point is at a symbol; otherwise, indent." + (interactive) + (if (or (completion-at-point) (looking-at "\\_>")) + (completion-at-point) + (c-indent-line-or-region))) + + (defun bard/setup-c++-mode () + "Configure completion behavior for `c++-mode'." + (local-set-key (kbd "") 'bard/c++-completion-or-indent)) + + (add-hook 'c++-mode-hook #'bard/setup-c++-mode) + (add-hook 'c-mode-hook #'bard/setup-c++-mode) + ) + +(use-package flycheck + :ensure t + :config + (global-flycheck-mode t)) + +(use-package ggtags + :ensure t + :config + (add-hook 'c-mode-common-hook + (lambda () + (when (derived-mode-p 'c-mode 'c++-mode 'java-mode) + (ggtags-mode 1) + (setq-local imenu-create-index-function #'ggtags-build-imenu-index))) + )) + +(use-package compile + :ensure nil + :defer 2 + :config + (require 'bard-compile) + (setq compilation-scroll-output t + compilation-auto-jump-to-first-error nil) + ) + +;; Version control +(use-package magit + :ensure t + :config + (setq magit-repository-directories + '(("~/Code" . 1) + ("~/Repositories" . 1) + ("~/dotfiles-stow" . 0) + ("~/.emacs.d" . 0) + ("~/Pictures/wallpaper" . 0))) + :bind ("C-c g" . magit-status) + ) + +;; (use-package ada-mode +;; :load-path "~/.emacs.d/old-ada" +;; :bind +;; (:map ada-mode-map +;; ("C-j" . dired-jump))) + +(provide 'bard-emacs-prog) + +#+end_src + +** bard-emacs-theme +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-theme.el :mkdirp yes + (require 'bard-theme) + + ;; declare all themes as safe (i trust developers) + (setq custom-safe-themes t) + + (use-package ef-themes + :ensure t + :demand t + :hook + ((ef-themes-post-load . bard/ef-themes-hl-todo-faces) + (ef-themes-post-load . logos-update-fringe-in-buffers)) + :config + (defun bard/ef-themes-hl-todo-faces () + "Configure `hl-todo-keyword-faces' with Ef themes colors. + The exact color values are taken from the active Ef theme." + (ef-themes-with-colors + (setq hl-todo-keyword-faces + `(("WAIT" . ,yellow) + ("TODO" . ,red) + ("NEXT" . ,blue) + ("THEM" . ,magenta) + ("PROG" . ,cyan-warmer) + ("OKAY" . ,green-warmer) + ("DONT" . ,yellow-warmer) + ("FAIL" . ,red-warmer) + ("BUG" . ,red-warmer) + ("DONE" . ,green) + ("NOTE" . ,blue-warmer) + ("KLUDGE" . ,cyan) + ("HACK" . ,cyan) + ("TEMP" . ,red) + ("FIXME" . ,red-warmer) + ("XXX+" . ,red-warmer) + ("KILLED" . ,cyan) + ("REVIEW" . ,red) + ("DEPRECATED" . ,yellow))))) + + (setq org-priority-faces + '((?A . (:inherit (bold next-error))) + (?B . org-priority) + (?C . (:inherit (shadow org-priority))))) + + ;; (setq ef-themes-headings + ;; '((0 variable-pitch bold 1.5) + ;; (1 variable-pitch bold 1.3) + ;; (2 regular 1.2) + ;; (3 1.1) + ;; (agenda-date . (variable-pitch bold 1.3)) + ;; (agenda-structure . (variable-pitch bold 1.5)) + ;; (t . 1.1))) + + (setq ef-themes-headings + '((0 . (variable-pitch light 1.5)) + (1 . (variable-pitch light 1.3)) + (2 . (regular 1.2)) + (t . (variable-pitch 1.1)) + (agenda-date . (variable-pitch bold 1.3)) + (agenda-structure . (variable-pitch bold 1.5)))) + + ;; better faces in overrides + (setq ef-themes-common-palette-overrides + `((fringe unspecified) + (bg-line-number-active bg-hl-line) + (bg-line-number-inactive unspecified) + (fg-line-number-active fg-main) + (bg-tab-bar bg-dim))) + + ;; verbatim need higher contrast for org mode + (setq ef-elea-dark-palette-overrides + '((prose-verbatim yellow-cooler))) + + (setq ef-elea-light-palette-overrides + '((prose-verbatim yellow-cooler))) + + (define-key global-map (kbd "M-") #'ef-themes-select) + (setq ef-themes-variable-pitch-ui t + ef-themes-mixed-fonts t)) + + (use-package doom-themes + :ensure t + :config + (setq doom-gruvbox-dark-variant "hard") + (bard/select-theme 'doom-gruvbox) + ) + + (use-package gruber-darker-theme + :ensure t + :vc (:url "https://github.com/BardofSprites/gruber-darker-theme" + :rev newest)) + + ;;;; Fonts + (use-package fontaine + :ensure nil + :config + ;; save file + (setq fontaine-latest-state-file + (locate-user-emacs-file "fontaine-latest-state.eld")) + + ;; Set last preset or fall back to desired style from `fontaine-presets'. + (fontaine-set-preset (or (fontaine-restore-latest-preset) 'default)) + + ;; The other side of `fontaine-restore-latest-preset'. + (add-hook 'kill-emacs-hook #'fontaine-store-latest-preset) + + ;; preserve fonts when switching themes + (dolist (hook '(modus-themes-after-load-theme-hook ef-themes-post-load-hook)) + (add-hook hook #'fontaine-apply-current-preset)) + + (define-key global-map (kbd "C-c f") #'fontaine-set-preset)) + + (use-package rainbow-mode + :ensure t) + + (global-set-key (kbd "M-") #'bard/select-theme) + + (use-package mixed-pitch + :ensure t + :hook + (org-mode . mixed-pitch-mode) + ) + + (use-package spacious-padding + :ensure t + :config + (setq spacious-padding-widths + '( :internal-border-width 10 + :header-line-width 4 + :mode-line-width 6 + :tab-width 4 + :right-divider-width 1 + :left-fringe-width 0 + :right-fringe-width 0 + :scroll-bar-width 0)) + (spacious-padding-mode t)) + + (provide 'bard-emacs-theme) + + ;;; bard-emacs-theme.el ends here + +#+end_src + +** bard-emacs-ui +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-ui.el :mkdirp yes +;; Generic variables for modes +(use-package whitespace + :ensure nil + :demand t + :config + (setq whitespace-style '(face + tabs + spaces + trailing + space-before-tab + newline indentation + empty space-after-tab + space-mark tab-mark)) + :hook + (prog-mode . whitespace-mode)) + +(use-package display-line-numbers + :ensure nil + :demand t + :bind + (("" . display-line-numbers-mode)) + :hook (prog-mode . display-line-numbers-mode) + :config + (setq display-line-numbers-type 'relative)) + +;; Todo Keywords highlighting, colors defined in bard-emacs-theme.el +(use-package hl-todo + :ensure t + :hook + (prog-mode . hl-todo-mode) + :config + (setq hl-todo-highlight-punctuation ":")) + +(use-package keycast + :ensure t + :commands (keycast-mode-line-mode keycast-header-line-mode keycast-tab-bar-mode keycast-log-mode) + :init + (setq keycast-mode-line-format "%2s%k%c%R") + (setq keycast-mode-line-insert-after 'prot-modeline-vc-branch) + (setq keycast-mode-line-window-predicate 'mode-line-window-selected-p) + (setq keycast-mode-line-remove-tail-elements nil) + :config + (dolist (input '(self-insert-command org-self-insert-command)) + (add-to-list 'keycast-substitute-alist `(,input "." "Typing…"))) + + (dolist (event '("" "" "" "" "" "" "" "" "" "" "" "" handle-select-window mouse-set-point mouse-drag-region)) + (add-to-list 'keycast-substitute-alist `(,event nil nil)))) + +(provide 'bard-emacs-ui) + +;;; bard-emacs-ui.el ends here + +#+end_src + +** bard-emacs-web +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-web.el :mkdirp yes +;;; IRC +(use-package circe + :ensure t + :config + (setq auth-sources '("~/.authinfo.gpg")) + + (defun my-fetch-password (&rest params) + (require 'auth-source) + (let ((match (car (apply 'auth-source-search params)))) + (if match + (let ((secret (plist-get match :secret))) + (if (functionp secret) + (funcall secret) + secret)) + (error "Password not found for %S" params)))) + + (defun my-nickserv-password (server) + (my-fetch-password :user "bardman" :machine "irc.libera.chat")) + + (setq circe-network-options + '(("Libera Chat" + :nick "bardman" + :channels ("#emacs" "##anime" "#gentoo") + :nickserv-password my-nickserv-password)))) + +;;; RSS Feeds +(use-package elfeed + :ensure t + :config + (require 'bard-web) + (global-set-key (kbd "C-c r") 'elfeed) + (setq elfeed-search-filter "+unread") + + :bind + (:map elfeed-search-mode-map + ;; C-p for play now + ("C-c C-p" . bard/play-elfeed-video) + ;; C-e for EMMS + ("C-c C-e" . bard/add-video-emms-queue) + ;; C-w for watch later + ("C-c C-w" . bard/add-video-watch-later) + ;; F is for fetch + ("F" . elfeed-update))) + +(use-package elfeed-org + :ensure t + :init + (elfeed-org) + :config + (setq rmh-elfeed-org-files (list "~/Notes/denote/feeds.org" + "~/Notes/denote/youtube.org"))) + +;; (use-package elfeed-goodies +;; :ensure t +;; :init +;; (elfeed-goodies/setup) +;; :config +;; (setq elfeed-goodies/powerline-default-separator 'box)) + +;;; Web Browsing (EWW and Firefox/Librewolf) + +(use-package eww + :defer t + :config + (setq browse-url-handlers + '(("wikipedia\\.org" . eww-browse-url) + ;; ("github" . browse-url-chromium) + ("github" . browse-url-default-browser) + ("youtube.com" . browse-url-default-browser) + ("reddit.com" . browse-url-default-browser))) + + ;; shr optimizations + (setq shr-use-colors nil) + (setq shr-use-fonts nil) + (setq shr-max-image-proportion 0.6) + (setq shr-image-animate nil) + (setq shr-width fill-column) + (setq shr-max-width fill-column) + (setq shr-discard-aria-hidden t) + (setq shr-cookie-policy nil) + + ;; eww + (setq eww-search-prefix "https://duckduckgo.com/html/?q=") + (setq eww-history-limit 150) + (setq eww-use-external-browser-for-content-type + "\\`\\(video/\\|audio\\)") + :bind + ("C-c w" . eww)) + +;; librewolf open browser + +;; TODO fix this to work with librewolf-bin on Gentoo +(defun browse-url-librewolf (url &optional new-window) + "Ask the Librewolf WWW browser to load URL. +Defaults to the URL around or before point. Passes the strings +in the variable `browse-url-librewolf-arguments' to Librewolf. + +Interactively, if the variable `browse-url-new-window-flag' is non-nil, +loads the document in a new Librewolf window. A non-nil prefix argument +reverses the effect of `browse-url-new-window-flag'. + +If `browse-url-librewolf-new-window-is-tab' is non-nil, then +whenever a document would otherwise be loaded in a new window, it +is loaded in a new tab in an existing window instead. + +Non-interactively, this uses the optional second argument NEW-WINDOW +instead of `browse-url-new-window-flag'." + (interactive (browse-url-interactive-arg "URL: ")) + (setq url (browse-url-encode-url url)) + (let* ((process-environment (browse-url-process-environment))) + (apply #'start-process + (concat "librewolf-bin " url) nil + "librewolf-bin" + (list url)))) + +(provide 'bard-emacs-web) +;;; bard-emacs-web.el ends here + +#+end_src + +** bard-emacs-window +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-window.el :mkdirp yes +(require 'bard-window) + +(use-package emacs + ;; configuration for window splits/window sizes + :config + (setq focus-follows-mouse t) + (setq mouse-autoselect-window t) + (setq window-combination-resize t) + (setq even-window-sizes 'height-only) + (setq window-sides-vertical nil) + (setq switch-to-buffer-in-dedicated-window 'pop) + (setq split-height-threshold 80) + (setq split-width-threshold 125) + (setq window-min-height 3) + (setq window-min-width 30)) + +(use-package windmove + :bind* + (("C-M-" . windmove-up) + ("C-M-" . windmove-right) + ("C-M-" . windmove-down) + ("C-M-" . windmove-left) + ("C-M-S-" . windmove-swap-states-up) + ("C-M-S-" . windmove-swap-states-right) + ("C-M-S-" . windmove-swap-states-down) + ("C-M-S-" . windmove-swap-states-left))) + +(use-package emacs + :config + (setq display-buffer-alist + `(;; no window + ("\\`\\*Async Shell Command\\*\\'" + (display-buffer-no-window)) + ("\\`\\*\\(Warnings\\|Compile-Log\\|Org Links\\)\\*\\'" + (display-buffer-no-window) + (allow-no-window . t)) + ;; bottom side window + ("\\*Org \\(Select\\|Note\\)\\*" ; the `org-capture' key selection and `org-add-log-note' + (display-buffer-in-side-window) + (dedicated . t) + (side . bottom) + (slot . 0) + (window-parameters . ((mode-line-format . none)))) + ;; bottom buffer (NOT side window) + ((or . ((derived-mode . flymake-diagnostics-buffer-mode) + (derived-mode . flymake-project-diagnostics-mode) + (derived-mode . messages-buffer-mode) + (derived-mode . backtrace-mode))) + (display-buffer-reuse-mode-window display-buffer-at-bottom) + (window-height . 0.3) + (dedicated . t) + (preserve-size . (t . t))) + ;; terminal popups + (prot-window-shell-or-term-p + (display-buffer-reuse-mode-window display-buffer-at-bottom) + (mode . (shell-mode eshell-mode comint-mode)) + (body-function . prot-window-select-fit-size)) + ("\\magit: .*" + (display-buffer-same-window) + (inhibit-same-window . nil) + (dedicated . t)) + ("\\*Org Agenda\\*" + (display-buffer-same-window) + (inhibit-same-window . nil) + (dedicated . t)) + ("\\*cfw-calendar\\*" + (display-buffer-same-window) + (inhibit-same-window . nil) + (dedicated . t)) + ("\\*image-dired\\*" + (display-buffer-reuse-mode-window display-buffer-in-side-window) + (side . bottom) + (window-height . 0.5)) + ("\\*image-dired-display-image\\*" + (display-buffer-reuse-mode-window display-buffer-in-side-window) + (side . right) + (window-width . 0.35)) + ;; ("\\*Embark Actions\\*" + ;; (display-buffer-reuse-mode-window display-buffer-below-selected) + ;; (window-height . fit-window-to-buffer) + ;; (window-parameters . ((no-other-window . t) + ;; (mode-line-format . none)))) + ("\\*\\(Output\\|Register Preview\\).*" + (display-buffer-reuse-mode-window display-buffer-at-bottom)) + ;; below current window + ("\\(\\*Capture\\*\\|CAPTURE-.*\\)" + (display-buffer-in-side-window) + (dedicated . t) + (side . bottom) + (slot . 0) + (window-parameters . ((mode-line-format . none)))) + ("\\*\\vc-\\(incoming\\|outgoing\\|git : \\).*" + (display-buffer-reuse-mode-window display-buffer-below-selected) + (window-height . 0.1) + (dedicated . t) + (preserve-size . (t . t))) + ((derived-mode . reb-mode) ; M-x re-builder + (display-buffer-reuse-mode-window display-buffer-below-selected) + (window-height . 4) ; note this is literal lines, not relative + (dedicated . t) + (preserve-size . (t . t))) + ((or . ((derived-mode . occur-mode) + (derived-mode . grep-mode) + (derived-mode . Buffer-menu-mode) + (derived-mode . log-view-mode) + (derived-mode . help-mode) ; See the hooks for `visual-line-mode' + "\\*\\(|Buffer List\\|Occur\\|vc-change-log\\|eldoc.*\\).*" + prot-window-shell-or-term-p + ;; ,world-clock-buffer-name + )) + (prot-window-display-buffer-below-or-pop) + (body-function . prot-window-select-fit-size)) + ("\\*\\(Calendar\\|Bookmark Annotation\\|ert\\).*" + (display-buffer-reuse-mode-window display-buffer-below-selected) + (dedicated . t) + (window-height . fit-window-to-buffer)) + ("\\*ispell-top-choices\\*.*" + (display-buffer-reuse-mode-window display-buffer-below-selected) + (window-height . fit-window-to-buffer)) + )) + ) + +(use-package frame + :ensure nil + :bind (("C-x u" . undelete-frame) + ("C-x f" . other-frame-prefix)) ; I use only C-/ for `undo' + :hook (after-init . undelete-frame-mode)) + +(use-package winner-mode + :init + (winner-mode 1) + :bind + (("C-x " . winner-redo) + ("C-x " . winner-undo) + ("C-x C-n" . next-buffer) + ("C-x C-p" . previous-buffer) + ("C-x " . next-buffer) + ("C-x " . previous-buffer))) + +(use-package emacs + :ensure nil + :bind + ("C-x w t" . tear-off-window) + ("C-x w c" . clone-indirect-buffer-other-window)) + +(use-package ibuffer + :ensure nil + :config + (setq ibuffer-default-sorting-mode 'major-mode) + (ibuffer-auto-mode t)) + +(use-package emacs + :ensure nil + :bind + ("C-x w w" . bard/toggle-window-split)) + +(use-package beframe + :ensure t + :config + (setq beframe-functions-in-frames '(project-prompt-project-dir + notmuch)) + (setq beframe-create-frame-scratch-buffer nil) + (setq beframe-global-buffers '("*scratch*" "*Messages*" "*Backtrace*")) + (beframe-mode 1) + + (define-key global-map (kbd "C-x f") #'other-frame-prefix) + (define-key global-map (kbd "C-c b") beframe-prefix-map) + (define-key global-map (kbd "C-x C-b") #'beframe-buffer-menu) + (define-key global-map (kbd "C-x B") #'select-frame-by-name) + (define-key global-map (kbd "C-c b u") #'beframe-unassume-current-frame-buffers-selectively) + (define-key global-map (kbd "C-c b a") #'beframe-assume-buffers-selectively-all-frames) + + ;; Consult integration + (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 'bard-emacs-window) +;;; bard-emacs-window.el ends here + +#+end_src + +** bard-emacs-writing +#+begin_src emacs-lisp :tangle bard-emacs-modules/bard-emacs-writing.el :mkdirp yes +;;; Input related settings + +(use-package emacs + :ensure nil + :demand t + :bind + (("C-x i" . insert-char) + ("M-z" . zap-to-char) + ("" . toggle-input-method)) + :config + ;; Sentence size + (setq sentence-end-double-space nil) + + ;; Keyboard things + (setq default-input-method "cyrillic-yawerty") + (setq default-transient-input-method "cyrillic-yawerty")) + +(use-package text-mode + :ensure nil + :hook + (text-mode . flyspell-mode)) + +;; Tab settings +(use-package emacs + :config + (setq tab-always-indent 'complete) + (setq tab-first-completion 'word-or-paren-or-punct) + (setq-default tab-width 4 + indent-tabs-mode nil)) + +(use-package electric + :hook + (prog-mode . electric-indent-local-mode) + (prog-mode . electric-pair-local-mode)) + +(use-package paren + :hook (prog-mode . show-paren-local-mode) + :config + (setq show-paren-style 'parenthesis) + (setq show-paren-when-point-in-periphery nil) + (setq show-paren-when-point-inside-paren nil) + (setq show-paren-context-when-offscreen 'overlay)) + +;; snippets +(use-package yasnippet + :ensure t + :config + (setq yas-snippet-dirs '("~/.emacs.d/snippets")) + (yas-global-mode t) + ) + +(use-package yasnippet-capf + :ensure t + :after cape + :config + (add-to-list 'completion-at-point-functions #'yasnippet-capf)) + +;;; Notes +(use-package denote + :ensure t + :config + (require 'bard-writing) + (setq denote-directory "~/Notes/denote/") + (setq denote-buffer-name-prefix "[Note] " + denote-rename-buffer-format "%t %b") + (setq denote-known-keywords + '("emacs" + "linux" + "programming" + "org" + "school" + "language" + "history" + "biology" + )) + + (setq denote-templates + '((default . "Related to — ") + (todo . bard/denote-todo-template))) + + (setq denote-save-buffers t) + (setq denote-prompts '(title keywords)) + (setq denote-sort-dired-extra-prompts nil) + (setq denote-sort-dired-default-sort-component 'identifier) + (setq denote-sort-dired-default-reverse-sort nil) + + ;; backlinks sidebar + (setq denote-backlinks-display-buffer-action + '((display-buffer-in-direction) + (direction . right) + (window-width . 0.33) + (window-height . fit-window-to-buffer) + (dedicated . t))) + + (denote-rename-buffer-mode 1) + + (require 'bard-writing) + + :hook + (dired-mode . denote-dired-mode) + :bind + (("C-c n n" . denote) + ("C-c n d" . denote-sort-dired) + ("C-c n r" . denote-rename-file-using-front-matter) + ("C-c n k" . denote-rename-file-keywords) + ("C-c n I" . denote-add-links) + ("C-c n b" . bard/consult-buffer-notes) ; notes buffer + ("C-c n B" . bard/ibuffer-notes) ; notes buffer but more + ("C-c n f" . bard/find-notes-file) ; notes-find + ("C-c n g" . bard/search-notes-directory))) ; notes-grep + +(use-package denote-org + :ensure t + ) + +(use-package denote-silo + :ensure t + :config + (setq denote-silo-directories '("~/Notes/denote" + "~/Notes/Old Notes/")) + ) + +(use-package denote-sequence + :ensure t + :config + (require 'bard-writing) + :bind + ("C-c n N" . denote-sequence) + ("C-c n D" . denote-sequence-dired) + ("C-c n " . denote-sequence-region)) + +(use-package denote-journal + :ensure t + :bind + ("C-c n j" . denote-journal-new-or-existing-entry) + :config + (setq denote-journal-directory "~/Notes/denote/journal/") + + (setq denote-journal-title-format "Daily Tasks and Notes") + ) + +(use-package denote-roam + :ensure nil + :load-path "~/Code/denote-roam/" + :bind + ("C-c n i" . denote-roam-insert-or-create-node) ; node insert + ("C-c n o" . denote-roam-find-or-create-node) ; node open + :custom + (denote-roam-include-journal nil) + (denote-roam-directory "~/Notes/denote") + :config + (denote-roam-mode t)) + +;;;; Org Roam +(use-package org-roam + :ensure t + :custom + (org-roam-directory (file-truename "~/Notes/denote")) + :bind (("C-c n l" . org-roam-buffer-toggle)) + :config + (setq org-roam-db-node-include-function + (lambda () + (not (member "ATTACH" (org-get-tags))))) + + (org-roam-db-autosync-mode 1)) + +(use-package org-roam-ui + :ensure t + :bind + ("C-c n u" . org-roam-ui-open) + :custom + (org-roam-ui-open-on-start nil)) + +;;; Focus mode for writing + +;; Center line scrolling for focused writing +(use-package emacs + :config + (define-minor-mode bard/scroll-center-cursor-mode + "Toggle centered cursor scrolling behavior." + :init-value nil + :lighter " S=" + :global nil + (if bard/scroll-center-cursor-mode + (setq-local scroll-margin (* (frame-height) 2) + scroll-conservatively 0 + maximum-scroll-margin 0.5) + (dolist (local '(scroll-preserve-screen-position + scroll-conservatively + maximum-scroll-margin + scroll-margin)) + (kill-local-variable `,local)))) + (defun bard/cursor-centered-p () + "Check if `bard/scroll-center-cursor-mode` is currently active." + (bound-and-true-p bard/scroll-center-cursor-mode)) + :bind + (("C-c L" . bard/scroll-center-cursor-mode))) + +(use-package olivetti + :ensure t + :config + (setq olivetti-minimum-body-width 90) + (setq olivetti-recall-visual-line-mode-entry-state t) + :hook + ((olivetti-mode-on . (lambda () (olivetti-set-width 90))) + )) + +;; narrowing and focus mode +(use-package logos + :ensure t + :config + (defun logos-reveal-entry () + "Reveal Org or Outline entry." + (cond + ((and (eq major-mode 'org-mode) + (org-at-heading-p)) + (org-show-subtree)) + ((or (eq major-mode 'outline-mode) + (bound-and-true-p outline-minor-mode)) + (outline-show-subtree)))) + + (setq logos-outlines-are-pages t) + + (setq logos-outline-regexp-alist + `((emacs-lisp-mode . "^;;;+ ") + (org-mode . "^\\* +") + (t . ,(or outline-regexp logos--page-delimiter)))) + + (setq-default logos-hide-cursor nil + logos-hide-mode-line nil + logos-hide-header-line t + logos-hide-buffer-boundaries t + logos-hide-fringe t + logos-variable-pitch t + logos-olivetti t) + + (defun bard/logos--recenter-top () + "Use `recenter' to reposition the view at the top." + (unless (derived-mode-p 'prog-mode) + (recenter 1))) ; Use 0 for the absolute top + :hook + ((logos-page-motion . bard/logos--recenter-top)) + :hook + ((org-mode . logos-focus-mode) + (markdown-mode . logos-focus-mode)) + :bind + (("M-]" . logos-forward-page-dwim) + ("M-[" . logos-backward-page-dwim) + ("" . logos-focus-mode) + ("C-x n n" . logos-narrow-dwim))) + +(use-package citar + :ensure t + :bind + ("C-c n c" . citar-open) + :config + (setq citar-bibliography '("~/Documents/bib/references.bib")) + (setq org-cite-global-bibliography citar-bibliography + org-cite-insert-processor 'citar + org-cite-follow-processor 'citar + org-cite-activate-processor 'citar) + + (setq citar-notes-paths '("~/Notes/denote")) + (setq citar-library-paths '("~/Documents/Research Articles/")) + ;; (setq citar-file-open-functions 'find-file) + :hook + (org-mode . citar-capf-setup) + :bind (("C-c i" . citar-insert-citation)) + ) + +(use-package citar-denote + :ensure t + :config + (citar-denote-mode t) + ) + +(use-package citar-embark + :ensure t + ) + +(provide 'bard-emacs-writing) + +#+end_src + +* Libraries +** bard-calendar +#+begin_src emacs-lisp :tangle bard-elisp/bard-calendar.el :mkdirp yes +(require 'org) + +;; Org Clock + +(defun bard/auto-clock-in () + "Automatically clock in when task marked in progress (INPROG), + and start study session." + (when (equal (org-get-todo-state) "INPROG") + (org-clock-in) + (bard/study-session))) + +(defun bard/study-session () + "Prompt for study parameters, run study session, and clock out when done." + (interactive) + (let* ((study-time (read-string "Study time (minutes): ")) + (break-time (read-string "Break time (minutes): ")) + (sessions (read-string "Number of sessions: ")) + (command (format "study %s %s %s" study-time break-time sessions)) + (process (start-process-shell-command "study-session" "*study*" command))) + (set-process-sentinel + process + (lambda (_proc event) + (when (string= event "finished\n") + (progn (org-clock-out) + (pop-to-buffer-same-window "todo.org"))))))) + +(defun bard/org-clock-report () + (interactive) + (bard/new-org-buffer) + (org-clock-report)) + +(defun bard/org-clock-update-mode-line () + (interactive) + (setq org-mode-line-string nil) + (force-mode-line-update)) + +(defun bard/org-clock-task-string () + "Return a simplified org clock task string." + (if (and (boundp 'org-mode-line-string) + (not (string-equal "" org-mode-line-string)) + org-mode-line-string) + (substring-no-properties org-mode-line-string) + "No task clocked in")) + +(defun bard/open-calendar () + "Opens calendar as only window" + (interactive) + (calendar) + (delete-other-windows)) + +;; Org Agenda + +(defun bard/choose-agenda () + "For viewing my custom agenda" + (interactive) + (let ((agenda-views '("Default" "Monthly" "Yearly"))) + (setq chosen-view (completing-read "Choose an agenda view: " agenda-views)) + (cond + ((string= chosen-view "Yearly") + (org-agenda nil "Y")) + ((string= chosen-view "Monthly") + (org-agenda nil "M")) + ((string= chosen-view "Default") + (org-agenda nil "D"))))) + +(defun bard/default-agenda () + "For viewing my custom agenda" + (interactive) + (org-agenda nil "D")) + +(provide 'bard-calendar) +;;; bard-calendar.el ends here + +#+end_src + +** bard-compile +#+begin_src emacs-lisp :tangle bard-elisp/bard-compile.el :mkdirp yes +;; Stolen from (http://endlessparentheses.com/ansi-colors-in-the-compilation-buffer-output.html) +(require 'ansi-color) +(defun endless/colorize-compilation () + "Colorize from `compilation-filter-start' to `point'." + (let ((inhibit-read-only t)) + (ansi-color-apply-on-region + compilation-filter-start (point)))) + +(add-hook 'compilation-filter-hook + #'endless/colorize-compilation) + +;; Stolen from (https://oleksandrmanzyuk.wordpress.com/2011/11/05/better-emacs-shell-part-i/) +(defun regexp-alternatives (regexps) + "Return the alternation of a list of regexps." + (mapconcat (lambda (regexp) + (concat "\\(?:" regexp "\\)")) + regexps "\\|")) + +(defvar non-sgr-control-sequence-regexp nil + "Regexp that matches non-SGR control sequences.") + +(setq non-sgr-control-sequence-regexp + (regexp-alternatives + '(;; icon name escape sequences + "\033\\][0-2];.*?\007" + ;; non-SGR CSI escape sequences + "\033\\[\\??[0-9;]*[^0-9;m]" + ;; noop + "\012\033\\[2K\033\\[1F" + ))) + +(defun filter-non-sgr-control-sequences-in-region (begin end) + (save-excursion + (goto-char begin) + (while (re-search-forward + non-sgr-control-sequence-regexp end t) + (replace-match "")))) + +(defun filter-non-sgr-control-sequences-in-output (ignored) + (let ((start-marker + (or comint-last-output-start + (point-min-marker))) + (end-marker + (process-mark + (get-buffer-process (current-buffer))))) + (filter-non-sgr-control-sequences-in-region + start-marker + end-marker))) + +(add-hook 'comint-output-filter-functions + 'filter-non-sgr-control-sequences-in-output) + +(provide 'bard-compile) + +#+end_src + +** bard-email +#+begin_src emacs-lisp :tangle bard-elisp/bard-email.el :mkdirp yes +(require 'prot-common) +(eval-when-compile (require 'cl-lib)) + +(defgroup prot-notmuch () + "Extensions for notmuch.el." + :group 'notmuch) + +(defcustom prot-notmuch-delete-tag "del" + "Single tag that applies to mail marked for deletion. +This is used by `prot-notmuch-delete-mail'." + :type 'string + :group 'prot-notmuch) + +(defcustom prot-notmuch-mark-delete-tags + `(,(format "+%s" prot-notmuch-delete-tag) "-inbox" "-unread") + "List of tags to mark for deletion. +To actually delete email, refer to `prot-notmuch-delete-mail'." + :type '(repeat string) + :group 'prot-notmuch) + +(defcustom prot-notmuch-mark-flag-tags '("+flag" "-unread") + "List of tags to mark as important (flagged). +This gets the `notmuch-tag-flagged' face, if that is specified in +`notmuch-tag-formats'." + :type '(repeat string) + :group 'prot-notmuch) + +(defcustom prot-notmuch-mark-spam-tags '("+spam" "-inbox" "-unread") + "List of tags to mark as spam." + :type '(repeat string) + :group 'prot-notmuch) + +(autoload 'notmuch-interactive-region "notmuch") +(autoload 'notmuch-tag-change-list "notmuch") +(autoload 'notmuch-search-next-thread "notmuch") +(autoload 'notmuch-search-tag "notmuch") + +(defmacro prot-notmuch-search-tag-thread (name tags) + "Produce NAME function parsing TAGS." + (declare (indent defun)) + `(defun ,name (&optional untag beg end) + ,(format + "Mark with `%s' the currently selected thread. + +Operate on each message in the currently selected thread. With +optional BEG and END as points delimiting a region that +encompasses multiple threads, operate on all those messages +instead. + +With optional prefix argument (\\[universal-argument]) as UNTAG, +reverse the application of the tags. + +This function advances to the next thread when finished." + tags) + (interactive (cons current-prefix-arg (notmuch-interactive-region))) + (when ,tags + (notmuch-search-tag + (notmuch-tag-change-list ,tags untag) beg end)) + (when (eq beg end) + (notmuch-search-next-thread)))) + +(prot-notmuch-search-tag-thread + prot-notmuch-search-delete-thread + prot-notmuch-mark-delete-tags) + +(prot-notmuch-search-tag-thread + prot-notmuch-search-flag-thread + prot-notmuch-mark-flag-tags) + +(prot-notmuch-search-tag-thread + prot-notmuch-search-spam-thread + prot-notmuch-mark-spam-tags) + +(defmacro prot-notmuch-show-tag-message (name tags) + "Produce NAME function parsing TAGS." + (declare (indent defun)) + `(defun ,name (&optional untag) + ,(format + "Apply `%s' to message. + +With optional prefix argument (\\[universal-argument]) as UNTAG, +reverse the application of the tags." + tags) + (interactive "P") + (when ,tags + (apply 'notmuch-show-tag-message + (notmuch-tag-change-list ,tags untag))))) + +(prot-notmuch-show-tag-message + prot-notmuch-show-delete-message + prot-notmuch-mark-delete-tags) + +(prot-notmuch-show-tag-message + prot-notmuch-show-flag-message + prot-notmuch-mark-flag-tags) + +(prot-notmuch-show-tag-message + prot-notmuch-show-spam-message + prot-notmuch-mark-spam-tags) + +(defun prot-notmuch-delete-mail () + "Permanently delete mail marked as `prot-notmuch-delete-mail'. +Prompt for confirmation before carrying out the operation. + +Do not attempt to refresh the index. This will be done upon the +next invocation of 'notmuch new'." + (interactive) + (let* ((del-tag prot-notmuch-delete-tag) + (count + (string-to-number + (with-temp-buffer + (shell-command + (format "notmuch count tag:%s" prot-notmuch-delete-tag) t) + (buffer-substring-no-properties (point-min) (1- (point-max)))))) + (mail (if (> count 1) "mails" "mail"))) + (unless (> count 0) + (user-error "No mail marked as `%s'" del-tag)) + (when (yes-or-no-p + (format "Delete %d %s marked as `%s'?" count mail del-tag)) + (shell-command + (format "notmuch search --output=files --format=text0 tag:%s | xargs -r0 rm" del-tag) + t)))) +(provide 'bard-email) + +#+end_src + +** bard-embark +#+begin_src emacs-lisp :tangle bard-elisp/bard-embark.el :mkdirp yes +(require 'embark) + +(defvar-keymap bard-embark-general-map + :parent embark-general-map + "i" #'embark-insert + "w" #'embark-copy-as-kill + "E" #'embark-export + "S" #'embark-collect + "A" #'embark-act-all + "DEL" #'delete-region) + +(defvar-keymap bard-embark-url-map + :parent embark-general-map + "b" #'browse-url + "d" #'embark-download-url + "e" #'eww) + +(defvar-keymap bard-embark-buffer-map + :parent embark-general-map + "k" #'bard-simple-kill-buffer + "o" #'switch-to-buffer-other-window + "e" #'ediff-buffers) + +(add-to-list 'embark-post-action-hooks (list 'bard-simple-kill-buffer 'embark--restart)) + +(defvar-keymap bard-embark-file-map + :parent embark-general-map + "f" #'find-file + "j" #'embark-dired-jump + "c" #'copy-file + "e" #'ediff-files) + +(defvar-keymap bard-embark-identifier-map + :parent embark-general-map + "h" #'display-local-help + "." #'xref-find-definitions + "o" #'occur) + +(defvar-keymap bard-embark-command-map + :parent embark-general-map + "h" #'describe-command + "." #'embark-find-definition) + +(defvar-keymap bard-embark-expression-map + :parent embark-general-map + "e" #'pp-eval-expression + "m" #'pp-macroexpand-expression) + +(defvar-keymap bard-embark-function-map + :parent embark-general-map + "h" #'describe-function + "." #'embark-find-definition) + +(defvar-keymap bard-embark-package-map + :parent embark-general-map + "h" #'describe-package + "i" #'package-install + "d" #'package-delete + "r" #'package-reinstall + "b" #'embark-browse-package-url + "w" #'embark-save-package-url) + +(defvar-keymap bard-embark-symbol-map + :parent embark-general-map + "h" #'describe-symbol + "." #'embark-find-definition) + +(defvar-keymap bard-embark-variable-map + :parent embark-general-map + "h" #'describe-variable + "." #'embark-find-definition) + +(defvar-keymap bard-embark-region-map + :parent embark-general-map + "a" #'align-regexp + "D" #'delete-duplicate-lines + "f" #'flush-lines + "i" #'epa-import-keys-region + "d" #'epa-decrypt-armor-in-region + "r" #'repunctuate-sentences + "s" #'sort-lines + "u" #'untabify) + +;; The minimal indicator shows cycling options, but I have no use +;; for those. I want it to be silent. +(defun bard-embark-no-minimal-indicator ()) +(advice-add #'embark-minimal-indicator :override #'bard-embark-no-minimal-indicator) + +(provide 'bard-embark) + +#+end_src + +** bard-eshell +#+begin_src emacs-lisp :tangle bard-elisp/bard-eshell.el :mkdirp yes +(require 'cl-lib) +(require 'eshell) + +;; aliases +(setq bard/eshell-aliases + '((g . magit) + (gl . magit-log) + (d . dired) + (o . find-file) + (oo . find-file-other-window) + (l . (lambda () (eshell/ls '-la))) + (eshell/clear . eshell/clear-scrollback))) + +(mapc (lambda (alias) + (defalias (car alias) (cdr alias))) + bard/eshell-aliases) + +(defun prot-eshell--cd (dir) + "Routine to cd into DIR." + (delete-region eshell-last-output-end (point-max)) + (when (> eshell-last-output-end (point)) + (goto-char eshell-last-output-end)) + (insert-and-inherit "cd " (eshell-quote-argument dir)) + (eshell-send-input)) + +(defun prot-eshell-complete-recent-dir (dir &optional arg) + "Switch to a recent Eshell directory. +When called interactively, DIR is selected with completion from +the elements of `eshell-last-dir-ring'. +With optional ARG prefix argument (\\[universal-argument]) also +open the directory in a `dired' buffer." + (interactive + (list + (if-let ((dirs (ring-elements eshell-last-dir-ring))) + (completing-read "Switch to recent dir: " dirs nil t) + (user-error "There is no Eshell history for recent directories")) + current-prefix-arg)) + (prot-eshell--cd dir) + ;; UPDATE 2022-01-04 10:48 +0200: The idea for `dired-other-window' + ;; was taken from Sean Whitton's `spw/eshell-cd-recent-dir'. Check + ;; Sean's dotfiles: . + (when arg + (dired-other-window dir))) + +(defun bard/eshell-find-file-at-point () + "Run `find-file` to find file" + (interactive) + (let ((file (ffap-file-at-point))) + (if file + (find-file file) + (user-error "No file at point")))) + +(defcustom prot-eshell-output-buffer "*Exported Eshell output*" + "Name of buffer with the last output of Eshell command. +Used by `prot-eshell-export'." + :type 'string + :group 'prot-eshell) + +(defcustom prot-eshell-output-delimiter "* * *" + "Delimiter for successive `prot-eshell-export' outputs. +This is formatted internally to have newline characters before +and after it." + :type 'string + :group 'prot-eshell) + +(defun prot-eshell--command-prompt-output () + "Capture last command prompt and its output." + (let ((beg (save-excursion + (goto-char (eshell-beginning-of-input)) + (goto-char (point-at-bol))))) + (when (derived-mode-p 'eshell-mode) + (buffer-substring-no-properties beg (eshell-end-of-output))))) + +;;;###autoload +(defun prot-eshell-export () + "Produce a buffer with output of the last Eshell command. +If `prot-eshell-output-buffer' does not exist, create it. Else +append to it, while separating multiple outputs with +`prot-eshell-output-delimiter'." + (interactive) + (let ((eshell-output (prot-eshell--command-prompt-output))) + (with-current-buffer (get-buffer-create prot-eshell-output-buffer) + (let ((inhibit-read-only t)) + (goto-char (point-max)) + (unless (eq (point-min) (point-max)) + (insert (format "\n%s\n\n" prot-eshell-output-delimiter))) + (goto-char (point-at-bol)) + (insert eshell-output) + (switch-to-buffer-other-window (current-buffer)))))) + +(defgroup bard-eshell-faces nil + "Faces for my custom modeline." + :group 'prot-eshell-faces) + +(defface bard-eshell-highlight-yellow-bg + '((default :inherit (bold prot-modeline-indicator-button)) + (((class color) (min-colors 88) (background light)) + :background "#805000" :foreground "white") + (((class color) (min-colors 88) (background dark)) + :background "#ffc800" :foreground "black") + (t :background "yellow" :foreground "black")) + "Face for modeline indicators with a background." + :group 'bard-eshell-faces) + +(defun prot-eshell-narrow-output-highlight-regexp (regexp) + "Narrow to last command output and highlight REGEXP." + (interactive + (list (read-regexp "Regexp to highlight" nil 'prot-eshell--output-highlight-history))) + (narrow-to-region (eshell-beginning-of-output) + (eshell-end-of-output)) + (goto-char (point-min)) + (highlight-regexp regexp 'prot-eshell-highlight-yellow-bg) + (message "%s to last output and highlighted '%s'" + (propertize "Narrowed" 'face 'bold) + (propertize regexp 'face 'italic))) + +(defun select-or-create (arg) + "Commentary ARG." + (if (string= arg "New eshell") + (eshell t) + (switch-to-buffer arg))) + (defun eshell-switcher (&optional arg) + "Commentary ARG." + (interactive) + (let* ( + (buffers (cl-remove-if-not (lambda (n) (eq (buffer-local-value 'major-mode n) 'eshell-mode)) (buffer-list)) ) + (names (mapcar (lambda (n) (buffer-name n)) buffers)) + (num-buffers (length buffers) ) + (in-eshellp (eq major-mode 'eshell-mode))) + (cond ((eq num-buffers 0) (eshell (or arg t))) + ((not in-eshellp) (switch-to-buffer (car buffers))) + (t (select-or-create (completing-read "Select Shell:" (cons "New eshell" names))))))) + +;; taken from https://github.com/karthink/.emacs.d/blob/master/lisp/setup-shells.el +(use-package eshell + :defer + :config + (setq eshell-prompt-regexp "^.* λ " + eshell-prompt-function #'bard/eshell-default-prompt-fn) + + (defun bard/eshell-default-prompt-fn () + "Generate the prompt string for eshell. Use for `eshell-prompt-function'." + (concat (if (bobp) "" "\n") + (let ((pwd (eshell/pwd))) + (propertize (if (equal pwd "~") + pwd + (abbreviate-file-name pwd)) + 'face 'bard/eshell-prompt-pwd)) + (propertize (bard/eshell--current-git-branch) + 'face 'bard/eshell-prompt-git-branch) + (propertize " λ" 'face (if (zerop eshell-last-command-status) 'success 'error)) + " ")) + + (defsubst bard/eshell--current-git-branch () + ;; TODO Refactor me + (cl-destructuring-bind (status . output) + (with-temp-buffer (cons + (or (call-process "git" nil t nil "symbolic-ref" "-q" "--short" "HEAD") + (call-process "git" nil t nil "describe" "--all" "--always" "HEAD") + -1) + (string-trim (buffer-string)))) + (if (equal status 0) + (format " [%s]" output) + ""))) + + (defface bard/eshell-prompt-pwd '((t (:inherit font-lock-keyword-face))) + "TODO" + :group 'eshell) + + (defface bard/eshell-prompt-git-branch '((t (:inherit font-lock-builtin-face))) + "TODO" + :group 'eshell)) + +(provide 'bard-eshell) + +#+end_src + +** bard-media +#+begin_src emacs-lisp :tangle bard-elisp/bard-media.el :mkdirp yes +(require 'cl-lib) +(require 'seq) +(require 'emms) +(require 'image-dired) +(require 'dired-x) + +(defun bard/play-youtube-video () + "Play the YouTube URL at point or prompt for one if none is found." + (interactive) + (let* ((url-at-point (thing-at-point 'url t)) + (url (if (and url-at-point + (string-match-p "https?://\\(www\\.\\)?\\(youtube\\.com\\|youtu\\.be\\)" url-at-point)) + url-at-point + (read-string "Enter YouTube URL: ")))) + (if (and url (string-match-p "https?://\\(www\\.\\)?\\(youtube\\.com\\|youtu\\.be\\)" url)) + (async-shell-command (format "mpv '%s'" url)) + (message "The URL is not a valid YouTube link: %s" url)))) + +(defun bard/save-emms-watch-later () + "Save the current EMMS playlist to `bard/watch-later-file` using `bard/emms-playlist-format`." + (interactive) + (when (and bard/watch-later-file bard/emms-playlist-format) + (emms-playlist-save bard/emms-playlist-format bard/watch-later-file) + (message "Playlist saved to %s" bard/watch-later-file))) + +(defun bard/image-browser-choose (directory) + "Open nsxiv in thumbnail mode on DIRECTORY. +Asks the user whether to enable recursive mode and whether to output marked files to a buffer." + (interactive "DSelect directory: ") + (let* ((recursive (if (y-or-n-p "Recursive searching? ") "-r" "")) + (stdout (if (y-or-n-p "Output marked files to buffer? ") "-o" "")) + (full-dir (expand-file-name directory)) + (args (remove "" (list "nsxiv" "-t" stdout recursive full-dir)))) + + ;; Pre-clear the output buffer if needed + (when (string= stdout "-o") + (with-current-buffer (get-buffer-create "*nsxiv*") + (read-only-mode 0) + (erase-buffer))) + + (message "Running: %s" (string-join args " ")) + + (let ((process (apply #'start-process "nsxiv" "*nsxiv*" args))) + (when (string= stdout "-o") + (set-process-sentinel + process + (lambda (proc event) + (when (string= event "finished\n") + (with-current-buffer "*nsxiv*" + (read-only-mode nil) + (goto-char (point-min))) + ;; Read marked files + (let ((files (with-current-buffer "*nsxiv*" + (split-string (buffer-string) "\n" t)))) + (bard/open-marked-in-dired files))))) + (pop-to-buffer "*nsxiv*"))))) + +(defun bard/open-marked-in-dired (files) + "Open a list of FILES in an interactive Dired buffer." + (if (and files (listp files)) + (dired (cons "*nsxiv-marked*" files)) + (message "No valid files to show in Dired."))) + +(defun bard/image-browser-marked () + "Open nsxiv on the marked files in Dired. +Assumes that files have already been validated." + (let ((files (dired-get-marked-files))) + (message "Opening marked files: %s" (string-join files ", ")) + (apply #'start-process "nsxiv" "*nsxiv*" "nsxiv" "-t" files))) + +(defun bard/image-browser () + "Open nsxiv in a context-sensitive way: +- If in Dired with marked files, open those with nsxiv. +- If in Dired with no marked files, prompt for a directory. +- If not in Dired, prompt for a directory." + (interactive) + (cond + ;; In Dired and files are marked + ((and (derived-mode-p 'dired-mode) + (< 1 (length (dired-get-marked-files)))) + (message "Opening marked files from Dired...") + (bard/image-browser-marked)) + + ;; In Dired but no marked files + ((derived-mode-p 'dired-mode) + (message "No files marked in Dired. Prompting for directory...") + (call-interactively #'bard/image-browser-choose)) + + ;; Not in Dired + (t + (message "Not in Dired. Prompting for directory...") + (call-interactively #'bard/image-browser-choose)))) + +(defun bard/emms-download-current-video (destination) + "Download the currently playing EMMS video and move it to DESTINATION." + (interactive "DSelect destination directory: ") + (require 'emms) + (let* ((track (emms-playlist-current-selected-track)) + (url (emms-track-get track 'name)) + (default-directory (file-name-as-directory temporary-file-directory)) + (downloader (executable-find "yt-dlp")) + (output-template "%(title)s.%(ext)s")) + (unless downloader + (error "yt-dlp or youtube-dl is not installed or not in PATH")) + (unless (string-match-p "^https?://" url) + (error "Current track is not a valid video URL")) + + (let ((cmd (format "%s -o \"%s\" \"%s\"" + downloader output-template url))) + (message "Downloading video from: %s" url) + (let ((exit-code (shell-command cmd))) + (if (not (eq exit-code 0)) + (error "Download failed, see *Messages* for details") + ;; Move the downloaded file + (let* ((downloaded-file (car (directory-files default-directory t ".*\\(mp4\\|mkv\\|webm\\)$" 'time))) + (target-path (expand-file-name (file-name-nondirectory downloaded-file) destination))) + (rename-file downloaded-file target-path t) + (message "Video saved to: %s" target-path))))))) + +(provide 'bard-media.el) + +#+end_src + +** bard-modeline +#+begin_src emacs-lisp :tangle bard-elisp/bard-modeline.el :mkdirp yes +(require 'prot-common) + +(defgroup prot-modeline nil + "Custom modeline that is stylistically close to the default." + :group 'mode-line) + +(defgroup prot-modeline-faces nil + "Faces for my custom modeline." + :group 'prot-modeline) + +(defcustom prot-modeline-string-truncate-length 9 + "String length after which truncation should be done in small windows." + :type 'natnum) + +(defun mode-line-window-selected-p () + "Return non-nil if we're updating the mode line for the selected window. +This function is meant to be called in `:eval' mode line +constructs to allow altering the look of the mode line depending +on whether the mode line belongs to the currently selected window +or not." + (let ((window (selected-window))) + (or (eq window (old-selected-window)) + (and (minibuffer-window-active-p (minibuffer-window)) + (with-selected-window (minibuffer-window) + (eq window (minibuffer-selected-window))))))) + +;;;; Faces + +(defface prot-modeline-indicator-button nil + "Generic face used for indicators that have a background. +Modify this face to, for example, add a :box attribute to all +relevant indicators (combines nicely with my `spacious-padding' +package).") + +(defface prot-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 (e.g. see my `notmuch-indicator')." + :group 'prot-modeline-faces) + +(defface prot-modeline-indicator-red-bg + '((default :inherit (bold prot-modeline-indicator-button)) + (((class color) (min-colors 88) (background light)) + :background "#aa1111" :foreground "white") + (((class color) (min-colors 88) (background dark)) + :background "#ff9090" :foreground "black") + (t :background "red" :foreground "black")) + "Face for modeline indicators with a background." + :group 'prot-modeline-faces) + +(defface prot-modeline-indicator-green + '((default :inherit bold) + (((class color) (min-colors 88) (background light)) + :foreground "#005f00") + (((class color) (min-colors 88) (background dark)) + :foreground "#73fa7f") + (t :foreground "green")) + "Face for modeline indicators (e.g. see my `notmuch-indicator')." + :group 'prot-modeline-faces) + +(defface prot-modeline-indicator-green-bg + '((default :inherit (bold prot-modeline-indicator-button)) + (((class color) (min-colors 88) (background light)) + :background "#207b20" :foreground "white") + (((class color) (min-colors 88) (background dark)) + :background "#77d077" :foreground "black") + (t :background "green" :foreground "black")) + "Face for modeline indicators with a background." + :group 'prot-modeline-faces) + +(defface prot-modeline-indicator-yellow + '((default :inherit bold) + (((class color) (min-colors 88) (background light)) + :foreground "#6f4000") + (((class color) (min-colors 88) (background dark)) + :foreground "#f0c526") + (t :foreground "yellow")) + "Face for modeline indicators (e.g. see my `notmuch-indicator')." + :group 'prot-modeline-faces) + +(defface prot-modeline-indicator-yellow-bg + '((default :inherit (bold prot-modeline-indicator-button)) + (((class color) (min-colors 88) (background light)) + :background "#805000" :foreground "white") + (((class color) (min-colors 88) (background dark)) + :background "#ffc800" :foreground "black") + (t :background "yellow" :foreground "black")) + "Face for modeline indicators with a background." + :group 'prot-modeline-faces) + +(defface prot-modeline-indicator-blue + '((default :inherit bold) + (((class color) (min-colors 88) (background light)) + :foreground "#00228a") + (((class color) (min-colors 88) (background dark)) + :foreground "#88bfff") + (t :foreground "blue")) + "Face for modeline indicators (e.g. see my `notmuch-indicator')." + :group 'prot-modeline-faces) + +(defface prot-modeline-indicator-blue-bg + '((default :inherit (bold prot-modeline-indicator-button)) + (((class color) (min-colors 88) (background light)) + :background "#0000aa" :foreground "white") + (((class color) (min-colors 88) (background dark)) + :background "#77aaff" :foreground "black") + (t :background "blue" :foreground "black")) + "Face for modeline indicators with a background." + :group 'prot-modeline-faces) + +(defface prot-modeline-indicator-magenta + '((default :inherit bold) + (((class color) (min-colors 88) (background light)) + :foreground "#6a1aaf") + (((class color) (min-colors 88) (background dark)) + :foreground "#e0a0ff") + (t :foreground "magenta")) + "Face for modeline indicators (e.g. see my `notmuch-indicator')." + :group 'prot-modeline-faces) + +(defface prot-modeline-indicator-magenta-bg + '((default :inherit (bold prot-modeline-indicator-button)) + (((class color) (min-colors 88) (background light)) + :background "#6f0f9f" :foreground "white") + (((class color) (min-colors 88) (background dark)) + :background "#e3a2ff" :foreground "black") + (t :background "magenta" :foreground "black")) + "Face for modeline indicators with a background." + :group 'prot-modeline-faces) + +(defface prot-modeline-indicator-cyan + '((default :inherit bold) + (((class color) (min-colors 88) (background light)) + :foreground "#004060") + (((class color) (min-colors 88) (background dark)) + :foreground "#30b7cc") + (t :foreground "cyan")) + "Face for modeline indicators (e.g. see my `notmuch-indicator')." + :group 'prot-modeline-faces) + +(defface prot-modeline-indicator-cyan-bg + '((default :inherit (bold prot-modeline-indicator-button)) + (((class color) (min-colors 88) (background light)) + :background "#006080" :foreground "white") + (((class color) (min-colors 88) (background dark)) + :background "#40c0e0" :foreground "black") + (t :background "cyan" :foreground "black")) + "Face for modeline indicators with a background." + :group 'prot-modeline-faces) + +;;;; Common helper functions + +(defun prot-modeline--string-truncate-p (str) + "Return non-nil if STR should be truncated." + (and (prot-common-window-small-p) + (> (length str) prot-modeline-string-truncate-length) + (not (one-window-p :no-minibuffer)))) + +(defun prot-modeline--truncate-p () + "Return non-nil if truncation should happen. +This is a more general and less stringent variant of +`prot-modeline--string-truncate-p'." + (and (prot-common-window-small-p) + (not (one-window-p :no-minibuffer)))) + +(defun prot-modeline-string-truncate (str) + "Return truncated STR, if appropriate, else return STR. +Truncation is done up to `prot-modeline-string-truncate-length'." + (if (prot-modeline--string-truncate-p str) + (concat (substring str 0 prot-modeline-string-truncate-length) "...") + str)) + +(defun prot-modeline-string-truncate-end (str) + "Like `prot-modeline-string-truncate' but truncate from STR beginning." + (if (prot-modeline--string-truncate-p str) + (concat "..." (substring str (- prot-modeline-string-truncate-length))) + str)) + +(defun prot-modeline--first-char (str) + "Return first character from STR." + (substring str 0 1)) + +(defun prot-modeline-string-abbreviate (str) + "Abbreviate STR individual hyphen or underscore separated words. +Also see `prot-modeline-string-abbreviate-but-last'." + (if (prot-modeline--string-truncate-p str) + (mapconcat #'prot-modeline--first-char (split-string str "[_-]") "-") + str)) + +(defun prot-modeline-string-abbreviate-but-last (str nthlast) + "Abbreviate STR, keeping NTHLAST words intact. +Also see `prot-modeline-string-abbreviate'." + (if (prot-modeline--string-truncate-p str) + (let* ((all-strings (split-string str "[_-]")) + (nbutlast-strings (nbutlast (copy-sequence all-strings) nthlast)) + (last-strings (nreverse (ntake nthlast (nreverse (copy-sequence all-strings))))) + (first-component (mapconcat #'prot-modeline--first-char nbutlast-strings "-")) + (last-component (mapconcat #'identity last-strings "-"))) + (if (string-empty-p first-component) + last-component + (concat first-component "-" last-component))) + str)) + +;;;; Keyboard macro indicator + +(defvar-local prot-modeline-kbd-macro + '(:eval + (when (and (mode-line-window-selected-p) defining-kbd-macro) + (propertize " KMacro " 'face 'prot-modeline-indicator-blue-bg))) + "Mode line construct displaying `mode-line-defining-kbd-macro'. +Specific to the current window's mode line.") + +;;;; Narrow indicator + +(defvar-local prot-modeline-narrow + '(:eval + (when (and (mode-line-window-selected-p) + (buffer-narrowed-p) + (not (derived-mode-p 'Info-mode 'help-mode 'special-mode 'message-mode))) + (propertize " Narrow " 'face 'prot-modeline-indicator-cyan-bg))) + "Mode line construct to report the multilingual environment.") + +;;;; Centered cursor indicator +(defvar-local bard-modeline-centered-cursor + '(:eval + (when (and (mode-line-window-selected-p) + (bard/cursor-centered-p) + (not (derived-mode-p 'Info-mode 'help-mode 'special-mode 'message-mode))) + (propertize " Center " 'face 'prot-modeline-indicator-yellow-bg))) + "Mode line construct to report the multilingual environment.") + +;; FIXME: Combine these two functions one day... +(defvar-local bard-modeline-ryo-modal-normal + '(:eval + (when (and (mode-line-window-selected-p) + (not (bard/ryo-insert-p)) + (not (derived-mode-p 'Info-mode 'help-mode 'special-mode 'message-mode))) + (propertize "" 'face 'prot-modeline-indicator-magenta-bg)) + ) + "Mode line construct to show normal mode for ryo-modal.") + +(defvar-local bard-modeline-ryo-modal-insert + '(:eval + (when (and (mode-line-window-selected-p) + (bard/ryo-insert-p) + (not (derived-mode-p 'Info-mode 'help-mode 'special-mode 'message-mode))) + (propertize "" 'face 'prot-modeline-indicator-blue-bg)) + ) + "Mode line construct to show insert mode for ryo-modal.") + +;;;; Input method + +(defvar-local prot-modeline-input-method + '(:eval + (when current-input-method-title + (propertize (format " %s " current-input-method-title) + 'face 'prot-modeline-indicator-green-bg + 'mouse-face 'mode-line-highlight))) + "Mode line construct to report the multilingual environment.") + +;;;; Buffer status +(defvar-local prot-modeline-buffer-status + '(:eval + (when (file-remote-p default-directory) + (propertize " @ " + 'face 'prot-modeline-indicator-red-bg + 'mouse-face 'mode-line-highlight))) + "Mode line construct for showing remote file name.") + +;;;; Dedicated window + +(defvar-local prot-modeline-window-dedicated-status + '(:eval + (when (window-dedicated-p) + (propertize " = " + 'face 'prot-modeline-indicator-magenta-bg + 'mouse-face 'mode-line-highlight))) + "Mode line construct for dedicated window indicator.") + +;;;; Buffer name and modified status + +(defun prot-modeline-buffer-identification-face () + "Return appropriate face or face list for `prot-modeline-buffer-identification'." + (let ((file (buffer-file-name))) + (cond + ((and (mode-line-window-selected-p) + file + (buffer-modified-p)) + '(error italic mode-line-buffer-id)) + ((and file (buffer-modified-p)) + 'italic) + ((mode-line-window-selected-p) + 'mode-line-buffer-id)))) + +(defun prot-modeline--buffer-name () + "Return `buffer-name', truncating it if necessary. +See `prot-modeline-string-truncate'." + (when-let ((name (buffer-name))) + (prot-modeline-string-truncate name))) + +(defun prot-modeline-buffer-name () + "Return buffer name, with read-only indicator if relevant." + (let ((name (prot-modeline--buffer-name))) + (if buffer-read-only + (format "%s %s" (char-to-string #xE0A2) name) + name))) + +(defun prot-modeline-buffer-name-help-echo () + "Return `help-echo' value for `prot-modeline-buffer-identification'." + (concat + (propertize (buffer-name) 'face 'mode-line-buffer-id) + "\n" + (propertize + (or (buffer-file-name) + (format "No underlying file.\nDirectory is: %s" default-directory)) + 'face 'font-lock-doc-face))) + +(defvar-local prot-modeline-buffer-identification + '(:eval + (propertize (prot-modeline-buffer-name) + 'face (prot-modeline-buffer-identification-face) + 'mouse-face 'mode-line-highlight + 'help-echo (prot-modeline-buffer-name-help-echo))) + "Mode line construct for identifying the buffer being displayed. +Propertize the current buffer with the `mode-line-buffer-id' +face. Let other buffers have no face.") + +;;;; Major mode + +(defun prot-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 'term-mode) ">_") + ((derived-mode-p 'emms-playlist-mode) "♪") + (t "◦")))) + (propertize indicator 'face 'shadow))) + +(defun prot-modeline-major-mode-name () + "Return capitalized `major-mode' without the -mode suffix." + (capitalize (string-replace "-mode" "" (symbol-name major-mode)))) + +(defun prot-modeline-major-mode-help-echo () + "Return `help-echo' value for `prot-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 prot-modeline-major-mode + (list + (propertize "%[" 'face 'prot-modeline-indicator-red) + '(:eval + (concat + (prot-modeline-major-mode-indicator) + " " + (propertize + (prot-modeline-string-abbreviate-but-last + (prot-modeline-major-mode-name) + 2) + 'mouse-face 'mode-line-highlight + 'help-echo (prot-modeline-major-mode-help-echo)))) + (propertize "%]" 'face 'prot-modeline-indicator-red)) + "Mode line construct for displaying major modes.") + +(defvar-local prot-modeline-process + (list '("" mode-line-process)) + "Mode line construct for the running process indicator.") + +;;;; Git branch and diffstat + +(declare-function vc-git--symbolic-ref "vc-git" (file)) + +(defun prot-modeline--vc-branch-name (file backend) + "Return capitalized VC branch name for FILE with BACKEND." + (when-let ((rev (vc-working-revision file backend)) + (branch (or (vc-git--symbolic-ref file) + (substring rev 0 7)))) + (capitalize branch))) + +;; NOTE 2023-07-27: This is a good idea, but it hardcodes Git, whereas +;; I want a generic VC method. Granted, I only use Git but I still +;; want it to work as a VC extension. + +;; (defun prot-modeline-diffstat (file) +;; "Return shortened Git diff numstat for FILE." +;; (when-let* ((output (shell-command-to-string (format "git diff --numstat %s" file))) +;; (stats (split-string output "[\s\t]" :omit-nulls "[\s\f\t\n\r\v]+")) +;; (added (nth 0 stats)) +;; (deleted (nth 1 stats))) +;; (cond +;; ((and (equal added "0") (equal deleted "0")) +;; "") +;; ((and (not (equal added "0")) (equal deleted "0")) +;; (propertize (format "+%s" added) 'face 'shadow)) +;; ((and (equal added "0") (not (equal deleted "0"))) +;; (propertize (format "-%s" deleted) 'face 'shadow)) +;; (t +;; (propertize (format "+%s -%s" added deleted) 'face 'shadow))))) + +(declare-function vc-git-working-revision "vc-git" (file)) + +(defvar prot-modeline-vc-map + (let ((map (make-sparse-keymap))) + (define-key map [mode-line down-mouse-1] 'vc-diff) + (define-key map [mode-line down-mouse-3] 'vc-root-diff) + map) + "Keymap to display on VC indicator.") + +(defun prot-modeline--vc-help-echo (file) + "Return `help-echo' message for FILE tracked by VC." + (format "Revision: %s\nmouse-1: `vc-diff'\nmouse-3: `vc-root-diff'" + (vc-working-revision file))) + +(defun prot-modeline--vc-text (file branch &optional face) + "Prepare text for Git controlled FILE, given BRANCH. +With optional FACE, use it to propertize the BRANCH." + (concat + (propertize (char-to-string #xE0A0) 'face 'shadow) + " " + (propertize branch + 'face face + 'mouse-face 'mode-line-highlight + 'help-echo (prot-modeline--vc-help-echo file) + 'local-map prot-modeline-vc-map) + ;; " " + ;; (prot-modeline-diffstat file) + )) + +(defun prot-modeline--vc-details (file branch &optional face) + "Return Git BRANCH details for FILE, truncating it if necessary. +The string is truncated if the width of the window is smaller +than `split-width-threshold'." + (prot-modeline-string-truncate + (prot-modeline--vc-text file branch face))) + +(defvar prot-modeline--vc-faces + '((added . vc-locally-added-state) + (edited . vc-edited-state) + (removed . vc-removed-state) + (missing . vc-missing-state) + (conflict . vc-conflict-state) + (locked . vc-locked-state) + (up-to-date . vc-up-to-date-state)) + "VC state faces.") + +(defun prot-modeline--vc-get-face (key) + "Get face from KEY in `prot-modeline--vc-faces'." + (alist-get key prot-modeline--vc-faces 'up-to-date)) + +(defun prot-modeline--vc-face (file backend) + "Return VC state face for FILE with BACKEND." + (prot-modeline--vc-get-face (vc-state file backend))) + +(defvar-local prot-modeline-vc-branch + '(:eval + (when-let* (((mode-line-window-selected-p)) + (file (buffer-file-name)) + (backend (vc-backend file)) + ;; ((vc-git-registered file)) + (branch (prot-modeline--vc-branch-name file backend)) + (face (prot-modeline--vc-face file backend))) + (prot-modeline--vc-details file branch face))) + "Mode line construct to return propertized VC branch.") + +;;;; Flymake errors, warnings, notes + +(declare-function flymake--severity "flymake" (type)) +(declare-function flymake-diagnostic-type "flymake" (diag)) + +;; Based on `flymake--mode-line-counter'. +(defun prot-modeline-flymake-counter (type) + "Compute number of diagnostics in buffer with TYPE's severity. +TYPE is usually keyword `:error', `:warning' or `:note'." + (let ((count 0)) + (dolist (d (flymake-diagnostics)) + (when (= (flymake--severity type) + (flymake--severity (flymake-diagnostic-type d))) + (cl-incf count))) + (when (cl-plusp count) + (number-to-string count)))) + +(defvar prot-modeline-flymake-map + (let ((map (make-sparse-keymap))) + (define-key map [mode-line down-mouse-1] 'flymake-show-buffer-diagnostics) + (define-key map [mode-line down-mouse-3] 'flymake-show-project-diagnostics) + map) + "Keymap to display on Flymake indicator.") + +(defmacro prot-modeline-flymake-type (type indicator &optional face) + "Return function that handles Flymake TYPE with stylistic INDICATOR and FACE." + `(defun ,(intern (format "prot-modeline-flymake-%s" type)) () + (when-let ((count (prot-modeline-flymake-counter + ,(intern (format ":%s" type))))) + (concat + (propertize ,indicator 'face 'shadow) + (propertize count + 'face ',(or face type) + 'mouse-face 'mode-line-highlight + ;; FIXME 2023-07-03: Clicking on the text with + ;; this buffer and a single warning present, the + ;; diagnostics take up the entire frame. Why? + 'local-map prot-modeline-flymake-map + 'help-echo "mouse-1: buffer diagnostics\nmouse-3: project diagnostics"))))) + +(prot-modeline-flymake-type error "☣") +(prot-modeline-flymake-type warning "!") +(prot-modeline-flymake-type note "·" success) + +(defvar-local prot-modeline-flymake + `(:eval + (when (and (bound-and-true-p flymake-mode) + (mode-line-window-selected-p)) + (list + ;; See the calls to the macro `prot-modeline-flymake-type' + '(:eval (prot-modeline-flymake-error)) + '(:eval (prot-modeline-flymake-warning)) + '(:eval (prot-modeline-flymake-note))))) + "Mode line construct displaying `flymake-mode-line-format'. +Specific to the current window's mode line.") + +(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.") + +(defvar-local prot-modeline-notmuch-indicator + '(notmuch-indicator-mode + (" " + (:eval (when (mode-line-window-selected-p) + notmuch-indicator--counters)))) + "The equivalent of `notmuch-indicator-mode-line-construct'. +Display the indicator only on the focused window's mode line.") + +(defvar-local bard-evil-state-indicator + '(:eval + (when (and (bound-and-true-p evil-local-mode) + (mode-line-window-selected-p)) + (let ((state-label + (pcase evil-state + ('normal (propertize " " 'face 'prot-modeline-indicator-green)) + ('insert (propertize " " 'face 'prot-modeline-indicator-blue)) + ('visual (propertize " " 'face 'prot-modeline-indicator-yellow)) + ('replace (propertize " " 'face 'prot-modeline-indicator-red)) + ('emacs (propertize " " 'face 'prot-modeline-indicator-magenta)) + ('motion (propertize " " 'face 'prot-modeline-indicator-cyan)) + (_ (propertize " <> " 'face 'shadow))))) + state-label))) + "Modeline indicator for current Evil state.") + +;;;; Miscellaneous + +(defvar-local prot-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.") + +;;;; Risky local variables + +;; NOTE 2023-04-28: The `risky-local-variable' is critical, as those +;; variables will not work without it. +(dolist (construct '(prot-modeline-kbd-macro + prot-modeline-narrow + bard-modeline-centered-cursor + bard-evil-state-indicator + prot-modeline-input-method + prot-modeline-buffer-status + prot-modeline-window-dedicated-status + prot-modeline-evil + prot-modeline-buffer-identification + prot-modeline-major-mode + prot-modeline-process + prot-modeline-vc-branch + prot-modeline-flymake + prot-modeline-eglot + prot-modeline-misc-info + prot-modeline-notmuch-indicator)) + (put construct 'risky-local-variable t)) + +(provide 'bard-modeline) + +#+end_src + +** bard-package +#+begin_src emacs-lisp :tangle bard-elisp/bard-package.el :mkdirp yes +;; taken and renamed functions from https://manueluberti.eu/posts/2021-09-01-package-report/ + +(defun bard/package-report () + "Report total package counts grouped by archive." + (interactive) + (package-refresh-contents) + (bard/display-package-report + (let* ((arch-pkgs (bard/archive-packages)) + (counts (seq-sort-by #'cdr #'> (bard/archive-counts arch-pkgs))) + (by-arch (seq-group-by #'car arch-pkgs))) + (concat + (format "Total packages: %s\n\n" (apply #'+ (mapcar #'cdr counts))) + (mapconcat + (lambda (archive) + (concat "• " + (format "%s (%s)" (car archive) (cdr archive)) + ": " + (mapconcat (lambda (ap-pair) (cdr ap-pair)) + (alist-get (car archive) by-arch) + ", "))) + counts + "\n\n"))))) + +(defun bard/display-package-report (output) + "Display OUTPUT in a popup buffer." + (let ((buffer-name "*package-report*")) + (with-help-window buffer-name + (with-current-buffer buffer-name + (visual-line-mode 1) + (erase-buffer) + (insert output) + (goto-char (point-min)))))) + +(defun bard/archive-packages () + "Return a list of (archive . package) cons cells." + (seq-reduce + (lambda (res package) + (let ((archive (package-desc-archive + (cadr (assq package package-archive-contents)))) + (pkg (symbol-name package))) + (push (cons archive pkg) res))) + (mapcar #'car package-alist) + nil)) + +(defun bard/archive-counts (arch-pkgs) + "Return a list of cons cells from alist ARCH-PKGS. +The cars are package archives, the cdrs are the number of +packages installed from each archive." + (seq-reduce + (lambda (counts key) + (cons (cons key (+ 1 (or (cdr (assoc key counts)) 0))) + (assoc-delete-all key counts))) + (mapcar #'car arch-pkgs) + nil)) + +(provide 'bard-package) + +#+end_src + +** bard-theme +#+begin_src emacs-lisp :tangle bard-elisp/bard-theme.el :mkdirp yes + (setq fontaine-presets + '((default + :default-height 140 + :default-family "Iosevka Comfy" + :variable-pitch-family "Iosevka Comfy" + :variable-pitch-height 1.0 + :fixed-pitch-family "Iosevka Comfy" + :fixed-pitch-height 1.0 + :bold-weight bold + ) + (tiny + :inherit default + :default-height 135) + (wide + :default-height 135 + :default-family "Iosevka Comfy Wide" + :fixed-pitch-family "Iosevka Comfy Wide" + :variable-pitch-family "Iosevka Comfy Wide Motion Duo") + (prot + :default-family "Iosevka Comfy Wide Motion" + :default-height 130 + :default-weight medium + :fixed-pitch-family "Iosevka Comfy Wide Motion" + :variable-pitch-family "Iosevka Comfy Wide Duo" + :bold-weight extrabold) + (mono + :default-height 130 + :default-family "monospace" + :fixed-pitch-family "monospace" + :variable-pitch-family "Baskerville" + :variable-pitch-height 140) + (mono-large + :inherit mono + :default-height 150 + :variable-pitch-height 160) + (mac + :default-height 130 + :default-family "Monaco" + :variable-pitch-family "Monaco" + :fixed-pitch-family "Monaco") + (large + :inherit default + :default-height 160 + ) + (huge + :inherit default + :default-height 180 + ) + (t + :default-family "Monospace" + ))) + + (set-fontset-font + t 'han + (font-spec :family "Noto Serif CJK JP") nil 'prepend) + + ;;; Switching themes + (defun bard/disable-all-themes () + "disable all active themes." + (interactive) + (dolist (i custom-enabled-themes) + (disable-theme i))) + + (defvar bard/after-theme-load-hook nil + "Hook that runs after a new theme is loaded using `bard/select-theme`.") + + (dolist (hook '(bard/after-theme-load-hook)) + (add-hook hook #'fontaine-apply-current-preset) + (add-hook hook #'logos-update-fringe-in-buffers)) + + (defun bard/select-theme (&optional theme) + "Enable the specified THEME, or prompt the user to select one if THEME is nil." + (interactive + (list + (completing-read "Select theme: " + (mapcar 'symbol-name (custom-available-themes))))) + (let* ((theme-symbol (if (symbolp theme) theme (intern theme))) + (theme-name (symbol-name theme-symbol)) + (display-theme-name (if (string-suffix-p "-theme" theme-name) + (substring theme-name 0 -6) + theme-name)) + (colored-theme-name (propertize display-theme-name 'face '(:weight bold)))) + (bard/disable-all-themes) + (load-theme theme-symbol t) + (message "Loaded the %s theme" colored-theme-name) + (run-hooks 'bard/after-theme-load-hook))) + + (defun bard/update-ryo-cursor-color () + "Update the color variable of `ryo-modal-mode' cursor to match the ef/modus theme." + (let ((active-theme (car custom-enabled-themes)) + (cursor-color nil)) + (cond + ((and (fboundp 'ef-themes-with-colors) + (string-prefix-p "ef-" (symbol-name active-theme))) + (ef-themes-with-colors + (setq ryo-modal-cursor-color cursor + ryo-modal-default-cursor-color cursor))) + ((and (fboundp 'modus-themes-with-colors) + (string-prefix-p "modus-" (symbol-name active-theme))) + (modus-themes-with-colors + (setq ryo-modal-cursor-color cursor + ryo-modal-default-cursor-color cursor))) + (t (setq cursor-color "red")) + (setq ryo-modal-cursor-color cursor-color + ryo-modal-default-cursor-color cursor-color)))) + + (defvar my-last-cursor-type nil) + + (defun bard/update-cursor-type () + "Set cursor type to 'bar in text modes, 'box otherwise. + leave it alone in pdf-view-mode." + (unless (derived-mode-p 'pdf-view-mode) + (let ((new-cursor (if (derived-mode-p 'text-mode) 'bar 'box))) + (unless (eq my-last-cursor-type new-cursor) + (setq cursor-type new-cursor) + (setq my-last-cursor-type new-cursor))))) + + (add-hook 'post-command-hook #'bard/update-cursor-type) + + (defun bard/outline-heading-faces () + (set-face-attribute 'org-level-1 nil + :inherit '(outline-1 variable-pitch) + :weight 'light + :height 1.5) + + (set-face-attribute 'org-level-2 nil + :inherit '(outline-2 variable-pitch) + :weight 'light + :height 1.3) + + (set-face-attribute 'org-level-3 nil + :inherit 'outline-3 + :height 1.2) + + (set-face-attribute 'org-level-4 nil + :inherit '(outline-4 variable-pitch) + :height 1.1) + + (set-face-attribute 'org-level-5 nil + :inherit '(outline-5 variable-pitch) + :height 1.1) + + (set-face-attribute 'org-level-6 nil + :inherit '(outline-6 variable-pitch) + :height 1.1) + + (set-face-attribute 'org-agenda-date nil + :inherit 'variable-pitch + :weight 'bold + :height 1.3) + + (set-face-attribute 'org-agenda-structure nil + :inherit 'variable-pitch + :weight 'bold + :height 1.5)) + + (add-hook 'bard/after-theme-load-hook #'bard/outline-heading-faces) + + (provide 'bard-theme) + +#+end_src + +** bard-web +#+begin_src emacs-lisp :tangle bard-elisp/bard-web.el :mkdirp yes +(require 'emms) +(require 'elfeed-search) + +(defun bard/play-elfeed-video () + "Play the URL of the entry at point in mpv if it's a YouTube video." + (interactive) + (let ((entry (elfeed-search-selected :single))) + (if entry + (let ((url (elfeed-entry-link entry))) + (if (and url (string-match-p "https?://\\(www\\.\\)?youtube\\.com\\|youtu\\.be" url)) + (progn + (async-shell-command (format "mpv '%s'" url)) + (elfeed-search-untag-all-unread)) + (message "The URL is not a YouTube link: %s" url))) + (message "No entry selected in Elfeed.")))) + +(defun bard/add-video-emms-queue () + "Play the URL of the entry at point in mpv if it's a YouTube video. Add it to EMMS queue." + (interactive) + (let ((entry (elfeed-search-selected :single))) + (if entry + (let ((url (elfeed-entry-link entry))) + (if (and url (string-match-p "https?://\\(www\\.\\)?youtube\\.com\\|youtu\\.be" url)) + (let* ((playlist-name "Watch Later") + (playlist-buffer (get-buffer (format " *%s*" playlist-name)))) + (unless playlist-buffer + (setq playlist-buffer (emms-playlist-new (format " *%s*" playlist-name)))) + (emms-playlist-set-playlist-buffer playlist-buffer) + (emms-add-url url) + (elfeed-search-untag-all-unread) + (message "Added YouTube video to EMMS playlist: %s" url)) + (message "The URL is not a YouTube link: %s" url))) + (message "No entry selected in Elfeed.")))) + +(defun bard/add-video-watch-later () + "Add the current Elfeed YouTube entry URL to '~/Videos/watch-later.m3u' and mark it as read." + (interactive) + (let ((entry (elfeed-search-selected :single))) + (if entry + (let* ((url (elfeed-entry-link entry)) + (watch-later-file (expand-file-name "~/Videos/watch-later.m3u"))) + (if (and url (string-match-p "https?://\\(www\\.\\)?youtube\\.com\\|youtu\\.be" url)) + (progn + (with-temp-buffer + (insert (concat url "\n")) + (append-to-file (point-min) (point-max) watch-later-file)) + ;; Remove the 'unread tag from the entry directly + (setf (elfeed-entry-tags entry) + (remove 'unread (elfeed-entry-tags entry))) + ;; Force UI update + (when (derived-mode-p 'elfeed-search-mode) + (elfeed-search-update-entry entry)) + (message "Added video to watch later: %s" url)) + (message "The URL is not a YouTube link: %s" url))) + (message "No entry selected in Elfeed.")))) + +(provide 'bard-web) + +#+end_src + +** bard-window +#+begin_src emacs-lisp :tangle bard-elisp/bard-window.el :mkdirp yes +(require 'prot-common) + +(defvar prot-window-window-sizes + '( :max-height (lambda () (floor (frame-height) 3)) + :min-height 10 + :max-width (lambda () (floor (frame-width) 4)) + :min-width 20) + "Property list of maximum and minimum window sizes. +The property keys are `:max-height', `:min-height', `:max-width', +and `:min-width'. They all accept a value of either a +number (integer or floating point) or a function.") + +(defun prot-window--get-window-size (key) + "Extract the value of KEY from `prot-window-window-sizes'." + (when-let ((value (plist-get prot-window-window-sizes key))) + (cond + ((functionp value) + (funcall value)) + ((numberp value) + value) + (t + (error "The value of `%s' is neither a number nor a function" key))))) + +(defun prot-window-select-fit-size (window) + "Select WINDOW and resize it. +The resize pertains to the maximum and minimum values for height +and width, per `prot-window-window-sizes'. + +Use this as the `body-function' in a `display-buffer-alist' entry." + (select-window window) + (fit-window-to-buffer + window + (prot-window--get-window-size :max-height) + (prot-window--get-window-size :min-height) + (prot-window--get-window-size :max-width) + (prot-window--get-window-size :min-width)) + ;; If we did not use `display-buffer-below-selected', then we must + ;; be in a lateral window, which has more space. Then we do not + ;; want to dedicate the window to this buffer, because we will be + ;; running out of space. + (when (or (window-in-direction 'above) (window-in-direction 'below)) + (set-window-dedicated-p window t))) + +(defun prot-window--get-display-buffer-below-or-pop () + "Return list of functions for `prot-window-display-buffer-below-or-pop'." + (list + #'display-buffer-reuse-mode-window + (if (or (prot-common-window-small-p) + (prot-common-three-or-more-windows-p)) + #'display-buffer-below-selected + #'display-buffer-pop-up-window))) + +(defun prot-window-display-buffer-below-or-pop (&rest args) + "Display buffer below current window or pop a new window. +The criterion for choosing to display the buffer below the +current one is a non-nil return value for +`prot-common-window-small-p'. + +Apply ARGS expected by the underlying `display-buffer' functions. + +This as the action function in a `display-buffer-alist' entry." + (let ((functions (prot-window--get-display-buffer-below-or-pop))) + (catch 'success + (dolist (fn functions) + (when (apply fn args) + (throw 'success fn)))))) + +;; from protesilaos prot-shell library +(defun prot-window-shell-or-term-p (buffer &rest _) + "Check if BUFFER is a shell or terminal. +This is a predicate function for `buffer-match-p', intended for +use in `display-buffer-alist'." + (when (string-match-p "\\*.*\\(e?shell\\|v?term\\|terminal\\).*" (buffer-name (get-buffer buffer))) + (with-current-buffer buffer + ;; REVIEW 2022-07-14: Is this robust? + (and (not (derived-mode-p 'message-mode 'text-mode)) + (derived-mode-p 'eshell-mode 'shell-mode 'term-mode 'comint-mode 'fundamental-mode))))) + +;; taken from https://github.com/hylophile/.files/blob/1f3f01e4e25b00f7b61eca286fcf4f865885090c/.config/doom/config.org#fancy-tab-bar + +(defun hy/tab-bar-format-align-center () + "Align the rest of tab bar items centered." + (let* ((rest (cdr (memq 'hy/tab-bar-format-align-center tab-bar-format))) + (rest (tab-bar-format-list rest)) + (rest (mapconcat (lambda (item) (nth 2 item)) rest "")) + (hpos (progn + (add-face-text-property 0 (length rest) 'tab-bar t rest) + (string-pixel-width rest))) + (hpos (+ hpos (/ (- (frame-inner-width) hpos) 2))) + (str (propertize "​" 'display + ;; The `right' spec doesn't work on TTY frames + ;; when windows are split horizontally (bug#59620) + (if (window-system) + `(space :align-to (- right (,hpos))) + `(space :align-to (,(- (frame-inner-width) hpos))))))) + `((align-center menu-item ,str ignore)))) + +(setq tab-bar-tab-name-format-function #'hy/tab-bar-tab-name-format-default) +(defun hy/tab-bar-tab-name-format-default (tab i) + (let* ((hint (format "%d" i)) + (name (alist-get 'name tab)) + (dir (concat "(" (alist-get 'dir tab "") ")")) + (name-format (concat + " " + (propertize hint 'face 'tab-bar-hint) + " " + name + " "))) + (add-face-text-property + 0 (length name-format) + (funcall tab-bar-tab-face-function tab) + 'append name-format) + name-format)) + + +(setq tab-bar-tab-name-function #'hy/tab-bar-tab-name-current) +(defun hy/tab-bar-tab-name-current () + (hy/shorten-string + (hy/abbreviate-tab-name + (buffer-name (window-buffer (or (minibuffer-selected-window) + (and (window-minibuffer-p) + (get-mru-window)))))) + 25)) + +(defun hy/set-tab-dir () + (setf (alist-get 'dir (cdr (tab-bar--current-tab-find))) + (hy/tab-bar-dir))) + +(defun hy/abbreviate-directory-path (path) + "Turns `~/code/test/t` into `~/c/t/project`." + (let* ((directories (seq-filter (lambda (s) (not (string= s ""))) (split-string path "/"))) + (last-dir (car (last directories))) + (abbreviated-dirs (mapcar (lambda (dir) + (if (string= dir last-dir) + dir + (substring dir 0 (if (string-prefix-p "." dir) 2 1)))) + directories))) + (mapconcat 'identity abbreviated-dirs "/"))) + +(defun hy/tab-bar-dir () + (hy/shorten-string (hy/abbreviate-directory-path + (abbreviate-file-name + (or (projectile-project-root) default-directory))) + 10 + t)) + +(defun hy/shorten-string (string max-length &optional at-start) + (let ((len (length string))) + (if (> len max-length) + (if at-start + (concat "…" (substring string (- len max-length) len)) + (concat (substring string 0 max-length) "…")) + string))) + +(defun hy/abbreviate-tab-name (name) + (string-trim (replace-regexp-in-string + (rx (or "*" "helpful" "Org Src")) + "" name))) + +(defun bard/toggle-window-split () + "Toggle between horizontal and vertical window splits, preserving buffer layout." + (interactive) + (let ((current-buffers (mapcar #'window-buffer (window-list))) ; List of buffers in current windows + (split-direction (if (= (window-width) (frame-width)) + 'vertical + 'horizontal))) + (delete-other-windows) + ;; Toggle the split direction + (if (eq split-direction 'horizontal) + (split-window-vertically) + (split-window-horizontally)) + ;; Restore buffers to the new windows + (let ((windows (window-list))) + (cl-loop for buffer in current-buffers + for window in windows + do (set-window-buffer window buffer))))) + +(provide 'bard-window) + +#+end_src + +** bard-writing +#+begin_src emacs-lisp :tangle bard-elisp/bard-writing.el :mkdirp yes +(require 'consult) +(require 'beframe) +(require 'calendar) +(require 'org-roam-node) +(require 'denote) + +(defvar bard/consult--source-notes + `(:name "Note Buffers" + :narrow ?n + :category buffer + :face consult-buffer + :history buffer-name-history + :items ,(lambda () + (mapcar #'buffer-name + (seq-filter + (lambda (buf) + (string-prefix-p "[Note]" (buffer-name buf))) + (beframe-buffer-list)))) + :action ,#'switch-to-buffer + :state ,#'consult--buffer-state) + "Consult source for note buffers (limited to beframe buffers).") + + +(defun bard/find-notes-file () + (interactive) + (consult-find "~/Notes/denote")) + +(defun bard/search-notes-directory () + (interactive) + (consult-grep "~/Notes/denote")) + +(defun bard/consult-buffer-notes () + "Show `consult-buffer` limited to buffers starting with [Note]." + (interactive) + (consult-buffer '(bard/consult--source-notes))) + +(defun bard/ibuffer-notes () + "Open `ibuffer` limited to buffers starting with [Note]." + (interactive) + (ibuffer nil "*Ibuffer-Notes*" + '((name . "^\\[Note\\]")))) + +(defun denote-sequence-region () + "Call `denote-sequence' and insert therein the text of the active region. + +Note that, currently, `denote-save-buffers' and +`denote-kill-buffers' are NOT respected. The buffer is not +saved or killed at the end of `denote-sequence-region'." + (declare (interactive-only t)) + (interactive) + (if-let* (((region-active-p)) + ;; We capture the text early, otherwise it will be empty + ;; the moment `insert' is called. + (text (buffer-substring-no-properties (region-beginning) (region-end)))) + (progn + (let ((denote-ignore-region-in-denote-command t) + ;; FIXME: Find a way to insert the region before the buffer is + ;; saved/killed by the creation command. + (denote-save-buffers nil) + (denote-kill-buffers nil)) + (call-interactively 'denote-sequence)) + (push-mark (point)) + (insert text) + (run-hook-with-args 'denote-region-after-new-note-functions (mark) (point))) + (call-interactively 'denote-sequence))) + +(defvar bard/class-dirs + '(("ANTH 204" . "~/Documents/dox/Uni/FALL2025-ANTH 204/") + ("CHEM 201" . "~/Documents/dox/Uni/FALL2025-CHEM 201/") + ("CHEM 207" . "~/Documents/dox/Uni/FALL2025-CHEM 207/") + ("ENGL 105" . "~/Documents/dox/Uni/FALL2025-ENGL 105/") + ("ENGR 101" . "~/Documents/dox/Uni/FALL2025-ENGR 101/") + ("ENGR 110" . "~/Documents/dox/Uni/FALL2025-ENGR 110/")) + "Mapping of class names to their document directories.") + +(defvar bard/uni-notes-file "~/Notes/denote/uni.org" + "Path to the main university org file.") + +(defun bard/jump-to-class (class) + "Jump to CLASS heading in `bard/uni-notes-file` and open its dir in dired." + (interactive + (list (completing-read "Class: " (mapcar #'car bard/class-dirs)))) + (let* ((dir (cdr (assoc class bard/class-dirs)))) + ;; split windows + (delete-other-windows) + (let ((notes-window (selected-window)) + (dired-window (split-window-right))) + ;; open notes file and jump to heading + (with-selected-window notes-window + (find-file bard/uni-notes-file) + (widen) + (goto-char (point-min)) + (message class) + (search-forward class nil nil)) + ;; open dired in right window + (with-selected-window dired-window + (dired dir))))) + +;; (defun bard/jump-to-class-new-frame (class) +;; "Open CLASS notes and dir in a new frame titled after CLASS." +;; (interactive +;; (list (completing-read "Class: " (mapcar #'car bard/class-dirs)))) +;; (let* ((dir (cdr (assoc class bard/class-dirs))) +;; ;; make a new frame with title +;; (frame (make-frame `((name . ,class))))) +;; (select-frame-set-input-focus frame) +;; (delete-other-windows) +;; (let ((notes-window (selected-window)) +;; (dired-window (split-window-right))) +;; ;; open notes file and jump to heading +;; (with-selected-window notes-window +;; (find-file bard/uni-notes-file) +;; (widen) +;; (goto-char (point-min)) +;; (search-forward class nil nil)) +;; ;; open dired in right window +;; (with-selected-window dired-window +;; (dired dir))))) + +(defun bard/jump-to-class-new-frame (class) + "Open CLASS notes and dir in a new frame titled after CLASS, even with beframe." + (interactive + (list (completing-read "Class: " (mapcar #'car bard/class-dirs)))) + (let* ((dir (cdr (assoc class bard/class-dirs))) + (frame (make-frame `((frame-title-format . ,class))))) + (select-frame-set-input-focus frame) + (delete-other-windows) + (let ((notes-window (selected-window)) + (dired-window (split-window-right))) + (with-selected-window notes-window + (find-file bard/uni-notes-file) + (widen) + (goto-char (point-min)) + (search-forward class nil nil)) + (with-selected-window dired-window + (dired dir) + (beframe-rename-current-frame))))) + +;; Optional: bind to a key +(global-set-key (kbd "C-c u") #'bard/jump-to-class) +(global-set-key (kbd "C-c U") #'bard/jump-to-class-new-frame) + +(defun bard/denote-todo-template () + "Return string for daily tasks heading in `denote-journal' entries" + (format "* Tasks for %s\n\n* Notes for today" + (format-time-string "%Y-%m-%d (%a)"))) + +;; Taken from: https://stackoverflow.com/a/75314192 +(defun add-multiple-into-list (lst items) + "Add each item from ITEMS into LST." + (dolist (item items) + (add-to-list lst item))) + +(defun bard/cdlatex-add-math-symbols () + "Add functions into list." + (add-multiple-into-list + 'cdlatex-math-symbol-alist-comb + '((?V "\\vec")))) + +(define-minor-mode bard/org-math-mode + "Enable features to write math in `org-mode'." + :init-value nil + :lighter " S=" + :global nil + (org-fragtog-mode t) + (org-cdlatex-mode t) + (electric-pair-local-mode t) + (bard/cdlatex-add-math-symbols)) + + +(provide 'bard-writing) + +#+end_src + +** prot-common +#+begin_src emacs-lisp :tangle bard-elisp/prot-common.el :mkdirp yes +;;; prot-common.el --- Common functions for my dotemacs -*- lexical-binding: t -*- + +;; Copyright (C) 2020-2023 Protesilaos Stavrou + +;; Author: Protesilaos Stavrou +;; URL: https://protesilaos.com/emacs/dotemacs +;; Version: 0.1.0 +;; Package-Requires: ((emacs "30.1")) + +;; This file is NOT part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or (at +;; your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: +;; +;; Common functions for my Emacs: . +;; +;; Remember that every piece of Elisp that I write is for my own +;; educational and recreational purposes. I am not a programmer and I +;; do not recommend that you copy any of this if you are not certain of +;; what it does. + +;;; Code: + +(eval-when-compile + (require 'subr-x) + (require 'cl-lib)) + +(defgroup prot-common () + "Auxiliary functions for my dotemacs." + :group 'editing) + +;;;###autoload +(defun prot-common-number-even-p (n) + "Test if N is an even number." + (if (numberp n) + (= (% n 2) 0) + (error "%s is not a number" n))) + +;;;###autoload +(defun prot-common-number-integer-p (n) + "Test if N is an integer." + (if (integerp n) + n + (error "%s is not an integer" n))) + +;;;###autoload +(defun prot-common-number-integer-positive-p (n) + "Test if N is a positive integer." + (if (prot-common-number-integer-p n) + (> n 0) + (error "%s is not a positive integer" n))) + +;; Thanks to Gabriel for providing a cleaner version of +;; `prot-common-number-negative': . +;;;###autoload +(defun prot-common-number-negative (n) + "Make N negative." + (if (and (numberp n) (> n 0)) + (* -1 n) + (error "%s is not a valid positive number" n))) + +;;;###autoload +(defun prot-common-reverse-percentage (number percent change-p) + "Determine the original value of NUMBER given PERCENT. + +CHANGE-P should specify the increase or decrease. For simplicity, +nil means decrease while non-nil stands for an increase. + +NUMBER must satisfy `numberp', while PERCENT must be `natnump'." + (unless (numberp number) + (user-error "NUMBER must satisfy numberp")) + (unless (natnump percent) + (user-error "PERCENT must satisfy natnump")) + (let* ((pc (/ (float percent) 100)) + (pc-change (if change-p (+ 1 pc) pc)) + (n (if change-p pc-change (float (- 1 pc-change))))) + ;; FIXME 2021-12-21: If float, round to 4 decimal points. + (/ number n))) + +;;;###autoload +(defun prot-common-percentage-change (n-original n-final) + "Find percentage change between N-ORIGINAL and N-FINAL numbers. + +When the percentage is not an integer, it is rounded to 4 +floating points: 16.666666666666664 => 16.667." + (unless (numberp n-original) + (user-error "N-ORIGINAL must satisfy numberp")) + (unless (numberp n-final) + (user-error "N-FINAL must satisfy numberp")) + (let* ((difference (float (abs (- n-original n-final)))) + (n (* (/ difference n-original) 100)) + (round (floor n))) + ;; FIXME 2021-12-21: Any way to avoid the `string-to-number'? + (if (> n round) (string-to-number (format "%0.4f" n)) round))) + +;; REVIEW 2023-04-07 07:43 +0300: I just wrote the conversions from +;; seconds. Hopefully they are correct, but I need to double check. +(defun prot-common-seconds-to-minutes (seconds) + "Convert a number representing SECONDS to MM:SS notation." + (let ((minutes (/ seconds 60)) + (seconds (% seconds 60))) + (format "%.2d:%.2d" minutes seconds))) + +(defun prot-common-seconds-to-hours (seconds) + "Convert a number representing SECONDS to HH:MM:SS notation." + (let* ((hours (/ seconds 3600)) + (minutes (/ (% seconds 3600) 60)) + (seconds (% seconds 60))) + (format "%.2d:%.2d:%.2d" hours minutes seconds))) + +;;;###autoload +(defun prot-common-seconds-to-minutes-or-hours (seconds) + "Convert SECONDS to either minutes or hours, depending on the value." + (if (> seconds 3599) + (prot-common-seconds-to-hours seconds) + (prot-common-seconds-to-minutes seconds))) + +;;;###autoload +(defun prot-common-rotate-list-of-symbol (symbol) + "Rotate list value of SYMBOL by moving its car to the end. +Return the first element before performing the rotation. + +This means that if `sample-list' has an initial value of `(one +two three)', this function will first return `one' and update the +value of `sample-list' to `(two three one)'. Subsequent calls +will continue rotating accordingly." + (unless (symbolp symbol) + (user-error "%s is not a symbol" symbol)) + (when-let* ((value (symbol-value symbol)) + (list (and (listp value) value)) + (first (car list))) + (set symbol (append (cdr list) (list first))) + first)) + +;;;###autoload +(defun prot-common-empty-buffer-p () + "Test whether the buffer is empty." + (or (= (point-min) (point-max)) + (save-excursion + (goto-char (point-min)) + (while (and (looking-at "^\\([a-zA-Z]+: ?\\)?$") + (zerop (forward-line 1)))) + (eobp)))) + +;;;###autoload +(defun prot-common-minor-modes-active () + "Return list of active minor modes for the current buffer." + (let ((active-modes)) + (mapc (lambda (m) + (when (and (boundp m) (symbol-value m)) + (push m active-modes))) + minor-mode-list) + active-modes)) + +;;;###autoload +(defun prot-common-truncate-lines-silently () + "Toggle line truncation without printing messages." + (let ((inhibit-message t)) + (toggle-truncate-lines t))) + +;; NOTE 2023-08-12: I tried the `clear-message-function', but it did +;; not work. What I need is very simple and this gets the job done. +;;;###autoload +(defun prot-common-clear-minibuffer-message (&rest _) + "Print an empty message to clear the echo area. +Use this as advice :after a noisy function." + (message "")) + +;;;###autoload +(defun prot-common-disable-hl-line () + "Disable Hl-Line-Mode (for hooks)." + (hl-line-mode -1)) + +;;;###autoload +(defun prot-common-window-bounds () + "Return start and end points in the window as a cons cell." + (cons (window-start) (window-end))) + +;;;###autoload +(defun prot-common-page-p () + "Return non-nil if there is a `page-delimiter' in the buffer." + (or (save-excursion (re-search-forward page-delimiter nil t)) + (save-excursion (re-search-backward page-delimiter nil t)))) + +;;;###autoload +(defun prot-common-window-small-p () + "Return non-nil if window is small. +Check if the `window-width' or `window-height' is less than +`split-width-threshold' and `split-height-threshold', +respectively." + (or (and (numberp split-width-threshold) + (< (window-total-width) split-width-threshold)) + (and (numberp split-height-threshold) + (> (window-total-height) split-height-threshold)))) + +;;;###autoload +(defun prot-common-three-or-more-windows-p (&optional frame) + "Return non-nil if three or more windows occupy FRAME. +If FRAME is non-nil, inspect the current frame." + (>= (length (window-list frame :no-minibuffer)) 3)) + +;;;###autoload +(defun prot-common-read-data (file) + "Read Elisp data from FILE." + (with-temp-buffer + (insert-file-contents file) + (read (current-buffer)))) + +;;;###autoload +(defun prot-common-completion-category () + "Return completion category." + (when-let ((window (active-minibuffer-window))) + (with-current-buffer (window-buffer window) + (completion-metadata-get + (completion-metadata (buffer-substring-no-properties + (minibuffer-prompt-end) + (max (minibuffer-prompt-end) (point))) + minibuffer-completion-table + minibuffer-completion-predicate) + 'category)))) + +;; Thanks to Omar Antolín Camarena for providing this snippet! +;;;###autoload +(defun prot-common-completion-table (category candidates) + "Pass appropriate metadata CATEGORY to completion CANDIDATES. + +This is intended for bespoke functions that need to pass +completion metadata that can then be parsed by other +tools (e.g. `embark')." + (lambda (string pred action) + (if (eq action 'metadata) + `(metadata (category . ,category)) + (complete-with-action action candidates string pred)))) + +;;;###autoload +(defun prot-common-completion-table-no-sort (category candidates) + "Pass appropriate metadata CATEGORY to completion CANDIDATES. +Like `prot-common-completion-table' but also disable sorting." + (lambda (string pred action) + (if (eq action 'metadata) + `(metadata (category . ,category) + (display-sort-function . ,#'identity)) + (complete-with-action action candidates string pred)))) + +;; Thanks to Igor Lima for the `prot-common-crm-exclude-selected-p': +;; . +;; This is used as a filter predicate in the relevant prompts. +(defvar crm-separator) + +;;;###autoload +(defun prot-common-crm-exclude-selected-p (input) + "Filter out INPUT from `completing-read-multiple'. +Hide non-destructively the selected entries from the completion +table, thus avoiding the risk of inputting the same match twice. + +To be used as the PREDICATE of `completing-read-multiple'." + (if-let* ((pos (string-match-p crm-separator input)) + (rev-input (reverse input)) + (element (reverse + (substring rev-input 0 + (string-match-p crm-separator rev-input)))) + (flag t)) + (progn + (while pos + (if (string= (substring input 0 pos) element) + (setq pos nil) + (setq input (substring input (1+ pos)) + pos (string-match-p crm-separator input) + flag (when pos t)))) + (not flag)) + t)) + +;; The `prot-common-line-regexp-p' and `prot-common--line-regexp-alist' +;; are contributed by Gabriel: . They +;; provide a more elegant approach to using a macro, as shown further +;; below. +(defvar prot-common--line-regexp-alist + '((empty . "[\s\t]*$") + (indent . "^[\s\t]+") + (non-empty . "^.+$") + (list . "^\\([\s\t#*+]+\\|[0-9]+[^\s]?[).]+\\)") + (heading . "^[=-]+")) + "Alist of regexp types used by `prot-common-line-regexp-p'.") + +(defun prot-common-line-regexp-p (type &optional n) + "Test for TYPE on line. +TYPE is the car of a cons cell in +`prot-common--line-regexp-alist'. It matches a regular +expression. + +With optional N, search in the Nth line from point." + (save-excursion + (goto-char (line-beginning-position)) + (and (not (bobp)) + (or (beginning-of-line n) t) + (save-match-data + (looking-at + (alist-get type prot-common--line-regexp-alist)))))) + +;; The `prot-common-shell-command-with-exit-code-and-output' function is +;; courtesy of Harold Carr, who also sent a patch that improved +;; `prot-eww-download-html' (from the `prot-eww.el' library). +;; +;; More about Harold: . +(defun prot-common-shell-command-with-exit-code-and-output (command &rest args) + "Run COMMAND with ARGS. +Return the exit code and output in a list." + (with-temp-buffer + (list (apply 'call-process command nil (current-buffer) nil args) + (buffer-string)))) + +(defvar prot-common-url-regexp + (concat + "~?\\<\\([-a-zA-Z0-9+&@#/%?=~_|!:,.;]*\\)" + "[.@]" + "\\([-a-zA-Z0-9+&@#/%?=~_|!:,.;]+\\)\\>/?") + "Regular expression to match (most?) URLs or email addresses.") + +(autoload 'auth-source-search "auth-source") + +;;;###autoload +(defun prot-common-auth-get-field (host prop) + "Find PROP in `auth-sources' for HOST entry." + (when-let ((source (auth-source-search :host host))) + (if (eq prop :secret) + (funcall (plist-get (car source) prop)) + (plist-get (flatten-list source) prop)))) + +;;;###autoload +(defun prot-common-parse-file-as-list (file) + "Return the contents of FILE as a list of strings. +Strings are split at newline characters and are then trimmed for +negative space. + +Use this function to provide a list of candidates for +completion (per `completing-read')." + (split-string + (with-temp-buffer + (insert-file-contents file) + (buffer-substring-no-properties (point-min) (point-max))) + "\n" :omit-nulls "[\s\f\t\n\r\v]+")) + +(defun prot-common-ignore (&rest _) + "Use this as override advice to make a function do nothing." + nil) + +;; NOTE 2023-06-02: The `prot-common-wcag-formula' and +;; `prot-common-contrast' are taken verbatim from my `modus-themes' +;; and renamed to have the prefix `prot-common-' instead of +;; `modus-themes-'. This is all my code, of course, but I do it this +;; way to ensure that this file is self-contained in case someone +;; copies it. + +;; This is the WCAG formula: . +(defun prot-common-wcag-formula (hex) + "Get WCAG value of color value HEX. +The value is defined in hexadecimal RGB notation, such #123456." + (cl-loop for k in '(0.2126 0.7152 0.0722) + for x in (color-name-to-rgb hex) + sum (* k (if (<= x 0.03928) + (/ x 12.92) + (expt (/ (+ x 0.055) 1.055) 2.4))))) + +;;;###autoload +(defun prot-common-contrast (c1 c2) + "Measure WCAG contrast ratio between C1 and C2. +C1 and C2 are color values written in hexadecimal RGB." + (let ((ct (/ (+ (prot-common-wcag-formula c1) 0.05) + (+ (prot-common-wcag-formula c2) 0.05)))) + (max ct (/ ct)))) + +;;;; EXPERIMENTAL macros (not meant to be used anywhere) + +;; TODO 2023-09-30: Try the same with `cl-defmacro' and &key +(defmacro prot-common-if (condition &rest consequences) + "Separate the CONSEQUENCES of CONDITION semantically. +Like `if', `when', `unless' but done by using `:then' and `:else' +keywords. The forms under each keyword of `:then' and `:else' +belong to the given subset of CONSEQUENCES. + +- The absence of `:else' means: (if CONDITION (progn CONSEQUENCES)). +- The absence of `:then' means: (if CONDITION nil CONSEQUENCES). +- Otherwise: (if CONDITION (progn then-CONSEQUENCES) else-CONSEQUENCES)." + (declare (indent 1)) + (let (then-consequences else-consequences last-kw) + (dolist (elt consequences) + (let ((is-keyword (keywordp elt))) + (cond + ((and (not is-keyword) (eq last-kw :then)) + (push elt then-consequences)) + ((and (not is-keyword) (eq last-kw :else)) + (push elt else-consequences)) + ((and is-keyword (eq elt :then)) + (setq last-kw :then)) + ((and is-keyword (eq elt :else)) + (setq last-kw :else))))) + `(if ,condition + ,(if then-consequences + `(progn ,@(nreverse then-consequences)) + nil) + ,@(nreverse else-consequences)))) + +(provide 'prot-common) +;;; prot-common.el ends here + +#+end_src + diff --git a/snippets/org-mode/time b/snippets/org-mode/time index 108b567..06a924c 100644 --- a/snippets/org-mode/time +++ b/snippets/org-mode/time @@ -2,4 +2,4 @@ # name: time # key: time # -- -[`(format-time-string "%Y-%m-%d %a %H:%M")`] +`(format-time-string "%Y-%m-%d %a %H:%M")` -- cgit v1.2.3