From 200692bb1fb8acdcc467862d92a1d24ac3e381bc Mon Sep 17 00:00:00 2001 From: jao Date: Tue, 7 Jun 2022 23:48:59 +0100 Subject: desorgification --- .gitignore | 10 - attic/counsel.org | 337 ---- attic/misc.org | 255 --- attic/orgs/blog.org | 254 +++ attic/orgs/completion.org | 657 ++++++++ attic/orgs/counsel.org | 337 ++++ attic/orgs/email.org | 391 +++++ attic/orgs/eww.org | 191 +++ attic/orgs/exwm.org | 551 +++++++ attic/orgs/gnus.org | 780 +++++++++ attic/orgs/init.org | 3427 +++++++++++++++++++++++++++++++++++++++ attic/orgs/misc.el | 95 ++ attic/orgs/misc.org | 255 +++ attic/orgs/notmuch.org | 675 ++++++++ attic/orgs/org.org | 316 ++++ blog.org | 254 --- completion.org | 657 -------- custom/jao-custom-blog.el | 226 +++ custom/jao-custom-completion.el | 530 ++++++ custom/jao-custom-email.el | 291 ++++ custom/jao-custom-eww.el | 182 +++ custom/jao-custom-exwm.el | 528 ++++++ custom/jao-custom-notmuch.el | 652 ++++++++ custom/jao-custom-org.el | 284 ++++ data/commons.html | 15 + email.org | 391 ----- eww.org | 191 --- exwm.org | 551 ------- gnus.org | 780 --------- init.el | 3006 ++++++++++++++++++++++++++++++++++ init.org | 3427 --------------------------------------- lib/doc/jao-doc-view.el | 14 +- lib/net/jao-notmuch.el | 36 +- notmuch.org | 675 -------- org.org | 316 ---- readme.org | 92 +- 36 files changed, 13679 insertions(+), 7950 deletions(-) delete mode 100644 attic/counsel.org delete mode 100644 attic/misc.org create mode 100644 attic/orgs/blog.org create mode 100644 attic/orgs/completion.org create mode 100644 attic/orgs/counsel.org create mode 100644 attic/orgs/email.org create mode 100644 attic/orgs/eww.org create mode 100644 attic/orgs/exwm.org create mode 100644 attic/orgs/gnus.org create mode 100644 attic/orgs/init.org create mode 100644 attic/orgs/misc.el create mode 100644 attic/orgs/misc.org create mode 100644 attic/orgs/notmuch.org create mode 100644 attic/orgs/org.org delete mode 100644 blog.org delete mode 100644 completion.org create mode 100644 custom/jao-custom-blog.el create mode 100644 custom/jao-custom-completion.el create mode 100644 custom/jao-custom-email.el create mode 100644 custom/jao-custom-eww.el create mode 100644 custom/jao-custom-exwm.el create mode 100644 custom/jao-custom-notmuch.el create mode 100644 custom/jao-custom-org.el create mode 100644 data/commons.html delete mode 100644 email.org delete mode 100644 eww.org delete mode 100644 exwm.org delete mode 100644 gnus.org create mode 100644 init.el delete mode 100644 init.org delete mode 100644 notmuch.org delete mode 100644 org.org diff --git a/.gitignore b/.gitignore index 7da9122..1800081 100644 --- a/.gitignore +++ b/.gitignore @@ -8,16 +8,6 @@ /lib/media/espotify-embark.el /lib/media/espotify.el /site -/gnus.el -/init.el /bin/ -/blog.el -/completion.el -/email.el -/eww.el -/exwm.el -/org.el /lib/net/signel.el /lib/net/signel.shell -/w3m.el -/notmuch.el diff --git a/attic/counsel.org b/attic/counsel.org deleted file mode 100644 index f6814ae..0000000 --- a/attic/counsel.org +++ /dev/null @@ -1,337 +0,0 @@ -#+title: Completion configuration using ivy, counsel and friends - -* selectrum - #+begin_src emacs-lisp :load no - (use-package selectrum - :ensure t - :init - (defun jao-selectrum--ord-refine (&rest args) - (let ((completion-styles '(orderless))) - (apply #'selectrum-refine-candidates-using-completions-styles args))) - - (defun jao-selectrum-orderless () - (interactive) - (setq selectrum-refine-candidates-function #'jao-selectrum--ord-refine) - (setq selectrum-highlight-candidates-function #'orderless-highlight-matches) - (setq orderless-skip-highlighting (lambda () selectrum-is-active))) - - - :config - ;; https://github.com/raxod502/selectrum/wiki/Ido,-icomplete(fido)-emulation - (defun selectrum-fido-backward-updir () - "Delete char before or go up directory, like `ido-mode'." - (interactive) - (if (and (eq (char-before) ?/) - (eq (selectrum--get-meta 'category) 'file)) - (save-excursion - (goto-char (1- (point))) - (when (search-backward "/" (point-min) t) - (delete-region (1+ (point)) (point-max)))) - (call-interactively 'backward-delete-char))) - - (defun selectrum-fido-delete-char () - "Delete char or maybe call `dired', like `ido-mode'." - (interactive) - (let ((end (point-max))) - (if (or (< (point) end) (not (eq (selectrum--get-meta 'category) 'file))) - (call-interactively 'delete-char) - (dired (file-name-directory (minibuffer-contents))) - (exit-minibuffer)))) - - (defun selectrum-fido-ret () - "Exit minibuffer or enter directory, like `ido-mode'." - (interactive) - (let* ((dir (and (eq (selectrum--get-meta 'category) 'file) - (file-name-directory (minibuffer-contents)))) - (current (selectrum-get-current-candidate)) - (probe (and dir current - (expand-file-name (directory-file-name current) dir)))) - (if (and probe (file-directory-p probe) (not (string= current "./"))) - (selectrum-insert-current-candidate) - (selectrum-select-current-candidate)))) - - ;; (define-key selectrum-minibuffer-map (kbd "RET") 'selectrum-fido-ret) - (define-key selectrum-minibuffer-map (kbd "DEL") 'selectrum-fido-backward-updir) - (define-key selectrum-minibuffer-map (kbd "C-d") 'selectrum-fido-delete-char) - - :custom ((selectrum-complete-in-buffer t) - ;; (selectrum-display-action '(display-buffer-at-bottom)) - (selectrum-extend-current-candidate-highlight t) - (selectrum-fix-vertical-window-height nil) - (selectrum-max-window-height 20) - (selectrum-show-indices nil) - (selectrum-count-style 'current/matches)) - :bind (("C-R" . selectrum-repeat))) - #+end_src -* ivy - #+begin_src emacs-lisp - (use-package ivy - :ensure t - :demand t - :custom - ((ivy-count-format "(%d/%d) ") - (ivy-do-completion-in-region t) - (ivy-height 20) - (ivy-re-builders-alist '((counsel-ag . ivy--regex) - (counsel-rg . ivy--regex) - (counsel-yank-pop . ivy--regex) - (swiper . ivy--regex) - (swiper-isearch . ivy--regex) - (t . ivy--regex-fuzzy))) - (ivy-use-virtual-buffers t) - (ivy-virtual-abbreviate 'abbreviate) - (ivy-wrap t)) - - :config - ;; used by ivy--regex-fuzzy to order results - (use-package flx :ensure t) - - ;; Try C-o in the minibuffer - (use-package ivy-hydra - :after ivy - :ensure t - :init (setq ivy-read-action-function #'ivy-hydra-read-action)) - - (add-to-list 'ivy-initial-inputs-alist - '(gnus-summary-move-article . "")) - - :bind (("C-R" . ivy-resume) - ("C-x b" . ivy-switch-buffer) - ("C-c v" . ivy-push-view) - ("C-c V" . ivy-pop-view)) - :diminish) - #+end_src -* counsel - #+begin_src emacs-lisp - (use-package counsel - :ensure t - :custom ((counsel-describe-function-function 'helpful-callable) - (counsel-describe-variable-function 'helpful-variable) - (counsel-find-file-at-point t) - (counsel-linux-app-format-function - #'counsel-linux-app-format-function-name-pretty) - (counsel-mode-override-describe-bindings nil) - (counsel-recentf-include-xdg-list t)) - :config - :bind (("C-s" . swiper-isearch) - ("C-S-s" . isearch-forward) - ("M-x" . counsel-M-x) - ("C-x f" . counsel-find-file) - ("C-c k" . counsel-ag) - ("C-c K" . counsel-rg) - ("C-c l" . counsel-locate) - ("C-c b" . counsel-git) - ("C-c i" . counsel-imenu) - ("C-c G" . counsel-search) - ("s-r" . counsel-linux-app)) - :diminish) - #+end_src -* counsel add-ons -*** notmuch - #+begin_src emacs-lisp - (use-package counsel-notmuch - :ensure t - :config (with-eval-after-load "gnus-group" - (define-key gnus-group-mode-map "Gg" 'counsel-notmuch))) - #+end_src -*** recoll - #+begin_src emacs-lisp - (require 'jao-recoll) - (defvar jao-counsel-recoll--history nil) - (defun jao-counsel-recoll--function (str) - (let ((xs (counsel-recoll-function str))) - (cl-remove-if-not (lambda (x) (string-prefix-p "file://" x)) xs))) - - (defun jao-counsel-recoll (&optional initial-input) - (interactive) - (counsel-require-program "recoll") - (ivy-read "recoll: " 'jao-counsel-recoll--function - :initial-input initial-input - :dynamic-collection t - :history 'jao-counsel-recoll--history - :action (lambda (x) - (when (string-match "file://\\(.*\\)\\'" x) - (let ((file-name (match-string 1 x))) - (if (string-match "pdf$" x) - (jao-open-doc file-name) - (find-file file-name))))) - :unwind #'counsel-delete-process - :caller 'jao-counsel-recoll)) - - (defun jao-counsel-recoll--recoll (_s) (jao-recoll ivy-text)) - - (ivy-set-actions 'jao-counsel-recoll - '(("x" jao-counsel-recoll--recoll "List in buffer"))) - - (global-set-key (kbd "C-c R") #'jao-counsel-recoll) - #+end_src -* ivy rich - #+begin_src emacs-lisp - (use-package ivy-rich - :after (ivy counsel) - :ensure t - :custom ((ivy-rich-path-style 'relative) - (ivy-rich-parse-remote-buffer nil) - (ivy-rich-parse-remote-file-path nil)) - :config - (ivy-rich-modify-columns - 'ivy-switch-buffer - '((ivy-rich-candidate (:width 80)) - (ivy-rich-switch-buffer-indicators (:face jao-themes-f00)) - (ivy-rich-switch-buffer-project (:width 15)) - (ivy-rich-switch-buffer-major-mode (:width 15 :face jao-themes-f12))))) - #+end_src -* cmap - #+begin_src emacs-lisp - (jao-load-path "cmap") - (use-package cmap - :demand t - :bind (("C-;" . cmap-cmap) - ("C-'" . cmap-default))) - #+end_src -*** prompter - #+begin_src emacs-lisp - (defun jao-cmap--hide-help () - (when-let ((w (get-buffer-window (help-buffer)))) - (with-selected-window w (kill-buffer-and-window)))) - - (defun jao-cmap--prompter (keymap) - (let ((display-buffer-alist '(("*Help*" - (display-buffer-at-bottom) - (window-parameters (mode-line-format . none)) - (window-height . fit-window-to-buffer))))) - (let ((inhibit-message t)) - (describe-keymap keymap)))) - - (defun jao-cmap--prompter-done () - (save-current-buffer (jao-cmap--hide-help))) - - (setq cmap-prompter #'jao-cmap--prompter) - (setq cmap-prompter-done #'jao-cmap--prompter-done) - #+end_src -*** minibuffer actions - #+begin_src emacs-lisp - (defun jao-cmap--completion-metadata () - (completion-metadata - (buffer-substring-no-properties (field-beginning) (point)) - minibuffer-completion-table - minibuffer-completion-predicate)) - - (defun jao-cmap--completion-category () - (completion-metadata-get (jao-cmap--completion-metadata) 'category)) - - (defmacro cmap-define-keymap (v d &rest b) - `(defvar ,v (cmap-keymap ,@b) ,d)) - - (cmap-define-keymap jao-cmap-buffer-map - "Keymap for buffer actions." - ("k" . kill-buffer) - ("b" . switch-to-buffer) - ("o" . switch-to-buffer-other-window) - ("z" . bury-buffer) - ("q" . kill-buffer-and-window) - ("=" . ediff-buffers)) - - ;; (cmap-define-keymap espotify-item-keymap - ;; "Actions for Spotify search results" - ;; ("a" espotify--play-album) - ;; ("h" espotify--show-info)) - - (defvar jao-cmap--smaps - '((command . cmap-command-map) - ;; (espotify-search-item . espotify-item-keymap) - (function . cmap-function-map) - (variable . cmap-variable-map) - (face . cmap-face-map) - (buffer . jao-cmap-buffer-map) - (consult-buffer . jao-cmap-buffer-map))) - - (defun jao-cmap-target-minibuffer-candidate () - (when (minibuffer-window-active-p (selected-window)) - (let ((cand (ivy-state-current ivy-last)) - (cat (jao-cmap--completion-category))) - (when-let (m (alist-get cat jao-cmap--smaps)) - (cons m cand))))) - - (add-to-list 'cmap-targets #'jao-cmap-target-minibuffer-candidate) - #+end_src -*** url / video actions - #+begin_src emacs-lisp - (defvar jao-cmap-video-url-rx - (format "^https?://\\(?:www\\.\\)?%s/.+" - (regexp-opt '("youtu.be" - "youtube.com" - "blip.tv" - "vimeo.com" - "infoq.com") - t)) - "A regular expression matching URLs that point to video streams") - - (defun jao-cmap--play-video (player url) - (interactive "sURL: ") - (let ((cmd (format "%s %s" player (shell-quote-argument url)))) - (start-process-shell-command player nil cmd))) - - (defun jao-cmap-mpv (&optional url) - "Play video stream with mpv" - (interactive "sURL: ") - (jao-cmap--play-video "mpv" url)) - - (defun jao-cmap-vlc (&optional url) - "Play video stream with vlc" - (interactive "sURL: ") - (jao-cmap--play-video "vlc" url)) - - (defun jao-cmap-target-w3m-url () - (when-let (url (or (thing-at-point-url-at-point) - (w3m-anchor) - w3m-current-url)) - (cons 'cmap-url-map url))) - - (defun jao-cmap-kill (&optional x) - "Save to kill ring" - (interactive "s") - (kill-new x)) - - (defun jao-cmap-url (url) - "Browse URL, externally if we're already in emacs-w3m" - (if (derived-mode-p 'w3m-mode) - (jao-browse-with-external-browser url) - (browse-url url))) - - (define-key cmap-url-map [return] #'jao-cmap-url) - (define-key cmap-url-map "f" #'browse-url-firefox) - (define-key cmap-url-map "w" #'jao-cmap-kill) - - (defun jao-cmap-target-video-url () - (when-let (url (jao-cmap-target-w3m-url)) - (when (string-match-p jao-cmap-video-url-rx (cdr url)) - (cons 'jao-cmap-video-url-map (cdr url))))) - - (cmap-define-keymap jao-cmap-video-url-map - "Actions on URLs pointing to remote video streams." - ("v" . jao-cmap-vlc) - ([return] . jao-cmap-mpv)) - - (add-to-list 'cmap-targets #'jao-cmap-target-w3m-url) - (add-to-list 'cmap-targets #'jao-cmap-target-video-url) - #+end_src -* hooks - #+begin_src emacs-lisp - (with-eval-after-load "exwm" - (add-to-list 'exwm-input-global-keys '([?\s-r] . counsel-linux-app))) - - (with-eval-after-load "espotify" - (require 'ivy-spotify) - (defalias 'jao-spotify-album #'ivy-spotify-album) - (defalias 'jao-spotify-track #'ivy-spotify-track) - (defalias 'jao-spotify-artist #'ivy-spotify-artist) - (defalias 'jao-spotify-playlist #'ivy-spotify-playlist)) - #+end_src -* startup - #+begin_src emacs-lisp - (ivy-mode 1) - (counsel-mode 1) - (ivy-rich-mode 1) - (ivy-rich-project-root-cache-mode 1) - #+end_src diff --git a/attic/misc.org b/attic/misc.org deleted file mode 100644 index 3a31d6a..0000000 --- a/attic/misc.org +++ /dev/null @@ -1,255 +0,0 @@ -* corfu - #+begin_src emacs-lisp - (use-package corfu - :ensure t - :demand t - :init (setq corfu-echo-documentation 0.25 - corfu-cycle t - corfu-count 15 - corfu-quit-no-match t - corfu-auto t - corfu-commit-predicate nil - corfu-preview-current nil - corfu-preselect-first t - corfu-min-width 20 - corfu-max-width 100) - :config - - ;; show eldoc string immediately after accepted completion too - (with-eval-after-load "eldoc" - (eldoc-add-command-completions "corfu-")) - - (defun jao-corfu-enable-no-auto () - (setq-local corfu-auto nil) - (corfu-mode 1)) - - (defmacro jao-corfu-no-auto (mode) - (let ((mode-name (intern (format "%s-mode" mode))) - (hook-name (intern (format "%s-mode-hook" mode)))) - `(with-eval-after-load ',mode - (add-to-list 'corfu-excluded-modes ',mode-name) - (add-hook ',hook-name #'jao-corfu-enable-no-auto)))) - - (jao-corfu-no-auto eshell) - ;; (add-to-list 'corfu-excluded-modes 'notmuch-message-mode) - - (defun jao-corfu--active-p () - (and (>= corfu--index 0) (/= corfu--index corfu--preselect))) - - (defun jao-corfu-quit-or-insert () - (interactive) - (if (jao-corfu--active-p) (corfu-insert) (corfu-quit))) - - (defun jao-corfu-quit-or-previous () - (interactive) - (if (jao-corfu--active-p) - (corfu-previous) - (corfu-quit) - (previous-line))) - - :bind (:map corfu-map - ("C-" . corfu-insert) - ("\r" . jao-corfu-quit-or-insert) - ("C-p" . jao-corfu-quit-or-previous))) - - (defun corfu-in-minibuffer () - (when (not (bound-and-true-p vertico--input)) (corfu-mode 1))) - - (when (display-graphic-p) - (add-hook 'minibuffer-setup-hook #'corfu-in-minibuffer 1) - (global-corfu-mode 1)) - - #+end_src -* erc -*** package - #+begin_src emacs-lisp - (use-package erc - :init - (setq erc-modules - '(autojoin - button - dcc - fill - irccontrols - match - move-to-prompt - netsplit - networks - noncommands - notify - pcomplete - ring - services - stamp - track - truncate)) - - (setq erc-auto-query 'bury - erc-autojoin-channels-alist `(("libera.chat" ,@jao-libera-channels)) - erc-away-nickname "jao" - erc-button-buttonize-nicks t - erc-common-server-suffixes '(("libera.chat$" . "lb")) - erc-current-nick-highlight-type 'nick-or-keyword - erc-email-userid (car jao-mails) - erc-fill-column 84 - erc-fill-prefix " " - erc-format-nick-function 'erc-format-@nick - erc-header-line-face-method t - erc-header-line-format nil ;; "%l %o" - erc-header-line-uses-tabbar-p nil - erc-hide-list '("JOIN" "PART" "QUIT") - erc-hide-timestamps nil - erc-input-line-position -1 - erc-insert-timestamp-function 'erc-insert-timestamp-right - erc-join-buffer 'bury - erc-kill-buffer-on-part t - erc-kill-queries-on-quit t - erc-log-channels-directory nil - erc-mode-line-away-status-format "(a)" - erc-mode-line-format "%t" - erc-nick "jao" - erc-notice-highlight-type 'all - erc-notice-prefix "- " - erc-notify-signoff-hook 'erc-notify-signoff - erc-notify-signon-hook 'erc-notify-signon - erc-pcomplete-nick-postfix "," - erc-rename-buffers t - erc-server-send-ping-timeout 60 - erc-prompt ":" - erc-prompt-for-nickserv-password nil - erc-use-auth-source-for-nickserv-password t - erc-prompt-for-password nil - erc-public-away-p t - erc-server "irc.libera.chat" - erc-server-coding-system '(utf-8 . undecided) - erc-server-reconnect-attempts 10 - erc-server-reconnect-timeout 10 - erc-timestamp-format "%H:%M" - erc-timestamp-only-if-changed-flag t - erc-timestamp-right-column 84 - erc-user-full-name "https://jao.io" - erc-user-mode "+i" - erc-whowas-on-nosuchnick t) - - :config - - (define-minor-mode ncm-erc-mode "" nil - (:eval (format " [%s]" (hash-table-count erc-channel-users)))) - - (add-hook 'erc-mode-hook (lambda () (ncm-erc-mode 1))) - (add-hook 'erc-mode-hook (lambda () (auto-fill-mode -1)))) - #+end_src -*** no angles - #+begin_src emacs-lisp - (defun jao-erc--no-angles (old-func &rest args) - (let ((msg (apply old-func args))) - (replace-regexp-in-string "^<\\([^>]+\\)>" "(\\1)" msg))) - - (with-eval-after-load "erc" - (modify-syntax-entry ?\( "." erc-mode-syntax-table) - (modify-syntax-entry ?\) "." erc-mode-syntax-table) - (advice-add 'erc-format-privmessage :around #'jao-erc--no-angles) - (advice-add 'erc-format-my-nick :around #'jao-erc--no-angles)) - #+end_src -*** tracking - #+begin_src emacs-lisp - (defun jao-erc-track-shorten (names) - (let ((names (erc-track-shorten-names names))) - (mapcar (lambda (n) (string-remove-prefix "#" n)) names))) - - (setq erc-track-exclude-server-buffer t - erc-track-exclude-types '("NICK" "JOIN" "PART" "QUIT" "MODE" "KICK") - erc-track-remove-disconnected-buffers t - erc-track-shorten-aggressively t ;; 'max - erc-track-shorten-function #'jao-erc-track-shorten - erc-track-switch-direction 'importance - erc-track-visibility nil ;; t all, nil only selected frame - erc-track-position-in-mode-line nil - erc-track-enable-keybindings nil ;; 'ask - erc-track-faces-priority-list '(erc-error-face - erc-current-nick-face - erc-pal-face - erc-direct-msg-face - erc-nick-msg-face - erc-default-face - erc-action-face - erc-notice-face)) - (defun jao-track-erc-buffers () - (dolist (e erc-modified-channels-alist) - (tracking-add-buffer (car e) (list (cddr e))))) - - (with-eval-after-load "erc-track" - (require 'tracking nil t) - (add-hook 'exwm-workspace-switch-hook #'erc-modified-channels-update) - (add-hook 'erc-track-list-changed-hook #'jao-track-erc-buffers)) - - (jao-shorten-modes 'erc-mode) - (jao-tracking-faces 'erc-error-face - 'erc-pal-face - 'erc-current-nick-face - 'erc-direct-msg-face) - #+end_src -*** commands (/recover &co.) - #+begin_src emacs-lisp - (defun erc-cmd-RECOVER (&rest ignore) - "Recover nick" - (let ((fn (jao--get-user/password "freenode"))) - (erc-cmd-MSG (format "nickserv IDENTIFY %s %s" (car fn) (cadr fn))) - (erc-cmd-MSG (format "nickserv GHOST %s" (car fn))) - (erc-cmd-MSG (format "nickserv RELEASE %s" (car fn))) - (erc-cmd-NICK (car fn)))) - #+end_src -*** startup - #+begin_src emacs-lisp - (defun jao-erc (&optional yes) - (interactive "P") - ;; (when (or yes (y-or-n-p "Connect to bitlbee using ERC? ")) - ;; (erc-select :server "localhost")) - (when (or yes (y-or-n-p "Connect to libera using ERC? ")) - (erc-select :server "irc.libera.chat"))) - #+end_src -* dtache - #+begin_src emacs-lisp - (use-package dtache - :ensure t - :hook (after-init . dtache-setup)) - - (use-package dtache-eshell - :after dtache - :hook (eshell-mode . dtache-eshell-mode)) - - (use-package dtache-consult - :after (consult dtache) - :bind ([remap dtache-open-session] . dtache-consult-session)) - #+end_src -* signel - #+begin_src emacs-lisp :tangle no - (jao-load-org "lib/net/signel.org") - (with-eval-after-load "tracking" - (jao-tracking-faces 'signel-notification) - (jao-shorten-modes 'signel-chat-mode)) - (setq signel-report-deliveries t) - #+end_src -* cdlatex - #+begin_src emacs-lisp - (use-package cdlatex - :ensure t - :hook ((org-mode . org-cdlatex-mode)) - :diminish ((cdlatex-mode . " £") - (org-cdlatex-mode . " £"))) - #+end_src -* maps - #+begin_src emacs-lisp - (use-package osm - :ensure t - :init - (with-eval-after-load 'org (require 'osm-ol)) - :config - (transient-define-prefix jao-transient-osm () - ["Open Street Maps" - ("s" "search" osm-search) - ("g" "goto" osm-goto) - ("b" "jump to bookmark" osm-bookmark-jump) - ("t" "server" osm-server)]) - :bind ("C-c M" . #'jao-transient-osm)) - #+end_src diff --git a/attic/orgs/blog.org b/attic/orgs/blog.org new file mode 100644 index 0000000..29b35f0 --- /dev/null +++ b/attic/orgs/blog.org @@ -0,0 +1,254 @@ +#+title: Org static blog +#+property: header-args lexical: t :tangle yes :comments yes :results silent :shebang ";; -*- lexical-binding: t -*-" :tangle-mode (identity #o644) +#+auto_tangle: t + +* Vars and setup + #+begin_src emacs-lisp + (jao-load-path "org-static-blog") + (when (> emacs-major-version 26) (use-package htmlize :ensure t)) + (defvar jao-blog-base-dir "~/doc/jao.io") + (defun jao-blog-dir (p) (expand-file-name p jao-blog-base-dir)) + + (setq jao-org-blog-tag-files + (seq-difference (directory-files (jao-blog-dir "blog") nil "tag-.*") + "tag-norss.html") + + jao-org-blog-tags + (mapcar (lambda (f) + (string-match "tag-\\(.+\\)\\.html" f) + (format "%s" + f (match-string 1 f))) + jao-org-blog-tag-files) + + jao-org-blog-tag-rss + (mapcar (lambda (f) + (string-match "\\(.+\\)-rss\\.xml" f) + (format "%s" + f (match-string 1 f))) + (directory-files (jao-blog-dir "blog") nil ".*-rss.xml")) + + jao-org-blog-tag-names + (mapcar (lambda (f) + (string-match "tag-\\(.+\\)\\.html" f) + (match-string 1 f)) + jao-org-blog-tag-files)) + #+end_src +* HTML headers and footers +*** Header + #+begin_src emacs-lisp + (setq org-static-blog-page-header + (concat + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n") + + org-static-blog-page-preamble + (concat + "
" + " programming (and other) musings" + "
" + " about" + " | hacking" + " | archive" + " |
" + " tags" + "
" + (mapconcat #'identity jao-org-blog-tags "") + "
" + "
" + " |
" + " rss" + "
" + (mapconcat #'identity jao-org-blog-tag-rss "") + "
" + "
" + "
" + "
")) + #+end_src +*** Footer + #+begin_src html :tangle ~/.emacs.d/commons.html :comments no :shebang "" +
+ + Creative Commons License + +
+ jao.io by + jao + is licensed under a + + Creative Commons Attribution-ShareAlike 3.0 Unported License. +
+ #+end_src + + #+begin_src emacs-lisp + (setq org-static-blog-page-postamble + (with-temp-buffer + (insert-file-contents "~/.emacs.d/commons.html") + (buffer-string))) + #+end_src +* Package + #+begin_src emacs-lisp + (use-package org-static-blog + :ensure t + :init + (setq org-static-blog-use-preview t + org-static-blog-preview-link-p t + org-static-blog-preview-start "" + org-static-blog-preview-end "" + org-static-blog-preview-date-first-p t + org-static-blog-index-length 30 + org-static-blog-preview-convert-titles t + org-static-blog-preview-ellipsis "more ..." + org-static-blog-enable-tags t + org-static-blog-tags-file "tags.html" + org-static-blog-rss-file "rss.xml" + org-static-blog-publish-url "https://jao.io/blog/" + org-static-blog-publish-title "programming (and other) musings" + org-static-blog-posts-directory (jao-blog-dir "posts/") + org-static-blog-drafts-directory (jao-blog-dir "pages/") + org-static-blog-publish-directory (jao-blog-dir "blog/") + org-static-blog-rss-extra "" ; "mail@jao.io\n" + org-static-blog-rss-max-entries 30 + org-static-blog-rss-excluded-tag "norss" + org-static-blog-enable-tag-rss t + org-export-with-toc nil + org-export-with-section-numbers nil) + + :config + (defun jao-org-static-post-path (pf dt) + (cond ((string-match-p "pages/.*\\|in-no-particular-order" pf) + (file-name-nondirectory pf)) + ((string-match-p "drafts/.*" pf) pf) + ((string-match-p "^[[:digit:]]+-.*" pf) pf) + (t (concat (format-time-string "%Y-%m-%d-" dt) + (file-name-nondirectory pf))))) + (advice-add 'org-static-blog-generate-post-path :override + #'jao-org-static-post-path) + + :bind (:map org-mode-map (("C-c B" . jao-transient-org-blog)))) + #+end_src +* Commands +*** New entries + #+begin_src emacs-lisp + (defun jao-org-blog-publish-file (fname) + (interactive (list (read-file-name "Publish: " + nil + (buffer-file-name) + t + (buffer-file-name)))) + (let ((geiser-active-implementations '(guile)) + (geiser-default-implementation 'guile)) + (org-static-blog-publish-file fname))) + + (defconst jao-org-static-blog--prev-beg + "#+begin_export html\n \n#+end_export ") + + (defconst jao-org-static-blog--prev-end + "#+begin_export html\n \n#+end_export ") + + (defun jao-org-static-blog-create-new-post (&optional draft) + (interactive) + (let* ((title (read-string "Title: ")) + (file (replace-regexp-in-string "\s" "-" (downcase title))) + (tags (completing-read-multiple "Tags: " jao-org-blog-tag-names))) + (find-file (expand-file-name (concat file ".org") + (if draft + org-static-blog-drafts-directory + org-static-blog-posts-directory))) + (insert "#+title: " title "\n" + "#+date: " (format-time-string "<%Y-%m-%d %H:%M>") "\n" + "#+filetags: " + (mapconcat #'identity tags " ") + "\n\n") + (when (member "books" tags) + (insert jao-org-static-blog--prev-beg + "\n\n[[https://jao.io/img/" file ".jpg]]\n\n")) + (save-excursion (insert jao-org-static-blog--prev-end "\n")))) + #+end_src +*** Drafts + #+begin_src emacs-lisp + (defun jao-org-static-blog-update-date () + (interactive) + (when (y-or-n-p "Update date? ") + (goto-char (point-min)) + (when (re-search-forward "^#\\+date: " nil t) + (let ((kill-whole-line nil)) (kill-line)) + (insert (format-time-string "<%Y-%m-%d %H:%M>")) + (save-buffer)))) + + (defun jao-org-static-blog-create-new-draft () + (interactive) + (jao-org-static-blog-create-new-post t)) + + (defun jao-org-static-blog-publish-draft () + (interactive) + (let* ((from (read-file-name "Post: " + org-static-blog-drafts-directory + nil t)) + (to (expand-file-name (file-name-nondirectory from) + org-static-blog-posts-directory))) + (rename-file from to) + (when-let ((b (get-buffer from))) + (kill-buffer b)) + (find-file to) + (jao-org-static-blog-update-date) + (when (y-or-n-p "Generate HTML? ") + (jao-org-blog-publish)))) + + (defun jao-org-static-blog-edit-draft () + (interactive) + (find-file (read-file-name "Edit: " + org-static-blog-drafts-directory + nil + t))) + #+end_src +*** Publish + #+begin_src emacs-lisp + (defun jao-org-blog-publish (&optional force) + (interactive "P") + (let ((geiser-active-implementations '(guile)) + (geiser-default-implementation 'guile)) + (org-static-blog-publish force))) + + (defun jao-org-blog-republish () + (interactive) + (jao-org-blog-publish t)) + #+end_src +* Transient + #+begin_src emacs-lisp + (defun jao-org-static-prev-begin () + (interactive) + (insert jao-org-static-blog--prev-beg)) + + (defun jao-org-static-prev-end () + (interactive) + (insert jao-org-static-blog--prev-end)) + + (jao-transient-major-mode+ org + ["Insert blog snippet" + ("s" "preview begin" jao-org-static-prev-begin) + ("S" "preview end" jao-org-static-prev-end) + ("T" "update date" jao-org-static-blog-update-date)] + ["Edit blog" + ("n" "create post" jao-org-static-blog-create-new-post) + ("d" "create draft" jao-org-static-blog-create-new-draft) + ("e" "edit draft" jao-org-static-blog-edit-draft)] + ["Publish blog" + ("D" "publish draft" jao-org-static-blog-publish-draft) + ("f" "publish single file" jao-org-blog-publish-file) + ("p" "publish all" jao-org-blog-publish) + ("r" "republish" jao-org-blog-republish)]) + #+end_src diff --git a/attic/orgs/completion.org b/attic/orgs/completion.org new file mode 100644 index 0000000..3a4049a --- /dev/null +++ b/attic/orgs/completion.org @@ -0,0 +1,657 @@ +#+property: header-args :lexical t :tangle yes :comments yes :results silent :shebang ";; -*- lexical-binding: t -*-" :tangle-mode (identity #o644) +#+title: Completion configuration +#+auto_tangle: t + +* imenu + #+begin_src emacs-lisp + (use-package imenu + :init (setq org-imenu-depth 7) + :config + (defun jao-imenu-hook () + (cond ((derived-mode-p 'org-mode) (org-reveal t)) + (outline-minor-mode (outline-show-entry)))) + (add-hook 'imenu-after-jump-hook #'jao-imenu-hook)) + #+end_src +* completion styles +*** completion configuration + #+begin_src emacs-lisp + (setq tab-always-indent 'complete + read-extended-command-predicate #'command-completion-default-include-p + completion-category-defaults nil + completion-cycle-threshold nil + completions-detailed t + completion-show-help nil + completion-show-inline-help nil + completion-ignore-case t + completion-wrap-movement t + completion-auto-select nil + completions-format 'one-column + completion-styles '(basic substring partial-completion emacs22) + completion-category-overrides + '((file (styles partial-completion)) + (command (styles initials substring partial-completion)) + (symbol (styles initials substring partial-completion)) + (variable (styles initials substring partial-completion)))) + + ;; (setq completions-sort #'jao-completion--sort-by-length-alpha) + (setq completions-sort #'jao-completion--sort-by-history) + + (defun jao-completion--sort-by-alpha-length (elems) + (sort elems (lambda (c1 c2) + (or (string-version-lessp c1 c2) + (< (length c1) (length c2)))))) + + (defun jao-completion--sort-by-history (elems) + (let ((hist (and (not (eq minibuffer-history-variable t)) + (symbol-value minibuffer-history-variable)))) + (if hist + (minibuffer--sort-by-position hist elems) + (jao-completion--sort-by-alpha-length elems)))) + + #+end_src +*** crm indicator + #+begin_src emacs-lisp + (defun jao-completion--crm-indicator (args) + "Add prompt indicator to `completing-read-multiple' filter ARGS." + (cons (concat "[CRM] " (car args)) (cdr args))) + (advice-add #'completing-read-multiple + :filter-args #'jao-completion--crm-indicator) + #+end_src +*** directory navigation + #+begin_src emacs-lisp + (defun jao-completion-backward-updir () + "Delete char before point or go up a directory." + (interactive nil mct-minibuffer-mode) + (cond ((and (eq (char-before) ?/) + (eq (mct--completion-category) 'file)) + (when (string-equal (minibuffer-contents) "~/") + (delete-minibuffer-contents) + (insert (expand-file-name "~/")) + (goto-char (line-end-position))) + (save-excursion + (goto-char (1- (point))) + (when (search-backward "/" (minibuffer-prompt-end) t) + (delete-region (1+ (point)) (point-max))))) + (t (call-interactively 'backward-delete-char)))) + + (define-key minibuffer-local-filename-completion-map (kbd "DEL") + #'jao-completion-backward-updir) + #+end_src +* orderless + #+begin_src emacs-lisp + (use-package orderless + :ensure t + :init + :config + (orderless-define-completion-style orderless+initialism + (orderless-matching-styles '(orderless-initialism + orderless-prefixes + orderless-literal + orderless-regexp))) + + (defun jao-orderless--set-locally () + (setq-local completion-styles + '(substring partial-completion orderless) + + completion-category-overrides + '((file (styles partial-completion orderless)) + (command (styles orderless+initialism))) + orderless-matching-styles + '(orderless-literal orderless-regexp orderless-prefixes))) + (add-hook 'minibuffer-setup-hook #'jao-orderless--set-locally)) + + #+end_src +* marginalia + #+begin_src emacs-lisp + (use-package marginalia + :ensure t + :bind (:map minibuffer-local-map ("C-M-a" . marginalia-cycle)) + + :custom ((marginalia-align 'left) + (marginalia-align-offset 1) + (marginalia-field-width 200) + (marginalia-annotators + '(marginalia-annotators-heavy marginalia-annotators-light nil)) + (marginalia-separator " "))) + + (marginalia-mode 1) + #+end_src +* company + #+begin_src emacs-lisp + (use-package company + :ensure t + :custom ((company-backends '(company-capf + ;; company-bbdb + company-files + company-dabbrev + company-keywords)) + (company-global-modes '(not slack-message-buffer-mode + circe-channel-mode + telega-chat-mode)) + (company-format-margin-function nil) ;; #'company-text-icons-margin + (company-idle-delay 0.2) + (company-lighter "") + (company-lighter-base "") + (company-show-numbers nil) + (company-selection-wrap-around t) + (company-tooltip-limit 15) + (company-tooltip-align-annotations t) + (company-tooltip-offset-display 'lines)) ;; 'scrollbar + + :config + (defun jao-complete-at-point () + "Complete using company unless we're in the minibuffer." + (interactive) + (if (or (not company-mode) (window-minibuffer-p)) + (completion-at-point) + (company-manual-begin))) + + (defun jao-company-use-in-tab () + (global-set-key [remap completion-at-point] #'jao-complete-at-point) + (global-set-key [remap completion-symbol] #'jao-complete-at-point) + (global-set-key (kbd "M-TAB") #'jao-complete-at-point)) + + (jao-company-use-in-tab) + + :bind (:map company-active-map + + ("" . company-complete-common-or-cycle) + ("TAB" . company-complete-common-or-cycle) + + ("C-h" . company-show-doc-buffer) + ("M-." . company-show-location) + ("C-" . company-complete-selection) + ([remap return] . company-abort) + ("RET" . company-abort) + + :filter (or (not (derived-mode-p 'eshell-mode)) + (company-explicit-action-p)) + ("" . company-complete-selection) + ("RET" . company-complete-selection)) + :diminish) + + (unless (display-graphic-p) (global-company-mode 1)) + #+end_src +* vertico + #+begin_src emacs-lisp + (use-package vertico + :ensure t + :init + (setq vertico-count 20 + vertico-cycle t + vertico-resize t + org-refile-use-outline-path t) + + :config + + ;; (setq completion-in-region-function + ;; (lambda (&rest args) + ;; (apply (if (and (not window-system) vertico-mode) + ;; #'consult-completion-in-region + ;; #'completion--in-region) + ;; args))) + + (defun jao-vertico--display-candidates (lines) + (move-overlay vertico--candidates-ov (point-min) (point-min)) + (overlay-put vertico--candidates-ov 'after-string (apply #'concat lines)) + (vertico--resize-window (length lines))) + + (advice-add 'vertico--display-candidates + :override #'jao-vertico--display-candidates)) + + (use-package vertico-directory + :after vertico + :bind (:map vertico-map (("RET" . vertico-directory-enter) + ("M-" . vertico-directory-delete-word) + ("" . vertico-directory-delete-char)))) + + (vertico-mode) + + #+end_src +* consult +*** package + #+begin_src emacs-lisp + (use-package consult + :ensure t + :bind (("C-x M-:" . consult-complex-command) + ("C-x b" . consult-buffer) + ("C-x C-b" . consult-buffer) + ("C-x 4 b" . consult-buffer-other-window) + ("C-c b" . project-find-file) + ("C-c h" . consult-history) + ("C-c i" . consult-imenu) + ("C-c I" . consult-project-imenu) + ("C-c k" . consult-ripgrep) + ("C-c K" . consult-git-grep) + ("C-c L" . consult-locate) + ;; ("C-h m" . consult-mode-command) + ("C-c s" . consult-line) + ("C-x r x" . consult-register) + ("C-x r b" . consult-bookmark) + ("C-x C-f" . jao-find-file) + ("M-g b" . consult-bookmark) + ("M-g m" . consult-mark) + ("M-g e" . consult-error) + ("M-s m" . consult-multi-occur) + ("M-s o" . consult-outline) + ("M-y" . consult-yank-pop) + ("C-s" . isearch-forward) + ("C-S-s" . consult-line) + (" a" . consult-apropos)) + + :custom ((consult-preview-key (kbd "`"))) + + :init + (fset 'multi-occur #'consult-multi-occur) + + :config + + (defun jao-find-file (arg) + (interactive "P") + (call-interactively (if arg 'consult-file-externally 'find-file))) + + (define-key consult-narrow-map (vconcat consult-narrow-key "?") + #'consult-narrow-help) + + (consult-customize consult-mark :preview-key 'any) + (add-hook 'completion-list-mode-hook #'consult-preview-at-point-mode)) + + #+end_src +*** consult-dir + #+begin_src emacs-lisp + (use-package consult-dir + :ensure t + :bind (("C-x C-d" . consult-dir) + :map minibuffer-local-completion-map + (("C-x C-d" . consult-dir) + ("C-x C-j" . consult-dir-jump-file)))) + #+end_src +*** dh-diff hunks + #+begin_src emacs-lisp + (defun jao-consult--diff-lines (&optional backward) + (let ((candidates) + (width (length (number-to-string + (line-number-at-pos (point-max) + consult-line-numbers-widen))))) + (save-excursion + (while (ignore-errors (diff-hl-next-hunk backward)) + (let* ((str (buffer-substring (line-beginning-position) + (line-end-position))) + (no (line-number-at-pos (point))) + (no (consult--line-number-prefix (point-marker) no width))) + (push (concat no str) candidates)))) + (if backward candidates (nreverse candidates)))) + + (defun jao-consult-hunks () + (interactive) + (let ((candidates (append (jao-consult--diff-lines) + (jao-consult--diff-lines t)))) + (unless candidates (error "No changes!")) + (consult--jump + (consult--read candidates + :prompt "Go to hunk: " + :category 'consult--encode-location + :sort nil + :require-match t + :lookup #'consult--line-match + :state (consult--jump-state))))) + + (with-eval-after-load "consult" + (consult-customize '((jao-consult-hunks :preview-key any))) + (global-set-key (kbd "C-x v c") #'jao-consult-hunks)) + #+end_src +*** narrow helpers + #+begin_src emacs-lisp + (defvar jao-consult-narrow nil) + + (defun jao-consult-initial-narrow () + (when-let (c (cond ((eq this-command #'consult-buffer) + (cdr (assoc (jao-afio-current-frame) + jao-consult-narrow))) + ((eq this-command #'consult-mode-command) ?m))) + (setq unread-command-events (append unread-command-events `(,c 32))))) + + (add-hook 'minibuffer-setup-hook #'jao-consult-initial-narrow) + + (defmacro jao-consult--mode-buffers (&rest modes) + `(lambda () + (seq-map #'buffer-name + (seq-filter (lambda (b) + (with-current-buffer b + (derived-mode-p ,@modes))) + (buffer-list))))) + + (defun jao-consult-add-buffer-source (src &optional aframe key) + (add-to-list 'consult-buffer-sources src t) + (when (and aframe key) + (add-to-list 'jao-consult-narrow (cons aframe key)))) + #+end_src +*** narrowing chats + #+begin_src emacs-lisp + (defvar jao-chat-buffer-source + (list :name "chats" + :category 'buffer + :action #'pop-to-buffer + :hidden t + :narrow (cons ?c "chats") + :items (jao-consult--mode-buffers 'erc-mode + 'circe-channel-mode + 'circe-query-mode + 'signel-chat-mode + 'slack-message-buffer-mode + 'slack-thread-message-buffer-mode + 'telega-root-mode + 'telega-chat-mode))) + (with-eval-after-load "consult" + (jao-consult-add-buffer-source 'jao-chat-buffer-source)) + #+end_src + +*** exwm + #+begin_src emacs-lisp :tangle no + (with-eval-after-load "exwm" + (defun consult-exwm-preview-fix (&rest _args) + "Kludge to stop EXWM buffers from stealing focus during Consult previews." + (when-let ((mini (active-minibuffer-window))) + (select-window (active-minibuffer-window)))) + + (advice-add #'consult--buffer-action :after #'consult-exwm-preview-fix)) + + #+end_src +* embark +*** package + #+begin_src emacs-lisp + (use-package embark + :ensure t + :demand t + :init + (setq embark-quit-after-action nil + embark-indicator #'embark-mixed-indicator + embark-verbose-indicator-buffer-sections '(bindings) + embark-mixed-indicator-both t + embark-verbose-indicator-excluded-commands + '(embark-become embark-export embark-collect) + embark-verbose-indicator-nested t + embark-verbose-indicator-display-action + '((display-buffer-at-bottom) + (window-parameters (mode-line-format . none)) + (window-height . fit-window-to-buffer))) + + :bind (("C-;" . embark-act) + ("C-c ;" . embark-act) + ("C-'" . embark-dwim) + ("C-c '" . embark-dwim) + (:map minibuffer-local-map + (("C-'" . embark-dwim) + ("C-c '" . embark-dwim) + ("C-," . embark-become) + ("C-c ," . embark-become) + ("C-o" . embark-export))))) + + (use-package embark-consult + :ensure t + :after (embark consult)) + + (with-eval-after-load 'consult + (with-eval-after-load 'embark + (require 'embark-consult))) + + #+end_src +*** randomsig + #+begin_src emacs-lisp + (defun jao-random-sig-read (_ignored) + "Import region as signature and edit it." + (randomsig-message-read-sig t)) + + (define-key embark-region-map "m" #'jao-random-sig-read) + (define-key embark-region-map "M" #'apply-macro-to-region-lines) + #+end_src +*** dict/say + #+begin_src emacs-lisp + (defun jao-say (&optional word) + "Isn't it nice to have a computer that can talk to you?" + (interactive "sWhat? ") + (shell-command-to-string (format "say %s" word)) + "") + + (define-key embark-identifier-map "D" #'dictionary-search) + (define-key embark-identifier-map "S" #'jao-say) + #+end_src +*** org targets + #+begin_src emacs-lisp + (declare-function org-link-any-re "ol") + (declare-function org-open-link-from-string "ol") + (declare-function org-in-regexp "org-macs") + + (defun jao-embark-targets--org-link () + (when (derived-mode-p 'org-mode) + (when (org-in-regexp org-link-bracket-re) + (let ((lnk (match-string-no-properties 1))) + (if (string-match-p "https?://.+" (or lnk "")) + (cons 'url lnk) + (cons 'org-link (match-string-no-properties 0))))))) + + (embark-define-keymap jao-embark-targets-org-link-map + "Actions for org links" + ((kbd "RET") org-open-link-from-string)) + + (defun jao-embark-targets--gl-org-link () + (when (org-in-regexp org-link-bracket-re) + (cons 'gl-org-link (match-string-no-properties 0)))) + + (embark-define-keymap jao-embark-targets-gl-org-link-map + "Actions for exteranl org links" + ((kbd "RET") org-open-at-point-global)) + + (add-to-list 'embark-target-finders #'jao-embark-targets--gl-org-link) + (add-to-list 'embark-keymap-alist + '(gl-org-link . jao-embark-targets-gl-org-link-map)) + + (add-to-list 'embark-target-finders #'jao-embark-targets--org-link) + (add-to-list 'embark-keymap-alist + '(org-link . jao-embark-targets-org-link-map)) + + #+end_src +*** url targets + #+begin_src emacs-lisp + (declare-function w3m-anchor "w3m") + + (defun jao-embark-targets--w3m-anchor () + (when (not (region-active-p)) + (when-let ((url (or (jao-url-around-point) + (thing-at-point 'url) + (and (derived-mode-p 'w3m-mode) + (or (w3m-anchor) w3m-current-url)) + (and (derived-mode-p 'eww-mode) + (eww-current-url))))) + (when (string-match-p "^https?.*" url) + (cons 'url url))))) + + (add-to-list 'embark-target-finders #'jao-embark-targets--w3m-anchor) + + (defun jao-embark-url (url) + "Browse URL, externally if we're already in an emacs browser." + (if (derived-mode-p 'w3m-mode 'eww-mode) + (jao-browse-with-external-browser url) + (browse-url url))) + + (define-key embark-url-map (kbd "RET") #'jao-embark-url) + (define-key embark-url-map (kbd "f") #'browse-url-firefox) + (define-key embark-url-map (kbd "x") #'jao-rss-subscribe) + (define-key embark-url-map (kbd "m") 'jao-browse-with-external-browser) + (define-key embark-url-map (kbd "p") 'jao-browse-add-url-to-mpc) + + #+end_src +*** video url targets + #+begin_src emacs-lisp + (defvar jao-embark-targets-video-sites + '("youtu.be" "youtube.com" "blip.tv" "vimeo.com" "infoq.com")) + + (defun jao-embark--video-url-rx (&optional sites) + (format "^https?://\\(?:www\\.\\)?%s/.+" + (regexp-opt (or sites jao-embark-targets-video-sites) t))) + + (defvar jao-embark-targets-video-url-rx (jao-embark--video-url-rx) + "A regular expression matching URLs that point to video streams") + + (defun jao-embark-targets--refine-url (_ url) + (if (string-match-p jao-embark-targets-video-url-rx url) + (cons 'video-url url) + (cons 'url url))) + + (defun jao-embark-targets--play-video (player url) + (interactive "sURL: ") + (let ((cmd (format "%s %s" player (shell-quote-argument url)))) + (jao-afio--goto-www) + (start-process-shell-command player nil cmd))) + + (defun jao-embark-targets-mpv (&optional url) + "Play video stream with mpv" + (interactive "sURL: ") + (jao-embark-targets--play-video "mpv" url)) + + (defun jao-embark-targets-vlc (&optional url) + "Play video stream with vlc" + (interactive "sURL: ") + (jao-embark-targets--play-video "vlc" url)) + + (embark-define-keymap jao-embark-targets-video-url-map + "Actions on URLs pointing to remote video streams." + :parent embark-url-map + ("v" jao-embark-targets-vlc) + ("RET" jao-embark-targets-mpv)) + + (add-to-list 'embark-transformer-alist '(url . jao-embark-targets--refine-url)) + (add-to-list 'embark-keymap-alist '(video-url . jao-embark-targets-video-url-map)) + (define-key embark-url-map "v" #'jao-embark-targets-vlc) + (define-key embark-url-map "V" #'jao-embark-targets-vlc) + + #+end_src +*** spotify + #+begin_src emacs-lisp + (with-eval-after-load "consult-spotify" + (defun jao-consult-spt-play (candidate) + (when-let (url (espotify-candidate-url candidate)) + (jao-spt-play-uri url))) + + (embark-define-keymap spotify-item-keymap + "Actions for Spotify search results" + ("s" jao-consult-spt-play) + ("y" espotify-yank-candidate-url) + ("a" espotify-play-candidate-album) + ("h" espotify-show-candidate-info)) + + (add-to-list 'embark-keymap-alist + '(spotify-search-item . spotify-item-keymap))) + #+end_src +* avy and link hints + [[https://karthinks.com/software/avy-can-do-anything/][Avy can do anything | Karthinks]] + #+begin_src emacs-lisp + (use-package avy + :ensure t + :init (setq avy-style 'pre + avy-background t + avy-timeout-seconds 0.6 + avy-single-candidate-jump t) + + :config + + (defun avy-embark-act (pt) + "Use Embark to act on the completion at PT." + (save-excursion + (goto-char pt) + (embark-act))) + (add-to-list 'avy-dispatch-alist '(?\; . avy-embark-act)) + + :bind (("s-j" . avy-goto-char-timer) + ("C-M-j" . avy-goto-char-timer))) + + (use-package link-hint + :ensure t + :init (setq link-hint-avy-style 'pre) + + :config + (defun jao-link-hint-open-link-ext () + (interactive) + (let ((jao-browse-url-function jao-browse-url-external-function)) + (link-hint-open-link))) + + :bind (("C-l" . link-hint-open-link) + ("C-M-l" . jao-link-hint-open-link-ext) + ("C-S-l" . jao-link-hint-open-link-ext) + ("C-x C-l" . recenter-top-bottom))) + + #+end_src +* recoll + #+begin_src emacs-lisp + (jao-load-path "consult-recoll") + + (defun jao-recoll-format (title url mtype) + (let* ((u (replace-regexp-in-string "/home/jao/" "" url)) + (u (replace-regexp-in-string + "\\(doc\\|org/doc\\|.emacs.d/gnus/Mail\\|var/mail\\)/" "" u))) + (format "%s (%s, %s)" + title + (propertize u 'face 'jao-themes-f00) + (propertize mtype 'face 'jao-themes-f01)))) + + (defun jao-recoll-open-html (file) + (jao-afio--goto-www) + (eww-open-file file)) + + (defun jao-recoll-consult-messages () + (interactive) + (consult-recoll "mime:message ")) + + (defun jao-recoll-consult-docs () + (interactive) + (consult-recoll (format "dir:%s/doc " jao-org-dir))) + + (defun jao-recoll-messages () + (interactive) + (jao-recoll "mime:message ")) + + (defun jao-recoll-docs () + (interactive) + (jao-recoll (format "dir:%s/doc " jao-org-dir))) + + (defun jao-recoll-notes () + (interactive) + (jao-recoll (format "dir:%s " jao-org-notes-dir))) + + (defun jao-recoll-consult-notes () + "Use consult-recoll to search notes." + (interactive) + (consult-recoll (format "dir:%s " jao-org-notes-dir))) + + (defun jao-recoll-open-with-notmuch (fname) + (let ((id (with-temp-buffer + (insert-file fname) + (goto-char (point-min)) + (when (re-search-forward "[Mm]essage-[Ii][Dd]: <]+\\)>?" + nil t) + (match-string 1))))) + (when id (notmuch-show (concat "id:" id))))) + + (use-package consult-recoll + :init (setq consult-recoll-open-fns + '(("application/pdf" . jao-open-doc) + ("message/rfc822" . jao-recoll-open-with-notmuch) + ("text/html" . jao-recoll-open-html)) + consult-recoll-search-flags nil + consult-recoll-format-candidate #'jao-recoll-format) + :config + (transient-define-prefix jao-recoll-transient () + [["Consult recoll queries" + ("r" "consult recoll query" consult-recoll) + ("n" "consult recoll on notes" jao-recoll-consult-notes) + ("d" "consult recoll on docs" jao-recoll-consult-docs) + ("m" "consult recoll on messages" jao-recoll-consult-messages)] + ["Recoll queries" + ("R" "recoll query" jao-recoll) + ("N" "recoll on notes" jao-recoll-notes) + ("D" "consult recoll on docs" jao-recoll-docs) + ("M" "consult recoll on messages" jao-recoll-messages)]]) + + :bind (("s-r" . #'jao-recoll-transient))) + + #+end_src diff --git a/attic/orgs/counsel.org b/attic/orgs/counsel.org new file mode 100644 index 0000000..f6814ae --- /dev/null +++ b/attic/orgs/counsel.org @@ -0,0 +1,337 @@ +#+title: Completion configuration using ivy, counsel and friends + +* selectrum + #+begin_src emacs-lisp :load no + (use-package selectrum + :ensure t + :init + (defun jao-selectrum--ord-refine (&rest args) + (let ((completion-styles '(orderless))) + (apply #'selectrum-refine-candidates-using-completions-styles args))) + + (defun jao-selectrum-orderless () + (interactive) + (setq selectrum-refine-candidates-function #'jao-selectrum--ord-refine) + (setq selectrum-highlight-candidates-function #'orderless-highlight-matches) + (setq orderless-skip-highlighting (lambda () selectrum-is-active))) + + + :config + ;; https://github.com/raxod502/selectrum/wiki/Ido,-icomplete(fido)-emulation + (defun selectrum-fido-backward-updir () + "Delete char before or go up directory, like `ido-mode'." + (interactive) + (if (and (eq (char-before) ?/) + (eq (selectrum--get-meta 'category) 'file)) + (save-excursion + (goto-char (1- (point))) + (when (search-backward "/" (point-min) t) + (delete-region (1+ (point)) (point-max)))) + (call-interactively 'backward-delete-char))) + + (defun selectrum-fido-delete-char () + "Delete char or maybe call `dired', like `ido-mode'." + (interactive) + (let ((end (point-max))) + (if (or (< (point) end) (not (eq (selectrum--get-meta 'category) 'file))) + (call-interactively 'delete-char) + (dired (file-name-directory (minibuffer-contents))) + (exit-minibuffer)))) + + (defun selectrum-fido-ret () + "Exit minibuffer or enter directory, like `ido-mode'." + (interactive) + (let* ((dir (and (eq (selectrum--get-meta 'category) 'file) + (file-name-directory (minibuffer-contents)))) + (current (selectrum-get-current-candidate)) + (probe (and dir current + (expand-file-name (directory-file-name current) dir)))) + (if (and probe (file-directory-p probe) (not (string= current "./"))) + (selectrum-insert-current-candidate) + (selectrum-select-current-candidate)))) + + ;; (define-key selectrum-minibuffer-map (kbd "RET") 'selectrum-fido-ret) + (define-key selectrum-minibuffer-map (kbd "DEL") 'selectrum-fido-backward-updir) + (define-key selectrum-minibuffer-map (kbd "C-d") 'selectrum-fido-delete-char) + + :custom ((selectrum-complete-in-buffer t) + ;; (selectrum-display-action '(display-buffer-at-bottom)) + (selectrum-extend-current-candidate-highlight t) + (selectrum-fix-vertical-window-height nil) + (selectrum-max-window-height 20) + (selectrum-show-indices nil) + (selectrum-count-style 'current/matches)) + :bind (("C-R" . selectrum-repeat))) + #+end_src +* ivy + #+begin_src emacs-lisp + (use-package ivy + :ensure t + :demand t + :custom + ((ivy-count-format "(%d/%d) ") + (ivy-do-completion-in-region t) + (ivy-height 20) + (ivy-re-builders-alist '((counsel-ag . ivy--regex) + (counsel-rg . ivy--regex) + (counsel-yank-pop . ivy--regex) + (swiper . ivy--regex) + (swiper-isearch . ivy--regex) + (t . ivy--regex-fuzzy))) + (ivy-use-virtual-buffers t) + (ivy-virtual-abbreviate 'abbreviate) + (ivy-wrap t)) + + :config + ;; used by ivy--regex-fuzzy to order results + (use-package flx :ensure t) + + ;; Try C-o in the minibuffer + (use-package ivy-hydra + :after ivy + :ensure t + :init (setq ivy-read-action-function #'ivy-hydra-read-action)) + + (add-to-list 'ivy-initial-inputs-alist + '(gnus-summary-move-article . "")) + + :bind (("C-R" . ivy-resume) + ("C-x b" . ivy-switch-buffer) + ("C-c v" . ivy-push-view) + ("C-c V" . ivy-pop-view)) + :diminish) + #+end_src +* counsel + #+begin_src emacs-lisp + (use-package counsel + :ensure t + :custom ((counsel-describe-function-function 'helpful-callable) + (counsel-describe-variable-function 'helpful-variable) + (counsel-find-file-at-point t) + (counsel-linux-app-format-function + #'counsel-linux-app-format-function-name-pretty) + (counsel-mode-override-describe-bindings nil) + (counsel-recentf-include-xdg-list t)) + :config + :bind (("C-s" . swiper-isearch) + ("C-S-s" . isearch-forward) + ("M-x" . counsel-M-x) + ("C-x f" . counsel-find-file) + ("C-c k" . counsel-ag) + ("C-c K" . counsel-rg) + ("C-c l" . counsel-locate) + ("C-c b" . counsel-git) + ("C-c i" . counsel-imenu) + ("C-c G" . counsel-search) + ("s-r" . counsel-linux-app)) + :diminish) + #+end_src +* counsel add-ons +*** notmuch + #+begin_src emacs-lisp + (use-package counsel-notmuch + :ensure t + :config (with-eval-after-load "gnus-group" + (define-key gnus-group-mode-map "Gg" 'counsel-notmuch))) + #+end_src +*** recoll + #+begin_src emacs-lisp + (require 'jao-recoll) + (defvar jao-counsel-recoll--history nil) + (defun jao-counsel-recoll--function (str) + (let ((xs (counsel-recoll-function str))) + (cl-remove-if-not (lambda (x) (string-prefix-p "file://" x)) xs))) + + (defun jao-counsel-recoll (&optional initial-input) + (interactive) + (counsel-require-program "recoll") + (ivy-read "recoll: " 'jao-counsel-recoll--function + :initial-input initial-input + :dynamic-collection t + :history 'jao-counsel-recoll--history + :action (lambda (x) + (when (string-match "file://\\(.*\\)\\'" x) + (let ((file-name (match-string 1 x))) + (if (string-match "pdf$" x) + (jao-open-doc file-name) + (find-file file-name))))) + :unwind #'counsel-delete-process + :caller 'jao-counsel-recoll)) + + (defun jao-counsel-recoll--recoll (_s) (jao-recoll ivy-text)) + + (ivy-set-actions 'jao-counsel-recoll + '(("x" jao-counsel-recoll--recoll "List in buffer"))) + + (global-set-key (kbd "C-c R") #'jao-counsel-recoll) + #+end_src +* ivy rich + #+begin_src emacs-lisp + (use-package ivy-rich + :after (ivy counsel) + :ensure t + :custom ((ivy-rich-path-style 'relative) + (ivy-rich-parse-remote-buffer nil) + (ivy-rich-parse-remote-file-path nil)) + :config + (ivy-rich-modify-columns + 'ivy-switch-buffer + '((ivy-rich-candidate (:width 80)) + (ivy-rich-switch-buffer-indicators (:face jao-themes-f00)) + (ivy-rich-switch-buffer-project (:width 15)) + (ivy-rich-switch-buffer-major-mode (:width 15 :face jao-themes-f12))))) + #+end_src +* cmap + #+begin_src emacs-lisp + (jao-load-path "cmap") + (use-package cmap + :demand t + :bind (("C-;" . cmap-cmap) + ("C-'" . cmap-default))) + #+end_src +*** prompter + #+begin_src emacs-lisp + (defun jao-cmap--hide-help () + (when-let ((w (get-buffer-window (help-buffer)))) + (with-selected-window w (kill-buffer-and-window)))) + + (defun jao-cmap--prompter (keymap) + (let ((display-buffer-alist '(("*Help*" + (display-buffer-at-bottom) + (window-parameters (mode-line-format . none)) + (window-height . fit-window-to-buffer))))) + (let ((inhibit-message t)) + (describe-keymap keymap)))) + + (defun jao-cmap--prompter-done () + (save-current-buffer (jao-cmap--hide-help))) + + (setq cmap-prompter #'jao-cmap--prompter) + (setq cmap-prompter-done #'jao-cmap--prompter-done) + #+end_src +*** minibuffer actions + #+begin_src emacs-lisp + (defun jao-cmap--completion-metadata () + (completion-metadata + (buffer-substring-no-properties (field-beginning) (point)) + minibuffer-completion-table + minibuffer-completion-predicate)) + + (defun jao-cmap--completion-category () + (completion-metadata-get (jao-cmap--completion-metadata) 'category)) + + (defmacro cmap-define-keymap (v d &rest b) + `(defvar ,v (cmap-keymap ,@b) ,d)) + + (cmap-define-keymap jao-cmap-buffer-map + "Keymap for buffer actions." + ("k" . kill-buffer) + ("b" . switch-to-buffer) + ("o" . switch-to-buffer-other-window) + ("z" . bury-buffer) + ("q" . kill-buffer-and-window) + ("=" . ediff-buffers)) + + ;; (cmap-define-keymap espotify-item-keymap + ;; "Actions for Spotify search results" + ;; ("a" espotify--play-album) + ;; ("h" espotify--show-info)) + + (defvar jao-cmap--smaps + '((command . cmap-command-map) + ;; (espotify-search-item . espotify-item-keymap) + (function . cmap-function-map) + (variable . cmap-variable-map) + (face . cmap-face-map) + (buffer . jao-cmap-buffer-map) + (consult-buffer . jao-cmap-buffer-map))) + + (defun jao-cmap-target-minibuffer-candidate () + (when (minibuffer-window-active-p (selected-window)) + (let ((cand (ivy-state-current ivy-last)) + (cat (jao-cmap--completion-category))) + (when-let (m (alist-get cat jao-cmap--smaps)) + (cons m cand))))) + + (add-to-list 'cmap-targets #'jao-cmap-target-minibuffer-candidate) + #+end_src +*** url / video actions + #+begin_src emacs-lisp + (defvar jao-cmap-video-url-rx + (format "^https?://\\(?:www\\.\\)?%s/.+" + (regexp-opt '("youtu.be" + "youtube.com" + "blip.tv" + "vimeo.com" + "infoq.com") + t)) + "A regular expression matching URLs that point to video streams") + + (defun jao-cmap--play-video (player url) + (interactive "sURL: ") + (let ((cmd (format "%s %s" player (shell-quote-argument url)))) + (start-process-shell-command player nil cmd))) + + (defun jao-cmap-mpv (&optional url) + "Play video stream with mpv" + (interactive "sURL: ") + (jao-cmap--play-video "mpv" url)) + + (defun jao-cmap-vlc (&optional url) + "Play video stream with vlc" + (interactive "sURL: ") + (jao-cmap--play-video "vlc" url)) + + (defun jao-cmap-target-w3m-url () + (when-let (url (or (thing-at-point-url-at-point) + (w3m-anchor) + w3m-current-url)) + (cons 'cmap-url-map url))) + + (defun jao-cmap-kill (&optional x) + "Save to kill ring" + (interactive "s") + (kill-new x)) + + (defun jao-cmap-url (url) + "Browse URL, externally if we're already in emacs-w3m" + (if (derived-mode-p 'w3m-mode) + (jao-browse-with-external-browser url) + (browse-url url))) + + (define-key cmap-url-map [return] #'jao-cmap-url) + (define-key cmap-url-map "f" #'browse-url-firefox) + (define-key cmap-url-map "w" #'jao-cmap-kill) + + (defun jao-cmap-target-video-url () + (when-let (url (jao-cmap-target-w3m-url)) + (when (string-match-p jao-cmap-video-url-rx (cdr url)) + (cons 'jao-cmap-video-url-map (cdr url))))) + + (cmap-define-keymap jao-cmap-video-url-map + "Actions on URLs pointing to remote video streams." + ("v" . jao-cmap-vlc) + ([return] . jao-cmap-mpv)) + + (add-to-list 'cmap-targets #'jao-cmap-target-w3m-url) + (add-to-list 'cmap-targets #'jao-cmap-target-video-url) + #+end_src +* hooks + #+begin_src emacs-lisp + (with-eval-after-load "exwm" + (add-to-list 'exwm-input-global-keys '([?\s-r] . counsel-linux-app))) + + (with-eval-after-load "espotify" + (require 'ivy-spotify) + (defalias 'jao-spotify-album #'ivy-spotify-album) + (defalias 'jao-spotify-track #'ivy-spotify-track) + (defalias 'jao-spotify-artist #'ivy-spotify-artist) + (defalias 'jao-spotify-playlist #'ivy-spotify-playlist)) + #+end_src +* startup + #+begin_src emacs-lisp + (ivy-mode 1) + (counsel-mode 1) + (ivy-rich-mode 1) + (ivy-rich-project-root-cache-mode 1) + #+end_src diff --git a/attic/orgs/email.org b/attic/orgs/email.org new file mode 100644 index 0000000..e8a8a71 --- /dev/null +++ b/attic/orgs/email.org @@ -0,0 +1,391 @@ +#+property: header-args:emacs-lisp :lexical t :tangle yes :comments yes :results silent :shebang ";; -*- lexical-binding: t; -*-" :tangle-mode (identity #o644) +#+title: email handling (message mode, bbdb, gnus, notmuch) + +* personal emails and others + #+begin_src emacs-lisp + (defvar jao-mails) + (defvar jao-extra-mails) + (defvar jao-mails-regexp (regexp-opt jao-mails)) + (defvar jao-notmuch-enabled (eq jao-afio-mail-function 'notmuch)) + #+end_src +* gnus + #+begin_src emacs-lisp + (setq gnus-init-file "~/.emacs.d/gnus.el" + gnus-home-directory "~/.emacs.d/gnus" + gnus-directory gnus-home-directory + gnus-cache-directory (expand-file-name "cache" gnus-home-directory) + gnus-kill-files-directory (expand-file-name "News" gnus-home-directory) + message-directory (expand-file-name "Mail" gnus-home-directory) + mail-source-directory (expand-file-name "Mail" gnus-home-directory)) + + (let ((org-file (expand-file-name "gnus.org" jao-emacs-dir))) + (when (file-newer-than-file-p org-file gnus-init-file) + (org-babel-tangle-file org-file))) + + #+end_src +* message mode +*** Customization + #+begin_src emacs-lisp + (setq message-send-mail-function 'message-send-mail-with-sendmail + message-sendmail-envelope-from 'header + message-sendmail-f-is-evil nil) + (setq imap-store-password t) + (setq password-cache-expiry nil) + (setq message-generate-headers-first t) + (setq message-forward-before-signature nil) + (setq message-alternative-emails + (regexp-opt (append jao-mails jao-extra-mails))) + (setq message-dont-reply-to-names + (regexp-opt (append jao-mails '("noreply@" "@noreply" + "no-reply@" "@no-reply" + "notifications@github")))) + (setq message-citation-line-format "On %a, %b %d %Y, %N wrote:\n") + (setq message-citation-line-function 'message-insert-formatted-citation-line) + + (setq message-user-fqdn "gnus.jao.io") + + ;; writing messages + (setq message-kill-buffer-on-exit t) + (setq message-max-buffers 5) + (setq message-insert-signature t) + (setq message-from-style 'angles + user-mail-address (car jao-mails) + mail-host-address system-name + message-syntax-checks '((sender . disabled)) + message-default-headers + (concat + "X-Attribution: jao\n" + "X-Clacks-Overhead: GNU Terry Pratchett\n" + "X-URL: \n") + message-hidden-headers + '("^References:" "^Face:" "^X-Face:" "^X-Draft-From:") + message-make-forward-subject-function 'message-forward-subject-fwd) + + (setq message-expand-name-standard-ui t) + #+end_src +*** Adjust Bcc + #+begin_src emacs-lisp + (defvar jao-message--bcc-rx (regexp-opt '("mail.io" "gnu.org"))) + + (defun jao-message-insert-bcc () + (let ((f (or (message-fetch-field "From") ""))) + (when (or (string-blank-p f) (string-match-p jao-message--bcc-rx f)) + (insert "Bcc: proton@jao.io\n")))) + + (add-hook 'message-header-setup-hook #'jao-message-insert-bcc) + #+end_src +*** To->From and Bcc + #+begin_src emacs-lisp + (defvar jao-message-to-from nil) + + (defun jao-message-adjust-from () + (let ((to (concat (message-fetch-field "To") (message-fetch-field "Cc")))) + (when-let* ((tf (seq-find (lambda (tf) (string-match-p (car tf) to)) + jao-message-to-from)) + (from (message-make-from "Jose A Ortega Ruiz" (cdr tf)))) + (save-restriction + (widen) + (message-replace-header "From" from))))) + + (when jao-notmuch-enabled + (add-hook 'message-header-setup-hook #'jao-message-adjust-from)) + #+end_src +*** Clean up reply addresses + #+begin_src emacs-lisp :tangle no + (defun jao-message--dont-reply () + (let ((x (message-dont-reply-to-names))) + (if (functionp x) + x + (lambda (mail) (unless (string-match-p x mail) mail))))) + + (defun jao-message-strip-replies () + (dolist (header '("To" "Cc")) + (when-let ((v (message-fetch-field header))) + (let* ((v (message-tokenize-header v)) + (vs (delq nil (mapcar (jao-message--dont-reply) v))) + (v (when vs (mapconcat #'string-trim vs ", ")))) + (message-replace-header header v))))) + + (when jao-notmuch-enabled + (add-hook 'message-setup-hook #'jao-message-strip-replies)) + + #+end_src +*** Encryption + #+begin_src emacs-lisp + ;; avoiding bogus warning + (setq gnutls-min-prime-bits nil) + (setq gnus-buttonized-mime-types + '("multipart/encrypted" "multipart/signed" "multipart/alternative")) + + (setq mm-verify-option 'always) + (setq mm-decrypt-option 'always) + + (setq mm-sign-option 'guided) + (setq mm-encrypt-option 'guided) + + (setq mml-secure-passphrase-cache-expiry (* 3600 24) + password-cache-expiry (* 3600 24)) + + (setq smime-CA-directory "/etc/ssl/certs/" + smime-certificate-directory + (expand-file-name "certs/" gnus-directory)) + + (with-eval-after-load "mm-decode" + ;; Tells Gnus to inline the part + (add-to-list 'mm-inlined-types "application/pgp$") + ;; Tells Gnus how to display the part when it is requested + (add-to-list 'mm-inline-media-tests '("application/pgp$" + mm-inline-text identity)) + ;; Tell Gnus not to wait for a request, just display the thing + ;; straight away. + (add-to-list 'mm-automatic-display "application/pgp$") + ;; But don't display the signatures, please. + (setq mm-automatic-display (remove "application/pgp-signature" + mm-automatic-display))) + + ;; decide whether to encrypt or just sign outgoing messages + (defvar jao-message-try-sign nil) + (defun jao-message-maybe-sign () + (when (and jao-message-try-sign (y-or-n-p "Sign message? ")) + (if (y-or-n-p "Encrypt message? ") + (let ((recipient (message-fetch-field "To"))) + (if (or (pgg-lookup-key recipient) + (and (y-or-n-p (format "Fetch %s's key? " recipient)) + (pgg-fetch-key pgg-default-keyserver-address + recipient))) + (mml-secure-message-encrypt-pgp) + (mml-secure-message-sign-pgp))) + (mml-secure-message-sign-pgp)))) + + ;; for ma gnus + (eval-after-load "rfc2047" + '(add-to-list 'rfc2047-header-encoding-alist + '("User-Agent" . address-mime))) + #+end_src +*** Attach image to message + Use ~C-c C-p~ in message-mode, and ~C-c y~. +*** Check attachment + #+begin_src emacs-lisp + (defvar jao-message-attachment-regexp "\\([Ww]e send\\|[Ii] send\\|attach\\)") + (defun jao-message-check-attachment () + "Check if there is an attachment in the message if I claim it." + (save-excursion + (message-goto-body) + (when (search-forward-regexp jao-message-attachment-regexp nil t nil) + (message-goto-body) + (unless (or (search-forward "<#part" nil t nil) + (message-y-or-n-p + "No attachment. Send the message? " nil nil)) + (error "No message sent"))))) + #+end_src +*** Check Fcc/Gcc + #+begin_src emacs-lisp + (defun jao-message-check-gcc () + "Ask whether to keep a copy of message." + (save-excursion + (save-restriction + (message-narrow-to-headers) + (when (and (or (message-fetch-field "Gcc") + (message-fetch-field "Fcc")) + (not (y-or-n-p "Archive? "))) + (message-remove-header "\\(?:[BFG]cc\\)"))))) + #+end_src +*** Check recipient + #+begin_src emacs-lisp + (defun jao-message-check-recipient () + (save-excursion + (save-restriction + (message-narrow-to-headers) + (when-let ((to (message-fetch-field "To"))) + (when (string-match-p jao-mails-regexp to) + (unless (y-or-n-p "Message is addressed to yourself. Continue?") + (error "Message not sent"))))))) + #+end_src +*** Randomsig + #+begin_src emacs-lisp + (with-eval-after-load "message" + (when (require 'randomsig nil t) + (define-key message-mode-map (kbd "C-c s") 'randomsig-replace-sig) + (define-key message-mode-map (kbd "C-c S") 'randomsig-select-sig) + (setq randomsig-dir (expand-file-name "~/etc/config/emacs")) + (setq randomsig-files '("signatures.txt")) + ;; or (setq randomsig-files (randomsig-search-sigfiles)) + ;; or (setq randomsig-files 'randomsig-search-sigfiles) + (setq message-signature 'randomsig-signature) + (setq randomsig-delimiter-pattern "^%$" + randomsig-delimiter "%"))) + #+end_src +*** Send mail hooks + #+begin_src emacs-lisp + (dolist (h '(jao-message-check-gcc + jao-message-check-recipient + jao-message-maybe-sign)) + (add-hook 'message-send-hook h)) + (unless jao-notmuch-enabled + (add-hook 'message-send-hook #'jao-message-check-attachment)) + #+end_src +*** Keybindings + #+begin_src emacs-lisp + (with-eval-after-load "message" + ;; (define-key message-mode-map [f7] 'mml-secure-message-sign-pgp) + (define-key message-mode-map [f8] 'mml-secure-message-encrypt-pgp) + (define-key message-mode-map (kbd "C-c y") #'yank-media)) + #+end_src +* sendmail/smtp + #+begin_src emacs-lisp + (defun jao-sendmail-gmail () + (setq smtpmail-auth-supported '(login cram-md5 plain)) + (setq smtpmail-smtp-server "smtp.gmail.com") + (setq smtpmail-smtp-service 587)) + + (defun jao-sendmail-local () + (setq send-mail-function 'sendmail-send-it) + (setq smtpmail-auth-supported nil) ;; (cram-md5 plain login) + (setq smtpmail-smtp-server "127.0.0.1") + (setq smtpmail-smtp-service 25)) + + (defun jao-sendmail-msmtp () + (setq send-mail-function 'sendmail-send-it + sendmail-program "/usr/bin/msmtp" + mail-specify-envelope-from t + message-sendmail-envelope-from 'header + mail-envelope-from 'header)) + + (jao-sendmail-local) + #+end_src +* mailcap + #+begin_src emacs-lisp + (use-package mailcap + :config + (add-to-list 'mailcap-mime-extensions '(".JPEG" . "image/jpeg")) + (add-to-list 'mailcap-mime-extensions '(".JPG" . "image/jpeg")) + + (defun jao-icalendar-import-buffer () + (let ((icalendar-import-format "%s%u%l%d")) + (icalendar-import-buffer diary-file t nil)) + (kill-buffer) + (message "Event imported into diary")) + + :custom + ((mailcap-user-mime-data + '((jao-icalendar-import-buffer "application/ics") + ("viewpdf.sh %s" "application/pdf"))))) + #+end_src +* multipart html renderer + #+begin_src emacs-lisp + (defun jao-w3m-html-renderer (handle) + (let ((w3m-message-silent t) + (mm-w3m-safe-url-regexp nil)) + (condition-case nil + (mm-inline-text-html-render-with-w3m handle) + (error (delete-region (point) (point-max)) + (let ((shr-use-fonts nil) + (shr-use-colors nil)) + (mm-shr handle)))))) + + (defun jao-shr-html-renderer (handle) + (let (;; (shr-use-fonts t) + ;; (shr-use-colors t) + (shr-width 130)) + (mm-shr handle))) + + ;; (setq mm-text-html-renderer #'jao-w3m-html-renderer) + (setq mm-text-html-renderer #'jao-shr-html-renderer) + #+end_src +* bbdb + #+begin_src emacs-lisp :tangle no + (use-package bbdb + :ensure t + :init (setq bbdb-complete-name-allow-cycling t + bbdb-completion-display-record nil + bbdb-gui t + bbdb-message-all-addresses t + bbdb-complete-mail-allow-cycling t + bbdb-north-american-phone-numbers-p nil + bbdb-add-aka t + bbdb-add-name 2 + bbdb-message-all-addresses t + bbdb-mua-pop-up t ;; 'horiz + bbdb-mua-pop-up-window-size 0.3 + bbdb-layout 'multi-line + bbdb-mua-update-interactive-p '(query . create) + bbdb-mua-auto-update-p 'bbdb-select-message + bbdb-user-mail-address-re jao-mails-regexp + bbdb-auto-notes-ignore-headers + `(("From" . ,jao-mails-regexp) + ("From" . ".*@.*github\.com.*") + ("To" . ".*@.*github\.com.*") + ("Reply-to" . ".*") + ("References" . ".*")) + bbdb-auto-notes-ignore-messages + `(("To" . ".*@.*github\\.com.*") + ("From" . ".*@.*github\\.com.*") + ("From" . "info-list") + ("From" . "no-?reply\\|deploy") + ("X-Mailer" . "MailChimp")) + bbdb-accept-message-alist + `(("To" . ,jao-mails-regexp) + ("Cc" . ,jao-mails-regexp) + ("BCc" . ,jao-mails-regexp)) + bbdb-ignore-message-alist bbdb-auto-notes-ignore-messages) + :config + (add-hook 'message-setup-hook 'bbdb-mail-aliases) + ;; (add-hook 'bbdb-notice-mail-hook 'bbdb-auto-notes) + (add-hook 'bbdb-after-change-hook (lambda (arg) (bbdb-save))) + (require 'bbdb-anniv) ;; BBDB 3.x this gets birthdays in org agenda + ;; and diary - clever stuff + (add-hook 'diary-list-entries-hook 'bbdb-anniv-diary-entries) + + (setq bbdb-file (expand-file-name "~/.emacs.d/bbdb")) + (if jao-notmuch-enabled + (bbdb-initialize 'message 'pgp) + (bbdb-initialize 'message 'pgp 'gnus))) + #+end_src +* mailboxes + #+begin_src emacs-lisp + (defun jao-list-mailboxes (base &rest excl) + (let ((dir (expand-file-name base "~/var/mail"))) + (seq-difference (directory-files dir) (append '("." "..") excl)))) + + + #+end_src +* consult narrowing + #+begin_src emacs-lisp + (defvar jao-mail-consult-buffer-history nil) + + (defun jao-mail-buffer-p (b) + (or (member (buffer-name b) + '("*Calendar*" "inbox.org" "*Org Agenda*" + "*Fancy Diary Entries*" "diary")) + (with-current-buffer b + (derived-mode-p 'notmuch-show-mode + 'notmuch-search-mode + 'notmuch-tree-mode + 'notmuch-hello-mode + 'notmuch-message-mode + 'gnus-group-mode + 'gnus-summary-mode + 'gnus-article-mode)))) + + (defvar jao-mail-consult-source + (list :name "mail buffer" + :category 'buffer + :hidden t + :narrow (cons ?n "mail buffer") + :history 'jao-mail-consult-buffer-history + :action (lambda (b) + (when (not (string-blank-p (or b ""))) + (jao-afio--goto-mail) + (if (get-buffer-window b) + (pop-to-buffer b) + (pop-to-buffer-same-window b)))) + :items (lambda () + (mapcar #'buffer-name + (seq-filter #'jao-mail-buffer-p (buffer-list)))))) + + (jao-consult-add-buffer-source 'jao-mail-consult-source "Mail" ?n) + #+end_src +* notmuch + #+begin_src emacs-lisp + (jao-load-org "notmuch") + #+end_src diff --git a/attic/orgs/eww.org b/attic/orgs/eww.org new file mode 100644 index 0000000..4a9dd71 --- /dev/null +++ b/attic/orgs/eww.org @@ -0,0 +1,191 @@ +#+property: header-args :lexical t :tangle yes :comments yes :results silent :shebang ";; -*- lexical-binding: t -*-" :tangle-mode (identity #o644) +#+title: Web browsing using eww + +* Integration with browse-url and afio + #+begin_src emacs-lisp + (defun jao-eww-browse-url (url &rest r) + "Browse URL using eww." + (if (derived-mode-p 'eww-mode) + (eww url) + (jao-afio--goto-www) + (select-window (frame-first-window)) + (let* ((url (url-encode-url url)) + (bf (seq-find `(lambda (b) + (with-current-buffer b + (string= ,url + (url-encode-url (eww-current-url))))) + (jao-eww-session--list-buffers)))) + (cond (bf (switch-to-buffer bf)) + ((string-match-p url "^file://") (eww-open-file url)) + (t (eww url 4)))))) + + (setq jao-browse-url-function #'jao-eww-browse-url) + (setq eww-use-browse-url "^\\(gemini\\|gopher\\):") + #+end_src +* Opening URLs + #+begin_src emacs-lisp + (defun jao-eww-copy-link () + (interactive) + (when-let (lnk (or (car (eww-links-at-point)) (eww-current-url))) + (message "%s" lnk) + (kill-new lnk))) + + (defun jao-eww-browse (arg) + (interactive "P" eww-mode) + (setq eww-prompt-history + (cl-remove-duplicates eww-prompt-history :test #'string=)) + (let ((url (completing-read (if arg "eww in new buffer: " "eww: ") + eww-prompt-history nil nil nil + 'eww-prompt-history (eww-current-url)))) + (eww url (when arg 4)))) + + (defun jao-eww-browse-new () + (interactive nil eww-mode) + (jao-eww-browse t)) + + (defun jao-eww-reload (images) + (interactive "P" eww-mode) + (if images + (let ((shr-blocked-images nil)) + (eww-reload t)) + (call-interactively 'eww-reload))) + #+end_src +* Consult narrowing + #+begin_src emacs-lisp + (with-eval-after-load "consult" + (defvar jao-eww-consult-history nil) + (defvar jao-eww-buffer-source + (list :name "eww buffer" + :category 'eww-buffer + :hidden t + :narrow (cons ?e "eww") + :annotate (lambda (c) (get-text-property 0 'url c)) + :history 'jao-eww-consult-history + :action (lambda (b) + (jao-afio--goto-www) + (switch-to-buffer (get-text-property 0 'buffer b))) + :items + (lambda () + (seq-map (lambda (b) + (with-current-buffer b + (let ((tl (or (plist-get eww-data :title) "")) + (url (or (eww-current-url) (buffer-name)))) + (propertize (if (string-blank-p tl) url tl) + 'buffer b 'url url)))) + (seq-filter #'jao-www--buffer-p (buffer-list)))))) + (jao-consult-add-buffer-source 'jao-eww-buffer-source "Web" ?e)) + #+end_src +* Images + #+begin_src emacs-lisp + (defun jao-eww-next-image () + (interactive nil eww-mode) + (when-let (p (text-property-search-forward 'image-displayer nil nil t)) + (goto-char (prop-match-beginning p)))) + #+end_src +* Close page and reopen + #+begin_src emacs-lisp + (defvar jao-eww--closed-urls ()) + + (defun jao-eww-close () + (interactive nil eww-mode) + (when-let (current (eww-current-url)) + (add-to-list 'jao-eww--closed-urls current)) + (let ((nxt (car (jao-eww-session-invisible-buffers)))) + (kill-current-buffer) + (when nxt (switch-to-buffer nxt nil t)))) + + (defun jao-eww-reopen (arg) + (interactive "P") + (if (> (length jao-eww--closed-urls) 0) + (let ((url (completing-read "URL: " jao-eww--closed-urls))) + (jao-afio--goto-www) + (setq jao-eww--closed-urls (remove url jao-eww--closed-urls)) + (eww url (when arg 4))) + (message "No previously closed URLs."))) + + (defun jao-eww-reopen-new () + (interactive) + (jao-eww-reopen t)) + #+end_src +* Sessions + #+begin_src emacs-lisp + (use-package jao-eww-session + :custom ((jao-eww-session-file "~/.emacs.d/cache/eww-session.eld"))) + #+end_src +* Package + #+begin_src emacs-lisp + (use-package shr + :custom ((shr-width nil) + (shr-use-colors t) + (shr-use-fonts t) + (shr-max-width 130) + (shr-blocked-images nil) + (shr-inhibit-images t) + (shr-max-image-proportion 1.0) + (shr-hr-line ?―))) + + (use-package eww + :demand t + :custom ((eww-browse-url-new-window-is-tab nil) + (eww-download-directory jao-sink-dir) + (eww-header-line-format " %u") + (eww-form-checkbox-selected-symbol "☒") + (eww-buffer-name-length 180)) + + :config + (with-eval-after-load "org" (require 'ol-eww nil t)) + + (defun jao-eww-buffer-name () + (when-let ((s (or (plist-get eww-data :title) + (plist-get eww-data :url)))) + (when (not (string-blank-p s)) (format "%s" s)))) + (setq eww-auto-rename-buffer #'jao-eww-buffer-name) + + :bind (:map eww-mode-map (("b" . eww-back-url) + ("B" . eww-forward-url) + ("d" . jao-download) + ("f" . link-hint-open-link) + ("F" . embark-on-link) + ("L" . eww-forward-url) + ("N" . jao-eww-next-image) + ("o" . jao-eww-browse) + ("O" . jao-eww-browse-new) + ("r" . jao-eww-reload) + ("s" . eww-search-words) + ("S" . jao-eww-browse-new) + ("u" . jao-eww-reopen) + ("U" . jao-eww-reopen-new) + ("w" . org-eww-copy-for-org-mode) + ("q" . jao-eww-close) + ("x" . jao-rss-subscribe) + ("y" . jao-eww-copy-link) + ("\\" . eww-view-source) + ("C-c C-w" . jao-eww-close) + ("M-i" . eww-toggle-images)))) + + #+end_src +* Fixes for shr image rendering + #+begin_src emacs-lisp + (require 'shr) + + (defun jao-shr--kill-nl (p) + (save-excursion + (goto-char p) + (when (looking-at-p "\n") (delete-char 1)))) + + (defun jao-shr-tag-img (fn &rest args) + (let ((p (point))) + (prog1 (apply fn args) + (when (> (point) p) (jao-shr--kill-nl p))))) + + (defun jao-shr-insert (fn &rest args) + (let ((p (when (and (not (bolp)) + (get-text-property (1- (point)) 'image-url)) + (point)))) + (prog1 (apply fn args) + (when (and p (> (point) p)) (jao-shr--kill-nl p))))) + + (advice-add 'shr-tag-img :around #'jao-shr-tag-img) + (advice-add 'shr-insert :around #'jao-shr-insert) + + #+end_src diff --git a/attic/orgs/exwm.org b/attic/orgs/exwm.org new file mode 100644 index 0000000..65db454 --- /dev/null +++ b/attic/orgs/exwm.org @@ -0,0 +1,551 @@ +#+property: header-args :lexical t :tangle yes :comments yes :results silent :shebang ";; -*- lexical-binding: t; -*-" :tangle-mode (identity #o644) +#+title: exwm configuration +#+auto_tangle: t + +* Load and basic config + #+begin_src emacs-lisp + (defvar jao-exwm--use-afio t) + + (jao-load-path "exwm") + + (use-package exwm + :ensure t + :init (setq exwm-debug nil + exwm-workspace-number 1 + exwm-workspace-show-all-buffers t + exwm-workspace-warp-cursor nil + exwm-layout-show-all-buffers t + exwm-floating-border-color + (if (jao-colors-scheme-dark-p) "black" "grey90"))) + + (use-package exwm-edit :ensure t) + (require 'exwm) + #+end_src +* Frame(s) as workspaces + #+begin_src emacs-lisp + (defun jao-exwm--new-frame-p () + (not (frame-parameter nil 'jao-frames-initialized))) + + (defun jao-exwm--mark-frame (force) + (prog1 (or force (jao-exwm--new-frame-p)) + (set-frame-parameter nil 'jao-frames-initialized t))) + + (defun jao-exwm--goto-main (&optional init) + (interactive "P") + (exwm-workspace-switch-create 1) + (when (jao-exwm--mark-frame init) (jao-trisect))) + + (defun jao-exwm--goto-mail (&optional init) + (interactive "P") + (exwm-workspace-switch-create 2) + (when (jao-exwm--mark-frame init) + (jao-afio-open-gnus))) + + (defun jao-exwm--goto-www (&optional init) + (interactive "P") + (exwm-workspace-switch-create 5) + (when (jao-exwm--mark-frame init) + (jao-afio-open-www) + (let ((scroll-bar-mode 'left)) + (toggle-scroll-bar 1) + (set-frame-parameter (window-frame) 'scroll-bar-width 12)) + (jao-toggle-inactive-mode-line))) + + (defun jao-exwm--goto-docs (&optional init) + (interactive "P") + (exwm-workspace-switch-create 4) + (when (jao-exwm--mark-frame init) + (jao-afio-open-doc))) + + (defun jao-exwm-open-doc (file) + (jao-exwm--goto-docs) + (jao-find-or-open file)) + + (defun jao-exwm-no-afio-setup () + (interactive) + (defalias 'jao-open-gnus-frame 'jao-exwm--goto-mail) + (defalias 'jao-goto-www-frame 'jao-exwm--goto-www) + (setq jao-open-doc-fun #'jao-exwm-open-doc) + (setq minibuffer-follows-selected-frame t) + (global-set-key "\C-cf" 'jao-exwm--goto-main) + (global-set-key "\C-cg" 'jao-exwm--goto-mail) + (global-set-key "\C-cw" 'jao-exwm--goto-www) + (global-set-key "\C-cz" 'jao-exwm--goto-docs)) + + (if jao-exwm--use-afio + (setq minibuffer-follows-selected-frame nil) + (jao-exwm-no-afio-setup)) + #+end_src +* Tracking + #+begin_src emacs-lisp + (add-hook 'exwm-workspace-switch-hook 'tracking-remove-visible-buffers) + #+end_src +* Buffer names + #+begin_src emacs-lisp + (defun jao-exwm--use-title-p () + (and exwm-title (not (string-blank-p exwm-title)))) + + (defun jao-exwm-rename-buffer/class () + (unless (jao-exwm--use-title-p) + (exwm-workspace-rename-buffer exwm-class-name))) + + (defun jao-exwm-rename-buffer/title () + (cond ((or (not exwm-instance-name) + (jao-exwm--use-title-p)) + (exwm-workspace-rename-buffer exwm-title)) + ((string= "Zathura" exwm-class-name) + (exwm-workspace-rename-buffer + (format "zathura: %s" (file-name-nondirectory exwm-title)))))) + + (defun jao-exwm--set-exwm-name () + (when (not jao-exwm--name) + (setq jao-exwm--name jao-exwm--current-name + jao-exwm--current-name nil))) + + (add-hook 'exwm-mode-hook 'jao-exwm--set-exwm-name) + (add-hook 'exwm-update-class-hook 'jao-exwm-rename-buffer/class) + (add-hook 'exwm-update-title-hook 'jao-minibuffer-refresh) + (add-hook 'exwm-update-title-hook 'jao-exwm-rename-buffer/title) + #+end_src +* Float windows + #+begin_src emacs-lisp + (defvar jao-exwm-max-x (x-display-pixel-width)) + (defvar jao-exwm-max-y (x-display-pixel-height)) + + (defun jao-exwm--float-to (x y &optional w h) + (let* ((w (or w (frame-pixel-width))) + (h (or h (frame-pixel-height))) + (x (if (< x 0) (- jao-exwm-max-x (- x) w) x)) + (y (if (< y 0) (- jao-exwm-max-y (- y) h) y)) + (p (or (frame-parameter nil 'jao-position) (frame-position)))) + (exwm-floating-move (- x (car p)) (- y (cdr p))) + (exwm-layout-enlarge-window-horizontally (- w (frame-pixel-width))) + (exwm-layout-enlarge-window (- h (frame-pixel-height))) + (set-frame-parameter nil 'jao-position (cons x y)))) + + (defun jao-exwm--center-float (&optional w h) + (interactive) + (let* ((mx jao-exwm-max-x) + (my jao-exwm-max-y) + (w (or w (frame-pixel-width))) + (h (or h (/ (* w my) mx)))) + (jao-exwm--float-to (/ (- mx w) 2) (/ (- my h) 2) w h))) + + (defun jao-exwm--setup-float () + (set-frame-parameter nil 'jao-position nil) + (cond ((string= "Firefox" exwm-class-name) + (jao-exwm--center-float 900 600)) + ((member exwm-class-name '("mpv" "vlc")) + (jao-exwm--center-float 1200)))) + + (defvar jao-exwm-floating-classes '("mpv" "vlc")) + (setq jao-exwm-floating-classes nil) + + (defun jao-exwm--maybe-float () + (when (member exwm-class-name jao-exwm-floating-classes) + (when (not exwm--floating-frame) + (exwm-floating-toggle-floating)))) + + (add-hook 'exwm-floating-setup-hook #'jao-exwm--setup-float) + (add-hook 'exwm-manage-finish-hook #'jao-exwm--maybe-float) + + #+end_src +* Minibuffer + #+begin_src emacs-lisp + (setq jao-minibuffer-frame-width 271) + (add-hook 'exwm-workspace-switch-hook #'jao-minibuffer-refresh) + #+end_src +* System tray + #+begin_src emacs-lisp + (require 'exwm-systemtray) + (exwm-systemtray-enable) + + (defun jao-exwm--watch-tray (sym newval op where) + (setq jao-minibuffer-right-margin (* 2 (length newval))) + (jao-minibuffer-refresh)) + + (add-variable-watcher 'exwm-systemtray--list #'jao-exwm--watch-tray) + #+end_src +* Switch to buffer / app + #+begin_src emacs-lisp + (defvar-local jao-exwm--name nil) + (defvar jao-exwm--current-name nil) + + (defun jao-exwm--check-name (name) + (or (string= jao-exwm--name name) + (string= (buffer-name) name) + (string= exwm-class-name name) + (string= exwm-title name))) + + (defun jao-exwm-find-class-buffer (cln) + (if (jao-exwm--check-name cln) + (current-buffer) + (let* ((cur-buff (current-buffer)) + (bfs (seq-filter (lambda (b) + (and (not (eq b cur-buff)) + (with-current-buffer b + (jao-exwm--check-name cln)))) + (buffer-list)))) + (when (car bfs) (car (reverse bfs)))))) + + (defun jao-exwm-switch-to-class/title (cln) + (interactive) + (when cln + (if (jao-exwm--check-name cln) + (current-buffer) + (when-let ((b (jao-exwm-find-class-buffer cln))) + (pop-to-buffer b))))) + + (defun jao-exwm-switch-to-next-class () + (interactive) + (jao-exwm-switch-to-class/title exwm-class-name)) + + (defun jao-exwm-switch-to-next-x () + (interactive) + (let ((bfs (seq-filter (lambda (b) (buffer-local-value 'exwm-class-name b)) + (buffer-list (window-frame))))) + (when (car bfs) (switch-to-buffer (car (reverse bfs)))))) + + #+end_src +* App runner helpers + #+begin_src emacs-lisp + (defun jao-exwm-run (command) + (interactive + (list (read-shell-command "$ " + (if current-prefix-arg + (cons (concat " " (buffer-file-name)) 0) + "")))) + (setq jao-exwm--current-name nil) + (start-process-shell-command command nil command)) + + (defmacro jao-exwm-runner (&rest args) + `(lambda () (interactive) (start-process "" nil ,@args))) + + (defun jao-exwm-workspace (n) + (if jao-exwm--use-afio + (jao-afio-goto-nth n) + (exwm-workspace-switch-create n))) + + (defmacro jao-def-exwm-runner (name ws class &rest args) + `(defun ,name (&rest other-args) + (interactive) + ,@(when ws `((jao-exwm-workspace ,ws))) + (if (jao-exwm-switch-to-class/title ,class) + ,(cond ((equal ws 5) '(delete-other-windows)) + ((stringp (car args)) (cdr args)) + (t args)) + (setq jao-exwm--current-name ,class) + ,(if (stringp (car args)) + `(start-process-shell-command ,(car args) + "* exwm - console *" + (string-join (append (list ,@args) + other-args) + " ")) + args)))) + + (defmacro jao-def-exwm-toggler (name ws class &rest args) + (let ((toggler (intern (format "%s*" name))) + (arg (gensym))) + `(progn (jao-def-exwm-runner ,name ,ws ,class ,@args) + (defun ,toggler (,arg) + (interactive "P") + (if (and (not ,arg) (equal exwm-class-name ,class)) + (jao-afio--goto-main) + (,name)))))) + + (defun jao-exwm--send-str (str) + (dolist (k (string-to-list (kbd str))) + (exwm-input--fake-key k))) + + #+end_src +* Runners + #+begin_src emacs-lisp + (jao-def-exwm-runner jao-exwm-vlc 4 "VLC" "vlc") + + (jao-def-exwm-runner jao-exwm-slack 0 "Slack" "slack") + (jao-def-exwm-runner jao-exwm-signal 0 "Signal" "signal-desktop") + + (jao-def-exwm-runner jao-exwm-proton-bridge 0 "*proton-bridge*" "protonmail-bridge") + + ;; (jao-def-exwm-runner jao-exwm-htop 0 "htop-xterm" + ;; "xterm" "-title" "htop-xterm" "-e" "htop") + (jao-def-exwm-runner jao-exwm-htop 0 "htop" jao-term-htop) + + ;; (jao-def-exwm-runner jao-exwm-aptitude 0 "aptitude-xterm" + ;; "xterm" "-title" "aptitude-xterm" "-e" "aptitude") + (jao-def-exwm-runner jao-exwm-aptitude 0 "aptitude" jao-term-aptitude) + + (jao-def-exwm-runner jao-exwm-blueman 0 "Blueman-manager" "blueman-manager") + (jao-def-exwm-runner jao-exwm-ncmpcpp 0 "ncmpcpp" "xterm" "-e" "ncmpcpp" "-p" "6669") + (jao-def-exwm-runner jao-exwm-mpc 0 "*MPC-Status*" mpc) + + (jao-def-exwm-runner jao-exwm-proton-vpn 0 "*pvpn*" proton-vpn-status) + (jao-def-exwm-runner jao-exwm-enwc 0 "*ENWC*" enwc) + (jao-def-exwm-runner jao-exwm-bluetooth 0 "*Bluetooth*" bluetooth-list-devices) + (jao-def-exwm-runner jao-exwm-packages 0 "*Packages*" list-packages nil) + (jao-def-exwm-runner jao-exwm-proced 0 "*Proced*" proced) + + (jao-def-exwm-runner jao-exwm-open-with-zathura nil nil "zathura" (buffer-file-name)) + (jao-def-exwm-runner jao-exwm-open-with-mupdf nil nil "mupdf" (buffer-file-name)) + (jao-def-exwm-runner jao-exwm-xterm 0 nil "xterm") + + (jao-def-exwm-toggler jao-exwm-tidal 5 "tidal-hifi" "tidal-hifi") + (defalias 'jao-streaming-list #'jao-exwm-tidal) + + (defun jao-exwm-import-screen (&optional area) + (interactive "P") + (when (not (file-directory-p "/tmp/screenshot")) + (make-directory "/tmp/screenshot")) + (let ((c (format "import %s %s" + (if area "" "-window root") + "/tmp/screenshot/$(date +'%g%m%d-%H%M%S').png"))) + (start-process-shell-command "import" "* exwm - console *" c))) + + #+end_src +* Zathura support + #+begin_src emacs-lisp + (defun jao-zathura--buffer-p (b) + (string= "Zathura" (or (buffer-local-value 'exwm-class-name b) ""))) + + (defun jao-zathura--buffers () + (seq-filter #'jao-zathura--buffer-p (buffer-list))) + + (defun jao-zathura--file-info (b) + (with-current-buffer b + (jao-zathura-file-info (or exwm-title "")))) + + (defun jao-zathura-goto-page (page-no) + (jao-exwm--send-str (format "%sg" page-no))) + + (defun jao-zathura-open-doc (&optional file-name page-no height) + (interactive) + (let* ((file-name (expand-file-name (or file-name (buffer-file-name)))) + (buffer (seq-find `(lambda (b) + (string= ,file-name + (car (jao-zathura--file-info b)))) + (jao-zathura--buffers))) + (page-no (or page-no (jao-doc-view-current-page)))) + (if jao-exwm--use-afio (jao-afio--goto-docs) (jao-exwm--goto-docs)) + (if (not buffer) + (jao-exwm-run (if page-no + (format "zathura -P %s '%s'" page-no file-name) + (format "zathura '%s'" file-name))) + (pop-to-buffer buffer) + (when page-no (jao-zathura-goto-page page-no))) + (current-buffer))) + + (defun jao-exwm--zathura-setup () + (when (and (string= exwm-class-name "Zathura") + (not jao-doc-view--imenu-file)) + (let ((info (jao-zathura--file-info (current-buffer)))) + (jao-doc-view-session-mark (car info)) + (jao-doc-view-save-session) + (jao-doc-view--enable-imenu (car info) #'jao-zathura-goto-page)))) + + (add-hook 'exwm-update-title-hook #'jao-exwm--zathura-setup t) + + (defun jao-exwm-pdf-zathura-close-all () + (interactive) + (dolist (b (jao-zathura--buffers)) + (ignore-errors + (switch-to-buffer b) + (jao-exwm--send-str "q"))) + t) + + (defun jao-exwm-zathura-goto-org (&optional arg) + (interactive "P") + (when-let ((info (jao-zathura--file-info (current-buffer)))) + (when-let ((file (jao-org-pdf-to-org-file (car info)))) + (let ((newp (not (file-exists-p file)))) + (when (or arg newp) (org-store-link nil t)) + (find-file-other-window file) + (when newp + (jao-org-insert-doc-skeleton) + (org-insert-link)))))) + + (defun jao-exwm-zathura-goto-org* () + (interactive) + (jao-exwm-zathura-goto-org t)) + + (defun jao-exwm-org-store-zathura-link () + (when-let ((info (jao-zathura--file-info (current-buffer)))) + (let* ((file-name (car info)) + (page (cadr info)) + (desc (jao-doc-view-section-title page file-name))) + (jao-org-links-store-pdf-link file-name page desc)))) + + (defun jao-exwm-pdf-enable-zathura () + (interactive) + (add-hook 'kill-emacs-query-functions #'jao-exwm-pdf-zathura-close-all t) + (setq jao-org-open-pdf-fun #'jao-zathura-open-doc) + (setq jao-org-links-pdf-store-fun #'jao-exwm-org-store-zathura-link) + (setq jao-open-doc-fun #'jao-zathura-open-doc)) + + (defun jao-exwm-pdf-disable-zathura () + (interactive) + (define-key org-mode-map (kbd "C-c o") #'jao-org-org-goto-pdf) + (remove-hook 'kill-emacs-query-functions #'jao-exwm-pdf-zathura-close-all) + (setq jao-org-open-pdf-fun #'jao-find-or-open) + (setq jao-org-links-pdf-store-fun nil) + (setq jao-open-doc-fun #'jao-find-or-open)) + + (defun jao-exwm-zathura-goto-pdf () + (interactive) + (if jao-browse-doc-use-emacs-p + (jao-org-org-goto-pdf) + (when-let (pdf (jao-org-org-to-pdf-file)) + (jao-zathura-open-doc pdf)))) + + (with-eval-after-load "org" + (define-key org-mode-map (kbd "C-c o") #'jao-exwm-zathura-goto-pdf)) + + (when (not jao-browse-doc-use-emacs-p) + (jao-exwm-pdf-enable-zathura)) + + (defun jao-exwm-select-pdf () + (interactive) + (let ((b (read-buffer "Document: " nil t + (lambda (b) + (let ((b (cdr b))) + (or (jao-zathura--buffer-p b) + (member (buffer-local-value 'major-mode b) + '(pdf-view-mode doc-view-mode)))))))) + (jao-afio--goto-docs) + (pop-to-buffer b))) + + + (let ((viewers ["External viewers" + ("z" "open with zathura" jao-zathura-open-doc) + ("m" "open with mupdf" jao-exwm-open-with-mupdf)])) + (jao-transient-major-mode+ pdf-view viewers) + (jao-transient-major-mode+ doc-view viewers)) + + #+end_src +* Firefox support + #+begin_src emacs-lisp + (jao-def-exwm-toggler jao-exwm-firefox 5 "Firefox" "firefox") + + (defun jao-exwm-browse-with-firefox (&rest args) + (jao-exwm-firefox) + (apply #'browse-url-firefox args)) + + (setq browse-url-secondary-browser-function #'jao-exwm-browse-with-firefox) + + (defun jao-exwm-kill-firefox-url () + (interactive) + (when-let (b (jao-exwm-find-class-buffer "Firefox")) + (let ((cb (current-buffer))) + (switch-to-buffer b) + (jao-exwm--send-str "yy") + (prog1 (current-kill 1) + (switch-to-buffer cb))))) + + #+end_src +* Transients + #+begin_src emacs-lisp + (defun jao-exwm--floating-p () exwm--floating-frame) + (defun jao-exwm--m0-5 () (interactive nil exwm-mode) (exwm-floating-move 0 -5)) + (defun jao-exwm--m05 () (interactive nil exwm-mode) (exwm-floating-move 0 5)) + (defun jao-exwm--m-50 () (interactive nil exwm-mode) (exwm-floating-move -5 0)) + (defun jao-exwm--m50 () (interactive nil exwm-mode) (exwm-floating-move 5 0)) + (defun jao-exwm--e-5 () (interactive nil exwm-mode) (exwm-layout-enlarge-window -5)) + (defun jao-exwm--e5 () (interactive nil exwm-mode) (exwm-layout-enlarge-window 5)) + (defun jao-exwm--eh5 () (interactive nil exwm-mode) (exwm-layout-enlarge-window 5 t)) + (defun jao-exwm--eh-5 () (interactive nil exwm-mode) (exwm-layout-enlarge-window -5 t)) + (defun jao-exwm--tl () (interactive nil exwm-mode) (jao-exwm--float-to 20 20)) + (defun jao-exwm--tr () (interactive nil exwm-mode) (jao-exwm--float-to -20 20)) + (defun jao-exwm--bl () (interactive nil exwm-mode) (jao-exwm--float-to 20 -20)) + (defun jao-exwm--br () (interactive nil exwm-mode) (jao-exwm--float-to -20 -20)) + + (defun jao-exwm--def-center-float () + (interactive) + (exwm-floating-toggle-floating) + (jao-exwm--center-float 900 600)) + + (transient-define-prefix jao-transient-float () + "Operations on EXWM floating windows." + :transient-non-suffix 'transient--do-quit-one + [["Float" + ("f" "float" exwm-floating-toggle-floating) + ("F" "full" exwm-layout-toggle-fullscreen) + ("c" "center" jao-exwm--center-float :if jao-exwm--floating-p) + ("c" "float and resize" jao-exwm--def-center-float + :if-not jao-exwm--floating-p) + ("x" "hide" exwm-floating-hide :if jao-exwm--floating-p)] + ["Slide" :if jao-exwm--floating-p + ("k" "up" jao-exwm--m0-5 :transient t) + ("j" "down" jao-exwm--m05 :transient t) + ("h" "left" jao-exwm--m-50 :transient t) + ("l" "right" jao-exwm--m50 :transient t)] + ["Resize" :if jao-exwm--floating-p + ("K" "up" jao-exwm--e5 :transient t) + ("J" "down" jao-exwm--e-5 :transient t) + ("H" "left" jao-exwm--eh5 :transient t) + ("L" "right" jao-exwm--eh-5 :transient t)] + ["Nudge" :if jao-exwm--floating-p + ("t" "top-left" jao-exwm--tl) + ("T" "top-right" jao-exwm--tr) + ("b" "bottom-left" jao-exwm--bl) + ("B" "bottom-right " jao-exwm--br)]]) + + (defun jao-exwm--buffer () + (interactive) + (jao-buffer-same-mode 'exwm-mode nil 'exwm-workspace-switch-to-buffer)) + + #+end_src +* Keybindings + #+begin_src emacs-lisp + (define-key exwm-mode-map [?\C-q] #'exwm-input-send-next-key) + (define-key exwm-mode-map [?\s-f] #'jao-transient-float) + (define-key exwm-mode-map (kbd "C-c o") #'jao-exwm-zathura-goto-org) + (define-key exwm-mode-map (kbd "C-c O") #'jao-exwm-zathura-goto-org*) + (define-key exwm-mode-map (kbd "M-o") #'other-window) + (define-key exwm-mode-map (kbd "M-p") #'jao-prev-window) + + (setq + exwm-input-global-keys + '(([?\s-0] . jao-afio--goto-scratch) + ([?\s-1] . jao-afio--goto-main) + ([?\s-2] . jao-afio--goto-mail) + ([?\s-3] . jao-afio--goto-www) + ([?\s-4] . jao-afio--goto-docs) + ([?\s-A] . org-agenda-list) + ([?\s-a] . jao-first-window) + ([?\s-b] . jao-transient-org-blog) + ;; ([?\s-d] . jao-exwm-tidal*) + ([?\s-d] . jao-mpc-echo-current) + ([?\s-e] . jao-exwm-firefox*) + ([?\s-m] . jao-transient-media) + ;; ([?\s-O] . jao-transpose-windows) + ;; ([?\s-o] . jao-other-window) + ;; ([?\s-P] . jao-transpose-windows-prev) + ([?\s-O] . ace-swap-window) + ([?\s-o] . ace-window) + ([?\s-p] . jao-prev-window) + ([?\s-R] . app-launcher-run-app) + ([?\s-r] . jao-recoll-transient) + ([?\s-s] . jao-transient-streaming) + ([?\s-t] . vterm) + ([?\s-w] . jao-transient-utils) + ([?\s-z] . jao-transient-sleep) + ([XF86AudioMute] . jao-mixer-master-toggle) + ([XF86AudioPlay] . jao-player-toggle) + ([XF86AudioPause] . jao-player-toggle) + ([XF86AudioNext] . jao-player-next) + ([XF86AudioPrev] . jao-player-previous) + ([XF86AudioStop] . jao-player-stop) + ([XF86AudioRaiseVolume] . jao-mixer-master-up) + ([XF86AudioLowerVolume] . jao-mixer-master-down) + ([XF86MonBrightnessUp] . jao-bright-up) + ([XF86MonBrightnessDown] . jao-bright-down) + ([?\s-\`] . jao-exwm-switch-to-next-x) + ([s-tab] . jao-exwm-switch-to-next-class) + ([print] . jao-exwm-import-screen) + ([f5] . jao-weather) + ([f6] . jao-toggle-audio-applet) + ([f8] . jao-toggle-nm-applet) + ([f9] . jao-bright-show))) + + ;; (customize-set-variable 'exwm-input-global-keys exwm-input-global-keys) + + #+end_src diff --git a/attic/orgs/gnus.org b/attic/orgs/gnus.org new file mode 100644 index 0000000..c0c0346 --- /dev/null +++ b/attic/orgs/gnus.org @@ -0,0 +1,780 @@ +#+property: header-args :lexical t :tangle ~/.emacs.d/gnus.el :comments yes :results silent :shebang ";; -*- lexical-binding: t -*-" :tangle-mode (identity #o644) +#+title: Gnus + +* Feature switching vars + #+begin_src emacs-lisp + (defvar jao-gnus-use-local-imap nil) + (defvar jao-gnus-use-leafnode nil) + (defvar jao-gnus-use-gandi-imap nil) + (defvar jao-gnus-use-pm-imap nil) + (defvar jao-gnus-use-gmane nil) + (defvar jao-gnus-use-nnml nil) + (defvar jao-gnus-use-maildirs nil) + #+end_src +* Startup and kill + #+begin_src emacs-lisp + ;;;;; close gnus when closing emacs, but ask when exiting + (setq gnus-interactive-exit t) + + (defun jao-gnus-started-hook () + (add-hook 'before-kill-emacs-hook 'gnus-group-exit)) + + (add-hook 'gnus-started-hook 'jao-gnus-started-hook) + + (defun jao-gnus-after-exiting-hook () + (remove-hook 'before-kill-emacs-hook 'gnus-group-exit)) + + (add-hook 'gnus-after-exiting-gnus-hook 'jao-gnus-after-exiting-hook) + + ;; define a wrapper around the save-buffers-kill-emacs + ;; to run the new hook before: + (defadvice save-buffers-kill-emacs + (before my-save-buffers-kill-emacs activate) + "Install hook when emacs exits before emacs asks to save this and that." + (run-hooks 'before-kill-emacs-hook)) + #+end_src +* Directories + #+begin_src emacs-lisp + (defun jao-gnus-dir (dir) + (expand-file-name dir gnus-home-directory)) + + (setq smtpmail-queue-dir (jao-gnus-dir "Mail/queued-mail/")) + + (setq mail-source-directory (jao-gnus-dir "Mail/") + message-directory (jao-gnus-dir "Mail/")) + + (setq gnus-default-directory (expand-file-name "~") + gnus-startup-file (jao-gnus-dir "newsrc") + gnus-agent-directory (jao-gnus-dir "News/agent") + gnus-home-score-file (jao-gnus-dir "scores") + gnus-article-save-directory (jao-gnus-dir "saved/") + nntp-authinfo-file (jao-gnus-dir "authinfo") + nnmail-message-id-cache-file (jao-gnus-dir "nnmail-cache") + nndraft-directory (jao-gnus-dir "drafts") + nnrss-directory (jao-gnus-dir "rss")) + #+end_src +* Looks +*** Verbosity + #+begin_src emacs-lisp + (setq gnus-verbose 4) + #+end_src +*** Geometry + #+begin_src emacs-lisp + ;;; geometry: + (defvar jao-gnus-use-three-panes window-system) + (defvar jao-gnus-groups-width 50) + (defvar jao-gnus-wide-width 190) + + (setq gnus-use-trees nil + gnus-generate-tree-function 'gnus-generate-horizontal-tree + gnus-tree-minimize-window nil) + + (when jao-gnus-use-three-panes + (let ((side-bar '(vertical 1.0 + ("inbox.org" 0.4) + ("*Org Agenda*" 1.0) + ("*Calendar*" 8))) + (wide-len jao-gnus-wide-width) + (groups-len jao-gnus-groups-width) + (summary-len (- jao-gnus-wide-width jao-gnus-groups-width))) + (gnus-add-configuration + `(article + (horizontal 1.0 + (vertical ,groups-len (group 1.0)) + (vertical ,summary-len + (summary 0.25 point) + (article 1.0)) + ,side-bar))) + + (gnus-add-configuration + `(group (horizontal 1.0 (group ,wide-len point) ,side-bar))) + + (gnus-add-configuration + `(message (horizontal 1.0 (message ,wide-len point) ,side-bar))) + + (gnus-add-configuration + `(reply-yank (horizontal 1.0 (message ,wide-len point) ,side-bar))) + + (gnus-add-configuration + `(summary + (horizontal 1.0 + (vertical ,groups-len (group 1.0)) + (vertical ,summary-len (summary 1.0 point)) + ,side-bar))) + + (gnus-add-configuration + `(reply + (horizontal 1.0 + (message ,(- wide-len 100) point) + (article 100) + ,side-bar))))) + #+end_src +*** No blue icon + #+begin_src emacs-lisp + (advice-add 'gnus-mode-line-buffer-identification :override #'identity) + (setq gnus-mode-line-image-cache nil) + #+end_src +* Search + #+begin_src emacs-lisp + + (setq gnus-search-use-parsed-queries t + gnus-search-notmuch-raw-queries-p nil + gnus-permanently-visible-groups "^nnselect:.*" + gnus-search-ignored-newsgroups "nndraft.*\\|nnselect.*") + + (with-eval-after-load "gnus-search" + (add-to-list 'gnus-search-expandable-keys "list") + + (cl-defmethod gnus-search-transform-expression ((engine gnus-search-notmuch) + (expr (head list))) + (format "List:%s" (gnus-search-transform-expression engine (cdr expr))))) + + (defun jao-gnus--notmuch-engine (prefix config) + (let ((prefix (file-name-as-directory (expand-file-name prefix "~"))) + (config (expand-file-name config gnus-home-directory))) + `(gnus-search-engine gnus-search-notmuch + (remove-prefix ,prefix) + (config-file ,config)))) + + ;; (add-to-list 'gnus-parameters '("^nnselect:.*" (nnselect-rescan . t))) + + #+end_src +* News server + #+begin_src emacs-lisp + (setq gnus-select-method + (cond + (jao-gnus-use-leafnode + `(nntp "localhost" + ,(jao-gnus--notmuch-engine "var/news" "notmuch-news.config"))) + (jao-gnus-use-gmane '(nntp "news.gmane.io")) + (t '(nnnil "")))) + + (setq gnus-secondary-select-methods '()) + + (setq nnheader-read-timeout 0.02 + gnus-save-newsrc-file nil) ; .newsrc only needed by other newsreaders + #+end_src +* Local mail +*** nnmail params + #+begin_src emacs-lisp + (setq nnmail-treat-duplicates 'delete + nnmail-scan-directory-mail-source-once nil + nnmail-cache-accepted-message-ids t + nnmail-message-id-cache-length 100000 + nnmail-split-fancy-with-parent-ignore-groups nil + nnmail-use-long-file-names t + nnmail-crosspost t + nnmail-resplit-incoming t + nnmail-mail-splitting-decodes t + nnmail-split-methods 'nnmail-split-fancy) + #+end_src +*** nnml + #+begin_src emacs-lisp + (setq mail-sources + (when jao-gnus-use-nnml + (cons '(file :path "/var/mail/jao") + (when (eq jao-afio-mail-function 'gnus) + (mapcar (lambda (d) `(maildir :path ,(concat d "/"))) + (directory-files "~/var/mail" t "[^\\.]"))))) + gnus-message-archive-group nil + nnml-get-new-mail t + nnml-directory message-directory) + + (when jao-gnus-use-nnml + (add-to-list + 'gnus-secondary-select-methods + `(nnml "" ,(jao-gnus--notmuch-engine (jao-gnus-dir "Mail") "notmuch.config")))) + + (defvar jao-gnus-nnml-group-params + `(("nnml:\\(local\\|trash\\|spam\\)" + (auto-expire . t) + (total-expire . t) + (expiry-wait . 1) + (expiry-target . delete)) + ("nnml:jao\\..*" + (posting-style ("Bcc" "proton@jao.io") + ("Gcc" "nnml:jao.trove")) + (jao-gnus--trash-group "nnml:trash") + (jao-gnus--spam-group "nnml:spam") + (jao-gnus--archiving-group "nnml:jao.trove")) + ("nnml:bigml\\..*" + (gcc-self . nil) + (auto-expire . t) + (total-expire . t) + (expiry-wait . 3) + (expiry-target . delete) + (posting-style (address "jao@bigml.com")) + (jao-gnus--trash-group "nnml:trash") + (jao-gnus--spam-group "nnml:spam") + (jao-gnus--archiving-group "nnml:bigml.trove")) + ("nnml:bigml\\.\\(inbox\\|support\\)" + (gcc-self . t) + (auto-expire . t) + (total-expire . t) + (expiry-wait . 7) + (expiry-target . "nnml:bigml.trove")) + ("nnml:bigml\\.trove" + (auto-expire . t) + (total-expire . t) + (expiry-target . delete) + (expiry-wait . 365)) + ("nnml:jao\\.drivel" + (auto-expire . t) + (total-expire . t) + (expiry-wait . 3) + (expiry-target . delete)) + ("nnml:feeds\\.\\(.*\\)" + (auto-expire . t) + (total-expire . t) + (expiry-wait . 7) + (expiry-target . delete) + (comment . "feeds.\\1") + (jao-gnus--archiving-group "nnml:feeds.trove")) + ("^nnml:feeds\\.\\(news\\)$" (expiry-wait . 2)) + ("nnml:feeds\\.\\(trove\\|lobsters\\|philosophy\\)" + (auto-expire . nil) + (total-expire . nil)) + ("nnml:feeds\\.fun" + (mm-html-inhibit-images nil) + (mm-html-blocked-images nil)))) + + (when jao-gnus-use-nnml + (dolist (p jao-gnus-nnml-group-params) + (add-to-list 'gnus-parameters p t))) + + #+end_src +*** leafnode + #+begin_src emacs-lisp + (defvar jao-gnus-image-groups '("xkcd")) + + (defvar jao-gnus-leafnode-group-params + `((,(format "gwene\\..*%s.*" (regexp-opt jao-gnus-image-groups)) + (mm-html-inhibit-images nil) + (mm-html-blocked-images nil)) + ("\\(gmane\\|gwene\\)\\..*" + (jao-gnus--archiving-group "nnml:feeds.trove") + (posting-style (address "jao@gnu.org"))))) + + (when jao-gnus-use-leafnode + (dolist (p jao-gnus-leafnode-group-params) + (add-to-list 'gnus-parameters p t))) + + #+end_src +*** maildirs + #+begin_src emacs-lisp + (when jao-gnus-use-maildirs + (add-to-list + 'gnus-secondary-select-methods + `(nnmaildir "mail" + (directory "~/.nnmaildirs") + ,(jao-gnus--notmuch-engine "~/var/mail/" + "~/.notmuch-config")))) + + #+end_src +* IMAP servers + #+begin_src emacs-lisp + (setq nnimap-quirks nil) + + (when jao-gnus-use-local-imap + (add-to-list 'gnus-secondary-select-methods + `(nnimap "" (nnimap-address "localhost")))) + + (when jao-gnus-use-pm-imap + (add-to-list 'gnus-secondary-select-methods + '(nnimap "pm" + (nnimap-address "127.0.0.1") + (nnimap-stream network) + (nnimap-server-port 1143)))) + + (when jao-gnus-use-gandi-imap + (add-to-list 'gnus-secondary-select-methods + '(nnimap "gandi" (nnimap-address "mail.gandi.net")))) + #+end_src +* Demon and notifications + #+begin_src emacs-lisp + (setq mail-user-agent 'gnus-user-agent) + + ;; synchronicity + (setq gnus-asynchronous t) + ;;; prefetch as many articles as possible + (setq gnus-use-article-prefetch nil) + + (setq gnus-save-killed-list nil) + (setq gnus-check-new-newsgroups nil) + + (defvar jao-gnus-tracked-groups + (let ((feeds (thread-first + (directory-files mail-source-directory nil "feeds") + (seq-difference '("feeds.trove"))))) + `(("nnml:bigml.inbox" "B" jao-themes-f00) + ("nnml:bigml.bugs" "b" jao-themes-error) + ("nnml:bigml.support" "S" default) + ("nnml:jao.inbox" "I" jao-themes-f01) + ("nnml:bigml.[^ibs]" "W" jao-themes-dimm) + ("nnml:jao.[^ist]" "J" jao-themes-dimm) + (,(format "^nnml:%s" (regexp-opt feeds)) "F" jao-themes-dimm) + ("^gmane" "G" jao-themes-dimm) + ("nnml:local" "l" jao-themes-dimm)))) + + (defun jao-gnus--unread-counts () + (seq-reduce (lambda (r g) + (let ((n (gnus-group-unread (car g)))) + (if (and (numberp n) (> n 0)) + (prog1 (cons (cons (car g) n) r) + (gnus-message 7 "%s in %s" n g)) + r))) + gnus-newsrc-alist + ())) + + (defun jao-gnus--unread-label (counts rx label face) + (let ((n (seq-reduce (lambda (n c) + (if (string-match-p rx (car c)) (+ n (cdr c)) n)) + counts + 0))) + (when (> n 0) `(:propertize ,(format "%s%d " label n) face ,face)))) + + (defvar jao-gnus--notify-strs ()) + + (defun jao-gnus--notify-strs () + (let ((counts (jao-gnus--unread-counts))) + (seq-filter #'identity + (seq-map (lambda (args) + (apply 'jao-gnus--unread-label counts args)) + jao-gnus-tracked-groups)))) + + (defun jao-gnus--notify () + (setq jao-gnus--notify-strs (jao-gnus--notify-strs)) + (save-window-excursion (jao-minibuffer-refresh))) + + (defun jao-gnus-scan () + (interactive) + (let ((inhibit-message t)) + (gnus-demon-scan-mail) + (shell-command "index-mail.sh") + (save-window-excursion ) + (jao-gnus--notify))) + + (require 'gnus-demon) + (gnus-demon-add-handler 'gnus-demon-scan-news 5 1) + ;; (gnus-demon-remove-handler 'jao-gnus-scan) + + (add-hook 'gnus-started-hook #'jao-gnus-scan) + (add-hook 'gnus-summary-exit-hook #'jao-gnus--notify) + (add-hook 'gnus-summary-exit-hook #'org-agenda-list) + (add-hook 'gnus-after-getting-new-news-hook #'jao-gnus-scan) + + (with-eval-after-load "jao-minibuffer" + (jao-minibuffer-add-variable 'jao-gnus--notify-strs -20)) + + #+end_src +* Delayed messages + #+BEGIN_SRC emacs-lisp + ;;; delayed messages (C-cC-j in message buffer) + (require 'gnus-util) + (gnus-delay-initialize) + (setq gnus-delay-default-delay "3h") + ;;; so that the Date is set when the message is sent, not when it's + ;;; delayed + (eval-after-load "message" + '(setq message-draft-headers (remove 'Date message-draft-headers))) + #+END_SRC +* Groups buffer + #+begin_src emacs-lisp + ;; (setq gnus-group-line-format " %m%S%p%P:%~(pad-right 35)c %3y %B\n") + ;; (setq gnus-group-line-format " %m%S%p%3y%P%* %~(pad-right 30)C %B\n") + (setq gnus-group-line-format " %m%S%p%3y%P%* %~(pad-right 30)G %B\n") + (setq gnus-topic-line-format "%i[ %(%{%n%}%) -- %A ]%v\n") + (setq gnus-group-uncollapsed-levels 2) + (setq gnus-auto-select-subject 'unread) + (setq-default gnus-large-newsgroup 2000) + + (add-hook 'gnus-select-group-hook 'gnus-group-set-timestamp) + (add-hook 'gnus-group-mode-hook 'gnus-topic-mode) + + (defvar jao-gnus--expire-every 50) + (defvar jao-gnus--get-count (1+ jao-gnus--expire-every)) + + (defun jao-gnus-get-new-news (&optional arg) + (interactive "p") + (when (and jao-gnus--expire-every + (> jao-gnus--get-count jao-gnus--expire-every)) + (when jao-gnus-use-pm-imap (gnus-group-catchup "nnimap:pm/spam" t)) + (gnus-group-expire-all-groups) + (setq jao-gnus--get-count 0)) + (setq jao-gnus--get-count (1+ jao-gnus--get-count)) + (gnus-group-get-new-news (max (if (= 1 jao-gnus--get-count) 4 3) + (or arg 0)))) + + ;; (define-key gnus-group-mode-map "g" 'jao-gnus-get-new-news) + + (defun jao-gnus-restart-servers () + (interactive) + (message "Restarting all servers...") + (gnus-group-enter-server-mode) + (gnus-server-close-all-servers) + (gnus-server-open-all-servers) + (gnus-server-exit) + (message "Restarting all servers... done")) + + (define-key gnus-group-mode-map "\C-x\C-s" #'gnus-group-save-newsrc) + + (jao-transient-major-mode gnus-group + ["Search" + ("zc" "consult search" consult-notmuch) + ("zf" "consult folder search" jao-consult-notmuch-folder) + ("g" "gnus search" gnus-group-read-ephemeral-search-group)]) + + (defun jao-gnus--first-group () + (when (derived-mode-p 'gnus-group-mode) + (gnus-group-first-unread-group))) + + (with-eval-after-load "jao-afio" + (add-hook 'jao-afio-switch-hook #'jao-gnus--first-group)) + #+end_src +* Summary buffer +*** Configuration + #+begin_src emacs-lisp + (setq gnus-face-1 'jao-gnus-face-tree) + + (setq gnus-show-threads t + gnus-thread-hide-subtree t + gnus-summary-make-false-root 'adopt + gnus-summary-gather-subject-limit 120 + gnus-sort-gathered-threads-function 'gnus-thread-sort-by-date + gnus-thread-sort-functions '(gnus-thread-sort-by-date)) + + (setq gnus-summary-ignore-duplicates t + gnus-suppress-duplicates t + ;; gnus-summary-ignored-from-addresses jao-mails-regexp + gnus-process-mark-toggle t + gnus-refer-thread-use-search t + gnus-auto-select-next 'almost-quietly) + #+end_src +*** Search + #+begin_src emacs-lisp + (defun jao-gnus--maybe-reselect (&rest _i) + (when (string-match-p "^nnselect" (or (gnus-group-name-at-point) "")) + (save-excursion (gnus-group-get-new-news-this-group)))) + + (advice-add 'gnus-group-select-group :before #'jao-gnus--maybe-reselect) + #+end_src +*** Summary line + #+begin_src emacs-lisp + (setq gnus-not-empty-thread-mark ?↓) ; ↓) ?· + (setq jao-gnus--summary-line-fmt + (concat "%%U %%*%%R %%uj " + "[ %%~(max-right 23)~(pad-right 23)uf " + " %%I%%~(pad-left 2)t ] %%s" + "%%-%s=" + "%%~(max-right 8)~(pad-left 8)&user-date;" + "\n")) + + (defun jao-gnus--set-summary-line (&optional w) + (let* ((d (if jao-gnus-use-three-panes (+ jao-gnus-groups-width 11) 12)) + (w (- (or w (window-width)) d))) + (setq gnus-summary-line-format (format jao-gnus--summary-line-fmt w)))) + + (add-hook 'gnus-select-group-hook 'jao-gnus--set-summary-line) + ;; (jao-gnus--set-summary-line 187) + + (add-to-list 'nnmail-extra-headers 'Cc) + (add-to-list 'nnmail-extra-headers 'BCc) + (add-to-list 'gnus-extra-headers 'Cc) + (add-to-list 'gnus-extra-headers 'BCc) + + (defun gnus-user-format-function-j (headers) + (let ((to (gnus-extra-header 'To headers))) + (if (string-match jao-mails-regexp to) + (if (string-match "," to) "¬" "»") ;; "~" "=") + (if (or (string-match jao-mails-regexp + (gnus-extra-header 'Cc headers)) + (string-match jao-mails-regexp + (gnus-extra-header 'BCc headers))) + "¬" ;; "~" + " ")))) + + (defconst jao-gnus--news-rx + (concat (regexp-opt '("ElDiaro.es " + "ElDiario.es - ElDiario.es: " + "The Guardian: " + "Aeon | a world of ideas: " + ": ")) + "\\|unofficial mirror of [^:]+: ")) + + (defun gnus-user-format-function-f (headers) + (let* ((from (gnus-header-from headers)) + (from (gnus-summary-extract-address-component from))) + (replace-regexp-in-string jao-gnus--news-rx "" from))) + + (setq gnus-user-date-format-alist + '(((gnus-seconds-today) . "%H:%M") + ((+ 86400 (gnus-seconds-today)) . "'%H:%M") + ;; (604800 . "%a %H:%M") ;; that's one week + ((gnus-seconds-month) . "%a %d") + ((gnus-seconds-year) . "%b %d") + (t . "%b '%y"))) + + #+end_src +*** Moving messages around + #+BEGIN_SRC emacs-lisp + (defvar-local jao-gnus--spam-group nil) + (defvar-local jao-gnus--archiving-group nil) + (defvar-local jao-gnus--archive-as-copy-p nil) + + (defvar jao-gnus--last-move nil) + (defun jao-gnus-move-hook (a headers c to d) + (setq jao-gnus--last-move (cons to (mail-header-id headers)))) + (defun jao-gnus-goto-last-moved () + (interactive) + (when jao-gnus--last-move + (when (eq major-mode 'gnus-summary-mode) (gnus-summary-exit)) + (gnus-group-goto-group (car jao-gnus--last-move)) + (gnus-group-select-group) + (gnus-summary-goto-article (cdr jao-gnus--last-move) nil t))) + (add-hook 'gnus-summary-article-move-hook 'jao-gnus-move-hook) + + (defun jao-gnus-archive (follow) + (interactive "P") + (if jao-gnus--archiving-group + (progn + (if (or jao-gnus--archive-as-copy-p + (not (gnus-check-backend-function + 'request-move-article gnus-newsgroup-name))) + (gnus-summary-copy-article nil jao-gnus--archiving-group) + (gnus-summary-move-article nil jao-gnus--archiving-group)) + (when follow (jao-gnus-goto-last-moved))) + (gnus-summary-mark-as-read) + (gnus-summary-delete-article))) + + (defun jao-gnus-archive-tickingly () + (interactive) + (gnus-summary-tick-article) + (jao-gnus-archive) + (when jao-gnus--archive-as-copy-p + (gnus-summary-mark-as-read))) + + (defun jao-gnus-show-tickled () + (interactive) + (gnus-summary-limit-to-marks "!")) + + (make-variable-buffer-local + (defvar jao-gnus--trash-group nil)) + + (defun jao-gnus-trash () + (interactive) + (gnus-summary-mark-as-read) + (if jao-gnus--trash-group + (gnus-summary-move-article nil jao-gnus--trash-group) + (gnus-summary-delete-article))) + + (defun jao-gnus-move-to-spam () + (interactive) + (gnus-summary-mark-as-read) + (gnus-summary-move-article nil jao-gnus--spam-group)) + + (define-key gnus-summary-mode-map "Ba" 'jao-gnus-archive) + (define-key gnus-summary-mode-map "BA" 'jao-gnus-archive-tickingly) + (define-key gnus-summary-mode-map "Bl" 'jao-gnus-goto-last-moved) + + (define-key gnus-summary-mode-map (kbd "B DEL") 'jao-gnus-trash) + (define-key gnus-summary-mode-map (kbd "B ") 'jao-gnus-trash) + (define-key gnus-summary-mode-map "Bs" 'jao-gnus-move-to-spam) + (define-key gnus-summary-mode-map "/!" 'jao-gnus-show-tickled) + (define-key gnus-summary-mode-map [f7] 'gnus-summary-force-verify-and-decrypt) + #+END_SRC +*** Saving emails + #+BEGIN_SRC emacs-lisp + (setq gnus-default-article-saver 'gnus-summary-save-article-mail) + (defvar jao-gnus-file-save-directory (expand-file-name "~/tmp")) + (defun jao-gnus-file-save (newsgroup headers &optional last-file) + (expand-file-name (format "%s.eml" (mail-header-subject headers)) + jao-gnus-file-save-directory)) + (setq gnus-mail-save-name 'jao-gnus-file-save) + #+END_SRC +*** arXiv capture + #+begin_src emacs-lisp + (use-package org-capture + :config + (add-to-list 'org-capture-templates + '("X" "arXiv" entry (file "notes/physics/arxiv.org") + "* %:subject\n %i" :immediate-finish t) + t) + (org-capture-upgrade-templates org-capture-templates)) + + (defun jao-gnus-arXiv-capture () + (interactive) + (gnus-summary-select-article-buffer) + (gnus-article-goto-part 0) + (forward-paragraph) + (setq-local transient-mark-mode 'lambda) + (set-mark (point)) + (goto-char (point-max)) + (org-capture nil "X")) + #+end_src +* Article buffer +*** Config, headers + #+begin_src emacs-lisp + (setq mail-source-delete-incoming t) + (setq gnus-gcc-mark-as-read t) + (setq gnus-treat-display-smileys nil) + (setq gnus-treat-fill-long-lines nil) + (setq gnus-treat-fill-article nil) + (setq gnus-treat-fold-headers nil) + (setq gnus-treat-strip-leading-blank-lines t) + (setq gnus-article-auto-eval-lisp-snippets nil) + (setq gnus-posting-styles '((".*" (name "Jose A. Ortega Ruiz")))) + (setq gnus-single-article-buffer nil) + (setq gnus-article-update-lapsed-header 60) + (setq gnus-article-update-date-headers 60) + + (eval-after-load "gnus-art" + '(setq + gnus-visible-headers + (concat + gnus-visible-headers + "\\|^List-[iI][Dd]:\\|^X-Newsreader:\\|^X-Mailer:\\|User-Agent:\\|X-User-Agent:"))) + #+end_src +*** HTML email (washing, images) + #+begin_src emacs-lisp + (setq gnus-button-url 'browse-url-generic + gnus-inhibit-images t + mm-discouraged-alternatives nil ;; '("text/html" "text/richtext") + mm-inline-large-images 'resize) + + ;; no html in From: (washing articles from arxiv feeds) and cleaning up + ;; addresses + (require 'shr) + (defvar jao-gnus--from-rx + (concat "From: \\\"?\\( " jao-gnus--news-rx "\\)")) + + (defun jao-gnus-remove-anchors () + (save-excursion + (goto-char (point-min)) + (cond ((re-search-forward jao-gnus--from-rx nil t) + (replace-match "" nil nil nil 1)) + ((re-search-forward "[gq].+ updates on arXiv.org: " nil t) + (replace-match "") + (let ((begin (point))) + (when (re-search-forward "^\\(To\\|Subject\\):" nil t) + (beginning-of-line) + (let ((shr-width 10000)) + (shr-render-region begin (1- (point)))))))))) + + (add-hook 'gnus-part-display-hook 'jao-gnus-remove-anchors) + + (defvar-local jao-gnus--images nil) + + (defun jao-gnus--init-images () + (with-current-buffer gnus-article-buffer + (setq jao-gnus--images nil))) + + (add-hook 'gnus-select-article-hook #'jao-gnus--init-images) + + (defun jao-gnus-show-images () + (interactive) + (save-window-excursion + (gnus-summary-select-article-buffer) + (save-excursion + (setq jao-gnus--images (not jao-gnus--images)) + (if jao-gnus--images + (gnus-article-show-images) + (gnus-article-remove-images))))) + #+end_src +*** Follow links and enclosures + #+begin_src emacs-lisp + (defun jao-gnus-follow-link (&optional external) + (interactive "P") + (when (eq major-mode 'gnus-summary-mode) + (gnus-summary-select-article-buffer)) + (save-excursion + (goto-char (point-min)) + (when (or (search-forward-regexp "^Via: h" nil t) + (search-forward-regexp "^URL: h" nil t) + (and (search-forward-regexp "^Link$" nil t) + (not (beginning-of-line)))) + (if external + (jao-browse-with-external-browser) + (browse-url (jao-url-around-point)))))) + + (defun jao-gnus-open-enclosure () + (interactive) + (save-window-excursion + (gnus-summary-select-article-buffer) + (save-excursion + (goto-char (point-min)) + (when (search-forward "Enclosure:") + (forward-char 2) + (when-let ((url (thing-at-point-url-at-point))) + (jao-browse-add-url-to-mpc url)))))) + #+end_src +* Add-ons +*** notmuch integration + #+begin_src emacs-lisp + (require 'jao-notmuch-gnus) + + (jao-notmuch-gnus-auto-tag) + + (defun jao-gnus-toggle-todo () + (interactive) + (jao-notmuch-gnus-toggle-tags '("todo"))) + + (define-key gnus-summary-mode-map (kbd "C-c T") #'jao-notmuch-gnus-tag-message) + (define-key gnus-summary-mode-map (kbd "C-c t") #'jao-notmuch-gnus-show-tags) + (define-key gnus-summary-mode-map (kbd "C-c C-t") #'jao-gnus-toggle-todo) + + (with-eval-after-load "notmuch-show" + (define-key gnus-group-mode-map "z" #'jao-gnus-consult-notmuch) + (define-key gnus-group-mode-map "Z" #'notmuch) + (define-key notmuch-show-mode-map + (kbd "C-c C-c") + #'jao-notmuch-gnus-goto-message)) + + (defun jao-gnus-notmuch-export (query) + (notmuch-tree query nil nil "* consult-notmuch results *")) + + (setq consult-notmuch-export-function #'jao-gnus-notmuch-export) + + (with-eval-after-load "notmuch-tree" + (define-key notmuch-tree-mode-map + (kbd "C-") + #'jao-notmuch-gnus-goto-message)) + + #+end_src +*** gnus-icalendar + #+begin_src emacs-lisp + (require 'ol-gnus) + (use-package gnus-icalendar + :demand t + :init (setq gnus-icalendar-org-capture-file + (expand-file-name "inbox.org" org-directory) + gnus-icalendar-org-capture-headline '("Appointments")) + :config (gnus-icalendar-org-setup)) + #+end_src +*** bbdb + #+begin_src emacs-lisp + (with-eval-after-load "bbdb" + (bbdb-initialize 'gnus 'message 'pgp 'mail) + (bbdb-mua-auto-update-init 'gnus) + (with-eval-after-load "gnus-sum" + (define-key gnus-summary-mode-map ":" 'bbdb-mua-annotate-sender) + (define-key gnus-summary-mode-map ";" 'bbdb-mua-annotate-recipients))) + #+end_src +*** randomsig + #+begin_src emacs-lisp + (with-eval-after-load "randomsig" + (with-eval-after-load "gnus-sum" + (define-key gnus-summary-save-map "-" 'gnus/randomsig-summary-read-sig))) + #+end_src +* Keyboard shortcuts + #+begin_src emacs-lisp + (define-key gnus-article-mode-map "i" 'jao-gnus-show-images) + (define-key gnus-summary-mode-map "i" 'jao-gnus-show-images) + (define-key gnus-article-mode-map "\M-g" 'jao-gnus-follow-link) + (define-key gnus-summary-mode-map "\M-g" 'jao-gnus-follow-link) + (define-key gnus-summary-mode-map "v" 'scroll-other-window) + (define-key gnus-summary-mode-map "V" 'scroll-other-window-down) + (define-key gnus-summary-mode-map "X" 'jao-gnus-arXiv-capture) + (define-key gnus-summary-mode-map "e" 'jao-gnus-open-enclosure) + (define-key gnus-summary-mode-map "\C-l" nil) + + #+end_src diff --git a/attic/orgs/init.org b/attic/orgs/init.org new file mode 100644 index 0000000..fcd69fb --- /dev/null +++ b/attic/orgs/init.org @@ -0,0 +1,3427 @@ +#+property: header-args :lexical t :tangle yes :comments yes :results silent :shebang ";; -*- lexical-binding: t -*-" :tangle-mode (identity #o644) + +* Packages +*** Use package + #+begin_src emacs-lisp + (unless (package-installed-p 'use-package) + (package-refresh-contents) + (package-install 'use-package)) + (require 'use-package) + #+end_src +*** ELPA Keyring + #+begin_src emacs-lisp + (use-package gnu-elpa-keyring-update :ensure t) + #+end_src +*** Loading .el newer than .elc files, and eln stuff + #+begin_src emacs-lisp + (setq load-prefer-newer t) + (setq comp-async-report-warnings-errors nil + warning-suppress-types '((comp))) + #+end_src +* Initialisation +*** Paths + #+begin_src emacs-lisp + (defvar jao-local-lisp-dir "~/lib/elisp" + "Directory for external elisp libraries and repos") + + (defvar jao-data-dir (expand-file-name "data" jao-emacs-dir) + "Directory containing static data, such as images.") + + (defun jao-data-file (file) (expand-file-name file jao-data-dir)) + + (defvar jao-org-dir (expand-file-name "~/org")) + + (defvar jao-sink-dir + (file-name-as-directory (expand-file-name "~/doc/sink")) + "Directory used for downloads and such.") + + (defvar jao-site-dir (expand-file-name "site" jao-emacs-dir)) + (defun jao-site-el (basename &optional gpg) + (expand-file-name (concat basename ".el" (when gpg ".gpg")) jao-site-dir)) + + (defun jao-load-site-el (basename &optional gpg) + (let ((lf (jao-site-el basename gpg))) + (if (file-exists-p lf) + (load lf) + (message "Attempted to load non existing %s" lf)))) + + (defun jao-exec-path (file) + (let ((fn (expand-file-name file))) + (add-to-list 'exec-path fn nil) + (setenv "PATH" (concat fn ":" (getenv "PATH"))))) + + (defun jao-load-path (subdir) + "Add to load path a subdir of `jao-local-lisp-dir'" + (let ((path (expand-file-name subdir jao-local-lisp-dir))) + (when (file-directory-p path) (add-to-list 'load-path path)))) + #+end_src +*** Load and info path initialisation + #+begin_src emacs-lisp + (add-to-list 'load-path jao-site-dir) + (add-to-list 'load-path jao-local-lisp-dir) + (add-to-list 'load-path (expand-file-name "custom" jao-emacs-dir)) + (add-to-list 'load-path "/usr/local/share/emacs/site-lisp/") + + (let ((libd (expand-file-name "lib" jao-emacs-dir))) + (add-to-list 'load-path libd) + (dolist (f (directory-files libd t "^[^.]+$")) + (when (file-directory-p f) (add-to-list 'load-path f)))) + + (defvar jao-info-dir (expand-file-name "~/doc/info")) + (require 'info) + (add-to-list 'Info-directory-list jao-info-dir) + #+end_src +*** Custom location of custom.el and co. + #+begin_src emacs-lisp + (setq custom-file (jao-site-el "custom")) + ;; (load custom-file) + (setq custom-unlispify-tag-names nil) + (setq custom-buffer-done-kill t) + (setq custom-raised-buttons nil) + #+end_src +*** Preamble (pre.el) + #+begin_src emacs-lisp + (jao-load-site-el "pre") + #+end_src +*** Session and history + #+BEGIN_SRC emacs-lisp + (setq backup-directory-alist (quote (("." . "~/.emacs.d/backups")))) + (setq delete-old-versions t + kept-new-versions 3 + kept-old-versions 2) + + (require 'saveplace) + (setq save-place-file (expand-file-name "~/.emacs.d/cache/places")) + (save-place-mode 1) + + (setq recentf-save-file (expand-file-name "~/.emacs.d/cache/recentf") + recentf-max-saved-items 2000 + recentf-exclude '("/home/jao/\\.emacs\\.d/elpa.*/.*" + ".*/.git/COMMIT_EDITMSG")) + (require 'recentf) + (recentf-mode 1) + + ;; Command history + (setq savehist-file (expand-file-name "~/.emacs.d/cache/history")) + (require 'savehist) + (savehist-mode t) + (setq savehist-additional-variables + '(kill-ring search-ring regexp-search-ring) + savehist-ignored-variables + '(ido-file-history)) + #+END_SRC +*** yes/no, bell, startup message + #+BEGIN_SRC emacs-lisp + ;;; change yes/no for y/n + (if (version< emacs-version "28") + (fset 'yes-or-no-p 'y-or-n-p) + (setq use-short-answers t)) + (setq inhibit-startup-message t) + (setq visible-bell t) + #+END_SRC +*** Server + #+begin_src emacs-lisp + (setenv "EDITOR" "emacsclient") + ;; (unless (daemonp) (server-start)) + #+end_src +* System utilities +*** Tramp + #+begin_src emacs-lisp + (setq tramp-mode nil) + #+end_src +*** Sleep/awake + #+begin_src emacs-lisp + (use-package jao-sleep) + (jao-sleep-dbus-register) + #+end_src +*** Process runners + #+begin_src emacs-lisp + (use-package jao-shell + :demand t + :config (jao-shell-def-exec jao-trayer "trayer.sh") + :bind (("s-r" . jao-shell-exec))) + + #+end_src +*** App launcher + #+begin_src emacs-lisp + (jao-load-path "app-launcher") + (use-package app-launcher + :bind (("s-R" . app-launcher-run-app))) + #+end_src +*** Brightness control + #+begin_src emacs-lisp + (jao-shell-def-exec jao-bright-set-up "brightnessctl" "-q" "s" "5%+") + (jao-shell-def-exec jao-bright-set-down "brightnessctl" "-q" "s" "5%-") + + (defun jao-bright-show () + (interactive) + (message "%s" (thread-first (jao-shell-string "brightnessctl") + (split-string "\n") + (cadr) + (string-trim)))) + + (defun jao-bright-up () + (interactive) + (jao-shell-string "brightnessctl -q s 5%%+") + (jao-bright-show)) + + (defun jao-bright-down () + (interactive) + (jao-shell-string "brightnessctl -q s 5%%-") + (jao-bright-show)) + #+end_src +*** Keyboard + #+begin_src emacs-lisp + (use-package repeat + :config (setq repeat-echo-function #'repeat-echo-mode-line)) + (when (> emacs-major-version 27) (repeat-mode)) + + (defun jao-kb-toggle (&optional lyt) + (interactive) + (shell-command-to-string (or lyt + (if (jao-kb-toggled-p) + "setxkbmap us" + "setxkbmap us -variant intl")))) + + (defun jao-kb-toggled-p () + (not (string-empty-p + (shell-command-to-string "setxkbmap -query|grep variant")))) + + (set-keyboard-coding-system 'latin-1) + (set-language-environment "UTF-8") + ;; must be set after current-language-environment + (customize-set-variable 'default-input-method "catalan-prefix") + ;; http://mbork.pl/2022-03-07_Transient_input_method + (customize-set-variable 'default-transient-input-method "TeX") + + (defun jao--set-kb-system (frame) + (select-frame frame) + (set-keyboard-coding-system 'latin-1) + t) + (add-to-list 'after-make-frame-functions 'jao--set-kb-system) + + (setq echo-keystrokes 1 + suggest-key-bindings nil) + #+end_src +*** Transient + #+begin_src emacs-lisp + (use-package transient + :init (setq transient-show-popup t) ;; 2.0 + :demand t + :config + (transient-bind-q-to-quit)) + + (defmacro jao-transient-major-mode (mode &rest suffix) + (declare (indent defun)) + (let ((mode (intern (format "%s-mode" mode))) + (mmap (intern (format "%s-mode-map" mode))) + (name (intern (format "jao-transient-%s" mode)))) + `(progn + (transient-define-prefix ,name () + ,(format "Transient ops for %s" mode) + [,(format "Operations for %s" mode) :if-derived ',mode ,@suffix]) + (define-key ,mmap (kbd "s-SPC") #',name)))) + + (defmacro jao-transient-major-mode+1 (mode suffix) + (declare (indent defun)) + (let ((name (intern (format "jao-transient-%s" mode)))) + (if (fboundp name) + `(transient-append-suffix ',name '(0 -1) ,suffix) + `(jao-transient-major-mode ,mode ,suffix)))) + + (defmacro jao-transient-major-mode+ (mode &rest suffixes) + (declare (indent defun)) + `(progn ,@(mapcar (lambda (s) `(jao-transient-major-mode+1 ,mode ,s)) + suffixes))) + + #+end_src +*** Disk + #+begin_src emacs-lisp + (when (featurep 'multisession) + (use-package jao-dirmon)) + #+end_src +* Crypto +*** PGP, EPG, passwords + #+begin_src emacs-lisp + (setq epg-pinentry-mode 'loopback) + (setq auth-source-debug nil) + + (require 'auth-source) + (add-to-list 'auth-source-protocols '(local "local")) + (setq auth-sources '("~/.emacs.d/authinfo.gpg" "~/.netrc")) + + (use-package epa-file + :init (setq epa-file-cache-passphrase-for-symmetric-encryption t) + :config (epa-file-enable)) + (require 'epa-file) + + (defun jao--get-user/password (h) + (let ((item (car (auth-source-search :type 'netrc :host h :max 1)))) + (when item + (let ((user (plist-get item :user)) + (pwd (plist-get item :secret))) + (list user (when pwd (funcall pwd))))))) + #+end_src +*** pass + #+begin_src emacs-lisp + (use-package password-store :ensure t + :bind (("C-c p" . jao-transient-password))) + + (transient-define-prefix jao-transient-password () + [[("c" "copy secret" password-store-copy) + ("C" "copy field" password-store-copy-field)] + [("i" "insert entry" password-store-insert) + ("e" "edit entry" password-store-edit) + ("g" "generate password" password-store-generate)] + [("d" "delete entry" password-store-remove) + ("r" "rename entry" password-store-rename)]]) + + #+end_src +*** Pinentry + #+begin_src emacs-lisp + (use-package pinentry :ensure t) + (pinentry-start) + #+end_src +* Fonts and colour themes +*** Widgets + #+begin_src emacs-lisp + (setq widget-image-enable nil + widget-link-prefix "" + widget-link-suffix "" + widget-button-prefix " " + widget-button-suffix " " + widget-push-button-prefix "" + widget-push-button-suffix "") + #+end_src +*** Fonts +***** fontsets + See [[https://emacs.stackexchange.com/questions/251/line-height-with-unicode-characters/5386#5386][fonts - Line height with unicode characters]] for a good + discussion. + #+begin_src emacs-lisp + (defun jao--set-fontsets (_f) + (set-fontset-font t 64257 "Quivira" nil) + (set-fontset-font t 'egyptian "Noto Sans Egyptian Hieroglyphs" nil) + (set-fontset-font t 'hangul "NanumGothicCoding" nil) + (set-fontset-font t 'unicode (face-attribute 'default :family) nil) + (set-fontset-font t 'unicode-bmp (face-attribute 'default :family) nil) + (set-fontset-font t 'symbol "Symbola-10" nil) + (set-fontset-font t 'greek "GFS Didot" nil) + (set-fontset-font t 'mathematical "FreeSerif" nil) + (set-fontset-font t 'emoji "Noto Color Emoji" nil) + ;; boxes + (set-fontset-font t '(9472 . 9599) "Source Code Pro" nil) + ;; variation selector-16 + (set-fontset-font t 65039 "BabelStone Modern-1" nil)) + + (jao--set-fontsets nil) + (add-to-list 'after-make-frame-functions 'jao--set-fontsets) + + #+end_src +***** nobreak char display + #+begin_src emacs-lisp + (setq nobreak-char-display nil) + #+end_src +***** list-fonts-display + #+begin_src emacs-lisp + (defun list-fonts-display (&optional matching) + "Display a list of font-families available via font-config, in a new buffer. + If the optional argument MATCHING is non-nil, only + font families matching that regexp are displayed; + interactively, a prefix argument will prompt for the + regexp. The name of each font family is displayed + using that family, as well as in the default font (to + handle the case where a font cannot be used to display + its own name)." + (interactive + (list + (and current-prefix-arg + (read-string "Display font families matching regexp: ")))) + (let (families) + (with-temp-buffer + (shell-command "fc-list : family" t) + (goto-char (point-min)) + (while (not (eobp)) + (let ((fam (buffer-substring (line-beginning-position) + (line-end-position)))) + (when (or (null matching) (string-match matching fam)) + (push fam families))) + (forward-line))) + (setq families + (sort families + (lambda (x y) (string-lessp (downcase x) (downcase y))))) + (let ((buf (get-buffer-create "*Font Families*"))) + (with-current-buffer buf + (erase-buffer) + (dolist (family families) + ;; We need to pick one of the comma-separated names to + ;; actually use the font; choose the longest one because some + ;; fonts have ambiguous general names as well as specific + ;; ones. + (let ((family-name + (car (sort (split-string family ",") + (lambda (x y) (> (length x) (length y)))))) + (nice-family (replace-regexp-in-string "," ", " family))) + (insert (concat (propertize nice-family + 'face (list :family family-name)) + " (" nice-family ")")) + (newline))) + (goto-char (point-min))) + (display-buffer buf)))) + #+end_src +*** Vertical separator + #+begin_src emacs-lisp + (unless (display-graphic-p) + (set-display-table-slot standard-display-table + 'vertical-border + (make-glyph-code ?│))) + #+end_src +*** Themes + #+begin_src emacs-lisp + (defun jao-colors-scheme-dark-p () + (equal "dark" (getenv "JAO_COLOR_SCHEME"))) + + (setq custom-theme-directory + (expand-file-name "lib/themes" jao-emacs-dir)) + + (require 'jao-themes) + + (defvar jao-theme-dark 'jao-dark) + (defvar jao-theme-light 'jao-light) + (defvar jao-theme-term-dark 'modus-vivendi) + (defvar jao-theme-term-light 'jao-light) + + (defun jao-themes-setup () + (let* ((dark (jao-colors-scheme-dark-p)) + (theme (cond ((and dark window-system) jao-theme-dark) + (dark jao-theme-term-dark) + (window-system jao-theme-light) + (t jao-theme-term-light)))) + (load-theme theme t) + (modify-all-frames-parameters `((font . ,jao-themes-default-face))))) + + (unless (eq window-system 'pgtk) (jao-themes-setup)) + + (global-font-lock-mode 1) + #+end_src +* Help system +*** Help buffers and shortcuts + #+begin_src emacs-lisp + (setq help-window-select t + help-link-key-to-documentation t) + + (use-package find-func + :bind (("C-h C-v" . find-variable) + ("C-h C-f" . find-function) + ("C-h C-k" . find-function-on-key) + ("C-h C-l" . find-library))) + #+end_src +*** eldoc + #+begin_src emacs-lisp + (use-package eldoc + :init (setq eldoc-mode-line-string nil + eldoc-echo-area-use-multiline-p t + eldoc-echo-area-prefer-doc-buffer nil + eldoc-display-functions '(eldoc-display-in-echo-area)) + :config (global-eldoc-mode 1) + :diminish ((eldoc-mode . ""))) + #+end_src +*** Bookmarks + #+begin_src emacs-lisp + (setq bookmark-default-file "~/.emacs.d/emacs.bmk" + bookmark-set-fringe-mark nil) + + #+end_src +*** Man pages + #+begin_src emacs-lisp + (setq Man-notify-method 'pushy) ;; pushy - same window + #+end_src +*** Recoll + #+begin_src emacs-lisp + (use-package jao-recoll) + #+end_src +* Window manager helpers +*** transparency + #+begin_src emacs-lisp + (defvar jao-transparent-only-bg (> emacs-major-version 28)) + + (defvar jao-frames-default-alpha + (cond ((eq window-system 'pgtk) 80) + (jao-transparent-only-bg 88) + (t 85))) + + (defvar jao-transparent-frame (< jao-frames-default-alpha 100)) + + (defun jao-transparent-p () jao-transparent-frame) + + (defun jao-alpha-parameters (&optional level) + (let ((level (or level jao-frames-default-alpha))) + (if jao-transparent-only-bg + `((alpha-background . ,level) (alpha)) + `((alpha . ,(cons level level)) (alpha-background))))) + + (defun jao-set-transparency (&optional level all) + (interactive "nOpacity (0-100): ") + (let ((level (or level jao-frames-default-alpha))) + (setq jao-transparent-frame (< level 100)) + (if all + (modify-all-frames-parameters (jao-alpha-parameters level)) + (modify-frame-parameters nil (jao-alpha-parameters level))))) + + (defun jao-toggle-transparency (&optional all) + (interactive "P") + (let ((level (if jao-transparent-frame 100 jao-frames-default-alpha))) + (jao-set-transparency level all))) + + #+end_src +*** exwm + #+begin_src emacs-lisp + (defvar jao-exwm-enabled nil) + (defun jao-exwm-enabled-p () jao-exwm-enabled) + + (defun jao-exwm-enable () + (require 'jao-custom-exwm) + (setq jao-exwm-enabled t) + (display-time-mode -1) + (exwm-enable) + (setq jao-frames-default-alpha 88) + (jao-set-transparency) + (x-change-window-property "_XMONAD_TRAYPAD" "" nil nil nil nil 0) + (jao-trisect t)) + #+end_src +*** xmonad + #+begin_src emacs-lisp + (defvar jao-xmonad-enabled (string= "xmonad" (or (getenv "wm") ""))) + (defun jao-xmonad-enabled-p () jao-xmonad-enabled) + + (defun jao-xmonad-enable () + (setq jao-browse-doc-use-emacs-p t) + (setq jao-wallpaper-random-wake t) + ;; (jao-set-transparency) + (jao-trisect) + (message "Welcome to xmonad")) + + (when jao-xmonad-enabled + (add-hook 'after-init-hook #'jao-xmonad-enable t)) + + #+end_src +*** sway + When starting emacs inside a sway session, we use ~-f + jao-sway-enable~ and don't load any separate configuration file. + + #+begin_src emacs-lisp + (defun jao-swaymsg (msg) + (shell-command (format "swaymsg '%s' >/dev/null" msg))) + + (defmacro jao-def-swaymsg (name msg) + `(defun ,(intern (format "jao-sway-%s" name)) () + (interactive) + (jao-swaymsg ,msg))) + (jao-def-swaymsg firefox "[app_id=firefox] focus") + + (defvar jao-sway-enabled + (and (eq window-system 'pgtk) (not jao-xmonad-enabled))) + + (defun jao-sway-set-wallpaper (f) + (jao-swaymsg (format "output * bg %s fill" f)) + (make-symbolic-link f "~/.wallpaper.sway" t)) + + (defun jao-sway-run-or-focus (cmd &optional ws) + (if (jao-shell-running-p "firefox") + (jao-swaymsg (format "[app_id=%s] focus" cmd)) + (jao-swaymsg (format "workspace %s" (or ws 2))) + (start-process-shell-command cmd nil cmd))) + + (defun jao-sway-run-or-focus-tidal () + (interactive) + (if (jao-shell-running-p "tidal-hifi") + (jao-swaymsg "[app_id=tidal-hifi] scratchpad show") + (start-process-shell-command "tidal-hifi" nil "tidal-hifi &") + (jao-sway-run-or-focus-tidal))) + + (defun jao-sway-run-or-focus-firefox () + (interactive) + (jao-sway-run-or-focus "firefox")) + + (defun jao-sway-enable () + (setq jao-browse-doc-use-emacs-p t) + (setq jao-wallpaper-random-wake nil) + (jao-trisect) + (jao-set-transparency 85) + (jao-themes-setup) + ;; (display-time-mode 1) + (global-set-key (kbd "s-f") #'jao-sway-run-or-focus-firefox) + (defalias 'jao-streaming-list #'jao-sway-run-or-focus-tidal) + (message "Welcome to sway")) + + (when jao-sway-enabled + (defalias 'x-change-window-property #'ignore) + (add-hook 'after-init-hook #'jao-sway-enable)) + + #+end_src +*** wallpaper + #+begin_src emacs-lisp + (defvar jao-wallpaper-dir "~/.wallpapers/") + + (defvar jao-wallpaper-random-candidates + '("wallpaper.jpg" "wallpaper2.jpg")) + + (defvar jao-wallpaper-random-candidates-light + '("wallpaper.jpg" "wallpaper2.jpg")) + + (defvar jao-wallpaper-random-wake t + "Set to t for getting a new wallpaper on awaking from sleep") + + (defun jao-set-wallpaper (&optional path) + (interactive) + (let ((current (format "~/.wallpaper.%s" + (if (jao-colors-scheme-dark-p) "dark" "light")))) + (when-let ((f (or path + (read-file-name "Image: " + jao-wallpaper-dir + (file-symlink-p current) + t)))) + (make-symbolic-link (expand-file-name f) current t) + (if jao-sway-enabled + (jao-sway-set-wallpaper (expand-file-name f)) + (shell-command (format "xwallpaper --zoom %s" f)))))) + + (defun jao-set-random-wallpaper () + (interactive) + (when (or (called-interactively-p 'interactive) + jao-wallpaper-random-wake) + (let* ((ws (if (jao-colors-scheme-dark-p) + jao-wallpaper-random-candidates + jao-wallpaper-random-candidates-light)) + (f (seq-random-elt ws))) + (jao-set-wallpaper (expand-file-name f jao-wallpaper-dir)) + (message "%s" f)))) + + (add-to-list 'jao-sleep-awake-functions #'jao-set-random-wallpaper) + #+end_src +*** screensaver and lock + #+begin_src emacs-lisp + (defun jao-screensaver-enabled () + (string= (jao-shell-string "xdg-screensaver status") "enabled")) + + (defun jao-screensaver-toggle () + (interactive) + (let ((wid (jao-shell-string "xdotool getwindowfocus"))) + (if (jao-screensaver-enabled) + (jao-shell-string "xdg-screensaver suspend" wid) + (jao-shell-string "xdg-screensaver resume" wid)) + (jao-notify (format "Using '%s'" + (jao-shell-string "xdotool getwindownames" wid)) + (format "Screensaver %s" + (jao-shell-string "xdg-screensaver status"))))) + + (jao-shell-def-exec jao-xlock-screen "xdg-screensaver" "activate") + (jao-shell-def-exec jao-suspend "sudo" "systemctl" "suspend") + (jao-shell-def-exec jao-poweroff "sudo" "systemctl" "poweroff") + + (defun jao-lock-screen () + (interactive) + (if jao-sway-enabled + (shell-command "swaylock -i ~/.lockimage") + (jao-xlock-screen))) + + (transient-define-prefix jao-transient-sleep () + ["Sleep" + ("l" "lock screen" jao-lock-screen) + ("z" "sleep" jao-suspend) + ("u" "enable/disable screensaver" jao-screensaver-toggle) + ("poof" "power-off" jao-poweroff)]) + + #+end_src +*** mouse + #+begin_src emacs-lisp + (dolist (k '([mouse-3] + [down-mouse-3] + [drag-mouse-3] + [double-mouse-3] + [mouse-4] + [down-mouse-4] + [drag-mouse-4] + [double-mouse-4] + [triple-mouse-4] + [mouse-5] + [down-mouse-5] + [drag-mouse-5] + [double-mouse-5] + [triple-mouse-5])) + (global-unset-key k)) + #+end_src +*** X clipboard + #+BEGIN_SRC emacs-lisp + (setq select-enable-clipboard t + select-enable-primary t + selection-timeout 100) + #+END_SRC +*** pop-up frames + #+begin_src emacs-lisp + (defun jao-open-in-x-frame (&optional width height) + (interactive) + (make-frame `((window-system . x) + (name . "emacs popup") + (width . ,(or width (window-width))) + (height . ,(or height (window-height))))) + (define-key (current-local-map) "q" #'delete-frame)) + #+end_src +*** xmobar + #+begin_src emacs-lisp + (defun jao-xmobar-kill () + (interactive) + (shell-command "killall xmobar-exwm")) + + (defun jao-xmobar-restart () + (interactive) + (jao-xmobar-kill) + (start-process "" nil "xmobar-exwm" "-d")) + + #+end_src +* Mode line and minibuffer +*** Time display + #+begin_src emacs-lisp + (setq display-time-world-list + '(("Europe/Paris" "Barcelona") + ("America/Los_Angeles" "Los Angeles") + ("America/New_York" "New York") + ("Europe/London" "London") + ("Asia/Calcutta" "Bangalore") + ("Asia/Tokyo" "Tokyo"))) + + (defun jao-time--pdt-hour () + (jao-time-at-zone "%H" "America/Los_Angeles")) + + (defun jao-time--chicago-hour () + (jao-time-at-zone "%H" "America/Chicago")) + + (defun jao-time-at-zone (format zone) + (set-time-zone-rule zone) + (prog1 (format-time-string format) + (set-time-zone-rule nil))) + + (defun jao-time-echo-la-time () + (interactive) + (message (jao-time-at-zone "LA %H:%M" "America/Los_Angeles"))) + + (defun jao-time-echo-times () + (interactive) + (let ((msg (format "%s (%s)" + (format-time-string "%a, %e %B - %H:%M") + (jao-time-at-zone "%H:%M" "America/Los_Angeles")))) + (jao-notify msg "" (jao-data-file "clock-world-icon.png")))) + + (defun jao-time-to-epoch (&optional s) + "Transform a time string to an epoch integer in milliseconds." + (interactive) + (let ((s (or s (read-string "Time string: " (thing-at-point 'string))))) + (message "%s = %s" + s + (round (* 1000 (time-to-seconds (parse-time-string s))))))) + + (defun jao-epoch-to-time (&optional v) + "Transform an epoch, given in milliseconds, to a time string." + (interactive) + (let ((v (or v (read-number "Milliseconds: " (thing-at-point 'number))))) + (message "%s = %s" v + (format-time-string "%Y-%m-%d %H:%M:%S" + (seconds-to-time (/ v 1000.0)))))) + + (setq display-time-day-and-date nil + display-time-24hr-format nil + display-time-default-load-average nil + display-time-format " %a %e %H:%M") + + #+end_src +*** Minibuffer + #+begin_src emacs-lisp + (defvar jao-modeline-in-minibuffer (and window-system t)) + + (use-package jao-minibuffer + :init + (if (jao-colors-scheme-dark-p) + (setq jao-minibuffer-active-buffer-line-color "azure4" + jao-minibuffer-inactive-buffer-line-color "grey25") + (setq jao-minibuffer-active-buffer-line-color "burlywood3" + jao-minibuffer-inactive-buffer-line-color "grey65")) + :commands (jao-minibuffer-add-variable + jao-minibuffer-refresh + jao-minibuffer-mode)) + + (use-package jao-mode-line + :commands (jao-mode-line-add-to-minibuffer + jao-mode-line-remove-from-minibuffer)) + + (setq enable-recursive-minibuffers t) + (require 'mb-depth) + (minibuffer-depth-indicate-mode 1) + (require 'minibuf-eldef) + (setq minibuffer-eldef-shorten-default t) + (minibuffer-electric-default-mode 1) + + (jao-minibuffer-mode 1) + + (when jao-modeline-in-minibuffer + (add-hook 'display-time-hook #'jao-minibuffer-refresh) + (add-hook 'after-init-hook + (lambda () (jao-mode-line-add-to-minibuffer 90)))) + #+end_src +*** Mode line format + #+begin_src emacs-lisp + (setq line-number-display-limit-width 250) + (setq mode-line-position-column-format '(" %c") + mode-line-position-line-format '(" %l,%c")) + (setq mode-line-percent-position + '(" %l" (:eval (format "/%d" (line-number-at-pos (point-max)))))) + (line-number-mode -1) + (column-number-mode 1) + #+end_src +*** Mode line toggle + #+begin_src emacs-lisp + (use-package jao-mode-line + :init + (when (and window-system (not jao-modeline-in-minibuffer)) + (add-to-list 'after-make-frame-functions #'jao-mode-line-hide-inactive) + (add-hook 'after-init-hook #'jao-toggle-inactive-mode-line)) + :demand t + :bind (("" . jao-mode-line-toggle-inactive) + ("" . jao-mode-line-toggle) + ("" . jao-mode-line-echo))) + #+end_src +*** Diminish + #+BEGIN_SRC emacs-lisp + (use-package diminish :ensure t) + (when (require 'use-package-diminish nil 'noerror) + (eval-after-load "simple" '(diminish 'auto-fill-function " §")) + (eval-after-load "autorevert" '(diminish 'auto-revert-mode ""))) + #+END_SRC +*** Battery + #+begin_src emacs-lisp + (use-package battery + :init (setq battery-load-low 15 + battery-load-critical 8 + battery-mode-line-limit 40 + battery-echo-area-format + "%L %r %B (%p%% load, remaining time %t)" + battery-mode-line-format " %b%p ")) ;; " 🔋%b%p " + (display-battery-mode 1) + (with-eval-after-load "jao-minibuffer" + (unless jao-modeline-in-minibuffer + (jao-minibuffer-add-variable 'battery-mode-line-string 80))) + #+end_src +* Notifications +*** alert + #+BEGIN_SRC emacs-lisp + (use-package alert + :ensure t + :init + (setq alert-default-style 'message ;; 'libnotify + alert-hide-all-notifications nil)) + #+END_SRC +*** jao-notify + #+begin_src emacs-lisp + (require 'jao-notify) + #+end_src +*** tracking + #+begin_src emacs-lisp + (use-package tracking + :demand t + :init (setq tracking-position 'before-modes + tracking-frame-behavior nil + tracking-most-recent-first nil + tracking-max-mode-line-entries 10 + tracking-sort-faces-first t + tracking-shorten-modes '()) + :config + (setq erc-track-enable-keybindings nil) + + (defun jao-tracking-next-buffer () + (interactive) + (tracking-next-buffer) + (jao-tracking-update-minibuffer)) + + :bind (("C-c C-SPC" . jao-tracking-next-buffer))) + + (use-package jao-tracking + :demand t + :init (setq jao-tracking-bkg + (if (jao-colors-scheme-dark-p) "grey20" "grey93"))) + #+end_src +*** tmr + #+begin_src emacs-lisp + (use-package tmr + :ensure t + :init + (setq tmr-sound-file "/usr/share/sounds/freedesktop/stereo/message.oga" + tmr-descriptions-list '("tea is ready"))) + #+end_src +* Calendar, diary, weather +*** Diary + #+BEGIN_SRC emacs-lisp + (setq diary-file (expand-file-name "diary" jao-org-dir) + diary-display-function 'diary-fancy-display + diary-mail-addr "jao@localhost" + diary-comment-start ";;" + diary-comment-end "") + + (add-hook 'diary-list-entries-hook 'diary-sort-entries t) + #+END_SRC +*** Calendar + #+begin_src emacs-lisp + (setq appt-display-format nil) + (appt-activate 1) + (setq calendar-latitude 55.9533 + calendar-longitude -3.1883 + calendar-location-name "Edinburgh, Scotland" + calendar-mark-diary-entries-flag t + calendar-date-echo-text '(format "ISO date: %s" + (calendar-iso-date-string + (list month day year)))) + + (setq calendar-holidays + '((holiday-fixed 1 1 "New Year's Day") + (holiday-fixed 4 1 "April Fools' Day") + (holiday-float 5 0 2 "Mother's Day") + (holiday-fixed 3 19 "Father's Day") + (holiday-float 11 4 4 "Thanksgiving") + (holiday-fixed 12 25 "Christmas") + (holiday-chinese-new-year) + (solar-equinoxes-solstices) + (holiday-sexp calendar-daylight-savings-starts + (format "Daylight Saving Time Begins %s" + (solar-time-string + (/ calendar-daylight-savings-starts-time + (float 60)) + calendar-standard-time-zone-name))) + (holiday-sexp calendar-daylight-savings-ends + (format "Daylight Saving Time Ends %s" + (solar-time-string + (/ calendar-daylight-savings-ends-time + (float 60)) + calendar-daylight-time-zone-name))))) + + (add-to-list 'display-buffer-alist + `(,(regexp-quote diary-fancy-buffer) + (display-buffer-at-bottom) + (window-parameters (mode-line-format . none)) + (window-height . fit-window-to-buffer))) + + (defun jao-diary--select () + (switch-to-buffer diary-fancy-buffer)) + + (add-hook 'diary-fancy-display-mode-hook #'jao-diary--select) + (setq org-calendar-insert-diary-entry-key nil + org-agenda-diary-file 'diary-file) + + #+end_src +*** Weather +***** winttr + #+begin_src emacs-lisp + (defun jao-weather (&optional wide) + (interactive "P") + (if (not wide) + (message "%s" + (jao-shell-string "curl -s" + "https://wttr.in/?format=%l++%m++%C+%c+%t+%w++%p")) + (jao-afio--goto-scratch) + (if-let ((b (get-buffer "*wttr*"))) + (progn (pop-to-buffer b) + (term-send-string "clear;curl wttr.in\n")) + (jao-exec-in-term "curl wttr.in" "*wttr*")))) + (global-set-key (kbd "") #'jao-weather) + #+end_src +*** Timers + #+BEGIN_SRC emacs-lisp + (put 'list-timers 'disabled nil) + #+END_SRC +* Files, dired and scratch buffer +*** so-long + #+begin_src emacs-lisp + (setq large-file-warning-threshold (* 200 1024 1024)) + + (use-package so-long + :ensure t + :diminish) + (global-so-long-mode 1) + #+end_src +*** Persistent scratch + #+BEGIN_SRC emacs-lisp + (use-package persistent-scratch + :ensure t + :config (persistent-scratch-setup-default)) + #+END_SRC +*** Automatically uncompress + #+BEGIN_SRC emacs-lisp + (require 'jka-compr) + (auto-compression-mode 1) + #+END_SRC +*** wgrep + #+begin_src emacs-lisp + (use-package wgrep :ensure t) + (require 'wgrep) + #+end_src +*** dired + - [[https://www.masteringemacs.org/article/working-multiple-files-dired][Working with multiple files in dired - Mastering Emacs]] + #+begin_src emacs-lisp + (use-package dired + :init + (setq dired-recursive-deletes 'top + dired-recursive-copies 'top + dired-listing-switches "-alhF --group-directories-first" + ls-lisp-dirs-first t + dired-dwim-target t + dired-kill-when-opening-new-dired-buffer t + dired-mouse-drag-files t + wdired-create-parent-directories t) + + (put 'dired-find-alternate-file 'disabled nil) + :hook (dired-mode . turn-on-gnus-dired-mode) + :bind (:map dired-mode-map + ("C-c C-r" . wdired-change-to-wdired-mode) + ("C-M-m" . gnus-dired-attach))) + + (use-package dired-x :demand t) + + (use-package find-dired + :init (setq find-ls-option '("-print0 | xargs -0 ls -ld" . "-ld")) + :bind ("C-c D" . find-name-dired)) + + (use-package dired-git-info + :ensure t + :bind (:map dired-mode-map (")" . dired-git-info-mode))) + + #+end_src +* General editing +*** Executable scripts + #+begin_src emacs-lisp + (add-hook 'after-save-hook + 'executable-make-buffer-file-executable-if-script-p) + #+end_src +*** Long lines + [[https://200ok.ch/posts/2020-09-29_comprehensive_guide_on_handling_long_lines_in_emacs.html][Comprehensive guide on handling long lines in Emacs - 200ok]] + #+begin_src emacs-lisp + (when (version<= "27.1" emacs-version) + (setq bidi-inhibit-bpa t)) + #+end_src +*** Spaces, tabs, kill + #+begin_src emacs-lisp + (setq kill-whole-line t) + (setq-default indent-tabs-mode nil) + (setq indent-tabs-width 4) + (setq-default default-tab-width 8) + (setq tab-always-indent t) + (setq kill-read-only-ok t) + (setq view-read-only nil) + #+end_src +*** Whitespace and filling column + #+begin_src emacs-lisp + (add-hook 'write-file-functions 'delete-trailing-whitespace) + (setq-default indicate-empty-lines nil) + (setq fill-column 78) + (setq comment-auto-fill-only-comments nil) + + (use-package whitespace + :init + (setq whitespace-style '(face tabs trailing ;; lines-tail + empty missing-newline-at-eof) + whitespace-line-column 80) + :hook (prog-mode . whitespace-mode) + :diminish) + + (use-package display-fill-column-indicator + :init (setq-default display-fill-column-indicator-column 80) + :hook (prog-mode . display-fill-column-indicator-mode)) + + #+end_src +*** Visible mode + #+begin_src emacs-lisp + (use-package visible-mode + :bind (("s-v" . visible-mode))) + #+end_src +*** Changes + #+begin_src emacs-lisp + (use-package goto-chg + :ensure t + :bind (("C-." . goto-last-change) + ("C-c ." . goto-last-change) + ("C-c ," . goto-last-change-reverse))) + #+end_src +*** Eval-and-replace + #+BEGIN_SRC emacs-lisp + (defun fc-eval-and-replace () + "Replace the preceding sexp with its value." + (interactive) + (backward-kill-sexp) + (condition-case nil + (prin1 (eval (read (current-kill 0))) + (current-buffer)) + (error (message "Invalid expression") + (insert (current-kill 0))))) + + (global-set-key "\C-ce" 'fc-eval-and-replace) + #+END_SRC +*** Skeletons and autoinsert + #+begin_src emacs-lisp + (use-package autoinsert + :config + (setq auto-insert-directory "~/.emacs.d/autoinsert/" + auto-insert t + auto-insert-query t) + (setf (alist-get 'html-mode auto-insert-alist nil t) nil)) + (add-hook 'find-file-hooks #'auto-insert) + + (use-package jao-skel + :demand t + :config + (require 'jao-skel-geiser) + (require 'jao-skel-lisp) + (require 'jao-skel-haskell) + (require 'jao-skel-latex)) + #+end_src +*** Undo + f to go forward + b to go backward + + n to go to the node below when you at a branching point + p to go to the node above + + a to go back to the last branching point + e to go forward to the end/tip of the branch + + #+begin_src emacs-lisp + (use-package vundo + :ensure t + :config + (set-face-attribute 'vundo-default nil :family "Symbola") + (setq vundo-glyph-alist vundo-unicode-symbols) + :bind (("C-?" . vundo))) + + #+end_src +*** Completion + #+begin_src emacs-lisp + (use-package jao-custom-completion) + #+end_src +* Buffers +*** cursor and mark + #+begin_src emacs-lisp + (transient-mark-mode -1) + (blink-cursor-mode -1) + (setq cursor-in-non-selected-windows nil) + #+end_src +*** uniquifiy + #+begin_src emacs-lisp + (require 'uniquify) + (setq uniquify-buffer-name-style 'forward + uniquify-trailing-separator-p t) + #+end_src +*** autosave + #+begin_src emacs-lisp + (setq auto-save-list-file-prefix "~/.emacs.d/auto-save-list/.saves-" + auto-save-no-message t + kill-buffer-delete-auto-save-files t) + + (setq lock-file-name-transforms + '(("\\`/.*/\\([^/]+\\)\\'" "/tmp/emacs-lock/\\1" t))) + #+end_src +*** autorevert + #+BEGIN_SRC emacs-lisp + (setq auto-revert-check-vc-info nil) + (setq auto-revert-verbose nil) + (setq auto-revert-avoid-polling t) + (setq auto-revert-mode-text "") + (require 'autorevert) + (global-auto-revert-mode 1) + #+END_SRC +*** attached buffers + #+begin_src emacs-lisp + (defun jao-display-buffer-below-selected (buffer alist) + (delete-other-windows-vertically) + (display-buffer-below-selected buffer alist)) + + (defun jao-attached-buffer-entry (name-rx height) + `(,name-rx (display-buffer-reuse-window + jao-display-buffer-below-selected) + (window-height . ,(or height 25)))) + + (defmacro jao-with-attached-buffer (name-rx height &rest body) + (declare (indent defun)) + `(let ((display-buffer-alist '(,(jao-attached-buffer-entry name-rx height)))) + ,@body)) + + (defun jao-define-attached-buffer (name-rx &optional height) + (add-to-list 'display-buffer-alist + (jao-attached-buffer-entry name-rx height))) + + #+end_src +*** images + #+begin_src emacs-lisp + (setq image-use-external-converter t) + (setq widget-image-enable nil) + #+end_src +*** same mode + #+begin_src emacs-lisp + (defun jao-buffer-same-mode (&optional mode pre-fn switch-fn) + (interactive) + (let* ((mode (or mode major-mode)) + (modes (if (symbolp mode) (list mode) mode)) + (pred `(lambda (b) + (let ((b (get-buffer (if (consp b) (car b) b)))) + (member (buffer-local-value 'major-mode b) + ',modes)))) + (buff (read-buffer "Buffer: " nil t pred))) + (when pre-fn (funcall pre-fn)) + (if switch-fn (funcall switch-fn buff) (pop-to-buffer buff)))) + (global-set-key (kbd "C-c C-b") #'jao-buffer-same-mode) + #+end_src +*** projects + #+begin_src emacs-lisp + (use-package project + :bind (("C-x C-p" . project-prefix-map))) + #+end_src +*** buffer quit function (the triple ESC) + #+begin_src emacs-lisp + (setq buffer-quit-function (lambda () t)) + #+end_src +*** pulsar + #+begin_src emacs-lisp + (use-package pulsar + :ensure t + :diminish + :custom ((pulsar-pulse-functions + '(ace-window + backward-page + delete-other-windows + delete-window + forward-page + jao-prev-window + move-to-window-line-top-bottom + org-backward-heading-same-level + org-forward-heading-same-level + org-next-visible-heading + org-previous-visible-heading + other-window + outline-backward-same-level + outline-forward-same-level + outline-next-visible-heading + outline-previous-visible-heading + outline-up-heading + recenter-top-bottom + reposition-window + scroll-down-command + scroll-up-command)) + (pulsar-pulse t) + (pulsar-delay 0.1) + (pulsar-iterations 10) + (pulsar-face 'pulsar-yellow) + (pulsar-highlight-face 'pulsar-face)) + :hook ((jao-afio-switch . pulsar-pulse-line))) + + (pulsar-global-mode) + + #+end_src +* Windows +*** scrolling + #+begin_src emacs-lisp + (setq scroll-preserve-screen-position 'always + scroll-conservatively most-positive-fixnum + scroll-margin 4 + scroll-step 2 + redisplay-skip-fontification-on-input t) + #+end_src +*** splitting and switch + #+begin_src emacs-lisp + (setq split-height-threshold 80 + split-width-threshold 144 + display-buffer-avoid-small-windows 20) + + (setq switch-to-buffer-preserve-window-point nil + switch-to-buffer-obey-display-actions t + switch-to-prev-buffer-skip 'this) ;; don't switch to a + ;; buffer already visible in + ;; this frame + + (global-set-key (kbd "C-x _") #'delete-other-windows-vertically) + #+end_src +*** first window + #+begin_src emacs-lisp + (defvar jao-first-window--from nil) + + (defun jao-first-window () + "Go to previous windows in frame, remembering where we were." + (interactive) + (let ((cb (current-buffer))) + (if (eq (get-buffer-window cb) (select-window (frame-first-window))) + (when jao-first-window--from (pop-to-buffer jao-first-window--from)) + (setq jao-first-window--from cb)))) + + (defun jao-prev-window () + "Go to previous window." + (interactive) + (other-window -1)) + + (global-set-key (kbd "C-x p") #'jao-prev-window) + (global-set-key (kbd "s-a") #'jao-first-window) + (global-set-key (kbd "M-a") #'jao-first-window) + + #+end_src +*** ace window + #+begin_src emacs-lisp + (use-package ace-window + :ensure t + :demand t + :init (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l) + aw-char-position 'top-left + aw-ignore-current nil + aw-dispatch-when-more-than 2 + aw-leading-char-style 'path + aw-display-mode-overlay t + aw-scope 'frame) + :config + + (defun jao-ace-consult-buffer-other-window (w) + (interactive) + (aw-switch-to-window w) + (consult-buffer)) + + (setf (alist-get ?b aw-dispatch-alist) + '(jao-ace-consult-buffer-other-window "Consult buffer")) + + (setf (alist-get ?B aw-dispatch-alist) + (alist-get ?u aw-dispatch-alist)) + + + :bind (("M-o" . ace-window) + ("M-O" . ace-swap-window) + ("C-x 4 t" . ace-swap-window))) + + #+end_src +*** window navigation (custom) + #+begin_src emacs-lisp + (defun jao-nth-window (n) + (if (zerop n) + 'jao-first-window + `(lambda () + (interactive) + (select-window (frame-first-window)) + (dotimes (x ,n) (other-window 1))))) + + (defun jao-prev-window () + "Go to previous window" + (interactive) + (other-window -1)) + + (defun jao-next-window () + "Go to previous window" + (interactive) + ;; next-window-any-frame + (other-window 1)) + + (global-set-key (kbd "C-x p") 'jao-prev-window) + (global-set-key (kbd "C-x o") 'other-window) + (mapc (lambda (n) + (global-set-key (format "\C-c%s" (1+ n)) (jao-nth-window n))) + '(0 1 2 3 4 5 6 7 8)) + + ;; transposing windows + (defun transpose-windows (arg) + "Transpose the buffers shown in two windows." + (interactive "p") + (let ((selector (if (>= arg 0) 'next-window 'previous-window))) + (while (/= arg 0) + (let ((this-win (window-buffer)) + (next-win (window-buffer (funcall selector)))) + (set-window-buffer (selected-window) next-win) + (set-window-buffer (funcall selector) this-win) + (select-window (funcall selector))) + (setq arg (if (plusp arg) (1- arg) (1+ arg)))))) + + (define-key ctl-x-4-map (kbd "t") 'transpose-windows) + #+end_src + + #+RESULTS: + : transpose-windows +*** winner mode + #+begin_src emacs-lisp + (winner-mode 1) + #+end_src +* Frames +*** Frame geometry + #+begin_src emacs-lisp + (setq frame-resize-pixelwise t) + (modify-all-frames-parameters + `((horizontal-scroll-bars . nil) + (vertical-scroll-bars . nil) + (scroll-bar-width . 0) + (menu-bar . nil))) + #+end_src +*** Frame layout, title, etc. + #+begin_src emacs-lisp + (setq frame-title-format '("%b")) + (use-package fringe) + (fringe-mode) + + (menu-bar-mode -1) + + ;; (setting it to nil avoids mouse wrapping after other-frame) + (setq focus-follows-mouse t) + + (use-package scroll-bar) + (set-scroll-bar-mode nil) + (use-package tool-bar) + (tool-bar-mode -1) + + (defun jao-trisect (&optional force) + (interactive) + (let ((fw (frame-width))) + (delete-other-windows) + (cond ((or force (>= fw 240)) + (let ((w (- (/ fw 3)))) + (delete-other-windows) + (split-window-horizontally w) + (split-window-horizontally w) + (balance-windows))) + ((> fw 162) + (split-window-horizontally) + (switch-to-buffer (other-buffer)))))) + + (defun jao-bisect () + (interactive) + (jao-trisect t) + (next-window) + (delete-window)) + #+end_src +*** afio + #+begin_src emacs-lisp + (use-package jao-afio) + + (defun jao-xmonad-goto-1 () + (shell-command "sendCommand 1")) + + (defun jao-afio--goto-scratch-1 () + (interactive) + (jao-afio-goto-scratch t)) + + (jao-afio-setup 'jao-afio--goto-scratch-1 t) + + (defun jao-current--frame-id () + (propertize (if (and (jao-exwm-enabled-p) + (not (bound-and-true-p jao-exwm--use-afio))) + (format "F%s" exwm-workspace-current-index) + (format "%s" (jao-afio-current-no))) + 'face 'font-lock-warning-face)) + + (add-hook 'jao-afio-switch-hook #'tracking-remove-visible-buffers) + (jao-minibuffer-add-variable '(jao-current--frame-id) 100) + + (defun jao-afio--set-mode-line () + (when (and window-system (fboundp 'jao-mode-line-hide-inactive)) + (if (string= "Docs" (jao-afio-current-frame)) + (jao-mode-line-show-inactive nil) + (jao-mode-line-hide-inactive nil)))) + + (unless jao-modeline-in-minibuffer + (add-hook 'jao-afio-switch-hook #'jao-afio--set-mode-line)) + #+end_src +* Writing and writing modes +*** Copyright notices + #+begin_src emacs-lisp + (setq copyright-year-ranges t) + (add-hook 'write-file-functions 'copyright-update) + #+end_src +*** Indent on yank + #+begin_src emacs-lisp + ;;; indent on yank + (defvar jao-auto-indent-modes + '(emacs-lisp-mode ;; clojure-mode + scheme-mode objc-mode + tuareg-mode c-mode c++-mode + tcl-mode sql-mode + perl-mode cperl-mode + java-mode jde-mode + LaTeX-mode TeX-mode)) + + (defadvice yank (after indent-region activate) + (if (member major-mode jao-auto-indent-modes) + (indent-region (region-beginning) (region-end) nil))) + #+end_src +*** Org mode + #+begin_src emacs-lisp + (use-package jao-custom-org) + #+end_src +*** Blog + #+begin_src emacs-lisp + (use-package jao-custom-blog) + #+end_src +*** Text-ish mode settings + #+begin_src emacs-lisp + ;;; SENTENCES separated by just one space + (setq sentence-end "[.?!][]\"')]*\\($\\|\t\\| \\)[ \t\n]*") + (setq sentence-end-double-space t) + ;;; copy rectangle + (defun kill-rectangle-save (start end) + "Save the region-rectangle as the last killed one." + (interactive "r") + (require 'rect) ; Make sure killed-rectangle is defvar'ed. + (setq killed-rectangle (extract-rectangle start end)) + (message "Rectangle saved")) + + ;; text mode, autoinserts and write hooks + ;;; misc + (setq default-major-mode 'text-mode) + + (add-hook 'text-mode-hook 'turn-on-auto-fill) + #+end_src +*** Dictionaries + #+begin_src emacs-lisp + (defun jao-word-definition-lookup () + "Look up the word under cursor in a browser." + (interactive) + (require 'thingatpt) + (browse-url + (concat "http://www.wordnik.com/words/" + ;; "http://www.answers.com/main/ntquery?s=" + (thing-at-point 'word)))) + + (use-package dictionary + :init (setq dictionary-use-single-buffer t + dictionary-server "localhost") + :commands (dictionary-search + dictionary-match-words + dictionary-lookup-definition + dictionary + dictionary-mouse-popup-matching-words + dictionary-popup-matching-words + dictionary-tooltip-mode + global-dictionary-tooltip-mode) + :bind (("C-c d" . dictionary-search))) + + (setq ispell-personal-dictionary + (expand-file-name "~/.emacs.d/ispell.dict")) + + (use-package wordreference + :ensure t + :init (setq wordreference-target-lang "es" + wordreference-source-lang "en") + :bind (("C-c D" . wordreference-search))) + + #+end_src +*** Markdown + #+BEGIN_SRC emacs-lisp + (use-package markdown-mode + :ensure t) + + (use-package markdown-toc + :ensure t) + + (dolist (ext '("\\.md$" "\\.markdown$")) + (add-to-list 'auto-mode-alist (cons ext 'markdown-mode))) + #+END_SRC +*** TeX and LaTex + #+begin_src emacs-lisp + (use-package tex-site + :ensure auctex + :init + (setq TeX-auto-save t) + (setq TeX-parse-self t) + (setq TeX-a4-paper t) + (setq TeX-auto-local ".tex-auto-local") + ;; Preferred view format: dvi, ps, pdf, pdfs + (setq TeX-view-format "pdf") + (setq-default TeX-master "../main") ; nil to ask + (setq TeX-view-program-selection + ;; '((output-dvi "open") + ;; (output-pdf "open") + ;; (output-html "open")) + '(((output-dvi has-no-display-manager) "dvi2tty") + ((output-dvi style-pstricks) "dvips and gv") + (output-dvi "xdvi") + (output-pdf "xdg-open") + (output-html "xdg-open"))) + ;; to make RefTeX faster for large documents, try these: + (setq reftex-enable-partial-scans t) + (setq reftex-save-parse-info t) + (setq reftex-use-multiple-selection-buffers t) + ;; to integrate with AUCTeX + (setq reftex-plug-into-AUCTeX t) + (setq reftex-ref-style-default-list + '("Hyperref" "Varioref" "Fancyref")) + (setq LaTeX-command "latex -shell-escape") + (setq LaTeX-biblatex-use-Biber t) + (setq bibtex-dialect 'biblatex) + :config + (add-hook 'TeX-after-compilation-finished-functions + #'TeX-revert-document-buffer) + (add-hook 'LaTeX-mode-hook 'turn-on-reftex) + ) + + ;; (use-package ebib + ;; :ensure t + ;; :config (setq ebib-bibtex-dialect 'biblatex)) + + ;; for M-x biblio-lookup + ;; (use-package biblio :ensure t) + #+end_src +* Browsing +*** Variables + #+begin_src emacs-lisp + (defvar jao-browse-doc-use-emacs-p t) + (defvar jao-browse-url-function nil) + (defvar jao-browse-url-external-function nil) + #+end_src +*** URL around point + #+begin_src emacs-lisp + (defun jao-url-around-point (&optional current-url) + (or (and (fboundp 'w3m-anchor) (w3m-anchor)) + (shr-url-at-point nil) + (ffap-url-at-point) + (thing-at-point 'url) + (when current-url + (or (and (fboundp 'w3m-anchor) (w3m-anchor)) + (and (derived-mode-p 'eww-mode) (plist-get eww-data :url)))))) + + (defun jao--url-prompt () + (let* ((def (jao-url-around-point t)) + (prompt (concat "URL" (if def (format " (%s): " def) ": ")))) + (read-string prompt nil nil def))) + #+end_src +*** Downloads using wget + #+BEGIN_SRC emacs-lisp + (defun jao-wget--get-title (filename) + (let ((fn (file-name-base filename))) + (if (string-blank-p fn) + (plist-get eww-data :title) + (subst-char-in-string ?- ? (capitalize fn))))) + + (defun jao-wget (url &optional user pwd) + "Download URL using wget." + (let* ((def (file-name-nondirectory url)) + (pmt (format "Save %s to: " url)) + (read-file-name-function nil) + (dest (expand-file-name + (read-file-name pmt jao-sink-dir nil nil def))) + (title (jao-wget--get-title dest)) + (src-url (jao-url-around-point t)) + (auth (when (and user pwd) + `(,(format "--http-user=%s" user) + ,(format "--http-password=%s" pwd))))) + (switch-to-buffer-other-window (get-buffer-create "*downloads*")) + (erase-buffer) + (kill-new (format "[[doc:%s][%s]] (from [[%s][here]])" + (file-name-nondirectory dest) + (read-string "Title: " title) + (or src-url (file-name-directory url)))) + (apply 'make-term `("downloads" "wget" nil ,@auth "-O" ,dest ,url)))) + + (defun jao-download (url &optional pws) + "Download URL using wget" + (interactive (list (jao--url-prompt))) + (when url + (let ((usr (and pws (read-string "Login name: "))) + (pwd (and pws (read-passwd "Password: ")))) + (jao-wget url usr pwd)))) + + (with-eval-after-load "embark" + (define-key embark-url-map (kbd "d") #'jao-download)) + + #+END_SRC +*** Video + #+BEGIN_SRC emacs-lisp + (defvar jao-video--url-rx + (format "^https?://\\(?:www\\.\\)?%s/.+" + (regexp-opt '("youtu.be" + "youtube.com" + "blip.tv" + "vimeo.com" + "infoq.com") + t))) + + (defvar jao-video--ext-rx + (format "^https?://.+/.+\\.%s" (regexp-opt '("mp3" "webm" "mp4")))) + + (defun jao-video--url-p (url) + (or (string-match-p jao-video--url-rx url) + (string-match-p jao-video--ext-rx url))) + + (defun jao--remote-run (url prg) + (let ((args (format "%s %s" prg (shell-quote-argument url)))) + (start-process-shell-command prg nil args))) + + (defun jao--mpv (url &rest args) (jao--remote-run url "mpv")) + (defun jao--vlc (url &rest args) (jao--remote-run url "vlc")) + + (defvar jao--video-player 'jao--mpv) + + (defun jao-view-video (url) + "Tries to stream a video from the current or given URL" + (interactive (list (jao--url-prompt))) + (when url (funcall jao--video-player url))) + + (defun jao-maybe-view-video (url &rest _ignored) + (interactive) + (if (y-or-n-p "View video (y) or web page (n)? ") + (jao-view-video url) + (funcall jao-browse-url-function url))) + + #+END_SRC +*** Web browsers + #+begin_src emacs-lisp + (defun jao-www--buffer-p (b) + (with-current-buffer b + (or (derived-mode-p 'w3m-mode 'eww-mode) + (and (boundp 'exwm-class-name) + (member (buffer-local-value 'exwm-class-name b) + '("vlc" "mpv")))))) + + (use-package jao-custom-eww) + #+end_src +*** Browse URL + #+begin_src emacs-lisp + (require 'browse-url) + + (setq browse-url-generic-program "~/bin/firehog") + + (defun jao-browse-with-external-browser (&rest url) + "Browse with external hogging" + (interactive "s") + (let ((url (or (car url) (jao-url-around-point)))) + (if (not url) + (message "No URL at point") + (when (and (jao-exwm-enabled-p) (fboundp 'jao-exwm-firefox)) + (jao-exwm-firefox)) + (when (and jao-sway-enabled (fboundp 'jao-sway-firefox)) + (jao-sway-firefox)) + (browse-url-generic url)))) + (setq jao-browse-url-external-function 'jao-browse-with-external-browser) + + (defun jao--fln (url) + (shell-quote-argument + (if (string-match "^[^:]*:/*?\\(/?[^/].*\\)" url) + (match-string-no-properties 1 url) + url))) + + (defun jao--browse-doc (url search &optional no-add) + (let* ((url (substring-no-properties url)) + (file (jao--fln url))) + (when file + (unless (file-exists-p file) + (error "File %s does not exist" file)) + (jao-open-doc file)))) + + (defun jao--make-file-rx (exts) + (format "file:/?/?.+\\.%s$" (regexp-opt exts))) + + (defvar jao--see-exts + (jao--make-file-rx '("jpg" "jpeg" "png" "mov" "wmv" "avi" "mp4"))) + + (defvar jao--doc-exts + (jao--make-file-rx '("ps" "ps.gz" "pdf" "dvi" "djvu" "chm"))) + + (defvar jao-browse-url-wget-exts + '("ps" "pdf" "dvi" "djvu" "zip" "gz" "tgz" "mp4" "mp3" "flv")) + + (defvar jao-browse-external-domains + '("github.com" "gitlab.com" "slack.com" "meet.google.com" + "twitter.com" "t.com" "linkedin.com" "bigml.com")) + + (defvar jao-browse--external-regexp + (format "https?://.*%s\\(/.*\\)?" + (regexp-opt jao-browse-external-domains))) + + (defun jao-wget--regexp () + (concat "^http[s]?://.+\\(\\." + (mapconcat 'identity jao-browse-url-wget-exts "\\|\\.") + "\\)\\'")) + + (defun jao--see (url &rest _r) + (start-process-shell-command "see" nil (format "see %s" (jao--fln url)))) + + (defun jao--find-file-other-window (url &rest _) + (find-file-other-window (jao--fln url))) + + (use-package elpher :ensure t) + + (defun jao-elpher--browse (url &rest _) (elpher-go url)) + + (defvar jao-browse--sound-rx + (format "^https?://.*/.*\\.%s" (regexp-opt '("mp4" "mp3" "flv")))) + + (defun jao-browse-add-url-to-mpc (url &rest _) + "Add the given URL to mpc's playing list, or just play it." + (let ((p (yes-or-no-p (format "Play %s right now?" url)))) + (when p (jao-mpc-clear)) + (jao-mpc-add-url url) + (if p (jao-mpc-play) (message "%s added to mpc queue" url)))) + + (defun jao-browse-url-browse (&rest args) + (apply jao-browse-url-function args)) + + (setq browse-url-handlers + `(("^\\(gemini\\|gopher\\)://.*" . jao-elpher--browse) + (,jao--doc-exts . jao--browse-doc) + (,jao--see-exts . jao--see) + ("^file://?.+\\.html?$" . ,jao-browse-url-function) + ("^file://?" . jao--find-file-other-window) + (,jao-browse--external-regexp . ,jao-browse-url-external-function) + ("^https?://.*\\.gotomeeting\\.com\\.*" . browse-url-chrome) + (,jao-browse--sound-rx . jao-browse-add-url-to-mpc) + (,(jao-wget--regexp) . jao-download) + (jao-video--url-p . jao-maybe-view-video) + ("." . jao-browse-url-browse))) + + (when (< emacs-major-version 28) + (setf (alist-get 'jao-video--url-p browse-url-handlers nil t) nil) + (setq browse-url-browser-function browse-url-handlers)) + + #+end_src +*** Subscribe rss using r2e + #+begin_src emacs-lisp + (autoload 'View-quit "view") + + (defun jao-rss--find-url () + (save-excursion + (when (derived-mode-p 'w3m-mode 'eww-mode) + (if (fboundp 'w3m-view-source) (w3m-view-source) (eww-view-source))) + (goto-char (point-min)) + (when (re-search-forward + "type=\"application/\\(?:atom\\|rss\\)\\+xml\" +" nil t) + (let ((url (save-excursion + (when (re-search-forward + "href=\"\\([^\n\"]+\\)\"" nil t) + (match-string-no-properties 1)))) + (title (when (re-search-forward + "\\(?:title=\"\\([^\n\"]+\\)\" +\\)" nil t) + (match-string-no-properties 1)))) + (cond ((derived-mode-p 'w3m-view-mode) (w3m-view-source)) + ((string-match-p ".*\\*eww-source\\b.*" (buffer-name)) + (View-quit))) + (when url (cons url (or title ""))))))) + + (defun jao-rss2e-append (name url mbox) + (with-current-buffer (find-file-noselect "~/.config/rss2email.cfg") + (goto-char (point-max)) + (insert "[feed." name "]\nurl = " url) + (insert "\nto = " mbox "+" name "@localhost") + (insert "\nmaildir-mailbox = " mbox "\n\n") + (save-buffer))) + + (defun jao-rss--feeds-dirs () + (mapcar (lambda (d) (cadr (split-string d "\\."))) + (directory-files "~/.emacs.d/gnus/Mail/" nil "^feeds"))) + + (defun jao-rss-subscribe (url) + "Subscribe to a given RSS URL. If URL not given, look for it." + (interactive (list (or (jao-url-around-point) + (jao-rss--find-url) + (read-string "Feed URL: ")))) + (let* ((url+title (if (consp url) url (list url))) + (url (car url+title)) + (title (cdr url+title)) + ;; (cats (cons "prog" (jao-notmuch--subtags "feeds"))) + (cats (jao-rss--feeds-dirs))) + (if url + (let ((url (if (string-match "^feed:" url) (substring url 5) url))) + (when (y-or-n-p (format "Subscribe to <%s>? " url)) + (let* ((name (read-string "Feed name: " title)) + (cat (completing-read "Category: " cats nil t)) + (subs (format "r2e add %s '%s' feeds.%s@localhost" + name url cat))) + ;; (jao-rss2e-append name url cat) + (shell-command-to-string subs) + (shell-command (format "r2e run %s" name))))) + (message "No feeds found")))) + #+end_src +* Email + #+begin_src emacs-lisp + (setq jao-afio-mail-function 'notmuch) + (use-package jao-custom-email) + (use-package jao-custom-notmuch) + #+end_src +* PDFs and other docs +*** doc-view + #+begin_src emacs-lisp + (use-package doc-view + :init + (setq doc-view-cache-directory "~/.emacs.d/cache/docview" + doc-view-resolution 110 + doc-view-continuous t + doc-view-conversion-refresh-interval 1) + + :bind (:map doc-view-mode-map + ("j" . doc-view-next-line-or-next-page) + ("J" . doc-view-scroll-up-or-next-page) + ("k" . doc-view-previous-line-or-previous-page) + ("K" . doc-view-scroll-down-or-previous-page))) + + (use-package jao-doc-view + :bind (:map doc-view-mode-map + ("b" . jao-doc-view-back) + ("B" . jao-doc-view-forward) + ("S" . jao-doc-view-save-session) + ("u" . jao-doc-view-visit-url))) + #+end_src +*** pdf-tools + #+begin_src emacs-lisp + (use-package pdf-tools + :ensure t + :demand t + :init + (add-hook 'after-init-hook + (lambda () + (setq pdf-view-midnight-colors + (cons (frame-parameter nil 'foreground-color) + (frame-parameter nil 'background-color))))) + + :config (pdf-tools-install) + + :diminish ((pdf-view-midnight-minor-mode . "")) + + :bind (:map pdf-view-mode-map + (("C-c C-d" . pdf-view-midnight-minor-mode) + ("j" . pdf-view-next-line-or-next-page) + ("J" . pdf-view-scroll-up-or-next-page) + ("k" . pdf-view-previous-line-or-previous-page) + ("K" . pdf-view-scroll-down-or-previous-page)))) + + #+end_src +*** zathura + #+begin_src emacs-lisp + (defun jao-zathura-file-info (title) + (when (string-match "\\(.+\\) \\[\\(.+\\) (\\([0-9]+\\)/\\([0-9]+\\))\\]" + title) + (list (expand-file-name (match-string 1 title)) + (string-to-number (match-string 3 title)) + (string-to-number (match-string 4 title)) + (match-string 2 title)))) + + (defun jao-zathura-goto-org (&optional title) + (when-let* ((title (or title (jao-shell-string "xdotool" + "getactivewindow" + "getwindowname"))) + (info (jao-zathura-file-info title)) + (file (jao-org-pdf-to-org-file (car info))) + (page (cadr info)) + (pageno (or (car (last info)) page))) + (ignore-errors (jao-afio--goto-docs)) + (let* ((exists (file-exists-p file)) + (fn (file-name-nondirectory file)) + (lnk (format "[[doc:%s::%d][Page %s]]" fn page pageno))) + (find-file file) + (unless exists (jao-org-insert-doc-skeleton)) + (if (or (not exists) (y-or-n-p "Insert link?")) + (insert lnk "\n") + (kill-new lnk) + (message "Link to %s (%s) killed" file page))))) + + (defun jao-zathura-open (file page) + (let ((id (jao-shell-string (format "xdotool search --name %s" + (file-name-nondirectory file))))) + (if (string-blank-p id) + (progn + (when jao-xmonad-enabled + (jao-shell-exec "xdotool set_destktop 2")) + (jao-shell-exec (format "zathura %s -P %s" file (or page 1)))) + (let* ((page (if page (format " && xdotool type %dg" page) "")) + (cmd (format "xdotool windowactivate %s%s" id page))) + (jao-shell-string cmd))))) + + #+end_src +*** open pdfs + #+begin_src emacs-lisp + (use-package saveplace-pdf-view + :ensure t + :demand t + :after doc-view) + + (setq jao-open-doc-fun 'jao-find-or-open) + (setq jao-org-open-pdf-fun 'jao-find-or-open) + + (defun jao-find-or-open (file &optional page height) + (if (and jao-browse-doc-use-emacs-p window-system) + (let* ((buffs (buffer-list)) + (b (catch 'done + (while buffs + (when (string-equal (buffer-file-name (car buffs)) file) + (throw 'done (car buffs))) + (setq buffs (cdr buffs)))))) + (jao-afio--goto-docs) + (if b (pop-to-buffer b) (find-file file)) + (when page (jao-doc-view-goto-page page height))) + (jao-zathura-open file page))) + + (defun jao-open-doc (&optional file page height) + (interactive) + (when-let (file (or file + (read-file-name "Document: " + (concat jao-org-dir "/doc/")))) + (funcall jao-open-doc-fun file page height))) + + (defun jao-select-pdf () + (interactive) + (jao-buffer-same-mode '(pdf-view-mode doc-view-mode) + #'jao-afio--goto-docs)) + #+end_src +*** epub + #+begin_src emacs-lisp + (use-package nov + :ensure t + :after doc-view + :init (setq nov-variable-pitch t + nov-text-width 80) + :config (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))) + + #+end_src +*** transient + #+begin_src emacs-lisp + (defun jao-org-pdf-goto-org-linking () + (interactive) + (jao-org-pdf-goto-org 4)) + + (jao-transient-major-mode doc-view + ["Notes" + ("o" "notes file" jao-org-pdf-goto-org) + ("O" "notes file, linking" jao-org-pdf-goto-org-linking)] + ["Navigation" + ("b" "back jump" jao-doc-view-back) + ("B" "forward jump" jao-doc-view-back) + ("u" "visit URL" jao-doc-view-visit-url)] + ["Slices" + ("cb" "bounding box" doc-view-set-slice-from-bounding-box) + ("cm" "using mouse" doc-view-set-slice-using-mouse)] + ["Session" + ("s" "load session" jao-afio-open-pdf-session) + ("S" "save session" jao-doc-view-save-session) + ("d" "visit cache directory" doc-view-dired-cache)]) + + (with-eval-after-load "pdf-view" + (jao-transient-major-mode pdf-view + ["Notes" + ("o" "notes file" jao-org-pdf-goto-org) + ("O" "notes file, linking" jao-org-pdf-goto-org-linking)] + ["Navigation" + ("b" "back jump" pdf-history-backward) + ("f" "forward jump" pdf-history-forward)] + ["Session" + ("s" "load session" jao-afio-open-pdf-session) + ("S" "save session" jao-doc-view-save-session)])) + + ;; (transient-get-suffix 'jao-transient-pdf-view '(0 -1)) + + #+end_src +* Shells and terms +*** shell modes + #+begin_src emacs-lisp + (setq sh-basic-offset 2) + ;; translates ANSI colors into text-properties, for eshell + (autoload 'ansi-color-for-comint-mode-on "ansi-color" nil t) + (add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on) + #+end_src +*** vterm + #+begin_src emacs-lisp + (defvar jao-use-vterm nil) + + (use-package vterm + :ensure t + :demand t + :commands (vterm vterm-mode) + :init (setq vterm-kill-buffer-on-exit t + vterm-copy-exclude-prompt t + jao-use-vterm t) + :config (jao-define-attached-buffer "\\*vterm\\*" 0.5) + :bind (:map vterm-mode-map ("C-c C-c" . vterm-send-C-c))) + + (defun jao-exec-in-vterm (cmd bname) + (if (string-blank-p (or cmd "")) + (vterm) + (let ((vterm-shell cmd) + (vterm-kill-buffer-on-exit t) + (buff (generate-new-buffer bname))) + (switch-to-buffer buff) + (vterm-mode)))) + + #+end_src +*** term + #+begin_src emacs-lisp + (defvar-local jao-term--cmd nil) + + (defun jao-term--find (cmd) + (seq-find (lambda (b) + (with-current-buffer b + (and (derived-mode-p 'term-mode 'vterm-mode) + (string= (or jao-term--cmd "") cmd)))) + (buffer-list))) + + (defun jao-exec-in-term (cmd &optional name) + (if jao-use-vterm + (jao-exec-in-vterm cmd name) + (ansi-term "bash" name) + (set-process-sentinel (get-buffer-process (current-buffer)) + (lambda (process event) + (when (string= event "finished\n") + (kill-buffer (process-buffer process))))) + (term-send-string nil (concat cmd " ; exit\n")))) + + (defmacro jao-def-exec-in-term (name cmd &rest prelude) + `(defun ,(intern (format "jao-term-%s" name)) (&optional term) + (interactive "P") + ,@prelude + (let ((jao-use-vterm (if term (not jao-use-vterm) jao-use-vterm))) + (if-let ((b (jao-term--find ,cmd))) + (pop-to-buffer b) + (jao-exec-in-term ,cmd ,(format "*%s*" name)) + (setq-local jao-term--cmd ,cmd))))) + + #+end_src +*** eshell +***** Basic custom + #+begin_src emacs-lisp + (use-package eshell + :init + (setq eshell-directory-name "~/.emacs.d/eshell") + (setq eshell-hist-ignoredups 'erase) + + (defun jao-eshell--outline () + (setq-local outline-regexp eshell-prompt-regexp)) + + :hook (eshell-mode . jao-eshell--outline)) + #+end_src +***** Colors + #+begin_src emacs-lisp + (autoload 'ansi-color-apply "ansi-color") + ;; (add-hook 'eshell-preoutput-filter-functions 'ansi-color-filter-apply) + (add-hook 'eshell-preoutput-filter-functions 'ansi-color-apply) + + (use-package eshell-syntax-highlighting + :after esh-mode + :ensure t + :config + ;; Enable in all Eshell buffers. + (eshell-syntax-highlighting-global-mode +1)) + #+end_src +***** Visual commands + #+begin_src emacs-lisp + (require 'em-term) + ;;; commands using ansi scape seqs + (dolist (c '("editor" "more" "wget" "dict" "vim" "links" "w3m" "guile" + "ssh" "autossh" "zmore" "pager" "aptitude" "su" "htop" "top" + "screen" "whizzml" "iex" "spt")) + (add-to-list 'eshell-visual-commands c)) + + (setq eshell-visual-subcommands '(("git" "log" "diff" "show") + ("sudo" "vim") + ("rebar3" "shell")) + eshell-destroy-buffer-when-process-dies nil + eshell-escape-control-x t) + + (use-package eshell-vterm :ensure t) + + (when jao-use-vterm (eshell-vterm-mode)) + + #+end_src +***** bol + #+begin_src emacs-lisp + (defun jao-eshell-maybe-bol () + (interactive) + (let ((p (point))) + (eshell-bol) + (if (= p (point)) + (beginning-of-line)))) + #+end_src +***** Prompt + #+BEGIN_SRC emacs-lisp + ;; tracking git repos + (defun jao-eshell--git-dirty () + (shell-command-to-string "git diff-index --quiet HEAD -- || echo -n '*'")) + + (use-package git-ps1-mode + :ensure t + :init (setq git-ps1-mode-showupstream "1" + git-ps1-mode-showdirtystate "1")) + + (defun jao-eshell--git-info () + (if (fboundp 'git-ps1-mode-get-current) + (git-ps1-mode-get-current) + (let ((desc (shell-command-to-string "git branch --no-color"))) + (when (string-match "^* \\(\\<.+\\>\\)" desc) + (format "%s%s" (match-string 1 desc) (jao-eshell--git-dirty)))))) + + (defun jao-eshell--git-current-branch (suffix) + (let ((desc (or (jao-eshell--git-info) ""))) + (cond ((and (string-empty-p desc) suffix) (format " (%s)" suffix)) + ((string-empty-p (or suffix "")) (format " (%s)" desc)) + (t (format " (%s %s)" desc suffix))))) + + (defun jao-eshell--virtualenv () + (let ((venv (getenv "VIRTUAL_ENV"))) + (when (and venv (string-match ".*/\\([^/]+\\)/$" venv)) + (match-string-no-properties 1 venv)))) + + (defun jao-eshell-prompt-function () + (let* ((venv (jao-eshell--virtualenv)) + (venv (if venv (format "%s" venv) ""))) + (concat (abbreviate-file-name (eshell/pwd)) + (jao-eshell--git-current-branch venv) + (if (= (user-uid) 0) " # " " $ ")))) + + (setq eshell-prompt-function 'jao-eshell-prompt-function) + #+END_SRC +***** in-term + #+begin_src emacs-lisp + (defun eshell/in-term (prog &rest args) + (switch-to-buffer + (apply #'make-term (format "in-term %s %s" prog args) prog nil args)) + (term-mode) + (term-char-mode)) + #+end_src +***** Dir navigation + #+BEGIN_SRC emacs-lisp + (use-package eshell-up + :ensure t + :config (setq eshell-up-print-parent-dir t)) + + (use-package eshell-autojump :ensure t) + #+END_SRC +***** Completion + #+begin_src emacs-lisp + (defun jao-eshell-completion-capf () + (let* ((b (save-excursion (eshell-bol) (point))) + (c (bash-completion-dynamic-complete-nocomint b (point) t))) + (when (and c (listp c)) + (append c '(:exclusive no))))) + + (defun jao-eshell--set-up-completion () + (setq-local completion-styles '(basic partial-completion) + completion-at-point-functions + '(jao-eshell-completion-capf + pcomplete-completions-at-point t))) + + (use-package bash-completion + :ensure t + :hook (eshell-mode . jao-eshell--set-up-completion)) + #+end_src +***** History + #+BEGIN_SRC emacs-lisp + (setq eshell-history-size 10000) + ;;; Fix eshell history completion to allow !$ + ;; This is done by advising eshell-history-reference to expand !$ + ;; into !!:$ which works... + (defadvice jao-eshell-history-reference (before ben-fix-eshell-history) + "Fixes eshell history to allow !$ as abbreviation for !!:$" + (when (string= (ad-get-arg 0) "!$") (ad-set-arg 0 "!!:$"))) + (ad-activate 'jao-eshell-history-reference) + #+END_SRC + This is needed if we want ! to expand in emacs >= 27 + #+BEGIN_SRC emacs-lisp + (add-hook 'eshell-expand-input-functions #'eshell-expand-history-references) + #+END_SRC +***** Toggle + #+begin_src emacs-lisp + + (use-package jao-eshell-here + :demand t + :config (jao-define-attached-buffer "^\\*eshell" 0.5) + :bind (("" . jao-eshell-here-toggle) + ("C-" . jao-eshell-here-toggle-new))) + + #+end_src +***** Workarounds + #+begin_src emacs-lisp + ;; at some point, bash completion started insertig the TAB + ;; after the commands ends + (defun jao-eshell--clean-prompt () + (eshell-bol) + (ignore-errors (kill-line))) + + (add-hook 'eshell-after-prompt-hook 'jao-eshell--clean-prompt) + #+end_src +***** Keybindings + #+begin_src emacs-lisp + (defun jao-eshell--kbds () + (define-key eshell-mode-map "\C-a" 'jao-eshell-maybe-bol) + (define-key eshell-mode-map "\C-ci" 'consult-outline)) + ;; Eshell mode is sillily re-creating its mode map + ;; in every buffer in emacs < 28. + (if (> emacs-major-version 27) + (jao-eshell--kbds) + (add-hook 'eshell-mode-hook #'jao-eshell--kbds)) + #+end_src +* Version control and CI +*** General options + #+begin_src emacs-lisp + (setq vc-follow-symlinks t) + (setq auto-revert-check-vc-info nil) + #+end_src +*** Diff fringe indicators (diff-hl) + #+begin_src emacs-lisp + (use-package diff-hl + :ensure t + :custom ((diff-hl-draw-borders nil) + (diff-hl-side 'right) + (diff-hl-margin-symbols-alist + '((insert . "+") + (delete . "-") + (change . "~") + (unknown . "?") + (ignored . "i")))) + :config + (map-keymap (lambda (_k cmd) + (put cmd 'repeat-map 'diff-hl-command-map)) + diff-hl-command-map) + (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh) + (when (jao-colors-scheme-dark-p) (diff-hl-margin-mode 1))) + + (global-diff-hl-mode 1) + + #+end_src +*** Git config files: more informative diffs + See [[https://protesilaos.com/codelog/2021-01-26-git-diff-hunk-elisp-org/][Informative diff hunks for Emacs Lisp and Org | Protesilaos Stavrou]] + #+begin_src config :tangle ~/.config/git/attributtes :comments no + *.clj diff=lisp + *.cljc diff=lisp + *.cljs diff=lisp + *.lisp diff=lisp + *.el diff=lisp + *.org diff=org + #+end_src + #+begin_src gitconfig :tangle ~/.config/git/config + [diff "lisp"] + xfuncname = "^(((;;;+ )|\\(|([ \t]+\\(((cl-|el-patch-)?def(un|var|macro|method|custom)|gb/))).*)$" + [diff "org"] + xfuncname = "^(\\*+ +.*)$" + #+end_src +*** Magit and forge + #+begin_src emacs-lisp + (use-package magit + :ensure t + :commands magit-status + :init + (setq magit-status-initial-section nil + magit-define-global-key-bindings nil + magit-completing-read-function 'magit-builtin-completing-read + magit-display-buffer-function + 'magit-display-buffer-fullcolumn-most-v1 + magit-delete-by-moving-to-trash nil + magit-last-seen-setup-instructions "1.4.0" + magit-log-edit-confirm-cancellation t + magit-omit-untracked-dir-contents t + magit-process-connection-type nil + magit-push-always-verify nil + magit-repository-directories + '(("/home/jao/usr/bigml" . 2) + ("/home/jao/usr/jao" . 2) + ("/home/jao/lib/elisp" . 3) + ("/usr/local/src" . 1)) + magit-save-repository-buffers 'dontask + magit-section-visibility-indicator '("…" . t) + magit-status-buffer-switch-function 'switch-to-buffer + magit-status-show-hashes-in-headers t) + :config + + (use-package forge + :ensure t + :demand t + :init + (setq forge-topic-list-limit (cons 100 -1) + forge-pull-notifications nil)) + + (add-hook 'magit-status-sections-hook #'forge-insert-assigned-pullreqs t) + (add-hook 'magit-status-sections-hook #'forge-insert-assigned-issues t) + + :bind (("" . magit-status) + (:map forge-topic-mode-map ("M-w" . copy-region-as-kill)))) + + (use-package code-review + :ensure t + :after forge + :bind (:map magit-status-mode-map + ("C-c C-r" . code-review-forge-pr-at-point))) + + + #+end_src +*** Eldoc for magit status/log buffers + [[https://tsdh.org/posts/2021-06-21-using-eldoc-with-magit.html][Using Eldoc with Magit]]. + #+begin_src emacs-lisp + (defun jao-magit-eldoc-for-commit (_callback) + (when-let ((commit (magit-commit-at-point))) + (with-temp-buffer + (magit-git-insert "show" + "--format=format:%an <%ae>, %ar" + (format "--stat=%d" (window-width)) + commit) + (goto-char (point-min)) + (put-text-property (point-min) (line-end-position) 'face 'bold) + (buffer-string)))) + + (defun jao-magit-eldoc-setup () + (add-hook 'eldoc-documentation-functions + #'jao-magit-eldoc-for-commit nil t) + (eldoc-mode 1)) + + (add-hook 'magit-log-mode-hook #'jao-magit-eldoc-setup) + (add-hook 'magit-status-mode-hook #'jao-magit-eldoc-setup) + + (with-eval-after-load "eldoc" + (eldoc-add-command 'magit-next-line) + (eldoc-add-command 'magit-previous-line) + (eldoc-add-command 'magit-section-forward) + (eldoc-add-command 'magit-section-backward)) + #+end_src +*** Other git packages + #+begin_src emacs-lisp + (use-package git-timemachine :ensure t) + + ;; git config --local git-link.remote / git-link.branch + (use-package git-link :ensure t) + + (use-package git-modes :ensure t) + + #+end_src +*** Jenkins + [[https://github.com/rmuslimov/jenkins.el][GitHub - rmuslimov/jenkins.el: Jenkins plugin for emacs]] + #+BEGIN_SRC emacs-lisp + (use-package jenkins + :ensure t + :init + ;; one also needs jenkins-api-token, jenkins-username and jenkins-url + ;; optionally: jenkins-colwidth-id, jenkins-colwidth-last-status + (setq jenkins-colwidth-name 35) + :config + (defun jao-jenkins-first-job (&rest _) + (interactive) + (goto-char (point-min)) + (when (re-search-forward "^- Job" nil t) + (goto-char (match-beginning 0)))) + (add-hook 'jenkins-job-view-mode-hook #'jao-jenkins-first-job) + (advice-add 'jenkins-job-render :after #'jao-jenkins-first-job) + + (defun jenkins-refresh-console-output () + (interactive) + (let ((n (buffer-name))) + (when (string-match "\\*jenkins-console-\\([^-]+\\)-\\(.+\\)\\*$" n) + (jenkins-get-console-output (match-string 1 n) (match-string 2 n)) + (goto-char (point-max))))) + + :bind (:map jenkins-job-view-mode-map + (("n" . next-line) + ("p" . previous-line) + ("f" . jao-jenkins-first-job) + ("RET" . jenkins--show-console-output-from-job-screen)) + :map jenkins-console-output-mode-map + (("n" . next-line) + ("p" . previous-line) + ("g" . jenkins-refresh-console-output)))) + #+END_SRC +* Programming +*** Automatic modes + #+BEGIN_SRC emacs-lisp + (add-to-list 'auto-mode-alist '("\\.mix\\'" . hexl-mode)) + (add-to-list 'auto-mode-alist '("\\.m4\\'" . m4-mode)) + (add-to-list 'auto-mode-alist '("\\.am\\'" . makefile-mode)) + (add-to-list 'auto-mode-alist '("\\.pl\\'\\|\\.pm\\'" . cperl-mode)) + #+END_SRC +*** Smart scan + #+begin_src emacs-lisp + (use-package smartscan + :ensure t + :commands smartscan-mode + :init (add-hook 'prog-mode-hook #'smartscan-mode) + :diminish) + #+end_src +*** Paredit and parens + #+begin_src emacs-lisp + (require 'paren) + (show-paren-mode t) + (setq show-paren-context-when-offscreen t) + + (use-package paredit + :ensure t + :commands paredit-mode + :hook ((pie-mode . paredit-mode) + (scheme-mode . paredit-mode) + (clojure-mode . paredit-mode) + (emacs-lisp-mode . paredit-mode) + (eval-expression-minibuffer-setup . paredit-mode) + (lisp-interaction-mode . disable-paredit-mode)) + :diminish ((paredit-mode . " þ"))) + #+end_src +*** Diff/Ediff + #+BEGIN_SRC emacs-lisp + (setq ediff-split-window-function 'split-window-horizontally) + (setq ediff-make-buffers-readonly-at-startup nil) + (setq ediff-window-setup-function 'ediff-setup-windows-plain) + (setq ediff-keep-variants nil) + #+END_SRC +*** Compilation +***** Compilation mode options + #+begin_src emacs-lisp + (require 'compile) + (setq compilation-scroll-output t) + (setq compilation-error-regexp-alist + (remove 'omake compilation-error-regexp-alist)) + ;; (add-hook 'compilation-mode-hook #'visual-line-mode) + #+end_src +***** Mode line (no "Compiling"!) + #+BEGIN_SRC emacs-lisp + (require 'compile) + (diminish 'compilation-minor-mode " ‡") + (when (< emacs-major-version 27) + (setcdr (assq 'compilation-in-progress minor-mode-alist) '(" ‡"))) + (when (> emacs-major-version 26) + (setcdr (assq 'compilation-in-progress mode-line-modes) '("‡ "))) + #+END_SRC +***** Colorizing compilation buffer + #+begin_src emacs-lisp + (setq compilation-message-face 'default) + (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) + #+end_src +***** Compilation commands + #+begin_src emacs-lisp + (use-package jao-compilation + :commands jao-compilation-setup + :bind (("C-c C" . compile) + ("C-c c" . jao-compile))) + (jao-compilation-setup) + #+end_src +***** Next error + #+begin_src emacs-lisp + (setq next-error-find-buffer-function + #'next-error-buffer-on-selected-frame + next-error-verbose t) + #+end_src +*** Flymake + #+begin_src emacs-lisp + (use-package flymake + :ensure t + :custom ((flymake-mode-line-format '(" " flymake-mode-line-counters))) + :hook ((haskell-mode . flymake-mode)) + :config (jao-define-attached-buffer "^\\*Flymake diagnostics .*\\*\\'") + :bind (:map flymake-mode-map (("s-f n" . flymake-goto-next-error) + ("s-f p" . flymake-goto-prev-error) + ("s-f i" . flymake-show-diagnostic) + ("s-f f" . flymake-show-diagnostics-buffer) + ("s-f l" . consult-flymake)))) + #+end_src +*** Workarounds + #+begin_src emacs-lisp + (setq c-type-finder-time-slot nil) + #+end_src +*** Outline minor mode + #+begin_src emacs-lisp + (setq outline-minor-mode-cycle t) + + (defvar jao-outline-minor-mode-map + (let ((map (make-keymap))) + (define-key map (kbd "C-c C-n") #'outline-next-visible-heading) + (define-key map (kbd "C-c C-p") #'outline-previous-visible-heading) + (define-key map (kbd "C-c o") 'consult-outline) + map)) + + (define-minor-mode jao-outline-minor-mode + "Minor outline mode for programming languages" + :lighter "" + (if jao-outline-minor-mode + (progn (setq-local outline-level #'outline-level + outline-regexp (format "[%s]\\{3,\\} " + comment-start) + outline-default-state 4) + (outline-minor-mode 1)) + (outline-minor-mode -1))) + + #+end_src +* Programming languages +*** Elisp + #+begin_src emacs-lisp + (add-hook 'emacs-lisp-mode-hook #'jao-outline-minor-mode) + + (use-package edit-list :ensure t) + (use-package package-lint :ensure t) + + (defun elisp-disassemble (function) + (interactive (list (function-called-at-point))) + (disassemble function)) + + (defun elisp-pp (sexp) + (with-output-to-temp-buffer "*Pp Eval Output*" + (pp sexp) + (with-current-buffer standard-output + (emacs-lisp-mode)))) + + (defun elisp-macroexpand (form) + (interactive (list (form-at-point 'sexp))) + (elisp-pp (macroexpand form))) + + (defun elisp-macroexpand-all (form) + (interactive (list (form-at-point 'sexp))) + (elisp-pp (macroexpand-all form))) + + (defun elisp-find-definition (name) + (interactive (list (thing-at-point 'symbol))) + (cond (name + (let ((symbol (intern-soft name)) + (search (lambda (fun sym) + (let* ((r (save-excursion (funcall fun sym))) + (buffer (car r)) + (point (cdr r))) + (cond ((not point) + (error "Found no definition for %s in %s" + name buffer)) + (t + (switch-to-buffer buffer) + (goto-char point) + (recenter 1))))))) + (cond ((fboundp symbol) + (xref-push-marker-stack) + (funcall search 'find-function-noselect symbol)) + ((boundp symbol) + (xref-push-marker-stack) + (funcall search 'find-variable-noselect symbol)) + (t + (message "Symbol not bound: %S" symbol))))) + (t (message "No symbol at point")))) + + + (defun elisp-bytecompile-and-load () + (interactive) + (or buffer-file-name + (error "The buffer must be saved in a file first")) + (require 'bytecomp) + ;; Recompile if file or buffer has changed since last compilation. + (when (and (buffer-modified-p) + (y-or-n-p (format "save buffer %s first? " (buffer-name)))) + (save-buffer)) + (let ((filename (expand-file-name buffer-file-name))) + (with-temp-buffer + (byte-compile-file filename t)))) + + (use-package elisp-mode + :bind (:map emacs-lisp-mode-map + (("C-c C-M" . emacs-lisp-macroexpand) + ("C-c C-m" . elisp-macroexpand-all) + ("C-c C-k" . elisp-bytecompile-and-load) + ;; ("C-c C-p" . pp-eval-last-sexp) + ("M-." . elisp-find-definition) + ("M-," . pop-tag-mark) + ("C-c <" . lc-show-package-summary)))) + #+end_src +*** Erlang + #+begin_src emacs-lisp + (use-package erlang + :disabled t + :ensure t + :custom ((inferior-erlang-machine-options '("shell")) + (inferior-erlang-machine "rebar3") + (inferior-erlang-shell-type nil) + (erlang-indent-level 4)) + + ;; :bind (:map erlang-mode-map (("C-c C-z" . jao-vterm-repl-pop-to-repl))) + + :init + ;; (require 'jao-vterm-repl) + ;; (add-to-list 'auto-mode-alist '("^rebar\\.config\\`" . erlang-mode)) + ;; (jao-vterm-repl-register "rebar.config" "rebar3 shell" "^[0-9]+> ") + + :config + ;; (defun jao-erlang-current-module () + ;; (when (save-excursion (goto-char (point-min)) + ;; (re-search-forward "^-module(\\([^)]+\\))" nil t)) + ;; (match-string-no-properties 1))) + + ;; (defun jao-erlang-compile (arg) + ;; (interactive "P") + ;; (save-some-buffers) + ;; (when-let ((mname (jao-erlang-current-module))) + ;; (with-current-buffer (jao-vterm-repl) + ;; (vterm-send-string (format "c(%s).\n" mname)) + ;; (sit-for 0) + ;; (setq compilation-last-buffer (current-buffer)) + ;; (when arg (jao-vterm-repl-pop-to-repl))))) + + ;; (setq erlang-shell-function #'jao-vterm-repl + ;; erlang-shell-display-function #'jao-vterm-repl-pop-to-repl + ;; erlang-compile-function #'jao-erlang-compile) + ) + #+end_src +*** Idris + #+begin_src emacs-lisp + (use-package idris-mode + :ensure t + :custom ((idris-interpreter-path "idris2") + (idris-pretty-printer-width 80) + (idris-repl-history-file "~/.emacs.d/cache/idris-history.eld") + (idris-stay-in-current-window-on-compiler-error t))) + (jao-define-attached-buffer "^\\*idris.*") + #+end_src +*** Clojure + #+begin_src emacs-lisp + (use-package clojure-mode + :ensure t + :config + (defun jao-clojure--fix-things () + (setq-local completion-styles '(basic partial-completion emacs22)) + (eldoc-mode 1) + (setq mode-name "λ")) + :hook (clojure-mode . jao-clojure--fix-things)) + + (use-package cider + :ensure t + :commands cider-mode + :init (setq cider-annotate-completion-candidates t + cider-auto-select-error-buffer nil + cider-auto-select-test-report-buffer nil + cider-eldoc-display-for-symbol-at-point t + cider-eldoc-ns-function #'identity ;; #'cider-last-ns-segment + cider-enrich-classpath nil + cider-lein-parameters "repl :headless :host localhost" + cider-mode-line " ÷" + cider-prompt-for-symbol nil + cider-repl-history-file + (expand-file-name "~/.emacs.d/cache/cider.history") + cider-repl-pop-to-buffer-on-connect nil + cider-repl-use-pretty-printing t + cider-show-error-buffer 'except-in-repl + cider-test-show-report-on-success nil + cider-use-fringe-indicators nil + cider-use-overlays nil + clojure-docstring-fill-column 72 + nrepl-prompt-to-kill-server-buffer-on-quit nil) + :bind (("" . cider-selector))) + + (with-eval-after-load "cider-test" + (advice-add 'cider-scale-background-color :override + (lambda () (frame-parameter nil 'background-color))) + (setq cider-test-items-background-color + (frame-parameter nil 'background-color))) + + (use-package cider-macroexpansion + :after cider + :diminish " µ") + + #+end_src +*** Geiser + #+begin_src emacs-lisp + (defun jao-org--set-geiser-impl () (setq-local geiser-repl--impl 'guile)) + (add-hook 'org-mode-hook #'jao-org--set-geiser-impl) + + (jao-load-path "geiser/geiser/elisp") + (use-package geiser + :init + (setq geiser-repl-history-filename "~/.emacs.d/cache/geiser-history" + geiser-repl-startup-time 20000 + geiser-debug-auto-display-images-p t + geiser-log-verbose-p t + geiser-active-implementations '(guile) + geiser-default-implementation 'guile)) + + (jao-load-path "geiser/guile") + (use-package geiser-guile) + + ;; (jao-load-path "geiser/mit") + ;; (use-package geiser-mit) + + ;; (jao-load-path "geiser/chicken") + ;; (use-package geiser-chicken) + + ;; (jao-load-path "geiser/chibi") + ;; (use-package geiser-chibi) + + ;; (jao-load-path "geiser/chez") + ;; (use-package geiser-chez + ;; :init (setq geiser-chez-binary "scheme")) + + ;; (jao-load-path "geiser/gambit") + ;; (use-package geiser-gambit) + + ;; (jao-load-path "geiser/gauche") + ;; (use-package geiser-gauche) + + (jao-define-attached-buffer "^\\* ?Geiser .*\\*" 0.4) + (jao-define-attached-buffer "^\\* Guile REPL \\*" 0.4) + + #+end_src +*** Haskell +***** packages + #+begin_src emacs-lisp + (use-package haskell-mode + :ensure t + :custom + ((inferior-haskell-find-project-root t) + (haskell-check-remember-last-command-p nil) + (haskell-font-lock-symbols nil) + (haskell-interactive-popup-errors nil) + (haskell-process-auto-import-loaded-modules t) + (haskell-process-log t) + (haskell-process-suggest-remove-import-lines t) + (haskell-process-suggest-hoogle-imports t) + (haskell-process-type 'cabal-repl) + (haskell-process-use-presentation-mode t) + (haskell-stylish-on-save nil)) + + :config + (defun jao-haskell-hoogle (query) + (interactive (hoogle-prompt)) + (haskell-hoogle query t)) + + (put 'haskell-process-args-cabal-repl + 'safe-local-variable + (apply-partially #'seq-every-p #'stringp)) + + (require 'haskell-doc) + (dolist (h '(interactive-haskell-mode + haskell-doc-mode + haskell-decl-scan-mode + haskell-indentation-mode + haskell-auto-insert-module-template)) + (add-hook 'haskell-mode-hook h)) + + :bind (:map haskell-mode-map + (("C-c C-d" . jao-haskell-hoogle) + ("C-c h" . haskell-hoogle-lookup-from-local) + ("C-c C-c" . haskell-compile)))) + + (require 'haskell) + + (diminish 'interactive-haskell-mode " λ") + (diminish haskell-doc-mode) + (diminish haskell-decl-scan-mode) + + (jao-define-attached-buffer "\\*hoogle\\*.*") + + ;; needs cabal install apply-refact + (use-package hlint-refactor + :ensure t + :after (haskell-mode) + :diminish "" + :hook (haskell-mode . hlint-refactor-mode)) + #+end_src +***** transient + #+begin_src emacs-lisp + (jao-transient-major-mode haskell + ["Imports" + ("in" "Navigate imports" haskell-navigate-imports) + ("if" "Format imports" haskell-mode-format-imports) + ("is" "Sort imports" haskell-sort-imports) + ("ia" "Align imports" haskell-align-imports)] + ["Code" + ("c" "Compile" haskell-compile) + ("s" "stylish on buffer" haskell-mode-stylish-buffer)] + ["Hoogle" + ("h" "Hoogle" jao-haskell-hoogle) + ("H" "Hoogle from local server" haskell-hoogle-lookup-from-local)]) + #+end_src +*** Pie + #+begin_src emacs-lisp + (jao-load-path "pie") + (use-package pie + :demand t + :commands (pie-mode)) + #+end_src +*** Lisp + #+begin_src emacs-lisp :tangle no + (use-package sly + :ensure t + :init (setq inferior-lisp-program "sbcl") + :config (sly-setup)) + + (use-package sly-quicklisp + :after (sly) + :ensure t) + #+end_src +*** Prolog + #+BEGIN_SRC emacs-lisp + (use-package ediprolog :ensure t) + + (use-package prolog + :ensure t + :commands (run-prolog prolog-mode mercury-mode) + :init (progn + (setq prolog-system 'swi) + (add-to-list 'auto-mode-alist '("\\.pl$" . prolog-mode)) + (setq prolog-consult-string '((t "[%f]."))) + (setq prolog-program-name + '(((getenv "EPROLOG") (eval (getenv "EPROLOG"))) + (eclipse "eclipse") + (mercury nil) + (sicstus "sicstus") + (swi "swipl") + (t "prolog"))))) + #+END_SRC +*** Racket + #+begin_src emacs-lisp + (use-package racket-mode + :ensure t + :init (setq racket-show-functions '(racket-show-echo-area) + racket-documentation-search-location 'local) + :config + (jao-define-attached-buffer "\\`\\*Racket REPL") + (jao-define-attached-buffer "\\`\\*Racket Describe" 0.5) + (add-hook 'racket-mode-hook #'paredit-mode) + (require 'racket-xp) + (add-hook 'racket-mode-hook #'racket-xp-mode) + :bind (:map racket-xp-mode-map (("C-c C-S-d" . racket-xp-documentation) + ("C-c C-d" . racket-xp-describe)))) + + #+end_src +*** Python +***** Virtual envs (with eshell support) + See also [[https://github.com/porterjamesj/virtualenvwrapper.el][the docs]]. + #+begin_src emacs-lisp + (use-package virtualenvwrapper + :ensure t + :config + (venv-initialize-eshell) + (jao-compilation-env "VIRTUAL_ENV")) + #+end_src +* Text/data formats +*** YAML + #+begin_src emacs-lisp + (use-package yaml-mode :ensure t) + #+end_src +*** JSON + #+BEGIN_SRC emacs-lisp + (use-package json-mode :ensure t) + ;; (use-package json-navigator :ensure nil) + #+END_SRC +* Graphics +*** Images + #+begin_src emacs-lisp + (setq image-use-external-converter t) + #+end_src +*** Gnuplot + #+BEGIN_SRC emacs-lisp + (use-package gnuplot + :ensure t + :commands (gnuplot-mode gnuplot-make-buffer) + :init (add-to-list 'auto-mode-alist '("\\.gp$" . gnuplot-mode))) + #+END_SRC +* Network +*** nm applet + #+begin_src emacs-lisp + (jao-shell-def-exec jao-nm-applet "nm-applet") + + (defun jao-toggle-nm-applet () + (interactive) + (if (jao-shell-running-p "nm-applet") + (jao-shell-string "killall nm-applet") + (jao-nm-applet))) + #+end_src +*** enwc + #+begin_src emacs-lisp + (use-package enwc + :ensure t + :custom ((enwc-default-backend 'nm) + (enwc-wired-device "wlp164s0") + (enwc-wireless-device "wlp164s0") + (enwc-display-mode-line nil))) + #+end_src + +*** bluetooth + #+BEGIN_SRC emacs-lisp + (use-package bluetooth :ensure t) + #+END_SRC +*** vpn + #+begin_src emacs-lisp + (use-package jao-mullvad :demand t) + #+end_src +*** ssh + #+begin_src emacs-lisp + (use-package tramp) + (defun jao-tramp-hosts () + (seq-uniq + (mapcan (lambda (x) + (remove nil (mapcar 'cadr (apply (car x) (cdr x))))) + (tramp-get-completion-function "ssh")) + #'string=)) + + (defun jao-ssh () + (interactive) + (let ((h (completing-read "Host: " (jao-tramp-hosts)))) + (jao-afio-goto-scratch) + (jao-exec-in-term (format "ssh %s" h) (format "*ssh %s*" h)))) + #+end_src +* Chats +*** circe + #+begin_src emacs-lisp + (defvar jao-libera-channels '()) + (defvar jao-oftc-channels '()) + (defvar jao-bitlbee-channels '()) + + (use-package circe + :ensure t + :bind (:map circe-channel-mode-map + (("C-c C-a" . lui-track-jump-to-indicator))) + :init + (setq circe-chat-buffer-name "{target}" + circe-default-realname "https://jao.io" + circe-default-part-message "" + circe-default-quit-message "" + circe-ignore-list nil + circe-server-coding-system '(undecided . undecided) + circe-server-killed-confirmation 'ask-and-kill-all + circe-server-auto-join-default-type :after-auth + circe-format-say "({nick}) {body}" + circe-format-self-say "(jao) {body}" + circe-new-buffer-behavior 'ignore + circe-new-buffer-behavior-ignore-auto-joins t + circe-nickserv-ghost-style 'after-auth + circe-prompt-string ": " + circe-completion-suffix ", " + circe-reduce-lurker-spam t + + circe-nick-next-function + (lambda (old) + (replace-regexp-in-string "-" "`" (circe-nick-next old))) + + circe-lagmon-mode-line-format-string "" ;; "%.0f " + circe-lagmon-mode-line-unknown-lag-string "" ;; "? " + circe-lagmon-timer-tick 120 + circe-lagmon-reconnect-interval 180 + + lui-max-buffer-size 30000 + lui-fill-column 80 + lui-time-stamp-position 'right + lui-time-stamp-format "%H:%M" + lui-flyspell-p nil + + lui-track-indicator (if window-system 'fringe 'bar) + lui-track-behavior 'before-tracking-next-buffer) + :config + + (define-minor-mode jao-circe-user-number-mode "" + :lighter (:eval (format " [%s]" (length (circe-channel-nicks))))) + + (add-hook 'circe-channel-mode-hook #'jao-circe-user-number-mode) + + (defun circe-command-RECOVER (&rest ignore) + "Recover nick" + (let* ((fn (jao--get-user/password "freenode")) + (u (car fn)) + (p (cadr fn))) + (circe-command-MSG "nickserv" (format "IDENTIFY %s %s" u p)) + (circe-command-MSG "nickserv" (format "GHOST %s" u)) + (circe-command-MSG "nickserv" (format "RELEASE %s" u)) + (circe-command-NICK u))) + + (defun circe-command-NNICKS (&rest _) + "Echo number of nicks" + (circe-display-server-message + (format "%d nicks in this channel" (length (circe-channel-nicks))))) + + (advice-add 'circe-command-NAMES :after #'circe-command-NNICKS) + + (setq circe-network-options + (let ((up (jao--get-user/password "libera")) + (oup (jao--get-user/password "oftc")) + (bup (jao--get-user/password "bitlbee"))) + `(("Libera Chat" + :nick ,(car up) :channels ,jao-libera-channels + :tls t :sasl-username ,(car up) :sasl-password ,(cadr up)) + ("OFTC" :nick ,(car oup) :channels ,jao-oftc-channels + :nickserv-password ,(cadr oup) + :tls t :sasl-username ,(car oup) :sasl-password ,(cadr oup)) + ("Bitlbee" + :host "127.0.0.1" :nick ,(car bup) + :channels ,jao-bitlbee-channels + :lagmon-disabled t + :nickserv-password ,(cadr bup) :user ,(car bup))))) + + (jao-shorten-modes 'circe-channel-mode + 'circe-server-mode + 'circe-query-mode) + + (enable-lui-track) + (circe-lagmon-mode) + (enable-circe-display-images)) + #+end_src +*** slack + [[https://github.com/jackellenberger/emojme#finding-a-slack-token][How to get a token]]: It's easyish! Open and sign into the slack + customization page, e.g. https://my.slack.com/customize, right + click anywhere > inspect element. Open the console and paste: + + =window.prompt("your api token is: ", TS.boot_data.api_token)= + + Lately things are iffy. We've needed to add the ~:override~ to + slack-counts update, and it might be needed to replace + ~slack-conversations-view~ by ~slack-conversations-history~ + + #+begin_src emacs-lisp + (use-package slack + :commands (slack-start) + :init + (setq slack-alert-icon (jao-data-file "slack.svg") + slack-buffer-emojify nil + slack-buffer-create-on-notify t + slack-display-team-name t + slack-typing-visibility 'never ;; 'buffer, 'frame + slack-profile-image-file-directory "/tmp/slack-imgs/" + slack-image-file-directory "/tmp/slack-imgs/" + slack-file-dir "~/var/download/slack/" + slack-prefer-current-team t + slack-message-tracking-faces '(warning) + slack-log-level 'warn + slack-message-custom-notifier (lambda (msg room team) room)) + :bind (:map slack-mode-map (("@" . slack-message-embed-mention) + ("#" . slack-message-embed-channel)) + :map slack-message-buffer-mode-map + (("C-c C-e" . slack-message-edit) + ("C-c C-a" . slack-file-upload))) + :config + (dolist (f (list slack-file-dir slack-image-file-directory)) + (when (not (file-exists-p f)) (make-directory f))) + + (jao-shorten-modes 'slack-message-buffer-mode + 'slack-thread-message-buffer-mode) + (jao-tracking-faces 'warning) + + (jao-define-attached-buffer "\\*Slack .+ Edit Message [0-9].+" 20)) + #+end_src +*** telegram + #+begin_src emacs-lisp + (use-package telega + :ensure t + :custom + (telega-use-tracking-for '(unmuted) ;; '(or unmuted mention) + telega-rainbow-color-custom-for nil + telega-msg-rainbow-title nil + telega-sticker-set-download t) + :config + (define-key global-map (kbd "C-c C-t") telega-prefix-map) + (setq telega-chat-show-avatars nil + telega-chat-prompt-format ">> " + telega-root-show-avatars nil + telega-emoji-use-images nil + telega-temp-dir "/tmp/telega" + telega-symbol-checkmark "·" + telega-symbol-heavy-checkmark "×" + telega-symbol-verified "*" + telega-symbol-horizontal-bar + (propertize "-" 'face 'jao-themes-f00) + telega-symbol-vertical-bar + (propertize "| " 'face 'jao-themes-dimm) + telega-mode-line-string-format + '(:eval (telega-mode-line-unread-unmuted)) + telega-use-images (display-graphic-p)) + (with-eval-after-load "tracking" + (jao-shorten-modes 'telega-chat-mode) + (jao-tracking-faces 'telega-tracking)) + (telega-mode-line-mode 1)) + #+end_src +*** startup + #+begin_src emacs-lisp + (defun jao-chats (&optional p) + (interactive "P") + (when (or p (y-or-n-p "Connect to slack? ")) + (slack-start)) + (when (or p (y-or-n-p "Connect to telegram? ")) + (telega)) + (when (or p (y-or-n-p "Connect to libera? ")) + (unless (get-buffer "irc.libera.chat:6697") + (circe "Libera Chat")))) + + (defun jao-all-chats () (interactive) (jao-chats t)) + + (defun jao-chats-telega () + (interactive) + (jao-buffer-same-mode '(telega-root-mode telega-chat-mode))) + + (defun jao-chats-slack () + (interactive) + (jao-buffer-same-mode 'slack-message-buffer-mode)) + + (defun jao-chats-irc () + (interactive) + (jao-buffer-same-mode '(circe-channel-mode circe-query-mode erc-mode))) + + #+end_src +* Multimedia +*** mixer + #+begin_src emacs-lisp + (defun jao-mixer-get-level (&optional dev) + (interactive) + (let* ((dev (or dev "Master")) + (s (shell-command-to-string (format "amixer sget %s" dev))) + (s (car (last (split-string s "\n" t))))) + (when (string-match ".*Front .*\\[\\([0-9]+\\)%\\] .*" s) + (let ((level (match-string 1 s))) + (message "%s level: %s%%" dev level) + (string-to-number level))))) + + (defun jao-mixer-set (dev v) + (jao-shell-string "amixer sset" dev v) + (jao-mixer-get-level dev)) + + (defun jao-mixer-master-toggle () + (interactive) + (jao-mixer-set "Master" "toggle")) + + (defun jao-mixer-master-up () + (interactive) + (jao-mixer-set "Master" "10%+")) + + (defun jao-mixer-master-down () + (interactive) + (jao-mixer-set "Master" "10%-")) + + (defun jao-mixer-capture-up () + (interactive) + (jao-mixer-set "Capture" "10%+")) + + (defun jao-mixer-capture-down () + (interactive) + (jao-mixer-set "Capture" "10%-")) + + (jao-shell-def-exec jao-audio-applet "pasystray") + + (defun jao-toggle-audio-applet () + (interactive) + (if (string-empty-p (jao-shell-string "pidof pasystray")) + (jao-audio-applet) + (jao-shell-string "killall pasystray"))) + + (global-set-key (kbd "") #'jao-toggle-audio-applet) + + #+end_src +*** mpris + #+begin_src emacs-lisp + (defun jao-mpris-lyrics (&optional force) + (interactive "P") + (jao-show-lyrics force #'jao-mpris-artist-title)) + + (use-package jao-mpris :demand t) + + (defalias 'jao-streaming-list #'ignore) + (defalias 'jao-streaming-like #'ignore) + (defalias 'jao-streaming-dislike #'ignore) + (defalias 'jao-streaming-lyrics #'jao-mpris-lyrics) + (defalias 'jao-streaming-toggle #'jao-mpris-play-pause) + (defalias 'jao-streaming-next #'jao-mpris-next) + (defalias 'jao-streaming-prev #'jao-mpris-previous) + (defalias 'jao-streaming-current #'jao-mpris-show-osd) + (defalias 'jao-streaming-seek #'jao-mpris-seek) + (defalias 'jao-streaming-seek-back #'jao-mpris-seek-back) + (defalias 'jao-streaming-volume #'jao-mpris-vol) + (defalias 'jao-streaming-volume-down #'jao-mpris-vol-down) + + (jao-mpris-register "playerctld" + :session (if jao-modeline-in-minibuffer -10 70)) + + #+end_src +*** mpc + #+begin_src emacs-lisp + (use-package jao-mpc + :demand t + :commands jao-mpc-setup) + + (defvar jao-mopidy-port 6669) + (defvar jao-mpc-last-port nil) + + (defun jao-mpc-toggle-port () + (interactive) + (setq jao-mpc-last-port (unless jao-mpc-last-port jao-mopidy-port) + jao-mpc-port jao-mpc-last-port)) + + (jao-mpc-setup jao-mopidy-port (if jao-modeline-in-minibuffer -10 70)) + + (defun jao-mpc-pport (&optional mop) + (cond ((or mop (jao-mpc--playing-p jao-mopidy-port)) jao-mopidy-port) + ((jao-mpc--playing-p) nil) + (t jao-mpc-last-port))) + + (defmacro jao-defun-play (name &optional mpc-name) + (let ((arg (gensym))) + `(defun ,(intern (format "jao-player-%s" name)) (&optional ,arg) + (interactive "P") + (,(intern (format "jao-mpc-%s" (or mpc-name name))) + (setq jao-mpc-last-port (jao-mpc-pport ,arg)))))) + + (jao-defun-play toggle) + (jao-defun-play next) + (jao-defun-play previous) + (jao-defun-play stop) + (jao-defun-play echo echo-current) + (jao-defun-play list show-playlist) + (jao-defun-play info lyrics-track-data) + (jao-defun-play browse show-albums) + (jao-defun-play select-album) + + (defun jao-player-seek (delta) (jao-mpc-seek delta (jao-mpc-pport))) + + (defalias 'jao-player-connect 'jao-mpc-connect) + (defalias 'jao-player-play 'jao-mpc-play) + #+end_src +*** transients + #+begin_src emacs-lisp + + (require 'jao-lyrics) + (setq jao-lyrics-info-function #'jao-player-info) + + (defun jao-player-volume-delta (raise) + (jao-player-vol-delta (if raise 5 -5)) + (sit-for 0.05) + (jao-player-show-volume)) + + (defun jao-player-volume-raise () + (interactive) + (jao-player-volume-delta t)) + + (defun jao-player-volume-lower () + (interactive) + (jao-player-volume-delta nil)) + + (defun jao-player-show-volume () + (interactive) + (jao-notify "Volume" (format "%s%%" (jao-player-volume)))) + + (defun jao-player-seek-10 () (interactive) (jao-player-seek 10)) + (defun jao-player-seek--10 () (interactive) (jao-player-seek -10)) + + (defun jao-streaming-clear () (interactive) (jao-mpc-clear jao-mopidy-port)) + + (defun jao-streaming-echo-current () + (interactive) + (jao-mpc-echo-current jao-mopidy-port)) + + (defun jao-streaming-show-playlist () + (interactive) + (jao-mpc-show-playlist jao-mopidy-port)) + + (defun jao-player-level-cap () (interactive) (jao-mixer-level "Capture")) + + (use-package jao-random-album :demand t) + + (jao-def-exec-in-term "aptitude" "aptitude" (jao-afio--goto-scratch)) + (jao-def-exec-in-term "htop" "htop" (jao-afio--goto-scratch)) + + (transient-define-prefix jao-transient-streaming () + [:description + (lambda () (format "Streaming using %s" jao-mpris-player)) + ;; ["Search" + ;; ("a" "album" jao-streaming-album) + ;; ("A" "artist" jao-streaming-artist) + ;; ("t" "track" jao-streaming-track) + ;; ("P" "playlist" jao-streaming-playlist)] + ["Play" + ("s" "toggle" jao-streaming-toggle) + ("n" "next" jao-streaming-next) + ("p" "previous" jao-streaming-prev)] + ["Seek & shout" + ("f" "seek fwd" jao-streaming-seek :transient t) + ("F" "seek bwd" jao-streaming-seek-back :transient t) + ("u" "up" jao-streaming-volume :transient t) + ("d" "down" jao-streaming-volume-down :transient t)] + ["Browse" + ("l" "playing list" jao-streaming-list) + ("L" "lyrics" jao-streaming-lyrics) + ("w" "currently playing" jao-streaming-current)] + ["Act" + ("k" "like" jao-streaming-like) + ("K" "dislike" jao-streaming-dislike)]]) + + (transient-define-prefix jao-transient-media () + [["Play" + ("m" "toggle" jao-player-toggle) + ("n" "next" jao-player-next) + ("p" "previous" jao-player-previous) + ("s" "select album" jao-player-select-album)] + ["Seek and search" + ("f" "seek fwd" jao-player-seek-10 :transient t) + ("F" "seek bwd" jao-player-seek--10 :transient t) + ("a" "search album" jao-mpc-search-and-select-album)] + ["Browse" + ("b" "browse" jao-player-browse) + ("l" "show play list" jao-player-list) + ("L" "show lyrics" jao-show-lyrics) + ("w" "now playing" jao-player-echo)] + ["Master volume" + ("d" "master down" jao-mixer-master-down :transient t) + ("u" "master up" jao-mixer-master-up :transient t) + ("M" "master toggle" jao-mixer-master-toggle) + ("v" "show" jao-mixer-get-level)] + ["Capture volume" + ("D" "capture down" jao-mixer-capture-down :transient t) + ("U" "capture up" jao-mixer-capture-up :transient t) + ("V" "show" jao-player-level-cap)] + ["Utilities" + ("c" "reconnect to mpd" jao-player-connect) + ("N" "next random album" jao-random-album-next) + ("r" (lambda () + (concat (if jao-random-album-p "dis" "en") "able random album")) + jao-random-album-toggle) + ("P" (lambda () + (concat "Toggle to " + (if (equal jao-mpc-last-port jao-mopidy-port) + "mpd" "mopidy"))) + jao-mpc-toggle-port)]]) + + (global-set-key (kbd "s-m") #'jao-transient-media) + + #+end_src +* General transients + #+begin_src emacs-lisp + (defun jao-list-packages () + (interactive) + (jao-afio--goto-scratch-1) + (package-list-packages)) + + (defun jao-window-system-p () + (or jao-exwm-enabled jao-xmonad-enabled jao-sway-enabled)) + + (transient-define-prefix jao-transient-utils () + "Global operations in X11." + [["Notes" + ("n" "capture note" jao-org-notes-open-or-create) + ("/" "search notes" jao-org-notes-open) + ("\\" "grep notes" jao-org-notes-grep)] + ["Documents" :if jao-window-system-p + ("d" "go to doc" jao-select-pdf) + ("D" "open to doc" jao-open-doc)] + ["Packages" + ("a" "aptitude" jao-term-aptitude) + ("l" "packages" jao-list-packages)] + ["Monitors" + ("p" "htop" jao-term-htop) + ("v" "vpn status" jao-mullvad-status) + ("m" "set tmr" tmr)] + ["Network" + ("S" "ssh" jao-ssh) + ("b" "bluetooth" bluetooth-list-devices) + ("c" "connect chats" jao-all-chats) + ("N" "network interfaces" enwc)] + ["Chats" + ("t" "telegram" jao-chats-telega) + ("s" "slack" jao-chats-slack) + ("i" "irc" jao-chats-irc) + ("T" "telegram rooster" telega)] + ["Window system" :if jao-window-system-p + ("w" "set wallpaper" jao-set-wallpaper) + ("W" "set radom wallpaper" jao-set-random-wallpaper) + ("x" "restart xmobar" jao-xmobar-restart :if jao-exwm-enabled-p) + ("x" "kill xmobar" jao-xmobar-kill :if jao-xmonad-enabled-p)] + ["Helpers" + ("r" "org reveal" org-reveal) + ("k" (lambda () (concat "keyboard" (when (jao-kb-toggled-p) "*"))) + jao-kb-toggle :if jao-window-system-p) + ("M" (lambda () (concat "minibuffer" (when jao-minibuffer-mode "*"))) + jao-minibuffer-mode)]]) + + (global-set-key (kbd "s-w") #'jao-transient-utils) + #+end_src +* Key bindings + #+begin_src emacs-lisp + (global-set-key "\C-cj" #'join-line) + (global-set-key "\C-cn" #'next-error) + (global-set-key "\C-cq" #'auto-fill-mode) + (global-set-key "\C-xr\M-w" #'kill-rectangle-save) + (global-set-key "\C-c\C-z" #'comment-or-uncomment-region) + (global-set-key "\C-z" #'comment-or-uncomment-region) + (global-set-key (kbd "C-c W") #'jao-open-in-x-frame) + #+end_src +* Last minute (post.el) + #+begin_src emacs-lisp + (jao-load-site-el "post") + #+end_src diff --git a/attic/orgs/misc.el b/attic/orgs/misc.el new file mode 100644 index 0000000..b56c5df --- /dev/null +++ b/attic/orgs/misc.el @@ -0,0 +1,95 @@ +;;;; sway +(defun jao-swaymsg (msg) + (shell-command (format "swaymsg '%s' >/dev/null" msg))) + +(defmacro jao-def-swaymsg (name msg) + `(defun ,(intern (format "jao-sway-%s" name)) () + (interactive) + (jao-swaymsg ,msg))) +(jao-def-swaymsg firefox "[app_id=firefox] focus") + +(defvar jao-sway-enabled + (and (eq window-system 'pgtk) (not jao-xmonad-enabled))) + +(defun jao-sway-set-wallpaper (f) + (jao-swaymsg (format "output * bg %s fill" f)) + (make-symbolic-link f "~/.wallpaper.sway" t)) + +(defun jao-sway-run-or-focus (cmd &optional ws) + (if (jao-shell-running-p "firefox") + (jao-swaymsg (format "[app_id=%s] focus" cmd)) + (jao-swaymsg (format "workspace %s" (or ws 2))) + (start-process-shell-command cmd nil cmd))) + +(defun jao-sway-run-or-focus-tidal () + (interactive) + (if (jao-shell-running-p "tidal-hifi") + (jao-swaymsg "[app_id=tidal-hifi] scratchpad show") + (start-process-shell-command "tidal-hifi" nil "tidal-hifi &") + (jao-sway-run-or-focus-tidal))) + +(defun jao-sway-run-or-focus-firefox () + (interactive) + (jao-sway-run-or-focus "firefox")) + +(defun jao-sway-enable () + (setq jao-browse-doc-use-emacs-p t) + (setq jao-wallpaper-random-wake nil) + (jao-trisect) + (jao-set-transparency 85) + (jao-themes-setup) + ;; (display-time-mode 1) + (global-set-key (kbd "s-f") #'jao-sway-run-or-focus-firefox) + (defalias 'jao-streaming-list #'jao-sway-run-or-focus-tidal) + (message "Welcome to sway")) + +(when jao-sway-enabled + (defalias 'x-change-window-property #'ignore) + (add-hook 'after-init-hook #'jao-sway-enable)) + +;;;; time display +(setq display-time-world-list + '(("Europe/Paris" "Barcelona") + ("America/Los_Angeles" "Los Angeles") + ("America/New_York" "New York") + ("Europe/London" "London") + ("Asia/Calcutta" "Bangalore") + ("Asia/Tokyo" "Tokyo"))) + +(defun jao-time--pdt-hour () + (jao-time-at-zone "%H" "America/Los_Angeles")) + +(defun jao-time--chicago-hour () + (jao-time-at-zone "%H" "America/Chicago")) + +(defun jao-time-at-zone (format zone) + (set-time-zone-rule zone) + (prog1 (format-time-string format) + (set-time-zone-rule nil))) + +(defun jao-time-echo-la-time () + (interactive) + (message (jao-time-at-zone "LA %H:%M" "America/Los_Angeles"))) + +(defun jao-time-echo-times () + (interactive) + (let ((msg (format "%s (%s)" + (format-time-string "%a, %e %B - %H:%M") + (jao-time-at-zone "%H:%M" "America/Los_Angeles")))) + (jao-notify msg "" (jao-data-file "clock-world-icon.png")))) + +(defun jao-time-to-epoch (&optional s) + "Transform a time string to an epoch integer in milliseconds." + (interactive) + (let ((s (or s (read-string "Time string: " (thing-at-point 'string))))) + (message "%s = %s" + s + (round (* 1000 (time-to-seconds (parse-time-string s))))))) + +(defun jao-epoch-to-time (&optional v) + "Transform an epoch, given in milliseconds, to a time string." + (interactive) + (let ((v (or v (read-number "Milliseconds: " (thing-at-point 'number))))) + (message "%s = %s" v + (format-time-string "%Y-%m-%d %H:%M:%S" + (seconds-to-time (/ v 1000.0)))))) diff --git a/attic/orgs/misc.org b/attic/orgs/misc.org new file mode 100644 index 0000000..3a31d6a --- /dev/null +++ b/attic/orgs/misc.org @@ -0,0 +1,255 @@ +* corfu + #+begin_src emacs-lisp + (use-package corfu + :ensure t + :demand t + :init (setq corfu-echo-documentation 0.25 + corfu-cycle t + corfu-count 15 + corfu-quit-no-match t + corfu-auto t + corfu-commit-predicate nil + corfu-preview-current nil + corfu-preselect-first t + corfu-min-width 20 + corfu-max-width 100) + :config + + ;; show eldoc string immediately after accepted completion too + (with-eval-after-load "eldoc" + (eldoc-add-command-completions "corfu-")) + + (defun jao-corfu-enable-no-auto () + (setq-local corfu-auto nil) + (corfu-mode 1)) + + (defmacro jao-corfu-no-auto (mode) + (let ((mode-name (intern (format "%s-mode" mode))) + (hook-name (intern (format "%s-mode-hook" mode)))) + `(with-eval-after-load ',mode + (add-to-list 'corfu-excluded-modes ',mode-name) + (add-hook ',hook-name #'jao-corfu-enable-no-auto)))) + + (jao-corfu-no-auto eshell) + ;; (add-to-list 'corfu-excluded-modes 'notmuch-message-mode) + + (defun jao-corfu--active-p () + (and (>= corfu--index 0) (/= corfu--index corfu--preselect))) + + (defun jao-corfu-quit-or-insert () + (interactive) + (if (jao-corfu--active-p) (corfu-insert) (corfu-quit))) + + (defun jao-corfu-quit-or-previous () + (interactive) + (if (jao-corfu--active-p) + (corfu-previous) + (corfu-quit) + (previous-line))) + + :bind (:map corfu-map + ("C-" . corfu-insert) + ("\r" . jao-corfu-quit-or-insert) + ("C-p" . jao-corfu-quit-or-previous))) + + (defun corfu-in-minibuffer () + (when (not (bound-and-true-p vertico--input)) (corfu-mode 1))) + + (when (display-graphic-p) + (add-hook 'minibuffer-setup-hook #'corfu-in-minibuffer 1) + (global-corfu-mode 1)) + + #+end_src +* erc +*** package + #+begin_src emacs-lisp + (use-package erc + :init + (setq erc-modules + '(autojoin + button + dcc + fill + irccontrols + match + move-to-prompt + netsplit + networks + noncommands + notify + pcomplete + ring + services + stamp + track + truncate)) + + (setq erc-auto-query 'bury + erc-autojoin-channels-alist `(("libera.chat" ,@jao-libera-channels)) + erc-away-nickname "jao" + erc-button-buttonize-nicks t + erc-common-server-suffixes '(("libera.chat$" . "lb")) + erc-current-nick-highlight-type 'nick-or-keyword + erc-email-userid (car jao-mails) + erc-fill-column 84 + erc-fill-prefix " " + erc-format-nick-function 'erc-format-@nick + erc-header-line-face-method t + erc-header-line-format nil ;; "%l %o" + erc-header-line-uses-tabbar-p nil + erc-hide-list '("JOIN" "PART" "QUIT") + erc-hide-timestamps nil + erc-input-line-position -1 + erc-insert-timestamp-function 'erc-insert-timestamp-right + erc-join-buffer 'bury + erc-kill-buffer-on-part t + erc-kill-queries-on-quit t + erc-log-channels-directory nil + erc-mode-line-away-status-format "(a)" + erc-mode-line-format "%t" + erc-nick "jao" + erc-notice-highlight-type 'all + erc-notice-prefix "- " + erc-notify-signoff-hook 'erc-notify-signoff + erc-notify-signon-hook 'erc-notify-signon + erc-pcomplete-nick-postfix "," + erc-rename-buffers t + erc-server-send-ping-timeout 60 + erc-prompt ":" + erc-prompt-for-nickserv-password nil + erc-use-auth-source-for-nickserv-password t + erc-prompt-for-password nil + erc-public-away-p t + erc-server "irc.libera.chat" + erc-server-coding-system '(utf-8 . undecided) + erc-server-reconnect-attempts 10 + erc-server-reconnect-timeout 10 + erc-timestamp-format "%H:%M" + erc-timestamp-only-if-changed-flag t + erc-timestamp-right-column 84 + erc-user-full-name "https://jao.io" + erc-user-mode "+i" + erc-whowas-on-nosuchnick t) + + :config + + (define-minor-mode ncm-erc-mode "" nil + (:eval (format " [%s]" (hash-table-count erc-channel-users)))) + + (add-hook 'erc-mode-hook (lambda () (ncm-erc-mode 1))) + (add-hook 'erc-mode-hook (lambda () (auto-fill-mode -1)))) + #+end_src +*** no angles + #+begin_src emacs-lisp + (defun jao-erc--no-angles (old-func &rest args) + (let ((msg (apply old-func args))) + (replace-regexp-in-string "^<\\([^>]+\\)>" "(\\1)" msg))) + + (with-eval-after-load "erc" + (modify-syntax-entry ?\( "." erc-mode-syntax-table) + (modify-syntax-entry ?\) "." erc-mode-syntax-table) + (advice-add 'erc-format-privmessage :around #'jao-erc--no-angles) + (advice-add 'erc-format-my-nick :around #'jao-erc--no-angles)) + #+end_src +*** tracking + #+begin_src emacs-lisp + (defun jao-erc-track-shorten (names) + (let ((names (erc-track-shorten-names names))) + (mapcar (lambda (n) (string-remove-prefix "#" n)) names))) + + (setq erc-track-exclude-server-buffer t + erc-track-exclude-types '("NICK" "JOIN" "PART" "QUIT" "MODE" "KICK") + erc-track-remove-disconnected-buffers t + erc-track-shorten-aggressively t ;; 'max + erc-track-shorten-function #'jao-erc-track-shorten + erc-track-switch-direction 'importance + erc-track-visibility nil ;; t all, nil only selected frame + erc-track-position-in-mode-line nil + erc-track-enable-keybindings nil ;; 'ask + erc-track-faces-priority-list '(erc-error-face + erc-current-nick-face + erc-pal-face + erc-direct-msg-face + erc-nick-msg-face + erc-default-face + erc-action-face + erc-notice-face)) + (defun jao-track-erc-buffers () + (dolist (e erc-modified-channels-alist) + (tracking-add-buffer (car e) (list (cddr e))))) + + (with-eval-after-load "erc-track" + (require 'tracking nil t) + (add-hook 'exwm-workspace-switch-hook #'erc-modified-channels-update) + (add-hook 'erc-track-list-changed-hook #'jao-track-erc-buffers)) + + (jao-shorten-modes 'erc-mode) + (jao-tracking-faces 'erc-error-face + 'erc-pal-face + 'erc-current-nick-face + 'erc-direct-msg-face) + #+end_src +*** commands (/recover &co.) + #+begin_src emacs-lisp + (defun erc-cmd-RECOVER (&rest ignore) + "Recover nick" + (let ((fn (jao--get-user/password "freenode"))) + (erc-cmd-MSG (format "nickserv IDENTIFY %s %s" (car fn) (cadr fn))) + (erc-cmd-MSG (format "nickserv GHOST %s" (car fn))) + (erc-cmd-MSG (format "nickserv RELEASE %s" (car fn))) + (erc-cmd-NICK (car fn)))) + #+end_src +*** startup + #+begin_src emacs-lisp + (defun jao-erc (&optional yes) + (interactive "P") + ;; (when (or yes (y-or-n-p "Connect to bitlbee using ERC? ")) + ;; (erc-select :server "localhost")) + (when (or yes (y-or-n-p "Connect to libera using ERC? ")) + (erc-select :server "irc.libera.chat"))) + #+end_src +* dtache + #+begin_src emacs-lisp + (use-package dtache + :ensure t + :hook (after-init . dtache-setup)) + + (use-package dtache-eshell + :after dtache + :hook (eshell-mode . dtache-eshell-mode)) + + (use-package dtache-consult + :after (consult dtache) + :bind ([remap dtache-open-session] . dtache-consult-session)) + #+end_src +* signel + #+begin_src emacs-lisp :tangle no + (jao-load-org "lib/net/signel.org") + (with-eval-after-load "tracking" + (jao-tracking-faces 'signel-notification) + (jao-shorten-modes 'signel-chat-mode)) + (setq signel-report-deliveries t) + #+end_src +* cdlatex + #+begin_src emacs-lisp + (use-package cdlatex + :ensure t + :hook ((org-mode . org-cdlatex-mode)) + :diminish ((cdlatex-mode . " £") + (org-cdlatex-mode . " £"))) + #+end_src +* maps + #+begin_src emacs-lisp + (use-package osm + :ensure t + :init + (with-eval-after-load 'org (require 'osm-ol)) + :config + (transient-define-prefix jao-transient-osm () + ["Open Street Maps" + ("s" "search" osm-search) + ("g" "goto" osm-goto) + ("b" "jump to bookmark" osm-bookmark-jump) + ("t" "server" osm-server)]) + :bind ("C-c M" . #'jao-transient-osm)) + #+end_src diff --git a/attic/orgs/notmuch.org b/attic/orgs/notmuch.org new file mode 100644 index 0000000..0368ac1 --- /dev/null +++ b/attic/orgs/notmuch.org @@ -0,0 +1,675 @@ +#+property: header-args:emacs-lisp :lexical t :tangle yes :comments yes :results silent :shebang ";; -*- lexical-binding: t; -*-" :tangle-mode (identity #o644) +#+title: notmuch configuration + +* minibuffer + #+begin_src emacs-lisp + (defvar jao-notmuch-minibuffer-string "") + + (defvar jao-notmuch-minibuffer-queries + '((:name "" :query "tag:new and not tag:draft" :face jao-themes-f00) + (:name "B" :query "tag:new and tag:bigml and tag:inbox" :face default) + (:name "b" :query "tag:new and tag:bigml and tag:bugs" + :face jao-themes-error) + (:name "S" :query "tag:new and tag:bigml and tag:support" :face default) + (:name "W" + :query "tag:new and tag:bigml and not tag:\"/support|bugs|inbox/\"" + :face default) + (:name "I" + :query "tag:new and tag:jao and tag:inbox" + :face jao-themes-warning) + (:name "J" + :query "tag:new and tag:jao and not tag:\"/local|hacking|draft|inbox|prog|words/\"" + :face default) + (:name "H" :query "tag:new and tag:hacking and not tag:\"/emacs/\"") + (:name "E" :query "tag:new and tag:\"/emacs/\"") + (:name "l" :query "tag:new and tag:local") + (:name "F" :query "tag:new and tag:feeds and not tag:\"/emacs/\""))) + + (defun jao-notmuch-notify () + (let ((cnts (notmuch-hello-query-counts jao-notmuch-minibuffer-queries))) + (setq jao-notmuch-minibuffer-string + (mapconcat (lambda (c) + (propertize (format "%s%s" + (plist-get c :name) + (plist-get c :count)) + 'face (or (plist-get c :face) + 'jao-themes-dimm))) + cnts + " ")) + (jao-minibuffer-refresh))) + + (when jao-notmuch-enabled + (jao-minibuffer-add-variable 'jao-notmuch-minibuffer-string -20)) + #+end_src +* saved searches + #+begin_src emacs-lisp + (defvar jao-notmuch--new "tag:\"/^(unread|new)$/\"") + (defvar jao-notmuch--newa (concat jao-notmuch--new " AND ")) + + (defun jao-notmuch--q (d0 d1 &optional k qs st) + (let ((q (or (when qs (mapconcat #'identity qs " AND ")) + (concat jao-notmuch--newa + (mapconcat (lambda (d) (when d (concat "tag:" d))) + (list d0 d1) " AND "))))) + (list :name (concat d0 (when (and d1 (not (string= "" d1))) "/") d1) + :key k :query q :search-type (or st 'tree) + :sort-order 'oldest-first))) + + (defun jao-notmuch--qn (d0 d1 k qs &optional st) + (jao-notmuch--q d0 d1 k (cons jao-notmuch--new qs) st)) + + (defun jao-notmuch--sq (tag &optional k d0 d1) + (jao-notmuch--qn (or d0 "feeds") (or d1 tag) k (list (concat "tag:" tag)))) + + (defvar jao-notmuch--shared-tags + '("new" "unread" "flagged" "signed" "sent" "attachment" "forwarded" + "encrypted" "gmane" "gnus" "feeds" "rss" "mce" "trove" "prog" "emacs")) + + (defun jao-notmuch--subtags (tag &rest excl) + (let* ((cmd (concat "notmuch search --output=tags tag:" tag)) + (ts (split-string (shell-command-to-string cmd)))) + (seq-difference ts (append jao-notmuch--shared-tags (cons tag excl))))) + + (defvar jao-notmuch-feed-searches-news + (mapcar #'jao-notmuch--sq '("news" "fun" "words" "computers"))) + + (defvar jao-notmuch-feed-searches-hack + (mapcar #'jao-notmuch--sq + '("xmobar" "geiser" "mdk" "mailutils" "notmuch"))) + + (defvar jao-notmuch-feed-searches-lang + (append (mapcar #'jao-notmuch--sq + '( "lobsters" "clojure" "lisp" "scheme" + "haskell" "idris" "erlang" "pharo")) + `(,(jao-notmuch--qn "feeds" "prog" "fp" + '("tag:prog" "not tag:\"/emacs/\""))))) + + (defvar jao-notmuch-feed-searches-sci + (mapcar #'jao-notmuch--sq + '("philosophy" "math" "physics" "sci" "gr-qc" "quant-ph"))) + + (defvar jao-notmuch-feed-searches + (append jao-notmuch-feed-searches-news + jao-notmuch-feed-searches-hack + jao-notmuch-feed-searches-lang + jao-notmuch-feed-searches-sci)) + + (defvar jao-notmuch-bigml-searches + `(,(jao-notmuch--q "bigml" "inbox" "bi") + ,(jao-notmuch--q "bigml" "alba" "ba") + ,(jao-notmuch--q "bigml" "support" "bs") + ,(jao-notmuch--q "bigml" "bugs" "bb") + ,(jao-notmuch--q "bigml" "drivel" "bd") + ,(jao-notmuch--q "bigml" "lists" "bl"))) + + (defvar jao-notmuch-inbox-searches + `(,(jao-notmuch--q "jao" "inbox" "ji") + ,(jao-notmuch--q "jao" "bills" "jb") + ,(jao-notmuch--q "jao" "drivel" "jd") + ,(jao-notmuch--q "jao" "mdk" "jm") + ,(jao-notmuch--qn "jao" "hacking" "jh" + '("tag:hacking" "not tag:\"/emacs/\"")) + ,(jao-notmuch--qn "jao" "local" "jl" '("tag:local")))) + + (defvar jao-notmuch-mark-searches + `(,(jao-notmuch--q "jao" "drafts" "d" '("tag:draft")) + ,(jao-notmuch--q "bml" "flagged" "rb" '("tag:flagged" "tag:bigml")) + ,(jao-notmuch--q "jao" "flagged" "rj" '("tag:flagged" "tag:jao")) + ,(jao-notmuch--q "feeds" "flagged" "rf" '("tag:flagged" "tag:feeds")))) + + (defvar jao-notmuch-emacs-searches + `(,(jao-notmuch--sq "emacs" "ee" "emacs" "feeds") + ,(jao-notmuch--sq "emacs-help" "eh" "emacs" "help") + ,(jao-notmuch--sq "emacs-github" "eg" "emacs" "github") + ,(jao-notmuch--sq "emacs-devel" "ed" "emacs" "devel") + ,(jao-notmuch--sq "emacs-bugs" "eb" "emacs" "bugs") + ,(jao-notmuch--sq "emacs-diffs" "ec" "emacs" "diffs") + ,(jao-notmuch--sq "emacs-orgmode" "eo" "emacs" "org"))) + + (setq notmuch-saved-searches + (append jao-notmuch-inbox-searches + jao-notmuch-bigml-searches + jao-notmuch-mark-searches + jao-notmuch-feed-searches + jao-notmuch-emacs-searches)) + + (defvar jao-notmuch-dynamic-searches + `(,(jao-notmuch--q "bml" "today" "tb" '("tag:bigml" "date:24h..")) + ,(jao-notmuch--q "jao" "today" "tj" + '("tag:jao" "date:24h.." + "not tag:\"/(feeds|spam|local)/\"")))) + + (defvar jao-notmuch-new-searches + `(,(jao-notmuch--q "new" nil "nn" '("tag:new" "not tag:draft")) + ,(jao-notmuch--q "unread" nil "nu" '("tag:unread")) + (:query "*" :name "messages"))) + + (defun jao-notmuch-tree-widen-search () + (interactive) + (when-let ((query (notmuch-tree-get-query))) + (let ((notmuch-show-process-crypto (notmuch-tree--message-process-crypto))) + (notmuch-tree-close-message-window) + (notmuch-tree (string-replace jao-notmuch--newa "" query))))) + + (defun jao-notmuch-widen-searches (searches &optional extra) + (mapcar (lambda (s) + (let* ((q (plist-get s :query)) + (qs (string-replace jao-notmuch--newa "" q))) + (plist-put (copy-sequence s) :query (concat qs extra)))) + searches)) + + (defvar jao-notmuch-widened-searches + (jao-notmuch-widen-searches notmuch-saved-searches)) + + (defvar jao-notmuch-flagged-searches + (let ((s (seq-difference notmuch-saved-searches + jao-notmuch-mark-searches))) + (jao-notmuch-widen-searches s " AND tag:flagged"))) + + (defun jao-notmuch-jump-search (&optional widen) + (interactive "P") + (let ((notmuch-saved-searches + (if widen jao-notmuch-widened-searches notmuch-saved-searches))) + (notmuch-jump-search))) + + #+end_src +* tags + #+begin_src emacs-lisp + (setq notmuch-archive-tags '("+trove" "-new" "-inbox") + notmuch-show-mark-read-tags '("-new" "-unread") + notmuch-tag-formats + (let ((d `(:foreground ,(face-attribute 'jao-themes-dimm :foreground))) + (e `(:foreground ,(face-attribute 'jao-themes-error :foreground) + :weight bold))) + `(("unread") + ("signed") + ("new" "N") + ("replied" "↩" (propertize tag 'face '(:family "Fira Code"))) + ("sent" "S") + ("attachment" "📎") + ("deleted" "🗙" (propertize tag 'face '(:underline nil ,@e))) + ;; ("attachment" "+") + ;; ("deleted" "xxx" (propertize tag 'face '(:underline nil ,@e))) + ("flagged" "!" (propertize tag 'face ',e)) + ("jao" "j") + ("bigml" "b") + ("feeds" "f") + ("gmane" "g"))) + notmuch-tag-deleted-formats + '(("unread") + ("new") + (".*" (notmuch-apply-face tag 'notmuch-tag-deleted)))) + + (with-eval-after-load "notmuch-tag" + (advice-add #'notmuch-read-tag-changes + :filter-return (lambda (x) (mapcar #'string-trim x)))) + #+end_src +* package + #+begin_src emacs-lisp + (add-to-list 'load-path "/usr/local/share/emacs/site-lisp/") + + (use-package notmuch + :init + (setq notmuch-address-use-company t + notmuch-address-command (if jao-notmuch-enabled 'internal 'as-is) + notmuch-always-prompt-for-sender t + notmuch-draft-folder "local" + notmuch-draft-quoted-tags '("part") + notmuch-address-internal-completion '(received nil) + notmuch-fcc-dirs + '(("\\(support\\|education\\)@bigml.com" . nil) + (".*@bigml.com" . "bigml/trove +bigml +sent -new -unread") + (".*" . "jao/trove +jao +sent +trove -new -unread")) + notmuch-maildir-use-notmuch-insert t) + + :config + + (add-hook 'message-send-hook #'notmuch-mua-attachment-check) + + (when jao-notmuch-enabled + (define-key message-mode-map (kbd "C-c C-d") #'notmuch-draft-postpone) + (setq message-directory "~/var/mail/" + message-auto-save-directory "/tmp" + mail-user-agent 'message-user-agent)) + + :bind (:map notmuch-common-keymap + (("E" . jao-notmuch-open-enclosure) + ("B" . notmuch-show-resend-message) + ("b" . jao-notmuch-browse-urls)))) + + (use-package jao-notmuch :demand t) + + #+end_src +* hello + #+begin_src emacs-lisp + (defun jao-notmuch-hello--insert-searches (searches title) + (when-let (searches (notmuch-hello-query-counts searches)) + (let* ((cnt (when title + (seq-reduce (lambda (c q) + (+ c (or (plist-get q :count) 0))) + searches + 0))) + (title (if title (format "[ %d %s ]\n\n" cnt title) "\n"))) + (widget-insert (propertize title 'face 'jao-themes-f00)) + (let ((notmuch-column-control 1.0) + (start (point))) + (notmuch-hello-insert-buttons searches) + (indent-rigidly start (point) notmuch-hello-indent)) + cnt))) + + (defun jao-notmuch-hello-insert-inbox-searches () + (jao-notmuch-hello--insert-searches jao-notmuch-inbox-searches "inbox")) + + (defun jao-notmuch-hello-insert-bigml-searches () + (jao-notmuch-hello--insert-searches jao-notmuch-bigml-searches "bigml")) + + (defun jao-notmuch-hello-insert-mark-searches () + (jao-notmuch-hello--insert-searches jao-notmuch-mark-searches "marks") + (jao-notmuch-hello--insert-searches jao-notmuch-flagged-searches nil)) + + (defun jao-notmuch-hello-insert-feeds-searches () + (let ((sect "feeds")) + (dolist (s `(,jao-notmuch-feed-searches-news + ,jao-notmuch-feed-searches-hack + ,jao-notmuch-feed-searches-lang + ,jao-notmuch-feed-searches-sci)) + (let ((i (funcall #'jao-notmuch-hello--insert-searches s sect))) + (setq sect (unless i sect)))))) + + (defun jao-notmuch-hello-insert-emacs-searches () + (jao-notmuch-hello--insert-searches jao-notmuch-emacs-searches "emacs")) + + (defun jao-notmuch-hello-insert-dynamic-searches () + (jao-notmuch-hello--insert-searches jao-notmuch-dynamic-searches "dynamic") + (jao-notmuch-hello--insert-searches jao-notmuch-new-searches nil)) + + (defun jao-notmuch-refresh-agenda () + (interactive) + (save-window-excursion (org-agenda-list)) + (let ((b (current-buffer))) + (pop-to-buffer "*Calendar*") + (goto-char (point-min)) + (calendar-goto-today) + (pop-to-buffer b))) + + (defun jao-notmuch-hello-first () + (interactive) + (let ((inhibit-message t)) + (beginning-of-buffer) + (widget-forward 1))) + + (defun jao-notmuch-refresh-hello (&optional agenda) + (interactive "P") + (ignore-errors + (when (and (string= "Mail" (jao-afio-current-frame)) + (derived-mode-p 'notmuch-hello-mode)) + (when (not (string-blank-p jao-notmuch-minibuffer-string)) + (let ((notmuch-hello-auto-refresh nil)) (notmuch-hello))) + (when agenda (jao-notmuch-refresh-agenda)) + (unless (widget-at) (jao-notmuch-hello-first))))) + + (defvar jao-notmuch-hello--sec-rx "^\\(\\[ [0-9]+\\|All tags:.+\\)") + + (defun jao-notmuch-hello-next-section () + (interactive) + (when (re-search-forward jao-notmuch-hello--sec-rx nil t) + (widget-forward 1))) + + (defun jao-notmuch-hello-prev-section () + (interactive) + (beginning-of-line) + (unless (looking-at-p jao-notmuch-hello--sec-rx) + (re-search-backward jao-notmuch-hello--sec-rx nil t)) + (when (re-search-backward jao-notmuch-hello--sec-rx nil t) + (end-of-line) + (widget-forward 1))) + + (defun jao-notmuch-hello-next () + (interactive) + (if (widget-at) + (widget-button-press (point)) + (jao-notmuch-hello-next-section))) + + (use-package notmuch-hello + :init + (setq notmuch-column-control t + notmuch-hello-sections '(jao-notmuch-hello-insert-bigml-searches + jao-notmuch-hello-insert-inbox-searches + jao-notmuch-hello-insert-feeds-searches + jao-notmuch-hello-insert-emacs-searches + jao-notmuch-hello-insert-mark-searches + jao-notmuch-hello-insert-dynamic-searches + notmuch-hello-insert-alltags) + notmuch-hello-hide-tags nil + notmuch-hello-thousands-separator "," + notmuch-hello-auto-refresh t + notmuch-show-all-tags-list nil + notmuch-show-logo nil + notmuch-show-empty-saved-searches nil) + + :hook ((notmuch-hello-refresh . jao-notmuch-notify) + (jao-afio-switch . jao-notmuch-refresh-hello)) + + :bind (:map notmuch-hello-mode-map + (("a" . jao-notmuch-refresh-agenda) + ("j" . jao-notmuch-jump-search) + ("n" . jao-notmuch-hello-next) + ("p" . widget-backward) + ("S" . consult-notmuch) + ("g" . jao-notmuch-refresh-hello) + ("." . jao-notmuch-hello-first) + ("SPC" . widget-button-press) + ("[" . jao-notmuch-hello-prev-section) + ("]" . jao-notmuch-hello-next-section)))) + + #+end_src +* show + #+begin_src emacs-lisp + (defun jao-notmuch-open-enclosure (add) + (interactive "P") + (with-current-notmuch-show-message + (goto-char (point-min)) + (if (not (search-forward "Enclosure:" nil t)) + (user-error "No enclosure in message body") + (re-search-forward "https?://" nil t) + (if-let (url (thing-at-point-url-at-point)) + (progn + (message "%s %s ..." (if add "Adding" "Playing") url) + (unless add (jao-mpc-clear)) + (jao-mpc-add-url url) + (unless add (jao-mpc-play))) + (error "Found an enclosure, but not a link!"))))) + + (defconst jao-mail-clean-rx + (regexp-opt '("ElDiario.es - ElDiario.es: " "The Guardian: " + "The Conversation – Articles (UK): "))) + + (defun jao-mail-clean-address (args) + (when-let ((address (car args))) + (list (if (string-match ".+ updates on arXiv.org: \\(.+\\)" address) + (with-temp-buffer + (insert (match-string 1 address)) + (let ((shr-width 1000)) + (shr-render-region (point-min) (point-max))) + (replace-regexp-in-string "\"" "" (buffer-string))) + (replace-regexp-in-string jao-mail-clean-rx "" address))))) + + (use-package notmuch-show + :init + (setq gnus-blocked-images "." + notmuch-message-headers + '("To" "Cc" "Date" "Reply-To" "List-Id" "X-RSS-Feed") + notmuch-show-only-matching-messages t + notmuch-show-part-button-default-action 'notmuch-show-view-part + notmuch-wash-signature-lines-max 0 + notmuch-wash-wrap-lines-length 80 + notmuch-wash-citation-lines-prefix 10 + notmuch-wash-citation-lines-suffix 20 + notmuch-show-text/html-blocked-images "." + notmuch-show-header-line #'jao-notmuch-message-header-line) + + :config + + (advice-add 'notmuch-clean-address :filter-args #'jao-mail-clean-address) + + :bind + (:map notmuch-show-mode-map + (("h" . jao-notmuch-goto-tree-buffer) + ("TAB" . jao-notmuch-show-next-button) + ([backtab] . jao-notmuch-show-previous-button) + ("RET" . jao-notmuch-show-ret)))) + #+end_src +* search + #+begin_src emacs-lisp + (use-package notmuch-search + :init (setq notmuch-search-result-format + '(("date" . "%12s ") + ("count" . "%-7s ") + ("authors" . "%-35s") + ("subject" . " %-100s") + (jao-notmuch-format-tags . " (%s)")) + notmuch-search-buffer-name-format "*%s*" + notmuch-saved-search-buffer-name-format "*%s*") + :bind (:map notmuch-search-mode-map + (("RET" . notmuch-tree-from-search-thread) + ("M-RET" . notmuch-search-show-thread)))) + + #+end_src +* tree + #+begin_src emacs-lisp + (defun jao-notmuch-tree--forward (&optional prev) + (interactive) + (forward-line (if prev -1 1)) + (when prev (forward-char 2)) + (jao-notmuch-tree-scroll-or-next)) + + (defun jao-notmuch-tree--backward () + (interactive) + (jao-notmuch-tree--forward t)) + + (defun jao-notmuch--via-url () + (when (window-live-p notmuch-tree-message-window) + (with-selected-window notmuch-tree-message-window + (goto-char (point-min)) + (when (re-search-forward "^Via: http" nil t) + (thing-at-point-url-at-point))))) + + (defun jao-notmuch-browse-url (ext) + (interactive "P") + (when-let (url (or (jao-notmuch--via-url) + (car (last (jao-notmuch-message-urls))))) + (funcall (if ext browse-url-secondary-browser-function #'browse-url) + url))) + + (defun jao-notmuch-adjust-tree-fonts (&optional family) + (let ((fg (face-attribute 'jao-themes-dimm :foreground)) + (family (or family "Source Code Pro"))) + (dolist (f '(notmuch-tree-match-tree-face + notmuch-tree-no-match-tree-face)) + (if family + (set-face-attribute f nil :family family :foreground fg) + (set-face-attribute f nil :foreground fg))))) + + (use-package notmuch-tree + :init + (setq notmuch-tree-result-format + `(("date" . "%12s ") + (jao-notmuch-format-author . 25) + (jao-notmuch-format-msg-ticks . ,jao-mails-regexp) + (jao-notmuch-format-tree-and-subject . "%>-85s") + (jao-notmuch-format-tags . " (%s)")) + notmuch-unthreaded-result-format notmuch-search-result-format + consult-notmuch-result-format + `((jao-notmuch-format-msg-ticks . ,jao-mails-regexp) + ("date" . "%12s ") + ("authors" . "%-35s") + ("subject" . " %-100s") + (jao-notmuch-format-tags . " (%s)")) + ;; notmuch-tree-thread-symbols + ;; '((prefix . "─") (top . "─") (top-tee . "┬") + ;; (vertical . "│") (vertical-tee . "├") (bottom . "╰") + ;; (arrow . "")) + notmuch-tree-thread-symbols + '((prefix . " ") (top . " ") (top-tee . " ") + (vertical . " ") (vertical-tee . " ") (bottom . " ") + (arrow . ""))) + :config + + (jao-notmuch-adjust-tree-fonts + (when (string-prefix-p "Hack" jao-themes-default-face) "Source Code Pro")) + + (jao-notmuch-tree-setup "T") + + (defun jao-notmuch-before-tree (&rest args) + (when (string= (buffer-name) "*notmuch-hello*") + (split-window-right 40) + (other-window 1))) + + (defvar jao-notmuch--visits 0) + + (defun jao-notmuch-after-tree-quit (&optional both) + (when (and (not (derived-mode-p 'notmuch-tree-mode 'notmuch-hello-mode)) + (save-window-excursion (other-window -1) + (derived-mode-p 'notmuch-hello-mode))) + (delete-window) + (jao-notmuch-refresh-hello (= 0 (mod (cl-incf jao-notmuch--visits) 10))))) + + (advice-add 'notmuch-tree :before #'jao-notmuch-before-tree) + (advice-add 'notmuch-tree-quit :after #'jao-notmuch-after-tree-quit) + + :bind (:map notmuch-tree-mode-map + (("b" . jao-notmuch-browse-urls) + ("d" . jao-notmuch-tree-toggle-delete) + ("D" . jao-notmuch-tree-toggle-delete-thread) + ("h" . jao-notmuch-goto-message-buffer) + ("H" . jao-notmuch-click-message-buffer) + ("i" . jao-notmuch-toggle-images) + ("K" . jao-notmuch-tag-jump-and-next) + ("k" . jao-notmuch-tree-read-thread) + ("n" . jao-notmuch-tree-next) + ("N" . jao-notmuch-tree--forward) + ("O" . notmuch-tree-toggle-order) + ("o" . jao-notmuch-tree-widen-search) + ("p" . jao-notmuch-tree-previous) + ("P" . jao-notmuch-tree--backward) + ("r" . notmuch-tree-reply) + ("R" . notmuch-tree-reply-sender) + ("s" . jao-notmuch-tree-toggle-spam) + ("u" . jao-notmuch-tree-toggle-flag) + ("v" . notmuch-tree-scroll-message-window) + ("V" . notmuch-tree-scroll-message-window-back) + ("x" . jao-notmuch-arXiv-capture) + ("<" . jao-notmuch-tree-beginning-of-buffer) + (">" . jao-notmuch-tree-end-of-buffer) + ("\\" . notmuch-tree-view-raw-message) + ("." . jao-notmuch-toggle-mime-parts) + ("=" . jao-notmuch-tree-toggle-message) + ("RET" . jao-notmuch-tree-show-or-scroll) + ("SPC" . jao-notmuch-tree-scroll-or-next) + ("M-g" . jao-notmuch-browse-url) + ("M-u" . jao-notmuch-tree-reset-tags)))) + #+end_src +* org mode + Stolen and adapted from [[https://gist.github.com/fedxa/fac592424473f1b70ea489cc64e08911][Fedor Bezrukov]]. + #+begin_src emacs-lisp + (defvar jao-org-notmuch-last-subject nil) + (defun jao-org-notmuch-last-subject () jao-org-notmuch-last-subject) + + (defun jao-notmuch--add-tags (tags) + (if (derived-mode-p 'notmuch-show-mode) + (notmuch-show-add-tag tags) + (notmuch-tree-add-tag tags))) + + (defun org-notmuch-store-link () + "Store a link to a notmuch mail message." + (cl-case major-mode + ((notmuch-show-mode notmuch-tree-mode) + ;; Store link to the current message + (let* ((id (notmuch-show-get-message-id)) + (link (concat "notmuch:" id)) + (subj (notmuch-show-get-subject)) + (description (format "Mail: %s" subj))) + (setq jao-org-notmuch-last-subject subj) + (when (y-or-n-p "Archive message? ") + (jao-notmuch--add-tags '("+trove"))) + (when (y-or-n-p "Flag message as todo? ") + (jao-notmuch--add-tags '("+flagged"))) + (org-store-link-props + :type "notmuch" + :link link + :description description))) + (notmuch-search-mode + ;; Store link to the thread on the current line + (let* ((id (notmuch-search-find-thread-id)) + (link (concat "notmuch:" id)) + (subj (notmuch-search-find-subject)) + (description (format "Mail: %s" subj))) + (setq jao-org-notmuch-last-subject subj) + (org-store-link-props + :type "notmuch" + :link link + :description description))))) + + (with-eval-after-load "org" + (org-link-set-parameters "notmuch" + :follow 'notmuch-show + :store 'org-notmuch-store-link)) + #+end_src +* arXiv + #+begin_src emacs-lisp + (use-package org-capture + :config + (add-to-list 'org-capture-templates + '("X" "arXiv" entry (file "notes/physics/arxiv.org") + "* %(jao-org-notmuch-last-subject)\n %i" + :immediate-finish t) + t) + (org-capture-upgrade-templates org-capture-templates)) + + (defun jao-notmuch-arXiv-capture () + (interactive) + (save-window-excursion + (jao-notmuch-goto-message-buffer) + (save-excursion + (goto-char (point-min)) + (re-search-forward "\\[ text/html \\]") + (forward-paragraph) + (setq-local transient-mark-mode 'lambda) + (set-mark (point)) + (goto-char (point-max)) + (org-capture nil "X")))) + + #+end_src +* html render + #+begin_src emacs-lisp + (when jao-notmuch-enabled (setq mm-text-html-renderer 'shr)) + #+end_src +* consult + #+begin_src emacs-lisp + (jao-load-path "consult-notmuch") + (require 'consult-notmuch) + (consult-customize consult-notmuch :preview-key 'any) + + (defvar jao-consult-notmuch-history nil) + + (defvar jao-mailbox-folders '("bigml" "jao")) + + (defun jao-consult-notmuch-folder (&optional tree folder) + (interactive "P") + (let ((folder (if folder + (file-name-as-directory folder) + (completing-read "Group: " + jao-mailbox-folders + nil nil nil + jao-consult-notmuch-history + "."))) + (folder (replace-regexp-in-string "/\\(.\\)" ".\\1" folder)) + (init (read-string "Initial query: ")) + (init (format "folder:/%s/ %s" folder init))) + (if tree (consult-notmuch-tree init) (consult-notmuch init)))) + + (with-eval-after-load "notmuch-hello" + (define-key notmuch-hello-mode-map "f" #'jao-consult-notmuch-folder)) + #+end_src +* link hint + #+begin_src emacs-lisp + (with-eval-after-load "link-hint" + (defun jao-link-hint--notmuch-next-part (&optional bound) + (when-let (p (next-single-property-change (point) :notmuch-part nil bound)) + (and (< p (or bound (point-max))) p))) + + (defun jao-link-hint--notmuch-part-p () + (and (get-text-property (point) :notmuch-part) + (when-let (b (button-at (point))) (button-label b)))) + + (link-hint-define-type 'notmuch-part + :next #'jao-link-hint--notmuch-next-part + :at-point-p #'jao-link-hint--notmuch-part-p + :vars '(notmuch-show-mode) + :open #'push-button + :open-message "Toggled" + :open-multiple t) + + (push 'link-hint-notmuch-part link-hint-types)) + + #+end_src diff --git a/attic/orgs/org.org b/attic/orgs/org.org new file mode 100644 index 0000000..bcce3e1 --- /dev/null +++ b/attic/orgs/org.org @@ -0,0 +1,316 @@ +#+property: header-args :lexical t :tangle yes :comments yes :results silent :shebang ";; -*- lexical-binding: t" :tangle-mode (identity #o644) +#+title: Org (and related) mode configuration + +* General configuration + #+begin_src emacs-lisp + (use-package org + :ensure t + :custom ((org-export-backends '(ascii html latex texinfo))) + :init + (defalias 'jao-open-gnus-frame 'jao-afio--goto-mail) + + (setq org-adapt-indentation t + org-catch-invisible-edits 'smart + org-complete-tags-always-offer-all-agenda-tags t + org-cycle-separator-lines 0 ;; no blank lines when all colapsed + org-deadline-warning-days 14 + org-default-notes-file (expand-file-name "inbox.org" org-directory) + org-directory jao-org-dir + org-display-remote-inline-images 'download ;; 'skip 'cache + org-ellipsis " .." ;; ↴ + org-email-link-description-format "Email %c: %s" + org-enforce-todo-dependencies t + org-fast-tag-selection-single-key 'expert + ;; org-list-demote-modify-bullet '(("+" . "-") ("-" . "+") ("*" . "+")) + org-link-frame-setup + '((gnus . (lambda (&optional x) (jao-open-gnus-frame))) + (file . find-file-other-window)) + org-log-done nil + org-modules '(bibtex info eww eshell git-link) + org-odd-levels-only t + org-outline-path-complete-in-steps nil + org-refile-allow-creating-parent-nodes 'confirm + org-refile-targets '((nil :maxlevel . 5) + (org-agenda-files :maxlevel . 5)) + org-refile-use-outline-path 'file + org-return-follows-link t + org-reverse-note-order t + org-special-ctrl-a/e t + org-src-fontify-natively t + org-startup-folded t + org-tag-alist nil + org-tags-column -75 + org-todo-keywords + '((sequence "TODO(t)" "STARTED(s!)" "|" "DONE(d!)") + (sequence "REPLY(r)" "WAITING(w!)" "|" "DONE(d!)") + (sequence "TOREAD(T)" "READING(R!)" "|" "READ(a!)") + (sequence "|" "CANCELLED(x!)" "SOMEDAY(o!)" "DONE(d!)")) + org-use-fast-todo-selection t + org-use-speed-commands nil ;; t and then ? to see help + org-gnus-prefer-web-links nil)) + (require 'org) + #+end_src +* Agenda + #+begin_src emacs-lisp + (setq ;; org-agenda-custom-commands + ;; '(("w" todo "WAITING" nil) + ;; ("W" agenda "" ((org-agenda-ndays 21)))) + org-agenda-files (mapcar (lambda (f) + (expand-file-name f jao-org-dir)) + '("inbox.org" "bigml.org")) + org-agenda-block-separator " " + org-agenda-breadcrumbs-separator "•" + org-agenda-current-time-string "•" ;; "*" + org-agenda-time-grid + '((daily today require-timed) + (800 1000 1200 1400 1600 1800 2000) "" "·") + org-agenda-include-diary t + org-agenda-include-inactive-timestamps t + org-agenda-inhibit-startup nil + org-agenda-restore-windows-after-quit t + org-agenda-show-all-dates t + org-agenda-skip-deadline-if-done t + org-agenda-skip-scheduled-if-done nil + org-agenda-span 14 + org-agenda-start-on-weekday nil + org-agenda-window-setup 'current-window) + #+end_src +* Capture templates + #+BEGIN_SRC emacs-lisp + (setq org-capture-templates + '(("t" "TODO" entry + (file+headline "inbox.org" "Todo") + "* TODO %?\n %i%a" :prepend t) + ("r" "REPLY" entry + (file+headline "inbox.org" "Todo") + "* REPLY %:subject%?\n %t\n %i%a" :prepend t) + ("a" "Appointment" entry + (file+olp "inbox.org" "Appointments") + "* %^T %?\n %a" :time-prompt t) + ("i" "Inbox note" entry (file+headline "inbox.org" "Notes") + "* %a\n %i%?(added on: %u)" :prepend t))) + ;; (org-capture-upgrade-templates org-capture-templates) + #+END_SRC +* MIME and file apps + #+BEGIN_SRC emacs-lisp + (setq org-file-apps + '((system . mailcap) + (".*\\.djvu" . system) + (t . emacs))) + #+END_SRC +* Appearance + #+begin_src emacs-lisp + ;; Show hidden emphasis markers + (use-package org-appear + :ensure t + :hook (org-mode . org-appear-mode)) + + ;; #+caption: Image caption. + ;; #+attr_org: :width 100 + ;; [[file:path/to/image.png]] + + (setq org-startup-indented nil + org-pretty-entities nil + org-hide-emphasis-markers t + org-hide-leading-stars t + org-startup-with-inline-images t + org-image-actual-width '(300)) + + (use-package org-modern + :ensure t + :init (setq org-modern-label-border 1)) + + ;; (unless (display-graphic-p) (global-org-modern-mode 1)) + + #+end_src +* LaTeX + #+begin_src emacs-lisp + (use-package org-fragtog + :after org + :ensure t + :hook ((org-mode . org-fragtog-mode))) + + (require 'org-fragtog) + + (setq org-format-latex-options + `(:foreground default + :background + ,(if (jao-colors-scheme-dark-p) "black" "white") + :scale 1.25 + :html-foreground "black" + :html-background "Transparent" + :html-scale 1.0 + :matchers ("begin" "$1" "$" "$$" "\\(" "\\[")) + org-preview-latex-image-directory + (expand-file-name "~/.emacs.d/cache/ltximg/") + org-latex-hyperref-template nil + org-highlight-latex-and-related '(latex script entities)) + + (require 'ox-latex) + #+end_src + +* Export (minted) + + #+begin_src emacs-lisp + (setq org-latex-listings 'minted + org-latex-packages-alist '(("" "minted")) + org-latex-pdf-process + '("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f" + "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f")) + #+end_src + +* Babel and literate programming + - [[http://cachestocaches.com/2018/6/org-literate-programming][Literate Programming with Org-mode]] + - [[http://howardism.org/Technical/Emacs/literate-devops.html][Literate DevOps]] + + #+begin_src emacs-lisp + (setq org-src-window-setup 'other-window) ;; current-window + (require 'org-tempo nil t) ;; emacs-major-version 26) (use-package htmlize :ensure t)) - (defvar jao-blog-base-dir "~/doc/jao.io") - (defun jao-blog-dir (p) (expand-file-name p jao-blog-base-dir)) - - (setq jao-org-blog-tag-files - (seq-difference (directory-files (jao-blog-dir "blog") nil "tag-.*") - "tag-norss.html") - - jao-org-blog-tags - (mapcar (lambda (f) - (string-match "tag-\\(.+\\)\\.html" f) - (format "%s" - f (match-string 1 f))) - jao-org-blog-tag-files) - - jao-org-blog-tag-rss - (mapcar (lambda (f) - (string-match "\\(.+\\)-rss\\.xml" f) - (format "%s" - f (match-string 1 f))) - (directory-files (jao-blog-dir "blog") nil ".*-rss.xml")) - - jao-org-blog-tag-names - (mapcar (lambda (f) - (string-match "tag-\\(.+\\)\\.html" f) - (match-string 1 f)) - jao-org-blog-tag-files)) - #+end_src -* HTML headers and footers -*** Header - #+begin_src emacs-lisp - (setq org-static-blog-page-header - (concat - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n") - - org-static-blog-page-preamble - (concat - "
" - " programming (and other) musings" - "
" - " about" - " | hacking" - " | archive" - " |
" - " tags" - "
" - (mapconcat #'identity jao-org-blog-tags "") - "
" - "
" - " |
" - " rss" - "
" - (mapconcat #'identity jao-org-blog-tag-rss "") - "
" - "
" - "
" - "
")) - #+end_src -*** Footer - #+begin_src html :tangle ~/.emacs.d/commons.html :comments no :shebang "" -
- - Creative Commons License - -
- jao.io by - jao - is licensed under a - - Creative Commons Attribution-ShareAlike 3.0 Unported License. -
- #+end_src - - #+begin_src emacs-lisp - (setq org-static-blog-page-postamble - (with-temp-buffer - (insert-file-contents "~/.emacs.d/commons.html") - (buffer-string))) - #+end_src -* Package - #+begin_src emacs-lisp - (use-package org-static-blog - :ensure t - :init - (setq org-static-blog-use-preview t - org-static-blog-preview-link-p t - org-static-blog-preview-start "" - org-static-blog-preview-end "" - org-static-blog-preview-date-first-p t - org-static-blog-index-length 30 - org-static-blog-preview-convert-titles t - org-static-blog-preview-ellipsis "more ..." - org-static-blog-enable-tags t - org-static-blog-tags-file "tags.html" - org-static-blog-rss-file "rss.xml" - org-static-blog-publish-url "https://jao.io/blog/" - org-static-blog-publish-title "programming (and other) musings" - org-static-blog-posts-directory (jao-blog-dir "posts/") - org-static-blog-drafts-directory (jao-blog-dir "pages/") - org-static-blog-publish-directory (jao-blog-dir "blog/") - org-static-blog-rss-extra "" ; "mail@jao.io\n" - org-static-blog-rss-max-entries 30 - org-static-blog-rss-excluded-tag "norss" - org-static-blog-enable-tag-rss t - org-export-with-toc nil - org-export-with-section-numbers nil) - - :config - (defun jao-org-static-post-path (pf dt) - (cond ((string-match-p "pages/.*\\|in-no-particular-order" pf) - (file-name-nondirectory pf)) - ((string-match-p "drafts/.*" pf) pf) - ((string-match-p "^[[:digit:]]+-.*" pf) pf) - (t (concat (format-time-string "%Y-%m-%d-" dt) - (file-name-nondirectory pf))))) - (advice-add 'org-static-blog-generate-post-path :override - #'jao-org-static-post-path) - - :bind (:map org-mode-map (("C-c B" . jao-transient-org-blog)))) - #+end_src -* Commands -*** New entries - #+begin_src emacs-lisp - (defun jao-org-blog-publish-file (fname) - (interactive (list (read-file-name "Publish: " - nil - (buffer-file-name) - t - (buffer-file-name)))) - (let ((geiser-active-implementations '(guile)) - (geiser-default-implementation 'guile)) - (org-static-blog-publish-file fname))) - - (defconst jao-org-static-blog--prev-beg - "#+begin_export html\n \n#+end_export ") - - (defconst jao-org-static-blog--prev-end - "#+begin_export html\n \n#+end_export ") - - (defun jao-org-static-blog-create-new-post (&optional draft) - (interactive) - (let* ((title (read-string "Title: ")) - (file (replace-regexp-in-string "\s" "-" (downcase title))) - (tags (completing-read-multiple "Tags: " jao-org-blog-tag-names))) - (find-file (expand-file-name (concat file ".org") - (if draft - org-static-blog-drafts-directory - org-static-blog-posts-directory))) - (insert "#+title: " title "\n" - "#+date: " (format-time-string "<%Y-%m-%d %H:%M>") "\n" - "#+filetags: " - (mapconcat #'identity tags " ") - "\n\n") - (when (member "books" tags) - (insert jao-org-static-blog--prev-beg - "\n\n[[https://jao.io/img/" file ".jpg]]\n\n")) - (save-excursion (insert jao-org-static-blog--prev-end "\n")))) - #+end_src -*** Drafts - #+begin_src emacs-lisp - (defun jao-org-static-blog-update-date () - (interactive) - (when (y-or-n-p "Update date? ") - (goto-char (point-min)) - (when (re-search-forward "^#\\+date: " nil t) - (let ((kill-whole-line nil)) (kill-line)) - (insert (format-time-string "<%Y-%m-%d %H:%M>")) - (save-buffer)))) - - (defun jao-org-static-blog-create-new-draft () - (interactive) - (jao-org-static-blog-create-new-post t)) - - (defun jao-org-static-blog-publish-draft () - (interactive) - (let* ((from (read-file-name "Post: " - org-static-blog-drafts-directory - nil t)) - (to (expand-file-name (file-name-nondirectory from) - org-static-blog-posts-directory))) - (rename-file from to) - (when-let ((b (get-buffer from))) - (kill-buffer b)) - (find-file to) - (jao-org-static-blog-update-date) - (when (y-or-n-p "Generate HTML? ") - (jao-org-blog-publish)))) - - (defun jao-org-static-blog-edit-draft () - (interactive) - (find-file (read-file-name "Edit: " - org-static-blog-drafts-directory - nil - t))) - #+end_src -*** Publish - #+begin_src emacs-lisp - (defun jao-org-blog-publish (&optional force) - (interactive "P") - (let ((geiser-active-implementations '(guile)) - (geiser-default-implementation 'guile)) - (org-static-blog-publish force))) - - (defun jao-org-blog-republish () - (interactive) - (jao-org-blog-publish t)) - #+end_src -* Transient - #+begin_src emacs-lisp - (defun jao-org-static-prev-begin () - (interactive) - (insert jao-org-static-blog--prev-beg)) - - (defun jao-org-static-prev-end () - (interactive) - (insert jao-org-static-blog--prev-end)) - - (jao-transient-major-mode+ org - ["Insert blog snippet" - ("s" "preview begin" jao-org-static-prev-begin) - ("S" "preview end" jao-org-static-prev-end) - ("T" "update date" jao-org-static-blog-update-date)] - ["Edit blog" - ("n" "create post" jao-org-static-blog-create-new-post) - ("d" "create draft" jao-org-static-blog-create-new-draft) - ("e" "edit draft" jao-org-static-blog-edit-draft)] - ["Publish blog" - ("D" "publish draft" jao-org-static-blog-publish-draft) - ("f" "publish single file" jao-org-blog-publish-file) - ("p" "publish all" jao-org-blog-publish) - ("r" "republish" jao-org-blog-republish)]) - #+end_src diff --git a/completion.org b/completion.org deleted file mode 100644 index 3a4049a..0000000 --- a/completion.org +++ /dev/null @@ -1,657 +0,0 @@ -#+property: header-args :lexical t :tangle yes :comments yes :results silent :shebang ";; -*- lexical-binding: t -*-" :tangle-mode (identity #o644) -#+title: Completion configuration -#+auto_tangle: t - -* imenu - #+begin_src emacs-lisp - (use-package imenu - :init (setq org-imenu-depth 7) - :config - (defun jao-imenu-hook () - (cond ((derived-mode-p 'org-mode) (org-reveal t)) - (outline-minor-mode (outline-show-entry)))) - (add-hook 'imenu-after-jump-hook #'jao-imenu-hook)) - #+end_src -* completion styles -*** completion configuration - #+begin_src emacs-lisp - (setq tab-always-indent 'complete - read-extended-command-predicate #'command-completion-default-include-p - completion-category-defaults nil - completion-cycle-threshold nil - completions-detailed t - completion-show-help nil - completion-show-inline-help nil - completion-ignore-case t - completion-wrap-movement t - completion-auto-select nil - completions-format 'one-column - completion-styles '(basic substring partial-completion emacs22) - completion-category-overrides - '((file (styles partial-completion)) - (command (styles initials substring partial-completion)) - (symbol (styles initials substring partial-completion)) - (variable (styles initials substring partial-completion)))) - - ;; (setq completions-sort #'jao-completion--sort-by-length-alpha) - (setq completions-sort #'jao-completion--sort-by-history) - - (defun jao-completion--sort-by-alpha-length (elems) - (sort elems (lambda (c1 c2) - (or (string-version-lessp c1 c2) - (< (length c1) (length c2)))))) - - (defun jao-completion--sort-by-history (elems) - (let ((hist (and (not (eq minibuffer-history-variable t)) - (symbol-value minibuffer-history-variable)))) - (if hist - (minibuffer--sort-by-position hist elems) - (jao-completion--sort-by-alpha-length elems)))) - - #+end_src -*** crm indicator - #+begin_src emacs-lisp - (defun jao-completion--crm-indicator (args) - "Add prompt indicator to `completing-read-multiple' filter ARGS." - (cons (concat "[CRM] " (car args)) (cdr args))) - (advice-add #'completing-read-multiple - :filter-args #'jao-completion--crm-indicator) - #+end_src -*** directory navigation - #+begin_src emacs-lisp - (defun jao-completion-backward-updir () - "Delete char before point or go up a directory." - (interactive nil mct-minibuffer-mode) - (cond ((and (eq (char-before) ?/) - (eq (mct--completion-category) 'file)) - (when (string-equal (minibuffer-contents) "~/") - (delete-minibuffer-contents) - (insert (expand-file-name "~/")) - (goto-char (line-end-position))) - (save-excursion - (goto-char (1- (point))) - (when (search-backward "/" (minibuffer-prompt-end) t) - (delete-region (1+ (point)) (point-max))))) - (t (call-interactively 'backward-delete-char)))) - - (define-key minibuffer-local-filename-completion-map (kbd "DEL") - #'jao-completion-backward-updir) - #+end_src -* orderless - #+begin_src emacs-lisp - (use-package orderless - :ensure t - :init - :config - (orderless-define-completion-style orderless+initialism - (orderless-matching-styles '(orderless-initialism - orderless-prefixes - orderless-literal - orderless-regexp))) - - (defun jao-orderless--set-locally () - (setq-local completion-styles - '(substring partial-completion orderless) - - completion-category-overrides - '((file (styles partial-completion orderless)) - (command (styles orderless+initialism))) - orderless-matching-styles - '(orderless-literal orderless-regexp orderless-prefixes))) - (add-hook 'minibuffer-setup-hook #'jao-orderless--set-locally)) - - #+end_src -* marginalia - #+begin_src emacs-lisp - (use-package marginalia - :ensure t - :bind (:map minibuffer-local-map ("C-M-a" . marginalia-cycle)) - - :custom ((marginalia-align 'left) - (marginalia-align-offset 1) - (marginalia-field-width 200) - (marginalia-annotators - '(marginalia-annotators-heavy marginalia-annotators-light nil)) - (marginalia-separator " "))) - - (marginalia-mode 1) - #+end_src -* company - #+begin_src emacs-lisp - (use-package company - :ensure t - :custom ((company-backends '(company-capf - ;; company-bbdb - company-files - company-dabbrev - company-keywords)) - (company-global-modes '(not slack-message-buffer-mode - circe-channel-mode - telega-chat-mode)) - (company-format-margin-function nil) ;; #'company-text-icons-margin - (company-idle-delay 0.2) - (company-lighter "") - (company-lighter-base "") - (company-show-numbers nil) - (company-selection-wrap-around t) - (company-tooltip-limit 15) - (company-tooltip-align-annotations t) - (company-tooltip-offset-display 'lines)) ;; 'scrollbar - - :config - (defun jao-complete-at-point () - "Complete using company unless we're in the minibuffer." - (interactive) - (if (or (not company-mode) (window-minibuffer-p)) - (completion-at-point) - (company-manual-begin))) - - (defun jao-company-use-in-tab () - (global-set-key [remap completion-at-point] #'jao-complete-at-point) - (global-set-key [remap completion-symbol] #'jao-complete-at-point) - (global-set-key (kbd "M-TAB") #'jao-complete-at-point)) - - (jao-company-use-in-tab) - - :bind (:map company-active-map - - ("" . company-complete-common-or-cycle) - ("TAB" . company-complete-common-or-cycle) - - ("C-h" . company-show-doc-buffer) - ("M-." . company-show-location) - ("C-" . company-complete-selection) - ([remap return] . company-abort) - ("RET" . company-abort) - - :filter (or (not (derived-mode-p 'eshell-mode)) - (company-explicit-action-p)) - ("" . company-complete-selection) - ("RET" . company-complete-selection)) - :diminish) - - (unless (display-graphic-p) (global-company-mode 1)) - #+end_src -* vertico - #+begin_src emacs-lisp - (use-package vertico - :ensure t - :init - (setq vertico-count 20 - vertico-cycle t - vertico-resize t - org-refile-use-outline-path t) - - :config - - ;; (setq completion-in-region-function - ;; (lambda (&rest args) - ;; (apply (if (and (not window-system) vertico-mode) - ;; #'consult-completion-in-region - ;; #'completion--in-region) - ;; args))) - - (defun jao-vertico--display-candidates (lines) - (move-overlay vertico--candidates-ov (point-min) (point-min)) - (overlay-put vertico--candidates-ov 'after-string (apply #'concat lines)) - (vertico--resize-window (length lines))) - - (advice-add 'vertico--display-candidates - :override #'jao-vertico--display-candidates)) - - (use-package vertico-directory - :after vertico - :bind (:map vertico-map (("RET" . vertico-directory-enter) - ("M-" . vertico-directory-delete-word) - ("" . vertico-directory-delete-char)))) - - (vertico-mode) - - #+end_src -* consult -*** package - #+begin_src emacs-lisp - (use-package consult - :ensure t - :bind (("C-x M-:" . consult-complex-command) - ("C-x b" . consult-buffer) - ("C-x C-b" . consult-buffer) - ("C-x 4 b" . consult-buffer-other-window) - ("C-c b" . project-find-file) - ("C-c h" . consult-history) - ("C-c i" . consult-imenu) - ("C-c I" . consult-project-imenu) - ("C-c k" . consult-ripgrep) - ("C-c K" . consult-git-grep) - ("C-c L" . consult-locate) - ;; ("C-h m" . consult-mode-command) - ("C-c s" . consult-line) - ("C-x r x" . consult-register) - ("C-x r b" . consult-bookmark) - ("C-x C-f" . jao-find-file) - ("M-g b" . consult-bookmark) - ("M-g m" . consult-mark) - ("M-g e" . consult-error) - ("M-s m" . consult-multi-occur) - ("M-s o" . consult-outline) - ("M-y" . consult-yank-pop) - ("C-s" . isearch-forward) - ("C-S-s" . consult-line) - (" a" . consult-apropos)) - - :custom ((consult-preview-key (kbd "`"))) - - :init - (fset 'multi-occur #'consult-multi-occur) - - :config - - (defun jao-find-file (arg) - (interactive "P") - (call-interactively (if arg 'consult-file-externally 'find-file))) - - (define-key consult-narrow-map (vconcat consult-narrow-key "?") - #'consult-narrow-help) - - (consult-customize consult-mark :preview-key 'any) - (add-hook 'completion-list-mode-hook #'consult-preview-at-point-mode)) - - #+end_src -*** consult-dir - #+begin_src emacs-lisp - (use-package consult-dir - :ensure t - :bind (("C-x C-d" . consult-dir) - :map minibuffer-local-completion-map - (("C-x C-d" . consult-dir) - ("C-x C-j" . consult-dir-jump-file)))) - #+end_src -*** dh-diff hunks - #+begin_src emacs-lisp - (defun jao-consult--diff-lines (&optional backward) - (let ((candidates) - (width (length (number-to-string - (line-number-at-pos (point-max) - consult-line-numbers-widen))))) - (save-excursion - (while (ignore-errors (diff-hl-next-hunk backward)) - (let* ((str (buffer-substring (line-beginning-position) - (line-end-position))) - (no (line-number-at-pos (point))) - (no (consult--line-number-prefix (point-marker) no width))) - (push (concat no str) candidates)))) - (if backward candidates (nreverse candidates)))) - - (defun jao-consult-hunks () - (interactive) - (let ((candidates (append (jao-consult--diff-lines) - (jao-consult--diff-lines t)))) - (unless candidates (error "No changes!")) - (consult--jump - (consult--read candidates - :prompt "Go to hunk: " - :category 'consult--encode-location - :sort nil - :require-match t - :lookup #'consult--line-match - :state (consult--jump-state))))) - - (with-eval-after-load "consult" - (consult-customize '((jao-consult-hunks :preview-key any))) - (global-set-key (kbd "C-x v c") #'jao-consult-hunks)) - #+end_src -*** narrow helpers - #+begin_src emacs-lisp - (defvar jao-consult-narrow nil) - - (defun jao-consult-initial-narrow () - (when-let (c (cond ((eq this-command #'consult-buffer) - (cdr (assoc (jao-afio-current-frame) - jao-consult-narrow))) - ((eq this-command #'consult-mode-command) ?m))) - (setq unread-command-events (append unread-command-events `(,c 32))))) - - (add-hook 'minibuffer-setup-hook #'jao-consult-initial-narrow) - - (defmacro jao-consult--mode-buffers (&rest modes) - `(lambda () - (seq-map #'buffer-name - (seq-filter (lambda (b) - (with-current-buffer b - (derived-mode-p ,@modes))) - (buffer-list))))) - - (defun jao-consult-add-buffer-source (src &optional aframe key) - (add-to-list 'consult-buffer-sources src t) - (when (and aframe key) - (add-to-list 'jao-consult-narrow (cons aframe key)))) - #+end_src -*** narrowing chats - #+begin_src emacs-lisp - (defvar jao-chat-buffer-source - (list :name "chats" - :category 'buffer - :action #'pop-to-buffer - :hidden t - :narrow (cons ?c "chats") - :items (jao-consult--mode-buffers 'erc-mode - 'circe-channel-mode - 'circe-query-mode - 'signel-chat-mode - 'slack-message-buffer-mode - 'slack-thread-message-buffer-mode - 'telega-root-mode - 'telega-chat-mode))) - (with-eval-after-load "consult" - (jao-consult-add-buffer-source 'jao-chat-buffer-source)) - #+end_src - -*** exwm - #+begin_src emacs-lisp :tangle no - (with-eval-after-load "exwm" - (defun consult-exwm-preview-fix (&rest _args) - "Kludge to stop EXWM buffers from stealing focus during Consult previews." - (when-let ((mini (active-minibuffer-window))) - (select-window (active-minibuffer-window)))) - - (advice-add #'consult--buffer-action :after #'consult-exwm-preview-fix)) - - #+end_src -* embark -*** package - #+begin_src emacs-lisp - (use-package embark - :ensure t - :demand t - :init - (setq embark-quit-after-action nil - embark-indicator #'embark-mixed-indicator - embark-verbose-indicator-buffer-sections '(bindings) - embark-mixed-indicator-both t - embark-verbose-indicator-excluded-commands - '(embark-become embark-export embark-collect) - embark-verbose-indicator-nested t - embark-verbose-indicator-display-action - '((display-buffer-at-bottom) - (window-parameters (mode-line-format . none)) - (window-height . fit-window-to-buffer))) - - :bind (("C-;" . embark-act) - ("C-c ;" . embark-act) - ("C-'" . embark-dwim) - ("C-c '" . embark-dwim) - (:map minibuffer-local-map - (("C-'" . embark-dwim) - ("C-c '" . embark-dwim) - ("C-," . embark-become) - ("C-c ," . embark-become) - ("C-o" . embark-export))))) - - (use-package embark-consult - :ensure t - :after (embark consult)) - - (with-eval-after-load 'consult - (with-eval-after-load 'embark - (require 'embark-consult))) - - #+end_src -*** randomsig - #+begin_src emacs-lisp - (defun jao-random-sig-read (_ignored) - "Import region as signature and edit it." - (randomsig-message-read-sig t)) - - (define-key embark-region-map "m" #'jao-random-sig-read) - (define-key embark-region-map "M" #'apply-macro-to-region-lines) - #+end_src -*** dict/say - #+begin_src emacs-lisp - (defun jao-say (&optional word) - "Isn't it nice to have a computer that can talk to you?" - (interactive "sWhat? ") - (shell-command-to-string (format "say %s" word)) - "") - - (define-key embark-identifier-map "D" #'dictionary-search) - (define-key embark-identifier-map "S" #'jao-say) - #+end_src -*** org targets - #+begin_src emacs-lisp - (declare-function org-link-any-re "ol") - (declare-function org-open-link-from-string "ol") - (declare-function org-in-regexp "org-macs") - - (defun jao-embark-targets--org-link () - (when (derived-mode-p 'org-mode) - (when (org-in-regexp org-link-bracket-re) - (let ((lnk (match-string-no-properties 1))) - (if (string-match-p "https?://.+" (or lnk "")) - (cons 'url lnk) - (cons 'org-link (match-string-no-properties 0))))))) - - (embark-define-keymap jao-embark-targets-org-link-map - "Actions for org links" - ((kbd "RET") org-open-link-from-string)) - - (defun jao-embark-targets--gl-org-link () - (when (org-in-regexp org-link-bracket-re) - (cons 'gl-org-link (match-string-no-properties 0)))) - - (embark-define-keymap jao-embark-targets-gl-org-link-map - "Actions for exteranl org links" - ((kbd "RET") org-open-at-point-global)) - - (add-to-list 'embark-target-finders #'jao-embark-targets--gl-org-link) - (add-to-list 'embark-keymap-alist - '(gl-org-link . jao-embark-targets-gl-org-link-map)) - - (add-to-list 'embark-target-finders #'jao-embark-targets--org-link) - (add-to-list 'embark-keymap-alist - '(org-link . jao-embark-targets-org-link-map)) - - #+end_src -*** url targets - #+begin_src emacs-lisp - (declare-function w3m-anchor "w3m") - - (defun jao-embark-targets--w3m-anchor () - (when (not (region-active-p)) - (when-let ((url (or (jao-url-around-point) - (thing-at-point 'url) - (and (derived-mode-p 'w3m-mode) - (or (w3m-anchor) w3m-current-url)) - (and (derived-mode-p 'eww-mode) - (eww-current-url))))) - (when (string-match-p "^https?.*" url) - (cons 'url url))))) - - (add-to-list 'embark-target-finders #'jao-embark-targets--w3m-anchor) - - (defun jao-embark-url (url) - "Browse URL, externally if we're already in an emacs browser." - (if (derived-mode-p 'w3m-mode 'eww-mode) - (jao-browse-with-external-browser url) - (browse-url url))) - - (define-key embark-url-map (kbd "RET") #'jao-embark-url) - (define-key embark-url-map (kbd "f") #'browse-url-firefox) - (define-key embark-url-map (kbd "x") #'jao-rss-subscribe) - (define-key embark-url-map (kbd "m") 'jao-browse-with-external-browser) - (define-key embark-url-map (kbd "p") 'jao-browse-add-url-to-mpc) - - #+end_src -*** video url targets - #+begin_src emacs-lisp - (defvar jao-embark-targets-video-sites - '("youtu.be" "youtube.com" "blip.tv" "vimeo.com" "infoq.com")) - - (defun jao-embark--video-url-rx (&optional sites) - (format "^https?://\\(?:www\\.\\)?%s/.+" - (regexp-opt (or sites jao-embark-targets-video-sites) t))) - - (defvar jao-embark-targets-video-url-rx (jao-embark--video-url-rx) - "A regular expression matching URLs that point to video streams") - - (defun jao-embark-targets--refine-url (_ url) - (if (string-match-p jao-embark-targets-video-url-rx url) - (cons 'video-url url) - (cons 'url url))) - - (defun jao-embark-targets--play-video (player url) - (interactive "sURL: ") - (let ((cmd (format "%s %s" player (shell-quote-argument url)))) - (jao-afio--goto-www) - (start-process-shell-command player nil cmd))) - - (defun jao-embark-targets-mpv (&optional url) - "Play video stream with mpv" - (interactive "sURL: ") - (jao-embark-targets--play-video "mpv" url)) - - (defun jao-embark-targets-vlc (&optional url) - "Play video stream with vlc" - (interactive "sURL: ") - (jao-embark-targets--play-video "vlc" url)) - - (embark-define-keymap jao-embark-targets-video-url-map - "Actions on URLs pointing to remote video streams." - :parent embark-url-map - ("v" jao-embark-targets-vlc) - ("RET" jao-embark-targets-mpv)) - - (add-to-list 'embark-transformer-alist '(url . jao-embark-targets--refine-url)) - (add-to-list 'embark-keymap-alist '(video-url . jao-embark-targets-video-url-map)) - (define-key embark-url-map "v" #'jao-embark-targets-vlc) - (define-key embark-url-map "V" #'jao-embark-targets-vlc) - - #+end_src -*** spotify - #+begin_src emacs-lisp - (with-eval-after-load "consult-spotify" - (defun jao-consult-spt-play (candidate) - (when-let (url (espotify-candidate-url candidate)) - (jao-spt-play-uri url))) - - (embark-define-keymap spotify-item-keymap - "Actions for Spotify search results" - ("s" jao-consult-spt-play) - ("y" espotify-yank-candidate-url) - ("a" espotify-play-candidate-album) - ("h" espotify-show-candidate-info)) - - (add-to-list 'embark-keymap-alist - '(spotify-search-item . spotify-item-keymap))) - #+end_src -* avy and link hints - [[https://karthinks.com/software/avy-can-do-anything/][Avy can do anything | Karthinks]] - #+begin_src emacs-lisp - (use-package avy - :ensure t - :init (setq avy-style 'pre - avy-background t - avy-timeout-seconds 0.6 - avy-single-candidate-jump t) - - :config - - (defun avy-embark-act (pt) - "Use Embark to act on the completion at PT." - (save-excursion - (goto-char pt) - (embark-act))) - (add-to-list 'avy-dispatch-alist '(?\; . avy-embark-act)) - - :bind (("s-j" . avy-goto-char-timer) - ("C-M-j" . avy-goto-char-timer))) - - (use-package link-hint - :ensure t - :init (setq link-hint-avy-style 'pre) - - :config - (defun jao-link-hint-open-link-ext () - (interactive) - (let ((jao-browse-url-function jao-browse-url-external-function)) - (link-hint-open-link))) - - :bind (("C-l" . link-hint-open-link) - ("C-M-l" . jao-link-hint-open-link-ext) - ("C-S-l" . jao-link-hint-open-link-ext) - ("C-x C-l" . recenter-top-bottom))) - - #+end_src -* recoll - #+begin_src emacs-lisp - (jao-load-path "consult-recoll") - - (defun jao-recoll-format (title url mtype) - (let* ((u (replace-regexp-in-string "/home/jao/" "" url)) - (u (replace-regexp-in-string - "\\(doc\\|org/doc\\|.emacs.d/gnus/Mail\\|var/mail\\)/" "" u))) - (format "%s (%s, %s)" - title - (propertize u 'face 'jao-themes-f00) - (propertize mtype 'face 'jao-themes-f01)))) - - (defun jao-recoll-open-html (file) - (jao-afio--goto-www) - (eww-open-file file)) - - (defun jao-recoll-consult-messages () - (interactive) - (consult-recoll "mime:message ")) - - (defun jao-recoll-consult-docs () - (interactive) - (consult-recoll (format "dir:%s/doc " jao-org-dir))) - - (defun jao-recoll-messages () - (interactive) - (jao-recoll "mime:message ")) - - (defun jao-recoll-docs () - (interactive) - (jao-recoll (format "dir:%s/doc " jao-org-dir))) - - (defun jao-recoll-notes () - (interactive) - (jao-recoll (format "dir:%s " jao-org-notes-dir))) - - (defun jao-recoll-consult-notes () - "Use consult-recoll to search notes." - (interactive) - (consult-recoll (format "dir:%s " jao-org-notes-dir))) - - (defun jao-recoll-open-with-notmuch (fname) - (let ((id (with-temp-buffer - (insert-file fname) - (goto-char (point-min)) - (when (re-search-forward "[Mm]essage-[Ii][Dd]: <]+\\)>?" - nil t) - (match-string 1))))) - (when id (notmuch-show (concat "id:" id))))) - - (use-package consult-recoll - :init (setq consult-recoll-open-fns - '(("application/pdf" . jao-open-doc) - ("message/rfc822" . jao-recoll-open-with-notmuch) - ("text/html" . jao-recoll-open-html)) - consult-recoll-search-flags nil - consult-recoll-format-candidate #'jao-recoll-format) - :config - (transient-define-prefix jao-recoll-transient () - [["Consult recoll queries" - ("r" "consult recoll query" consult-recoll) - ("n" "consult recoll on notes" jao-recoll-consult-notes) - ("d" "consult recoll on docs" jao-recoll-consult-docs) - ("m" "consult recoll on messages" jao-recoll-consult-messages)] - ["Recoll queries" - ("R" "recoll query" jao-recoll) - ("N" "recoll on notes" jao-recoll-notes) - ("D" "consult recoll on docs" jao-recoll-docs) - ("M" "consult recoll on messages" jao-recoll-messages)]]) - - :bind (("s-r" . #'jao-recoll-transient))) - - #+end_src diff --git a/custom/jao-custom-blog.el b/custom/jao-custom-blog.el new file mode 100644 index 0000000..6d2d3f6 --- /dev/null +++ b/custom/jao-custom-blog.el @@ -0,0 +1,226 @@ +;; -*- lexical-binding: t -*- + +;;; Vars and setup +(jao-load-path "org-static-blog") +(when (> emacs-major-version 26) (use-package htmlize :ensure t)) +(defvar jao-blog-base-dir "~/doc/jao.io") +(defun jao-blog-dir (p) (expand-file-name p jao-blog-base-dir)) + +(setq jao-org-blog-tag-files + (seq-difference (directory-files (jao-blog-dir "blog") nil "tag-.*") + "tag-norss.html") + + jao-org-blog-tags + (mapcar (lambda (f) + (string-match "tag-\\(.+\\)\\.html" f) + (format "%s" + f (match-string 1 f))) + jao-org-blog-tag-files) + + jao-org-blog-tag-rss + (mapcar (lambda (f) + (string-match "\\(.+\\)-rss\\.xml" f) + (format "%s" + f (match-string 1 f))) + (directory-files (jao-blog-dir "blog") nil ".*-rss.xml")) + + jao-org-blog-tag-names + (mapcar (lambda (f) + (string-match "tag-\\(.+\\)\\.html" f) + (match-string 1 f)) + jao-org-blog-tag-files)) + +;;; Header +(setq org-static-blog-page-header + (concat + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n") + + org-static-blog-page-preamble + (concat + "
" + " programming (and other) musings" + "
" + " about" + " | hacking" + " | archive" + " |
" + " tags" + "
" + (mapconcat #'identity jao-org-blog-tags "") + "
" + "
" + " |
" + " rss" + "
" + (mapconcat #'identity jao-org-blog-tag-rss "") + "
" + "
" + "
" + "
")) + +;;; Footer +(setq org-static-blog-page-postamble + (with-temp-buffer + (insert-file-contents "~/.emacs.d/commons.html") + (buffer-string))) + +;;; Package +(use-package org-static-blog + :ensure t + :init + (setq org-static-blog-use-preview t + org-static-blog-preview-link-p t + org-static-blog-preview-start "" + org-static-blog-preview-end "" + org-static-blog-preview-date-first-p t + org-static-blog-index-length 30 + org-static-blog-preview-convert-titles t + org-static-blog-preview-ellipsis "more ..." + org-static-blog-enable-tags t + org-static-blog-tags-file "tags.html" + org-static-blog-rss-file "rss.xml" + org-static-blog-publish-url "https://jao.io/blog/" + org-static-blog-publish-title "programming (and other) musings" + org-static-blog-posts-directory (jao-blog-dir "posts/") + org-static-blog-drafts-directory (jao-blog-dir "pages/") + org-static-blog-publish-directory (jao-blog-dir "blog/") + org-static-blog-rss-extra "" ; "mail@jao.io\n" + org-static-blog-rss-max-entries 30 + org-static-blog-rss-excluded-tag "norss" + org-static-blog-enable-tag-rss t + org-export-with-toc nil + org-export-with-section-numbers nil) + + :config + (defun jao-org-static-post-path (pf dt) + (cond ((string-match-p "pages/.*\\|in-no-particular-order" pf) + (file-name-nondirectory pf)) + ((string-match-p "drafts/.*" pf) pf) + ((string-match-p "^[[:digit:]]+-.*" pf) pf) + (t (concat (format-time-string "%Y-%m-%d-" dt) + (file-name-nondirectory pf))))) + (advice-add 'org-static-blog-generate-post-path :override + #'jao-org-static-post-path) + + :bind (:map org-mode-map (("C-c B" . jao-transient-org-blog)))) + +;;; New entries +(defun jao-org-blog-publish-file (fname) + (interactive (list (read-file-name "Publish: " + nil + (buffer-file-name) + t + (buffer-file-name)))) + (let ((geiser-active-implementations '(guile)) + (geiser-default-implementation 'guile)) + (org-static-blog-publish-file fname))) + +(defconst jao-org-static-blog--prev-beg + "#+begin_export html\n \n#+end_export ") + +(defconst jao-org-static-blog--prev-end + "#+begin_export html\n \n#+end_export ") + +(defun jao-org-static-blog-create-new-post (&optional draft) + (interactive) + (let* ((title (read-string "Title: ")) + (file (replace-regexp-in-string "\s" "-" (downcase title))) + (tags (completing-read-multiple "Tags: " jao-org-blog-tag-names))) + (find-file (expand-file-name (concat file ".org") + (if draft + org-static-blog-drafts-directory + org-static-blog-posts-directory))) + (insert "#+title: " title "\n" + "#+date: " (format-time-string "<%Y-%m-%d %H:%M>") "\n" + "#+filetags: " + (mapconcat #'identity tags " ") + "\n\n") + (when (member "books" tags) + (insert jao-org-static-blog--prev-beg + "\n\n[[https://jao.io/img/" file ".jpg]]\n\n")) + (save-excursion (insert jao-org-static-blog--prev-end "\n")))) + +;;; Drafts +(defun jao-org-static-blog-update-date () + (interactive) + (when (y-or-n-p "Update date? ") + (goto-char (point-min)) + (when (re-search-forward "^#\\+date: " nil t) + (let ((kill-whole-line nil)) (kill-line)) + (insert (format-time-string "<%Y-%m-%d %H:%M>")) + (save-buffer)))) + +(defun jao-org-static-blog-create-new-draft () + (interactive) + (jao-org-static-blog-create-new-post t)) + +(defun jao-org-static-blog-publish-draft () + (interactive) + (let* ((from (read-file-name "Post: " + org-static-blog-drafts-directory + nil t)) + (to (expand-file-name (file-name-nondirectory from) + org-static-blog-posts-directory))) + (rename-file from to) + (when-let ((b (get-buffer from))) + (kill-buffer b)) + (find-file to) + (jao-org-static-blog-update-date) + (when (y-or-n-p "Generate HTML? ") + (jao-org-blog-publish)))) + +(defun jao-org-static-blog-edit-draft () + (interactive) + (find-file (read-file-name "Edit: " + org-static-blog-drafts-directory + nil + t))) + +;;; Publish +(defun jao-org-blog-publish (&optional force) + (interactive "P") + (let ((geiser-active-implementations '(guile)) + (geiser-default-implementation 'guile)) + (org-static-blog-publish force))) + +(defun jao-org-blog-republish () + (interactive) + (jao-org-blog-publish t)) + +;;; Transient +(defun jao-org-static-prev-begin () + (interactive) + (insert jao-org-static-blog--prev-beg)) + +(defun jao-org-static-prev-end () + (interactive) + (insert jao-org-static-blog--prev-end)) + +(jao-transient-major-mode+ org + ["Insert blog snippet" + ("s" "preview begin" jao-org-static-prev-begin) + ("S" "preview end" jao-org-static-prev-end) + ("T" "update date" jao-org-static-blog-update-date)] + ["Edit blog" + ("n" "create post" jao-org-static-blog-create-new-post) + ("d" "create draft" jao-org-static-blog-create-new-draft) + ("e" "edit draft" jao-org-static-blog-edit-draft)] + ["Publish blog" + ("D" "publish draft" jao-org-static-blog-publish-draft) + ("f" "publish single file" jao-org-blog-publish-file) + ("p" "publish all" jao-org-blog-publish) + ("r" "republish" jao-org-blog-republish)]) + +;;; . +(provide 'jao-custom-blog) diff --git a/custom/jao-custom-completion.el b/custom/jao-custom-completion.el new file mode 100644 index 0000000..a0efbb1 --- /dev/null +++ b/custom/jao-custom-completion.el @@ -0,0 +1,530 @@ +;; -*- lexical-binding: t -*- + +;;; imenu +(use-package imenu + :init (setq org-imenu-depth 7) + :config + (defun jao-imenu-hook () + (cond ((derived-mode-p 'org-mode) (org-reveal t)) + (outline-minor-mode (outline-show-entry)))) + (add-hook 'imenu-after-jump-hook #'jao-imenu-hook)) + +;;; completion styles +;;;; completion configuration +(setq tab-always-indent 'complete + read-extended-command-predicate #'command-completion-default-include-p + completion-category-defaults nil + completion-cycle-threshold nil + completions-detailed t + completion-show-help nil + completion-show-inline-help nil + completion-ignore-case t + completion-wrap-movement t + completion-auto-select nil + completions-format 'one-column + completion-styles '(basic substring partial-completion emacs22) + completion-category-overrides + '((file (styles partial-completion)) + (command (styles initials substring partial-completion)) + (symbol (styles initials substring partial-completion)) + (variable (styles initials substring partial-completion)))) + +;;;; crm indicator +(defun jao-completion--crm-indicator (args) + "Add prompt indicator to `completing-read-multiple' filter ARGS." + (cons (concat "[CRM] " (car args)) (cdr args))) +(advice-add #'completing-read-multiple + :filter-args #'jao-completion--crm-indicator) + +;;; orderless +(use-package orderless + :ensure t + :init + :config + (orderless-define-completion-style orderless+initialism + (orderless-matching-styles '(orderless-initialism + orderless-prefixes + orderless-literal + orderless-regexp))) + + (defun jao-orderless--set-locally () + (setq-local completion-styles + '(substring partial-completion orderless) + + completion-category-overrides + '((file (styles partial-completion orderless)) + (command (styles orderless+initialism))) + orderless-matching-styles + '(orderless-literal orderless-regexp orderless-prefixes))) + (add-hook 'minibuffer-setup-hook #'jao-orderless--set-locally)) + +;;; marginalia +(use-package marginalia + :ensure t + :bind (:map minibuffer-local-map ("C-M-a" . marginalia-cycle)) + + :custom ((marginalia-align 'left) + (marginalia-align-offset 1) + (marginalia-field-width 200) + (marginalia-annotators + '(marginalia-annotators-heavy marginalia-annotators-light nil)) + (marginalia-separator " "))) + +(marginalia-mode 1) + +;;; company +(use-package company + :ensure t + :custom ((company-backends '(company-capf + ;; company-bbdb + company-files + company-dabbrev + company-keywords)) + (company-global-modes '(not slack-message-buffer-mode + circe-channel-mode + telega-chat-mode)) + (company-format-margin-function nil) ;; #'company-text-icons-margin + (company-idle-delay 0.2) + (company-lighter "") + (company-lighter-base "") + (company-show-numbers nil) + (company-selection-wrap-around t) + (company-tooltip-limit 15) + (company-tooltip-align-annotations t) + (company-tooltip-offset-display 'lines)) ;; 'scrollbar + + :config + (defun jao-complete-at-point () + "Complete using company unless we're in the minibuffer." + (interactive) + (if (or (not company-mode) (window-minibuffer-p)) + (completion-at-point) + (company-manual-begin))) + + (defun jao-company-use-in-tab () + (global-set-key [remap completion-at-point] #'jao-complete-at-point) + (global-set-key [remap completion-symbol] #'jao-complete-at-point) + (global-set-key (kbd "M-TAB") #'jao-complete-at-point)) + + (jao-company-use-in-tab) + + :bind (:map company-active-map + + ("" . company-complete-common-or-cycle) + ("TAB" . company-complete-common-or-cycle) + + ("C-h" . company-show-doc-buffer) + ("M-." . company-show-location) + ("C-" . company-complete-selection) + ([remap return] . company-abort) + ("RET" . company-abort) + + :filter (or (not (derived-mode-p 'eshell-mode)) + (company-explicit-action-p)) + ("" . company-complete-selection) + ("RET" . company-complete-selection)) + :diminish) + +(unless (display-graphic-p) (global-company-mode 1)) + +;;; vertico +(use-package vertico + :ensure t + :init + (setq vertico-count 20 + vertico-cycle t + vertico-resize t + org-refile-use-outline-path t) + + :config + + ;; (setq completion-in-region-function + ;; (lambda (&rest args) + ;; (apply (if (and (not window-system) vertico-mode) + ;; #'consult-completion-in-region + ;; #'completion--in-region) + ;; args))) + + (defun jao-vertico--display-candidates (lines) + (move-overlay vertico--candidates-ov (point-min) (point-min)) + (overlay-put vertico--candidates-ov 'after-string (apply #'concat lines)) + (vertico--resize-window (length lines))) + + (advice-add 'vertico--display-candidates + :override #'jao-vertico--display-candidates)) + +(use-package vertico-directory + :after vertico + :bind (:map vertico-map (("RET" . vertico-directory-enter) + ("M-" . vertico-directory-delete-word) + ("" . vertico-directory-delete-char)))) + +(vertico-mode) + +;;; consult +;;;; package +(use-package consult + :ensure t + :bind (("C-x M-:" . consult-complex-command) + ("C-x b" . consult-buffer) + ("C-x C-b" . consult-buffer) + ("C-x 4 b" . consult-buffer-other-window) + ("C-c b" . project-find-file) + ("C-c h" . consult-history) + ("C-c i" . consult-imenu) + ("C-c I" . consult-project-imenu) + ("C-c k" . consult-ripgrep) + ("C-c K" . consult-git-grep) + ("C-c L" . consult-locate) + ;; ("C-h m" . consult-mode-command) + ("C-c s" . consult-line) + ("C-x r x" . consult-register) + ("C-x r b" . consult-bookmark) + ("C-x C-f" . jao-find-file) + ("M-g b" . consult-bookmark) + ("M-g m" . consult-mark) + ("M-g e" . consult-error) + ("M-s m" . consult-multi-occur) + ("M-s o" . consult-outline) + ("M-y" . consult-yank-pop) + ("C-s" . isearch-forward) + ("C-S-s" . consult-line) + (" a" . consult-apropos)) + + :custom ((consult-preview-key (kbd "`"))) + + :init + (fset 'multi-occur #'consult-multi-occur) + + :config + + (defun jao-find-file (arg) + (interactive "P") + (call-interactively (if arg 'consult-file-externally 'find-file))) + + (define-key consult-narrow-map (vconcat consult-narrow-key "?") + #'consult-narrow-help) + + (consult-customize consult-mark :preview-key 'any) + (add-hook 'completion-list-mode-hook #'consult-preview-at-point-mode)) + +;;;; consult-dir +(use-package consult-dir + :ensure t + :bind (("C-x C-d" . consult-dir) + :map minibuffer-local-completion-map + (("C-x C-d" . consult-dir) + ("C-x C-j" . consult-dir-jump-file)))) + +;;;; narrow helpers +(defvar jao-consult-narrow nil) + +(defun jao-consult-initial-narrow () + (when-let (c (cond ((eq this-command #'consult-buffer) + (cdr (assoc (jao-afio-current-frame) + jao-consult-narrow))) + ((eq this-command #'consult-mode-command) ?m))) + (setq unread-command-events (append unread-command-events `(,c 32))))) + +(add-hook 'minibuffer-setup-hook #'jao-consult-initial-narrow) + +(defmacro jao-consult--mode-buffers (&rest modes) + `(lambda () + (seq-map #'buffer-name + (seq-filter (lambda (b) + (with-current-buffer b + (derived-mode-p ,@modes))) + (buffer-list))))) + +(defun jao-consult-add-buffer-source (src &optional aframe key) + (add-to-list 'consult-buffer-sources src t) + (when (and aframe key) + (add-to-list 'jao-consult-narrow (cons aframe key)))) + +;;;; narrowing chats +(defvar jao-chat-buffer-source + (list :name "chats" + :category 'buffer + :action #'pop-to-buffer + :hidden t + :narrow (cons ?c "chats") + :items (jao-consult--mode-buffers 'erc-mode + 'circe-channel-mode + 'circe-query-mode + 'signel-chat-mode + 'slack-message-buffer-mode + 'slack-thread-message-buffer-mode + 'telega-root-mode + 'telega-chat-mode))) +(with-eval-after-load "consult" + (jao-consult-add-buffer-source 'jao-chat-buffer-source)) + +;;; embark +;;;; package +(use-package embark + :ensure t + :demand t + :init + (setq embark-quit-after-action nil + embark-indicator #'embark-mixed-indicator + embark-verbose-indicator-buffer-sections '(bindings) + embark-mixed-indicator-both t + embark-verbose-indicator-excluded-commands + '(embark-become embark-export embark-collect) + embark-verbose-indicator-nested t + embark-verbose-indicator-display-action + '((display-buffer-at-bottom) + (window-parameters (mode-line-format . none)) + (window-height . fit-window-to-buffer))) + + :bind (("C-;" . embark-act) + ("C-c ;" . embark-act) + ("C-'" . embark-dwim) + ("C-c '" . embark-dwim) + (:map minibuffer-local-map + (("C-'" . embark-dwim) + ("C-c '" . embark-dwim) + ("C-," . embark-become) + ("C-c ," . embark-become) + ("C-o" . embark-export))))) + +(use-package embark-consult + :ensure t + :after (embark consult)) + +(with-eval-after-load 'consult + (with-eval-after-load 'embark + (require 'embark-consult))) + +;;;; randomsig +(defun jao-random-sig-read (_ignored) + "Import region as signature and edit it." + (randomsig-message-read-sig t)) + +(define-key embark-region-map "m" #'jao-random-sig-read) +(define-key embark-region-map "M" #'apply-macro-to-region-lines) + +;;;; dict/say +(defun jao-say (&optional word) + "Isn't it nice to have a computer that can talk to you?" + (interactive "sWhat? ") + (shell-command-to-string (format "say %s" word)) + "") + +(define-key embark-identifier-map "D" #'dictionary-search) +(define-key embark-identifier-map "S" #'jao-say) + +;;;; org targets +(declare-function org-link-any-re "ol") +(declare-function org-open-link-from-string "ol") +(declare-function org-in-regexp "org-macs") + +(defun jao-embark-targets--org-link () + (when (derived-mode-p 'org-mode) + (when (org-in-regexp org-link-bracket-re) + (let ((lnk (match-string-no-properties 1))) + (if (string-match-p "https?://.+" (or lnk "")) + (cons 'url lnk) + (cons 'org-link (match-string-no-properties 0))))))) + +(embark-define-keymap jao-embark-targets-org-link-map + "Actions for org links" + ((kbd "RET") org-open-link-from-string)) + +(defun jao-embark-targets--gl-org-link () + (when (org-in-regexp org-link-bracket-re) + (cons 'gl-org-link (match-string-no-properties 0)))) + +(embark-define-keymap jao-embark-targets-gl-org-link-map + "Actions for exteranl org links" + ((kbd "RET") org-open-at-point-global)) + +(add-to-list 'embark-target-finders #'jao-embark-targets--gl-org-link) +(add-to-list 'embark-keymap-alist + '(gl-org-link . jao-embark-targets-gl-org-link-map)) + +(add-to-list 'embark-target-finders #'jao-embark-targets--org-link) +(add-to-list 'embark-keymap-alist + '(org-link . jao-embark-targets-org-link-map)) + +;;;; url targets +(declare-function w3m-anchor "w3m") + +(defun jao-embark-targets--w3m-anchor () + (when (not (region-active-p)) + (when-let ((url (or (jao-url-around-point) + (thing-at-point 'url) + (and (derived-mode-p 'w3m-mode) + (or (w3m-anchor) w3m-current-url)) + (and (derived-mode-p 'eww-mode) + (eww-current-url))))) + (when (string-match-p "^https?.*" url) + (cons 'url url))))) + +(add-to-list 'embark-target-finders #'jao-embark-targets--w3m-anchor) + +(defun jao-embark-url (url) + "Browse URL, externally if we're already in an emacs browser." + (if (derived-mode-p 'w3m-mode 'eww-mode) + (jao-browse-with-external-browser url) + (browse-url url))) + +(define-key embark-url-map (kbd "RET") #'jao-embark-url) +(define-key embark-url-map (kbd "f") #'browse-url-firefox) +(define-key embark-url-map (kbd "x") #'jao-rss-subscribe) +(define-key embark-url-map (kbd "m") 'jao-browse-with-external-browser) +(define-key embark-url-map (kbd "p") 'jao-browse-add-url-to-mpc) + +;;;; video url targets +(defvar jao-embark-targets-video-sites + '("youtu.be" "youtube.com" "blip.tv" "vimeo.com" "infoq.com")) + +(defun jao-embark--video-url-rx (&optional sites) + (format "^https?://\\(?:www\\.\\)?%s/.+" + (regexp-opt (or sites jao-embark-targets-video-sites) t))) + +(defvar jao-embark-targets-video-url-rx (jao-embark--video-url-rx) + "A regular expression matching URLs that point to video streams") + +(defun jao-embark-targets--refine-url (_ url) + (if (string-match-p jao-embark-targets-video-url-rx url) + (cons 'video-url url) + (cons 'url url))) + +(defun jao-embark-targets--play-video (player url) + (interactive "sURL: ") + (let ((cmd (format "%s %s" player (shell-quote-argument url)))) + (jao-afio--goto-www) + (start-process-shell-command player nil cmd))) + +(defun jao-embark-targets-mpv (&optional url) + "Play video stream with mpv" + (interactive "sURL: ") + (jao-embark-targets--play-video "mpv" url)) + +(defun jao-embark-targets-vlc (&optional url) + "Play video stream with vlc" + (interactive "sURL: ") + (jao-embark-targets--play-video "vlc" url)) + +(embark-define-keymap jao-embark-targets-video-url-map + "Actions on URLs pointing to remote video streams." + :parent embark-url-map + ("v" jao-embark-targets-vlc) + ("RET" jao-embark-targets-mpv)) + +(add-to-list 'embark-transformer-alist '(url . jao-embark-targets--refine-url)) +(add-to-list 'embark-keymap-alist '(video-url . jao-embark-targets-video-url-map)) +(define-key embark-url-map "v" #'jao-embark-targets-vlc) +(define-key embark-url-map "V" #'jao-embark-targets-vlc) + +;;; avy +(use-package avy + :ensure t + :init (setq avy-style 'pre + avy-background t + avy-timeout-seconds 0.6 + avy-single-candidate-jump t) + + :config + + (defun avy-embark-act (pt) + "Use Embark to act on the completion at PT." + (save-excursion + (goto-char pt) + (embark-act))) + (add-to-list 'avy-dispatch-alist '(?\; . avy-embark-act)) + + :bind (("s-j" . avy-goto-char-timer) + ("C-M-j" . avy-goto-char-timer))) + +;;; link-hint +(use-package link-hint + :ensure t + :init (setq link-hint-avy-style 'pre) + + :config + (defun jao-link-hint-open-link-ext () + (interactive) + (let ((jao-browse-url-function jao-browse-url-external-function)) + (link-hint-open-link))) + + :bind (("C-l" . link-hint-open-link) + ("C-M-l" . jao-link-hint-open-link-ext) + ("C-S-l" . jao-link-hint-open-link-ext) + ("C-x C-l" . recenter-top-bottom))) + +;;; recoll +(jao-load-path "consult-recoll") + +(defun jao-recoll-format (title url mtype) + (let* ((u (replace-regexp-in-string "/home/jao/" "" url)) + (u (replace-regexp-in-string + "\\(doc\\|org/doc\\|.emacs.d/gnus/Mail\\|var/mail\\)/" "" u))) + (format "%s (%s, %s)" + title + (propertize u 'face 'jao-themes-f00) + (propertize mtype 'face 'jao-themes-f01)))) + +(defun jao-recoll-open-html (file) + (jao-afio--goto-www) + (eww-open-file file)) + +(defun jao-recoll-consult-messages () + (interactive) + (consult-recoll "mime:message ")) + +(defun jao-recoll-consult-docs () + (interactive) + (consult-recoll (format "dir:%s/doc " jao-org-dir))) + +(defun jao-recoll-messages () + (interactive) + (jao-recoll "mime:message ")) + +(defun jao-recoll-docs () + (interactive) + (jao-recoll (format "dir:%s/doc " jao-org-dir))) + +(defun jao-recoll-notes () + (interactive) + (jao-recoll (format "dir:%s " jao-org-notes-dir))) + +(defun jao-recoll-consult-notes () + "Use consult-recoll to search notes." + (interactive) + (consult-recoll (format "dir:%s " jao-org-notes-dir))) + +(defun jao-recoll-open-with-notmuch (fname) + (let ((id (with-temp-buffer + (insert-file fname) + (goto-char (point-min)) + (when (re-search-forward "[Mm]essage-[Ii][Dd]: <]+\\)>?" + nil t) + (match-string 1))))) + (when id (notmuch-show (concat "id:" id))))) + +(use-package consult-recoll + :init (setq consult-recoll-open-fns + '(("application/pdf" . jao-open-doc) + ("message/rfc822" . jao-recoll-open-with-notmuch) + ("text/html" . jao-recoll-open-html)) + consult-recoll-search-flags nil + consult-recoll-format-candidate #'jao-recoll-format) + :config + (transient-define-prefix jao-recoll-transient () + [["Consult recoll queries" + ("r" "consult recoll query" consult-recoll) + ("n" "consult recoll on notes" jao-recoll-consult-notes) + ("d" "consult recoll on docs" jao-recoll-consult-docs) + ("m" "consult recoll on messages" jao-recoll-consult-messages)] + ["Recoll queries" + ("R" "recoll query" jao-recoll) + ("N" "recoll on notes" jao-recoll-notes) + ("D" "consult recoll on docs" jao-recoll-docs) + ("M" "consult recoll on messages" jao-recoll-messages)]]) + + :bind (("s-r" . #'jao-recoll-transient))) + +;;; . +(provide 'jao-custom-completion) diff --git a/custom/jao-custom-email.el b/custom/jao-custom-email.el new file mode 100644 index 0000000..37c8acb --- /dev/null +++ b/custom/jao-custom-email.el @@ -0,0 +1,291 @@ +;; -*- lexical-binding: t; -*- + +;;; personal emails and others +(defvar jao-mails) +(defvar jao-extra-mails) +(defvar jao-mails-regexp (regexp-opt jao-mails)) +(defvar jao-notmuch-enabled (eq jao-afio-mail-function 'notmuch)) + +;;; gnus +(setq gnus-init-file "~/.emacs.d/gnus.el" + gnus-home-directory "~/.emacs.d/gnus" + gnus-directory gnus-home-directory + gnus-cache-directory (expand-file-name "cache" gnus-home-directory) + gnus-kill-files-directory (expand-file-name "News" gnus-home-directory) + message-directory (expand-file-name "Mail" gnus-home-directory) + mail-source-directory (expand-file-name "Mail" gnus-home-directory)) + +(let ((org-file (expand-file-name "gnus.org" jao-emacs-dir))) + (when (file-newer-than-file-p org-file gnus-init-file) + (org-babel-tangle-file org-file))) + +;;; message mode +;;;; customization +(setq message-send-mail-function 'message-send-mail-with-sendmail + message-sendmail-envelope-from 'header + message-sendmail-f-is-evil nil) +(setq imap-store-password t) +(setq password-cache-expiry nil) +(setq message-generate-headers-first t) +(setq message-forward-before-signature nil) +(setq message-alternative-emails + (regexp-opt (append jao-mails jao-extra-mails))) +(setq message-dont-reply-to-names + (regexp-opt (append jao-mails '("noreply@" "@noreply" + "no-reply@" "@no-reply" + "notifications@github")))) +(setq message-citation-line-format "On %a, %b %d %Y, %N wrote:\n") +(setq message-citation-line-function 'message-insert-formatted-citation-line) + +(setq message-user-fqdn "mail.jao.io") + +(setq message-kill-buffer-on-exit t) +(setq message-max-buffers 5) +(setq message-insert-signature t) +(setq message-from-style 'angles + user-mail-address (car jao-mails) + mail-host-address system-name + message-syntax-checks '((sender . disabled)) + message-default-headers + (concat + "X-Attribution: jao\n" + "X-Clacks-Overhead: GNU Terry Pratchett\n" + "X-URL: \n") + message-hidden-headers + '("^References:" "^Face:" "^X-Face:" "^X-Draft-From:") + message-make-forward-subject-function 'message-forward-subject-fwd) + +(setq message-expand-name-standard-ui t) + +;;;; adjust bcc +(defvar jao-message--bcc-rx (regexp-opt '("mail.io" "gnu.org"))) + +(defun jao-message-insert-bcc () + (let ((f (or (message-fetch-field "From") ""))) + (when (or (string-blank-p f) (string-match-p jao-message--bcc-rx f)) + (insert "Bcc: proton@jao.io\n")))) + +(add-hook 'message-header-setup-hook #'jao-message-insert-bcc) + +;;;; to->from +(defvar jao-message-to-from nil) + +(defun jao-message-adjust-from () + (let ((to (concat (message-fetch-field "To") (message-fetch-field "Cc")))) + (when-let* ((tf (seq-find (lambda (tf) (string-match-p (car tf) to)) + jao-message-to-from)) + (from (message-make-from "Jose A Ortega Ruiz" (cdr tf)))) + (save-restriction + (widen) + (message-replace-header "From" from))))) + +(when jao-notmuch-enabled + (add-hook 'message-header-setup-hook #'jao-message-adjust-from)) + +;;;; encryption +(setq gnutls-min-prime-bits nil) +(setq gnus-buttonized-mime-types + '("multipart/encrypted" "multipart/signed" "multipart/alternative")) + +(setq mm-verify-option 'always) +(setq mm-decrypt-option 'always) + +(setq mm-sign-option 'guided) +(setq mm-encrypt-option 'guided) + +(setq mml-secure-passphrase-cache-expiry (* 3600 24) + password-cache-expiry (* 3600 24)) + +(setq smime-CA-directory "/etc/ssl/certs/" + smime-certificate-directory + (expand-file-name "certs/" gnus-directory)) + +(with-eval-after-load "mm-decode" + ;; Tells Gnus to inline the part + (add-to-list 'mm-inlined-types "application/pgp$") + ;; Tells Gnus how to display the part when it is requested + (add-to-list 'mm-inline-media-tests '("application/pgp$" + mm-inline-text identity)) + ;; Tell Gnus not to wait for a request, just display the thing + ;; straight away. + (add-to-list 'mm-automatic-display "application/pgp$") + ;; But don't display the signatures, please. + (setq mm-automatic-display (remove "application/pgp-signature" + mm-automatic-display))) + +;; decide whether to encrypt or just sign outgoing messages +(defvar jao-message-try-sign nil) +(defun jao-message-maybe-sign () + (when (and jao-message-try-sign (y-or-n-p "Sign message? ")) + (if (y-or-n-p "Encrypt message? ") + (let ((recipient (message-fetch-field "To"))) + (if (or (pgg-lookup-key recipient) + (and (y-or-n-p (format "Fetch %s's key? " recipient)) + (pgg-fetch-key pgg-default-keyserver-address + recipient))) + (mml-secure-message-encrypt-pgp) + (mml-secure-message-sign-pgp))) + (mml-secure-message-sign-pgp)))) + +;; for ma gnus +(eval-after-load "rfc2047" + '(add-to-list 'rfc2047-header-encoding-alist + '("User-Agent" . address-mime))) + +;;;; check attachment +(defvar jao-message-attachment-regexp "\\([Ww]e send\\|[Ii] send\\|attach\\)") +(defun jao-message-check-attachment () + "Check if there is an attachment in the message if I claim it." + (save-excursion + (message-goto-body) + (when (search-forward-regexp jao-message-attachment-regexp nil t nil) + (message-goto-body) + (unless (or (search-forward "<#part" nil t nil) + (message-y-or-n-p + "No attachment. Send the message? " nil nil)) + (error "No message sent"))))) + +;;;; check fcc/gcc +(defun jao-message-check-gcc () + "Ask whether to keep a copy of message." + (save-excursion + (save-restriction + (message-narrow-to-headers) + (when (and (or (message-fetch-field "Gcc") + (message-fetch-field "Fcc")) + (not (y-or-n-p "Archive? "))) + (message-remove-header "\\(?:[BFG]cc\\)"))))) + +;;;; check recipient +(defun jao-message-check-recipient () + (save-excursion + (save-restriction + (message-narrow-to-headers) + (when-let ((to (message-fetch-field "To"))) + (when (string-match-p jao-mails-regexp to) + (unless (y-or-n-p "Message is addressed to yourself. Continue?") + (error "Message not sent"))))))) + +;;;; randomsig +(with-eval-after-load "message" + (when (require 'randomsig nil t) + (define-key message-mode-map (kbd "C-c s") 'randomsig-replace-sig) + (define-key message-mode-map (kbd "C-c S") 'randomsig-select-sig) + (setq randomsig-dir (expand-file-name "~/etc/config/emacs")) + (setq randomsig-files '("signatures.txt")) + ;; or (setq randomsig-files (randomsig-search-sigfiles)) + ;; or (setq randomsig-files 'randomsig-search-sigfiles) + (setq message-signature 'randomsig-signature) + (setq randomsig-delimiter-pattern "^%$" + randomsig-delimiter "%"))) + +;;;; send mail hooks +(dolist (h '(jao-message-check-gcc + jao-message-check-recipient + jao-message-maybe-sign)) + (add-hook 'message-send-hook h)) + +(unless jao-notmuch-enabled + (add-hook 'message-send-hook #'jao-message-check-attachment)) + +;;;; keybindings +(with-eval-after-load "message" + ;; (define-key message-mode-map [f7] 'mml-secure-message-sign-pgp) + (define-key message-mode-map [f8] 'mml-secure-message-encrypt-pgp) + (define-key message-mode-map (kbd "C-c y") #'yank-media)) + +;;; sendmail/smtp +(defun jao-sendmail-gmail () + (setq smtpmail-auth-supported '(login cram-md5 plain)) + (setq smtpmail-smtp-server "smtp.gmail.com") + (setq smtpmail-smtp-service 587)) + +(defun jao-sendmail-local () + (setq send-mail-function 'sendmail-send-it) + (setq smtpmail-auth-supported nil) ;; (cram-md5 plain login) + (setq smtpmail-smtp-server "127.0.0.1") + (setq smtpmail-smtp-service 25)) + +(defun jao-sendmail-msmtp () + (setq send-mail-function 'sendmail-send-it + sendmail-program "/usr/bin/msmtp" + mail-specify-envelope-from t + message-sendmail-envelope-from 'header + mail-envelope-from 'header)) + +(jao-sendmail-local) + +;;; mailcap +(use-package mailcap + :config + (add-to-list 'mailcap-mime-extensions '(".JPEG" . "image/jpeg")) + (add-to-list 'mailcap-mime-extensions '(".JPG" . "image/jpeg")) + + (defun jao-icalendar-import-buffer () + (let ((icalendar-import-format "%s%u%l%d")) + (icalendar-import-buffer diary-file t nil)) + (kill-buffer) + (message "Event imported into diary")) + + :custom + ((mailcap-user-mime-data + '((jao-icalendar-import-buffer "application/ics") + ("viewpdf.sh %s" "application/pdf"))))) + +;;; multipart html renderer +(defun jao-w3m-html-renderer (handle) + (let ((w3m-message-silent t) + (mm-w3m-safe-url-regexp nil)) + (condition-case nil + (mm-inline-text-html-render-with-w3m handle) + (error (delete-region (point) (point-max)) + (let ((shr-use-fonts nil) + (shr-use-colors nil)) + (mm-shr handle)))))) + +(defun jao-shr-html-renderer (handle) + (let (;; (shr-use-fonts t) + ;; (shr-use-colors t) + (shr-width 130)) + (mm-shr handle))) + +;; (setq mm-text-html-renderer #'jao-w3m-html-renderer) +(setq mm-text-html-renderer #'jao-shr-html-renderer) + +;;; narrowing +(defvar jao-mail-consult-buffer-history nil) + +(defun jao-mail-buffer-p (b) + (or (member (buffer-name b) + '("*Calendar*" "inbox.org" "*Org Agenda*" + "*Fancy Diary Entries*" "diary")) + (with-current-buffer b + (derived-mode-p 'notmuch-show-mode + 'notmuch-search-mode + 'notmuch-tree-mode + 'notmuch-hello-mode + 'notmuch-message-mode + 'gnus-group-mode + 'gnus-summary-mode + 'gnus-article-mode)))) + +(defvar jao-mail-consult-source + (list :name "mail buffer" + :category 'buffer + :hidden t + :narrow (cons ?n "mail buffer") + :history 'jao-mail-consult-buffer-history + :action (lambda (b) + (when (not (string-blank-p (or b ""))) + (jao-afio--goto-mail) + (if (get-buffer-window b) + (pop-to-buffer b) + (pop-to-buffer-same-window b)))) + :items (lambda () + (mapcar #'buffer-name + (seq-filter #'jao-mail-buffer-p (buffer-list)))))) + +(jao-consult-add-buffer-source 'jao-mail-consult-source "Mail" ?n) + +;;; . +(provide 'jao-custom-email) diff --git a/custom/jao-custom-eww.el b/custom/jao-custom-eww.el new file mode 100644 index 0000000..531cc2b --- /dev/null +++ b/custom/jao-custom-eww.el @@ -0,0 +1,182 @@ +;; -*- lexical-binding: t -*- + +;;; Integration with browse-url and afio +(defun jao-eww-browse-url (url &rest r) + "Browse URL using eww." + (if (derived-mode-p 'eww-mode) + (eww url) + (jao-afio--goto-www) + (select-window (frame-first-window)) + (let* ((url (url-encode-url url)) + (bf (seq-find `(lambda (b) + (with-current-buffer b + (string= ,url + (url-encode-url (eww-current-url))))) + (jao-eww-session--list-buffers)))) + (cond (bf (switch-to-buffer bf)) + ((string-match-p url "^file://") (eww-open-file url)) + (t (eww url 4)))))) + +(setq jao-browse-url-function #'jao-eww-browse-url) +(setq eww-use-browse-url "^\\(gemini\\|gopher\\):") + +;;; Opening URLs +(defun jao-eww-copy-link () + (interactive) + (when-let (lnk (or (car (eww-links-at-point)) (eww-current-url))) + (message "%s" lnk) + (kill-new lnk))) + +(defun jao-eww-browse (arg) + (interactive "P" eww-mode) + (setq eww-prompt-history + (cl-remove-duplicates eww-prompt-history :test #'string=)) + (let ((url (completing-read (if arg "eww in new buffer: " "eww: ") + eww-prompt-history nil nil nil + 'eww-prompt-history (eww-current-url)))) + (eww url (when arg 4)))) + +(defun jao-eww-browse-new () + (interactive nil eww-mode) + (jao-eww-browse t)) + +(defun jao-eww-reload (images) + (interactive "P" eww-mode) + (if images + (let ((shr-blocked-images nil)) + (eww-reload t)) + (call-interactively 'eww-reload))) + +;;; Consult narrowing +(with-eval-after-load "consult" + (defvar jao-eww-consult-history nil) + (defvar jao-eww-buffer-source + (list :name "eww buffer" + :category 'eww-buffer + :hidden t + :narrow (cons ?e "eww") + :annotate (lambda (c) (get-text-property 0 'url c)) + :history 'jao-eww-consult-history + :action (lambda (b) + (jao-afio--goto-www) + (switch-to-buffer (get-text-property 0 'buffer b))) + :items + (lambda () + (seq-map (lambda (b) + (with-current-buffer b + (let ((tl (or (plist-get eww-data :title) "")) + (url (or (eww-current-url) (buffer-name)))) + (propertize (if (string-blank-p tl) url tl) + 'buffer b 'url url)))) + (seq-filter #'jao-www--buffer-p (buffer-list)))))) + (jao-consult-add-buffer-source 'jao-eww-buffer-source "Web" ?e)) + +;;; Images +(defun jao-eww-next-image () + (interactive nil eww-mode) + (when-let (p (text-property-search-forward 'image-displayer nil nil t)) + (goto-char (prop-match-beginning p)))) + +;;; Close page and reopen +(defvar jao-eww--closed-urls ()) + +(defun jao-eww-close () + (interactive nil eww-mode) + (when-let (current (eww-current-url)) + (add-to-list 'jao-eww--closed-urls current)) + (let ((nxt (car (jao-eww-session-invisible-buffers)))) + (kill-current-buffer) + (when nxt (switch-to-buffer nxt nil t)))) + +(defun jao-eww-reopen (arg) + (interactive "P") + (if (> (length jao-eww--closed-urls) 0) + (let ((url (completing-read "URL: " jao-eww--closed-urls))) + (jao-afio--goto-www) + (setq jao-eww--closed-urls (remove url jao-eww--closed-urls)) + (eww url (when arg 4))) + (message "No previously closed URLs."))) + +(defun jao-eww-reopen-new () + (interactive) + (jao-eww-reopen t)) + +;;; Sessions +(use-package jao-eww-session + :custom ((jao-eww-session-file "~/.emacs.d/cache/eww-session.eld"))) + +;;; Package +(use-package shr + :custom ((shr-width nil) + (shr-use-colors t) + (shr-use-fonts t) + (shr-max-width 130) + (shr-blocked-images nil) + (shr-inhibit-images t) + (shr-max-image-proportion 1.0) + (shr-hr-line ?―))) + +(use-package eww + :demand t + :custom ((eww-browse-url-new-window-is-tab nil) + (eww-download-directory jao-sink-dir) + (eww-header-line-format " %u") + (eww-form-checkbox-selected-symbol "☒") + (eww-buffer-name-length 180)) + + :config + (with-eval-after-load "org" (require 'ol-eww nil t)) + + (defun jao-eww-buffer-name () + (when-let ((s (or (plist-get eww-data :title) + (plist-get eww-data :url)))) + (when (not (string-blank-p s)) (format "%s" s)))) + (setq eww-auto-rename-buffer #'jao-eww-buffer-name) + + :bind (:map eww-mode-map (("b" . eww-back-url) + ("B" . eww-forward-url) + ("d" . jao-download) + ("f" . link-hint-open-link) + ("F" . embark-on-link) + ("L" . eww-forward-url) + ("N" . jao-eww-next-image) + ("o" . jao-eww-browse) + ("O" . jao-eww-browse-new) + ("r" . jao-eww-reload) + ("s" . eww-search-words) + ("S" . jao-eww-browse-new) + ("u" . jao-eww-reopen) + ("U" . jao-eww-reopen-new) + ("w" . org-eww-copy-for-org-mode) + ("q" . jao-eww-close) + ("x" . jao-rss-subscribe) + ("y" . jao-eww-copy-link) + ("\\" . eww-view-source) + ("C-c C-w" . jao-eww-close) + ("M-i" . eww-toggle-images)))) + +;;; Fixes for shr image rendering +(require 'shr) + +(defun jao-shr--kill-nl (p) + (save-excursion + (goto-char p) + (when (looking-at-p "\n") (delete-char 1)))) + +(defun jao-shr-tag-img (fn &rest args) + (let ((p (point))) + (prog1 (apply fn args) + (when (> (point) p) (jao-shr--kill-nl p))))) + +(defun jao-shr-insert (fn &rest args) + (let ((p (when (and (not (bolp)) + (get-text-property (1- (point)) 'image-url)) + (point)))) + (prog1 (apply fn args) + (when (and p (> (point) p)) (jao-shr--kill-nl p))))) + +(advice-add 'shr-tag-img :around #'jao-shr-tag-img) +(advice-add 'shr-insert :around #'jao-shr-insert) + +;;; . +(provide 'jao-custom-eww) diff --git a/custom/jao-custom-exwm.el b/custom/jao-custom-exwm.el new file mode 100644 index 0000000..2b94068 --- /dev/null +++ b/custom/jao-custom-exwm.el @@ -0,0 +1,528 @@ +;; -*- lexical-binding: t; -*- + +;;; Load and basic config +(defvar jao-exwm--use-afio t) + +(jao-load-path "exwm") + +(use-package exwm + :ensure t + :init (setq exwm-debug nil + exwm-workspace-number 1 + exwm-workspace-show-all-buffers t + exwm-workspace-warp-cursor nil + exwm-layout-show-all-buffers t + exwm-floating-border-color + (if (jao-colors-scheme-dark-p) "black" "grey90"))) + +(use-package exwm-edit :ensure t) +(require 'exwm) + +;;; Frame(s) as workspaces +(defun jao-exwm--new-frame-p () + (not (frame-parameter nil 'jao-frames-initialized))) + +(defun jao-exwm--mark-frame (force) + (prog1 (or force (jao-exwm--new-frame-p)) + (set-frame-parameter nil 'jao-frames-initialized t))) + +(defun jao-exwm--goto-main (&optional init) + (interactive "P") + (exwm-workspace-switch-create 1) + (when (jao-exwm--mark-frame init) (jao-trisect))) + +(defun jao-exwm--goto-mail (&optional init) + (interactive "P") + (exwm-workspace-switch-create 2) + (when (jao-exwm--mark-frame init) + (jao-afio-open-gnus))) + +(defun jao-exwm--goto-www (&optional init) + (interactive "P") + (exwm-workspace-switch-create 5) + (when (jao-exwm--mark-frame init) + (jao-afio-open-www) + (let ((scroll-bar-mode 'left)) + (toggle-scroll-bar 1) + (set-frame-parameter (window-frame) 'scroll-bar-width 12)) + (jao-toggle-inactive-mode-line))) + +(defun jao-exwm--goto-docs (&optional init) + (interactive "P") + (exwm-workspace-switch-create 4) + (when (jao-exwm--mark-frame init) + (jao-afio-open-doc))) + +(defun jao-exwm-open-doc (file) + (jao-exwm--goto-docs) + (jao-find-or-open file)) + +(defun jao-exwm-no-afio-setup () + (interactive) + (defalias 'jao-open-gnus-frame 'jao-exwm--goto-mail) + (defalias 'jao-goto-www-frame 'jao-exwm--goto-www) + (setq jao-open-doc-fun #'jao-exwm-open-doc) + (setq minibuffer-follows-selected-frame t) + (global-set-key "\C-cf" 'jao-exwm--goto-main) + (global-set-key "\C-cg" 'jao-exwm--goto-mail) + (global-set-key "\C-cw" 'jao-exwm--goto-www) + (global-set-key "\C-cz" 'jao-exwm--goto-docs)) + +(if jao-exwm--use-afio + (setq minibuffer-follows-selected-frame nil) + (jao-exwm-no-afio-setup)) + +;; tracking +(add-hook 'exwm-workspace-switch-hook 'tracking-remove-visible-buffers) + +;;; Buffer names +(defun jao-exwm--use-title-p () + (and exwm-title (not (string-blank-p exwm-title)))) + +(defun jao-exwm-rename-buffer/class () + (unless (jao-exwm--use-title-p) + (exwm-workspace-rename-buffer exwm-class-name))) + +(defun jao-exwm-rename-buffer/title () + (cond ((or (not exwm-instance-name) + (jao-exwm--use-title-p)) + (exwm-workspace-rename-buffer exwm-title)) + ((string= "Zathura" exwm-class-name) + (exwm-workspace-rename-buffer + (format "zathura: %s" (file-name-nondirectory exwm-title)))))) + +(defun jao-exwm--set-exwm-name () + (when (not jao-exwm--name) + (setq jao-exwm--name jao-exwm--current-name + jao-exwm--current-name nil))) + +(add-hook 'exwm-mode-hook 'jao-exwm--set-exwm-name) +(add-hook 'exwm-update-class-hook 'jao-exwm-rename-buffer/class) +(add-hook 'exwm-update-title-hook 'jao-minibuffer-refresh) +(add-hook 'exwm-update-title-hook 'jao-exwm-rename-buffer/title) + +;;; Float windows +(defvar jao-exwm-max-x (x-display-pixel-width)) +(defvar jao-exwm-max-y (x-display-pixel-height)) + +(defun jao-exwm--float-to (x y &optional w h) + (let* ((w (or w (frame-pixel-width))) + (h (or h (frame-pixel-height))) + (x (if (< x 0) (- jao-exwm-max-x (- x) w) x)) + (y (if (< y 0) (- jao-exwm-max-y (- y) h) y)) + (p (or (frame-parameter nil 'jao-position) (frame-position)))) + (exwm-floating-move (- x (car p)) (- y (cdr p))) + (exwm-layout-enlarge-window-horizontally (- w (frame-pixel-width))) + (exwm-layout-enlarge-window (- h (frame-pixel-height))) + (set-frame-parameter nil 'jao-position (cons x y)))) + +(defun jao-exwm--center-float (&optional w h) + (interactive) + (let* ((mx jao-exwm-max-x) + (my jao-exwm-max-y) + (w (or w (frame-pixel-width))) + (h (or h (/ (* w my) mx)))) + (jao-exwm--float-to (/ (- mx w) 2) (/ (- my h) 2) w h))) + +(defun jao-exwm--setup-float () + (set-frame-parameter nil 'jao-position nil) + (cond ((string= "Firefox" exwm-class-name) + (jao-exwm--center-float 900 600)) + ((member exwm-class-name '("mpv" "vlc")) + (jao-exwm--center-float 1200)))) + +(defvar jao-exwm-floating-classes '("mpv" "vlc")) +(setq jao-exwm-floating-classes nil) + +(defun jao-exwm--maybe-float () + (when (member exwm-class-name jao-exwm-floating-classes) + (when (not exwm--floating-frame) + (exwm-floating-toggle-floating)))) + +(add-hook 'exwm-floating-setup-hook #'jao-exwm--setup-float) +(add-hook 'exwm-manage-finish-hook #'jao-exwm--maybe-float) + +;; Minibuffer and system tray +(setq jao-minibuffer-frame-width 271) +(add-hook 'exwm-workspace-switch-hook #'jao-minibuffer-refresh) + +(require 'exwm-systemtray) +(exwm-systemtray-enable) + +(defun jao-exwm--watch-tray (sym newval op where) + (setq jao-minibuffer-right-margin (* 2 (length newval))) + (jao-minibuffer-refresh)) + +(add-variable-watcher 'exwm-systemtray--list #'jao-exwm--watch-tray) + +;;Switch to buffer / app +(defvar-local jao-exwm--name nil) +(defvar jao-exwm--current-name nil) + +(defun jao-exwm--check-name (name) + (or (string= jao-exwm--name name) + (string= (buffer-name) name) + (string= exwm-class-name name) + (string= exwm-title name))) + +(defun jao-exwm-find-class-buffer (cln) + (if (jao-exwm--check-name cln) + (current-buffer) + (let* ((cur-buff (current-buffer)) + (bfs (seq-filter (lambda (b) + (and (not (eq b cur-buff)) + (with-current-buffer b + (jao-exwm--check-name cln)))) + (buffer-list)))) + (when (car bfs) (car (reverse bfs)))))) + +(defun jao-exwm-switch-to-class/title (cln) + (interactive) + (when cln + (if (jao-exwm--check-name cln) + (current-buffer) + (when-let ((b (jao-exwm-find-class-buffer cln))) + (pop-to-buffer b))))) + +(defun jao-exwm-switch-to-next-class () + (interactive) + (jao-exwm-switch-to-class/title exwm-class-name)) + +(defun jao-exwm-switch-to-next-x () + (interactive) + (let ((bfs (seq-filter (lambda (b) (buffer-local-value 'exwm-class-name b)) + (buffer-list (window-frame))))) + (when (car bfs) (switch-to-buffer (car (reverse bfs)))))) + +;;; App runner helpers +(defun jao-exwm-run (command) + (interactive + (list (read-shell-command "$ " + (if current-prefix-arg + (cons (concat " " (buffer-file-name)) 0) + "")))) + (setq jao-exwm--current-name nil) + (start-process-shell-command command nil command)) + +(defmacro jao-exwm-runner (&rest args) + `(lambda () (interactive) (start-process "" nil ,@args))) + +(defun jao-exwm-workspace (n) + (if jao-exwm--use-afio + (jao-afio-goto-nth n) + (exwm-workspace-switch-create n))) + +(defmacro jao-def-exwm-runner (name ws class &rest args) + `(defun ,name (&rest other-args) + (interactive) + ,@(when ws `((jao-exwm-workspace ,ws))) + (if (jao-exwm-switch-to-class/title ,class) + ,(cond ((equal ws 5) '(delete-other-windows)) + ((stringp (car args)) (cdr args)) + (t args)) + (setq jao-exwm--current-name ,class) + ,(if (stringp (car args)) + `(start-process-shell-command ,(car args) + "* exwm - console *" + (string-join (append (list ,@args) + other-args) + " ")) + args)))) + +(defmacro jao-def-exwm-toggler (name ws class &rest args) + (let ((toggler (intern (format "%s*" name))) + (arg (gensym))) + `(progn (jao-def-exwm-runner ,name ,ws ,class ,@args) + (defun ,toggler (,arg) + (interactive "P") + (if (and (not ,arg) (equal exwm-class-name ,class)) + (jao-afio--goto-main) + (,name)))))) + +(defun jao-exwm--send-str (str) + (dolist (k (string-to-list (kbd str))) + (exwm-input--fake-key k))) + +;;; Runners +(jao-def-exwm-runner jao-exwm-vlc 4 "VLC" "vlc") + +(jao-def-exwm-runner jao-exwm-slack 0 "Slack" "slack") +(jao-def-exwm-runner jao-exwm-signal 0 "Signal" "signal-desktop") + +(jao-def-exwm-runner jao-exwm-proton-bridge 0 "*proton-bridge*" "protonmail-bridge") + +;; (jao-def-exwm-runner jao-exwm-htop 0 "htop-xterm" +;; "xterm" "-title" "htop-xterm" "-e" "htop") +(jao-def-exwm-runner jao-exwm-htop 0 "htop" jao-term-htop) + +;; (jao-def-exwm-runner jao-exwm-aptitude 0 "aptitude-xterm" +;; "xterm" "-title" "aptitude-xterm" "-e" "aptitude") +(jao-def-exwm-runner jao-exwm-aptitude 0 "aptitude" jao-term-aptitude) + +(jao-def-exwm-runner jao-exwm-blueman 0 "Blueman-manager" "blueman-manager") +(jao-def-exwm-runner jao-exwm-ncmpcpp 0 "ncmpcpp" "xterm" "-e" "ncmpcpp" "-p" "6669") +(jao-def-exwm-runner jao-exwm-mpc 0 "*MPC-Status*" mpc) + +(jao-def-exwm-runner jao-exwm-proton-vpn 0 "*pvpn*" proton-vpn-status) +(jao-def-exwm-runner jao-exwm-enwc 0 "*ENWC*" enwc) +(jao-def-exwm-runner jao-exwm-bluetooth 0 "*Bluetooth*" bluetooth-list-devices) +(jao-def-exwm-runner jao-exwm-packages 0 "*Packages*" list-packages nil) +(jao-def-exwm-runner jao-exwm-proced 0 "*Proced*" proced) + +(jao-def-exwm-runner jao-exwm-open-with-zathura nil nil "zathura" (buffer-file-name)) +(jao-def-exwm-runner jao-exwm-open-with-mupdf nil nil "mupdf" (buffer-file-name)) +(jao-def-exwm-runner jao-exwm-xterm 0 nil "xterm") + +(jao-def-exwm-toggler jao-exwm-tidal 5 "tidal-hifi" "tidal-hifi") +(defalias 'jao-streaming-list #'jao-exwm-tidal) + +(defun jao-exwm-import-screen (&optional area) + (interactive "P") + (when (not (file-directory-p "/tmp/screenshot")) + (make-directory "/tmp/screenshot")) + (let ((c (format "import %s %s" + (if area "" "-window root") + "/tmp/screenshot/$(date +'%g%m%d-%H%M%S').png"))) + (start-process-shell-command "import" "* exwm - console *" c))) + +;;; Zathura support +(defun jao-zathura--buffer-p (b) + (string= "Zathura" (or (buffer-local-value 'exwm-class-name b) ""))) + +(defun jao-zathura--buffers () + (seq-filter #'jao-zathura--buffer-p (buffer-list))) + +(defun jao-zathura--file-info (b) + (with-current-buffer b + (jao-zathura-file-info (or exwm-title "")))) + +(defun jao-zathura-goto-page (page-no) + (jao-exwm--send-str (format "%sg" page-no))) + +(defun jao-zathura-open-doc (&optional file-name page-no height) + (interactive) + (let* ((file-name (expand-file-name (or file-name (buffer-file-name)))) + (buffer (seq-find `(lambda (b) + (string= ,file-name + (car (jao-zathura--file-info b)))) + (jao-zathura--buffers))) + (page-no (or page-no (jao-doc-view-current-page)))) + (if jao-exwm--use-afio (jao-afio--goto-docs) (jao-exwm--goto-docs)) + (if (not buffer) + (jao-exwm-run (if page-no + (format "zathura -P %s '%s'" page-no file-name) + (format "zathura '%s'" file-name))) + (pop-to-buffer buffer) + (when page-no (jao-zathura-goto-page page-no))) + (current-buffer))) + +(defun jao-exwm--zathura-setup () + (when (and (string= exwm-class-name "Zathura") + (not jao-doc-view--imenu-file)) + (let ((info (jao-zathura--file-info (current-buffer)))) + (jao-doc-view-session-mark (car info)) + (jao-doc-view-save-session) + (jao-doc-view--enable-imenu (car info) #'jao-zathura-goto-page)))) + +(add-hook 'exwm-update-title-hook #'jao-exwm--zathura-setup t) + +(defun jao-exwm-pdf-zathura-close-all () + (interactive) + (dolist (b (jao-zathura--buffers)) + (ignore-errors + (switch-to-buffer b) + (jao-exwm--send-str "q"))) + t) + +(defun jao-exwm-zathura-goto-org (&optional arg) + (interactive "P") + (when-let ((info (jao-zathura--file-info (current-buffer)))) + (when-let ((file (jao-org-pdf-to-org-file (car info)))) + (let ((newp (not (file-exists-p file)))) + (when (or arg newp) (org-store-link nil t)) + (find-file-other-window file) + (when newp + (jao-org-insert-doc-skeleton) + (org-insert-link)))))) + +(defun jao-exwm-zathura-goto-org* () + (interactive) + (jao-exwm-zathura-goto-org t)) + +(defun jao-exwm-org-store-zathura-link () + (when-let ((info (jao-zathura--file-info (current-buffer)))) + (let* ((file-name (car info)) + (page (cadr info)) + (desc (jao-doc-view-section-title page file-name))) + (jao-org-links-store-pdf-link file-name page desc)))) + +(defun jao-exwm-pdf-enable-zathura () + (interactive) + (add-hook 'kill-emacs-query-functions #'jao-exwm-pdf-zathura-close-all t) + (setq jao-org-open-pdf-fun #'jao-zathura-open-doc) + (setq jao-org-links-pdf-store-fun #'jao-exwm-org-store-zathura-link) + (setq jao-open-doc-fun #'jao-zathura-open-doc)) + +(defun jao-exwm-pdf-disable-zathura () + (interactive) + (define-key org-mode-map (kbd "C-c o") #'jao-org-org-goto-pdf) + (remove-hook 'kill-emacs-query-functions #'jao-exwm-pdf-zathura-close-all) + (setq jao-org-open-pdf-fun #'jao-find-or-open) + (setq jao-org-links-pdf-store-fun nil) + (setq jao-open-doc-fun #'jao-find-or-open)) + +(defun jao-exwm-zathura-goto-pdf () + (interactive) + (if jao-browse-doc-use-emacs-p + (jao-org-org-goto-pdf) + (when-let (pdf (jao-org-org-to-pdf-file)) + (jao-zathura-open-doc pdf)))) + +(with-eval-after-load "org" + (define-key org-mode-map (kbd "C-c o") #'jao-exwm-zathura-goto-pdf)) + +(when (not jao-browse-doc-use-emacs-p) + (jao-exwm-pdf-enable-zathura)) + +(defun jao-exwm-select-pdf () + (interactive) + (let ((b (read-buffer "Document: " nil t + (lambda (b) + (let ((b (cdr b))) + (or (jao-zathura--buffer-p b) + (member (buffer-local-value 'major-mode b) + '(pdf-view-mode doc-view-mode)))))))) + (jao-afio--goto-docs) + (pop-to-buffer b))) + + +(let ((viewers ["External viewers" + ("z" "open with zathura" jao-zathura-open-doc) + ("m" "open with mupdf" jao-exwm-open-with-mupdf)])) + (jao-transient-major-mode+ pdf-view viewers) + (jao-transient-major-mode+ doc-view viewers)) + +;;; Firefox support +(jao-def-exwm-toggler jao-exwm-firefox 5 "Firefox" "firefox") + +(defun jao-exwm-browse-with-firefox (&rest args) + (jao-exwm-firefox) + (apply #'browse-url-firefox args)) + +(setq browse-url-secondary-browser-function #'jao-exwm-browse-with-firefox) + +(defun jao-exwm-kill-firefox-url () + (interactive) + (when-let (b (jao-exwm-find-class-buffer "Firefox")) + (let ((cb (current-buffer))) + (switch-to-buffer b) + (jao-exwm--send-str "yy") + (prog1 (current-kill 1) + (switch-to-buffer cb))))) + +;;; Transients +(defun jao-exwm--floating-p () exwm--floating-frame) +(defun jao-exwm--m0-5 () (interactive nil exwm-mode) (exwm-floating-move 0 -5)) +(defun jao-exwm--m05 () (interactive nil exwm-mode) (exwm-floating-move 0 5)) +(defun jao-exwm--m-50 () (interactive nil exwm-mode) (exwm-floating-move -5 0)) +(defun jao-exwm--m50 () (interactive nil exwm-mode) (exwm-floating-move 5 0)) +(defun jao-exwm--e-5 () (interactive nil exwm-mode) (exwm-layout-enlarge-window -5)) +(defun jao-exwm--e5 () (interactive nil exwm-mode) (exwm-layout-enlarge-window 5)) +(defun jao-exwm--eh5 () (interactive nil exwm-mode) (exwm-layout-enlarge-window 5 t)) +(defun jao-exwm--eh-5 () (interactive nil exwm-mode) (exwm-layout-enlarge-window -5 t)) +(defun jao-exwm--tl () (interactive nil exwm-mode) (jao-exwm--float-to 20 20)) +(defun jao-exwm--tr () (interactive nil exwm-mode) (jao-exwm--float-to -20 20)) +(defun jao-exwm--bl () (interactive nil exwm-mode) (jao-exwm--float-to 20 -20)) +(defun jao-exwm--br () (interactive nil exwm-mode) (jao-exwm--float-to -20 -20)) + +(defun jao-exwm--def-center-float () + (interactive) + (exwm-floating-toggle-floating) + (jao-exwm--center-float 900 600)) + +(transient-define-prefix jao-transient-float () + "Operations on EXWM floating windows." + :transient-non-suffix 'transient--do-quit-one + [["Float" + ("f" "float" exwm-floating-toggle-floating) + ("F" "full" exwm-layout-toggle-fullscreen) + ("c" "center" jao-exwm--center-float :if jao-exwm--floating-p) + ("c" "float and resize" jao-exwm--def-center-float + :if-not jao-exwm--floating-p) + ("x" "hide" exwm-floating-hide :if jao-exwm--floating-p)] + ["Slide" :if jao-exwm--floating-p + ("k" "up" jao-exwm--m0-5 :transient t) + ("j" "down" jao-exwm--m05 :transient t) + ("h" "left" jao-exwm--m-50 :transient t) + ("l" "right" jao-exwm--m50 :transient t)] + ["Resize" :if jao-exwm--floating-p + ("K" "up" jao-exwm--e5 :transient t) + ("J" "down" jao-exwm--e-5 :transient t) + ("H" "left" jao-exwm--eh5 :transient t) + ("L" "right" jao-exwm--eh-5 :transient t)] + ["Nudge" :if jao-exwm--floating-p + ("t" "top-left" jao-exwm--tl) + ("T" "top-right" jao-exwm--tr) + ("b" "bottom-left" jao-exwm--bl) + ("B" "bottom-right " jao-exwm--br)]]) + +(defun jao-exwm--buffer () + (interactive) + (jao-buffer-same-mode 'exwm-mode nil 'exwm-workspace-switch-to-buffer)) + +;;; Keybindings +(define-key exwm-mode-map [?\C-q] #'exwm-input-send-next-key) +(define-key exwm-mode-map [?\s-f] #'jao-transient-float) +(define-key exwm-mode-map (kbd "C-c o") #'jao-exwm-zathura-goto-org) +(define-key exwm-mode-map (kbd "C-c O") #'jao-exwm-zathura-goto-org*) +(define-key exwm-mode-map (kbd "M-o") #'other-window) +(define-key exwm-mode-map (kbd "M-p") #'jao-prev-window) + +(setq + exwm-input-global-keys + '(([?\s-0] . jao-afio--goto-scratch) + ([?\s-1] . jao-afio--goto-main) + ([?\s-2] . jao-afio--goto-mail) + ([?\s-3] . jao-afio--goto-www) + ([?\s-4] . jao-afio--goto-docs) + ([?\s-A] . org-agenda-list) + ([?\s-a] . jao-first-window) + ([?\s-b] . jao-transient-org-blog) + ;; ([?\s-d] . jao-exwm-tidal*) + ([?\s-d] . jao-mpc-echo-current) + ([?\s-e] . jao-exwm-firefox*) + ([?\s-m] . jao-transient-media) + ;; ([?\s-O] . jao-transpose-windows) + ;; ([?\s-o] . jao-other-window) + ;; ([?\s-P] . jao-transpose-windows-prev) + ([?\s-O] . ace-swap-window) + ([?\s-o] . ace-window) + ([?\s-p] . jao-prev-window) + ([?\s-R] . app-launcher-run-app) + ([?\s-r] . jao-recoll-transient) + ([?\s-s] . jao-transient-streaming) + ([?\s-t] . vterm) + ([?\s-w] . jao-transient-utils) + ([?\s-z] . jao-transient-sleep) + ([XF86AudioMute] . jao-mixer-master-toggle) + ([XF86AudioPlay] . jao-player-toggle) + ([XF86AudioPause] . jao-player-toggle) + ([XF86AudioNext] . jao-player-next) + ([XF86AudioPrev] . jao-player-previous) + ([XF86AudioStop] . jao-player-stop) + ([XF86AudioRaiseVolume] . jao-mixer-master-up) + ([XF86AudioLowerVolume] . jao-mixer-master-down) + ([XF86MonBrightnessUp] . jao-bright-up) + ([XF86MonBrightnessDown] . jao-bright-down) + ([?\s-\`] . jao-exwm-switch-to-next-x) + ([s-tab] . jao-exwm-switch-to-next-class) + ([print] . jao-exwm-import-screen) + ([f5] . jao-weather) + ([f6] . jao-toggle-audio-applet) + ([f8] . jao-toggle-nm-applet) + ([f9] . jao-bright-show))) + +;; (customize-set-variable 'exwm-input-global-keys exwm-input-global-keys) + +;;; . +(provide 'jao-custom-exwm) diff --git a/custom/jao-custom-notmuch.el b/custom/jao-custom-notmuch.el new file mode 100644 index 0000000..28da05f --- /dev/null +++ b/custom/jao-custom-notmuch.el @@ -0,0 +1,652 @@ +;; -*- lexical-binding: t; -*- + +;;; minibuffer +(defvar jao-notmuch-minibuffer-string "") + +(defvar jao-notmuch-minibuffer-queries + '((:name "" :query "tag:new and not tag:draft" :face jao-themes-f00) + (:name "B" :query "tag:new and tag:bigml and tag:inbox" :face default) + (:name "b" :query "tag:new and tag:bigml and tag:bugs" + :face jao-themes-error) + (:name "S" :query "tag:new and tag:bigml and tag:support" :face default) + (:name "W" + :query "tag:new and tag:bigml and not tag:\"/support|bugs|inbox/\"" + :face default) + (:name "I" + :query "tag:new and tag:jao and tag:inbox" + :face jao-themes-warning) + (:name "J" + :query "tag:new and tag:jao and not tag:\"/local|hacking|draft|inbox|prog|words/\"" + :face default) + (:name "H" :query "tag:new and tag:hacking and not tag:\"/emacs/\"") + (:name "E" :query "tag:new and tag:\"/emacs/\"") + (:name "l" :query "tag:new and tag:local") + (:name "F" :query "tag:new and tag:feeds and not tag:\"/emacs/\""))) + +(defun jao-notmuch-notify () + (let ((cnts (notmuch-hello-query-counts jao-notmuch-minibuffer-queries))) + (setq jao-notmuch-minibuffer-string + (mapconcat (lambda (c) + (propertize (format "%s%s" + (plist-get c :name) + (plist-get c :count)) + 'face (or (plist-get c :face) + 'jao-themes-dimm))) + cnts + " ")) + (jao-minibuffer-refresh))) + +(when jao-notmuch-enabled + (jao-minibuffer-add-variable 'jao-notmuch-minibuffer-string -20)) + +;;; saved searches +(defvar jao-notmuch--new "tag:\"/^(unread|new)$/\"") +(defvar jao-notmuch--newa (concat jao-notmuch--new " AND ")) + +(defun jao-notmuch--q (d0 d1 &optional k qs st) + (let ((q (or (when qs (mapconcat #'identity qs " AND ")) + (concat jao-notmuch--newa + (mapconcat (lambda (d) (when d (concat "tag:" d))) + (list d0 d1) " AND "))))) + (list :name (concat d0 (when (and d1 (not (string= "" d1))) "/") d1) + :key k :query q :search-type (or st 'tree) + :sort-order 'oldest-first))) + +(defun jao-notmuch--qn (d0 d1 k qs &optional st) + (jao-notmuch--q d0 d1 k (cons jao-notmuch--new qs) st)) + +(defun jao-notmuch--sq (tag &optional k d0 d1) + (jao-notmuch--qn (or d0 "feeds") (or d1 tag) k (list (concat "tag:" tag)))) + +(defvar jao-notmuch--shared-tags + '("new" "unread" "flagged" "signed" "sent" "attachment" "forwarded" + "encrypted" "gmane" "gnus" "feeds" "rss" "mce" "trove" "prog" "emacs")) + +(defun jao-notmuch--subtags (tag &rest excl) + (let* ((cmd (concat "notmuch search --output=tags tag:" tag)) + (ts (split-string (shell-command-to-string cmd)))) + (seq-difference ts (append jao-notmuch--shared-tags (cons tag excl))))) + +(defvar jao-notmuch-feed-searches-news + (mapcar #'jao-notmuch--sq '("news" "fun" "words" "computers"))) + +(defvar jao-notmuch-feed-searches-hack + (mapcar #'jao-notmuch--sq + '("xmobar" "geiser" "mdk" "mailutils" "notmuch"))) + +(defvar jao-notmuch-feed-searches-lang + (append (mapcar #'jao-notmuch--sq + '( "lobsters" "clojure" "lisp" "scheme" + "haskell" "idris" "erlang" "pharo")) + `(,(jao-notmuch--qn "feeds" "prog" "fp" + '("tag:prog" "not tag:\"/emacs/\""))))) + +(defvar jao-notmuch-feed-searches-sci + (mapcar #'jao-notmuch--sq + '("philosophy" "math" "physics" "sci" "gr-qc" "quant-ph"))) + +(defvar jao-notmuch-feed-searches + (append jao-notmuch-feed-searches-news + jao-notmuch-feed-searches-hack + jao-notmuch-feed-searches-lang + jao-notmuch-feed-searches-sci)) + +(defvar jao-notmuch-bigml-searches + `(,(jao-notmuch--q "bigml" "inbox" "bi") + ,(jao-notmuch--q "bigml" "alba" "ba") + ,(jao-notmuch--q "bigml" "support" "bs") + ,(jao-notmuch--q "bigml" "bugs" "bb") + ,(jao-notmuch--q "bigml" "drivel" "bd") + ,(jao-notmuch--q "bigml" "lists" "bl"))) + +(defvar jao-notmuch-inbox-searches + `(,(jao-notmuch--q "jao" "inbox" "ji") + ,(jao-notmuch--q "jao" "bills" "jb") + ,(jao-notmuch--q "jao" "drivel" "jd") + ,(jao-notmuch--q "jao" "mdk" "jm") + ,(jao-notmuch--qn "jao" "hacking" "jh" + '("tag:hacking" "not tag:\"/emacs/\"")) + ,(jao-notmuch--qn "jao" "local" "jl" '("tag:local")))) + +(defvar jao-notmuch-mark-searches + `(,(jao-notmuch--q "jao" "drafts" "d" '("tag:draft")) + ,(jao-notmuch--q "bml" "flagged" "rb" '("tag:flagged" "tag:bigml")) + ,(jao-notmuch--q "jao" "flagged" "rj" '("tag:flagged" "tag:jao")) + ,(jao-notmuch--q "feeds" "flagged" "rf" '("tag:flagged" "tag:feeds")))) + +(defvar jao-notmuch-emacs-searches + `(,(jao-notmuch--sq "emacs" "ee" "emacs" "feeds") + ,(jao-notmuch--sq "emacs-help" "eh" "emacs" "help") + ,(jao-notmuch--sq "emacs-github" "eg" "emacs" "github") + ,(jao-notmuch--sq "emacs-devel" "ed" "emacs" "devel") + ,(jao-notmuch--sq "emacs-bugs" "eb" "emacs" "bugs") + ,(jao-notmuch--sq "emacs-diffs" "ec" "emacs" "diffs") + ,(jao-notmuch--sq "emacs-orgmode" "eo" "emacs" "org"))) + +(setq notmuch-saved-searches + (append jao-notmuch-inbox-searches + jao-notmuch-bigml-searches + jao-notmuch-mark-searches + jao-notmuch-feed-searches + jao-notmuch-emacs-searches)) + +(defvar jao-notmuch-dynamic-searches + `(,(jao-notmuch--q "bml" "today" "tb" '("tag:bigml" "date:24h..")) + ,(jao-notmuch--q "jao" "today" "tj" + '("tag:jao" "date:24h.." + "not tag:\"/(feeds|spam|local)/\"")))) + +(defvar jao-notmuch-new-searches + `(,(jao-notmuch--q "new" nil "nn" '("tag:new" "not tag:draft")) + ,(jao-notmuch--q "unread" nil "nu" '("tag:unread")) + (:query "*" :name "messages"))) + +(defun jao-notmuch-tree-widen-search () + (interactive) + (when-let ((query (notmuch-tree-get-query))) + (let ((notmuch-show-process-crypto (notmuch-tree--message-process-crypto))) + (notmuch-tree-close-message-window) + (notmuch-tree (string-replace jao-notmuch--newa "" query))))) + +(defun jao-notmuch-widen-searches (searches &optional extra) + (mapcar (lambda (s) + (let* ((q (plist-get s :query)) + (qs (string-replace jao-notmuch--newa "" q))) + (plist-put (copy-sequence s) :query (concat qs extra)))) + searches)) + +(defvar jao-notmuch-widened-searches + (jao-notmuch-widen-searches notmuch-saved-searches)) + +(defvar jao-notmuch-flagged-searches + (let ((s (seq-difference notmuch-saved-searches + jao-notmuch-mark-searches))) + (jao-notmuch-widen-searches s " AND tag:flagged"))) + +(defun jao-notmuch-jump-search (&optional widen) + (interactive "P") + (let ((notmuch-saved-searches + (if widen jao-notmuch-widened-searches notmuch-saved-searches))) + (notmuch-jump-search))) + +;;; tags +(setq notmuch-archive-tags '("+trove" "-new" "-inbox") + notmuch-show-mark-read-tags '("-new" "-unread") + notmuch-tag-formats + (let ((d `(:foreground ,(face-attribute 'jao-themes-dimm :foreground))) + (e `(:foreground ,(face-attribute 'jao-themes-error :foreground) + :weight bold))) + `(("unread") + ("signed") + ("new" "N") + ("replied" "↩" (propertize tag 'face '(:family "Fira Code"))) + ("sent" "S") + ("attachment" "📎") + ("deleted" "🗙" (propertize tag 'face '(:underline nil ,@e))) + ;; ("attachment" "+") + ;; ("deleted" "xxx" (propertize tag 'face '(:underline nil ,@e))) + ("flagged" "!" (propertize tag 'face ',e)) + ("jao" "j") + ("bigml" "b") + ("feeds" "f") + ("gmane" "g"))) + notmuch-tag-deleted-formats + '(("unread") + ("new") + (".*" (notmuch-apply-face tag 'notmuch-tag-deleted)))) + +(with-eval-after-load "notmuch-tag" + (advice-add #'notmuch-read-tag-changes + :filter-return (lambda (x) (mapcar #'string-trim x)))) + +;;; package +(add-to-list 'load-path "/usr/local/share/emacs/site-lisp/") + +(use-package notmuch + :init + (setq notmuch-address-use-company t + notmuch-address-command (if jao-notmuch-enabled 'internal 'as-is) + notmuch-always-prompt-for-sender t + notmuch-draft-folder "local" + notmuch-draft-quoted-tags '("part") + notmuch-address-internal-completion '(received nil) + notmuch-fcc-dirs + '(("\\(support\\|education\\)@bigml.com" . nil) + (".*@bigml.com" . "bigml/trove +bigml +sent -new -unread") + (".*" . "jao/trove +jao +sent +trove -new -unread")) + notmuch-maildir-use-notmuch-insert t) + + :config + + (add-hook 'message-send-hook #'notmuch-mua-attachment-check) + + (when jao-notmuch-enabled + (define-key message-mode-map (kbd "C-c C-d") #'notmuch-draft-postpone) + (setq message-directory "~/var/mail/" + message-auto-save-directory "/tmp" + mail-user-agent 'message-user-agent)) + + :bind (:map notmuch-common-keymap + (("E" . jao-notmuch-open-enclosure) + ("B" . notmuch-show-resend-message) + ("b" . jao-notmuch-browse-urls)))) + +(use-package jao-notmuch :demand t) + +;;; hello +(defun jao-notmuch-hello--insert-searches (searches title) + (when-let (searches (notmuch-hello-query-counts searches)) + (let* ((cnt (when title + (seq-reduce (lambda (c q) + (+ c (or (plist-get q :count) 0))) + searches + 0))) + (title (if title (format "[ %d %s ]\n\n" cnt title) "\n"))) + (widget-insert (propertize title 'face 'jao-themes-f00)) + (let ((notmuch-column-control 1.0) + (start (point))) + (notmuch-hello-insert-buttons searches) + (indent-rigidly start (point) notmuch-hello-indent)) + cnt))) + +(defun jao-notmuch-hello-insert-inbox-searches () + (jao-notmuch-hello--insert-searches jao-notmuch-inbox-searches "inbox")) + +(defun jao-notmuch-hello-insert-bigml-searches () + (jao-notmuch-hello--insert-searches jao-notmuch-bigml-searches "bigml")) + +(defun jao-notmuch-hello-insert-mark-searches () + (jao-notmuch-hello--insert-searches jao-notmuch-mark-searches "marks") + (jao-notmuch-hello--insert-searches jao-notmuch-flagged-searches nil)) + +(defun jao-notmuch-hello-insert-feeds-searches () + (let ((sect "feeds")) + (dolist (s `(,jao-notmuch-feed-searches-news + ,jao-notmuch-feed-searches-hack + ,jao-notmuch-feed-searches-lang + ,jao-notmuch-feed-searches-sci)) + (let ((i (funcall #'jao-notmuch-hello--insert-searches s sect))) + (setq sect (unless i sect)))))) + +(defun jao-notmuch-hello-insert-emacs-searches () + (jao-notmuch-hello--insert-searches jao-notmuch-emacs-searches "emacs")) + +(defun jao-notmuch-hello-insert-dynamic-searches () + (jao-notmuch-hello--insert-searches jao-notmuch-dynamic-searches "dynamic") + (jao-notmuch-hello--insert-searches jao-notmuch-new-searches nil)) + +(defun jao-notmuch-refresh-agenda () + (interactive) + (save-window-excursion (org-agenda-list)) + (let ((b (current-buffer))) + (pop-to-buffer "*Calendar*") + (goto-char (point-min)) + (calendar-goto-today) + (pop-to-buffer b))) + +(defun jao-notmuch-hello-first () + (interactive) + (let ((inhibit-message t)) + (beginning-of-buffer) + (widget-forward 1))) + +(defun jao-notmuch-refresh-hello (&optional agenda) + (interactive "P") + (ignore-errors + (when (and (string= "Mail" (jao-afio-current-frame)) + (derived-mode-p 'notmuch-hello-mode)) + (when (not (string-blank-p jao-notmuch-minibuffer-string)) + (let ((notmuch-hello-auto-refresh nil)) (notmuch-hello))) + (when agenda (jao-notmuch-refresh-agenda)) + (unless (widget-at) (jao-notmuch-hello-first))))) + +(defvar jao-notmuch-hello--sec-rx "^\\(\\[ [0-9]+\\|All tags:.+\\)") + +(defun jao-notmuch-hello-next-section () + (interactive) + (when (re-search-forward jao-notmuch-hello--sec-rx nil t) + (widget-forward 1))) + +(defun jao-notmuch-hello-prev-section () + (interactive) + (beginning-of-line) + (unless (looking-at-p jao-notmuch-hello--sec-rx) + (re-search-backward jao-notmuch-hello--sec-rx nil t)) + (when (re-search-backward jao-notmuch-hello--sec-rx nil t) + (end-of-line) + (widget-forward 1))) + +(defun jao-notmuch-hello-next () + (interactive) + (if (widget-at) + (widget-button-press (point)) + (jao-notmuch-hello-next-section))) + +(use-package notmuch-hello + :init + (setq notmuch-column-control t + notmuch-hello-sections '(jao-notmuch-hello-insert-bigml-searches + jao-notmuch-hello-insert-inbox-searches + jao-notmuch-hello-insert-feeds-searches + jao-notmuch-hello-insert-emacs-searches + jao-notmuch-hello-insert-mark-searches + jao-notmuch-hello-insert-dynamic-searches + notmuch-hello-insert-alltags) + notmuch-hello-hide-tags nil + notmuch-hello-thousands-separator "," + notmuch-hello-auto-refresh t + notmuch-show-all-tags-list nil + notmuch-show-logo nil + notmuch-show-empty-saved-searches nil) + + :hook ((notmuch-hello-refresh . jao-notmuch-notify) + (jao-afio-switch . jao-notmuch-refresh-hello)) + + :bind (:map notmuch-hello-mode-map + (("a" . jao-notmuch-refresh-agenda) + ("j" . jao-notmuch-jump-search) + ("n" . jao-notmuch-hello-next) + ("p" . widget-backward) + ("S" . consult-notmuch) + ("g" . jao-notmuch-refresh-hello) + ("." . jao-notmuch-hello-first) + ("SPC" . widget-button-press) + ("[" . jao-notmuch-hello-prev-section) + ("]" . jao-notmuch-hello-next-section)))) + +;;; show +(defun jao-notmuch-open-enclosure (add) + (interactive "P") + (with-current-notmuch-show-message + (goto-char (point-min)) + (if (not (search-forward "Enclosure:" nil t)) + (user-error "No enclosure in message body") + (re-search-forward "https?://" nil t) + (if-let (url (thing-at-point-url-at-point)) + (progn + (message "%s %s ..." (if add "Adding" "Playing") url) + (unless add (jao-mpc-clear)) + (jao-mpc-add-url url) + (unless add (jao-mpc-play))) + (error "Found an enclosure, but not a link!"))))) + +(defconst jao-mail-clean-rx + (regexp-opt '("ElDiario.es - ElDiario.es: " "The Guardian: " + "The Conversation – Articles (UK): "))) + +(defun jao-mail-clean-address (args) + (when-let ((address (car args))) + (list (if (string-match ".+ updates on arXiv.org: \\(.+\\)" address) + (with-temp-buffer + (insert (match-string 1 address)) + (let ((shr-width 1000)) + (shr-render-region (point-min) (point-max))) + (replace-regexp-in-string "\"" "" (buffer-string))) + (replace-regexp-in-string jao-mail-clean-rx "" address))))) + +(use-package notmuch-show + :init + (setq gnus-blocked-images "." + notmuch-message-headers + '("To" "Cc" "Date" "Reply-To" "List-Id" "X-RSS-Feed") + notmuch-show-only-matching-messages t + notmuch-show-part-button-default-action 'notmuch-show-view-part + notmuch-wash-signature-lines-max 0 + notmuch-wash-wrap-lines-length 80 + notmuch-wash-citation-lines-prefix 10 + notmuch-wash-citation-lines-suffix 20 + notmuch-show-text/html-blocked-images "." + notmuch-show-header-line #'jao-notmuch-message-header-line) + + :config + + (advice-add 'notmuch-clean-address :filter-args #'jao-mail-clean-address) + + :bind + (:map notmuch-show-mode-map + (("h" . jao-notmuch-goto-tree-buffer) + ("TAB" . jao-notmuch-show-next-button) + ([backtab] . jao-notmuch-show-previous-button) + ("RET" . jao-notmuch-show-ret)))) + +;;; search +(use-package notmuch-search + :init (setq notmuch-search-result-format + '(("date" . "%12s ") + ("count" . "%-7s ") + ("authors" . "%-35s") + ("subject" . " %-100s") + (jao-notmuch-format-tags . " (%s)")) + notmuch-search-buffer-name-format "*%s*" + notmuch-saved-search-buffer-name-format "*%s*") + :bind (:map notmuch-search-mode-map + (("RET" . notmuch-tree-from-search-thread) + ("M-RET" . notmuch-search-show-thread)))) + +;;; tree +(defun jao-notmuch-tree--forward (&optional prev) + (interactive) + (forward-line (if prev -1 1)) + (when prev (forward-char 2)) + (jao-notmuch-tree-scroll-or-next)) + +(defun jao-notmuch-tree--backward () + (interactive) + (jao-notmuch-tree--forward t)) + +(defun jao-notmuch--via-url () + (when (window-live-p notmuch-tree-message-window) + (with-selected-window notmuch-tree-message-window + (goto-char (point-min)) + (when (re-search-forward "^Via: http" nil t) + (thing-at-point-url-at-point))))) + +(defun jao-notmuch-browse-url (ext) + (interactive "P") + (when-let (url (or (jao-notmuch--via-url) + (car (last (jao-notmuch-message-urls))))) + (funcall (if ext browse-url-secondary-browser-function #'browse-url) + url))) + +(defun jao-notmuch-adjust-tree-fonts (&optional family) + (let ((fg (face-attribute 'jao-themes-dimm :foreground))) + (dolist (f '(notmuch-tree-match-tree-face + notmuch-tree-no-match-tree-face)) + (if family + (set-face-attribute f nil :family family :foreground fg) + (set-face-attribute f nil :foreground fg))))) + +(use-package notmuch-tree + :init + (setq notmuch-tree-result-format + `(("date" . "%12s ") + (jao-notmuch-format-author . 25) + (jao-notmuch-format-msg-ticks . ,jao-mails-regexp) + (jao-notmuch-format-tree-and-subject . "%>-85s") + (jao-notmuch-format-tags . " (%s)")) + notmuch-unthreaded-result-format notmuch-search-result-format + consult-notmuch-result-format + `((jao-notmuch-format-msg-ticks . ,jao-mails-regexp) + ("date" . "%12s ") + ("authors" . "%-35s") + ("subject" . " %-100s") + (jao-notmuch-format-tags . " (%s)")) + ;; notmuch-tree-thread-symbols + ;; '((prefix . "─") (top . "─") (top-tee . "┬") + ;; (vertical . "│") (vertical-tee . "├") (bottom . "╰") + ;; (arrow . "")) + notmuch-tree-thread-symbols + '((prefix . " ") (top . " ") (top-tee . " ") + (vertical . " ") (vertical-tee . " ") (bottom . " ") + (arrow . ""))) + :config + + (when (display-graphic-p) + (jao-notmuch-adjust-tree-fonts + (when (string-prefix-p "Hack" jao-themes-default-face) "Source Code Pro"))) + + (jao-notmuch-tree-setup "T") + + (defun jao-notmuch-before-tree (&rest args) + (when (string= (buffer-name) "*notmuch-hello*") + (split-window-right 40) + (other-window 1))) + + (defvar jao-notmuch--visits 0) + + (defun jao-notmuch-after-tree-quit (&optional both) + (when (and (not (derived-mode-p 'notmuch-tree-mode 'notmuch-hello-mode)) + (save-window-excursion (other-window -1) + (derived-mode-p 'notmuch-hello-mode))) + (delete-window) + (jao-notmuch-refresh-hello (= 0 (mod (cl-incf jao-notmuch--visits) 10))))) + + (advice-add 'notmuch-tree :before #'jao-notmuch-before-tree) + (advice-add 'notmuch-tree-quit :after #'jao-notmuch-after-tree-quit) + + :bind (:map notmuch-tree-mode-map + (("b" . jao-notmuch-browse-urls) + ("d" . jao-notmuch-tree-toggle-delete) + ("D" . jao-notmuch-tree-toggle-delete-thread) + ("h" . jao-notmuch-goto-message-buffer) + ("H" . jao-notmuch-click-message-buffer) + ("i" . jao-notmuch-toggle-images) + ("K" . jao-notmuch-tag-jump-and-next) + ("k" . jao-notmuch-tree-read-thread) + ("n" . jao-notmuch-tree-next) + ("N" . jao-notmuch-tree--forward) + ("O" . notmuch-tree-toggle-order) + ("o" . jao-notmuch-tree-widen-search) + ("p" . jao-notmuch-tree-previous) + ("P" . jao-notmuch-tree--backward) + ("r" . notmuch-tree-reply) + ("R" . notmuch-tree-reply-sender) + ("s" . jao-notmuch-tree-toggle-spam) + ("u" . jao-notmuch-tree-toggle-flag) + ("v" . notmuch-tree-scroll-message-window) + ("V" . notmuch-tree-scroll-message-window-back) + ("x" . jao-notmuch-arXiv-capture) + ("<" . jao-notmuch-tree-beginning-of-buffer) + (">" . jao-notmuch-tree-end-of-buffer) + ("\\" . notmuch-tree-view-raw-message) + ("." . jao-notmuch-toggle-mime-parts) + ("=" . jao-notmuch-tree-toggle-message) + ("RET" . jao-notmuch-tree-show-or-scroll) + ("SPC" . jao-notmuch-tree-scroll-or-next) + ("M-g" . jao-notmuch-browse-url) + ("M-u" . jao-notmuch-tree-reset-tags)))) + +;;; org mode +(defvar jao-org-notmuch-last-subject nil) +(defun jao-org-notmuch-last-subject () jao-org-notmuch-last-subject) + +(defun jao-notmuch--add-tags (tags) + (if (derived-mode-p 'notmuch-show-mode) + (notmuch-show-add-tag tags) + (notmuch-tree-add-tag tags))) + +(defun org-notmuch-store-link () + "Store a link to a notmuch mail message." + (cl-case major-mode + ((notmuch-show-mode notmuch-tree-mode) + ;; Store link to the current message + (let* ((id (notmuch-show-get-message-id)) + (link (concat "notmuch:" id)) + (subj (notmuch-show-get-subject)) + (description (format "Mail: %s" subj))) + (setq jao-org-notmuch-last-subject subj) + (when (y-or-n-p "Archive message? ") + (jao-notmuch--add-tags '("+trove"))) + (when (y-or-n-p "Flag message as todo? ") + (jao-notmuch--add-tags '("+flagged"))) + (org-store-link-props "notmuch" link description))) + (notmuch-search-mode + ;; Store link to the thread on the current line + (let* ((id (notmuch-search-find-thread-id)) + (link (concat "notmuch:" id)) + (subj (notmuch-search-find-subject)) + (description (format "Mail: %s" subj))) + (setq jao-org-notmuch-last-subject subj) + (org-store-link-props + :type "notmuch" + :link link + :description description))))) + +(with-eval-after-load "org" + (org-link-set-parameters "notmuch" + :follow 'notmuch-show + :store 'org-notmuch-store-link)) +;;; arXiv +(use-package org-capture + :config + (add-to-list 'org-capture-templates + '("X" "arXiv" entry (file "notes/physics/arxiv.org") + "* %(jao-org-notmuch-last-subject)\n %i" + :immediate-finish t) + t) + (org-capture-upgrade-templates org-capture-templates)) + +(defun jao-notmuch-arXiv-capture () + (interactive) + (save-window-excursion + (jao-notmuch-goto-message-buffer) + (save-excursion + (goto-char (point-min)) + (re-search-forward "\\[ text/html \\]") + (forward-paragraph) + (setq-local transient-mark-mode 'lambda) + (set-mark (point)) + (goto-char (point-max)) + (org-capture nil "X")))) + +;;; html renderer +(when jao-notmuch-enabled (setq mm-text-html-renderer 'shr)) + +;;; consult +(jao-load-path "consult-notmuch") +(require 'consult-notmuch) +(consult-customize consult-notmuch :preview-key 'any) + +(defvar jao-consult-notmuch-history nil) + +(defvar jao-mailbox-folders '("bigml" "jao")) + +(defun jao-consult-notmuch-folder (&optional tree folder) + (interactive "P") + (let ((folder (if folder + (file-name-as-directory folder) + (completing-read "Group: " + jao-mailbox-folders + nil nil nil + jao-consult-notmuch-history + "."))) + (folder (replace-regexp-in-string "/\\(.\\)" ".\\1" folder)) + (init (read-string "Initial query: ")) + (init (format "folder:/%s/ %s" folder init))) + (if tree (consult-notmuch-tree init) (consult-notmuch init)))) + +(with-eval-after-load "notmuch-hello" + (define-key notmuch-hello-mode-map "f" #'jao-consult-notmuch-folder)) + +;;; link hint +(with-eval-after-load "link-hint" + (defun jao-link-hint--notmuch-next-part (&optional bound) + (when-let (p (next-single-property-change (point) :notmuch-part nil bound)) + (and (< p (or bound (point-max))) p))) + + (defun jao-link-hint--notmuch-part-p () + (and (get-text-property (point) :notmuch-part) + (when-let (b (button-at (point))) (button-label b)))) + + (link-hint-define-type 'notmuch-part + :next #'jao-link-hint--notmuch-next-part + :at-point-p #'jao-link-hint--notmuch-part-p + :vars '(notmuch-show-mode) + :open #'push-button + :open-message "Toggled" + :open-multiple t) + + (push 'link-hint-notmuch-part link-hint-types)) + +;;; . +(provide 'jao-custom-notmuch) diff --git a/custom/jao-custom-org.el b/custom/jao-custom-org.el new file mode 100644 index 0000000..aa4f9e0 --- /dev/null +++ b/custom/jao-custom-org.el @@ -0,0 +1,284 @@ +;; -*- lexical-binding: t -*- + +;;; General configuration +(use-package org + :ensure t + :custom ((org-export-backends '(ascii html latex texinfo))) + :init + (defalias 'jao-open-gnus-frame 'jao-afio--goto-mail) + + (setq org-adapt-indentation t + org-catch-invisible-edits 'smart + org-complete-tags-always-offer-all-agenda-tags t + org-cycle-separator-lines 0 ;; no blank lines when all colapsed + org-deadline-warning-days 14 + org-default-notes-file (expand-file-name "inbox.org" org-directory) + org-directory jao-org-dir + org-display-remote-inline-images 'download ;; 'skip 'cache + org-ellipsis " .." ;; ↴ + org-email-link-description-format "Email %c: %s" + org-enforce-todo-dependencies t + org-fast-tag-selection-single-key 'expert + ;; org-list-demote-modify-bullet '(("+" . "-") ("-" . "+") ("*" . "+")) + org-link-frame-setup + '((gnus . (lambda (&optional x) (jao-open-gnus-frame))) + (file . find-file-other-window)) + org-log-done nil + org-modules '(bibtex info eww eshell git-link) + org-odd-levels-only t + org-outline-path-complete-in-steps nil + org-refile-allow-creating-parent-nodes 'confirm + org-refile-targets '((nil :maxlevel . 5) + (org-agenda-files :maxlevel . 5)) + org-refile-use-outline-path 'file + org-return-follows-link t + org-reverse-note-order t + org-special-ctrl-a/e t + org-src-fontify-natively t + org-startup-folded t + org-tag-alist nil + org-tags-column -75 + org-todo-keywords + '((sequence "TODO(t)" "STARTED(s!)" "|" "DONE(d!)") + (sequence "REPLY(r)" "WAITING(w!)" "|" "DONE(d!)") + (sequence "TOREAD(T)" "READING(R!)" "|" "READ(a!)") + (sequence "|" "CANCELLED(x!)" "SOMEDAY(o!)" "DONE(d!)")) + org-use-fast-todo-selection t + org-use-speed-commands nil ;; t and then ? to see help + org-gnus-prefer-web-links nil)) +(require 'org) + +;;; Agenda +(setq ;; org-agenda-custom-commands + ;; '(("w" todo "WAITING" nil) + ;; ("W" agenda "" ((org-agenda-ndays 21)))) + org-agenda-files (mapcar (lambda (f) + (expand-file-name f jao-org-dir)) + '("inbox.org" "bigml.org")) + org-agenda-block-separator " " + org-agenda-breadcrumbs-separator "•" + org-agenda-current-time-string "•" ;; "*" + org-agenda-time-grid + '((daily today require-timed) + (800 1000 1200 1400 1600 1800 2000) "" "·") + org-agenda-include-diary t + org-agenda-include-inactive-timestamps t + org-agenda-inhibit-startup nil + org-agenda-restore-windows-after-quit t + org-agenda-show-all-dates t + org-agenda-skip-deadline-if-done t + org-agenda-skip-scheduled-if-done nil + org-agenda-span 14 + org-agenda-start-on-weekday nil + org-agenda-window-setup 'current-window) + +;;; Capture templates +(setq org-capture-templates + '(("t" "TODO" entry + (file+headline "inbox.org" "Todo") + "* TODO %?\n %i%a" :prepend t) + ("r" "REPLY" entry + (file+headline "inbox.org" "Todo") + "* REPLY %:subject%?\n %t\n %i%a" :prepend t) + ("a" "Appointment" entry + (file+olp "inbox.org" "Appointments") + "* %^T %?\n %a" :time-prompt t) + ("i" "Inbox note" entry (file+headline "inbox.org" "Notes") + "* %a\n %i%?(added on: %u)" :prepend t))) +;; (org-capture-upgrade-templates org-capture-templates) + +;;; MIME and file apps +(setq org-file-apps + '((system . mailcap) + (".*\\.djvu" . system) + (t . emacs))) + +;;; Appearance +;; Show hidden emphasis markers +(use-package org-appear + :ensure t + :hook (org-mode . org-appear-mode)) + +;; #+caption: Image caption. +;; #+attr_org: :width 100 +;; [[file:path/to/image.png]] + +(setq org-startup-indented nil + org-pretty-entities nil + org-hide-emphasis-markers t + org-hide-leading-stars t + org-startup-with-inline-images t + org-image-actual-width '(300)) + +;;; LaTeX +(use-package org-fragtog + :after org + :ensure t + :hook ((org-mode . org-fragtog-mode))) + +(require 'org-fragtog) + +(setq org-format-latex-options + `(:foreground default + :background + ,(if (jao-colors-scheme-dark-p) "black" "white") + :scale 1.25 + :html-foreground "black" + :html-background "Transparent" + :html-scale 1.0 + :matchers ("begin" "$1" "$" "$$" "\\(" "\\[")) + org-preview-latex-image-directory + (expand-file-name "~/.emacs.d/cache/ltximg/") + org-latex-hyperref-template nil + org-highlight-latex-and-related '(latex script entities)) + +(require 'ox-latex) + +;;; Export (minted) +(setq org-latex-listings 'minted + org-latex-packages-alist '(("" "minted")) + org-latex-pdf-process + '("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f" + "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f")) + +;;; Babel and literate programming +(setq org-src-window-setup 'other-window) ;; current-window +(require 'org-tempo nil t) ;; + + Creative Commons License + +
+ jao.io by + jao + is licensed under a + + Creative Commons Attribution-ShareAlike 3.0 Unported License. + diff --git a/email.org b/email.org deleted file mode 100644 index e8a8a71..0000000 --- a/email.org +++ /dev/null @@ -1,391 +0,0 @@ -#+property: header-args:emacs-lisp :lexical t :tangle yes :comments yes :results silent :shebang ";; -*- lexical-binding: t; -*-" :tangle-mode (identity #o644) -#+title: email handling (message mode, bbdb, gnus, notmuch) - -* personal emails and others - #+begin_src emacs-lisp - (defvar jao-mails) - (defvar jao-extra-mails) - (defvar jao-mails-regexp (regexp-opt jao-mails)) - (defvar jao-notmuch-enabled (eq jao-afio-mail-function 'notmuch)) - #+end_src -* gnus - #+begin_src emacs-lisp - (setq gnus-init-file "~/.emacs.d/gnus.el" - gnus-home-directory "~/.emacs.d/gnus" - gnus-directory gnus-home-directory - gnus-cache-directory (expand-file-name "cache" gnus-home-directory) - gnus-kill-files-directory (expand-file-name "News" gnus-home-directory) - message-directory (expand-file-name "Mail" gnus-home-directory) - mail-source-directory (expand-file-name "Mail" gnus-home-directory)) - - (let ((org-file (expand-file-name "gnus.org" jao-emacs-dir))) - (when (file-newer-than-file-p org-file gnus-init-file) - (org-babel-tangle-file org-file))) - - #+end_src -* message mode -*** Customization - #+begin_src emacs-lisp - (setq message-send-mail-function 'message-send-mail-with-sendmail - message-sendmail-envelope-from 'header - message-sendmail-f-is-evil nil) - (setq imap-store-password t) - (setq password-cache-expiry nil) - (setq message-generate-headers-first t) - (setq message-forward-before-signature nil) - (setq message-alternative-emails - (regexp-opt (append jao-mails jao-extra-mails))) - (setq message-dont-reply-to-names - (regexp-opt (append jao-mails '("noreply@" "@noreply" - "no-reply@" "@no-reply" - "notifications@github")))) - (setq message-citation-line-format "On %a, %b %d %Y, %N wrote:\n") - (setq message-citation-line-function 'message-insert-formatted-citation-line) - - (setq message-user-fqdn "gnus.jao.io") - - ;; writing messages - (setq message-kill-buffer-on-exit t) - (setq message-max-buffers 5) - (setq message-insert-signature t) - (setq message-from-style 'angles - user-mail-address (car jao-mails) - mail-host-address system-name - message-syntax-checks '((sender . disabled)) - message-default-headers - (concat - "X-Attribution: jao\n" - "X-Clacks-Overhead: GNU Terry Pratchett\n" - "X-URL: \n") - message-hidden-headers - '("^References:" "^Face:" "^X-Face:" "^X-Draft-From:") - message-make-forward-subject-function 'message-forward-subject-fwd) - - (setq message-expand-name-standard-ui t) - #+end_src -*** Adjust Bcc - #+begin_src emacs-lisp - (defvar jao-message--bcc-rx (regexp-opt '("mail.io" "gnu.org"))) - - (defun jao-message-insert-bcc () - (let ((f (or (message-fetch-field "From") ""))) - (when (or (string-blank-p f) (string-match-p jao-message--bcc-rx f)) - (insert "Bcc: proton@jao.io\n")))) - - (add-hook 'message-header-setup-hook #'jao-message-insert-bcc) - #+end_src -*** To->From and Bcc - #+begin_src emacs-lisp - (defvar jao-message-to-from nil) - - (defun jao-message-adjust-from () - (let ((to (concat (message-fetch-field "To") (message-fetch-field "Cc")))) - (when-let* ((tf (seq-find (lambda (tf) (string-match-p (car tf) to)) - jao-message-to-from)) - (from (message-make-from "Jose A Ortega Ruiz" (cdr tf)))) - (save-restriction - (widen) - (message-replace-header "From" from))))) - - (when jao-notmuch-enabled - (add-hook 'message-header-setup-hook #'jao-message-adjust-from)) - #+end_src -*** Clean up reply addresses - #+begin_src emacs-lisp :tangle no - (defun jao-message--dont-reply () - (let ((x (message-dont-reply-to-names))) - (if (functionp x) - x - (lambda (mail) (unless (string-match-p x mail) mail))))) - - (defun jao-message-strip-replies () - (dolist (header '("To" "Cc")) - (when-let ((v (message-fetch-field header))) - (let* ((v (message-tokenize-header v)) - (vs (delq nil (mapcar (jao-message--dont-reply) v))) - (v (when vs (mapconcat #'string-trim vs ", ")))) - (message-replace-header header v))))) - - (when jao-notmuch-enabled - (add-hook 'message-setup-hook #'jao-message-strip-replies)) - - #+end_src -*** Encryption - #+begin_src emacs-lisp - ;; avoiding bogus warning - (setq gnutls-min-prime-bits nil) - (setq gnus-buttonized-mime-types - '("multipart/encrypted" "multipart/signed" "multipart/alternative")) - - (setq mm-verify-option 'always) - (setq mm-decrypt-option 'always) - - (setq mm-sign-option 'guided) - (setq mm-encrypt-option 'guided) - - (setq mml-secure-passphrase-cache-expiry (* 3600 24) - password-cache-expiry (* 3600 24)) - - (setq smime-CA-directory "/etc/ssl/certs/" - smime-certificate-directory - (expand-file-name "certs/" gnus-directory)) - - (with-eval-after-load "mm-decode" - ;; Tells Gnus to inline the part - (add-to-list 'mm-inlined-types "application/pgp$") - ;; Tells Gnus how to display the part when it is requested - (add-to-list 'mm-inline-media-tests '("application/pgp$" - mm-inline-text identity)) - ;; Tell Gnus not to wait for a request, just display the thing - ;; straight away. - (add-to-list 'mm-automatic-display "application/pgp$") - ;; But don't display the signatures, please. - (setq mm-automatic-display (remove "application/pgp-signature" - mm-automatic-display))) - - ;; decide whether to encrypt or just sign outgoing messages - (defvar jao-message-try-sign nil) - (defun jao-message-maybe-sign () - (when (and jao-message-try-sign (y-or-n-p "Sign message? ")) - (if (y-or-n-p "Encrypt message? ") - (let ((recipient (message-fetch-field "To"))) - (if (or (pgg-lookup-key recipient) - (and (y-or-n-p (format "Fetch %s's key? " recipient)) - (pgg-fetch-key pgg-default-keyserver-address - recipient))) - (mml-secure-message-encrypt-pgp) - (mml-secure-message-sign-pgp))) - (mml-secure-message-sign-pgp)))) - - ;; for ma gnus - (eval-after-load "rfc2047" - '(add-to-list 'rfc2047-header-encoding-alist - '("User-Agent" . address-mime))) - #+end_src -*** Attach image to message - Use ~C-c C-p~ in message-mode, and ~C-c y~. -*** Check attachment - #+begin_src emacs-lisp - (defvar jao-message-attachment-regexp "\\([Ww]e send\\|[Ii] send\\|attach\\)") - (defun jao-message-check-attachment () - "Check if there is an attachment in the message if I claim it." - (save-excursion - (message-goto-body) - (when (search-forward-regexp jao-message-attachment-regexp nil t nil) - (message-goto-body) - (unless (or (search-forward "<#part" nil t nil) - (message-y-or-n-p - "No attachment. Send the message? " nil nil)) - (error "No message sent"))))) - #+end_src -*** Check Fcc/Gcc - #+begin_src emacs-lisp - (defun jao-message-check-gcc () - "Ask whether to keep a copy of message." - (save-excursion - (save-restriction - (message-narrow-to-headers) - (when (and (or (message-fetch-field "Gcc") - (message-fetch-field "Fcc")) - (not (y-or-n-p "Archive? "))) - (message-remove-header "\\(?:[BFG]cc\\)"))))) - #+end_src -*** Check recipient - #+begin_src emacs-lisp - (defun jao-message-check-recipient () - (save-excursion - (save-restriction - (message-narrow-to-headers) - (when-let ((to (message-fetch-field "To"))) - (when (string-match-p jao-mails-regexp to) - (unless (y-or-n-p "Message is addressed to yourself. Continue?") - (error "Message not sent"))))))) - #+end_src -*** Randomsig - #+begin_src emacs-lisp - (with-eval-after-load "message" - (when (require 'randomsig nil t) - (define-key message-mode-map (kbd "C-c s") 'randomsig-replace-sig) - (define-key message-mode-map (kbd "C-c S") 'randomsig-select-sig) - (setq randomsig-dir (expand-file-name "~/etc/config/emacs")) - (setq randomsig-files '("signatures.txt")) - ;; or (setq randomsig-files (randomsig-search-sigfiles)) - ;; or (setq randomsig-files 'randomsig-search-sigfiles) - (setq message-signature 'randomsig-signature) - (setq randomsig-delimiter-pattern "^%$" - randomsig-delimiter "%"))) - #+end_src -*** Send mail hooks - #+begin_src emacs-lisp - (dolist (h '(jao-message-check-gcc - jao-message-check-recipient - jao-message-maybe-sign)) - (add-hook 'message-send-hook h)) - (unless jao-notmuch-enabled - (add-hook 'message-send-hook #'jao-message-check-attachment)) - #+end_src -*** Keybindings - #+begin_src emacs-lisp - (with-eval-after-load "message" - ;; (define-key message-mode-map [f7] 'mml-secure-message-sign-pgp) - (define-key message-mode-map [f8] 'mml-secure-message-encrypt-pgp) - (define-key message-mode-map (kbd "C-c y") #'yank-media)) - #+end_src -* sendmail/smtp - #+begin_src emacs-lisp - (defun jao-sendmail-gmail () - (setq smtpmail-auth-supported '(login cram-md5 plain)) - (setq smtpmail-smtp-server "smtp.gmail.com") - (setq smtpmail-smtp-service 587)) - - (defun jao-sendmail-local () - (setq send-mail-function 'sendmail-send-it) - (setq smtpmail-auth-supported nil) ;; (cram-md5 plain login) - (setq smtpmail-smtp-server "127.0.0.1") - (setq smtpmail-smtp-service 25)) - - (defun jao-sendmail-msmtp () - (setq send-mail-function 'sendmail-send-it - sendmail-program "/usr/bin/msmtp" - mail-specify-envelope-from t - message-sendmail-envelope-from 'header - mail-envelope-from 'header)) - - (jao-sendmail-local) - #+end_src -* mailcap - #+begin_src emacs-lisp - (use-package mailcap - :config - (add-to-list 'mailcap-mime-extensions '(".JPEG" . "image/jpeg")) - (add-to-list 'mailcap-mime-extensions '(".JPG" . "image/jpeg")) - - (defun jao-icalendar-import-buffer () - (let ((icalendar-import-format "%s%u%l%d")) - (icalendar-import-buffer diary-file t nil)) - (kill-buffer) - (message "Event imported into diary")) - - :custom - ((mailcap-user-mime-data - '((jao-icalendar-import-buffer "application/ics") - ("viewpdf.sh %s" "application/pdf"))))) - #+end_src -* multipart html renderer - #+begin_src emacs-lisp - (defun jao-w3m-html-renderer (handle) - (let ((w3m-message-silent t) - (mm-w3m-safe-url-regexp nil)) - (condition-case nil - (mm-inline-text-html-render-with-w3m handle) - (error (delete-region (point) (point-max)) - (let ((shr-use-fonts nil) - (shr-use-colors nil)) - (mm-shr handle)))))) - - (defun jao-shr-html-renderer (handle) - (let (;; (shr-use-fonts t) - ;; (shr-use-colors t) - (shr-width 130)) - (mm-shr handle))) - - ;; (setq mm-text-html-renderer #'jao-w3m-html-renderer) - (setq mm-text-html-renderer #'jao-shr-html-renderer) - #+end_src -* bbdb - #+begin_src emacs-lisp :tangle no - (use-package bbdb - :ensure t - :init (setq bbdb-complete-name-allow-cycling t - bbdb-completion-display-record nil - bbdb-gui t - bbdb-message-all-addresses t - bbdb-complete-mail-allow-cycling t - bbdb-north-american-phone-numbers-p nil - bbdb-add-aka t - bbdb-add-name 2 - bbdb-message-all-addresses t - bbdb-mua-pop-up t ;; 'horiz - bbdb-mua-pop-up-window-size 0.3 - bbdb-layout 'multi-line - bbdb-mua-update-interactive-p '(query . create) - bbdb-mua-auto-update-p 'bbdb-select-message - bbdb-user-mail-address-re jao-mails-regexp - bbdb-auto-notes-ignore-headers - `(("From" . ,jao-mails-regexp) - ("From" . ".*@.*github\.com.*") - ("To" . ".*@.*github\.com.*") - ("Reply-to" . ".*") - ("References" . ".*")) - bbdb-auto-notes-ignore-messages - `(("To" . ".*@.*github\\.com.*") - ("From" . ".*@.*github\\.com.*") - ("From" . "info-list") - ("From" . "no-?reply\\|deploy") - ("X-Mailer" . "MailChimp")) - bbdb-accept-message-alist - `(("To" . ,jao-mails-regexp) - ("Cc" . ,jao-mails-regexp) - ("BCc" . ,jao-mails-regexp)) - bbdb-ignore-message-alist bbdb-auto-notes-ignore-messages) - :config - (add-hook 'message-setup-hook 'bbdb-mail-aliases) - ;; (add-hook 'bbdb-notice-mail-hook 'bbdb-auto-notes) - (add-hook 'bbdb-after-change-hook (lambda (arg) (bbdb-save))) - (require 'bbdb-anniv) ;; BBDB 3.x this gets birthdays in org agenda - ;; and diary - clever stuff - (add-hook 'diary-list-entries-hook 'bbdb-anniv-diary-entries) - - (setq bbdb-file (expand-file-name "~/.emacs.d/bbdb")) - (if jao-notmuch-enabled - (bbdb-initialize 'message 'pgp) - (bbdb-initialize 'message 'pgp 'gnus))) - #+end_src -* mailboxes - #+begin_src emacs-lisp - (defun jao-list-mailboxes (base &rest excl) - (let ((dir (expand-file-name base "~/var/mail"))) - (seq-difference (directory-files dir) (append '("." "..") excl)))) - - - #+end_src -* consult narrowing - #+begin_src emacs-lisp - (defvar jao-mail-consult-buffer-history nil) - - (defun jao-mail-buffer-p (b) - (or (member (buffer-name b) - '("*Calendar*" "inbox.org" "*Org Agenda*" - "*Fancy Diary Entries*" "diary")) - (with-current-buffer b - (derived-mode-p 'notmuch-show-mode - 'notmuch-search-mode - 'notmuch-tree-mode - 'notmuch-hello-mode - 'notmuch-message-mode - 'gnus-group-mode - 'gnus-summary-mode - 'gnus-article-mode)))) - - (defvar jao-mail-consult-source - (list :name "mail buffer" - :category 'buffer - :hidden t - :narrow (cons ?n "mail buffer") - :history 'jao-mail-consult-buffer-history - :action (lambda (b) - (when (not (string-blank-p (or b ""))) - (jao-afio--goto-mail) - (if (get-buffer-window b) - (pop-to-buffer b) - (pop-to-buffer-same-window b)))) - :items (lambda () - (mapcar #'buffer-name - (seq-filter #'jao-mail-buffer-p (buffer-list)))))) - - (jao-consult-add-buffer-source 'jao-mail-consult-source "Mail" ?n) - #+end_src -* notmuch - #+begin_src emacs-lisp - (jao-load-org "notmuch") - #+end_src diff --git a/eww.org b/eww.org deleted file mode 100644 index 4a9dd71..0000000 --- a/eww.org +++ /dev/null @@ -1,191 +0,0 @@ -#+property: header-args :lexical t :tangle yes :comments yes :results silent :shebang ";; -*- lexical-binding: t -*-" :tangle-mode (identity #o644) -#+title: Web browsing using eww - -* Integration with browse-url and afio - #+begin_src emacs-lisp - (defun jao-eww-browse-url (url &rest r) - "Browse URL using eww." - (if (derived-mode-p 'eww-mode) - (eww url) - (jao-afio--goto-www) - (select-window (frame-first-window)) - (let* ((url (url-encode-url url)) - (bf (seq-find `(lambda (b) - (with-current-buffer b - (string= ,url - (url-encode-url (eww-current-url))))) - (jao-eww-session--list-buffers)))) - (cond (bf (switch-to-buffer bf)) - ((string-match-p url "^file://") (eww-open-file url)) - (t (eww url 4)))))) - - (setq jao-browse-url-function #'jao-eww-browse-url) - (setq eww-use-browse-url "^\\(gemini\\|gopher\\):") - #+end_src -* Opening URLs - #+begin_src emacs-lisp - (defun jao-eww-copy-link () - (interactive) - (when-let (lnk (or (car (eww-links-at-point)) (eww-current-url))) - (message "%s" lnk) - (kill-new lnk))) - - (defun jao-eww-browse (arg) - (interactive "P" eww-mode) - (setq eww-prompt-history - (cl-remove-duplicates eww-prompt-history :test #'string=)) - (let ((url (completing-read (if arg "eww in new buffer: " "eww: ") - eww-prompt-history nil nil nil - 'eww-prompt-history (eww-current-url)))) - (eww url (when arg 4)))) - - (defun jao-eww-browse-new () - (interactive nil eww-mode) - (jao-eww-browse t)) - - (defun jao-eww-reload (images) - (interactive "P" eww-mode) - (if images - (let ((shr-blocked-images nil)) - (eww-reload t)) - (call-interactively 'eww-reload))) - #+end_src -* Consult narrowing - #+begin_src emacs-lisp - (with-eval-after-load "consult" - (defvar jao-eww-consult-history nil) - (defvar jao-eww-buffer-source - (list :name "eww buffer" - :category 'eww-buffer - :hidden t - :narrow (cons ?e "eww") - :annotate (lambda (c) (get-text-property 0 'url c)) - :history 'jao-eww-consult-history - :action (lambda (b) - (jao-afio--goto-www) - (switch-to-buffer (get-text-property 0 'buffer b))) - :items - (lambda () - (seq-map (lambda (b) - (with-current-buffer b - (let ((tl (or (plist-get eww-data :title) "")) - (url (or (eww-current-url) (buffer-name)))) - (propertize (if (string-blank-p tl) url tl) - 'buffer b 'url url)))) - (seq-filter #'jao-www--buffer-p (buffer-list)))))) - (jao-consult-add-buffer-source 'jao-eww-buffer-source "Web" ?e)) - #+end_src -* Images - #+begin_src emacs-lisp - (defun jao-eww-next-image () - (interactive nil eww-mode) - (when-let (p (text-property-search-forward 'image-displayer nil nil t)) - (goto-char (prop-match-beginning p)))) - #+end_src -* Close page and reopen - #+begin_src emacs-lisp - (defvar jao-eww--closed-urls ()) - - (defun jao-eww-close () - (interactive nil eww-mode) - (when-let (current (eww-current-url)) - (add-to-list 'jao-eww--closed-urls current)) - (let ((nxt (car (jao-eww-session-invisible-buffers)))) - (kill-current-buffer) - (when nxt (switch-to-buffer nxt nil t)))) - - (defun jao-eww-reopen (arg) - (interactive "P") - (if (> (length jao-eww--closed-urls) 0) - (let ((url (completing-read "URL: " jao-eww--closed-urls))) - (jao-afio--goto-www) - (setq jao-eww--closed-urls (remove url jao-eww--closed-urls)) - (eww url (when arg 4))) - (message "No previously closed URLs."))) - - (defun jao-eww-reopen-new () - (interactive) - (jao-eww-reopen t)) - #+end_src -* Sessions - #+begin_src emacs-lisp - (use-package jao-eww-session - :custom ((jao-eww-session-file "~/.emacs.d/cache/eww-session.eld"))) - #+end_src -* Package - #+begin_src emacs-lisp - (use-package shr - :custom ((shr-width nil) - (shr-use-colors t) - (shr-use-fonts t) - (shr-max-width 130) - (shr-blocked-images nil) - (shr-inhibit-images t) - (shr-max-image-proportion 1.0) - (shr-hr-line ?―))) - - (use-package eww - :demand t - :custom ((eww-browse-url-new-window-is-tab nil) - (eww-download-directory jao-sink-dir) - (eww-header-line-format " %u") - (eww-form-checkbox-selected-symbol "☒") - (eww-buffer-name-length 180)) - - :config - (with-eval-after-load "org" (require 'ol-eww nil t)) - - (defun jao-eww-buffer-name () - (when-let ((s (or (plist-get eww-data :title) - (plist-get eww-data :url)))) - (when (not (string-blank-p s)) (format "%s" s)))) - (setq eww-auto-rename-buffer #'jao-eww-buffer-name) - - :bind (:map eww-mode-map (("b" . eww-back-url) - ("B" . eww-forward-url) - ("d" . jao-download) - ("f" . link-hint-open-link) - ("F" . embark-on-link) - ("L" . eww-forward-url) - ("N" . jao-eww-next-image) - ("o" . jao-eww-browse) - ("O" . jao-eww-browse-new) - ("r" . jao-eww-reload) - ("s" . eww-search-words) - ("S" . jao-eww-browse-new) - ("u" . jao-eww-reopen) - ("U" . jao-eww-reopen-new) - ("w" . org-eww-copy-for-org-mode) - ("q" . jao-eww-close) - ("x" . jao-rss-subscribe) - ("y" . jao-eww-copy-link) - ("\\" . eww-view-source) - ("C-c C-w" . jao-eww-close) - ("M-i" . eww-toggle-images)))) - - #+end_src -* Fixes for shr image rendering - #+begin_src emacs-lisp - (require 'shr) - - (defun jao-shr--kill-nl (p) - (save-excursion - (goto-char p) - (when (looking-at-p "\n") (delete-char 1)))) - - (defun jao-shr-tag-img (fn &rest args) - (let ((p (point))) - (prog1 (apply fn args) - (when (> (point) p) (jao-shr--kill-nl p))))) - - (defun jao-shr-insert (fn &rest args) - (let ((p (when (and (not (bolp)) - (get-text-property (1- (point)) 'image-url)) - (point)))) - (prog1 (apply fn args) - (when (and p (> (point) p)) (jao-shr--kill-nl p))))) - - (advice-add 'shr-tag-img :around #'jao-shr-tag-img) - (advice-add 'shr-insert :around #'jao-shr-insert) - - #+end_src diff --git a/exwm.org b/exwm.org deleted file mode 100644 index 65db454..0000000 --- a/exwm.org +++ /dev/null @@ -1,551 +0,0 @@ -#+property: header-args :lexical t :tangle yes :comments yes :results silent :shebang ";; -*- lexical-binding: t; -*-" :tangle-mode (identity #o644) -#+title: exwm configuration -#+auto_tangle: t - -* Load and basic config - #+begin_src emacs-lisp - (defvar jao-exwm--use-afio t) - - (jao-load-path "exwm") - - (use-package exwm - :ensure t - :init (setq exwm-debug nil - exwm-workspace-number 1 - exwm-workspace-show-all-buffers t - exwm-workspace-warp-cursor nil - exwm-layout-show-all-buffers t - exwm-floating-border-color - (if (jao-colors-scheme-dark-p) "black" "grey90"))) - - (use-package exwm-edit :ensure t) - (require 'exwm) - #+end_src -* Frame(s) as workspaces - #+begin_src emacs-lisp - (defun jao-exwm--new-frame-p () - (not (frame-parameter nil 'jao-frames-initialized))) - - (defun jao-exwm--mark-frame (force) - (prog1 (or force (jao-exwm--new-frame-p)) - (set-frame-parameter nil 'jao-frames-initialized t))) - - (defun jao-exwm--goto-main (&optional init) - (interactive "P") - (exwm-workspace-switch-create 1) - (when (jao-exwm--mark-frame init) (jao-trisect))) - - (defun jao-exwm--goto-mail (&optional init) - (interactive "P") - (exwm-workspace-switch-create 2) - (when (jao-exwm--mark-frame init) - (jao-afio-open-gnus))) - - (defun jao-exwm--goto-www (&optional init) - (interactive "P") - (exwm-workspace-switch-create 5) - (when (jao-exwm--mark-frame init) - (jao-afio-open-www) - (let ((scroll-bar-mode 'left)) - (toggle-scroll-bar 1) - (set-frame-parameter (window-frame) 'scroll-bar-width 12)) - (jao-toggle-inactive-mode-line))) - - (defun jao-exwm--goto-docs (&optional init) - (interactive "P") - (exwm-workspace-switch-create 4) - (when (jao-exwm--mark-frame init) - (jao-afio-open-doc))) - - (defun jao-exwm-open-doc (file) - (jao-exwm--goto-docs) - (jao-find-or-open file)) - - (defun jao-exwm-no-afio-setup () - (interactive) - (defalias 'jao-open-gnus-frame 'jao-exwm--goto-mail) - (defalias 'jao-goto-www-frame 'jao-exwm--goto-www) - (setq jao-open-doc-fun #'jao-exwm-open-doc) - (setq minibuffer-follows-selected-frame t) - (global-set-key "\C-cf" 'jao-exwm--goto-main) - (global-set-key "\C-cg" 'jao-exwm--goto-mail) - (global-set-key "\C-cw" 'jao-exwm--goto-www) - (global-set-key "\C-cz" 'jao-exwm--goto-docs)) - - (if jao-exwm--use-afio - (setq minibuffer-follows-selected-frame nil) - (jao-exwm-no-afio-setup)) - #+end_src -* Tracking - #+begin_src emacs-lisp - (add-hook 'exwm-workspace-switch-hook 'tracking-remove-visible-buffers) - #+end_src -* Buffer names - #+begin_src emacs-lisp - (defun jao-exwm--use-title-p () - (and exwm-title (not (string-blank-p exwm-title)))) - - (defun jao-exwm-rename-buffer/class () - (unless (jao-exwm--use-title-p) - (exwm-workspace-rename-buffer exwm-class-name))) - - (defun jao-exwm-rename-buffer/title () - (cond ((or (not exwm-instance-name) - (jao-exwm--use-title-p)) - (exwm-workspace-rename-buffer exwm-title)) - ((string= "Zathura" exwm-class-name) - (exwm-workspace-rename-buffer - (format "zathura: %s" (file-name-nondirectory exwm-title)))))) - - (defun jao-exwm--set-exwm-name () - (when (not jao-exwm--name) - (setq jao-exwm--name jao-exwm--current-name - jao-exwm--current-name nil))) - - (add-hook 'exwm-mode-hook 'jao-exwm--set-exwm-name) - (add-hook 'exwm-update-class-hook 'jao-exwm-rename-buffer/class) - (add-hook 'exwm-update-title-hook 'jao-minibuffer-refresh) - (add-hook 'exwm-update-title-hook 'jao-exwm-rename-buffer/title) - #+end_src -* Float windows - #+begin_src emacs-lisp - (defvar jao-exwm-max-x (x-display-pixel-width)) - (defvar jao-exwm-max-y (x-display-pixel-height)) - - (defun jao-exwm--float-to (x y &optional w h) - (let* ((w (or w (frame-pixel-width))) - (h (or h (frame-pixel-height))) - (x (if (< x 0) (- jao-exwm-max-x (- x) w) x)) - (y (if (< y 0) (- jao-exwm-max-y (- y) h) y)) - (p (or (frame-parameter nil 'jao-position) (frame-position)))) - (exwm-floating-move (- x (car p)) (- y (cdr p))) - (exwm-layout-enlarge-window-horizontally (- w (frame-pixel-width))) - (exwm-layout-enlarge-window (- h (frame-pixel-height))) - (set-frame-parameter nil 'jao-position (cons x y)))) - - (defun jao-exwm--center-float (&optional w h) - (interactive) - (let* ((mx jao-exwm-max-x) - (my jao-exwm-max-y) - (w (or w (frame-pixel-width))) - (h (or h (/ (* w my) mx)))) - (jao-exwm--float-to (/ (- mx w) 2) (/ (- my h) 2) w h))) - - (defun jao-exwm--setup-float () - (set-frame-parameter nil 'jao-position nil) - (cond ((string= "Firefox" exwm-class-name) - (jao-exwm--center-float 900 600)) - ((member exwm-class-name '("mpv" "vlc")) - (jao-exwm--center-float 1200)))) - - (defvar jao-exwm-floating-classes '("mpv" "vlc")) - (setq jao-exwm-floating-classes nil) - - (defun jao-exwm--maybe-float () - (when (member exwm-class-name jao-exwm-floating-classes) - (when (not exwm--floating-frame) - (exwm-floating-toggle-floating)))) - - (add-hook 'exwm-floating-setup-hook #'jao-exwm--setup-float) - (add-hook 'exwm-manage-finish-hook #'jao-exwm--maybe-float) - - #+end_src -* Minibuffer - #+begin_src emacs-lisp - (setq jao-minibuffer-frame-width 271) - (add-hook 'exwm-workspace-switch-hook #'jao-minibuffer-refresh) - #+end_src -* System tray - #+begin_src emacs-lisp - (require 'exwm-systemtray) - (exwm-systemtray-enable) - - (defun jao-exwm--watch-tray (sym newval op where) - (setq jao-minibuffer-right-margin (* 2 (length newval))) - (jao-minibuffer-refresh)) - - (add-variable-watcher 'exwm-systemtray--list #'jao-exwm--watch-tray) - #+end_src -* Switch to buffer / app - #+begin_src emacs-lisp - (defvar-local jao-exwm--name nil) - (defvar jao-exwm--current-name nil) - - (defun jao-exwm--check-name (name) - (or (string= jao-exwm--name name) - (string= (buffer-name) name) - (string= exwm-class-name name) - (string= exwm-title name))) - - (defun jao-exwm-find-class-buffer (cln) - (if (jao-exwm--check-name cln) - (current-buffer) - (let* ((cur-buff (current-buffer)) - (bfs (seq-filter (lambda (b) - (and (not (eq b cur-buff)) - (with-current-buffer b - (jao-exwm--check-name cln)))) - (buffer-list)))) - (when (car bfs) (car (reverse bfs)))))) - - (defun jao-exwm-switch-to-class/title (cln) - (interactive) - (when cln - (if (jao-exwm--check-name cln) - (current-buffer) - (when-let ((b (jao-exwm-find-class-buffer cln))) - (pop-to-buffer b))))) - - (defun jao-exwm-switch-to-next-class () - (interactive) - (jao-exwm-switch-to-class/title exwm-class-name)) - - (defun jao-exwm-switch-to-next-x () - (interactive) - (let ((bfs (seq-filter (lambda (b) (buffer-local-value 'exwm-class-name b)) - (buffer-list (window-frame))))) - (when (car bfs) (switch-to-buffer (car (reverse bfs)))))) - - #+end_src -* App runner helpers - #+begin_src emacs-lisp - (defun jao-exwm-run (command) - (interactive - (list (read-shell-command "$ " - (if current-prefix-arg - (cons (concat " " (buffer-file-name)) 0) - "")))) - (setq jao-exwm--current-name nil) - (start-process-shell-command command nil command)) - - (defmacro jao-exwm-runner (&rest args) - `(lambda () (interactive) (start-process "" nil ,@args))) - - (defun jao-exwm-workspace (n) - (if jao-exwm--use-afio - (jao-afio-goto-nth n) - (exwm-workspace-switch-create n))) - - (defmacro jao-def-exwm-runner (name ws class &rest args) - `(defun ,name (&rest other-args) - (interactive) - ,@(when ws `((jao-exwm-workspace ,ws))) - (if (jao-exwm-switch-to-class/title ,class) - ,(cond ((equal ws 5) '(delete-other-windows)) - ((stringp (car args)) (cdr args)) - (t args)) - (setq jao-exwm--current-name ,class) - ,(if (stringp (car args)) - `(start-process-shell-command ,(car args) - "* exwm - console *" - (string-join (append (list ,@args) - other-args) - " ")) - args)))) - - (defmacro jao-def-exwm-toggler (name ws class &rest args) - (let ((toggler (intern (format "%s*" name))) - (arg (gensym))) - `(progn (jao-def-exwm-runner ,name ,ws ,class ,@args) - (defun ,toggler (,arg) - (interactive "P") - (if (and (not ,arg) (equal exwm-class-name ,class)) - (jao-afio--goto-main) - (,name)))))) - - (defun jao-exwm--send-str (str) - (dolist (k (string-to-list (kbd str))) - (exwm-input--fake-key k))) - - #+end_src -* Runners - #+begin_src emacs-lisp - (jao-def-exwm-runner jao-exwm-vlc 4 "VLC" "vlc") - - (jao-def-exwm-runner jao-exwm-slack 0 "Slack" "slack") - (jao-def-exwm-runner jao-exwm-signal 0 "Signal" "signal-desktop") - - (jao-def-exwm-runner jao-exwm-proton-bridge 0 "*proton-bridge*" "protonmail-bridge") - - ;; (jao-def-exwm-runner jao-exwm-htop 0 "htop-xterm" - ;; "xterm" "-title" "htop-xterm" "-e" "htop") - (jao-def-exwm-runner jao-exwm-htop 0 "htop" jao-term-htop) - - ;; (jao-def-exwm-runner jao-exwm-aptitude 0 "aptitude-xterm" - ;; "xterm" "-title" "aptitude-xterm" "-e" "aptitude") - (jao-def-exwm-runner jao-exwm-aptitude 0 "aptitude" jao-term-aptitude) - - (jao-def-exwm-runner jao-exwm-blueman 0 "Blueman-manager" "blueman-manager") - (jao-def-exwm-runner jao-exwm-ncmpcpp 0 "ncmpcpp" "xterm" "-e" "ncmpcpp" "-p" "6669") - (jao-def-exwm-runner jao-exwm-mpc 0 "*MPC-Status*" mpc) - - (jao-def-exwm-runner jao-exwm-proton-vpn 0 "*pvpn*" proton-vpn-status) - (jao-def-exwm-runner jao-exwm-enwc 0 "*ENWC*" enwc) - (jao-def-exwm-runner jao-exwm-bluetooth 0 "*Bluetooth*" bluetooth-list-devices) - (jao-def-exwm-runner jao-exwm-packages 0 "*Packages*" list-packages nil) - (jao-def-exwm-runner jao-exwm-proced 0 "*Proced*" proced) - - (jao-def-exwm-runner jao-exwm-open-with-zathura nil nil "zathura" (buffer-file-name)) - (jao-def-exwm-runner jao-exwm-open-with-mupdf nil nil "mupdf" (buffer-file-name)) - (jao-def-exwm-runner jao-exwm-xterm 0 nil "xterm") - - (jao-def-exwm-toggler jao-exwm-tidal 5 "tidal-hifi" "tidal-hifi") - (defalias 'jao-streaming-list #'jao-exwm-tidal) - - (defun jao-exwm-import-screen (&optional area) - (interactive "P") - (when (not (file-directory-p "/tmp/screenshot")) - (make-directory "/tmp/screenshot")) - (let ((c (format "import %s %s" - (if area "" "-window root") - "/tmp/screenshot/$(date +'%g%m%d-%H%M%S').png"))) - (start-process-shell-command "import" "* exwm - console *" c))) - - #+end_src -* Zathura support - #+begin_src emacs-lisp - (defun jao-zathura--buffer-p (b) - (string= "Zathura" (or (buffer-local-value 'exwm-class-name b) ""))) - - (defun jao-zathura--buffers () - (seq-filter #'jao-zathura--buffer-p (buffer-list))) - - (defun jao-zathura--file-info (b) - (with-current-buffer b - (jao-zathura-file-info (or exwm-title "")))) - - (defun jao-zathura-goto-page (page-no) - (jao-exwm--send-str (format "%sg" page-no))) - - (defun jao-zathura-open-doc (&optional file-name page-no height) - (interactive) - (let* ((file-name (expand-file-name (or file-name (buffer-file-name)))) - (buffer (seq-find `(lambda (b) - (string= ,file-name - (car (jao-zathura--file-info b)))) - (jao-zathura--buffers))) - (page-no (or page-no (jao-doc-view-current-page)))) - (if jao-exwm--use-afio (jao-afio--goto-docs) (jao-exwm--goto-docs)) - (if (not buffer) - (jao-exwm-run (if page-no - (format "zathura -P %s '%s'" page-no file-name) - (format "zathura '%s'" file-name))) - (pop-to-buffer buffer) - (when page-no (jao-zathura-goto-page page-no))) - (current-buffer))) - - (defun jao-exwm--zathura-setup () - (when (and (string= exwm-class-name "Zathura") - (not jao-doc-view--imenu-file)) - (let ((info (jao-zathura--file-info (current-buffer)))) - (jao-doc-view-session-mark (car info)) - (jao-doc-view-save-session) - (jao-doc-view--enable-imenu (car info) #'jao-zathura-goto-page)))) - - (add-hook 'exwm-update-title-hook #'jao-exwm--zathura-setup t) - - (defun jao-exwm-pdf-zathura-close-all () - (interactive) - (dolist (b (jao-zathura--buffers)) - (ignore-errors - (switch-to-buffer b) - (jao-exwm--send-str "q"))) - t) - - (defun jao-exwm-zathura-goto-org (&optional arg) - (interactive "P") - (when-let ((info (jao-zathura--file-info (current-buffer)))) - (when-let ((file (jao-org-pdf-to-org-file (car info)))) - (let ((newp (not (file-exists-p file)))) - (when (or arg newp) (org-store-link nil t)) - (find-file-other-window file) - (when newp - (jao-org-insert-doc-skeleton) - (org-insert-link)))))) - - (defun jao-exwm-zathura-goto-org* () - (interactive) - (jao-exwm-zathura-goto-org t)) - - (defun jao-exwm-org-store-zathura-link () - (when-let ((info (jao-zathura--file-info (current-buffer)))) - (let* ((file-name (car info)) - (page (cadr info)) - (desc (jao-doc-view-section-title page file-name))) - (jao-org-links-store-pdf-link file-name page desc)))) - - (defun jao-exwm-pdf-enable-zathura () - (interactive) - (add-hook 'kill-emacs-query-functions #'jao-exwm-pdf-zathura-close-all t) - (setq jao-org-open-pdf-fun #'jao-zathura-open-doc) - (setq jao-org-links-pdf-store-fun #'jao-exwm-org-store-zathura-link) - (setq jao-open-doc-fun #'jao-zathura-open-doc)) - - (defun jao-exwm-pdf-disable-zathura () - (interactive) - (define-key org-mode-map (kbd "C-c o") #'jao-org-org-goto-pdf) - (remove-hook 'kill-emacs-query-functions #'jao-exwm-pdf-zathura-close-all) - (setq jao-org-open-pdf-fun #'jao-find-or-open) - (setq jao-org-links-pdf-store-fun nil) - (setq jao-open-doc-fun #'jao-find-or-open)) - - (defun jao-exwm-zathura-goto-pdf () - (interactive) - (if jao-browse-doc-use-emacs-p - (jao-org-org-goto-pdf) - (when-let (pdf (jao-org-org-to-pdf-file)) - (jao-zathura-open-doc pdf)))) - - (with-eval-after-load "org" - (define-key org-mode-map (kbd "C-c o") #'jao-exwm-zathura-goto-pdf)) - - (when (not jao-browse-doc-use-emacs-p) - (jao-exwm-pdf-enable-zathura)) - - (defun jao-exwm-select-pdf () - (interactive) - (let ((b (read-buffer "Document: " nil t - (lambda (b) - (let ((b (cdr b))) - (or (jao-zathura--buffer-p b) - (member (buffer-local-value 'major-mode b) - '(pdf-view-mode doc-view-mode)))))))) - (jao-afio--goto-docs) - (pop-to-buffer b))) - - - (let ((viewers ["External viewers" - ("z" "open with zathura" jao-zathura-open-doc) - ("m" "open with mupdf" jao-exwm-open-with-mupdf)])) - (jao-transient-major-mode+ pdf-view viewers) - (jao-transient-major-mode+ doc-view viewers)) - - #+end_src -* Firefox support - #+begin_src emacs-lisp - (jao-def-exwm-toggler jao-exwm-firefox 5 "Firefox" "firefox") - - (defun jao-exwm-browse-with-firefox (&rest args) - (jao-exwm-firefox) - (apply #'browse-url-firefox args)) - - (setq browse-url-secondary-browser-function #'jao-exwm-browse-with-firefox) - - (defun jao-exwm-kill-firefox-url () - (interactive) - (when-let (b (jao-exwm-find-class-buffer "Firefox")) - (let ((cb (current-buffer))) - (switch-to-buffer b) - (jao-exwm--send-str "yy") - (prog1 (current-kill 1) - (switch-to-buffer cb))))) - - #+end_src -* Transients - #+begin_src emacs-lisp - (defun jao-exwm--floating-p () exwm--floating-frame) - (defun jao-exwm--m0-5 () (interactive nil exwm-mode) (exwm-floating-move 0 -5)) - (defun jao-exwm--m05 () (interactive nil exwm-mode) (exwm-floating-move 0 5)) - (defun jao-exwm--m-50 () (interactive nil exwm-mode) (exwm-floating-move -5 0)) - (defun jao-exwm--m50 () (interactive nil exwm-mode) (exwm-floating-move 5 0)) - (defun jao-exwm--e-5 () (interactive nil exwm-mode) (exwm-layout-enlarge-window -5)) - (defun jao-exwm--e5 () (interactive nil exwm-mode) (exwm-layout-enlarge-window 5)) - (defun jao-exwm--eh5 () (interactive nil exwm-mode) (exwm-layout-enlarge-window 5 t)) - (defun jao-exwm--eh-5 () (interactive nil exwm-mode) (exwm-layout-enlarge-window -5 t)) - (defun jao-exwm--tl () (interactive nil exwm-mode) (jao-exwm--float-to 20 20)) - (defun jao-exwm--tr () (interactive nil exwm-mode) (jao-exwm--float-to -20 20)) - (defun jao-exwm--bl () (interactive nil exwm-mode) (jao-exwm--float-to 20 -20)) - (defun jao-exwm--br () (interactive nil exwm-mode) (jao-exwm--float-to -20 -20)) - - (defun jao-exwm--def-center-float () - (interactive) - (exwm-floating-toggle-floating) - (jao-exwm--center-float 900 600)) - - (transient-define-prefix jao-transient-float () - "Operations on EXWM floating windows." - :transient-non-suffix 'transient--do-quit-one - [["Float" - ("f" "float" exwm-floating-toggle-floating) - ("F" "full" exwm-layout-toggle-fullscreen) - ("c" "center" jao-exwm--center-float :if jao-exwm--floating-p) - ("c" "float and resize" jao-exwm--def-center-float - :if-not jao-exwm--floating-p) - ("x" "hide" exwm-floating-hide :if jao-exwm--floating-p)] - ["Slide" :if jao-exwm--floating-p - ("k" "up" jao-exwm--m0-5 :transient t) - ("j" "down" jao-exwm--m05 :transient t) - ("h" "left" jao-exwm--m-50 :transient t) - ("l" "right" jao-exwm--m50 :transient t)] - ["Resize" :if jao-exwm--floating-p - ("K" "up" jao-exwm--e5 :transient t) - ("J" "down" jao-exwm--e-5 :transient t) - ("H" "left" jao-exwm--eh5 :transient t) - ("L" "right" jao-exwm--eh-5 :transient t)] - ["Nudge" :if jao-exwm--floating-p - ("t" "top-left" jao-exwm--tl) - ("T" "top-right" jao-exwm--tr) - ("b" "bottom-left" jao-exwm--bl) - ("B" "bottom-right " jao-exwm--br)]]) - - (defun jao-exwm--buffer () - (interactive) - (jao-buffer-same-mode 'exwm-mode nil 'exwm-workspace-switch-to-buffer)) - - #+end_src -* Keybindings - #+begin_src emacs-lisp - (define-key exwm-mode-map [?\C-q] #'exwm-input-send-next-key) - (define-key exwm-mode-map [?\s-f] #'jao-transient-float) - (define-key exwm-mode-map (kbd "C-c o") #'jao-exwm-zathura-goto-org) - (define-key exwm-mode-map (kbd "C-c O") #'jao-exwm-zathura-goto-org*) - (define-key exwm-mode-map (kbd "M-o") #'other-window) - (define-key exwm-mode-map (kbd "M-p") #'jao-prev-window) - - (setq - exwm-input-global-keys - '(([?\s-0] . jao-afio--goto-scratch) - ([?\s-1] . jao-afio--goto-main) - ([?\s-2] . jao-afio--goto-mail) - ([?\s-3] . jao-afio--goto-www) - ([?\s-4] . jao-afio--goto-docs) - ([?\s-A] . org-agenda-list) - ([?\s-a] . jao-first-window) - ([?\s-b] . jao-transient-org-blog) - ;; ([?\s-d] . jao-exwm-tidal*) - ([?\s-d] . jao-mpc-echo-current) - ([?\s-e] . jao-exwm-firefox*) - ([?\s-m] . jao-transient-media) - ;; ([?\s-O] . jao-transpose-windows) - ;; ([?\s-o] . jao-other-window) - ;; ([?\s-P] . jao-transpose-windows-prev) - ([?\s-O] . ace-swap-window) - ([?\s-o] . ace-window) - ([?\s-p] . jao-prev-window) - ([?\s-R] . app-launcher-run-app) - ([?\s-r] . jao-recoll-transient) - ([?\s-s] . jao-transient-streaming) - ([?\s-t] . vterm) - ([?\s-w] . jao-transient-utils) - ([?\s-z] . jao-transient-sleep) - ([XF86AudioMute] . jao-mixer-master-toggle) - ([XF86AudioPlay] . jao-player-toggle) - ([XF86AudioPause] . jao-player-toggle) - ([XF86AudioNext] . jao-player-next) - ([XF86AudioPrev] . jao-player-previous) - ([XF86AudioStop] . jao-player-stop) - ([XF86AudioRaiseVolume] . jao-mixer-master-up) - ([XF86AudioLowerVolume] . jao-mixer-master-down) - ([XF86MonBrightnessUp] . jao-bright-up) - ([XF86MonBrightnessDown] . jao-bright-down) - ([?\s-\`] . jao-exwm-switch-to-next-x) - ([s-tab] . jao-exwm-switch-to-next-class) - ([print] . jao-exwm-import-screen) - ([f5] . jao-weather) - ([f6] . jao-toggle-audio-applet) - ([f8] . jao-toggle-nm-applet) - ([f9] . jao-bright-show))) - - ;; (customize-set-variable 'exwm-input-global-keys exwm-input-global-keys) - - #+end_src diff --git a/gnus.org b/gnus.org deleted file mode 100644 index c0c0346..0000000 --- a/gnus.org +++ /dev/null @@ -1,780 +0,0 @@ -#+property: header-args :lexical t :tangle ~/.emacs.d/gnus.el :comments yes :results silent :shebang ";; -*- lexical-binding: t -*-" :tangle-mode (identity #o644) -#+title: Gnus - -* Feature switching vars - #+begin_src emacs-lisp - (defvar jao-gnus-use-local-imap nil) - (defvar jao-gnus-use-leafnode nil) - (defvar jao-gnus-use-gandi-imap nil) - (defvar jao-gnus-use-pm-imap nil) - (defvar jao-gnus-use-gmane nil) - (defvar jao-gnus-use-nnml nil) - (defvar jao-gnus-use-maildirs nil) - #+end_src -* Startup and kill - #+begin_src emacs-lisp - ;;;;; close gnus when closing emacs, but ask when exiting - (setq gnus-interactive-exit t) - - (defun jao-gnus-started-hook () - (add-hook 'before-kill-emacs-hook 'gnus-group-exit)) - - (add-hook 'gnus-started-hook 'jao-gnus-started-hook) - - (defun jao-gnus-after-exiting-hook () - (remove-hook 'before-kill-emacs-hook 'gnus-group-exit)) - - (add-hook 'gnus-after-exiting-gnus-hook 'jao-gnus-after-exiting-hook) - - ;; define a wrapper around the save-buffers-kill-emacs - ;; to run the new hook before: - (defadvice save-buffers-kill-emacs - (before my-save-buffers-kill-emacs activate) - "Install hook when emacs exits before emacs asks to save this and that." - (run-hooks 'before-kill-emacs-hook)) - #+end_src -* Directories - #+begin_src emacs-lisp - (defun jao-gnus-dir (dir) - (expand-file-name dir gnus-home-directory)) - - (setq smtpmail-queue-dir (jao-gnus-dir "Mail/queued-mail/")) - - (setq mail-source-directory (jao-gnus-dir "Mail/") - message-directory (jao-gnus-dir "Mail/")) - - (setq gnus-default-directory (expand-file-name "~") - gnus-startup-file (jao-gnus-dir "newsrc") - gnus-agent-directory (jao-gnus-dir "News/agent") - gnus-home-score-file (jao-gnus-dir "scores") - gnus-article-save-directory (jao-gnus-dir "saved/") - nntp-authinfo-file (jao-gnus-dir "authinfo") - nnmail-message-id-cache-file (jao-gnus-dir "nnmail-cache") - nndraft-directory (jao-gnus-dir "drafts") - nnrss-directory (jao-gnus-dir "rss")) - #+end_src -* Looks -*** Verbosity - #+begin_src emacs-lisp - (setq gnus-verbose 4) - #+end_src -*** Geometry - #+begin_src emacs-lisp - ;;; geometry: - (defvar jao-gnus-use-three-panes window-system) - (defvar jao-gnus-groups-width 50) - (defvar jao-gnus-wide-width 190) - - (setq gnus-use-trees nil - gnus-generate-tree-function 'gnus-generate-horizontal-tree - gnus-tree-minimize-window nil) - - (when jao-gnus-use-three-panes - (let ((side-bar '(vertical 1.0 - ("inbox.org" 0.4) - ("*Org Agenda*" 1.0) - ("*Calendar*" 8))) - (wide-len jao-gnus-wide-width) - (groups-len jao-gnus-groups-width) - (summary-len (- jao-gnus-wide-width jao-gnus-groups-width))) - (gnus-add-configuration - `(article - (horizontal 1.0 - (vertical ,groups-len (group 1.0)) - (vertical ,summary-len - (summary 0.25 point) - (article 1.0)) - ,side-bar))) - - (gnus-add-configuration - `(group (horizontal 1.0 (group ,wide-len point) ,side-bar))) - - (gnus-add-configuration - `(message (horizontal 1.0 (message ,wide-len point) ,side-bar))) - - (gnus-add-configuration - `(reply-yank (horizontal 1.0 (message ,wide-len point) ,side-bar))) - - (gnus-add-configuration - `(summary - (horizontal 1.0 - (vertical ,groups-len (group 1.0)) - (vertical ,summary-len (summary 1.0 point)) - ,side-bar))) - - (gnus-add-configuration - `(reply - (horizontal 1.0 - (message ,(- wide-len 100) point) - (article 100) - ,side-bar))))) - #+end_src -*** No blue icon - #+begin_src emacs-lisp - (advice-add 'gnus-mode-line-buffer-identification :override #'identity) - (setq gnus-mode-line-image-cache nil) - #+end_src -* Search - #+begin_src emacs-lisp - - (setq gnus-search-use-parsed-queries t - gnus-search-notmuch-raw-queries-p nil - gnus-permanently-visible-groups "^nnselect:.*" - gnus-search-ignored-newsgroups "nndraft.*\\|nnselect.*") - - (with-eval-after-load "gnus-search" - (add-to-list 'gnus-search-expandable-keys "list") - - (cl-defmethod gnus-search-transform-expression ((engine gnus-search-notmuch) - (expr (head list))) - (format "List:%s" (gnus-search-transform-expression engine (cdr expr))))) - - (defun jao-gnus--notmuch-engine (prefix config) - (let ((prefix (file-name-as-directory (expand-file-name prefix "~"))) - (config (expand-file-name config gnus-home-directory))) - `(gnus-search-engine gnus-search-notmuch - (remove-prefix ,prefix) - (config-file ,config)))) - - ;; (add-to-list 'gnus-parameters '("^nnselect:.*" (nnselect-rescan . t))) - - #+end_src -* News server - #+begin_src emacs-lisp - (setq gnus-select-method - (cond - (jao-gnus-use-leafnode - `(nntp "localhost" - ,(jao-gnus--notmuch-engine "var/news" "notmuch-news.config"))) - (jao-gnus-use-gmane '(nntp "news.gmane.io")) - (t '(nnnil "")))) - - (setq gnus-secondary-select-methods '()) - - (setq nnheader-read-timeout 0.02 - gnus-save-newsrc-file nil) ; .newsrc only needed by other newsreaders - #+end_src -* Local mail -*** nnmail params - #+begin_src emacs-lisp - (setq nnmail-treat-duplicates 'delete - nnmail-scan-directory-mail-source-once nil - nnmail-cache-accepted-message-ids t - nnmail-message-id-cache-length 100000 - nnmail-split-fancy-with-parent-ignore-groups nil - nnmail-use-long-file-names t - nnmail-crosspost t - nnmail-resplit-incoming t - nnmail-mail-splitting-decodes t - nnmail-split-methods 'nnmail-split-fancy) - #+end_src -*** nnml - #+begin_src emacs-lisp - (setq mail-sources - (when jao-gnus-use-nnml - (cons '(file :path "/var/mail/jao") - (when (eq jao-afio-mail-function 'gnus) - (mapcar (lambda (d) `(maildir :path ,(concat d "/"))) - (directory-files "~/var/mail" t "[^\\.]"))))) - gnus-message-archive-group nil - nnml-get-new-mail t - nnml-directory message-directory) - - (when jao-gnus-use-nnml - (add-to-list - 'gnus-secondary-select-methods - `(nnml "" ,(jao-gnus--notmuch-engine (jao-gnus-dir "Mail") "notmuch.config")))) - - (defvar jao-gnus-nnml-group-params - `(("nnml:\\(local\\|trash\\|spam\\)" - (auto-expire . t) - (total-expire . t) - (expiry-wait . 1) - (expiry-target . delete)) - ("nnml:jao\\..*" - (posting-style ("Bcc" "proton@jao.io") - ("Gcc" "nnml:jao.trove")) - (jao-gnus--trash-group "nnml:trash") - (jao-gnus--spam-group "nnml:spam") - (jao-gnus--archiving-group "nnml:jao.trove")) - ("nnml:bigml\\..*" - (gcc-self . nil) - (auto-expire . t) - (total-expire . t) - (expiry-wait . 3) - (expiry-target . delete) - (posting-style (address "jao@bigml.com")) - (jao-gnus--trash-group "nnml:trash") - (jao-gnus--spam-group "nnml:spam") - (jao-gnus--archiving-group "nnml:bigml.trove")) - ("nnml:bigml\\.\\(inbox\\|support\\)" - (gcc-self . t) - (auto-expire . t) - (total-expire . t) - (expiry-wait . 7) - (expiry-target . "nnml:bigml.trove")) - ("nnml:bigml\\.trove" - (auto-expire . t) - (total-expire . t) - (expiry-target . delete) - (expiry-wait . 365)) - ("nnml:jao\\.drivel" - (auto-expire . t) - (total-expire . t) - (expiry-wait . 3) - (expiry-target . delete)) - ("nnml:feeds\\.\\(.*\\)" - (auto-expire . t) - (total-expire . t) - (expiry-wait . 7) - (expiry-target . delete) - (comment . "feeds.\\1") - (jao-gnus--archiving-group "nnml:feeds.trove")) - ("^nnml:feeds\\.\\(news\\)$" (expiry-wait . 2)) - ("nnml:feeds\\.\\(trove\\|lobsters\\|philosophy\\)" - (auto-expire . nil) - (total-expire . nil)) - ("nnml:feeds\\.fun" - (mm-html-inhibit-images nil) - (mm-html-blocked-images nil)))) - - (when jao-gnus-use-nnml - (dolist (p jao-gnus-nnml-group-params) - (add-to-list 'gnus-parameters p t))) - - #+end_src -*** leafnode - #+begin_src emacs-lisp - (defvar jao-gnus-image-groups '("xkcd")) - - (defvar jao-gnus-leafnode-group-params - `((,(format "gwene\\..*%s.*" (regexp-opt jao-gnus-image-groups)) - (mm-html-inhibit-images nil) - (mm-html-blocked-images nil)) - ("\\(gmane\\|gwene\\)\\..*" - (jao-gnus--archiving-group "nnml:feeds.trove") - (posting-style (address "jao@gnu.org"))))) - - (when jao-gnus-use-leafnode - (dolist (p jao-gnus-leafnode-group-params) - (add-to-list 'gnus-parameters p t))) - - #+end_src -*** maildirs - #+begin_src emacs-lisp - (when jao-gnus-use-maildirs - (add-to-list - 'gnus-secondary-select-methods - `(nnmaildir "mail" - (directory "~/.nnmaildirs") - ,(jao-gnus--notmuch-engine "~/var/mail/" - "~/.notmuch-config")))) - - #+end_src -* IMAP servers - #+begin_src emacs-lisp - (setq nnimap-quirks nil) - - (when jao-gnus-use-local-imap - (add-to-list 'gnus-secondary-select-methods - `(nnimap "" (nnimap-address "localhost")))) - - (when jao-gnus-use-pm-imap - (add-to-list 'gnus-secondary-select-methods - '(nnimap "pm" - (nnimap-address "127.0.0.1") - (nnimap-stream network) - (nnimap-server-port 1143)))) - - (when jao-gnus-use-gandi-imap - (add-to-list 'gnus-secondary-select-methods - '(nnimap "gandi" (nnimap-address "mail.gandi.net")))) - #+end_src -* Demon and notifications - #+begin_src emacs-lisp - (setq mail-user-agent 'gnus-user-agent) - - ;; synchronicity - (setq gnus-asynchronous t) - ;;; prefetch as many articles as possible - (setq gnus-use-article-prefetch nil) - - (setq gnus-save-killed-list nil) - (setq gnus-check-new-newsgroups nil) - - (defvar jao-gnus-tracked-groups - (let ((feeds (thread-first - (directory-files mail-source-directory nil "feeds") - (seq-difference '("feeds.trove"))))) - `(("nnml:bigml.inbox" "B" jao-themes-f00) - ("nnml:bigml.bugs" "b" jao-themes-error) - ("nnml:bigml.support" "S" default) - ("nnml:jao.inbox" "I" jao-themes-f01) - ("nnml:bigml.[^ibs]" "W" jao-themes-dimm) - ("nnml:jao.[^ist]" "J" jao-themes-dimm) - (,(format "^nnml:%s" (regexp-opt feeds)) "F" jao-themes-dimm) - ("^gmane" "G" jao-themes-dimm) - ("nnml:local" "l" jao-themes-dimm)))) - - (defun jao-gnus--unread-counts () - (seq-reduce (lambda (r g) - (let ((n (gnus-group-unread (car g)))) - (if (and (numberp n) (> n 0)) - (prog1 (cons (cons (car g) n) r) - (gnus-message 7 "%s in %s" n g)) - r))) - gnus-newsrc-alist - ())) - - (defun jao-gnus--unread-label (counts rx label face) - (let ((n (seq-reduce (lambda (n c) - (if (string-match-p rx (car c)) (+ n (cdr c)) n)) - counts - 0))) - (when (> n 0) `(:propertize ,(format "%s%d " label n) face ,face)))) - - (defvar jao-gnus--notify-strs ()) - - (defun jao-gnus--notify-strs () - (let ((counts (jao-gnus--unread-counts))) - (seq-filter #'identity - (seq-map (lambda (args) - (apply 'jao-gnus--unread-label counts args)) - jao-gnus-tracked-groups)))) - - (defun jao-gnus--notify () - (setq jao-gnus--notify-strs (jao-gnus--notify-strs)) - (save-window-excursion (jao-minibuffer-refresh))) - - (defun jao-gnus-scan () - (interactive) - (let ((inhibit-message t)) - (gnus-demon-scan-mail) - (shell-command "index-mail.sh") - (save-window-excursion ) - (jao-gnus--notify))) - - (require 'gnus-demon) - (gnus-demon-add-handler 'gnus-demon-scan-news 5 1) - ;; (gnus-demon-remove-handler 'jao-gnus-scan) - - (add-hook 'gnus-started-hook #'jao-gnus-scan) - (add-hook 'gnus-summary-exit-hook #'jao-gnus--notify) - (add-hook 'gnus-summary-exit-hook #'org-agenda-list) - (add-hook 'gnus-after-getting-new-news-hook #'jao-gnus-scan) - - (with-eval-after-load "jao-minibuffer" - (jao-minibuffer-add-variable 'jao-gnus--notify-strs -20)) - - #+end_src -* Delayed messages - #+BEGIN_SRC emacs-lisp - ;;; delayed messages (C-cC-j in message buffer) - (require 'gnus-util) - (gnus-delay-initialize) - (setq gnus-delay-default-delay "3h") - ;;; so that the Date is set when the message is sent, not when it's - ;;; delayed - (eval-after-load "message" - '(setq message-draft-headers (remove 'Date message-draft-headers))) - #+END_SRC -* Groups buffer - #+begin_src emacs-lisp - ;; (setq gnus-group-line-format " %m%S%p%P:%~(pad-right 35)c %3y %B\n") - ;; (setq gnus-group-line-format " %m%S%p%3y%P%* %~(pad-right 30)C %B\n") - (setq gnus-group-line-format " %m%S%p%3y%P%* %~(pad-right 30)G %B\n") - (setq gnus-topic-line-format "%i[ %(%{%n%}%) -- %A ]%v\n") - (setq gnus-group-uncollapsed-levels 2) - (setq gnus-auto-select-subject 'unread) - (setq-default gnus-large-newsgroup 2000) - - (add-hook 'gnus-select-group-hook 'gnus-group-set-timestamp) - (add-hook 'gnus-group-mode-hook 'gnus-topic-mode) - - (defvar jao-gnus--expire-every 50) - (defvar jao-gnus--get-count (1+ jao-gnus--expire-every)) - - (defun jao-gnus-get-new-news (&optional arg) - (interactive "p") - (when (and jao-gnus--expire-every - (> jao-gnus--get-count jao-gnus--expire-every)) - (when jao-gnus-use-pm-imap (gnus-group-catchup "nnimap:pm/spam" t)) - (gnus-group-expire-all-groups) - (setq jao-gnus--get-count 0)) - (setq jao-gnus--get-count (1+ jao-gnus--get-count)) - (gnus-group-get-new-news (max (if (= 1 jao-gnus--get-count) 4 3) - (or arg 0)))) - - ;; (define-key gnus-group-mode-map "g" 'jao-gnus-get-new-news) - - (defun jao-gnus-restart-servers () - (interactive) - (message "Restarting all servers...") - (gnus-group-enter-server-mode) - (gnus-server-close-all-servers) - (gnus-server-open-all-servers) - (gnus-server-exit) - (message "Restarting all servers... done")) - - (define-key gnus-group-mode-map "\C-x\C-s" #'gnus-group-save-newsrc) - - (jao-transient-major-mode gnus-group - ["Search" - ("zc" "consult search" consult-notmuch) - ("zf" "consult folder search" jao-consult-notmuch-folder) - ("g" "gnus search" gnus-group-read-ephemeral-search-group)]) - - (defun jao-gnus--first-group () - (when (derived-mode-p 'gnus-group-mode) - (gnus-group-first-unread-group))) - - (with-eval-after-load "jao-afio" - (add-hook 'jao-afio-switch-hook #'jao-gnus--first-group)) - #+end_src -* Summary buffer -*** Configuration - #+begin_src emacs-lisp - (setq gnus-face-1 'jao-gnus-face-tree) - - (setq gnus-show-threads t - gnus-thread-hide-subtree t - gnus-summary-make-false-root 'adopt - gnus-summary-gather-subject-limit 120 - gnus-sort-gathered-threads-function 'gnus-thread-sort-by-date - gnus-thread-sort-functions '(gnus-thread-sort-by-date)) - - (setq gnus-summary-ignore-duplicates t - gnus-suppress-duplicates t - ;; gnus-summary-ignored-from-addresses jao-mails-regexp - gnus-process-mark-toggle t - gnus-refer-thread-use-search t - gnus-auto-select-next 'almost-quietly) - #+end_src -*** Search - #+begin_src emacs-lisp - (defun jao-gnus--maybe-reselect (&rest _i) - (when (string-match-p "^nnselect" (or (gnus-group-name-at-point) "")) - (save-excursion (gnus-group-get-new-news-this-group)))) - - (advice-add 'gnus-group-select-group :before #'jao-gnus--maybe-reselect) - #+end_src -*** Summary line - #+begin_src emacs-lisp - (setq gnus-not-empty-thread-mark ?↓) ; ↓) ?· - (setq jao-gnus--summary-line-fmt - (concat "%%U %%*%%R %%uj " - "[ %%~(max-right 23)~(pad-right 23)uf " - " %%I%%~(pad-left 2)t ] %%s" - "%%-%s=" - "%%~(max-right 8)~(pad-left 8)&user-date;" - "\n")) - - (defun jao-gnus--set-summary-line (&optional w) - (let* ((d (if jao-gnus-use-three-panes (+ jao-gnus-groups-width 11) 12)) - (w (- (or w (window-width)) d))) - (setq gnus-summary-line-format (format jao-gnus--summary-line-fmt w)))) - - (add-hook 'gnus-select-group-hook 'jao-gnus--set-summary-line) - ;; (jao-gnus--set-summary-line 187) - - (add-to-list 'nnmail-extra-headers 'Cc) - (add-to-list 'nnmail-extra-headers 'BCc) - (add-to-list 'gnus-extra-headers 'Cc) - (add-to-list 'gnus-extra-headers 'BCc) - - (defun gnus-user-format-function-j (headers) - (let ((to (gnus-extra-header 'To headers))) - (if (string-match jao-mails-regexp to) - (if (string-match "," to) "¬" "»") ;; "~" "=") - (if (or (string-match jao-mails-regexp - (gnus-extra-header 'Cc headers)) - (string-match jao-mails-regexp - (gnus-extra-header 'BCc headers))) - "¬" ;; "~" - " ")))) - - (defconst jao-gnus--news-rx - (concat (regexp-opt '("ElDiaro.es " - "ElDiario.es - ElDiario.es: " - "The Guardian: " - "Aeon | a world of ideas: " - ": ")) - "\\|unofficial mirror of [^:]+: ")) - - (defun gnus-user-format-function-f (headers) - (let* ((from (gnus-header-from headers)) - (from (gnus-summary-extract-address-component from))) - (replace-regexp-in-string jao-gnus--news-rx "" from))) - - (setq gnus-user-date-format-alist - '(((gnus-seconds-today) . "%H:%M") - ((+ 86400 (gnus-seconds-today)) . "'%H:%M") - ;; (604800 . "%a %H:%M") ;; that's one week - ((gnus-seconds-month) . "%a %d") - ((gnus-seconds-year) . "%b %d") - (t . "%b '%y"))) - - #+end_src -*** Moving messages around - #+BEGIN_SRC emacs-lisp - (defvar-local jao-gnus--spam-group nil) - (defvar-local jao-gnus--archiving-group nil) - (defvar-local jao-gnus--archive-as-copy-p nil) - - (defvar jao-gnus--last-move nil) - (defun jao-gnus-move-hook (a headers c to d) - (setq jao-gnus--last-move (cons to (mail-header-id headers)))) - (defun jao-gnus-goto-last-moved () - (interactive) - (when jao-gnus--last-move - (when (eq major-mode 'gnus-summary-mode) (gnus-summary-exit)) - (gnus-group-goto-group (car jao-gnus--last-move)) - (gnus-group-select-group) - (gnus-summary-goto-article (cdr jao-gnus--last-move) nil t))) - (add-hook 'gnus-summary-article-move-hook 'jao-gnus-move-hook) - - (defun jao-gnus-archive (follow) - (interactive "P") - (if jao-gnus--archiving-group - (progn - (if (or jao-gnus--archive-as-copy-p - (not (gnus-check-backend-function - 'request-move-article gnus-newsgroup-name))) - (gnus-summary-copy-article nil jao-gnus--archiving-group) - (gnus-summary-move-article nil jao-gnus--archiving-group)) - (when follow (jao-gnus-goto-last-moved))) - (gnus-summary-mark-as-read) - (gnus-summary-delete-article))) - - (defun jao-gnus-archive-tickingly () - (interactive) - (gnus-summary-tick-article) - (jao-gnus-archive) - (when jao-gnus--archive-as-copy-p - (gnus-summary-mark-as-read))) - - (defun jao-gnus-show-tickled () - (interactive) - (gnus-summary-limit-to-marks "!")) - - (make-variable-buffer-local - (defvar jao-gnus--trash-group nil)) - - (defun jao-gnus-trash () - (interactive) - (gnus-summary-mark-as-read) - (if jao-gnus--trash-group - (gnus-summary-move-article nil jao-gnus--trash-group) - (gnus-summary-delete-article))) - - (defun jao-gnus-move-to-spam () - (interactive) - (gnus-summary-mark-as-read) - (gnus-summary-move-article nil jao-gnus--spam-group)) - - (define-key gnus-summary-mode-map "Ba" 'jao-gnus-archive) - (define-key gnus-summary-mode-map "BA" 'jao-gnus-archive-tickingly) - (define-key gnus-summary-mode-map "Bl" 'jao-gnus-goto-last-moved) - - (define-key gnus-summary-mode-map (kbd "B DEL") 'jao-gnus-trash) - (define-key gnus-summary-mode-map (kbd "B ") 'jao-gnus-trash) - (define-key gnus-summary-mode-map "Bs" 'jao-gnus-move-to-spam) - (define-key gnus-summary-mode-map "/!" 'jao-gnus-show-tickled) - (define-key gnus-summary-mode-map [f7] 'gnus-summary-force-verify-and-decrypt) - #+END_SRC -*** Saving emails - #+BEGIN_SRC emacs-lisp - (setq gnus-default-article-saver 'gnus-summary-save-article-mail) - (defvar jao-gnus-file-save-directory (expand-file-name "~/tmp")) - (defun jao-gnus-file-save (newsgroup headers &optional last-file) - (expand-file-name (format "%s.eml" (mail-header-subject headers)) - jao-gnus-file-save-directory)) - (setq gnus-mail-save-name 'jao-gnus-file-save) - #+END_SRC -*** arXiv capture - #+begin_src emacs-lisp - (use-package org-capture - :config - (add-to-list 'org-capture-templates - '("X" "arXiv" entry (file "notes/physics/arxiv.org") - "* %:subject\n %i" :immediate-finish t) - t) - (org-capture-upgrade-templates org-capture-templates)) - - (defun jao-gnus-arXiv-capture () - (interactive) - (gnus-summary-select-article-buffer) - (gnus-article-goto-part 0) - (forward-paragraph) - (setq-local transient-mark-mode 'lambda) - (set-mark (point)) - (goto-char (point-max)) - (org-capture nil "X")) - #+end_src -* Article buffer -*** Config, headers - #+begin_src emacs-lisp - (setq mail-source-delete-incoming t) - (setq gnus-gcc-mark-as-read t) - (setq gnus-treat-display-smileys nil) - (setq gnus-treat-fill-long-lines nil) - (setq gnus-treat-fill-article nil) - (setq gnus-treat-fold-headers nil) - (setq gnus-treat-strip-leading-blank-lines t) - (setq gnus-article-auto-eval-lisp-snippets nil) - (setq gnus-posting-styles '((".*" (name "Jose A. Ortega Ruiz")))) - (setq gnus-single-article-buffer nil) - (setq gnus-article-update-lapsed-header 60) - (setq gnus-article-update-date-headers 60) - - (eval-after-load "gnus-art" - '(setq - gnus-visible-headers - (concat - gnus-visible-headers - "\\|^List-[iI][Dd]:\\|^X-Newsreader:\\|^X-Mailer:\\|User-Agent:\\|X-User-Agent:"))) - #+end_src -*** HTML email (washing, images) - #+begin_src emacs-lisp - (setq gnus-button-url 'browse-url-generic - gnus-inhibit-images t - mm-discouraged-alternatives nil ;; '("text/html" "text/richtext") - mm-inline-large-images 'resize) - - ;; no html in From: (washing articles from arxiv feeds) and cleaning up - ;; addresses - (require 'shr) - (defvar jao-gnus--from-rx - (concat "From: \\\"?\\( " jao-gnus--news-rx "\\)")) - - (defun jao-gnus-remove-anchors () - (save-excursion - (goto-char (point-min)) - (cond ((re-search-forward jao-gnus--from-rx nil t) - (replace-match "" nil nil nil 1)) - ((re-search-forward "[gq].+ updates on arXiv.org: " nil t) - (replace-match "") - (let ((begin (point))) - (when (re-search-forward "^\\(To\\|Subject\\):" nil t) - (beginning-of-line) - (let ((shr-width 10000)) - (shr-render-region begin (1- (point)))))))))) - - (add-hook 'gnus-part-display-hook 'jao-gnus-remove-anchors) - - (defvar-local jao-gnus--images nil) - - (defun jao-gnus--init-images () - (with-current-buffer gnus-article-buffer - (setq jao-gnus--images nil))) - - (add-hook 'gnus-select-article-hook #'jao-gnus--init-images) - - (defun jao-gnus-show-images () - (interactive) - (save-window-excursion - (gnus-summary-select-article-buffer) - (save-excursion - (setq jao-gnus--images (not jao-gnus--images)) - (if jao-gnus--images - (gnus-article-show-images) - (gnus-article-remove-images))))) - #+end_src -*** Follow links and enclosures - #+begin_src emacs-lisp - (defun jao-gnus-follow-link (&optional external) - (interactive "P") - (when (eq major-mode 'gnus-summary-mode) - (gnus-summary-select-article-buffer)) - (save-excursion - (goto-char (point-min)) - (when (or (search-forward-regexp "^Via: h" nil t) - (search-forward-regexp "^URL: h" nil t) - (and (search-forward-regexp "^Link$" nil t) - (not (beginning-of-line)))) - (if external - (jao-browse-with-external-browser) - (browse-url (jao-url-around-point)))))) - - (defun jao-gnus-open-enclosure () - (interactive) - (save-window-excursion - (gnus-summary-select-article-buffer) - (save-excursion - (goto-char (point-min)) - (when (search-forward "Enclosure:") - (forward-char 2) - (when-let ((url (thing-at-point-url-at-point))) - (jao-browse-add-url-to-mpc url)))))) - #+end_src -* Add-ons -*** notmuch integration - #+begin_src emacs-lisp - (require 'jao-notmuch-gnus) - - (jao-notmuch-gnus-auto-tag) - - (defun jao-gnus-toggle-todo () - (interactive) - (jao-notmuch-gnus-toggle-tags '("todo"))) - - (define-key gnus-summary-mode-map (kbd "C-c T") #'jao-notmuch-gnus-tag-message) - (define-key gnus-summary-mode-map (kbd "C-c t") #'jao-notmuch-gnus-show-tags) - (define-key gnus-summary-mode-map (kbd "C-c C-t") #'jao-gnus-toggle-todo) - - (with-eval-after-load "notmuch-show" - (define-key gnus-group-mode-map "z" #'jao-gnus-consult-notmuch) - (define-key gnus-group-mode-map "Z" #'notmuch) - (define-key notmuch-show-mode-map - (kbd "C-c C-c") - #'jao-notmuch-gnus-goto-message)) - - (defun jao-gnus-notmuch-export (query) - (notmuch-tree query nil nil "* consult-notmuch results *")) - - (setq consult-notmuch-export-function #'jao-gnus-notmuch-export) - - (with-eval-after-load "notmuch-tree" - (define-key notmuch-tree-mode-map - (kbd "C-") - #'jao-notmuch-gnus-goto-message)) - - #+end_src -*** gnus-icalendar - #+begin_src emacs-lisp - (require 'ol-gnus) - (use-package gnus-icalendar - :demand t - :init (setq gnus-icalendar-org-capture-file - (expand-file-name "inbox.org" org-directory) - gnus-icalendar-org-capture-headline '("Appointments")) - :config (gnus-icalendar-org-setup)) - #+end_src -*** bbdb - #+begin_src emacs-lisp - (with-eval-after-load "bbdb" - (bbdb-initialize 'gnus 'message 'pgp 'mail) - (bbdb-mua-auto-update-init 'gnus) - (with-eval-after-load "gnus-sum" - (define-key gnus-summary-mode-map ":" 'bbdb-mua-annotate-sender) - (define-key gnus-summary-mode-map ";" 'bbdb-mua-annotate-recipients))) - #+end_src -*** randomsig - #+begin_src emacs-lisp - (with-eval-after-load "randomsig" - (with-eval-after-load "gnus-sum" - (define-key gnus-summary-save-map "-" 'gnus/randomsig-summary-read-sig))) - #+end_src -* Keyboard shortcuts - #+begin_src emacs-lisp - (define-key gnus-article-mode-map "i" 'jao-gnus-show-images) - (define-key gnus-summary-mode-map "i" 'jao-gnus-show-images) - (define-key gnus-article-mode-map "\M-g" 'jao-gnus-follow-link) - (define-key gnus-summary-mode-map "\M-g" 'jao-gnus-follow-link) - (define-key gnus-summary-mode-map "v" 'scroll-other-window) - (define-key gnus-summary-mode-map "V" 'scroll-other-window-down) - (define-key gnus-summary-mode-map "X" 'jao-gnus-arXiv-capture) - (define-key gnus-summary-mode-map "e" 'jao-gnus-open-enclosure) - (define-key gnus-summary-mode-map "\C-l" nil) - - #+end_src diff --git a/init.el b/init.el new file mode 100644 index 0000000..5c41129 --- /dev/null +++ b/init.el @@ -0,0 +1,3006 @@ +;; -*- lexical-binding: t; -*- + +;;; Initialisation +;;;; Bootstrap and use package +(defvar jao-emacs-dir (expand-file-name "~/etc/emacs")) + +(setq package-user-dir + (expand-file-name (format "~/.emacs.d/elpa.%s" emacs-major-version)) + package-check-signature 'allow-unsigned) + +(require 'package) +(dolist (a '(("melpa" . "https://melpa.org/packages/") + ("gnu-devel" . "https://elpa.gnu.org/devel/"))) + (add-to-list 'package-archives a t)) +(setf (alist-get "gnu" package-archives nil t #'string=) nil) + +(package-initialize) + +(unless (package-installed-p 'use-package) + (package-refresh-contents) + (package-install 'use-package)) +(require 'use-package) + +(use-package gnu-elpa-keyring-update :ensure t) + +;;;; .elc vs .el loading + +(setq load-prefer-newer t) +(setq comp-async-report-warnings-errors nil + warning-suppress-types '((comp))) + +;;; Paths +(defvar jao-local-lisp-dir "~/lib/elisp" + "Directory for external elisp libraries and repos") + +(defvar jao-data-dir (expand-file-name "data" jao-emacs-dir) + "Directory containing static data, such as images.") + +(defun jao-data-file (file) (expand-file-name file jao-data-dir)) + +(defvar jao-org-dir (expand-file-name "~/org")) + +(defvar jao-sink-dir + (file-name-as-directory (expand-file-name "~/doc/sink")) + "Directory used for downloads and such.") + +(defvar jao-site-dir (expand-file-name "site" jao-emacs-dir)) +(defun jao-site-el (basename &optional gpg) + (expand-file-name (concat basename ".el" (when gpg ".gpg")) jao-site-dir)) + +(defun jao-load-site-el (basename &optional gpg) + (let ((lf (jao-site-el basename gpg))) + (if (file-exists-p lf) + (load lf) + (message "Attempted to load non existing %s" lf)))) + +(defun jao-exec-path (file) + (let ((fn (expand-file-name file))) + (add-to-list 'exec-path fn nil) + (setenv "PATH" (concat fn ":" (getenv "PATH"))))) + +(defun jao-load-path (subdir) + "Add to load path a subdir of `jao-local-lisp-dir'" + (let ((path (expand-file-name subdir jao-local-lisp-dir))) + (when (file-directory-p path) (add-to-list 'load-path path)))) + +;;;; load and info path initialisation +(add-to-list 'load-path jao-site-dir) +(add-to-list 'load-path jao-local-lisp-dir) +(add-to-list 'load-path (expand-file-name "custom" jao-emacs-dir)) +(add-to-list 'load-path "/usr/local/share/emacs/site-lisp/") + +(let ((libd (expand-file-name "lib" jao-emacs-dir))) + (add-to-list 'load-path libd) + (dolist (f (directory-files libd t "^[^.]+$")) + (when (file-directory-p f) (add-to-list 'load-path f)))) + +(defvar jao-info-dir (expand-file-name "~/doc/info")) +(require 'info) +(add-to-list 'Info-directory-list jao-info-dir) + +;;;; custom location of custom.el and co. +(setq custom-file (jao-site-el "custom")) +;; (load custom-file) +(setq custom-unlispify-tag-names nil) +(setq custom-buffer-done-kill t) +(setq custom-raised-buttons nil) + +;;;; preamble (pre.el) +(jao-load-site-el "pre") + +;;;; session and history +(setq backup-directory-alist (quote (("." . "~/.emacs.d/backups")))) +(setq delete-old-versions t + kept-new-versions 3 + kept-old-versions 2) + +(require 'saveplace) +(setq save-place-file (expand-file-name "~/.emacs.d/cache/places")) +(save-place-mode 1) + +(setq recentf-save-file (expand-file-name "~/.emacs.d/cache/recentf") + recentf-max-saved-items 2000 + recentf-exclude '("/home/jao/\\.emacs\\.d/elpa.*/.*" + ".*/.git/COMMIT_EDITMSG")) +(require 'recentf) +(recentf-mode 1) + +(setq savehist-file (expand-file-name "~/.emacs.d/cache/history")) +(require 'savehist) +(savehist-mode t) +(setq savehist-additional-variables + '(kill-ring search-ring regexp-search-ring) + savehist-ignored-variables + '(ido-file-history)) + +;;;; yes/no, bell, startup message + +(if (version< emacs-version "28") + (fset 'yes-or-no-p 'y-or-n-p) + (setq use-short-answers t)) +(setq inhibit-startup-message t) +(setq visible-bell t) + +;;;; server +(setenv "EDITOR" "emacsclient") +;; (unless (daemonp) (server-start)) + +;;; System Utilities +;;;; tramp +(setq tramp-mode nil) + +;;;; sleep/awake +(use-package jao-sleep) +(jao-sleep-dbus-register) + +;;;; process runners +(use-package jao-shell + :demand t + :config (jao-shell-def-exec jao-trayer "trayer.sh") + :bind (("s-r" . jao-shell-exec))) + +;;;; app launcher +(jao-load-path "app-launcher") +(use-package app-launcher + :bind (("s-R" . app-launcher-run-app))) + +;;;; brightness control +(jao-shell-def-exec jao-bright-set-up "brightnessctl" "-q" "s" "5%+") +(jao-shell-def-exec jao-bright-set-down "brightnessctl" "-q" "s" "5%-") + +(defun jao-bright-show () + (interactive) + (message "%s" (thread-first (jao-shell-string "brightnessctl") + (split-string "\n") + (cadr) + (string-trim)))) + +(defun jao-bright-up () + (interactive) + (jao-shell-string "brightnessctl -q s 5%%+") + (jao-bright-show)) + +(defun jao-bright-down () + (interactive) + (jao-shell-string "brightnessctl -q s 5%%-") + (jao-bright-show)) + +;;;; keyboard +(use-package repeat + :config (setq repeat-echo-function #'repeat-echo-mode-line)) +(when (> emacs-major-version 27) (repeat-mode)) + +(defun jao-kb-toggle (&optional lyt) + (interactive) + (shell-command-to-string (or lyt + (if (jao-kb-toggled-p) + "setxkbmap us" + "setxkbmap us -variant intl")))) + +(defun jao-kb-toggled-p () + (not (string-empty-p + (shell-command-to-string "setxkbmap -query|grep variant")))) + +(set-keyboard-coding-system 'latin-1) +(set-language-environment "UTF-8") +;; must be set after current-language-environment +(customize-set-variable 'default-input-method "catalan-prefix") +;; http://mbork.pl/2022-03-07_Transient_input_method +(customize-set-variable 'default-transient-input-method "TeX") + +(defun jao--set-kb-system (frame) + (select-frame frame) + (set-keyboard-coding-system 'latin-1) + t) +(add-to-list 'after-make-frame-functions 'jao--set-kb-system) + +(setq echo-keystrokes 1 + suggest-key-bindings nil) + +;;;; transient +(use-package transient + :init (setq transient-show-popup t) ;; 2.0 + :demand t + :config + (transient-bind-q-to-quit)) + +(defmacro jao-transient-major-mode (mode &rest suffix) + (declare (indent defun)) + (let ((mode (intern (format "%s-mode" mode))) + (mmap (intern (format "%s-mode-map" mode))) + (name (intern (format "jao-transient-%s" mode)))) + `(progn + (transient-define-prefix ,name () + ,(format "Transient ops for %s" mode) + [,(format "Operations for %s" mode) :if-derived ',mode ,@suffix]) + (define-key ,mmap (kbd "s-SPC") #',name)))) + +(defmacro jao-transient-major-mode+1 (mode suffix) + (declare (indent defun)) + (let ((name (intern (format "jao-transient-%s" mode)))) + (if (fboundp name) + `(transient-append-suffix ',name '(0 -1) ,suffix) + `(jao-transient-major-mode ,mode ,suffix)))) + +(defmacro jao-transient-major-mode+ (mode &rest suffixes) + (declare (indent defun)) + `(progn ,@(mapcar (lambda (s) `(jao-transient-major-mode+1 ,mode ,s)) + suffixes))) + +;;;; disk monitoring +(when (featurep 'multisession) + (use-package jao-dirmon)) + +;;; Crypto +;;;; PGP, EPG, passwords +(setq epg-pinentry-mode 'loopback) +(setq auth-source-debug nil) + +(require 'auth-source) +(add-to-list 'auth-source-protocols '(local "local")) +(setq auth-sources '("~/.emacs.d/authinfo.gpg" "~/.netrc")) + +(use-package epa-file + :init (setq epa-file-cache-passphrase-for-symmetric-encryption t) + :config (epa-file-enable)) +(require 'epa-file) + +(defun jao--get-user/password (h) + (let ((item (car (auth-source-search :type 'netrc :host h :max 1)))) + (when item + (let ((user (plist-get item :user)) + (pwd (plist-get item :secret))) + (list user (when pwd (funcall pwd))))))) + +;;;; pass +(use-package password-store :ensure t + :bind (("C-c p" . jao-transient-password))) + +(transient-define-prefix jao-transient-password () + [[("c" "copy secret" password-store-copy) + ("C" "copy field" password-store-copy-field)] + [("i" "insert entry" password-store-insert) + ("e" "edit entry" password-store-edit) + ("g" "generate password" password-store-generate)] + [("d" "delete entry" password-store-remove) + ("r" "rename entry" password-store-rename)]]) + +;;;; pinentry +(use-package pinentry :ensure t) +(pinentry-start) + +;;; Fonts and color themes +;;;; widgets +(setq widget-image-enable nil + widget-link-prefix "" + widget-link-suffix "" + widget-button-prefix " " + widget-button-suffix " " + widget-push-button-prefix "" + widget-push-button-suffix "") + +;;;; fontsets +(defun jao--set-fontsets (_f) + (set-fontset-font t 64257 "Quivira" nil) + (set-fontset-font t 'egyptian "Noto Sans Egyptian Hieroglyphs" nil) + (set-fontset-font t 'hangul "NanumGothicCoding" nil) + (set-fontset-font t 'unicode (face-attribute 'default :family) nil) + (set-fontset-font t 'unicode-bmp (face-attribute 'default :family) nil) + (set-fontset-font t 'symbol "Symbola-10" nil) + (set-fontset-font t 'greek "GFS Didot" nil) + (set-fontset-font t 'mathematical "FreeSerif" nil) + (set-fontset-font t 'emoji "Noto Color Emoji" nil) + ;; boxes + (set-fontset-font t '(9472 . 9599) "Source Code Pro" nil) + ;; variation selector-16 + (set-fontset-font t 65039 "BabelStone Modern-1" nil)) + +(jao--set-fontsets nil) +(add-to-list 'after-make-frame-functions 'jao--set-fontsets) + +;;;; nobreak char display +(setq nobreak-char-display nil) + +;;;; vertical separator +(unless (display-graphic-p) + (set-display-table-slot standard-display-table + 'vertical-border + (make-glyph-code ?│))) + +;;;; themes +(defun jao-colors-scheme-dark-p () + (equal "dark" (getenv "JAO_COLOR_SCHEME"))) + +(setq custom-theme-directory + (expand-file-name "lib/themes" jao-emacs-dir)) + +(require 'jao-themes) + +(defvar jao-theme-dark 'jao-dark) +(defvar jao-theme-light 'jao-light) +(defvar jao-theme-term-dark 'modus-vivendi) +(defvar jao-theme-term-light 'jao-light) + +(defun jao-themes-setup () + (let* ((dark (jao-colors-scheme-dark-p)) + (theme (cond ((and dark window-system) jao-theme-dark) + (dark jao-theme-term-dark) + (window-system jao-theme-light) + (t jao-theme-term-light)))) + (load-theme theme t) + (modify-all-frames-parameters `((font . ,jao-themes-default-face))))) + +(unless (eq window-system 'pgtk) (jao-themes-setup)) + +(global-font-lock-mode 1) + +;;; Help system +;;;; Help buffers and shortcuts +(setq help-window-select t + help-link-key-to-documentation t) + +(use-package find-func + :bind (("C-h C-v" . find-variable) + ("C-h C-f" . find-function) + ("C-h C-k" . find-function-on-key) + ("C-h C-l" . find-library))) + +;;;; eldoc +(use-package eldoc + :init (setq eldoc-mode-line-string nil + eldoc-echo-area-use-multiline-p t + eldoc-echo-area-prefer-doc-buffer nil + eldoc-display-functions '(eldoc-display-in-echo-area)) + :config (global-eldoc-mode 1) + :diminish ((eldoc-mode . ""))) + +;; bookmarks +(setq bookmark-default-file "~/.emacs.d/emacs.bmk" + bookmark-set-fringe-mark nil) + +;;;; man pages +(setq Man-notify-method 'pushy) ;; pushy - same window + +;;;; recoll +(use-package jao-recoll) + +;;; Window manager and window system helpers +;;;; transparency +(defvar jao-transparent-only-bg (> emacs-major-version 28)) + +(defvar jao-frames-default-alpha + (cond ((eq window-system 'pgtk) 80) + (jao-transparent-only-bg 88) + (t 85))) + +(defvar jao-transparent-frame (< jao-frames-default-alpha 100)) + +(defun jao-transparent-p () jao-transparent-frame) + +(defun jao-alpha-parameters (&optional level) + (let ((level (or level jao-frames-default-alpha))) + (if jao-transparent-only-bg + `((alpha-background . ,level) (alpha)) + `((alpha . ,(cons level level)) (alpha-background))))) + +(defun jao-set-transparency (&optional level all) + (interactive "nOpacity (0-100): ") + (let ((level (or level jao-frames-default-alpha))) + (setq jao-transparent-frame (< level 100)) + (if all + (modify-all-frames-parameters (jao-alpha-parameters level)) + (modify-frame-parameters nil (jao-alpha-parameters level))))) + +(defun jao-toggle-transparency (&optional all) + (interactive "P") + (let ((level (if jao-transparent-frame 100 jao-frames-default-alpha))) + (jao-set-transparency level all))) + +;;;; exwm: +(defvar jao-exwm-enabled nil) +(defun jao-exwm-enabled-p () jao-exwm-enabled) + +(defun jao-exwm-enable () + (require 'jao-custom-exwm) + (setq jao-exwm-enabled t) + (display-time-mode -1) + (exwm-enable) + (setq jao-frames-default-alpha 88) + (jao-set-transparency) + (x-change-window-property "_XMONAD_TRAYPAD" "" nil nil nil nil 0) + (jao-trisect t)) + +;;;; xmonad +(defvar jao-xmonad-enabled (string= "xmonad" (or (getenv "wm") ""))) +(defun jao-xmonad-enabled-p () jao-xmonad-enabled) + +(defun jao-xmonad-enable () + (setq jao-browse-doc-use-emacs-p t) + (setq jao-wallpaper-random-wake t) + ;; (jao-set-transparency) + (jao-trisect) + (message "Welcome to xmonad")) + +(when jao-xmonad-enabled + (add-hook 'after-init-hook #'jao-xmonad-enable t)) + +;;;; wallpaper +(defvar jao-wallpaper-dir "~/.wallpapers/") + +(defvar jao-wallpaper-random-candidates + '("wallpaper.jpg" "wallpaper2.jpg")) + +(defvar jao-wallpaper-random-candidates-light + '("wallpaper.jpg" "wallpaper2.jpg")) + +(defvar jao-wallpaper-random-wake t + "Set to t for getting a new wallpaper on awaking from sleep") + +(defun jao-set-wallpaper (&optional path) + (interactive) + (let ((current (format "~/.wallpaper.%s" + (if (jao-colors-scheme-dark-p) "dark" "light")))) + (when-let ((f (or path + (read-file-name "Image: " + jao-wallpaper-dir + (file-symlink-p current) + t)))) + (make-symbolic-link (expand-file-name f) current t) + (if jao-sway-enabled + (jao-sway-set-wallpaper (expand-file-name f)) + (shell-command (format "xwallpaper --zoom %s" f)))))) + +(defun jao-set-random-wallpaper () + (interactive) + (when (or (called-interactively-p 'interactive) + jao-wallpaper-random-wake) + (let* ((ws (if (jao-colors-scheme-dark-p) + jao-wallpaper-random-candidates + jao-wallpaper-random-candidates-light)) + (f (seq-random-elt ws))) + (jao-set-wallpaper (expand-file-name f jao-wallpaper-dir)) + (message "%s" f)))) + +(add-to-list 'jao-sleep-awake-functions #'jao-set-random-wallpaper) + +;;;; screensaver and lock +(defun jao-screensaver-enabled () + (string= (jao-shell-string "xdg-screensaver status") "enabled")) + +(defun jao-screensaver-toggle () + (interactive) + (let ((wid (jao-shell-string "xdotool getwindowfocus"))) + (if (jao-screensaver-enabled) + (jao-shell-string "xdg-screensaver suspend" wid) + (jao-shell-string "xdg-screensaver resume" wid)) + (jao-notify (format "Using '%s'" + (jao-shell-string "xdotool getwindownames" wid)) + (format "Screensaver %s" + (jao-shell-string "xdg-screensaver status"))))) + +(jao-shell-def-exec jao-xlock-screen "xdg-screensaver" "activate") +(jao-shell-def-exec jao-suspend "sudo" "systemctl" "suspend") +(jao-shell-def-exec jao-poweroff "sudo" "systemctl" "poweroff") + +(defun jao-lock-screen () + (interactive) + (if jao-sway-enabled + (shell-command "swaylock -i ~/.lockimage") + (jao-xlock-screen))) + +(transient-define-prefix jao-transient-sleep () + ["Sleep" + ("l" "lock screen" jao-lock-screen) + ("z" "sleep" jao-suspend) + ("u" "enable/disable screensaver" jao-screensaver-toggle) + ("poof" "power-off" jao-poweroff)]) + +;;;; mouse +(dolist (k '([mouse-3] + [down-mouse-3] + [drag-mouse-3] + [double-mouse-3] + [mouse-4] + [down-mouse-4] + [drag-mouse-4] + [double-mouse-4] + [triple-mouse-4] + [mouse-5] + [down-mouse-5] + [drag-mouse-5] + [double-mouse-5] + [triple-mouse-5])) + (global-unset-key k)) + +;;;; X clipboard +(setq select-enable-clipboard t + select-enable-primary t + selection-timeout 100) + +;;;; pop-up frames +(defun jao-open-in-x-frame (&optional width height) + (interactive) + (make-frame `((window-system . x) + (name . "emacs popup") + (width . ,(or width (window-width))) + (height . ,(or height (window-height))))) + (define-key (current-local-map) "q" #'delete-frame)) + +;;;; xmobar +(defun jao-xmobar-kill () + (interactive) + (shell-command "killall xmobar-exwm")) + +(defun jao-xmobar-restart () + (interactive) + (jao-xmobar-kill) + (start-process "" nil "xmobar-exwm" "-d")) + +;;; Minibuffer +(defvar jao-modeline-in-minibuffer (and window-system t)) + +(use-package jao-minibuffer + :init + (if (jao-colors-scheme-dark-p) + (setq jao-minibuffer-active-buffer-line-color "azure4" + jao-minibuffer-inactive-buffer-line-color "grey25") + (setq jao-minibuffer-active-buffer-line-color "burlywood3" + jao-minibuffer-inactive-buffer-line-color "grey65")) + :commands (jao-minibuffer-add-variable + jao-minibuffer-refresh + jao-minibuffer-mode)) + +(setq enable-recursive-minibuffers t) +(require 'mb-depth) +(minibuffer-depth-indicate-mode 1) +(require 'minibuf-eldef) +(setq minibuffer-eldef-shorten-default t) +(minibuffer-electric-default-mode 1) + +(jao-minibuffer-mode 1) + +;;; Mode line +(setq line-number-display-limit-width 250) +(setq mode-line-position-column-format '(" %c") + mode-line-position-line-format '(" %l,%c")) +(setq mode-line-percent-position + '(" %l" (:eval (format "/%d" (line-number-at-pos (point-max)))))) +(line-number-mode -1) +(column-number-mode 1) + +(use-package jao-mode-line + :commands (jao-mode-line-add-to-minibuffer + jao-mode-line-remove-from-minibuffer)) + +(when jao-modeline-in-minibuffer + (add-hook 'display-time-hook #'jao-minibuffer-refresh) + (add-hook 'after-init-hook + (lambda () (jao-mode-line-add-to-minibuffer 90)))) + +;;;; format tweaks +(setq mode-line-end-spaces nil) + +;;;; time display +(setq display-time-world-list + '(("Europe/Paris" "Barcelona") + ("America/Los_Angeles" "Los Angeles") + ("America/New_York" "New York") + ("Europe/London" "London") + ("Asia/Calcutta" "Bangalore") + ("Asia/Tokyo" "Tokyo"))) + +(setq display-time-day-and-date nil + display-time-24hr-format nil + display-time-default-load-average nil + display-time-format " %a %e %H:%M") +;;;; mode line toggle +(use-package jao-mode-line + :init + (when (and window-system (not jao-modeline-in-minibuffer)) + (add-to-list 'after-make-frame-functions #'jao-mode-line-hide-inactive) + (add-hook 'after-init-hook #'jao-toggle-inactive-mode-line)) + :demand t + :bind (("" . jao-mode-line-toggle-inactive) + ("" . jao-mode-line-toggle) + ("" . jao-mode-line-echo))) +;;;; diminish +(use-package diminish + :ensure t + :demand t + :diminish ((auto-fill-function . " §") + (auto-revert-mode . "") + (outline-minor-mode . " …"))) +;;;; battery +(use-package battery + :init (setq battery-load-low 15 + battery-load-critical 8 + battery-mode-line-limit 40 + battery-echo-area-format + "%L %r %B (%p%% load, remaining time %t)" + battery-mode-line-format " %b%p ")) ;; " 🔋%b%p " +(display-battery-mode 1) +(with-eval-after-load "jao-minibuffer" + (unless jao-modeline-in-minibuffer + (jao-minibuffer-add-variable 'battery-mode-line-string 80))) + +;;; Notifications +;;;; alert +(use-package alert + :ensure t + :init + (setq alert-default-style 'message ;; 'libnotify + alert-hide-all-notifications nil)) + +;;;; jao-notify +(require 'jao-notify) + +;;;; tracking +(use-package tracking + :demand t + :init (setq tracking-position 'before-modes + tracking-frame-behavior nil + tracking-most-recent-first nil + tracking-max-mode-line-entries 10 + tracking-sort-faces-first t + tracking-shorten-modes '()) + :config + (setq erc-track-enable-keybindings nil) + + (defun jao-tracking-next-buffer () + (interactive) + (tracking-next-buffer) + (jao-tracking-update-minibuffer)) + + :bind (("C-c C-SPC" . jao-tracking-next-buffer))) + +(use-package jao-tracking + :demand t + :init (setq jao-tracking-bkg + (if (jao-colors-scheme-dark-p) "grey20" "grey93"))) + +;;;; tmr +(use-package tmr + :ensure t + :init + (setq tmr-sound-file "/usr/share/sounds/freedesktop/stereo/message.oga" + tmr-descriptions-list '("tea is ready"))) + +;;;; pulsar +(use-package pulsar + :ensure t + :diminish + :custom ((pulsar-pulse-functions + '(ace-window + backward-page + delete-other-windows + delete-window + forward-page + jao-prev-window + move-to-window-line-top-bottom + org-backward-heading-same-level + org-forward-heading-same-level + org-next-visible-heading + org-previous-visible-heading + other-window + outline-backward-same-level + outline-forward-same-level + outline-next-visible-heading + outline-previous-visible-heading + outline-up-heading + recenter-top-bottom + reposition-window + scroll-down-command + scroll-up-command)) + (pulsar-pulse t) + (pulsar-delay 0.1) + (pulsar-iterations 10) + (pulsar-face 'pulsar-yellow) + (pulsar-highlight-face 'pulsar-face)) + :hook ((jao-afio-switch . pulsar-pulse-line))) + +(pulsar-global-mode) + +;;; Calendar, diary, weather +;;;; diary +(setq diary-file (expand-file-name "diary" jao-org-dir) + diary-display-function 'diary-fancy-display + diary-mail-addr "jao@localhost" + diary-comment-start ";;" + diary-comment-end "") + +(add-hook 'diary-list-entries-hook 'diary-sort-entries t) + +;;;; calendar +(setq appt-display-format nil) +(appt-activate 1) +(setq calendar-latitude 55.9533 + calendar-longitude -3.1883 + calendar-location-name "Edinburgh, Scotland" + calendar-mark-diary-entries-flag t + calendar-date-echo-text '(format "ISO date: %s" + (calendar-iso-date-string + (list month day year)))) + +(setq calendar-holidays + '((holiday-fixed 1 1 "New Year's Day") + (holiday-fixed 4 1 "April Fools' Day") + (holiday-float 5 0 2 "Mother's Day") + (holiday-fixed 3 19 "Father's Day") + (holiday-float 11 4 4 "Thanksgiving") + (holiday-fixed 12 25 "Christmas") + (holiday-chinese-new-year) + (solar-equinoxes-solstices) + (holiday-sexp calendar-daylight-savings-starts + (format "Daylight Saving Time Begins %s" + (solar-time-string + (/ calendar-daylight-savings-starts-time + (float 60)) + calendar-standard-time-zone-name))) + (holiday-sexp calendar-daylight-savings-ends + (format "Daylight Saving Time Ends %s" + (solar-time-string + (/ calendar-daylight-savings-ends-time + (float 60)) + calendar-daylight-time-zone-name))))) + +(add-to-list 'display-buffer-alist + `(,(regexp-quote diary-fancy-buffer) + (display-buffer-at-bottom) + (window-parameters (mode-line-format . none)) + (window-height . fit-window-to-buffer))) + +(defun jao-diary--select () + (switch-to-buffer diary-fancy-buffer)) + +(add-hook 'diary-fancy-display-mode-hook #'jao-diary--select) +(setq org-calendar-insert-diary-entry-key nil + org-agenda-diary-file 'diary-file) + +;;;; winttr +(defun jao-weather (&optional wide) + (interactive "P") + (if (not wide) + (message "%s" + (jao-shell-string "curl -s" + "https://wttr.in/?format=%l++%m++%C+%c+%t+%w++%p")) + (jao-afio--goto-scratch) + (if-let ((b (get-buffer "*wttr*"))) + (progn (pop-to-buffer b) + (term-send-string "clear;curl wttr.in\n")) + (jao-exec-in-term "curl wttr.in" "*wttr*")))) +(global-set-key (kbd "") #'jao-weather) + +;;;; timers +(put 'list-timers 'disabled nil) + +;;; Files, dired and scratch buffer +;;;; so-long +(setq large-file-warning-threshold (* 200 1024 1024)) + +(use-package so-long + :ensure t + :diminish) +(global-so-long-mode 1) + +;;;; persistent scratch +(use-package persistent-scratch + :ensure t + :config (persistent-scratch-setup-default)) + +;;;; dired +(use-package dired + :init + (setq dired-recursive-deletes 'top + dired-recursive-copies 'top + dired-listing-switches "-alhF --group-directories-first" + ls-lisp-dirs-first t + dired-dwim-target t + dired-kill-when-opening-new-dired-buffer t + dired-mouse-drag-files t + wdired-create-parent-directories t) + + (put 'dired-find-alternate-file 'disabled nil) + :hook (dired-mode . turn-on-gnus-dired-mode) + :bind (:map dired-mode-map + ("C-c C-r" . wdired-change-to-wdired-mode) + ("C-M-m" . gnus-dired-attach))) + +(use-package dired-x :demand t) + +(use-package find-dired + :init (setq find-ls-option '("-print0 | xargs -0 ls -ld" . "-ld")) + :bind ("C-c D" . find-name-dired)) + +(use-package dired-git-info + :ensure t + :bind (:map dired-mode-map (")" . dired-git-info-mode))) + +;;; General editing +;;;; automatically uncompress +(require 'jka-compr) +(auto-compression-mode 1) + +;;;; wgrep +(use-package wgrep :ensure t) +(require 'wgrep) + +;;;; executable scripts +(add-hook 'after-save-hook + 'executable-make-buffer-file-executable-if-script-p) + +;;;; spaces, tabs, kill +(setq kill-whole-line t) +(setq-default indent-tabs-mode nil) +(setq indent-tabs-width 4) +(setq-default default-tab-width 8) +(setq tab-always-indent t) +(setq kill-read-only-ok t) +(setq view-read-only nil) + +;;;; whitespace and filling column +(add-hook 'write-file-functions 'delete-trailing-whitespace) +(setq-default indicate-empty-lines nil) +(setq fill-column 78) +(setq comment-auto-fill-only-comments nil) + +(use-package whitespace + :init + (setq whitespace-style '(face tabs trailing ;; lines-tail + empty missing-newline-at-eof) + whitespace-line-column 80) + :hook (prog-mode . whitespace-mode) + :diminish) + +(use-package display-fill-column-indicator + :init (setq-default display-fill-column-indicator-column 80) + :hook (prog-mode . display-fill-column-indicator-mode)) + +;;;; visible mode +(use-package visible-mode + :bind (("s-v" . visible-mode))) + +;;;; changes +(use-package goto-chg + :ensure t + :bind (("C-." . goto-last-change) + ("C-c ." . goto-last-change) + ("C-c ," . goto-last-change-reverse))) + +;;;; eval-and-replace +(defun fc-eval-and-replace () + "Replace the preceding sexp with its value." + (interactive) + (backward-kill-sexp) + (condition-case nil + (prin1 (eval (read (current-kill 0))) + (current-buffer)) + (error (message "Invalid expression") + (insert (current-kill 0))))) + + (global-set-key "\C-ce" 'fc-eval-and-replace) + +;;;; skeletons and autoinsert +(use-package autoinsert + :config + (setq auto-insert-directory "~/.emacs.d/autoinsert/" + auto-insert t + auto-insert-query t) + (setf (alist-get 'html-mode auto-insert-alist nil t) nil)) +(add-hook 'find-file-hooks #'auto-insert) + +(use-package jao-skel + :demand t + :config + (require 'jao-skel-geiser) + (require 'jao-skel-lisp) + (require 'jao-skel-haskell) + (require 'jao-skel-latex)) + +;;;; completion +(use-package jao-custom-completion) + +;;; Buffers +;;;; cursor and mark +(transient-mark-mode -1) +(blink-cursor-mode -1) +(setq cursor-in-non-selected-windows nil) + +;;;; uniquifiy +(require 'uniquify) +(setq uniquify-buffer-name-style 'forward + uniquify-trailing-separator-p t) + +;;;; autosave +(setq auto-save-list-file-prefix "~/.emacs.d/auto-save-list/.saves-" + auto-save-no-message t + kill-buffer-delete-auto-save-files t) + +(setq lock-file-name-transforms + '(("\\`/.*/\\([^/]+\\)\\'" "/tmp/emacs-lock/\\1" t))) + +;;;; autorevert +(setq auto-revert-check-vc-info nil) +(setq auto-revert-verbose nil) +(setq auto-revert-avoid-polling t) +(setq auto-revert-mode-text "") +(require 'autorevert) +(global-auto-revert-mode 1) + +;;;; attached buffers +(defun jao-display-buffer-below-selected (buffer alist) + (delete-other-windows-vertically) + (display-buffer-below-selected buffer alist)) + +(defun jao-attached-buffer-entry (name-rx height) + `(,name-rx (display-buffer-reuse-window + jao-display-buffer-below-selected) + (window-height . ,(or height 25)))) + +(defmacro jao-with-attached-buffer (name-rx height &rest body) + (declare (indent defun)) + `(let ((display-buffer-alist '(,(jao-attached-buffer-entry name-rx height)))) + ,@body)) + +(defun jao-define-attached-buffer (name-rx &optional height) + (add-to-list 'display-buffer-alist + (jao-attached-buffer-entry name-rx height))) + +;;;; images +(setq image-use-external-converter t) +(setq widget-image-enable nil) + +;;;; same mode +(defun jao-buffer-same-mode (&optional mode pre-fn switch-fn) + (interactive) + (let* ((mode (or mode major-mode)) + (modes (if (symbolp mode) (list mode) mode)) + (pred `(lambda (b) + (let ((b (get-buffer (if (consp b) (car b) b)))) + (member (buffer-local-value 'major-mode b) + ',modes)))) + (buff (read-buffer "Buffer: " nil t pred))) + (when pre-fn (funcall pre-fn)) + (if switch-fn (funcall switch-fn buff) (pop-to-buffer buff)))) +(global-set-key (kbd "C-c C-b") #'jao-buffer-same-mode) + +;;;; projects +(use-package project + :bind (("C-x C-p" . project-prefix-map))) + +;;;; buffer quit function (the triple ESC) +(setq buffer-quit-function (lambda () t)) + + +;;;; scrolling +(setq scroll-preserve-screen-position 'always + scroll-conservatively most-positive-fixnum + scroll-margin 4 + scroll-step 2 + redisplay-skip-fontification-on-input t) + +;;; Windows +;;;; splitting and switch +(setq split-height-threshold 80 + split-width-threshold 144 + display-buffer-avoid-small-windows 20) + +(setq switch-to-buffer-preserve-window-point nil + switch-to-buffer-obey-display-actions t + switch-to-prev-buffer-skip 'this) ;; don't switch to a + ;; buffer already visible in + ;; this frame + +(global-set-key (kbd "C-x _") #'delete-other-windows-vertically) + +;;;; first window +(defvar jao-first-window--from nil) + +(defun jao-first-window () + "Go to previous windows in frame, remembering where we were." + (interactive) + (let ((cb (current-buffer))) + (if (eq (get-buffer-window cb) (select-window (frame-first-window))) + (when jao-first-window--from (pop-to-buffer jao-first-window--from)) + (setq jao-first-window--from cb)))) + +(defun jao-prev-window () + "Go to previous window." + (interactive) + (other-window -1)) + +(global-set-key (kbd "C-x p") #'jao-prev-window) +(global-set-key (kbd "s-a") #'jao-first-window) +(global-set-key (kbd "M-a") #'jao-first-window) + +;;;; ace window +(use-package ace-window + :ensure t + :demand t + :init (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l) + aw-char-position 'top-left + aw-ignore-current nil + aw-dispatch-when-more-than 2 + aw-leading-char-style 'path + aw-display-mode-overlay t + aw-scope 'frame) + :config + + (defun jao-ace-consult-buffer-other-window (w) + (interactive) + (aw-switch-to-window w) + (consult-buffer)) + + (setf (alist-get ?b aw-dispatch-alist) + '(jao-ace-consult-buffer-other-window "Consult buffer")) + + (setf (alist-get ?B aw-dispatch-alist) + (alist-get ?u aw-dispatch-alist)) + + + :bind (("M-o" . ace-window) + ("M-O" . ace-swap-window) + ("C-x 4 t" . ace-swap-window))) + +;;;; window navigation (custom) +(defun jao-nth-window (n) + (if (zerop n) + 'jao-first-window + `(lambda () + (interactive) + (select-window (frame-first-window)) + (dotimes (x ,n) (other-window 1))))) + +(defun jao-prev-window () + "Go to previous window" + (interactive) + (other-window -1)) + +(defun jao-next-window () + "Go to previous window" + (interactive) + ;; next-window-any-frame + (other-window 1)) + +(global-set-key (kbd "C-x p") 'jao-prev-window) +(global-set-key (kbd "C-x o") 'other-window) +(mapc (lambda (n) + (global-set-key (format "\C-c%s" (1+ n)) (jao-nth-window n))) + '(0 1 2 3 4 5 6 7 8)) + +;; transposing windows +(defun transpose-windows (arg) + "Transpose the buffers shown in two windows." + (interactive "p") + (let ((selector (if (>= arg 0) 'next-window 'previous-window))) + (while (/= arg 0) + (let ((this-win (window-buffer)) + (next-win (window-buffer (funcall selector)))) + (set-window-buffer (selected-window) next-win) + (set-window-buffer (funcall selector) this-win) + (select-window (funcall selector))) + (setq arg (if (plusp arg) (1- arg) (1+ arg)))))) + +(define-key ctl-x-4-map (kbd "t") 'transpose-windows) + +;;;; winner mode +(winner-mode 1) + +;;; Frames +;;;; frame geometry +(setq frame-resize-pixelwise t) +(modify-all-frames-parameters + `((horizontal-scroll-bars . nil) + (vertical-scroll-bars . nil) + (scroll-bar-width . 0) + (menu-bar . nil))) + +;;;; frame layout, title, etc +(setq frame-title-format '("%b")) +(use-package fringe) +(fringe-mode) + +(menu-bar-mode -1) + +;; (setting it to nil avoids mouse wrapping after other-frame) +(setq focus-follows-mouse t) + +(use-package scroll-bar) +(set-scroll-bar-mode nil) +(use-package tool-bar) +(tool-bar-mode -1) + +(defun jao-trisect (&optional force) + (interactive) + (let ((fw (frame-width))) + (delete-other-windows) + (cond ((or force (>= fw 240)) + (let ((w (- (/ fw 3)))) + (delete-other-windows) + (split-window-horizontally w) + (split-window-horizontally w) + (balance-windows))) + ((> fw 162) + (split-window-horizontally) + (switch-to-buffer (other-buffer)))))) + +(defun jao-bisect () + (interactive) + (jao-trisect t) + (next-window) + (delete-window)) + +;;;; afio +(use-package jao-afio) + +(defun jao-xmonad-goto-1 () + (shell-command "sendCommand 1")) + +(defun jao-afio--goto-scratch-1 () + (interactive) + (jao-afio-goto-scratch t)) + +(jao-afio-setup 'jao-afio--goto-scratch-1 t) + +(defun jao-current--frame-id () + (propertize (if (and (jao-exwm-enabled-p) + (not (bound-and-true-p jao-exwm--use-afio))) + (format "F%s" exwm-workspace-current-index) + (format "%s" (jao-afio-current-no))) + 'face 'font-lock-warning-face)) + +(add-hook 'jao-afio-switch-hook #'tracking-remove-visible-buffers) +(jao-minibuffer-add-variable '(jao-current--frame-id) 100) + +(defun jao-afio--set-mode-line () + (when (and window-system (fboundp 'jao-mode-line-hide-inactive)) + (if (string= "Docs" (jao-afio-current-frame)) + (jao-mode-line-show-inactive nil) + (jao-mode-line-hide-inactive nil)))) + +(unless jao-modeline-in-minibuffer + (add-hook 'jao-afio-switch-hook #'jao-afio--set-mode-line)) + +;;; Writing and writing modes +;;;; copyright notices +(setq copyright-year-ranges t) +(add-hook 'write-file-functions 'copyright-update) + +;;;; indent on yank +(defvar jao-auto-indent-modes + '(emacs-lisp-mode ;; clojure-mode + scheme-mode objc-mode + tuareg-mode c-mode c++-mode + tcl-mode sql-mode + perl-mode cperl-mode + java-mode jde-mode + LaTeX-mode TeX-mode)) + +(defadvice yank (after indent-region activate) + (if (member major-mode jao-auto-indent-modes) + (indent-region (region-beginning) (region-end) nil))) + +;;;; org mode +(use-package jao-custom-org) + +;;;; blog +(use-package jao-custom-blog) + +;;;; text-ish mode settings +;; SENTENCES separated by just one space +(setq sentence-end "[.?!][]\"')]*\\($\\|\t\\| \\)[ \t\n]*") +(setq sentence-end-double-space t) +;; copy rectangle +(defun kill-rectangle-save (start end) + "Save the region-rectangle as the last killed one." + (interactive "r") + (require 'rect) ; Make sure killed-rectangle is defvar'ed. + (setq killed-rectangle (extract-rectangle start end)) + (message "Rectangle saved")) + +;; text mode, autoinserts and write hooks +(setq default-major-mode 'text-mode) +(add-hook 'text-mode-hook 'turn-on-auto-fill) + +;;;; dictionaries +(defun jao-word-definition-lookup () + "Look up the word under cursor in a browser." + (interactive) + (require 'thingatpt) + (browse-url + (concat "http://www.wordnik.com/words/" + ;; "http://www.answers.com/main/ntquery?s=" + (thing-at-point 'word)))) + +(use-package dictionary + :init (setq dictionary-use-single-buffer t + dictionary-server "localhost") + :commands (dictionary-search + dictionary-match-words + dictionary-lookup-definition + dictionary + dictionary-mouse-popup-matching-words + dictionary-popup-matching-words + dictionary-tooltip-mode + global-dictionary-tooltip-mode) + :bind (("C-c d" . dictionary-search))) + +(setq ispell-personal-dictionary + (expand-file-name "~/.emacs.d/ispell.dict")) + +(use-package wordreference + :ensure t + :init (setq wordreference-target-lang "es" + wordreference-source-lang "en") + :bind (("C-c D" . wordreference-search))) + +;;;; markdown +(use-package markdown-mode + :ensure t) + +(use-package markdown-toc + :ensure t) + +(dolist (ext '("\\.md$" "\\.markdown$")) + (add-to-list 'auto-mode-alist (cons ext 'markdown-mode))) + +;;;; TeX and LaTex +(use-package tex-site + :ensure auctex + :init + (setq TeX-auto-save t) + (setq TeX-parse-self t) + (setq TeX-a4-paper t) + (setq TeX-auto-local ".tex-auto-local") + ;; Preferred view format: dvi, ps, pdf, pdfs + (setq TeX-view-format "pdf") + (setq-default TeX-master "../main") ; nil to ask + (setq TeX-view-program-selection + ;; '((output-dvi "open") + ;; (output-pdf "open") + ;; (output-html "open")) + '(((output-dvi has-no-display-manager) "dvi2tty") + ((output-dvi style-pstricks) "dvips and gv") + (output-dvi "xdvi") + (output-pdf "xdg-open") + (output-html "xdg-open"))) + ;; to make RefTeX faster for large documents, try these: + (setq reftex-enable-partial-scans t) + (setq reftex-save-parse-info t) + (setq reftex-use-multiple-selection-buffers t) + ;; to integrate with AUCTeX + (setq reftex-plug-into-AUCTeX t) + (setq reftex-ref-style-default-list + '("Hyperref" "Varioref" "Fancyref")) + (setq LaTeX-command "latex -shell-escape") + (setq LaTeX-biblatex-use-Biber t) + (setq bibtex-dialect 'biblatex) + :config + (add-hook 'TeX-after-compilation-finished-functions + #'TeX-revert-document-buffer) + (add-hook 'LaTeX-mode-hook 'turn-on-reftex)) + +;;; Browsing +;;;; variables +(defvar jao-browse-doc-use-emacs-p t) +(defvar jao-browse-url-function nil) +(defvar jao-browse-url-external-function nil) + +;;;; url around point +(defun jao-url-around-point (&optional current-url) + (or (and (fboundp 'w3m-anchor) (w3m-anchor)) + (shr-url-at-point nil) + (ffap-url-at-point) + (thing-at-point 'url) + (when current-url + (or (and (fboundp 'w3m-anchor) (w3m-anchor)) + (and (derived-mode-p 'eww-mode) (plist-get eww-data :url)))))) + +(defun jao--url-prompt () + (let* ((def (jao-url-around-point t)) + (prompt (concat "URL" (if def (format " (%s): " def) ": ")))) + (read-string prompt nil nil def))) + +;;;; downloads using wget +(defun jao-wget--get-title (filename) + (let ((fn (file-name-base filename))) + (if (string-blank-p fn) + (plist-get eww-data :title) + (subst-char-in-string ?- ? (capitalize fn))))) + +(defun jao-wget (url &optional user pwd) + "Download URL using wget." + (let* ((def (file-name-nondirectory url)) + (pmt (format "Save %s to: " url)) + (read-file-name-function nil) + (dest (expand-file-name + (read-file-name pmt jao-sink-dir nil nil def))) + (title (jao-wget--get-title dest)) + (src-url (jao-url-around-point t)) + (auth (when (and user pwd) + `(,(format "--http-user=%s" user) + ,(format "--http-password=%s" pwd))))) + (switch-to-buffer-other-window (get-buffer-create "*downloads*")) + (erase-buffer) + (kill-new (format "[[doc:%s][%s]] (from [[%s][here]])" + (file-name-nondirectory dest) + (read-string "Title: " title) + (or src-url (file-name-directory url)))) + (apply 'make-term `("downloads" "wget" nil ,@auth "-O" ,dest ,url)))) + +(defun jao-download (url &optional pws) + "Download URL using wget" + (interactive (list (jao--url-prompt))) + (when url + (let ((usr (and pws (read-string "Login name: "))) + (pwd (and pws (read-passwd "Password: ")))) + (jao-wget url usr pwd)))) + +(with-eval-after-load "embark" + (define-key embark-url-map (kbd "d") #'jao-download)) + +;;;; video +(defvar jao-video--url-rx + (format "^https?://\\(?:www\\.\\)?%s/.+" + (regexp-opt '("youtu.be" + "youtube.com" + "blip.tv" + "vimeo.com" + "infoq.com") + t))) + +(defvar jao-video--ext-rx + (format "^https?://.+/.+\\.%s" (regexp-opt '("mp3" "webm" "mp4")))) + +(defun jao-video--url-p (url) + (or (string-match-p jao-video--url-rx url) + (string-match-p jao-video--ext-rx url))) + +(defun jao--remote-run (url prg) + (let ((args (format "%s %s" prg (shell-quote-argument url)))) + (start-process-shell-command prg nil args))) + +(defun jao--mpv (url &rest args) (jao--remote-run url "mpv")) +(defun jao--vlc (url &rest args) (jao--remote-run url "vlc")) + +(defvar jao--video-player 'jao--mpv) + +(defun jao-view-video (url) + "Tries to stream a video from the current or given URL" + (interactive (list (jao--url-prompt))) + (when url (funcall jao--video-player url))) + +(defun jao-maybe-view-video (url &rest _ignored) + (interactive) + (if (y-or-n-p "View video (y) or web page (n)? ") + (jao-view-video url) + (funcall jao-browse-url-function url))) + +;;;; web browsers +(defun jao-www--buffer-p (b) + (with-current-buffer b + (or (derived-mode-p 'w3m-mode 'eww-mode) + (and (boundp 'exwm-class-name) + (member (buffer-local-value 'exwm-class-name b) + '("vlc" "mpv")))))) + +(use-package jao-custom-eww) + +;;;; browse-url +(require 'browse-url) + +(setq browse-url-generic-program "~/bin/firehog") + +(defun jao-browse-with-external-browser (&rest url) + "Browse with external hogging" + (interactive "s") + (let ((url (or (car url) (jao-url-around-point)))) + (if (not url) + (message "No URL at point") + (when (and (jao-exwm-enabled-p) (fboundp 'jao-exwm-firefox)) + (jao-exwm-firefox)) + (when (and jao-sway-enabled (fboundp 'jao-sway-firefox)) + (jao-sway-firefox)) + (browse-url-generic url)))) +(setq jao-browse-url-external-function 'jao-browse-with-external-browser) + +(defun jao--fln (url) + (shell-quote-argument + (if (string-match "^[^:]*:/*?\\(/?[^/].*\\)" url) + (match-string-no-properties 1 url) + url))) + +(defun jao--browse-doc (url search &optional no-add) + (let* ((url (substring-no-properties url)) + (file (jao--fln url))) + (when file + (unless (file-exists-p file) + (error "File %s does not exist" file)) + (jao-open-doc file)))) + +(defun jao--make-file-rx (exts) + (format "file:/?/?.+\\.%s$" (regexp-opt exts))) + +(defvar jao--see-exts + (jao--make-file-rx '("jpg" "jpeg" "png" "mov" "wmv" "avi" "mp4"))) + +(defvar jao--doc-exts + (jao--make-file-rx '("ps" "ps.gz" "pdf" "dvi" "djvu" "chm"))) + +(defvar jao-browse-url-wget-exts + '("ps" "pdf" "dvi" "djvu" "zip" "gz" "tgz" "mp4" "mp3" "flv")) + +(defvar jao-browse-external-domains + '("github.com" "gitlab.com" "slack.com" "meet.google.com" + "twitter.com" "t.com" "linkedin.com" "bigml.com")) + +(defvar jao-browse--external-regexp + (format "https?://.*%s\\(/.*\\)?" + (regexp-opt jao-browse-external-domains))) + +(defun jao-wget--regexp () + (concat "^http[s]?://.+\\(\\." + (mapconcat 'identity jao-browse-url-wget-exts "\\|\\.") + "\\)\\'")) + +(defun jao--see (url &rest _r) + (start-process-shell-command "see" nil (format "see %s" (jao--fln url)))) + +(defun jao--find-file-other-window (url &rest _) + (find-file-other-window (jao--fln url))) + +(use-package elpher :ensure t) + +(defun jao-elpher--browse (url &rest _) (elpher-go url)) + +(defvar jao-browse--sound-rx + (format "^https?://.*/.*\\.%s" (regexp-opt '("mp4" "mp3" "flv")))) + +(defun jao-browse-add-url-to-mpc (url &rest _) + "Add the given URL to mpc's playing list, or just play it." + (let ((p (yes-or-no-p (format "Play %s right now?" url)))) + (when p (jao-mpc-clear)) + (jao-mpc-add-url url) + (if p (jao-mpc-play) (message "%s added to mpc queue" url)))) + +(defun jao-browse-url-browse (&rest args) + (apply jao-browse-url-function args)) + +(setq browse-url-handlers + `(("^\\(gemini\\|gopher\\)://.*" . jao-elpher--browse) + (,jao--doc-exts . jao--browse-doc) + (,jao--see-exts . jao--see) + ("^file://?.+\\.html?$" . ,jao-browse-url-function) + ("^file://?" . jao--find-file-other-window) + (,jao-browse--external-regexp . ,jao-browse-url-external-function) + ("^https?://.*\\.gotomeeting\\.com\\.*" . browse-url-chrome) + (,jao-browse--sound-rx . jao-browse-add-url-to-mpc) + (,(jao-wget--regexp) . jao-download) + (jao-video--url-p . jao-maybe-view-video) + ("." . jao-browse-url-browse))) + +(when (< emacs-major-version 28) + (setf (alist-get 'jao-video--url-p browse-url-handlers nil t) nil) + (setq browse-url-browser-function browse-url-handlers)) + +;;;; subscribe to rss using r2e +(autoload 'View-quit "view") + +(defun jao-rss--find-url () + (save-excursion + (when (derived-mode-p 'w3m-mode 'eww-mode) + (if (fboundp 'w3m-view-source) (w3m-view-source) (eww-view-source))) + (goto-char (point-min)) + (when (re-search-forward + "type=\"application/\\(?:atom\\|rss\\)\\+xml\" +" nil t) + (let ((url (save-excursion + (when (re-search-forward + "href=\"\\([^\n\"]+\\)\"" nil t) + (match-string-no-properties 1)))) + (title (when (re-search-forward + "\\(?:title=\"\\([^\n\"]+\\)\" +\\)" nil t) + (match-string-no-properties 1)))) + (cond ((derived-mode-p 'w3m-view-mode) (w3m-view-source)) + ((string-match-p ".*\\*eww-source\\b.*" (buffer-name)) + (View-quit))) + (when url (cons url (or title ""))))))) + +(defun jao-rss2e-append (name url mbox) + (with-current-buffer (find-file-noselect "~/.config/rss2email.cfg") + (goto-char (point-max)) + (insert "[feed." name "]\nurl = " url) + (insert "\nto = " mbox "+" name "@localhost") + (insert "\nmaildir-mailbox = " mbox "\n\n") + (save-buffer))) + +(defun jao-rss--feeds-dirs () + (mapcar (lambda (d) (cadr (split-string d "\\."))) + (directory-files "~/.emacs.d/gnus/Mail/" nil "^feeds"))) + +(defun jao-rss-subscribe (url) + "Subscribe to a given RSS URL. If URL not given, look for it." + (interactive (list (or (jao-url-around-point) + (jao-rss--find-url) + (read-string "Feed URL: ")))) + (let* ((url+title (if (consp url) url (list url))) + (url (car url+title)) + (title (cdr url+title)) + ;; (cats (cons "prog" (jao-notmuch--subtags "feeds"))) + (cats (jao-rss--feeds-dirs))) + (if url + (let ((url (if (string-match "^feed:" url) (substring url 5) url))) + (when (y-or-n-p (format "Subscribe to <%s>? " url)) + (let* ((name (read-string "Feed name: " title)) + (cat (completing-read "Category: " cats nil t)) + (subs (format "r2e add %s '%s' feeds.%s@localhost" + name url cat))) + ;; (jao-rss2e-append name url cat) + (shell-command-to-string subs) + (shell-command (format "r2e run %s" name))))) + (message "No feeds found")))) + +;;; PDFs and other docs +;;;; doc-view +(use-package doc-view + :init + (setq doc-view-cache-directory "~/.emacs.d/cache/docview" + doc-view-resolution 110 + doc-view-continuous t + doc-view-conversion-refresh-interval 1) + + :bind (:map doc-view-mode-map + ("j" . doc-view-next-line-or-next-page) + ("J" . doc-view-scroll-up-or-next-page) + ("k" . doc-view-previous-line-or-previous-page) + ("K" . doc-view-scroll-down-or-previous-page))) + +(use-package jao-doc-view + :bind (:map doc-view-mode-map + ("b" . jao-doc-view-back) + ("B" . jao-doc-view-forward) + ("S" . jao-doc-view-save-session) + ("u" . jao-doc-view-visit-url))) + +;;;; pdf-tools +(use-package pdf-tools + :ensure t + :demand t + :init + (add-hook 'after-init-hook + (lambda () + (setq pdf-view-midnight-colors + (cons (frame-parameter nil 'foreground-color) + (frame-parameter nil 'background-color))))) + + :config (pdf-tools-install) + + :diminish ((pdf-view-midnight-minor-mode . "")) + + :bind (:map pdf-view-mode-map + (("C-c C-d" . pdf-view-midnight-minor-mode) + ("j" . pdf-view-next-line-or-next-page) + ("J" . pdf-view-scroll-up-or-next-page) + ("k" . pdf-view-previous-line-or-previous-page) + ("K" . pdf-view-scroll-down-or-previous-page)))) + +;;;; zathura +(defun jao-zathura-file-info (title) + (when (string-match "\\(.+\\) \\[\\(.+\\) (\\([0-9]+\\)/\\([0-9]+\\))\\]" + title) + (list (expand-file-name (match-string 1 title)) + (string-to-number (match-string 3 title)) + (string-to-number (match-string 4 title)) + (match-string 2 title)))) + +(defun jao-zathura-goto-org (&optional title) + (when-let* ((title (or title (jao-shell-string "xdotool" + "getactivewindow" + "getwindowname"))) + (info (jao-zathura-file-info title)) + (file (jao-org-pdf-to-org-file (car info))) + (page (cadr info)) + (pageno (or (car (last info)) page))) + (ignore-errors (jao-afio--goto-docs)) + (let* ((exists (file-exists-p file)) + (fn (file-name-nondirectory file)) + (lnk (format "[[doc:%s::%d][Page %s]]" fn page pageno))) + (find-file file) + (unless exists (jao-org-insert-doc-skeleton)) + (if (or (not exists) (y-or-n-p "Insert link?")) + (insert lnk "\n") + (kill-new lnk) + (message "Link to %s (%s) killed" file page))))) + +(defun jao-zathura-open (file page) + (let ((id (jao-shell-string (format "xdotool search --name %s" + (file-name-nondirectory file))))) + (if (string-blank-p id) + (progn + (when jao-xmonad-enabled + (jao-shell-exec "xdotool set_destktop 2")) + (jao-shell-exec (format "zathura %s -P %s" file (or page 1)))) + (let* ((page (if page (format " && xdotool type %dg" page) "")) + (cmd (format "xdotool windowactivate %s%s" id page))) + (jao-shell-string cmd))))) + +;;;; open pdfs +(use-package saveplace-pdf-view + :ensure t + :demand t + :after doc-view) + +(setq jao-open-doc-fun 'jao-find-or-open) +(setq jao-org-open-pdf-fun 'jao-find-or-open) + +(defun jao-find-or-open (file &optional page height) + (if (and jao-browse-doc-use-emacs-p window-system) + (let* ((buffs (buffer-list)) + (b (catch 'done + (while buffs + (when (string-equal (buffer-file-name (car buffs)) file) + (throw 'done (car buffs))) + (setq buffs (cdr buffs)))))) + (jao-afio--goto-docs) + (if b (pop-to-buffer b) (find-file file)) + (when page (jao-doc-view-goto-page page height))) + (jao-zathura-open file page))) + +(defun jao-open-doc (&optional file page height) + (interactive) + (when-let (file (or file + (read-file-name "Document: " + (concat jao-org-dir "/doc/")))) + (funcall jao-open-doc-fun file page height))) + +(defun jao-select-pdf () + (interactive) + (jao-buffer-same-mode '(pdf-view-mode doc-view-mode) + #'jao-afio--goto-docs)) + +;;;; epub +(use-package nov + :ensure t + :after doc-view + :init (setq nov-variable-pitch t + nov-text-width 80) + :config (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))) + +;;;; transient +(defun jao-org-pdf-goto-org-linking () + (interactive) + (jao-org-pdf-goto-org 4)) + +(jao-transient-major-mode doc-view + ["Notes" + ("o" "notes file" jao-org-pdf-goto-org) + ("O" "notes file, linking" jao-org-pdf-goto-org-linking)] + ["Navigation" + ("b" "back jump" jao-doc-view-back) + ("B" "forward jump" jao-doc-view-back) + ("u" "visit URL" jao-doc-view-visit-url)] + ["Slices" + ("cb" "bounding box" doc-view-set-slice-from-bounding-box) + ("cm" "using mouse" doc-view-set-slice-using-mouse)] + ["Session" + ("s" "load session" jao-afio-open-pdf-session) + ("S" "save session" jao-doc-view-save-session) + ("d" "visit cache directory" doc-view-dired-cache)]) + +(with-eval-after-load "pdf-view" + (jao-transient-major-mode pdf-view + ["Notes" + ("o" "notes file" jao-org-pdf-goto-org) + ("O" "notes file, linking" jao-org-pdf-goto-org-linking)] + ["Navigation" + ("b" "back jump" pdf-history-backward) + ("f" "forward jump" pdf-history-forward)] + ["Session" + ("s" "load session" jao-afio-open-pdf-session) + ("S" "save session" jao-doc-view-save-session)])) + +;; (transient-get-suffix 'jao-transient-pdf-view '(0 -1)) + +;;; Email +(setq jao-afio-mail-function 'notmuch) +(use-package jao-custom-email) +(use-package jao-custom-notmuch) + +;;; Shells and terms +;;;; shell modes +(setq sh-basic-offset 2) +;; translates ANSI colors into text-properties, for eshell +(autoload 'ansi-color-for-comint-mode-on "ansi-color" nil t) +(add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on) + +;;;; vterm +(defvar jao-use-vterm nil) + +(use-package vterm + :ensure t + :demand t + :commands (vterm vterm-mode) + :init (setq vterm-kill-buffer-on-exit t + vterm-copy-exclude-prompt t + jao-use-vterm t) + :config (jao-define-attached-buffer "\\*vterm\\*" 0.5) + :bind (:map vterm-mode-map ("C-c C-c" . vterm-send-C-c))) + +(defun jao-exec-in-vterm (cmd bname) + (if (string-blank-p (or cmd "")) + (vterm) + (let ((vterm-shell cmd) + (vterm-kill-buffer-on-exit t) + (buff (generate-new-buffer bname))) + (switch-to-buffer buff) + (vterm-mode)))) + +;;;; term +(defvar-local jao-term--cmd nil) + +(defun jao-term--find (cmd) + (seq-find (lambda (b) + (with-current-buffer b + (and (derived-mode-p 'term-mode 'vterm-mode) + (string= (or jao-term--cmd "") cmd)))) + (buffer-list))) + +(defun jao-exec-in-term (cmd &optional name) + (if jao-use-vterm + (jao-exec-in-vterm cmd name) + (ansi-term "bash" name) + (set-process-sentinel (get-buffer-process (current-buffer)) + (lambda (process event) + (when (string= event "finished\n") + (kill-buffer (process-buffer process))))) + (term-send-string nil (concat cmd " ; exit\n")))) + +(defmacro jao-def-exec-in-term (name cmd &rest prelude) + `(defun ,(intern (format "jao-term-%s" name)) (&optional term) + (interactive "P") + ,@prelude + (let ((jao-use-vterm (if term (not jao-use-vterm) jao-use-vterm))) + (if-let ((b (jao-term--find ,cmd))) + (pop-to-buffer b) + (jao-exec-in-term ,cmd ,(format "*%s*" name)) + (setq-local jao-term--cmd ,cmd))))) + +;;;; eshell +;;;;; basic custom +(use-package eshell + :init + (setq eshell-directory-name "~/.emacs.d/eshell") + (setq eshell-hist-ignoredups 'erase) + + (defun jao-eshell--outline () + (setq-local outline-regexp eshell-prompt-regexp)) + + :hook (eshell-mode . jao-eshell--outline)) + +;;;;; colors +(autoload 'ansi-color-apply "ansi-color") +;; (add-hook 'eshell-preoutput-filter-functions 'ansi-color-filter-apply) +(add-hook 'eshell-preoutput-filter-functions 'ansi-color-apply) + +(use-package eshell-syntax-highlighting + :after esh-mode + :ensure t + :config + ;; Enable in all Eshell buffers. + (eshell-syntax-highlighting-global-mode +1)) + +;;;;; visual commands +(require 'em-term) + +(dolist (c '("editor" "more" "wget" "dict" "vim" "links" "w3m" "guile" + "ssh" "autossh" "zmore" "pager" "aptitude" "su" "htop" "top" + "screen" "whizzml" "iex" "spt")) + (add-to-list 'eshell-visual-commands c)) + +(setq eshell-visual-subcommands '(("git" "log" "diff" "show") + ("sudo" "vim") + ("rebar3" "shell")) + eshell-destroy-buffer-when-process-dies nil + eshell-escape-control-x t) + +(use-package eshell-vterm :ensure t) + +(when jao-use-vterm (eshell-vterm-mode)) + +;;;;; bol +(defun jao-eshell-maybe-bol () + (interactive) + (let ((p (point))) + (eshell-bol) + (if (= p (point)) + (beginning-of-line)))) + +;;;;; prompt +;; tracking git repos +(defun jao-eshell--git-dirty () + (shell-command-to-string "git diff-index --quiet HEAD -- || echo -n '*'")) + +(use-package git-ps1-mode + :ensure t + :init (setq git-ps1-mode-showupstream "1" + git-ps1-mode-showdirtystate "1")) + +(defun jao-eshell--git-info () + (if (fboundp 'git-ps1-mode-get-current) + (git-ps1-mode-get-current) + (let ((desc (shell-command-to-string "git branch --no-color"))) + (when (string-match "^* \\(\\<.+\\>\\)" desc) + (format "%s%s" (match-string 1 desc) (jao-eshell--git-dirty)))))) + +(defun jao-eshell--git-current-branch (suffix) + (let ((desc (or (jao-eshell--git-info) ""))) + (cond ((and (string-empty-p desc) suffix) (format " (%s)" suffix)) + ((string-empty-p (or suffix "")) (format " (%s)" desc)) + (t (format " (%s %s)" desc suffix))))) + +(defun jao-eshell--virtualenv () + (let ((venv (getenv "VIRTUAL_ENV"))) + (when (and venv (string-match ".*/\\([^/]+\\)/$" venv)) + (match-string-no-properties 1 venv)))) + +(defun jao-eshell-prompt-function () + (let* ((venv (jao-eshell--virtualenv)) + (venv (if venv (format "%s" venv) ""))) + (concat (abbreviate-file-name (eshell/pwd)) + (jao-eshell--git-current-branch venv) + (if (= (user-uid) 0) " # " " $ ")))) + +(setq eshell-prompt-function 'jao-eshell-prompt-function) + +;;;;; in-term +(defun eshell/in-term (prog &rest args) + (switch-to-buffer + (apply #'make-term (format "in-term %s %s" prog args) prog nil args)) + (term-mode) + (term-char-mode)) + +;;;;; dir navigation +(use-package eshell-up + :ensure t + :config (setq eshell-up-print-parent-dir t)) + +(use-package eshell-autojump :ensure t) + +;;;;; completion +(defun jao-eshell-completion-capf () + (let* ((b (save-excursion (eshell-bol) (point))) + (c (bash-completion-dynamic-complete-nocomint b (point) t))) + (when (and c (listp c)) + (append c '(:exclusive no))))) + +(defun jao-eshell--set-up-completion () + (setq-local completion-styles '(basic partial-completion) + completion-at-point-functions + '(jao-eshell-completion-capf + pcomplete-completions-at-point t))) + +(use-package bash-completion + :ensure t + :hook (eshell-mode . jao-eshell--set-up-completion)) + +;;;;; history +(setq eshell-history-size 10000) +;;;;; fix eshell history completion to allow !$ +;; This is done by advising eshell-history-reference to expand !$ +;; into !!:$ which works... +(defadvice jao-eshell-history-reference (before ben-fix-eshell-history) + "Fixes eshell history to allow !$ as abbreviation for !!:$" + (when (string= (ad-get-arg 0) "!$") (ad-set-arg 0 "!!:$"))) +(ad-activate 'jao-eshell-history-reference) + +(add-hook 'eshell-expand-input-functions #'eshell-expand-history-references) + +;;;;; toggle +(use-package jao-eshell-here + :demand t + :config (jao-define-attached-buffer "^\\*eshell" 0.5) + :bind (("" . jao-eshell-here-toggle) + ("C-" . jao-eshell-here-toggle-new))) + +;;;;; workarounds +;; at some point, bash completion started insertig the TAB +;; after the commands ends +(defun jao-eshell--clean-prompt () + (eshell-bol) + (ignore-errors (kill-line))) + +(add-hook 'eshell-after-prompt-hook 'jao-eshell--clean-prompt) + +;;;;; keybindings +(defun jao-eshell--kbds () + (define-key eshell-mode-map "\C-a" 'jao-eshell-maybe-bol) + (define-key eshell-mode-map "\C-ci" 'consult-outline)) + +(jao-eshell--kbds) + +;;; Version control and CI +;;;; vc options +(setq vc-follow-symlinks t) +(setq auto-revert-check-vc-info nil) + +;;;; diff fringe indicators (diff-hl) +(use-package diff-hl + :ensure t + :custom ((diff-hl-draw-borders nil) + (diff-hl-side 'right) + (diff-hl-margin-symbols-alist + '((insert . "+") + (delete . "-") + (change . "~") + (unknown . "?") + (ignored . "i")))) + :config + (map-keymap (lambda (_k cmd) + (put cmd 'repeat-map 'diff-hl-command-map)) + diff-hl-command-map) + (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh) + (when (jao-colors-scheme-dark-p) (diff-hl-margin-mode 1))) + +(global-diff-hl-mode 1) + +;;;; magit and forge +(use-package magit + :ensure t + :commands magit-status + :init + (setq magit-status-initial-section nil + magit-define-global-key-bindings nil + magit-completing-read-function 'magit-builtin-completing-read + magit-display-buffer-function + 'magit-display-buffer-fullcolumn-most-v1 + magit-delete-by-moving-to-trash nil + magit-last-seen-setup-instructions "1.4.0" + magit-log-edit-confirm-cancellation t + magit-omit-untracked-dir-contents t + magit-process-connection-type nil + magit-push-always-verify nil + magit-repository-directories + '(("/home/jao/usr/bigml" . 2) + ("/home/jao/usr/jao" . 2) + ("/home/jao/lib/elisp" . 3) + ("/usr/local/src" . 1)) + magit-save-repository-buffers 'dontask + magit-section-visibility-indicator '("…" . t) + magit-status-buffer-switch-function 'switch-to-buffer + magit-status-show-hashes-in-headers t) + :config + + (use-package forge + :ensure t + :demand t + :init + (setq forge-topic-list-limit (cons 100 -1) + forge-pull-notifications nil)) + + (add-hook 'magit-status-sections-hook #'forge-insert-assigned-pullreqs t) + (add-hook 'magit-status-sections-hook #'forge-insert-assigned-issues t) + + :bind (("" . magit-status) + (:map forge-topic-mode-map ("M-w" . copy-region-as-kill)))) + +;;;;; code reviews +(use-package code-review + :ensure t + :after forge + :bind (:map magit-status-mode-map + ("C-c C-r" . code-review-forge-pr-at-point))) + +;;;;; eldoc for magit status/log buffers +(defun jao-magit-eldoc-for-commit (_callback) + (when-let ((commit (magit-commit-at-point))) + (with-temp-buffer + (magit-git-insert "show" + "--format=format:%an <%ae>, %ar" + (format "--stat=%d" (window-width)) + commit) + (goto-char (point-min)) + (put-text-property (point-min) (line-end-position) 'face 'bold) + (buffer-string)))) + +(defun jao-magit-eldoc-setup () + (add-hook 'eldoc-documentation-functions + #'jao-magit-eldoc-for-commit nil t) + (eldoc-mode 1)) + +(add-hook 'magit-log-mode-hook #'jao-magit-eldoc-setup) +(add-hook 'magit-status-mode-hook #'jao-magit-eldoc-setup) + +(with-eval-after-load "eldoc" + (eldoc-add-command 'magit-next-line) + (eldoc-add-command 'magit-previous-line) + (eldoc-add-command 'magit-section-forward) + (eldoc-add-command 'magit-section-backward)) + +;;;; other git packages +(use-package git-timemachine :ensure t) + +;; git config --local git-link.remote / git-link.branch +(use-package git-link :ensure t) + +(use-package git-modes :ensure t) + +;;;; jenkins +(use-package jenkins + :ensure t + :init + ;; one also needs jenkins-api-token, jenkins-username and jenkins-url + ;; optionally: jenkins-colwidth-id, jenkins-colwidth-last-status + (setq jenkins-colwidth-name 35) + :config + (defun jao-jenkins-first-job (&rest _) + (interactive) + (goto-char (point-min)) + (when (re-search-forward "^- Job" nil t) + (goto-char (match-beginning 0)))) + (add-hook 'jenkins-job-view-mode-hook #'jao-jenkins-first-job) + (advice-add 'jenkins-job-render :after #'jao-jenkins-first-job) + + (defun jenkins-refresh-console-output () + (interactive) + (let ((n (buffer-name))) + (when (string-match "\\*jenkins-console-\\([^-]+\\)-\\(.+\\)\\*$" n) + (jenkins-get-console-output (match-string 1 n) (match-string 2 n)) + (goto-char (point-max))))) + + :bind (:map jenkins-job-view-mode-map + (("n" . next-line) + ("p" . previous-line) + ("f" . jao-jenkins-first-job) + ("RET" . jenkins--show-console-output-from-job-screen)) + :map jenkins-console-output-mode-map + (("n" . next-line) + ("p" . previous-line) + ("g" . jenkins-refresh-console-output)))) + +;;; Programming +;;;; automatic modes +(add-to-list 'auto-mode-alist '("\\.mix\\'" . hexl-mode)) +(add-to-list 'auto-mode-alist '("\\.m4\\'" . m4-mode)) +(add-to-list 'auto-mode-alist '("\\.am\\'" . makefile-mode)) +(add-to-list 'auto-mode-alist '("\\.pl\\'\\|\\.pm\\'" . cperl-mode)) + +;;;; smart scan +(use-package smartscan + :ensure t + :commands smartscan-mode + :init (add-hook 'prog-mode-hook #'smartscan-mode) + :diminish) + +;;;; paredit and parens +(require 'paren) +(show-paren-mode t) +(setq show-paren-context-when-offscreen t) + +(use-package paredit + :ensure t + :commands paredit-mode + :hook ((pie-mode . paredit-mode) + (scheme-mode . paredit-mode) + (clojure-mode . paredit-mode) + (emacs-lisp-mode . paredit-mode) + (eval-expression-minibuffer-setup . paredit-mode) + (lisp-interaction-mode . disable-paredit-mode)) + :diminish ((paredit-mode . " þ"))) + +;;;; diff/ediff +(setq ediff-split-window-function 'split-window-horizontally) +(setq ediff-make-buffers-readonly-at-startup nil) +(setq ediff-window-setup-function 'ediff-setup-windows-plain) +(setq ediff-keep-variants nil) + +;;;; compilation +;;;;; compilation mode options +(require 'compile) +(setq compilation-scroll-output t) +(setq compilation-error-regexp-alist + (remove 'omake compilation-error-regexp-alist)) +;; (add-hook 'compilation-mode-hook #'visual-line-mode) + +;;;;; mode line (no "Compiling"!) +(require 'compile) +(diminish 'compilation-minor-mode " ‡") +(when (< emacs-major-version 27) + (setcdr (assq 'compilation-in-progress minor-mode-alist) '(" ‡"))) +(when (> emacs-major-version 26) + (setcdr (assq 'compilation-in-progress mode-line-modes) '("‡ "))) + +;;;;; colorizing compilation buffer +(setq compilation-message-face 'default) +(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) + +;;;;; compilation commands +(use-package jao-compilation + :commands jao-compilation-setup + :bind (("C-c C" . compile) + ("C-c c" . jao-compile))) +(jao-compilation-setup) + +;;;;; next error +(setq next-error-find-buffer-function + #'next-error-buffer-on-selected-frame + next-error-verbose t) + +;;;; flymake +(use-package flymake + :ensure t + :custom ((flymake-mode-line-format '(" " flymake-mode-line-counters))) + :hook ((haskell-mode . flymake-mode)) + :config (jao-define-attached-buffer "^\\*Flymake diagnostics .*\\*\\'") + :bind (:map flymake-mode-map (("s-f n" . flymake-goto-next-error) + ("s-f p" . flymake-goto-prev-error) + ("s-f i" . flymake-show-diagnostic) + ("s-f f" . flymake-show-diagnostics-buffer) + ("s-f l" . consult-flymake)))) + +;;;; workarounds +(setq c-type-finder-time-slot nil) + +;;;; outline minor mode +(setq outline-minor-mode-cycle t) + +(defun jao-outline-minor-mode-hide-all (arg) + (interactive "P") + (hide-sublevels (if arg 5 4))) + +(defvar jao-outline-minor-mode-map + (let ((map (make-keymap))) + (define-key map (kbd "C-c C-n") #'outline-next-visible-heading) + (define-key map (kbd "C-c C-p") #'outline-previous-visible-heading) + (define-key map (kbd "C-c o") 'consult-outline) + (define-key map (kbd "") #'jao-outline-minor-mode-hide-all) + map)) + +(use-package outline-minor-faces + :ensure t + :after outline) + +(define-minor-mode jao-outline-minor-mode + "Minor outline mode for programming languages" + :lighter "" + (if jao-outline-minor-mode + (progn (setq-local outline-level #'outline-level + outline-regexp (format "[%s]\\{3,\\} " comment-start)) + (outline-minor-mode 1) + (outline-minor-faces-mode 1)) + (outline-minor-mode -1) + (outline-minor-faces-mode -1))) + +;;; Programming languages +;;;; Elisp +(add-hook 'emacs-lisp-mode-hook #'jao-outline-minor-mode) + +(use-package edit-list :ensure t) +(use-package package-lint :ensure t) + +(defun elisp-disassemble (function) + (interactive (list (function-called-at-point))) + (disassemble function)) + +(defun elisp-pp (sexp) + (with-output-to-temp-buffer "*Pp Eval Output*" + (pp sexp) + (with-current-buffer standard-output + (emacs-lisp-mode)))) + +(defun elisp-macroexpand (form) + (interactive (list (form-at-point 'sexp))) + (elisp-pp (macroexpand form))) + +(defun elisp-macroexpand-all (form) + (interactive (list (form-at-point 'sexp))) + (elisp-pp (macroexpand-all form))) + +(defun elisp-find-definition (name) + (interactive (list (thing-at-point 'symbol))) + (cond (name + (let ((symbol (intern-soft name)) + (search (lambda (fun sym) + (let* ((r (save-excursion (funcall fun sym))) + (buffer (car r)) + (point (cdr r))) + (cond ((not point) + (error "Found no definition for %s in %s" + name buffer)) + (t + (switch-to-buffer buffer) + (goto-char point) + (recenter 1))))))) + (cond ((fboundp symbol) + (xref-push-marker-stack) + (funcall search 'find-function-noselect symbol)) + ((boundp symbol) + (xref-push-marker-stack) + (funcall search 'find-variable-noselect symbol)) + (t + (message "Symbol not bound: %S" symbol))))) + (t (message "No symbol at point")))) + + +(defun elisp-bytecompile-and-load () + (interactive) + (or buffer-file-name + (error "The buffer must be saved in a file first")) + (require 'bytecomp) + ;; Recompile if file or buffer has changed since last compilation. + (when (and (buffer-modified-p) + (y-or-n-p (format "save buffer %s first? " (buffer-name)))) + (save-buffer)) + (let ((filename (expand-file-name buffer-file-name))) + (with-temp-buffer + (byte-compile-file filename t)))) + +(use-package elisp-mode + :bind (:map emacs-lisp-mode-map + (("C-c C-M" . emacs-lisp-macroexpand) + ("C-c C-m" . elisp-macroexpand-all) + ("C-c C-k" . elisp-bytecompile-and-load) + ;; ("C-c C-p" . pp-eval-last-sexp) + ("M-." . elisp-find-definition) + ("M-," . pop-tag-mark) + ("C-c <" . lc-show-package-summary)))) + +;;;; Erlang +(use-package erlang + :disabled t + :ensure t + :custom ((inferior-erlang-machine-options '("shell")) + (inferior-erlang-machine "rebar3") + (inferior-erlang-shell-type nil) + (erlang-indent-level 4)) + + ;; :bind (:map erlang-mode-map (("C-c C-z" . jao-vterm-repl-pop-to-repl))) + + :init + ;; (require 'jao-vterm-repl) + ;; (add-to-list 'auto-mode-alist '("^rebar\\.config\\`" . erlang-mode)) + ;; (jao-vterm-repl-register "rebar.config" "rebar3 shell" "^[0-9]+> ") + + :config + ;; (defun jao-erlang-current-module () + ;; (when (save-excursion (goto-char (point-min)) + ;; (re-search-forward "^-module(\\([^)]+\\))" nil t)) + ;; (match-string-no-properties 1))) + + ;; (defun jao-erlang-compile (arg) + ;; (interactive "P") + ;; (save-some-buffers) + ;; (when-let ((mname (jao-erlang-current-module))) + ;; (with-current-buffer (jao-vterm-repl) + ;; (vterm-send-string (format "c(%s).\n" mname)) + ;; (sit-for 0) + ;; (setq compilation-last-buffer (current-buffer)) + ;; (when arg (jao-vterm-repl-pop-to-repl))))) + + ;; (setq erlang-shell-function #'jao-vterm-repl + ;; erlang-shell-display-function #'jao-vterm-repl-pop-to-repl + ;; erlang-compile-function #'jao-erlang-compile) + ) + +;;;; Idris +(use-package idris-mode + :ensure t + :custom ((idris-interpreter-path "idris2") + (idris-pretty-printer-width 80) + (idris-repl-history-file "~/.emacs.d/cache/idris-history.eld") + (idris-stay-in-current-window-on-compiler-error t))) +(jao-define-attached-buffer "^\\*idris.*") + +;;;; Clojure +(use-package clojure-mode + :ensure t + :config + (defun jao-clojure--fix-things () + (setq-local completion-styles '(basic partial-completion emacs22)) + (eldoc-mode 1) + (setq mode-name "λ")) + :hook (clojure-mode . jao-clojure--fix-things)) + +(use-package cider + :ensure t + :commands cider-mode + :init (setq cider-annotate-completion-candidates t + cider-auto-select-error-buffer nil + cider-auto-select-test-report-buffer nil + cider-eldoc-display-for-symbol-at-point t + cider-eldoc-ns-function #'identity ;; #'cider-last-ns-segment + cider-enrich-classpath nil + cider-lein-parameters "repl :headless :host localhost" + cider-mode-line " ÷" + cider-prompt-for-symbol nil + cider-repl-history-file + (expand-file-name "~/.emacs.d/cache/cider.history") + cider-repl-pop-to-buffer-on-connect nil + cider-repl-use-pretty-printing t + cider-show-error-buffer 'except-in-repl + cider-test-show-report-on-success nil + cider-use-fringe-indicators nil + cider-use-overlays nil + clojure-docstring-fill-column 72 + nrepl-prompt-to-kill-server-buffer-on-quit nil) + :bind (("" . cider-selector))) + +(with-eval-after-load "cider-test" + (advice-add 'cider-scale-background-color :override + (lambda () (frame-parameter nil 'background-color))) + (setq cider-test-items-background-color + (frame-parameter nil 'background-color))) + +(use-package cider-macroexpansion + :after cider + :diminish " µ") + +;;;; Geiser +(defun jao-org--set-geiser-impl () (setq-local geiser-repl--impl 'guile)) +(add-hook 'org-mode-hook #'jao-org--set-geiser-impl) + +(jao-load-path "geiser/geiser/elisp") +(use-package geiser + :init + (setq geiser-repl-history-filename "~/.emacs.d/cache/geiser-history" + geiser-repl-startup-time 20000 + geiser-debug-auto-display-images-p t + geiser-log-verbose-p t + geiser-active-implementations '(guile) + geiser-default-implementation 'guile)) + +(jao-load-path "geiser/guile") +(use-package geiser-guile) + +;; (jao-load-path "geiser/mit") +;; (use-package geiser-mit) + +;; (jao-load-path "geiser/chicken") +;; (use-package geiser-chicken) + +;; (jao-load-path "geiser/chibi") +;; (use-package geiser-chibi) + +;; (jao-load-path "geiser/chez") +;; (use-package geiser-chez +;; :init (setq geiser-chez-binary "scheme")) + +;; (jao-load-path "geiser/gambit") +;; (use-package geiser-gambit) + +;; (jao-load-path "geiser/gauche") +;; (use-package geiser-gauche) + +(jao-define-attached-buffer "^\\* ?Geiser .*\\*" 0.4) +(jao-define-attached-buffer "^\\* Guile REPL \\*" 0.4) + +;;;; Haskell +;;;;; packages +(use-package haskell-mode + :ensure t + :custom + ((inferior-haskell-find-project-root t) + (haskell-check-remember-last-command-p nil) + (haskell-font-lock-symbols nil) + (haskell-interactive-popup-errors nil) + (haskell-process-auto-import-loaded-modules t) + (haskell-process-log t) + (haskell-process-suggest-remove-import-lines t) + (haskell-process-suggest-hoogle-imports t) + (haskell-process-type 'cabal-repl) + (haskell-process-use-presentation-mode t) + (haskell-stylish-on-save nil)) + + :config + (defun jao-haskell-hoogle (query) + (interactive (hoogle-prompt)) + (haskell-hoogle query t)) + + (put 'haskell-process-args-cabal-repl + 'safe-local-variable + (apply-partially #'seq-every-p #'stringp)) + + (require 'haskell-doc) + (dolist (h '(interactive-haskell-mode + haskell-doc-mode + haskell-decl-scan-mode + haskell-indentation-mode + haskell-auto-insert-module-template)) + (add-hook 'haskell-mode-hook h)) + + :bind (:map haskell-mode-map + (("C-c C-d" . jao-haskell-hoogle) + ("C-c h" . haskell-hoogle-lookup-from-local) + ("C-c C-c" . haskell-compile)))) + +(require 'haskell) + +(diminish 'interactive-haskell-mode " λ") +(diminish haskell-doc-mode) +(diminish haskell-decl-scan-mode) + +(jao-define-attached-buffer "\\*hoogle\\*.*") + +;; needs cabal install apply-refact +(use-package hlint-refactor + :ensure t + :after (haskell-mode) + :diminish "" + :hook (haskell-mode . hlint-refactor-mode)) + +;;;;; transient +(jao-transient-major-mode haskell + ["Imports" + ("in" "Navigate imports" haskell-navigate-imports) + ("if" "Format imports" haskell-mode-format-imports) + ("is" "Sort imports" haskell-sort-imports) + ("ia" "Align imports" haskell-align-imports)] + ["Code" + ("c" "Compile" haskell-compile) + ("s" "stylish on buffer" haskell-mode-stylish-buffer)] + ["Hoogle" + ("h" "Hoogle" jao-haskell-hoogle) + ("H" "Hoogle from local server" haskell-hoogle-lookup-from-local)]) + +;;;; Pie +(jao-load-path "pie") +(use-package pie + :demand t + :commands (pie-mode)) + +;;;; Prolog +(use-package ediprolog :ensure t) + +(use-package prolog + :ensure t + :commands (run-prolog prolog-mode mercury-mode) + :init (progn + (setq prolog-system 'swi) + (add-to-list 'auto-mode-alist '("\\.pl$" . prolog-mode)) + (setq prolog-consult-string '((t "[%f]."))) + (setq prolog-program-name + '(((getenv "EPROLOG") (eval (getenv "EPROLOG"))) + (eclipse "eclipse") + (mercury nil) + (sicstus "sicstus") + (swi "swipl") + (t "prolog"))))) + +;;;; Racket +(use-package racket-mode + :ensure t + :init (setq racket-show-functions '(racket-show-echo-area) + racket-documentation-search-location 'local) + :config + (jao-define-attached-buffer "\\`\\*Racket REPL") + (jao-define-attached-buffer "\\`\\*Racket Describe" 0.5) + (add-hook 'racket-mode-hook #'paredit-mode) + (require 'racket-xp) + (add-hook 'racket-mode-hook #'racket-xp-mode) + :bind (:map racket-xp-mode-map (("C-c C-S-d" . racket-xp-documentation) + ("C-c C-d" . racket-xp-describe)))) + +;;;; Python +(use-package virtualenvwrapper + :ensure t + :config + (venv-initialize-eshell) + (jao-compilation-env "VIRTUAL_ENV")) + +;;; Text/data formats +;;;; yaml +(use-package yaml-mode :ensure t) + +;;;; json +(use-package json-mode :ensure t) +;; (use-package json-navigator :ensure nil) + +;;; Graphics +;;;; images +(setq image-use-external-converter t) + +;;;; gnuplot +(use-package gnuplot + :ensure t + :commands (gnuplot-mode gnuplot-make-buffer) + :init (add-to-list 'auto-mode-alist '("\\.gp$" . gnuplot-mode))) + +;;; Network +;;;; nm applet +(jao-shell-def-exec jao-nm-applet "nm-applet") + +(defun jao-toggle-nm-applet () + (interactive) + (if (jao-shell-running-p "nm-applet") + (jao-shell-string "killall nm-applet") + (jao-nm-applet))) + +;;;; enwc +(use-package enwc + :ensure t + :custom ((enwc-default-backend 'nm) + (enwc-wired-device "wlp164s0") + (enwc-wireless-device "wlp164s0") + (enwc-display-mode-line nil))) + +;;;; bluetooth +(use-package bluetooth :ensure t) + +;;;; vpn +(use-package jao-mullvad :demand t) + +;;;; ssh +(use-package tramp) +(defun jao-tramp-hosts () + (seq-uniq + (mapcan (lambda (x) + (remove nil (mapcar 'cadr (apply (car x) (cdr x))))) + (tramp-get-completion-function "ssh")) + #'string=)) + +(defun jao-ssh () + (interactive) + (let ((h (completing-read "Host: " (jao-tramp-hosts)))) + (jao-afio-goto-scratch) + (jao-exec-in-term (format "ssh %s" h) (format "*ssh %s*" h)))) + +;;; Chats +;;;; circe +(defvar jao-libera-channels '()) +(defvar jao-oftc-channels '()) +(defvar jao-bitlbee-channels '()) + +(use-package circe + :ensure t + :bind (:map circe-channel-mode-map + (("C-c C-a" . lui-track-jump-to-indicator))) + :init + (setq circe-chat-buffer-name "{target}" + circe-default-realname "https://jao.io" + circe-default-part-message "" + circe-default-quit-message "" + circe-ignore-list nil + circe-server-coding-system '(undecided . undecided) + circe-server-killed-confirmation 'ask-and-kill-all + circe-server-auto-join-default-type :after-auth + circe-format-say "({nick}) {body}" + circe-format-self-say "(jao) {body}" + circe-new-buffer-behavior 'ignore + circe-new-buffer-behavior-ignore-auto-joins t + circe-nickserv-ghost-style 'after-auth + circe-prompt-string ": " + circe-completion-suffix ", " + circe-reduce-lurker-spam t + + circe-nick-next-function + (lambda (old) + (replace-regexp-in-string "-" "`" (circe-nick-next old))) + + circe-lagmon-mode-line-format-string "" ;; "%.0f " + circe-lagmon-mode-line-unknown-lag-string "" ;; "? " + circe-lagmon-timer-tick 120 + circe-lagmon-reconnect-interval 180 + + lui-max-buffer-size 30000 + lui-fill-column 80 + lui-time-stamp-position 'right + lui-time-stamp-format "%H:%M" + lui-flyspell-p nil + + lui-track-indicator (if window-system 'fringe 'bar) + lui-track-behavior 'before-tracking-next-buffer) + :config + + (define-minor-mode jao-circe-user-number-mode "" + :lighter (:eval (format " [%s]" (length (circe-channel-nicks))))) + + (add-hook 'circe-channel-mode-hook #'jao-circe-user-number-mode) + + (defun circe-command-RECOVER (&rest ignore) + "Recover nick" + (let* ((fn (jao--get-user/password "freenode")) + (u (car fn)) + (p (cadr fn))) + (circe-command-MSG "nickserv" (format "IDENTIFY %s %s" u p)) + (circe-command-MSG "nickserv" (format "GHOST %s" u)) + (circe-command-MSG "nickserv" (format "RELEASE %s" u)) + (circe-command-NICK u))) + + (defun circe-command-NNICKS (&rest _) + "Echo number of nicks" + (circe-display-server-message + (format "%d nicks in this channel" (length (circe-channel-nicks))))) + + (advice-add 'circe-command-NAMES :after #'circe-command-NNICKS) + + (setq circe-network-options + (let ((up (jao--get-user/password "libera")) + (oup (jao--get-user/password "oftc")) + (bup (jao--get-user/password "bitlbee"))) + `(("Libera Chat" + :nick ,(car up) :channels ,jao-libera-channels + :tls t :sasl-username ,(car up) :sasl-password ,(cadr up)) + ("OFTC" :nick ,(car oup) :channels ,jao-oftc-channels + :nickserv-password ,(cadr oup) + :tls t :sasl-username ,(car oup) :sasl-password ,(cadr oup)) + ("Bitlbee" + :host "127.0.0.1" :nick ,(car bup) + :channels ,jao-bitlbee-channels + :lagmon-disabled t + :nickserv-password ,(cadr bup) :user ,(car bup))))) + + (jao-shorten-modes 'circe-channel-mode + 'circe-server-mode + 'circe-query-mode) + + (enable-lui-track) + (circe-lagmon-mode) + (enable-circe-display-images)) + +;;;; slack +(use-package slack + :commands (slack-start) + :init + (setq slack-alert-icon (jao-data-file "slack.svg") + slack-buffer-emojify nil + slack-buffer-create-on-notify t + slack-display-team-name t + slack-typing-visibility 'never ;; 'buffer, 'frame + slack-profile-image-file-directory "/tmp/slack-imgs/" + slack-image-file-directory "/tmp/slack-imgs/" + slack-file-dir "~/var/download/slack/" + slack-prefer-current-team t + slack-message-tracking-faces '(warning) + slack-log-level 'warn + slack-message-custom-notifier (lambda (msg room team) room)) + :bind (:map slack-mode-map (("@" . slack-message-embed-mention) + ("#" . slack-message-embed-channel)) + :map slack-message-buffer-mode-map + (("C-c C-e" . slack-message-edit) + ("C-c C-a" . slack-file-upload))) + :config + (dolist (f (list slack-file-dir slack-image-file-directory)) + (when (not (file-exists-p f)) (make-directory f))) + + (jao-shorten-modes 'slack-message-buffer-mode + 'slack-thread-message-buffer-mode) + (jao-tracking-faces 'warning) + + (jao-define-attached-buffer "\\*Slack .+ Edit Message [0-9].+" 20)) + +;;;; telegram +(use-package telega + :ensure t + :custom + (telega-use-tracking-for '(unmuted) ;; '(or unmuted mention) + telega-rainbow-color-custom-for nil + telega-msg-rainbow-title nil + telega-sticker-set-download t) + :config + (define-key global-map (kbd "C-c C-t") telega-prefix-map) + (setq telega-chat-show-avatars nil + telega-chat-prompt-format ">> " + telega-root-show-avatars nil + telega-emoji-use-images nil + telega-temp-dir "/tmp/telega" + telega-symbol-checkmark "·" + telega-symbol-heavy-checkmark "×" + telega-symbol-verified "*" + telega-symbol-horizontal-bar + (propertize "-" 'face 'jao-themes-f00) + telega-symbol-vertical-bar + (propertize "| " 'face 'jao-themes-dimm) + telega-mode-line-string-format + '(:eval (telega-mode-line-unread-unmuted)) + telega-use-images (display-graphic-p)) + (with-eval-after-load "tracking" + (jao-shorten-modes 'telega-chat-mode) + (jao-tracking-faces 'telega-tracking)) + (telega-mode-line-mode 1)) +;; telegram:1 ends here + +;; [[file:init.org::*startup][startup:1]] +(defun jao-chats (&optional p) + (interactive "P") + (when (or p (y-or-n-p "Connect to slack? ")) + (slack-start)) + (when (or p (y-or-n-p "Connect to telegram? ")) + (telega)) + (when (or p (y-or-n-p "Connect to libera? ")) + (unless (get-buffer "irc.libera.chat:6697") + (circe "Libera Chat")))) + +(defun jao-all-chats () (interactive) (jao-chats t)) + +(defun jao-chats-telega () + (interactive) + (jao-buffer-same-mode '(telega-root-mode telega-chat-mode))) + +(defun jao-chats-slack () + (interactive) + (jao-buffer-same-mode 'slack-message-buffer-mode)) + +(defun jao-chats-irc () + (interactive) + (jao-buffer-same-mode '(circe-channel-mode circe-query-mode erc-mode))) + +;;; Multimedia +;;;; mixer +(defun jao-mixer-get-level (&optional dev) + (interactive) + (let* ((dev (or dev "Master")) + (s (shell-command-to-string (format "amixer sget %s" dev))) + (s (car (last (split-string s "\n" t))))) + (when (string-match ".*Front .*\\[\\([0-9]+\\)%\\] .*" s) + (let ((level (match-string 1 s))) + (message "%s level: %s%%" dev level) + (string-to-number level))))) + +(defun jao-mixer-set (dev v) + (jao-shell-string "amixer sset" dev v) + (jao-mixer-get-level dev)) + +(defun jao-mixer-master-toggle () + (interactive) + (jao-mixer-set "Master" "toggle")) + +(defun jao-mixer-master-up () + (interactive) + (jao-mixer-set "Master" "10%+")) + +(defun jao-mixer-master-down () + (interactive) + (jao-mixer-set "Master" "10%-")) + +(defun jao-mixer-capture-up () + (interactive) + (jao-mixer-set "Capture" "10%+")) + +(defun jao-mixer-capture-down () + (interactive) + (jao-mixer-set "Capture" "10%-")) + +(jao-shell-def-exec jao-audio-applet "pasystray") + +(defun jao-toggle-audio-applet () + (interactive) + (if (string-empty-p (jao-shell-string "pidof pasystray")) + (jao-audio-applet) + (jao-shell-string "killall pasystray"))) + +(global-set-key (kbd "") #'jao-toggle-audio-applet) + +;;;; mpris +(defun jao-mpris-lyrics (&optional force) + (interactive "P") + (jao-show-lyrics force #'jao-mpris-artist-title)) + +(use-package jao-mpris :demand t) + +(defalias 'jao-streaming-list #'ignore) +(defalias 'jao-streaming-like #'ignore) +(defalias 'jao-streaming-dislike #'ignore) +(defalias 'jao-streaming-lyrics #'jao-mpris-lyrics) +(defalias 'jao-streaming-toggle #'jao-mpris-play-pause) +(defalias 'jao-streaming-next #'jao-mpris-next) +(defalias 'jao-streaming-prev #'jao-mpris-previous) +(defalias 'jao-streaming-current #'jao-mpris-show-osd) +(defalias 'jao-streaming-seek #'jao-mpris-seek) +(defalias 'jao-streaming-seek-back #'jao-mpris-seek-back) +(defalias 'jao-streaming-volume #'jao-mpris-vol) +(defalias 'jao-streaming-volume-down #'jao-mpris-vol-down) + +(jao-mpris-register "playerctld" + :session (if jao-modeline-in-minibuffer -10 70)) + +;;;; mpc +(use-package jao-mpc + :demand t + :commands jao-mpc-setup) + +(defvar jao-mopidy-port 6669) +(defvar jao-mpc-last-port nil) + +(defun jao-mpc-toggle-port () + (interactive) + (setq jao-mpc-last-port (unless jao-mpc-last-port jao-mopidy-port) + jao-mpc-port jao-mpc-last-port)) + +(jao-mpc-setup jao-mopidy-port (if jao-modeline-in-minibuffer -10 70)) + +(defun jao-mpc-pport (&optional mop) + (cond ((or mop (jao-mpc--playing-p jao-mopidy-port)) jao-mopidy-port) + ((jao-mpc--playing-p) nil) + (t jao-mpc-last-port))) + +(defmacro jao-defun-play (name &optional mpc-name) + (let ((arg (gensym))) + `(defun ,(intern (format "jao-player-%s" name)) (&optional ,arg) + (interactive "P") + (,(intern (format "jao-mpc-%s" (or mpc-name name))) + (setq jao-mpc-last-port (jao-mpc-pport ,arg)))))) + +(jao-defun-play toggle) +(jao-defun-play next) +(jao-defun-play previous) +(jao-defun-play stop) +(jao-defun-play echo echo-current) +(jao-defun-play list show-playlist) +(jao-defun-play info lyrics-track-data) +(jao-defun-play browse show-albums) +(jao-defun-play select-album) + +(defun jao-player-seek (delta) (jao-mpc-seek delta (jao-mpc-pport))) + +(defalias 'jao-player-connect 'jao-mpc-connect) +(defalias 'jao-player-play 'jao-mpc-play) + +;;;; music transients +(require 'jao-lyrics) +(setq jao-lyrics-info-function #'jao-player-info) + +(defun jao-player-volume-delta (raise) + (jao-player-vol-delta (if raise 5 -5)) + (sit-for 0.05) + (jao-player-show-volume)) + +(defun jao-player-volume-raise () + (interactive) + (jao-player-volume-delta t)) + +(defun jao-player-volume-lower () + (interactive) + (jao-player-volume-delta nil)) + +(defun jao-player-show-volume () + (interactive) + (jao-notify "Volume" (format "%s%%" (jao-player-volume)))) + +(defun jao-player-seek-10 () (interactive) (jao-player-seek 10)) +(defun jao-player-seek--10 () (interactive) (jao-player-seek -10)) + +(defun jao-streaming-clear () (interactive) (jao-mpc-clear jao-mopidy-port)) + +(defun jao-streaming-echo-current () + (interactive) + (jao-mpc-echo-current jao-mopidy-port)) + +(defun jao-streaming-show-playlist () + (interactive) + (jao-mpc-show-playlist jao-mopidy-port)) + +(defun jao-player-level-cap () (interactive) (jao-mixer-level "Capture")) + +(use-package jao-random-album :demand t) + +(jao-def-exec-in-term "aptitude" "aptitude" (jao-afio--goto-scratch)) +(jao-def-exec-in-term "htop" "htop" (jao-afio--goto-scratch)) + +(transient-define-prefix jao-transient-streaming () + [:description + (lambda () (format "Streaming using %s" jao-mpris-player)) + ;; ["Search" + ;; ("a" "album" jao-streaming-album) + ;; ("A" "artist" jao-streaming-artist) + ;; ("t" "track" jao-streaming-track) + ;; ("P" "playlist" jao-streaming-playlist)] + ["Play" + ("s" "toggle" jao-streaming-toggle) + ("n" "next" jao-streaming-next) + ("p" "previous" jao-streaming-prev)] + ["Seek & shout" + ("f" "seek fwd" jao-streaming-seek :transient t) + ("F" "seek bwd" jao-streaming-seek-back :transient t) + ("u" "up" jao-streaming-volume :transient t) + ("d" "down" jao-streaming-volume-down :transient t)] + ["Browse" + ("l" "playing list" jao-streaming-list) + ("L" "lyrics" jao-streaming-lyrics) + ("w" "currently playing" jao-streaming-current)] + ["Act" + ("k" "like" jao-streaming-like) + ("K" "dislike" jao-streaming-dislike)]]) + +(transient-define-prefix jao-transient-media () + [["Play" + ("m" "toggle" jao-player-toggle) + ("n" "next" jao-player-next) + ("p" "previous" jao-player-previous) + ("s" "select album" jao-player-select-album)] + ["Seek and search" + ("f" "seek fwd" jao-player-seek-10 :transient t) + ("F" "seek bwd" jao-player-seek--10 :transient t) + ("a" "search album" jao-mpc-search-and-select-album)] + ["Browse" + ("b" "browse" jao-player-browse) + ("l" "show play list" jao-player-list) + ("L" "show lyrics" jao-show-lyrics) + ("w" "now playing" jao-player-echo)] + ["Master volume" + ("d" "master down" jao-mixer-master-down :transient t) + ("u" "master up" jao-mixer-master-up :transient t) + ("M" "master toggle" jao-mixer-master-toggle) + ("v" "show" jao-mixer-get-level)] + ["Capture volume" + ("D" "capture down" jao-mixer-capture-down :transient t) + ("U" "capture up" jao-mixer-capture-up :transient t) + ("V" "show" jao-player-level-cap)] + ["Utilities" + ("c" "reconnect to mpd" jao-player-connect) + ("N" "next random album" jao-random-album-next) + ("r" (lambda () + (concat (if jao-random-album-p "dis" "en") "able random album")) + jao-random-album-toggle) + ("P" (lambda () + (concat "Toggle to " + (if (equal jao-mpc-last-port jao-mopidy-port) + "mpd" "mopidy"))) + jao-mpc-toggle-port)]]) + +(global-set-key (kbd "s-m") #'jao-transient-media) + +;;; Global transients +(defun jao-list-packages () + (interactive) + (jao-afio--goto-scratch-1) + (package-list-packages)) + +(defun jao-window-system-p () + (or jao-exwm-enabled jao-xmonad-enabled jao-sway-enabled)) + +(transient-define-prefix jao-transient-utils () + "Global operations in X11." + [["Notes" + ("n" "capture note" jao-org-notes-open-or-create) + ("/" "search notes" jao-org-notes-open) + ("\\" "grep notes" jao-org-notes-grep)] + ["Documents" :if jao-window-system-p + ("d" "go to doc" jao-select-pdf) + ("D" "open to doc" jao-open-doc)] + ["Packages" + ("a" "aptitude" jao-term-aptitude) + ("l" "packages" jao-list-packages)] + ["Monitors" + ("p" "htop" jao-term-htop) + ("v" "vpn status" jao-mullvad-status) + ("m" "set tmr" tmr)] + ["Network" + ("S" "ssh" jao-ssh) + ("b" "bluetooth" bluetooth-list-devices) + ("c" "connect chats" jao-all-chats) + ("N" "network interfaces" enwc)] + ["Chats" + ("t" "telegram" jao-chats-telega) + ("s" "slack" jao-chats-slack) + ("i" "irc" jao-chats-irc) + ("T" "telegram rooster" telega)] + ["Window system" :if jao-window-system-p + ("w" "set wallpaper" jao-set-wallpaper) + ("W" "set radom wallpaper" jao-set-random-wallpaper) + ("x" "restart xmobar" jao-xmobar-restart :if jao-exwm-enabled-p) + ("x" "kill xmobar" jao-xmobar-kill :if jao-xmonad-enabled-p)] + ["Helpers" + ("r" "org reveal" org-reveal) + ("k" (lambda () (concat "keyboard" (when (jao-kb-toggled-p) "*"))) + jao-kb-toggle :if jao-window-system-p) + ("M" (lambda () (concat "minibuffer" (when jao-minibuffer-mode "*"))) + jao-minibuffer-mode)]]) + +(global-set-key (kbd "s-w") #'jao-transient-utils) + +;;; Global key bindings +(global-set-key "\C-cj" #'join-line) +(global-set-key "\C-cn" #'next-error) +(global-set-key "\C-cq" #'auto-fill-mode) +(global-set-key "\C-xr\M-w" #'kill-rectangle-save) +(global-set-key "\C-c\C-z" #'comment-or-uncomment-region) +(global-set-key "\C-z" #'comment-or-uncomment-region) +(global-set-key (kbd "C-c W") #'jao-open-in-x-frame) + +;;; Last minute (post.el) +(jao-load-site-el "post") diff --git a/init.org b/init.org deleted file mode 100644 index fcd69fb..0000000 --- a/init.org +++ /dev/null @@ -1,3427 +0,0 @@ -#+property: header-args :lexical t :tangle yes :comments yes :results silent :shebang ";; -*- lexical-binding: t -*-" :tangle-mode (identity #o644) - -* Packages -*** Use package - #+begin_src emacs-lisp - (unless (package-installed-p 'use-package) - (package-refresh-contents) - (package-install 'use-package)) - (require 'use-package) - #+end_src -*** ELPA Keyring - #+begin_src emacs-lisp - (use-package gnu-elpa-keyring-update :ensure t) - #+end_src -*** Loading .el newer than .elc files, and eln stuff - #+begin_src emacs-lisp - (setq load-prefer-newer t) - (setq comp-async-report-warnings-errors nil - warning-suppress-types '((comp))) - #+end_src -* Initialisation -*** Paths - #+begin_src emacs-lisp - (defvar jao-local-lisp-dir "~/lib/elisp" - "Directory for external elisp libraries and repos") - - (defvar jao-data-dir (expand-file-name "data" jao-emacs-dir) - "Directory containing static data, such as images.") - - (defun jao-data-file (file) (expand-file-name file jao-data-dir)) - - (defvar jao-org-dir (expand-file-name "~/org")) - - (defvar jao-sink-dir - (file-name-as-directory (expand-file-name "~/doc/sink")) - "Directory used for downloads and such.") - - (defvar jao-site-dir (expand-file-name "site" jao-emacs-dir)) - (defun jao-site-el (basename &optional gpg) - (expand-file-name (concat basename ".el" (when gpg ".gpg")) jao-site-dir)) - - (defun jao-load-site-el (basename &optional gpg) - (let ((lf (jao-site-el basename gpg))) - (if (file-exists-p lf) - (load lf) - (message "Attempted to load non existing %s" lf)))) - - (defun jao-exec-path (file) - (let ((fn (expand-file-name file))) - (add-to-list 'exec-path fn nil) - (setenv "PATH" (concat fn ":" (getenv "PATH"))))) - - (defun jao-load-path (subdir) - "Add to load path a subdir of `jao-local-lisp-dir'" - (let ((path (expand-file-name subdir jao-local-lisp-dir))) - (when (file-directory-p path) (add-to-list 'load-path path)))) - #+end_src -*** Load and info path initialisation - #+begin_src emacs-lisp - (add-to-list 'load-path jao-site-dir) - (add-to-list 'load-path jao-local-lisp-dir) - (add-to-list 'load-path (expand-file-name "custom" jao-emacs-dir)) - (add-to-list 'load-path "/usr/local/share/emacs/site-lisp/") - - (let ((libd (expand-file-name "lib" jao-emacs-dir))) - (add-to-list 'load-path libd) - (dolist (f (directory-files libd t "^[^.]+$")) - (when (file-directory-p f) (add-to-list 'load-path f)))) - - (defvar jao-info-dir (expand-file-name "~/doc/info")) - (require 'info) - (add-to-list 'Info-directory-list jao-info-dir) - #+end_src -*** Custom location of custom.el and co. - #+begin_src emacs-lisp - (setq custom-file (jao-site-el "custom")) - ;; (load custom-file) - (setq custom-unlispify-tag-names nil) - (setq custom-buffer-done-kill t) - (setq custom-raised-buttons nil) - #+end_src -*** Preamble (pre.el) - #+begin_src emacs-lisp - (jao-load-site-el "pre") - #+end_src -*** Session and history - #+BEGIN_SRC emacs-lisp - (setq backup-directory-alist (quote (("." . "~/.emacs.d/backups")))) - (setq delete-old-versions t - kept-new-versions 3 - kept-old-versions 2) - - (require 'saveplace) - (setq save-place-file (expand-file-name "~/.emacs.d/cache/places")) - (save-place-mode 1) - - (setq recentf-save-file (expand-file-name "~/.emacs.d/cache/recentf") - recentf-max-saved-items 2000 - recentf-exclude '("/home/jao/\\.emacs\\.d/elpa.*/.*" - ".*/.git/COMMIT_EDITMSG")) - (require 'recentf) - (recentf-mode 1) - - ;; Command history - (setq savehist-file (expand-file-name "~/.emacs.d/cache/history")) - (require 'savehist) - (savehist-mode t) - (setq savehist-additional-variables - '(kill-ring search-ring regexp-search-ring) - savehist-ignored-variables - '(ido-file-history)) - #+END_SRC -*** yes/no, bell, startup message - #+BEGIN_SRC emacs-lisp - ;;; change yes/no for y/n - (if (version< emacs-version "28") - (fset 'yes-or-no-p 'y-or-n-p) - (setq use-short-answers t)) - (setq inhibit-startup-message t) - (setq visible-bell t) - #+END_SRC -*** Server - #+begin_src emacs-lisp - (setenv "EDITOR" "emacsclient") - ;; (unless (daemonp) (server-start)) - #+end_src -* System utilities -*** Tramp - #+begin_src emacs-lisp - (setq tramp-mode nil) - #+end_src -*** Sleep/awake - #+begin_src emacs-lisp - (use-package jao-sleep) - (jao-sleep-dbus-register) - #+end_src -*** Process runners - #+begin_src emacs-lisp - (use-package jao-shell - :demand t - :config (jao-shell-def-exec jao-trayer "trayer.sh") - :bind (("s-r" . jao-shell-exec))) - - #+end_src -*** App launcher - #+begin_src emacs-lisp - (jao-load-path "app-launcher") - (use-package app-launcher - :bind (("s-R" . app-launcher-run-app))) - #+end_src -*** Brightness control - #+begin_src emacs-lisp - (jao-shell-def-exec jao-bright-set-up "brightnessctl" "-q" "s" "5%+") - (jao-shell-def-exec jao-bright-set-down "brightnessctl" "-q" "s" "5%-") - - (defun jao-bright-show () - (interactive) - (message "%s" (thread-first (jao-shell-string "brightnessctl") - (split-string "\n") - (cadr) - (string-trim)))) - - (defun jao-bright-up () - (interactive) - (jao-shell-string "brightnessctl -q s 5%%+") - (jao-bright-show)) - - (defun jao-bright-down () - (interactive) - (jao-shell-string "brightnessctl -q s 5%%-") - (jao-bright-show)) - #+end_src -*** Keyboard - #+begin_src emacs-lisp - (use-package repeat - :config (setq repeat-echo-function #'repeat-echo-mode-line)) - (when (> emacs-major-version 27) (repeat-mode)) - - (defun jao-kb-toggle (&optional lyt) - (interactive) - (shell-command-to-string (or lyt - (if (jao-kb-toggled-p) - "setxkbmap us" - "setxkbmap us -variant intl")))) - - (defun jao-kb-toggled-p () - (not (string-empty-p - (shell-command-to-string "setxkbmap -query|grep variant")))) - - (set-keyboard-coding-system 'latin-1) - (set-language-environment "UTF-8") - ;; must be set after current-language-environment - (customize-set-variable 'default-input-method "catalan-prefix") - ;; http://mbork.pl/2022-03-07_Transient_input_method - (customize-set-variable 'default-transient-input-method "TeX") - - (defun jao--set-kb-system (frame) - (select-frame frame) - (set-keyboard-coding-system 'latin-1) - t) - (add-to-list 'after-make-frame-functions 'jao--set-kb-system) - - (setq echo-keystrokes 1 - suggest-key-bindings nil) - #+end_src -*** Transient - #+begin_src emacs-lisp - (use-package transient - :init (setq transient-show-popup t) ;; 2.0 - :demand t - :config - (transient-bind-q-to-quit)) - - (defmacro jao-transient-major-mode (mode &rest suffix) - (declare (indent defun)) - (let ((mode (intern (format "%s-mode" mode))) - (mmap (intern (format "%s-mode-map" mode))) - (name (intern (format "jao-transient-%s" mode)))) - `(progn - (transient-define-prefix ,name () - ,(format "Transient ops for %s" mode) - [,(format "Operations for %s" mode) :if-derived ',mode ,@suffix]) - (define-key ,mmap (kbd "s-SPC") #',name)))) - - (defmacro jao-transient-major-mode+1 (mode suffix) - (declare (indent defun)) - (let ((name (intern (format "jao-transient-%s" mode)))) - (if (fboundp name) - `(transient-append-suffix ',name '(0 -1) ,suffix) - `(jao-transient-major-mode ,mode ,suffix)))) - - (defmacro jao-transient-major-mode+ (mode &rest suffixes) - (declare (indent defun)) - `(progn ,@(mapcar (lambda (s) `(jao-transient-major-mode+1 ,mode ,s)) - suffixes))) - - #+end_src -*** Disk - #+begin_src emacs-lisp - (when (featurep 'multisession) - (use-package jao-dirmon)) - #+end_src -* Crypto -*** PGP, EPG, passwords - #+begin_src emacs-lisp - (setq epg-pinentry-mode 'loopback) - (setq auth-source-debug nil) - - (require 'auth-source) - (add-to-list 'auth-source-protocols '(local "local")) - (setq auth-sources '("~/.emacs.d/authinfo.gpg" "~/.netrc")) - - (use-package epa-file - :init (setq epa-file-cache-passphrase-for-symmetric-encryption t) - :config (epa-file-enable)) - (require 'epa-file) - - (defun jao--get-user/password (h) - (let ((item (car (auth-source-search :type 'netrc :host h :max 1)))) - (when item - (let ((user (plist-get item :user)) - (pwd (plist-get item :secret))) - (list user (when pwd (funcall pwd))))))) - #+end_src -*** pass - #+begin_src emacs-lisp - (use-package password-store :ensure t - :bind (("C-c p" . jao-transient-password))) - - (transient-define-prefix jao-transient-password () - [[("c" "copy secret" password-store-copy) - ("C" "copy field" password-store-copy-field)] - [("i" "insert entry" password-store-insert) - ("e" "edit entry" password-store-edit) - ("g" "generate password" password-store-generate)] - [("d" "delete entry" password-store-remove) - ("r" "rename entry" password-store-rename)]]) - - #+end_src -*** Pinentry - #+begin_src emacs-lisp - (use-package pinentry :ensure t) - (pinentry-start) - #+end_src -* Fonts and colour themes -*** Widgets - #+begin_src emacs-lisp - (setq widget-image-enable nil - widget-link-prefix "" - widget-link-suffix "" - widget-button-prefix " " - widget-button-suffix " " - widget-push-button-prefix "" - widget-push-button-suffix "") - #+end_src -*** Fonts -***** fontsets - See [[https://emacs.stackexchange.com/questions/251/line-height-with-unicode-characters/5386#5386][fonts - Line height with unicode characters]] for a good - discussion. - #+begin_src emacs-lisp - (defun jao--set-fontsets (_f) - (set-fontset-font t 64257 "Quivira" nil) - (set-fontset-font t 'egyptian "Noto Sans Egyptian Hieroglyphs" nil) - (set-fontset-font t 'hangul "NanumGothicCoding" nil) - (set-fontset-font t 'unicode (face-attribute 'default :family) nil) - (set-fontset-font t 'unicode-bmp (face-attribute 'default :family) nil) - (set-fontset-font t 'symbol "Symbola-10" nil) - (set-fontset-font t 'greek "GFS Didot" nil) - (set-fontset-font t 'mathematical "FreeSerif" nil) - (set-fontset-font t 'emoji "Noto Color Emoji" nil) - ;; boxes - (set-fontset-font t '(9472 . 9599) "Source Code Pro" nil) - ;; variation selector-16 - (set-fontset-font t 65039 "BabelStone Modern-1" nil)) - - (jao--set-fontsets nil) - (add-to-list 'after-make-frame-functions 'jao--set-fontsets) - - #+end_src -***** nobreak char display - #+begin_src emacs-lisp - (setq nobreak-char-display nil) - #+end_src -***** list-fonts-display - #+begin_src emacs-lisp - (defun list-fonts-display (&optional matching) - "Display a list of font-families available via font-config, in a new buffer. - If the optional argument MATCHING is non-nil, only - font families matching that regexp are displayed; - interactively, a prefix argument will prompt for the - regexp. The name of each font family is displayed - using that family, as well as in the default font (to - handle the case where a font cannot be used to display - its own name)." - (interactive - (list - (and current-prefix-arg - (read-string "Display font families matching regexp: ")))) - (let (families) - (with-temp-buffer - (shell-command "fc-list : family" t) - (goto-char (point-min)) - (while (not (eobp)) - (let ((fam (buffer-substring (line-beginning-position) - (line-end-position)))) - (when (or (null matching) (string-match matching fam)) - (push fam families))) - (forward-line))) - (setq families - (sort families - (lambda (x y) (string-lessp (downcase x) (downcase y))))) - (let ((buf (get-buffer-create "*Font Families*"))) - (with-current-buffer buf - (erase-buffer) - (dolist (family families) - ;; We need to pick one of the comma-separated names to - ;; actually use the font; choose the longest one because some - ;; fonts have ambiguous general names as well as specific - ;; ones. - (let ((family-name - (car (sort (split-string family ",") - (lambda (x y) (> (length x) (length y)))))) - (nice-family (replace-regexp-in-string "," ", " family))) - (insert (concat (propertize nice-family - 'face (list :family family-name)) - " (" nice-family ")")) - (newline))) - (goto-char (point-min))) - (display-buffer buf)))) - #+end_src -*** Vertical separator - #+begin_src emacs-lisp - (unless (display-graphic-p) - (set-display-table-slot standard-display-table - 'vertical-border - (make-glyph-code ?│))) - #+end_src -*** Themes - #+begin_src emacs-lisp - (defun jao-colors-scheme-dark-p () - (equal "dark" (getenv "JAO_COLOR_SCHEME"))) - - (setq custom-theme-directory - (expand-file-name "lib/themes" jao-emacs-dir)) - - (require 'jao-themes) - - (defvar jao-theme-dark 'jao-dark) - (defvar jao-theme-light 'jao-light) - (defvar jao-theme-term-dark 'modus-vivendi) - (defvar jao-theme-term-light 'jao-light) - - (defun jao-themes-setup () - (let* ((dark (jao-colors-scheme-dark-p)) - (theme (cond ((and dark window-system) jao-theme-dark) - (dark jao-theme-term-dark) - (window-system jao-theme-light) - (t jao-theme-term-light)))) - (load-theme theme t) - (modify-all-frames-parameters `((font . ,jao-themes-default-face))))) - - (unless (eq window-system 'pgtk) (jao-themes-setup)) - - (global-font-lock-mode 1) - #+end_src -* Help system -*** Help buffers and shortcuts - #+begin_src emacs-lisp - (setq help-window-select t - help-link-key-to-documentation t) - - (use-package find-func - :bind (("C-h C-v" . find-variable) - ("C-h C-f" . find-function) - ("C-h C-k" . find-function-on-key) - ("C-h C-l" . find-library))) - #+end_src -*** eldoc - #+begin_src emacs-lisp - (use-package eldoc - :init (setq eldoc-mode-line-string nil - eldoc-echo-area-use-multiline-p t - eldoc-echo-area-prefer-doc-buffer nil - eldoc-display-functions '(eldoc-display-in-echo-area)) - :config (global-eldoc-mode 1) - :diminish ((eldoc-mode . ""))) - #+end_src -*** Bookmarks - #+begin_src emacs-lisp - (setq bookmark-default-file "~/.emacs.d/emacs.bmk" - bookmark-set-fringe-mark nil) - - #+end_src -*** Man pages - #+begin_src emacs-lisp - (setq Man-notify-method 'pushy) ;; pushy - same window - #+end_src -*** Recoll - #+begin_src emacs-lisp - (use-package jao-recoll) - #+end_src -* Window manager helpers -*** transparency - #+begin_src emacs-lisp - (defvar jao-transparent-only-bg (> emacs-major-version 28)) - - (defvar jao-frames-default-alpha - (cond ((eq window-system 'pgtk) 80) - (jao-transparent-only-bg 88) - (t 85))) - - (defvar jao-transparent-frame (< jao-frames-default-alpha 100)) - - (defun jao-transparent-p () jao-transparent-frame) - - (defun jao-alpha-parameters (&optional level) - (let ((level (or level jao-frames-default-alpha))) - (if jao-transparent-only-bg - `((alpha-background . ,level) (alpha)) - `((alpha . ,(cons level level)) (alpha-background))))) - - (defun jao-set-transparency (&optional level all) - (interactive "nOpacity (0-100): ") - (let ((level (or level jao-frames-default-alpha))) - (setq jao-transparent-frame (< level 100)) - (if all - (modify-all-frames-parameters (jao-alpha-parameters level)) - (modify-frame-parameters nil (jao-alpha-parameters level))))) - - (defun jao-toggle-transparency (&optional all) - (interactive "P") - (let ((level (if jao-transparent-frame 100 jao-frames-default-alpha))) - (jao-set-transparency level all))) - - #+end_src -*** exwm - #+begin_src emacs-lisp - (defvar jao-exwm-enabled nil) - (defun jao-exwm-enabled-p () jao-exwm-enabled) - - (defun jao-exwm-enable () - (require 'jao-custom-exwm) - (setq jao-exwm-enabled t) - (display-time-mode -1) - (exwm-enable) - (setq jao-frames-default-alpha 88) - (jao-set-transparency) - (x-change-window-property "_XMONAD_TRAYPAD" "" nil nil nil nil 0) - (jao-trisect t)) - #+end_src -*** xmonad - #+begin_src emacs-lisp - (defvar jao-xmonad-enabled (string= "xmonad" (or (getenv "wm") ""))) - (defun jao-xmonad-enabled-p () jao-xmonad-enabled) - - (defun jao-xmonad-enable () - (setq jao-browse-doc-use-emacs-p t) - (setq jao-wallpaper-random-wake t) - ;; (jao-set-transparency) - (jao-trisect) - (message "Welcome to xmonad")) - - (when jao-xmonad-enabled - (add-hook 'after-init-hook #'jao-xmonad-enable t)) - - #+end_src -*** sway - When starting emacs inside a sway session, we use ~-f - jao-sway-enable~ and don't load any separate configuration file. - - #+begin_src emacs-lisp - (defun jao-swaymsg (msg) - (shell-command (format "swaymsg '%s' >/dev/null" msg))) - - (defmacro jao-def-swaymsg (name msg) - `(defun ,(intern (format "jao-sway-%s" name)) () - (interactive) - (jao-swaymsg ,msg))) - (jao-def-swaymsg firefox "[app_id=firefox] focus") - - (defvar jao-sway-enabled - (and (eq window-system 'pgtk) (not jao-xmonad-enabled))) - - (defun jao-sway-set-wallpaper (f) - (jao-swaymsg (format "output * bg %s fill" f)) - (make-symbolic-link f "~/.wallpaper.sway" t)) - - (defun jao-sway-run-or-focus (cmd &optional ws) - (if (jao-shell-running-p "firefox") - (jao-swaymsg (format "[app_id=%s] focus" cmd)) - (jao-swaymsg (format "workspace %s" (or ws 2))) - (start-process-shell-command cmd nil cmd))) - - (defun jao-sway-run-or-focus-tidal () - (interactive) - (if (jao-shell-running-p "tidal-hifi") - (jao-swaymsg "[app_id=tidal-hifi] scratchpad show") - (start-process-shell-command "tidal-hifi" nil "tidal-hifi &") - (jao-sway-run-or-focus-tidal))) - - (defun jao-sway-run-or-focus-firefox () - (interactive) - (jao-sway-run-or-focus "firefox")) - - (defun jao-sway-enable () - (setq jao-browse-doc-use-emacs-p t) - (setq jao-wallpaper-random-wake nil) - (jao-trisect) - (jao-set-transparency 85) - (jao-themes-setup) - ;; (display-time-mode 1) - (global-set-key (kbd "s-f") #'jao-sway-run-or-focus-firefox) - (defalias 'jao-streaming-list #'jao-sway-run-or-focus-tidal) - (message "Welcome to sway")) - - (when jao-sway-enabled - (defalias 'x-change-window-property #'ignore) - (add-hook 'after-init-hook #'jao-sway-enable)) - - #+end_src -*** wallpaper - #+begin_src emacs-lisp - (defvar jao-wallpaper-dir "~/.wallpapers/") - - (defvar jao-wallpaper-random-candidates - '("wallpaper.jpg" "wallpaper2.jpg")) - - (defvar jao-wallpaper-random-candidates-light - '("wallpaper.jpg" "wallpaper2.jpg")) - - (defvar jao-wallpaper-random-wake t - "Set to t for getting a new wallpaper on awaking from sleep") - - (defun jao-set-wallpaper (&optional path) - (interactive) - (let ((current (format "~/.wallpaper.%s" - (if (jao-colors-scheme-dark-p) "dark" "light")))) - (when-let ((f (or path - (read-file-name "Image: " - jao-wallpaper-dir - (file-symlink-p current) - t)))) - (make-symbolic-link (expand-file-name f) current t) - (if jao-sway-enabled - (jao-sway-set-wallpaper (expand-file-name f)) - (shell-command (format "xwallpaper --zoom %s" f)))))) - - (defun jao-set-random-wallpaper () - (interactive) - (when (or (called-interactively-p 'interactive) - jao-wallpaper-random-wake) - (let* ((ws (if (jao-colors-scheme-dark-p) - jao-wallpaper-random-candidates - jao-wallpaper-random-candidates-light)) - (f (seq-random-elt ws))) - (jao-set-wallpaper (expand-file-name f jao-wallpaper-dir)) - (message "%s" f)))) - - (add-to-list 'jao-sleep-awake-functions #'jao-set-random-wallpaper) - #+end_src -*** screensaver and lock - #+begin_src emacs-lisp - (defun jao-screensaver-enabled () - (string= (jao-shell-string "xdg-screensaver status") "enabled")) - - (defun jao-screensaver-toggle () - (interactive) - (let ((wid (jao-shell-string "xdotool getwindowfocus"))) - (if (jao-screensaver-enabled) - (jao-shell-string "xdg-screensaver suspend" wid) - (jao-shell-string "xdg-screensaver resume" wid)) - (jao-notify (format "Using '%s'" - (jao-shell-string "xdotool getwindownames" wid)) - (format "Screensaver %s" - (jao-shell-string "xdg-screensaver status"))))) - - (jao-shell-def-exec jao-xlock-screen "xdg-screensaver" "activate") - (jao-shell-def-exec jao-suspend "sudo" "systemctl" "suspend") - (jao-shell-def-exec jao-poweroff "sudo" "systemctl" "poweroff") - - (defun jao-lock-screen () - (interactive) - (if jao-sway-enabled - (shell-command "swaylock -i ~/.lockimage") - (jao-xlock-screen))) - - (transient-define-prefix jao-transient-sleep () - ["Sleep" - ("l" "lock screen" jao-lock-screen) - ("z" "sleep" jao-suspend) - ("u" "enable/disable screensaver" jao-screensaver-toggle) - ("poof" "power-off" jao-poweroff)]) - - #+end_src -*** mouse - #+begin_src emacs-lisp - (dolist (k '([mouse-3] - [down-mouse-3] - [drag-mouse-3] - [double-mouse-3] - [mouse-4] - [down-mouse-4] - [drag-mouse-4] - [double-mouse-4] - [triple-mouse-4] - [mouse-5] - [down-mouse-5] - [drag-mouse-5] - [double-mouse-5] - [triple-mouse-5])) - (global-unset-key k)) - #+end_src -*** X clipboard - #+BEGIN_SRC emacs-lisp - (setq select-enable-clipboard t - select-enable-primary t - selection-timeout 100) - #+END_SRC -*** pop-up frames - #+begin_src emacs-lisp - (defun jao-open-in-x-frame (&optional width height) - (interactive) - (make-frame `((window-system . x) - (name . "emacs popup") - (width . ,(or width (window-width))) - (height . ,(or height (window-height))))) - (define-key (current-local-map) "q" #'delete-frame)) - #+end_src -*** xmobar - #+begin_src emacs-lisp - (defun jao-xmobar-kill () - (interactive) - (shell-command "killall xmobar-exwm")) - - (defun jao-xmobar-restart () - (interactive) - (jao-xmobar-kill) - (start-process "" nil "xmobar-exwm" "-d")) - - #+end_src -* Mode line and minibuffer -*** Time display - #+begin_src emacs-lisp - (setq display-time-world-list - '(("Europe/Paris" "Barcelona") - ("America/Los_Angeles" "Los Angeles") - ("America/New_York" "New York") - ("Europe/London" "London") - ("Asia/Calcutta" "Bangalore") - ("Asia/Tokyo" "Tokyo"))) - - (defun jao-time--pdt-hour () - (jao-time-at-zone "%H" "America/Los_Angeles")) - - (defun jao-time--chicago-hour () - (jao-time-at-zone "%H" "America/Chicago")) - - (defun jao-time-at-zone (format zone) - (set-time-zone-rule zone) - (prog1 (format-time-string format) - (set-time-zone-rule nil))) - - (defun jao-time-echo-la-time () - (interactive) - (message (jao-time-at-zone "LA %H:%M" "America/Los_Angeles"))) - - (defun jao-time-echo-times () - (interactive) - (let ((msg (format "%s (%s)" - (format-time-string "%a, %e %B - %H:%M") - (jao-time-at-zone "%H:%M" "America/Los_Angeles")))) - (jao-notify msg "" (jao-data-file "clock-world-icon.png")))) - - (defun jao-time-to-epoch (&optional s) - "Transform a time string to an epoch integer in milliseconds." - (interactive) - (let ((s (or s (read-string "Time string: " (thing-at-point 'string))))) - (message "%s = %s" - s - (round (* 1000 (time-to-seconds (parse-time-string s))))))) - - (defun jao-epoch-to-time (&optional v) - "Transform an epoch, given in milliseconds, to a time string." - (interactive) - (let ((v (or v (read-number "Milliseconds: " (thing-at-point 'number))))) - (message "%s = %s" v - (format-time-string "%Y-%m-%d %H:%M:%S" - (seconds-to-time (/ v 1000.0)))))) - - (setq display-time-day-and-date nil - display-time-24hr-format nil - display-time-default-load-average nil - display-time-format " %a %e %H:%M") - - #+end_src -*** Minibuffer - #+begin_src emacs-lisp - (defvar jao-modeline-in-minibuffer (and window-system t)) - - (use-package jao-minibuffer - :init - (if (jao-colors-scheme-dark-p) - (setq jao-minibuffer-active-buffer-line-color "azure4" - jao-minibuffer-inactive-buffer-line-color "grey25") - (setq jao-minibuffer-active-buffer-line-color "burlywood3" - jao-minibuffer-inactive-buffer-line-color "grey65")) - :commands (jao-minibuffer-add-variable - jao-minibuffer-refresh - jao-minibuffer-mode)) - - (use-package jao-mode-line - :commands (jao-mode-line-add-to-minibuffer - jao-mode-line-remove-from-minibuffer)) - - (setq enable-recursive-minibuffers t) - (require 'mb-depth) - (minibuffer-depth-indicate-mode 1) - (require 'minibuf-eldef) - (setq minibuffer-eldef-shorten-default t) - (minibuffer-electric-default-mode 1) - - (jao-minibuffer-mode 1) - - (when jao-modeline-in-minibuffer - (add-hook 'display-time-hook #'jao-minibuffer-refresh) - (add-hook 'after-init-hook - (lambda () (jao-mode-line-add-to-minibuffer 90)))) - #+end_src -*** Mode line format - #+begin_src emacs-lisp - (setq line-number-display-limit-width 250) - (setq mode-line-position-column-format '(" %c") - mode-line-position-line-format '(" %l,%c")) - (setq mode-line-percent-position - '(" %l" (:eval (format "/%d" (line-number-at-pos (point-max)))))) - (line-number-mode -1) - (column-number-mode 1) - #+end_src -*** Mode line toggle - #+begin_src emacs-lisp - (use-package jao-mode-line - :init - (when (and window-system (not jao-modeline-in-minibuffer)) - (add-to-list 'after-make-frame-functions #'jao-mode-line-hide-inactive) - (add-hook 'after-init-hook #'jao-toggle-inactive-mode-line)) - :demand t - :bind (("" . jao-mode-line-toggle-inactive) - ("" . jao-mode-line-toggle) - ("" . jao-mode-line-echo))) - #+end_src -*** Diminish - #+BEGIN_SRC emacs-lisp - (use-package diminish :ensure t) - (when (require 'use-package-diminish nil 'noerror) - (eval-after-load "simple" '(diminish 'auto-fill-function " §")) - (eval-after-load "autorevert" '(diminish 'auto-revert-mode ""))) - #+END_SRC -*** Battery - #+begin_src emacs-lisp - (use-package battery - :init (setq battery-load-low 15 - battery-load-critical 8 - battery-mode-line-limit 40 - battery-echo-area-format - "%L %r %B (%p%% load, remaining time %t)" - battery-mode-line-format " %b%p ")) ;; " 🔋%b%p " - (display-battery-mode 1) - (with-eval-after-load "jao-minibuffer" - (unless jao-modeline-in-minibuffer - (jao-minibuffer-add-variable 'battery-mode-line-string 80))) - #+end_src -* Notifications -*** alert - #+BEGIN_SRC emacs-lisp - (use-package alert - :ensure t - :init - (setq alert-default-style 'message ;; 'libnotify - alert-hide-all-notifications nil)) - #+END_SRC -*** jao-notify - #+begin_src emacs-lisp - (require 'jao-notify) - #+end_src -*** tracking - #+begin_src emacs-lisp - (use-package tracking - :demand t - :init (setq tracking-position 'before-modes - tracking-frame-behavior nil - tracking-most-recent-first nil - tracking-max-mode-line-entries 10 - tracking-sort-faces-first t - tracking-shorten-modes '()) - :config - (setq erc-track-enable-keybindings nil) - - (defun jao-tracking-next-buffer () - (interactive) - (tracking-next-buffer) - (jao-tracking-update-minibuffer)) - - :bind (("C-c C-SPC" . jao-tracking-next-buffer))) - - (use-package jao-tracking - :demand t - :init (setq jao-tracking-bkg - (if (jao-colors-scheme-dark-p) "grey20" "grey93"))) - #+end_src -*** tmr - #+begin_src emacs-lisp - (use-package tmr - :ensure t - :init - (setq tmr-sound-file "/usr/share/sounds/freedesktop/stereo/message.oga" - tmr-descriptions-list '("tea is ready"))) - #+end_src -* Calendar, diary, weather -*** Diary - #+BEGIN_SRC emacs-lisp - (setq diary-file (expand-file-name "diary" jao-org-dir) - diary-display-function 'diary-fancy-display - diary-mail-addr "jao@localhost" - diary-comment-start ";;" - diary-comment-end "") - - (add-hook 'diary-list-entries-hook 'diary-sort-entries t) - #+END_SRC -*** Calendar - #+begin_src emacs-lisp - (setq appt-display-format nil) - (appt-activate 1) - (setq calendar-latitude 55.9533 - calendar-longitude -3.1883 - calendar-location-name "Edinburgh, Scotland" - calendar-mark-diary-entries-flag t - calendar-date-echo-text '(format "ISO date: %s" - (calendar-iso-date-string - (list month day year)))) - - (setq calendar-holidays - '((holiday-fixed 1 1 "New Year's Day") - (holiday-fixed 4 1 "April Fools' Day") - (holiday-float 5 0 2 "Mother's Day") - (holiday-fixed 3 19 "Father's Day") - (holiday-float 11 4 4 "Thanksgiving") - (holiday-fixed 12 25 "Christmas") - (holiday-chinese-new-year) - (solar-equinoxes-solstices) - (holiday-sexp calendar-daylight-savings-starts - (format "Daylight Saving Time Begins %s" - (solar-time-string - (/ calendar-daylight-savings-starts-time - (float 60)) - calendar-standard-time-zone-name))) - (holiday-sexp calendar-daylight-savings-ends - (format "Daylight Saving Time Ends %s" - (solar-time-string - (/ calendar-daylight-savings-ends-time - (float 60)) - calendar-daylight-time-zone-name))))) - - (add-to-list 'display-buffer-alist - `(,(regexp-quote diary-fancy-buffer) - (display-buffer-at-bottom) - (window-parameters (mode-line-format . none)) - (window-height . fit-window-to-buffer))) - - (defun jao-diary--select () - (switch-to-buffer diary-fancy-buffer)) - - (add-hook 'diary-fancy-display-mode-hook #'jao-diary--select) - (setq org-calendar-insert-diary-entry-key nil - org-agenda-diary-file 'diary-file) - - #+end_src -*** Weather -***** winttr - #+begin_src emacs-lisp - (defun jao-weather (&optional wide) - (interactive "P") - (if (not wide) - (message "%s" - (jao-shell-string "curl -s" - "https://wttr.in/?format=%l++%m++%C+%c+%t+%w++%p")) - (jao-afio--goto-scratch) - (if-let ((b (get-buffer "*wttr*"))) - (progn (pop-to-buffer b) - (term-send-string "clear;curl wttr.in\n")) - (jao-exec-in-term "curl wttr.in" "*wttr*")))) - (global-set-key (kbd "") #'jao-weather) - #+end_src -*** Timers - #+BEGIN_SRC emacs-lisp - (put 'list-timers 'disabled nil) - #+END_SRC -* Files, dired and scratch buffer -*** so-long - #+begin_src emacs-lisp - (setq large-file-warning-threshold (* 200 1024 1024)) - - (use-package so-long - :ensure t - :diminish) - (global-so-long-mode 1) - #+end_src -*** Persistent scratch - #+BEGIN_SRC emacs-lisp - (use-package persistent-scratch - :ensure t - :config (persistent-scratch-setup-default)) - #+END_SRC -*** Automatically uncompress - #+BEGIN_SRC emacs-lisp - (require 'jka-compr) - (auto-compression-mode 1) - #+END_SRC -*** wgrep - #+begin_src emacs-lisp - (use-package wgrep :ensure t) - (require 'wgrep) - #+end_src -*** dired - - [[https://www.masteringemacs.org/article/working-multiple-files-dired][Working with multiple files in dired - Mastering Emacs]] - #+begin_src emacs-lisp - (use-package dired - :init - (setq dired-recursive-deletes 'top - dired-recursive-copies 'top - dired-listing-switches "-alhF --group-directories-first" - ls-lisp-dirs-first t - dired-dwim-target t - dired-kill-when-opening-new-dired-buffer t - dired-mouse-drag-files t - wdired-create-parent-directories t) - - (put 'dired-find-alternate-file 'disabled nil) - :hook (dired-mode . turn-on-gnus-dired-mode) - :bind (:map dired-mode-map - ("C-c C-r" . wdired-change-to-wdired-mode) - ("C-M-m" . gnus-dired-attach))) - - (use-package dired-x :demand t) - - (use-package find-dired - :init (setq find-ls-option '("-print0 | xargs -0 ls -ld" . "-ld")) - :bind ("C-c D" . find-name-dired)) - - (use-package dired-git-info - :ensure t - :bind (:map dired-mode-map (")" . dired-git-info-mode))) - - #+end_src -* General editing -*** Executable scripts - #+begin_src emacs-lisp - (add-hook 'after-save-hook - 'executable-make-buffer-file-executable-if-script-p) - #+end_src -*** Long lines - [[https://200ok.ch/posts/2020-09-29_comprehensive_guide_on_handling_long_lines_in_emacs.html][Comprehensive guide on handling long lines in Emacs - 200ok]] - #+begin_src emacs-lisp - (when (version<= "27.1" emacs-version) - (setq bidi-inhibit-bpa t)) - #+end_src -*** Spaces, tabs, kill - #+begin_src emacs-lisp - (setq kill-whole-line t) - (setq-default indent-tabs-mode nil) - (setq indent-tabs-width 4) - (setq-default default-tab-width 8) - (setq tab-always-indent t) - (setq kill-read-only-ok t) - (setq view-read-only nil) - #+end_src -*** Whitespace and filling column - #+begin_src emacs-lisp - (add-hook 'write-file-functions 'delete-trailing-whitespace) - (setq-default indicate-empty-lines nil) - (setq fill-column 78) - (setq comment-auto-fill-only-comments nil) - - (use-package whitespace - :init - (setq whitespace-style '(face tabs trailing ;; lines-tail - empty missing-newline-at-eof) - whitespace-line-column 80) - :hook (prog-mode . whitespace-mode) - :diminish) - - (use-package display-fill-column-indicator - :init (setq-default display-fill-column-indicator-column 80) - :hook (prog-mode . display-fill-column-indicator-mode)) - - #+end_src -*** Visible mode - #+begin_src emacs-lisp - (use-package visible-mode - :bind (("s-v" . visible-mode))) - #+end_src -*** Changes - #+begin_src emacs-lisp - (use-package goto-chg - :ensure t - :bind (("C-." . goto-last-change) - ("C-c ." . goto-last-change) - ("C-c ," . goto-last-change-reverse))) - #+end_src -*** Eval-and-replace - #+BEGIN_SRC emacs-lisp - (defun fc-eval-and-replace () - "Replace the preceding sexp with its value." - (interactive) - (backward-kill-sexp) - (condition-case nil - (prin1 (eval (read (current-kill 0))) - (current-buffer)) - (error (message "Invalid expression") - (insert (current-kill 0))))) - - (global-set-key "\C-ce" 'fc-eval-and-replace) - #+END_SRC -*** Skeletons and autoinsert - #+begin_src emacs-lisp - (use-package autoinsert - :config - (setq auto-insert-directory "~/.emacs.d/autoinsert/" - auto-insert t - auto-insert-query t) - (setf (alist-get 'html-mode auto-insert-alist nil t) nil)) - (add-hook 'find-file-hooks #'auto-insert) - - (use-package jao-skel - :demand t - :config - (require 'jao-skel-geiser) - (require 'jao-skel-lisp) - (require 'jao-skel-haskell) - (require 'jao-skel-latex)) - #+end_src -*** Undo - f to go forward - b to go backward - - n to go to the node below when you at a branching point - p to go to the node above - - a to go back to the last branching point - e to go forward to the end/tip of the branch - - #+begin_src emacs-lisp - (use-package vundo - :ensure t - :config - (set-face-attribute 'vundo-default nil :family "Symbola") - (setq vundo-glyph-alist vundo-unicode-symbols) - :bind (("C-?" . vundo))) - - #+end_src -*** Completion - #+begin_src emacs-lisp - (use-package jao-custom-completion) - #+end_src -* Buffers -*** cursor and mark - #+begin_src emacs-lisp - (transient-mark-mode -1) - (blink-cursor-mode -1) - (setq cursor-in-non-selected-windows nil) - #+end_src -*** uniquifiy - #+begin_src emacs-lisp - (require 'uniquify) - (setq uniquify-buffer-name-style 'forward - uniquify-trailing-separator-p t) - #+end_src -*** autosave - #+begin_src emacs-lisp - (setq auto-save-list-file-prefix "~/.emacs.d/auto-save-list/.saves-" - auto-save-no-message t - kill-buffer-delete-auto-save-files t) - - (setq lock-file-name-transforms - '(("\\`/.*/\\([^/]+\\)\\'" "/tmp/emacs-lock/\\1" t))) - #+end_src -*** autorevert - #+BEGIN_SRC emacs-lisp - (setq auto-revert-check-vc-info nil) - (setq auto-revert-verbose nil) - (setq auto-revert-avoid-polling t) - (setq auto-revert-mode-text "") - (require 'autorevert) - (global-auto-revert-mode 1) - #+END_SRC -*** attached buffers - #+begin_src emacs-lisp - (defun jao-display-buffer-below-selected (buffer alist) - (delete-other-windows-vertically) - (display-buffer-below-selected buffer alist)) - - (defun jao-attached-buffer-entry (name-rx height) - `(,name-rx (display-buffer-reuse-window - jao-display-buffer-below-selected) - (window-height . ,(or height 25)))) - - (defmacro jao-with-attached-buffer (name-rx height &rest body) - (declare (indent defun)) - `(let ((display-buffer-alist '(,(jao-attached-buffer-entry name-rx height)))) - ,@body)) - - (defun jao-define-attached-buffer (name-rx &optional height) - (add-to-list 'display-buffer-alist - (jao-attached-buffer-entry name-rx height))) - - #+end_src -*** images - #+begin_src emacs-lisp - (setq image-use-external-converter t) - (setq widget-image-enable nil) - #+end_src -*** same mode - #+begin_src emacs-lisp - (defun jao-buffer-same-mode (&optional mode pre-fn switch-fn) - (interactive) - (let* ((mode (or mode major-mode)) - (modes (if (symbolp mode) (list mode) mode)) - (pred `(lambda (b) - (let ((b (get-buffer (if (consp b) (car b) b)))) - (member (buffer-local-value 'major-mode b) - ',modes)))) - (buff (read-buffer "Buffer: " nil t pred))) - (when pre-fn (funcall pre-fn)) - (if switch-fn (funcall switch-fn buff) (pop-to-buffer buff)))) - (global-set-key (kbd "C-c C-b") #'jao-buffer-same-mode) - #+end_src -*** projects - #+begin_src emacs-lisp - (use-package project - :bind (("C-x C-p" . project-prefix-map))) - #+end_src -*** buffer quit function (the triple ESC) - #+begin_src emacs-lisp - (setq buffer-quit-function (lambda () t)) - #+end_src -*** pulsar - #+begin_src emacs-lisp - (use-package pulsar - :ensure t - :diminish - :custom ((pulsar-pulse-functions - '(ace-window - backward-page - delete-other-windows - delete-window - forward-page - jao-prev-window - move-to-window-line-top-bottom - org-backward-heading-same-level - org-forward-heading-same-level - org-next-visible-heading - org-previous-visible-heading - other-window - outline-backward-same-level - outline-forward-same-level - outline-next-visible-heading - outline-previous-visible-heading - outline-up-heading - recenter-top-bottom - reposition-window - scroll-down-command - scroll-up-command)) - (pulsar-pulse t) - (pulsar-delay 0.1) - (pulsar-iterations 10) - (pulsar-face 'pulsar-yellow) - (pulsar-highlight-face 'pulsar-face)) - :hook ((jao-afio-switch . pulsar-pulse-line))) - - (pulsar-global-mode) - - #+end_src -* Windows -*** scrolling - #+begin_src emacs-lisp - (setq scroll-preserve-screen-position 'always - scroll-conservatively most-positive-fixnum - scroll-margin 4 - scroll-step 2 - redisplay-skip-fontification-on-input t) - #+end_src -*** splitting and switch - #+begin_src emacs-lisp - (setq split-height-threshold 80 - split-width-threshold 144 - display-buffer-avoid-small-windows 20) - - (setq switch-to-buffer-preserve-window-point nil - switch-to-buffer-obey-display-actions t - switch-to-prev-buffer-skip 'this) ;; don't switch to a - ;; buffer already visible in - ;; this frame - - (global-set-key (kbd "C-x _") #'delete-other-windows-vertically) - #+end_src -*** first window - #+begin_src emacs-lisp - (defvar jao-first-window--from nil) - - (defun jao-first-window () - "Go to previous windows in frame, remembering where we were." - (interactive) - (let ((cb (current-buffer))) - (if (eq (get-buffer-window cb) (select-window (frame-first-window))) - (when jao-first-window--from (pop-to-buffer jao-first-window--from)) - (setq jao-first-window--from cb)))) - - (defun jao-prev-window () - "Go to previous window." - (interactive) - (other-window -1)) - - (global-set-key (kbd "C-x p") #'jao-prev-window) - (global-set-key (kbd "s-a") #'jao-first-window) - (global-set-key (kbd "M-a") #'jao-first-window) - - #+end_src -*** ace window - #+begin_src emacs-lisp - (use-package ace-window - :ensure t - :demand t - :init (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l) - aw-char-position 'top-left - aw-ignore-current nil - aw-dispatch-when-more-than 2 - aw-leading-char-style 'path - aw-display-mode-overlay t - aw-scope 'frame) - :config - - (defun jao-ace-consult-buffer-other-window (w) - (interactive) - (aw-switch-to-window w) - (consult-buffer)) - - (setf (alist-get ?b aw-dispatch-alist) - '(jao-ace-consult-buffer-other-window "Consult buffer")) - - (setf (alist-get ?B aw-dispatch-alist) - (alist-get ?u aw-dispatch-alist)) - - - :bind (("M-o" . ace-window) - ("M-O" . ace-swap-window) - ("C-x 4 t" . ace-swap-window))) - - #+end_src -*** window navigation (custom) - #+begin_src emacs-lisp - (defun jao-nth-window (n) - (if (zerop n) - 'jao-first-window - `(lambda () - (interactive) - (select-window (frame-first-window)) - (dotimes (x ,n) (other-window 1))))) - - (defun jao-prev-window () - "Go to previous window" - (interactive) - (other-window -1)) - - (defun jao-next-window () - "Go to previous window" - (interactive) - ;; next-window-any-frame - (other-window 1)) - - (global-set-key (kbd "C-x p") 'jao-prev-window) - (global-set-key (kbd "C-x o") 'other-window) - (mapc (lambda (n) - (global-set-key (format "\C-c%s" (1+ n)) (jao-nth-window n))) - '(0 1 2 3 4 5 6 7 8)) - - ;; transposing windows - (defun transpose-windows (arg) - "Transpose the buffers shown in two windows." - (interactive "p") - (let ((selector (if (>= arg 0) 'next-window 'previous-window))) - (while (/= arg 0) - (let ((this-win (window-buffer)) - (next-win (window-buffer (funcall selector)))) - (set-window-buffer (selected-window) next-win) - (set-window-buffer (funcall selector) this-win) - (select-window (funcall selector))) - (setq arg (if (plusp arg) (1- arg) (1+ arg)))))) - - (define-key ctl-x-4-map (kbd "t") 'transpose-windows) - #+end_src - - #+RESULTS: - : transpose-windows -*** winner mode - #+begin_src emacs-lisp - (winner-mode 1) - #+end_src -* Frames -*** Frame geometry - #+begin_src emacs-lisp - (setq frame-resize-pixelwise t) - (modify-all-frames-parameters - `((horizontal-scroll-bars . nil) - (vertical-scroll-bars . nil) - (scroll-bar-width . 0) - (menu-bar . nil))) - #+end_src -*** Frame layout, title, etc. - #+begin_src emacs-lisp - (setq frame-title-format '("%b")) - (use-package fringe) - (fringe-mode) - - (menu-bar-mode -1) - - ;; (setting it to nil avoids mouse wrapping after other-frame) - (setq focus-follows-mouse t) - - (use-package scroll-bar) - (set-scroll-bar-mode nil) - (use-package tool-bar) - (tool-bar-mode -1) - - (defun jao-trisect (&optional force) - (interactive) - (let ((fw (frame-width))) - (delete-other-windows) - (cond ((or force (>= fw 240)) - (let ((w (- (/ fw 3)))) - (delete-other-windows) - (split-window-horizontally w) - (split-window-horizontally w) - (balance-windows))) - ((> fw 162) - (split-window-horizontally) - (switch-to-buffer (other-buffer)))))) - - (defun jao-bisect () - (interactive) - (jao-trisect t) - (next-window) - (delete-window)) - #+end_src -*** afio - #+begin_src emacs-lisp - (use-package jao-afio) - - (defun jao-xmonad-goto-1 () - (shell-command "sendCommand 1")) - - (defun jao-afio--goto-scratch-1 () - (interactive) - (jao-afio-goto-scratch t)) - - (jao-afio-setup 'jao-afio--goto-scratch-1 t) - - (defun jao-current--frame-id () - (propertize (if (and (jao-exwm-enabled-p) - (not (bound-and-true-p jao-exwm--use-afio))) - (format "F%s" exwm-workspace-current-index) - (format "%s" (jao-afio-current-no))) - 'face 'font-lock-warning-face)) - - (add-hook 'jao-afio-switch-hook #'tracking-remove-visible-buffers) - (jao-minibuffer-add-variable '(jao-current--frame-id) 100) - - (defun jao-afio--set-mode-line () - (when (and window-system (fboundp 'jao-mode-line-hide-inactive)) - (if (string= "Docs" (jao-afio-current-frame)) - (jao-mode-line-show-inactive nil) - (jao-mode-line-hide-inactive nil)))) - - (unless jao-modeline-in-minibuffer - (add-hook 'jao-afio-switch-hook #'jao-afio--set-mode-line)) - #+end_src -* Writing and writing modes -*** Copyright notices - #+begin_src emacs-lisp - (setq copyright-year-ranges t) - (add-hook 'write-file-functions 'copyright-update) - #+end_src -*** Indent on yank - #+begin_src emacs-lisp - ;;; indent on yank - (defvar jao-auto-indent-modes - '(emacs-lisp-mode ;; clojure-mode - scheme-mode objc-mode - tuareg-mode c-mode c++-mode - tcl-mode sql-mode - perl-mode cperl-mode - java-mode jde-mode - LaTeX-mode TeX-mode)) - - (defadvice yank (after indent-region activate) - (if (member major-mode jao-auto-indent-modes) - (indent-region (region-beginning) (region-end) nil))) - #+end_src -*** Org mode - #+begin_src emacs-lisp - (use-package jao-custom-org) - #+end_src -*** Blog - #+begin_src emacs-lisp - (use-package jao-custom-blog) - #+end_src -*** Text-ish mode settings - #+begin_src emacs-lisp - ;;; SENTENCES separated by just one space - (setq sentence-end "[.?!][]\"')]*\\($\\|\t\\| \\)[ \t\n]*") - (setq sentence-end-double-space t) - ;;; copy rectangle - (defun kill-rectangle-save (start end) - "Save the region-rectangle as the last killed one." - (interactive "r") - (require 'rect) ; Make sure killed-rectangle is defvar'ed. - (setq killed-rectangle (extract-rectangle start end)) - (message "Rectangle saved")) - - ;; text mode, autoinserts and write hooks - ;;; misc - (setq default-major-mode 'text-mode) - - (add-hook 'text-mode-hook 'turn-on-auto-fill) - #+end_src -*** Dictionaries - #+begin_src emacs-lisp - (defun jao-word-definition-lookup () - "Look up the word under cursor in a browser." - (interactive) - (require 'thingatpt) - (browse-url - (concat "http://www.wordnik.com/words/" - ;; "http://www.answers.com/main/ntquery?s=" - (thing-at-point 'word)))) - - (use-package dictionary - :init (setq dictionary-use-single-buffer t - dictionary-server "localhost") - :commands (dictionary-search - dictionary-match-words - dictionary-lookup-definition - dictionary - dictionary-mouse-popup-matching-words - dictionary-popup-matching-words - dictionary-tooltip-mode - global-dictionary-tooltip-mode) - :bind (("C-c d" . dictionary-search))) - - (setq ispell-personal-dictionary - (expand-file-name "~/.emacs.d/ispell.dict")) - - (use-package wordreference - :ensure t - :init (setq wordreference-target-lang "es" - wordreference-source-lang "en") - :bind (("C-c D" . wordreference-search))) - - #+end_src -*** Markdown - #+BEGIN_SRC emacs-lisp - (use-package markdown-mode - :ensure t) - - (use-package markdown-toc - :ensure t) - - (dolist (ext '("\\.md$" "\\.markdown$")) - (add-to-list 'auto-mode-alist (cons ext 'markdown-mode))) - #+END_SRC -*** TeX and LaTex - #+begin_src emacs-lisp - (use-package tex-site - :ensure auctex - :init - (setq TeX-auto-save t) - (setq TeX-parse-self t) - (setq TeX-a4-paper t) - (setq TeX-auto-local ".tex-auto-local") - ;; Preferred view format: dvi, ps, pdf, pdfs - (setq TeX-view-format "pdf") - (setq-default TeX-master "../main") ; nil to ask - (setq TeX-view-program-selection - ;; '((output-dvi "open") - ;; (output-pdf "open") - ;; (output-html "open")) - '(((output-dvi has-no-display-manager) "dvi2tty") - ((output-dvi style-pstricks) "dvips and gv") - (output-dvi "xdvi") - (output-pdf "xdg-open") - (output-html "xdg-open"))) - ;; to make RefTeX faster for large documents, try these: - (setq reftex-enable-partial-scans t) - (setq reftex-save-parse-info t) - (setq reftex-use-multiple-selection-buffers t) - ;; to integrate with AUCTeX - (setq reftex-plug-into-AUCTeX t) - (setq reftex-ref-style-default-list - '("Hyperref" "Varioref" "Fancyref")) - (setq LaTeX-command "latex -shell-escape") - (setq LaTeX-biblatex-use-Biber t) - (setq bibtex-dialect 'biblatex) - :config - (add-hook 'TeX-after-compilation-finished-functions - #'TeX-revert-document-buffer) - (add-hook 'LaTeX-mode-hook 'turn-on-reftex) - ) - - ;; (use-package ebib - ;; :ensure t - ;; :config (setq ebib-bibtex-dialect 'biblatex)) - - ;; for M-x biblio-lookup - ;; (use-package biblio :ensure t) - #+end_src -* Browsing -*** Variables - #+begin_src emacs-lisp - (defvar jao-browse-doc-use-emacs-p t) - (defvar jao-browse-url-function nil) - (defvar jao-browse-url-external-function nil) - #+end_src -*** URL around point - #+begin_src emacs-lisp - (defun jao-url-around-point (&optional current-url) - (or (and (fboundp 'w3m-anchor) (w3m-anchor)) - (shr-url-at-point nil) - (ffap-url-at-point) - (thing-at-point 'url) - (when current-url - (or (and (fboundp 'w3m-anchor) (w3m-anchor)) - (and (derived-mode-p 'eww-mode) (plist-get eww-data :url)))))) - - (defun jao--url-prompt () - (let* ((def (jao-url-around-point t)) - (prompt (concat "URL" (if def (format " (%s): " def) ": ")))) - (read-string prompt nil nil def))) - #+end_src -*** Downloads using wget - #+BEGIN_SRC emacs-lisp - (defun jao-wget--get-title (filename) - (let ((fn (file-name-base filename))) - (if (string-blank-p fn) - (plist-get eww-data :title) - (subst-char-in-string ?- ? (capitalize fn))))) - - (defun jao-wget (url &optional user pwd) - "Download URL using wget." - (let* ((def (file-name-nondirectory url)) - (pmt (format "Save %s to: " url)) - (read-file-name-function nil) - (dest (expand-file-name - (read-file-name pmt jao-sink-dir nil nil def))) - (title (jao-wget--get-title dest)) - (src-url (jao-url-around-point t)) - (auth (when (and user pwd) - `(,(format "--http-user=%s" user) - ,(format "--http-password=%s" pwd))))) - (switch-to-buffer-other-window (get-buffer-create "*downloads*")) - (erase-buffer) - (kill-new (format "[[doc:%s][%s]] (from [[%s][here]])" - (file-name-nondirectory dest) - (read-string "Title: " title) - (or src-url (file-name-directory url)))) - (apply 'make-term `("downloads" "wget" nil ,@auth "-O" ,dest ,url)))) - - (defun jao-download (url &optional pws) - "Download URL using wget" - (interactive (list (jao--url-prompt))) - (when url - (let ((usr (and pws (read-string "Login name: "))) - (pwd (and pws (read-passwd "Password: ")))) - (jao-wget url usr pwd)))) - - (with-eval-after-load "embark" - (define-key embark-url-map (kbd "d") #'jao-download)) - - #+END_SRC -*** Video - #+BEGIN_SRC emacs-lisp - (defvar jao-video--url-rx - (format "^https?://\\(?:www\\.\\)?%s/.+" - (regexp-opt '("youtu.be" - "youtube.com" - "blip.tv" - "vimeo.com" - "infoq.com") - t))) - - (defvar jao-video--ext-rx - (format "^https?://.+/.+\\.%s" (regexp-opt '("mp3" "webm" "mp4")))) - - (defun jao-video--url-p (url) - (or (string-match-p jao-video--url-rx url) - (string-match-p jao-video--ext-rx url))) - - (defun jao--remote-run (url prg) - (let ((args (format "%s %s" prg (shell-quote-argument url)))) - (start-process-shell-command prg nil args))) - - (defun jao--mpv (url &rest args) (jao--remote-run url "mpv")) - (defun jao--vlc (url &rest args) (jao--remote-run url "vlc")) - - (defvar jao--video-player 'jao--mpv) - - (defun jao-view-video (url) - "Tries to stream a video from the current or given URL" - (interactive (list (jao--url-prompt))) - (when url (funcall jao--video-player url))) - - (defun jao-maybe-view-video (url &rest _ignored) - (interactive) - (if (y-or-n-p "View video (y) or web page (n)? ") - (jao-view-video url) - (funcall jao-browse-url-function url))) - - #+END_SRC -*** Web browsers - #+begin_src emacs-lisp - (defun jao-www--buffer-p (b) - (with-current-buffer b - (or (derived-mode-p 'w3m-mode 'eww-mode) - (and (boundp 'exwm-class-name) - (member (buffer-local-value 'exwm-class-name b) - '("vlc" "mpv")))))) - - (use-package jao-custom-eww) - #+end_src -*** Browse URL - #+begin_src emacs-lisp - (require 'browse-url) - - (setq browse-url-generic-program "~/bin/firehog") - - (defun jao-browse-with-external-browser (&rest url) - "Browse with external hogging" - (interactive "s") - (let ((url (or (car url) (jao-url-around-point)))) - (if (not url) - (message "No URL at point") - (when (and (jao-exwm-enabled-p) (fboundp 'jao-exwm-firefox)) - (jao-exwm-firefox)) - (when (and jao-sway-enabled (fboundp 'jao-sway-firefox)) - (jao-sway-firefox)) - (browse-url-generic url)))) - (setq jao-browse-url-external-function 'jao-browse-with-external-browser) - - (defun jao--fln (url) - (shell-quote-argument - (if (string-match "^[^:]*:/*?\\(/?[^/].*\\)" url) - (match-string-no-properties 1 url) - url))) - - (defun jao--browse-doc (url search &optional no-add) - (let* ((url (substring-no-properties url)) - (file (jao--fln url))) - (when file - (unless (file-exists-p file) - (error "File %s does not exist" file)) - (jao-open-doc file)))) - - (defun jao--make-file-rx (exts) - (format "file:/?/?.+\\.%s$" (regexp-opt exts))) - - (defvar jao--see-exts - (jao--make-file-rx '("jpg" "jpeg" "png" "mov" "wmv" "avi" "mp4"))) - - (defvar jao--doc-exts - (jao--make-file-rx '("ps" "ps.gz" "pdf" "dvi" "djvu" "chm"))) - - (defvar jao-browse-url-wget-exts - '("ps" "pdf" "dvi" "djvu" "zip" "gz" "tgz" "mp4" "mp3" "flv")) - - (defvar jao-browse-external-domains - '("github.com" "gitlab.com" "slack.com" "meet.google.com" - "twitter.com" "t.com" "linkedin.com" "bigml.com")) - - (defvar jao-browse--external-regexp - (format "https?://.*%s\\(/.*\\)?" - (regexp-opt jao-browse-external-domains))) - - (defun jao-wget--regexp () - (concat "^http[s]?://.+\\(\\." - (mapconcat 'identity jao-browse-url-wget-exts "\\|\\.") - "\\)\\'")) - - (defun jao--see (url &rest _r) - (start-process-shell-command "see" nil (format "see %s" (jao--fln url)))) - - (defun jao--find-file-other-window (url &rest _) - (find-file-other-window (jao--fln url))) - - (use-package elpher :ensure t) - - (defun jao-elpher--browse (url &rest _) (elpher-go url)) - - (defvar jao-browse--sound-rx - (format "^https?://.*/.*\\.%s" (regexp-opt '("mp4" "mp3" "flv")))) - - (defun jao-browse-add-url-to-mpc (url &rest _) - "Add the given URL to mpc's playing list, or just play it." - (let ((p (yes-or-no-p (format "Play %s right now?" url)))) - (when p (jao-mpc-clear)) - (jao-mpc-add-url url) - (if p (jao-mpc-play) (message "%s added to mpc queue" url)))) - - (defun jao-browse-url-browse (&rest args) - (apply jao-browse-url-function args)) - - (setq browse-url-handlers - `(("^\\(gemini\\|gopher\\)://.*" . jao-elpher--browse) - (,jao--doc-exts . jao--browse-doc) - (,jao--see-exts . jao--see) - ("^file://?.+\\.html?$" . ,jao-browse-url-function) - ("^file://?" . jao--find-file-other-window) - (,jao-browse--external-regexp . ,jao-browse-url-external-function) - ("^https?://.*\\.gotomeeting\\.com\\.*" . browse-url-chrome) - (,jao-browse--sound-rx . jao-browse-add-url-to-mpc) - (,(jao-wget--regexp) . jao-download) - (jao-video--url-p . jao-maybe-view-video) - ("." . jao-browse-url-browse))) - - (when (< emacs-major-version 28) - (setf (alist-get 'jao-video--url-p browse-url-handlers nil t) nil) - (setq browse-url-browser-function browse-url-handlers)) - - #+end_src -*** Subscribe rss using r2e - #+begin_src emacs-lisp - (autoload 'View-quit "view") - - (defun jao-rss--find-url () - (save-excursion - (when (derived-mode-p 'w3m-mode 'eww-mode) - (if (fboundp 'w3m-view-source) (w3m-view-source) (eww-view-source))) - (goto-char (point-min)) - (when (re-search-forward - "type=\"application/\\(?:atom\\|rss\\)\\+xml\" +" nil t) - (let ((url (save-excursion - (when (re-search-forward - "href=\"\\([^\n\"]+\\)\"" nil t) - (match-string-no-properties 1)))) - (title (when (re-search-forward - "\\(?:title=\"\\([^\n\"]+\\)\" +\\)" nil t) - (match-string-no-properties 1)))) - (cond ((derived-mode-p 'w3m-view-mode) (w3m-view-source)) - ((string-match-p ".*\\*eww-source\\b.*" (buffer-name)) - (View-quit))) - (when url (cons url (or title ""))))))) - - (defun jao-rss2e-append (name url mbox) - (with-current-buffer (find-file-noselect "~/.config/rss2email.cfg") - (goto-char (point-max)) - (insert "[feed." name "]\nurl = " url) - (insert "\nto = " mbox "+" name "@localhost") - (insert "\nmaildir-mailbox = " mbox "\n\n") - (save-buffer))) - - (defun jao-rss--feeds-dirs () - (mapcar (lambda (d) (cadr (split-string d "\\."))) - (directory-files "~/.emacs.d/gnus/Mail/" nil "^feeds"))) - - (defun jao-rss-subscribe (url) - "Subscribe to a given RSS URL. If URL not given, look for it." - (interactive (list (or (jao-url-around-point) - (jao-rss--find-url) - (read-string "Feed URL: ")))) - (let* ((url+title (if (consp url) url (list url))) - (url (car url+title)) - (title (cdr url+title)) - ;; (cats (cons "prog" (jao-notmuch--subtags "feeds"))) - (cats (jao-rss--feeds-dirs))) - (if url - (let ((url (if (string-match "^feed:" url) (substring url 5) url))) - (when (y-or-n-p (format "Subscribe to <%s>? " url)) - (let* ((name (read-string "Feed name: " title)) - (cat (completing-read "Category: " cats nil t)) - (subs (format "r2e add %s '%s' feeds.%s@localhost" - name url cat))) - ;; (jao-rss2e-append name url cat) - (shell-command-to-string subs) - (shell-command (format "r2e run %s" name))))) - (message "No feeds found")))) - #+end_src -* Email - #+begin_src emacs-lisp - (setq jao-afio-mail-function 'notmuch) - (use-package jao-custom-email) - (use-package jao-custom-notmuch) - #+end_src -* PDFs and other docs -*** doc-view - #+begin_src emacs-lisp - (use-package doc-view - :init - (setq doc-view-cache-directory "~/.emacs.d/cache/docview" - doc-view-resolution 110 - doc-view-continuous t - doc-view-conversion-refresh-interval 1) - - :bind (:map doc-view-mode-map - ("j" . doc-view-next-line-or-next-page) - ("J" . doc-view-scroll-up-or-next-page) - ("k" . doc-view-previous-line-or-previous-page) - ("K" . doc-view-scroll-down-or-previous-page))) - - (use-package jao-doc-view - :bind (:map doc-view-mode-map - ("b" . jao-doc-view-back) - ("B" . jao-doc-view-forward) - ("S" . jao-doc-view-save-session) - ("u" . jao-doc-view-visit-url))) - #+end_src -*** pdf-tools - #+begin_src emacs-lisp - (use-package pdf-tools - :ensure t - :demand t - :init - (add-hook 'after-init-hook - (lambda () - (setq pdf-view-midnight-colors - (cons (frame-parameter nil 'foreground-color) - (frame-parameter nil 'background-color))))) - - :config (pdf-tools-install) - - :diminish ((pdf-view-midnight-minor-mode . "")) - - :bind (:map pdf-view-mode-map - (("C-c C-d" . pdf-view-midnight-minor-mode) - ("j" . pdf-view-next-line-or-next-page) - ("J" . pdf-view-scroll-up-or-next-page) - ("k" . pdf-view-previous-line-or-previous-page) - ("K" . pdf-view-scroll-down-or-previous-page)))) - - #+end_src -*** zathura - #+begin_src emacs-lisp - (defun jao-zathura-file-info (title) - (when (string-match "\\(.+\\) \\[\\(.+\\) (\\([0-9]+\\)/\\([0-9]+\\))\\]" - title) - (list (expand-file-name (match-string 1 title)) - (string-to-number (match-string 3 title)) - (string-to-number (match-string 4 title)) - (match-string 2 title)))) - - (defun jao-zathura-goto-org (&optional title) - (when-let* ((title (or title (jao-shell-string "xdotool" - "getactivewindow" - "getwindowname"))) - (info (jao-zathura-file-info title)) - (file (jao-org-pdf-to-org-file (car info))) - (page (cadr info)) - (pageno (or (car (last info)) page))) - (ignore-errors (jao-afio--goto-docs)) - (let* ((exists (file-exists-p file)) - (fn (file-name-nondirectory file)) - (lnk (format "[[doc:%s::%d][Page %s]]" fn page pageno))) - (find-file file) - (unless exists (jao-org-insert-doc-skeleton)) - (if (or (not exists) (y-or-n-p "Insert link?")) - (insert lnk "\n") - (kill-new lnk) - (message "Link to %s (%s) killed" file page))))) - - (defun jao-zathura-open (file page) - (let ((id (jao-shell-string (format "xdotool search --name %s" - (file-name-nondirectory file))))) - (if (string-blank-p id) - (progn - (when jao-xmonad-enabled - (jao-shell-exec "xdotool set_destktop 2")) - (jao-shell-exec (format "zathura %s -P %s" file (or page 1)))) - (let* ((page (if page (format " && xdotool type %dg" page) "")) - (cmd (format "xdotool windowactivate %s%s" id page))) - (jao-shell-string cmd))))) - - #+end_src -*** open pdfs - #+begin_src emacs-lisp - (use-package saveplace-pdf-view - :ensure t - :demand t - :after doc-view) - - (setq jao-open-doc-fun 'jao-find-or-open) - (setq jao-org-open-pdf-fun 'jao-find-or-open) - - (defun jao-find-or-open (file &optional page height) - (if (and jao-browse-doc-use-emacs-p window-system) - (let* ((buffs (buffer-list)) - (b (catch 'done - (while buffs - (when (string-equal (buffer-file-name (car buffs)) file) - (throw 'done (car buffs))) - (setq buffs (cdr buffs)))))) - (jao-afio--goto-docs) - (if b (pop-to-buffer b) (find-file file)) - (when page (jao-doc-view-goto-page page height))) - (jao-zathura-open file page))) - - (defun jao-open-doc (&optional file page height) - (interactive) - (when-let (file (or file - (read-file-name "Document: " - (concat jao-org-dir "/doc/")))) - (funcall jao-open-doc-fun file page height))) - - (defun jao-select-pdf () - (interactive) - (jao-buffer-same-mode '(pdf-view-mode doc-view-mode) - #'jao-afio--goto-docs)) - #+end_src -*** epub - #+begin_src emacs-lisp - (use-package nov - :ensure t - :after doc-view - :init (setq nov-variable-pitch t - nov-text-width 80) - :config (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))) - - #+end_src -*** transient - #+begin_src emacs-lisp - (defun jao-org-pdf-goto-org-linking () - (interactive) - (jao-org-pdf-goto-org 4)) - - (jao-transient-major-mode doc-view - ["Notes" - ("o" "notes file" jao-org-pdf-goto-org) - ("O" "notes file, linking" jao-org-pdf-goto-org-linking)] - ["Navigation" - ("b" "back jump" jao-doc-view-back) - ("B" "forward jump" jao-doc-view-back) - ("u" "visit URL" jao-doc-view-visit-url)] - ["Slices" - ("cb" "bounding box" doc-view-set-slice-from-bounding-box) - ("cm" "using mouse" doc-view-set-slice-using-mouse)] - ["Session" - ("s" "load session" jao-afio-open-pdf-session) - ("S" "save session" jao-doc-view-save-session) - ("d" "visit cache directory" doc-view-dired-cache)]) - - (with-eval-after-load "pdf-view" - (jao-transient-major-mode pdf-view - ["Notes" - ("o" "notes file" jao-org-pdf-goto-org) - ("O" "notes file, linking" jao-org-pdf-goto-org-linking)] - ["Navigation" - ("b" "back jump" pdf-history-backward) - ("f" "forward jump" pdf-history-forward)] - ["Session" - ("s" "load session" jao-afio-open-pdf-session) - ("S" "save session" jao-doc-view-save-session)])) - - ;; (transient-get-suffix 'jao-transient-pdf-view '(0 -1)) - - #+end_src -* Shells and terms -*** shell modes - #+begin_src emacs-lisp - (setq sh-basic-offset 2) - ;; translates ANSI colors into text-properties, for eshell - (autoload 'ansi-color-for-comint-mode-on "ansi-color" nil t) - (add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on) - #+end_src -*** vterm - #+begin_src emacs-lisp - (defvar jao-use-vterm nil) - - (use-package vterm - :ensure t - :demand t - :commands (vterm vterm-mode) - :init (setq vterm-kill-buffer-on-exit t - vterm-copy-exclude-prompt t - jao-use-vterm t) - :config (jao-define-attached-buffer "\\*vterm\\*" 0.5) - :bind (:map vterm-mode-map ("C-c C-c" . vterm-send-C-c))) - - (defun jao-exec-in-vterm (cmd bname) - (if (string-blank-p (or cmd "")) - (vterm) - (let ((vterm-shell cmd) - (vterm-kill-buffer-on-exit t) - (buff (generate-new-buffer bname))) - (switch-to-buffer buff) - (vterm-mode)))) - - #+end_src -*** term - #+begin_src emacs-lisp - (defvar-local jao-term--cmd nil) - - (defun jao-term--find (cmd) - (seq-find (lambda (b) - (with-current-buffer b - (and (derived-mode-p 'term-mode 'vterm-mode) - (string= (or jao-term--cmd "") cmd)))) - (buffer-list))) - - (defun jao-exec-in-term (cmd &optional name) - (if jao-use-vterm - (jao-exec-in-vterm cmd name) - (ansi-term "bash" name) - (set-process-sentinel (get-buffer-process (current-buffer)) - (lambda (process event) - (when (string= event "finished\n") - (kill-buffer (process-buffer process))))) - (term-send-string nil (concat cmd " ; exit\n")))) - - (defmacro jao-def-exec-in-term (name cmd &rest prelude) - `(defun ,(intern (format "jao-term-%s" name)) (&optional term) - (interactive "P") - ,@prelude - (let ((jao-use-vterm (if term (not jao-use-vterm) jao-use-vterm))) - (if-let ((b (jao-term--find ,cmd))) - (pop-to-buffer b) - (jao-exec-in-term ,cmd ,(format "*%s*" name)) - (setq-local jao-term--cmd ,cmd))))) - - #+end_src -*** eshell -***** Basic custom - #+begin_src emacs-lisp - (use-package eshell - :init - (setq eshell-directory-name "~/.emacs.d/eshell") - (setq eshell-hist-ignoredups 'erase) - - (defun jao-eshell--outline () - (setq-local outline-regexp eshell-prompt-regexp)) - - :hook (eshell-mode . jao-eshell--outline)) - #+end_src -***** Colors - #+begin_src emacs-lisp - (autoload 'ansi-color-apply "ansi-color") - ;; (add-hook 'eshell-preoutput-filter-functions 'ansi-color-filter-apply) - (add-hook 'eshell-preoutput-filter-functions 'ansi-color-apply) - - (use-package eshell-syntax-highlighting - :after esh-mode - :ensure t - :config - ;; Enable in all Eshell buffers. - (eshell-syntax-highlighting-global-mode +1)) - #+end_src -***** Visual commands - #+begin_src emacs-lisp - (require 'em-term) - ;;; commands using ansi scape seqs - (dolist (c '("editor" "more" "wget" "dict" "vim" "links" "w3m" "guile" - "ssh" "autossh" "zmore" "pager" "aptitude" "su" "htop" "top" - "screen" "whizzml" "iex" "spt")) - (add-to-list 'eshell-visual-commands c)) - - (setq eshell-visual-subcommands '(("git" "log" "diff" "show") - ("sudo" "vim") - ("rebar3" "shell")) - eshell-destroy-buffer-when-process-dies nil - eshell-escape-control-x t) - - (use-package eshell-vterm :ensure t) - - (when jao-use-vterm (eshell-vterm-mode)) - - #+end_src -***** bol - #+begin_src emacs-lisp - (defun jao-eshell-maybe-bol () - (interactive) - (let ((p (point))) - (eshell-bol) - (if (= p (point)) - (beginning-of-line)))) - #+end_src -***** Prompt - #+BEGIN_SRC emacs-lisp - ;; tracking git repos - (defun jao-eshell--git-dirty () - (shell-command-to-string "git diff-index --quiet HEAD -- || echo -n '*'")) - - (use-package git-ps1-mode - :ensure t - :init (setq git-ps1-mode-showupstream "1" - git-ps1-mode-showdirtystate "1")) - - (defun jao-eshell--git-info () - (if (fboundp 'git-ps1-mode-get-current) - (git-ps1-mode-get-current) - (let ((desc (shell-command-to-string "git branch --no-color"))) - (when (string-match "^* \\(\\<.+\\>\\)" desc) - (format "%s%s" (match-string 1 desc) (jao-eshell--git-dirty)))))) - - (defun jao-eshell--git-current-branch (suffix) - (let ((desc (or (jao-eshell--git-info) ""))) - (cond ((and (string-empty-p desc) suffix) (format " (%s)" suffix)) - ((string-empty-p (or suffix "")) (format " (%s)" desc)) - (t (format " (%s %s)" desc suffix))))) - - (defun jao-eshell--virtualenv () - (let ((venv (getenv "VIRTUAL_ENV"))) - (when (and venv (string-match ".*/\\([^/]+\\)/$" venv)) - (match-string-no-properties 1 venv)))) - - (defun jao-eshell-prompt-function () - (let* ((venv (jao-eshell--virtualenv)) - (venv (if venv (format "%s" venv) ""))) - (concat (abbreviate-file-name (eshell/pwd)) - (jao-eshell--git-current-branch venv) - (if (= (user-uid) 0) " # " " $ ")))) - - (setq eshell-prompt-function 'jao-eshell-prompt-function) - #+END_SRC -***** in-term - #+begin_src emacs-lisp - (defun eshell/in-term (prog &rest args) - (switch-to-buffer - (apply #'make-term (format "in-term %s %s" prog args) prog nil args)) - (term-mode) - (term-char-mode)) - #+end_src -***** Dir navigation - #+BEGIN_SRC emacs-lisp - (use-package eshell-up - :ensure t - :config (setq eshell-up-print-parent-dir t)) - - (use-package eshell-autojump :ensure t) - #+END_SRC -***** Completion - #+begin_src emacs-lisp - (defun jao-eshell-completion-capf () - (let* ((b (save-excursion (eshell-bol) (point))) - (c (bash-completion-dynamic-complete-nocomint b (point) t))) - (when (and c (listp c)) - (append c '(:exclusive no))))) - - (defun jao-eshell--set-up-completion () - (setq-local completion-styles '(basic partial-completion) - completion-at-point-functions - '(jao-eshell-completion-capf - pcomplete-completions-at-point t))) - - (use-package bash-completion - :ensure t - :hook (eshell-mode . jao-eshell--set-up-completion)) - #+end_src -***** History - #+BEGIN_SRC emacs-lisp - (setq eshell-history-size 10000) - ;;; Fix eshell history completion to allow !$ - ;; This is done by advising eshell-history-reference to expand !$ - ;; into !!:$ which works... - (defadvice jao-eshell-history-reference (before ben-fix-eshell-history) - "Fixes eshell history to allow !$ as abbreviation for !!:$" - (when (string= (ad-get-arg 0) "!$") (ad-set-arg 0 "!!:$"))) - (ad-activate 'jao-eshell-history-reference) - #+END_SRC - This is needed if we want ! to expand in emacs >= 27 - #+BEGIN_SRC emacs-lisp - (add-hook 'eshell-expand-input-functions #'eshell-expand-history-references) - #+END_SRC -***** Toggle - #+begin_src emacs-lisp - - (use-package jao-eshell-here - :demand t - :config (jao-define-attached-buffer "^\\*eshell" 0.5) - :bind (("" . jao-eshell-here-toggle) - ("C-" . jao-eshell-here-toggle-new))) - - #+end_src -***** Workarounds - #+begin_src emacs-lisp - ;; at some point, bash completion started insertig the TAB - ;; after the commands ends - (defun jao-eshell--clean-prompt () - (eshell-bol) - (ignore-errors (kill-line))) - - (add-hook 'eshell-after-prompt-hook 'jao-eshell--clean-prompt) - #+end_src -***** Keybindings - #+begin_src emacs-lisp - (defun jao-eshell--kbds () - (define-key eshell-mode-map "\C-a" 'jao-eshell-maybe-bol) - (define-key eshell-mode-map "\C-ci" 'consult-outline)) - ;; Eshell mode is sillily re-creating its mode map - ;; in every buffer in emacs < 28. - (if (> emacs-major-version 27) - (jao-eshell--kbds) - (add-hook 'eshell-mode-hook #'jao-eshell--kbds)) - #+end_src -* Version control and CI -*** General options - #+begin_src emacs-lisp - (setq vc-follow-symlinks t) - (setq auto-revert-check-vc-info nil) - #+end_src -*** Diff fringe indicators (diff-hl) - #+begin_src emacs-lisp - (use-package diff-hl - :ensure t - :custom ((diff-hl-draw-borders nil) - (diff-hl-side 'right) - (diff-hl-margin-symbols-alist - '((insert . "+") - (delete . "-") - (change . "~") - (unknown . "?") - (ignored . "i")))) - :config - (map-keymap (lambda (_k cmd) - (put cmd 'repeat-map 'diff-hl-command-map)) - diff-hl-command-map) - (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh) - (when (jao-colors-scheme-dark-p) (diff-hl-margin-mode 1))) - - (global-diff-hl-mode 1) - - #+end_src -*** Git config files: more informative diffs - See [[https://protesilaos.com/codelog/2021-01-26-git-diff-hunk-elisp-org/][Informative diff hunks for Emacs Lisp and Org | Protesilaos Stavrou]] - #+begin_src config :tangle ~/.config/git/attributtes :comments no - *.clj diff=lisp - *.cljc diff=lisp - *.cljs diff=lisp - *.lisp diff=lisp - *.el diff=lisp - *.org diff=org - #+end_src - #+begin_src gitconfig :tangle ~/.config/git/config - [diff "lisp"] - xfuncname = "^(((;;;+ )|\\(|([ \t]+\\(((cl-|el-patch-)?def(un|var|macro|method|custom)|gb/))).*)$" - [diff "org"] - xfuncname = "^(\\*+ +.*)$" - #+end_src -*** Magit and forge - #+begin_src emacs-lisp - (use-package magit - :ensure t - :commands magit-status - :init - (setq magit-status-initial-section nil - magit-define-global-key-bindings nil - magit-completing-read-function 'magit-builtin-completing-read - magit-display-buffer-function - 'magit-display-buffer-fullcolumn-most-v1 - magit-delete-by-moving-to-trash nil - magit-last-seen-setup-instructions "1.4.0" - magit-log-edit-confirm-cancellation t - magit-omit-untracked-dir-contents t - magit-process-connection-type nil - magit-push-always-verify nil - magit-repository-directories - '(("/home/jao/usr/bigml" . 2) - ("/home/jao/usr/jao" . 2) - ("/home/jao/lib/elisp" . 3) - ("/usr/local/src" . 1)) - magit-save-repository-buffers 'dontask - magit-section-visibility-indicator '("…" . t) - magit-status-buffer-switch-function 'switch-to-buffer - magit-status-show-hashes-in-headers t) - :config - - (use-package forge - :ensure t - :demand t - :init - (setq forge-topic-list-limit (cons 100 -1) - forge-pull-notifications nil)) - - (add-hook 'magit-status-sections-hook #'forge-insert-assigned-pullreqs t) - (add-hook 'magit-status-sections-hook #'forge-insert-assigned-issues t) - - :bind (("" . magit-status) - (:map forge-topic-mode-map ("M-w" . copy-region-as-kill)))) - - (use-package code-review - :ensure t - :after forge - :bind (:map magit-status-mode-map - ("C-c C-r" . code-review-forge-pr-at-point))) - - - #+end_src -*** Eldoc for magit status/log buffers - [[https://tsdh.org/posts/2021-06-21-using-eldoc-with-magit.html][Using Eldoc with Magit]]. - #+begin_src emacs-lisp - (defun jao-magit-eldoc-for-commit (_callback) - (when-let ((commit (magit-commit-at-point))) - (with-temp-buffer - (magit-git-insert "show" - "--format=format:%an <%ae>, %ar" - (format "--stat=%d" (window-width)) - commit) - (goto-char (point-min)) - (put-text-property (point-min) (line-end-position) 'face 'bold) - (buffer-string)))) - - (defun jao-magit-eldoc-setup () - (add-hook 'eldoc-documentation-functions - #'jao-magit-eldoc-for-commit nil t) - (eldoc-mode 1)) - - (add-hook 'magit-log-mode-hook #'jao-magit-eldoc-setup) - (add-hook 'magit-status-mode-hook #'jao-magit-eldoc-setup) - - (with-eval-after-load "eldoc" - (eldoc-add-command 'magit-next-line) - (eldoc-add-command 'magit-previous-line) - (eldoc-add-command 'magit-section-forward) - (eldoc-add-command 'magit-section-backward)) - #+end_src -*** Other git packages - #+begin_src emacs-lisp - (use-package git-timemachine :ensure t) - - ;; git config --local git-link.remote / git-link.branch - (use-package git-link :ensure t) - - (use-package git-modes :ensure t) - - #+end_src -*** Jenkins - [[https://github.com/rmuslimov/jenkins.el][GitHub - rmuslimov/jenkins.el: Jenkins plugin for emacs]] - #+BEGIN_SRC emacs-lisp - (use-package jenkins - :ensure t - :init - ;; one also needs jenkins-api-token, jenkins-username and jenkins-url - ;; optionally: jenkins-colwidth-id, jenkins-colwidth-last-status - (setq jenkins-colwidth-name 35) - :config - (defun jao-jenkins-first-job (&rest _) - (interactive) - (goto-char (point-min)) - (when (re-search-forward "^- Job" nil t) - (goto-char (match-beginning 0)))) - (add-hook 'jenkins-job-view-mode-hook #'jao-jenkins-first-job) - (advice-add 'jenkins-job-render :after #'jao-jenkins-first-job) - - (defun jenkins-refresh-console-output () - (interactive) - (let ((n (buffer-name))) - (when (string-match "\\*jenkins-console-\\([^-]+\\)-\\(.+\\)\\*$" n) - (jenkins-get-console-output (match-string 1 n) (match-string 2 n)) - (goto-char (point-max))))) - - :bind (:map jenkins-job-view-mode-map - (("n" . next-line) - ("p" . previous-line) - ("f" . jao-jenkins-first-job) - ("RET" . jenkins--show-console-output-from-job-screen)) - :map jenkins-console-output-mode-map - (("n" . next-line) - ("p" . previous-line) - ("g" . jenkins-refresh-console-output)))) - #+END_SRC -* Programming -*** Automatic modes - #+BEGIN_SRC emacs-lisp - (add-to-list 'auto-mode-alist '("\\.mix\\'" . hexl-mode)) - (add-to-list 'auto-mode-alist '("\\.m4\\'" . m4-mode)) - (add-to-list 'auto-mode-alist '("\\.am\\'" . makefile-mode)) - (add-to-list 'auto-mode-alist '("\\.pl\\'\\|\\.pm\\'" . cperl-mode)) - #+END_SRC -*** Smart scan - #+begin_src emacs-lisp - (use-package smartscan - :ensure t - :commands smartscan-mode - :init (add-hook 'prog-mode-hook #'smartscan-mode) - :diminish) - #+end_src -*** Paredit and parens - #+begin_src emacs-lisp - (require 'paren) - (show-paren-mode t) - (setq show-paren-context-when-offscreen t) - - (use-package paredit - :ensure t - :commands paredit-mode - :hook ((pie-mode . paredit-mode) - (scheme-mode . paredit-mode) - (clojure-mode . paredit-mode) - (emacs-lisp-mode . paredit-mode) - (eval-expression-minibuffer-setup . paredit-mode) - (lisp-interaction-mode . disable-paredit-mode)) - :diminish ((paredit-mode . " þ"))) - #+end_src -*** Diff/Ediff - #+BEGIN_SRC emacs-lisp - (setq ediff-split-window-function 'split-window-horizontally) - (setq ediff-make-buffers-readonly-at-startup nil) - (setq ediff-window-setup-function 'ediff-setup-windows-plain) - (setq ediff-keep-variants nil) - #+END_SRC -*** Compilation -***** Compilation mode options - #+begin_src emacs-lisp - (require 'compile) - (setq compilation-scroll-output t) - (setq compilation-error-regexp-alist - (remove 'omake compilation-error-regexp-alist)) - ;; (add-hook 'compilation-mode-hook #'visual-line-mode) - #+end_src -***** Mode line (no "Compiling"!) - #+BEGIN_SRC emacs-lisp - (require 'compile) - (diminish 'compilation-minor-mode " ‡") - (when (< emacs-major-version 27) - (setcdr (assq 'compilation-in-progress minor-mode-alist) '(" ‡"))) - (when (> emacs-major-version 26) - (setcdr (assq 'compilation-in-progress mode-line-modes) '("‡ "))) - #+END_SRC -***** Colorizing compilation buffer - #+begin_src emacs-lisp - (setq compilation-message-face 'default) - (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) - #+end_src -***** Compilation commands - #+begin_src emacs-lisp - (use-package jao-compilation - :commands jao-compilation-setup - :bind (("C-c C" . compile) - ("C-c c" . jao-compile))) - (jao-compilation-setup) - #+end_src -***** Next error - #+begin_src emacs-lisp - (setq next-error-find-buffer-function - #'next-error-buffer-on-selected-frame - next-error-verbose t) - #+end_src -*** Flymake - #+begin_src emacs-lisp - (use-package flymake - :ensure t - :custom ((flymake-mode-line-format '(" " flymake-mode-line-counters))) - :hook ((haskell-mode . flymake-mode)) - :config (jao-define-attached-buffer "^\\*Flymake diagnostics .*\\*\\'") - :bind (:map flymake-mode-map (("s-f n" . flymake-goto-next-error) - ("s-f p" . flymake-goto-prev-error) - ("s-f i" . flymake-show-diagnostic) - ("s-f f" . flymake-show-diagnostics-buffer) - ("s-f l" . consult-flymake)))) - #+end_src -*** Workarounds - #+begin_src emacs-lisp - (setq c-type-finder-time-slot nil) - #+end_src -*** Outline minor mode - #+begin_src emacs-lisp - (setq outline-minor-mode-cycle t) - - (defvar jao-outline-minor-mode-map - (let ((map (make-keymap))) - (define-key map (kbd "C-c C-n") #'outline-next-visible-heading) - (define-key map (kbd "C-c C-p") #'outline-previous-visible-heading) - (define-key map (kbd "C-c o") 'consult-outline) - map)) - - (define-minor-mode jao-outline-minor-mode - "Minor outline mode for programming languages" - :lighter "" - (if jao-outline-minor-mode - (progn (setq-local outline-level #'outline-level - outline-regexp (format "[%s]\\{3,\\} " - comment-start) - outline-default-state 4) - (outline-minor-mode 1)) - (outline-minor-mode -1))) - - #+end_src -* Programming languages -*** Elisp - #+begin_src emacs-lisp - (add-hook 'emacs-lisp-mode-hook #'jao-outline-minor-mode) - - (use-package edit-list :ensure t) - (use-package package-lint :ensure t) - - (defun elisp-disassemble (function) - (interactive (list (function-called-at-point))) - (disassemble function)) - - (defun elisp-pp (sexp) - (with-output-to-temp-buffer "*Pp Eval Output*" - (pp sexp) - (with-current-buffer standard-output - (emacs-lisp-mode)))) - - (defun elisp-macroexpand (form) - (interactive (list (form-at-point 'sexp))) - (elisp-pp (macroexpand form))) - - (defun elisp-macroexpand-all (form) - (interactive (list (form-at-point 'sexp))) - (elisp-pp (macroexpand-all form))) - - (defun elisp-find-definition (name) - (interactive (list (thing-at-point 'symbol))) - (cond (name - (let ((symbol (intern-soft name)) - (search (lambda (fun sym) - (let* ((r (save-excursion (funcall fun sym))) - (buffer (car r)) - (point (cdr r))) - (cond ((not point) - (error "Found no definition for %s in %s" - name buffer)) - (t - (switch-to-buffer buffer) - (goto-char point) - (recenter 1))))))) - (cond ((fboundp symbol) - (xref-push-marker-stack) - (funcall search 'find-function-noselect symbol)) - ((boundp symbol) - (xref-push-marker-stack) - (funcall search 'find-variable-noselect symbol)) - (t - (message "Symbol not bound: %S" symbol))))) - (t (message "No symbol at point")))) - - - (defun elisp-bytecompile-and-load () - (interactive) - (or buffer-file-name - (error "The buffer must be saved in a file first")) - (require 'bytecomp) - ;; Recompile if file or buffer has changed since last compilation. - (when (and (buffer-modified-p) - (y-or-n-p (format "save buffer %s first? " (buffer-name)))) - (save-buffer)) - (let ((filename (expand-file-name buffer-file-name))) - (with-temp-buffer - (byte-compile-file filename t)))) - - (use-package elisp-mode - :bind (:map emacs-lisp-mode-map - (("C-c C-M" . emacs-lisp-macroexpand) - ("C-c C-m" . elisp-macroexpand-all) - ("C-c C-k" . elisp-bytecompile-and-load) - ;; ("C-c C-p" . pp-eval-last-sexp) - ("M-." . elisp-find-definition) - ("M-," . pop-tag-mark) - ("C-c <" . lc-show-package-summary)))) - #+end_src -*** Erlang - #+begin_src emacs-lisp - (use-package erlang - :disabled t - :ensure t - :custom ((inferior-erlang-machine-options '("shell")) - (inferior-erlang-machine "rebar3") - (inferior-erlang-shell-type nil) - (erlang-indent-level 4)) - - ;; :bind (:map erlang-mode-map (("C-c C-z" . jao-vterm-repl-pop-to-repl))) - - :init - ;; (require 'jao-vterm-repl) - ;; (add-to-list 'auto-mode-alist '("^rebar\\.config\\`" . erlang-mode)) - ;; (jao-vterm-repl-register "rebar.config" "rebar3 shell" "^[0-9]+> ") - - :config - ;; (defun jao-erlang-current-module () - ;; (when (save-excursion (goto-char (point-min)) - ;; (re-search-forward "^-module(\\([^)]+\\))" nil t)) - ;; (match-string-no-properties 1))) - - ;; (defun jao-erlang-compile (arg) - ;; (interactive "P") - ;; (save-some-buffers) - ;; (when-let ((mname (jao-erlang-current-module))) - ;; (with-current-buffer (jao-vterm-repl) - ;; (vterm-send-string (format "c(%s).\n" mname)) - ;; (sit-for 0) - ;; (setq compilation-last-buffer (current-buffer)) - ;; (when arg (jao-vterm-repl-pop-to-repl))))) - - ;; (setq erlang-shell-function #'jao-vterm-repl - ;; erlang-shell-display-function #'jao-vterm-repl-pop-to-repl - ;; erlang-compile-function #'jao-erlang-compile) - ) - #+end_src -*** Idris - #+begin_src emacs-lisp - (use-package idris-mode - :ensure t - :custom ((idris-interpreter-path "idris2") - (idris-pretty-printer-width 80) - (idris-repl-history-file "~/.emacs.d/cache/idris-history.eld") - (idris-stay-in-current-window-on-compiler-error t))) - (jao-define-attached-buffer "^\\*idris.*") - #+end_src -*** Clojure - #+begin_src emacs-lisp - (use-package clojure-mode - :ensure t - :config - (defun jao-clojure--fix-things () - (setq-local completion-styles '(basic partial-completion emacs22)) - (eldoc-mode 1) - (setq mode-name "λ")) - :hook (clojure-mode . jao-clojure--fix-things)) - - (use-package cider - :ensure t - :commands cider-mode - :init (setq cider-annotate-completion-candidates t - cider-auto-select-error-buffer nil - cider-auto-select-test-report-buffer nil - cider-eldoc-display-for-symbol-at-point t - cider-eldoc-ns-function #'identity ;; #'cider-last-ns-segment - cider-enrich-classpath nil - cider-lein-parameters "repl :headless :host localhost" - cider-mode-line " ÷" - cider-prompt-for-symbol nil - cider-repl-history-file - (expand-file-name "~/.emacs.d/cache/cider.history") - cider-repl-pop-to-buffer-on-connect nil - cider-repl-use-pretty-printing t - cider-show-error-buffer 'except-in-repl - cider-test-show-report-on-success nil - cider-use-fringe-indicators nil - cider-use-overlays nil - clojure-docstring-fill-column 72 - nrepl-prompt-to-kill-server-buffer-on-quit nil) - :bind (("" . cider-selector))) - - (with-eval-after-load "cider-test" - (advice-add 'cider-scale-background-color :override - (lambda () (frame-parameter nil 'background-color))) - (setq cider-test-items-background-color - (frame-parameter nil 'background-color))) - - (use-package cider-macroexpansion - :after cider - :diminish " µ") - - #+end_src -*** Geiser - #+begin_src emacs-lisp - (defun jao-org--set-geiser-impl () (setq-local geiser-repl--impl 'guile)) - (add-hook 'org-mode-hook #'jao-org--set-geiser-impl) - - (jao-load-path "geiser/geiser/elisp") - (use-package geiser - :init - (setq geiser-repl-history-filename "~/.emacs.d/cache/geiser-history" - geiser-repl-startup-time 20000 - geiser-debug-auto-display-images-p t - geiser-log-verbose-p t - geiser-active-implementations '(guile) - geiser-default-implementation 'guile)) - - (jao-load-path "geiser/guile") - (use-package geiser-guile) - - ;; (jao-load-path "geiser/mit") - ;; (use-package geiser-mit) - - ;; (jao-load-path "geiser/chicken") - ;; (use-package geiser-chicken) - - ;; (jao-load-path "geiser/chibi") - ;; (use-package geiser-chibi) - - ;; (jao-load-path "geiser/chez") - ;; (use-package geiser-chez - ;; :init (setq geiser-chez-binary "scheme")) - - ;; (jao-load-path "geiser/gambit") - ;; (use-package geiser-gambit) - - ;; (jao-load-path "geiser/gauche") - ;; (use-package geiser-gauche) - - (jao-define-attached-buffer "^\\* ?Geiser .*\\*" 0.4) - (jao-define-attached-buffer "^\\* Guile REPL \\*" 0.4) - - #+end_src -*** Haskell -***** packages - #+begin_src emacs-lisp - (use-package haskell-mode - :ensure t - :custom - ((inferior-haskell-find-project-root t) - (haskell-check-remember-last-command-p nil) - (haskell-font-lock-symbols nil) - (haskell-interactive-popup-errors nil) - (haskell-process-auto-import-loaded-modules t) - (haskell-process-log t) - (haskell-process-suggest-remove-import-lines t) - (haskell-process-suggest-hoogle-imports t) - (haskell-process-type 'cabal-repl) - (haskell-process-use-presentation-mode t) - (haskell-stylish-on-save nil)) - - :config - (defun jao-haskell-hoogle (query) - (interactive (hoogle-prompt)) - (haskell-hoogle query t)) - - (put 'haskell-process-args-cabal-repl - 'safe-local-variable - (apply-partially #'seq-every-p #'stringp)) - - (require 'haskell-doc) - (dolist (h '(interactive-haskell-mode - haskell-doc-mode - haskell-decl-scan-mode - haskell-indentation-mode - haskell-auto-insert-module-template)) - (add-hook 'haskell-mode-hook h)) - - :bind (:map haskell-mode-map - (("C-c C-d" . jao-haskell-hoogle) - ("C-c h" . haskell-hoogle-lookup-from-local) - ("C-c C-c" . haskell-compile)))) - - (require 'haskell) - - (diminish 'interactive-haskell-mode " λ") - (diminish haskell-doc-mode) - (diminish haskell-decl-scan-mode) - - (jao-define-attached-buffer "\\*hoogle\\*.*") - - ;; needs cabal install apply-refact - (use-package hlint-refactor - :ensure t - :after (haskell-mode) - :diminish "" - :hook (haskell-mode . hlint-refactor-mode)) - #+end_src -***** transient - #+begin_src emacs-lisp - (jao-transient-major-mode haskell - ["Imports" - ("in" "Navigate imports" haskell-navigate-imports) - ("if" "Format imports" haskell-mode-format-imports) - ("is" "Sort imports" haskell-sort-imports) - ("ia" "Align imports" haskell-align-imports)] - ["Code" - ("c" "Compile" haskell-compile) - ("s" "stylish on buffer" haskell-mode-stylish-buffer)] - ["Hoogle" - ("h" "Hoogle" jao-haskell-hoogle) - ("H" "Hoogle from local server" haskell-hoogle-lookup-from-local)]) - #+end_src -*** Pie - #+begin_src emacs-lisp - (jao-load-path "pie") - (use-package pie - :demand t - :commands (pie-mode)) - #+end_src -*** Lisp - #+begin_src emacs-lisp :tangle no - (use-package sly - :ensure t - :init (setq inferior-lisp-program "sbcl") - :config (sly-setup)) - - (use-package sly-quicklisp - :after (sly) - :ensure t) - #+end_src -*** Prolog - #+BEGIN_SRC emacs-lisp - (use-package ediprolog :ensure t) - - (use-package prolog - :ensure t - :commands (run-prolog prolog-mode mercury-mode) - :init (progn - (setq prolog-system 'swi) - (add-to-list 'auto-mode-alist '("\\.pl$" . prolog-mode)) - (setq prolog-consult-string '((t "[%f]."))) - (setq prolog-program-name - '(((getenv "EPROLOG") (eval (getenv "EPROLOG"))) - (eclipse "eclipse") - (mercury nil) - (sicstus "sicstus") - (swi "swipl") - (t "prolog"))))) - #+END_SRC -*** Racket - #+begin_src emacs-lisp - (use-package racket-mode - :ensure t - :init (setq racket-show-functions '(racket-show-echo-area) - racket-documentation-search-location 'local) - :config - (jao-define-attached-buffer "\\`\\*Racket REPL") - (jao-define-attached-buffer "\\`\\*Racket Describe" 0.5) - (add-hook 'racket-mode-hook #'paredit-mode) - (require 'racket-xp) - (add-hook 'racket-mode-hook #'racket-xp-mode) - :bind (:map racket-xp-mode-map (("C-c C-S-d" . racket-xp-documentation) - ("C-c C-d" . racket-xp-describe)))) - - #+end_src -*** Python -***** Virtual envs (with eshell support) - See also [[https://github.com/porterjamesj/virtualenvwrapper.el][the docs]]. - #+begin_src emacs-lisp - (use-package virtualenvwrapper - :ensure t - :config - (venv-initialize-eshell) - (jao-compilation-env "VIRTUAL_ENV")) - #+end_src -* Text/data formats -*** YAML - #+begin_src emacs-lisp - (use-package yaml-mode :ensure t) - #+end_src -*** JSON - #+BEGIN_SRC emacs-lisp - (use-package json-mode :ensure t) - ;; (use-package json-navigator :ensure nil) - #+END_SRC -* Graphics -*** Images - #+begin_src emacs-lisp - (setq image-use-external-converter t) - #+end_src -*** Gnuplot - #+BEGIN_SRC emacs-lisp - (use-package gnuplot - :ensure t - :commands (gnuplot-mode gnuplot-make-buffer) - :init (add-to-list 'auto-mode-alist '("\\.gp$" . gnuplot-mode))) - #+END_SRC -* Network -*** nm applet - #+begin_src emacs-lisp - (jao-shell-def-exec jao-nm-applet "nm-applet") - - (defun jao-toggle-nm-applet () - (interactive) - (if (jao-shell-running-p "nm-applet") - (jao-shell-string "killall nm-applet") - (jao-nm-applet))) - #+end_src -*** enwc - #+begin_src emacs-lisp - (use-package enwc - :ensure t - :custom ((enwc-default-backend 'nm) - (enwc-wired-device "wlp164s0") - (enwc-wireless-device "wlp164s0") - (enwc-display-mode-line nil))) - #+end_src - -*** bluetooth - #+BEGIN_SRC emacs-lisp - (use-package bluetooth :ensure t) - #+END_SRC -*** vpn - #+begin_src emacs-lisp - (use-package jao-mullvad :demand t) - #+end_src -*** ssh - #+begin_src emacs-lisp - (use-package tramp) - (defun jao-tramp-hosts () - (seq-uniq - (mapcan (lambda (x) - (remove nil (mapcar 'cadr (apply (car x) (cdr x))))) - (tramp-get-completion-function "ssh")) - #'string=)) - - (defun jao-ssh () - (interactive) - (let ((h (completing-read "Host: " (jao-tramp-hosts)))) - (jao-afio-goto-scratch) - (jao-exec-in-term (format "ssh %s" h) (format "*ssh %s*" h)))) - #+end_src -* Chats -*** circe - #+begin_src emacs-lisp - (defvar jao-libera-channels '()) - (defvar jao-oftc-channels '()) - (defvar jao-bitlbee-channels '()) - - (use-package circe - :ensure t - :bind (:map circe-channel-mode-map - (("C-c C-a" . lui-track-jump-to-indicator))) - :init - (setq circe-chat-buffer-name "{target}" - circe-default-realname "https://jao.io" - circe-default-part-message "" - circe-default-quit-message "" - circe-ignore-list nil - circe-server-coding-system '(undecided . undecided) - circe-server-killed-confirmation 'ask-and-kill-all - circe-server-auto-join-default-type :after-auth - circe-format-say "({nick}) {body}" - circe-format-self-say "(jao) {body}" - circe-new-buffer-behavior 'ignore - circe-new-buffer-behavior-ignore-auto-joins t - circe-nickserv-ghost-style 'after-auth - circe-prompt-string ": " - circe-completion-suffix ", " - circe-reduce-lurker-spam t - - circe-nick-next-function - (lambda (old) - (replace-regexp-in-string "-" "`" (circe-nick-next old))) - - circe-lagmon-mode-line-format-string "" ;; "%.0f " - circe-lagmon-mode-line-unknown-lag-string "" ;; "? " - circe-lagmon-timer-tick 120 - circe-lagmon-reconnect-interval 180 - - lui-max-buffer-size 30000 - lui-fill-column 80 - lui-time-stamp-position 'right - lui-time-stamp-format "%H:%M" - lui-flyspell-p nil - - lui-track-indicator (if window-system 'fringe 'bar) - lui-track-behavior 'before-tracking-next-buffer) - :config - - (define-minor-mode jao-circe-user-number-mode "" - :lighter (:eval (format " [%s]" (length (circe-channel-nicks))))) - - (add-hook 'circe-channel-mode-hook #'jao-circe-user-number-mode) - - (defun circe-command-RECOVER (&rest ignore) - "Recover nick" - (let* ((fn (jao--get-user/password "freenode")) - (u (car fn)) - (p (cadr fn))) - (circe-command-MSG "nickserv" (format "IDENTIFY %s %s" u p)) - (circe-command-MSG "nickserv" (format "GHOST %s" u)) - (circe-command-MSG "nickserv" (format "RELEASE %s" u)) - (circe-command-NICK u))) - - (defun circe-command-NNICKS (&rest _) - "Echo number of nicks" - (circe-display-server-message - (format "%d nicks in this channel" (length (circe-channel-nicks))))) - - (advice-add 'circe-command-NAMES :after #'circe-command-NNICKS) - - (setq circe-network-options - (let ((up (jao--get-user/password "libera")) - (oup (jao--get-user/password "oftc")) - (bup (jao--get-user/password "bitlbee"))) - `(("Libera Chat" - :nick ,(car up) :channels ,jao-libera-channels - :tls t :sasl-username ,(car up) :sasl-password ,(cadr up)) - ("OFTC" :nick ,(car oup) :channels ,jao-oftc-channels - :nickserv-password ,(cadr oup) - :tls t :sasl-username ,(car oup) :sasl-password ,(cadr oup)) - ("Bitlbee" - :host "127.0.0.1" :nick ,(car bup) - :channels ,jao-bitlbee-channels - :lagmon-disabled t - :nickserv-password ,(cadr bup) :user ,(car bup))))) - - (jao-shorten-modes 'circe-channel-mode - 'circe-server-mode - 'circe-query-mode) - - (enable-lui-track) - (circe-lagmon-mode) - (enable-circe-display-images)) - #+end_src -*** slack - [[https://github.com/jackellenberger/emojme#finding-a-slack-token][How to get a token]]: It's easyish! Open and sign into the slack - customization page, e.g. https://my.slack.com/customize, right - click anywhere > inspect element. Open the console and paste: - - =window.prompt("your api token is: ", TS.boot_data.api_token)= - - Lately things are iffy. We've needed to add the ~:override~ to - slack-counts update, and it might be needed to replace - ~slack-conversations-view~ by ~slack-conversations-history~ - - #+begin_src emacs-lisp - (use-package slack - :commands (slack-start) - :init - (setq slack-alert-icon (jao-data-file "slack.svg") - slack-buffer-emojify nil - slack-buffer-create-on-notify t - slack-display-team-name t - slack-typing-visibility 'never ;; 'buffer, 'frame - slack-profile-image-file-directory "/tmp/slack-imgs/" - slack-image-file-directory "/tmp/slack-imgs/" - slack-file-dir "~/var/download/slack/" - slack-prefer-current-team t - slack-message-tracking-faces '(warning) - slack-log-level 'warn - slack-message-custom-notifier (lambda (msg room team) room)) - :bind (:map slack-mode-map (("@" . slack-message-embed-mention) - ("#" . slack-message-embed-channel)) - :map slack-message-buffer-mode-map - (("C-c C-e" . slack-message-edit) - ("C-c C-a" . slack-file-upload))) - :config - (dolist (f (list slack-file-dir slack-image-file-directory)) - (when (not (file-exists-p f)) (make-directory f))) - - (jao-shorten-modes 'slack-message-buffer-mode - 'slack-thread-message-buffer-mode) - (jao-tracking-faces 'warning) - - (jao-define-attached-buffer "\\*Slack .+ Edit Message [0-9].+" 20)) - #+end_src -*** telegram - #+begin_src emacs-lisp - (use-package telega - :ensure t - :custom - (telega-use-tracking-for '(unmuted) ;; '(or unmuted mention) - telega-rainbow-color-custom-for nil - telega-msg-rainbow-title nil - telega-sticker-set-download t) - :config - (define-key global-map (kbd "C-c C-t") telega-prefix-map) - (setq telega-chat-show-avatars nil - telega-chat-prompt-format ">> " - telega-root-show-avatars nil - telega-emoji-use-images nil - telega-temp-dir "/tmp/telega" - telega-symbol-checkmark "·" - telega-symbol-heavy-checkmark "×" - telega-symbol-verified "*" - telega-symbol-horizontal-bar - (propertize "-" 'face 'jao-themes-f00) - telega-symbol-vertical-bar - (propertize "| " 'face 'jao-themes-dimm) - telega-mode-line-string-format - '(:eval (telega-mode-line-unread-unmuted)) - telega-use-images (display-graphic-p)) - (with-eval-after-load "tracking" - (jao-shorten-modes 'telega-chat-mode) - (jao-tracking-faces 'telega-tracking)) - (telega-mode-line-mode 1)) - #+end_src -*** startup - #+begin_src emacs-lisp - (defun jao-chats (&optional p) - (interactive "P") - (when (or p (y-or-n-p "Connect to slack? ")) - (slack-start)) - (when (or p (y-or-n-p "Connect to telegram? ")) - (telega)) - (when (or p (y-or-n-p "Connect to libera? ")) - (unless (get-buffer "irc.libera.chat:6697") - (circe "Libera Chat")))) - - (defun jao-all-chats () (interactive) (jao-chats t)) - - (defun jao-chats-telega () - (interactive) - (jao-buffer-same-mode '(telega-root-mode telega-chat-mode))) - - (defun jao-chats-slack () - (interactive) - (jao-buffer-same-mode 'slack-message-buffer-mode)) - - (defun jao-chats-irc () - (interactive) - (jao-buffer-same-mode '(circe-channel-mode circe-query-mode erc-mode))) - - #+end_src -* Multimedia -*** mixer - #+begin_src emacs-lisp - (defun jao-mixer-get-level (&optional dev) - (interactive) - (let* ((dev (or dev "Master")) - (s (shell-command-to-string (format "amixer sget %s" dev))) - (s (car (last (split-string s "\n" t))))) - (when (string-match ".*Front .*\\[\\([0-9]+\\)%\\] .*" s) - (let ((level (match-string 1 s))) - (message "%s level: %s%%" dev level) - (string-to-number level))))) - - (defun jao-mixer-set (dev v) - (jao-shell-string "amixer sset" dev v) - (jao-mixer-get-level dev)) - - (defun jao-mixer-master-toggle () - (interactive) - (jao-mixer-set "Master" "toggle")) - - (defun jao-mixer-master-up () - (interactive) - (jao-mixer-set "Master" "10%+")) - - (defun jao-mixer-master-down () - (interactive) - (jao-mixer-set "Master" "10%-")) - - (defun jao-mixer-capture-up () - (interactive) - (jao-mixer-set "Capture" "10%+")) - - (defun jao-mixer-capture-down () - (interactive) - (jao-mixer-set "Capture" "10%-")) - - (jao-shell-def-exec jao-audio-applet "pasystray") - - (defun jao-toggle-audio-applet () - (interactive) - (if (string-empty-p (jao-shell-string "pidof pasystray")) - (jao-audio-applet) - (jao-shell-string "killall pasystray"))) - - (global-set-key (kbd "") #'jao-toggle-audio-applet) - - #+end_src -*** mpris - #+begin_src emacs-lisp - (defun jao-mpris-lyrics (&optional force) - (interactive "P") - (jao-show-lyrics force #'jao-mpris-artist-title)) - - (use-package jao-mpris :demand t) - - (defalias 'jao-streaming-list #'ignore) - (defalias 'jao-streaming-like #'ignore) - (defalias 'jao-streaming-dislike #'ignore) - (defalias 'jao-streaming-lyrics #'jao-mpris-lyrics) - (defalias 'jao-streaming-toggle #'jao-mpris-play-pause) - (defalias 'jao-streaming-next #'jao-mpris-next) - (defalias 'jao-streaming-prev #'jao-mpris-previous) - (defalias 'jao-streaming-current #'jao-mpris-show-osd) - (defalias 'jao-streaming-seek #'jao-mpris-seek) - (defalias 'jao-streaming-seek-back #'jao-mpris-seek-back) - (defalias 'jao-streaming-volume #'jao-mpris-vol) - (defalias 'jao-streaming-volume-down #'jao-mpris-vol-down) - - (jao-mpris-register "playerctld" - :session (if jao-modeline-in-minibuffer -10 70)) - - #+end_src -*** mpc - #+begin_src emacs-lisp - (use-package jao-mpc - :demand t - :commands jao-mpc-setup) - - (defvar jao-mopidy-port 6669) - (defvar jao-mpc-last-port nil) - - (defun jao-mpc-toggle-port () - (interactive) - (setq jao-mpc-last-port (unless jao-mpc-last-port jao-mopidy-port) - jao-mpc-port jao-mpc-last-port)) - - (jao-mpc-setup jao-mopidy-port (if jao-modeline-in-minibuffer -10 70)) - - (defun jao-mpc-pport (&optional mop) - (cond ((or mop (jao-mpc--playing-p jao-mopidy-port)) jao-mopidy-port) - ((jao-mpc--playing-p) nil) - (t jao-mpc-last-port))) - - (defmacro jao-defun-play (name &optional mpc-name) - (let ((arg (gensym))) - `(defun ,(intern (format "jao-player-%s" name)) (&optional ,arg) - (interactive "P") - (,(intern (format "jao-mpc-%s" (or mpc-name name))) - (setq jao-mpc-last-port (jao-mpc-pport ,arg)))))) - - (jao-defun-play toggle) - (jao-defun-play next) - (jao-defun-play previous) - (jao-defun-play stop) - (jao-defun-play echo echo-current) - (jao-defun-play list show-playlist) - (jao-defun-play info lyrics-track-data) - (jao-defun-play browse show-albums) - (jao-defun-play select-album) - - (defun jao-player-seek (delta) (jao-mpc-seek delta (jao-mpc-pport))) - - (defalias 'jao-player-connect 'jao-mpc-connect) - (defalias 'jao-player-play 'jao-mpc-play) - #+end_src -*** transients - #+begin_src emacs-lisp - - (require 'jao-lyrics) - (setq jao-lyrics-info-function #'jao-player-info) - - (defun jao-player-volume-delta (raise) - (jao-player-vol-delta (if raise 5 -5)) - (sit-for 0.05) - (jao-player-show-volume)) - - (defun jao-player-volume-raise () - (interactive) - (jao-player-volume-delta t)) - - (defun jao-player-volume-lower () - (interactive) - (jao-player-volume-delta nil)) - - (defun jao-player-show-volume () - (interactive) - (jao-notify "Volume" (format "%s%%" (jao-player-volume)))) - - (defun jao-player-seek-10 () (interactive) (jao-player-seek 10)) - (defun jao-player-seek--10 () (interactive) (jao-player-seek -10)) - - (defun jao-streaming-clear () (interactive) (jao-mpc-clear jao-mopidy-port)) - - (defun jao-streaming-echo-current () - (interactive) - (jao-mpc-echo-current jao-mopidy-port)) - - (defun jao-streaming-show-playlist () - (interactive) - (jao-mpc-show-playlist jao-mopidy-port)) - - (defun jao-player-level-cap () (interactive) (jao-mixer-level "Capture")) - - (use-package jao-random-album :demand t) - - (jao-def-exec-in-term "aptitude" "aptitude" (jao-afio--goto-scratch)) - (jao-def-exec-in-term "htop" "htop" (jao-afio--goto-scratch)) - - (transient-define-prefix jao-transient-streaming () - [:description - (lambda () (format "Streaming using %s" jao-mpris-player)) - ;; ["Search" - ;; ("a" "album" jao-streaming-album) - ;; ("A" "artist" jao-streaming-artist) - ;; ("t" "track" jao-streaming-track) - ;; ("P" "playlist" jao-streaming-playlist)] - ["Play" - ("s" "toggle" jao-streaming-toggle) - ("n" "next" jao-streaming-next) - ("p" "previous" jao-streaming-prev)] - ["Seek & shout" - ("f" "seek fwd" jao-streaming-seek :transient t) - ("F" "seek bwd" jao-streaming-seek-back :transient t) - ("u" "up" jao-streaming-volume :transient t) - ("d" "down" jao-streaming-volume-down :transient t)] - ["Browse" - ("l" "playing list" jao-streaming-list) - ("L" "lyrics" jao-streaming-lyrics) - ("w" "currently playing" jao-streaming-current)] - ["Act" - ("k" "like" jao-streaming-like) - ("K" "dislike" jao-streaming-dislike)]]) - - (transient-define-prefix jao-transient-media () - [["Play" - ("m" "toggle" jao-player-toggle) - ("n" "next" jao-player-next) - ("p" "previous" jao-player-previous) - ("s" "select album" jao-player-select-album)] - ["Seek and search" - ("f" "seek fwd" jao-player-seek-10 :transient t) - ("F" "seek bwd" jao-player-seek--10 :transient t) - ("a" "search album" jao-mpc-search-and-select-album)] - ["Browse" - ("b" "browse" jao-player-browse) - ("l" "show play list" jao-player-list) - ("L" "show lyrics" jao-show-lyrics) - ("w" "now playing" jao-player-echo)] - ["Master volume" - ("d" "master down" jao-mixer-master-down :transient t) - ("u" "master up" jao-mixer-master-up :transient t) - ("M" "master toggle" jao-mixer-master-toggle) - ("v" "show" jao-mixer-get-level)] - ["Capture volume" - ("D" "capture down" jao-mixer-capture-down :transient t) - ("U" "capture up" jao-mixer-capture-up :transient t) - ("V" "show" jao-player-level-cap)] - ["Utilities" - ("c" "reconnect to mpd" jao-player-connect) - ("N" "next random album" jao-random-album-next) - ("r" (lambda () - (concat (if jao-random-album-p "dis" "en") "able random album")) - jao-random-album-toggle) - ("P" (lambda () - (concat "Toggle to " - (if (equal jao-mpc-last-port jao-mopidy-port) - "mpd" "mopidy"))) - jao-mpc-toggle-port)]]) - - (global-set-key (kbd "s-m") #'jao-transient-media) - - #+end_src -* General transients - #+begin_src emacs-lisp - (defun jao-list-packages () - (interactive) - (jao-afio--goto-scratch-1) - (package-list-packages)) - - (defun jao-window-system-p () - (or jao-exwm-enabled jao-xmonad-enabled jao-sway-enabled)) - - (transient-define-prefix jao-transient-utils () - "Global operations in X11." - [["Notes" - ("n" "capture note" jao-org-notes-open-or-create) - ("/" "search notes" jao-org-notes-open) - ("\\" "grep notes" jao-org-notes-grep)] - ["Documents" :if jao-window-system-p - ("d" "go to doc" jao-select-pdf) - ("D" "open to doc" jao-open-doc)] - ["Packages" - ("a" "aptitude" jao-term-aptitude) - ("l" "packages" jao-list-packages)] - ["Monitors" - ("p" "htop" jao-term-htop) - ("v" "vpn status" jao-mullvad-status) - ("m" "set tmr" tmr)] - ["Network" - ("S" "ssh" jao-ssh) - ("b" "bluetooth" bluetooth-list-devices) - ("c" "connect chats" jao-all-chats) - ("N" "network interfaces" enwc)] - ["Chats" - ("t" "telegram" jao-chats-telega) - ("s" "slack" jao-chats-slack) - ("i" "irc" jao-chats-irc) - ("T" "telegram rooster" telega)] - ["Window system" :if jao-window-system-p - ("w" "set wallpaper" jao-set-wallpaper) - ("W" "set radom wallpaper" jao-set-random-wallpaper) - ("x" "restart xmobar" jao-xmobar-restart :if jao-exwm-enabled-p) - ("x" "kill xmobar" jao-xmobar-kill :if jao-xmonad-enabled-p)] - ["Helpers" - ("r" "org reveal" org-reveal) - ("k" (lambda () (concat "keyboard" (when (jao-kb-toggled-p) "*"))) - jao-kb-toggle :if jao-window-system-p) - ("M" (lambda () (concat "minibuffer" (when jao-minibuffer-mode "*"))) - jao-minibuffer-mode)]]) - - (global-set-key (kbd "s-w") #'jao-transient-utils) - #+end_src -* Key bindings - #+begin_src emacs-lisp - (global-set-key "\C-cj" #'join-line) - (global-set-key "\C-cn" #'next-error) - (global-set-key "\C-cq" #'auto-fill-mode) - (global-set-key "\C-xr\M-w" #'kill-rectangle-save) - (global-set-key "\C-c\C-z" #'comment-or-uncomment-region) - (global-set-key "\C-z" #'comment-or-uncomment-region) - (global-set-key (kbd "C-c W") #'jao-open-in-x-frame) - #+end_src -* Last minute (post.el) - #+begin_src emacs-lisp - (jao-load-site-el "post") - #+end_src diff --git a/lib/doc/jao-doc-view.el b/lib/doc/jao-doc-view.el index ea55565..b8fbe1b 100644 --- a/lib/doc/jao-doc-view.el +++ b/lib/doc/jao-doc-view.el @@ -1,4 +1,4 @@ -;; jao-doc-view.el -- Remembering visited documents -*- lexical-binding: t; -*- +;;; jao-doc-view.el -- extensions for doc-view -*- lexical-binding: t; -*- ;; Copyright (c) 2013, 2015, 2017, 2018, 2019, 2021, 2022 Jose Antonio Ortega Ruiz @@ -18,13 +18,7 @@ ;; Author: Jose Antonio Ortega Ruiz ;; Start date: Fri Feb 15, 2013 01:21 -;;; Comentary: -;; Some utilities to keep track of visited documents and their structure. - -;;; Code: - - ;;; Session (require 'doc-view) @@ -84,7 +78,6 @@ (add-hook 'pdf-view-mode-hook #'jao-doc-view--save-session) (add-hook 'nov-mode-hook #'jao-doc-view--save-session) - ;;; PDF info (defvar-local jao--pdf-outline nil) @@ -145,7 +138,6 @@ (or (jao-doc-view-section-title) (when buffer-file-name (jao-doc-view-title buffer-file-name))))) - ;;; imenu (defvar-local jao-doc-view--imenu-file nil) (defvar-local jao-doc-view--goer 'jao-doc-view-goto-page) @@ -173,7 +165,6 @@ (add-hook 'doc-view-mode-hook #'jao-doc-view--enable-imenu) - ;;; Page trailing (defvar-local jao-doc-view--trail-back ()) (defvar-local jao-doc-view--trail-fwd ()) @@ -199,7 +190,6 @@ (advice-add 'doc-view-goto-page :before #'jao-doc-view--trail-push) - ;;; Find URLs (defun jao-doc-view--page-urls (all) (if doc-view--current-converter-processes @@ -232,5 +222,5 @@ (when (null (cdr urls)) (car urls)))) (browse-url url)))))) - +;;; . (provide 'jao-doc-view) diff --git a/lib/net/jao-notmuch.el b/lib/net/jao-notmuch.el index 05ec394..edffade 100644 --- a/lib/net/jao-notmuch.el +++ b/lib/net/jao-notmuch.el @@ -32,8 +32,8 @@ (require 'notmuch-tree) (require 'notmuch-show) - -;;;; Targetting the displayed message from the tree view + +;;; Targetting the displayed message from the tree view (defvar-local jao-notmuch--tree-buffer nil) (declare eww--url-at-point "eww") @@ -130,8 +130,8 @@ (completing-read "Browse URL: " urls)) (message "No URLs in this message"))) - -;;;; Navigating URLs + +;;; Navigating URLs (require 'ffap) @@ -157,8 +157,8 @@ (thing-at-point-url-at-point))) (browse-url url))) - -;;;; Toggling mime parts and images + +;;; Toggling mime parts and images (defun jao-notmuch--toggle-mime () (save-excursion @@ -220,8 +220,8 @@ (with-current-buffer notmuch-tree-message-buffer (jao-notmuch--view-html)))))) - -;;;; Keeping track of unread messages in current tree view + +;;; Keeping track of unread messages in current tree view (defun jao-notmuch--looking-at-new-p (&optional p) (when-let (ts (if p (plist-get p :tags) (notmuch-show-get-tags))) @@ -278,8 +278,8 @@ `((:eval (jao-notmuch--format-header-line ,(buffer-name tb) ,cb ,subject))) (concat " " subject))) - -;;;; Outline mode for tree view + +;;; Outline mode for tree view (defun jao-notmuch-tree--msg-prefix (msg) (insert (propertize (if (plist-get msg :first) "> " " ") 'display " "))) @@ -347,8 +347,8 @@ (interactive "P") (jao-notmuch-tree--next t t exit)) - -;;;; Updating the tree window after insertion + +;;; Updating the tree window after insertion (defun jao-notmuch--tree-sentinel (proc &rest _) (when (eq (process-status proc) 'exit) @@ -359,8 +359,8 @@ (jao-notmuch-tree-outline-setup prefix) (advice-add 'notmuch-tree-process-sentinel :after #'jao-notmuch--tree-sentinel)) - -;;;; Tagging + +;;; Tagging (defsubst jao-notmuch--has-tag (tag) (member tag (notmuch-tree-get-tags))) @@ -430,8 +430,8 @@ (advice-add 'notmuch-mua-new-reply :around #'jao-notmuch-mua-new-reply) - -;;;; Results formatters + +;;; Results formatters (defun jao-notmuch-format-tags (fmt msg) (let ((ts (thread-last (notmuch-tree-format-field "tags" "%s" msg) @@ -451,12 +451,12 @@ 'notmuch-tree-no-match-author-face))) (propertize auth 'face face))) -(defun jao-notmuch-tree-and-subject (fmt msg) +(defun jao-notmuch-format-tree-and-subject (fmt msg) (let ((tr (notmuch-tree-format-field "tree" " %s" msg)) (sb (notmuch-tree-format-field "subject" " %s" msg))) (format-spec fmt `((?s . ,(concat tr sb)))))) -(defun jao-notmuch-msg-ticks (mails-rx msg) +(defun jao-notmuch-format-msg-ticks (mails-rx msg) (let ((headers (plist-get msg :headers))) (cond ((string-match-p mails-rx (or (plist-get headers :To) "")) (propertize " »" 'face 'notmuch-tree-match-tree-face)) diff --git a/notmuch.org b/notmuch.org deleted file mode 100644 index b9edb29..0000000 --- a/notmuch.org +++ /dev/null @@ -1,675 +0,0 @@ -#+property: header-args:emacs-lisp :lexical t :tangle yes :comments yes :results silent :shebang ";; -*- lexical-binding: t; -*-" :tangle-mode (identity #o644) -#+title: notmuch configuration - -* minibuffer - #+begin_src emacs-lisp - (defvar jao-notmuch-minibuffer-string "") - - (defvar jao-notmuch-minibuffer-queries - '((:name "" :query "tag:new and not tag:draft" :face jao-themes-f00) - (:name "B" :query "tag:new and tag:bigml and tag:inbox" :face default) - (:name "b" :query "tag:new and tag:bigml and tag:bugs" - :face jao-themes-error) - (:name "S" :query "tag:new and tag:bigml and tag:support" :face default) - (:name "W" - :query "tag:new and tag:bigml and not tag:\"/support|bugs|inbox/\"" - :face default) - (:name "I" - :query "tag:new and tag:jao and tag:inbox" - :face jao-themes-warning) - (:name "J" - :query "tag:new and tag:jao and not tag:\"/local|hacking|draft|inbox|prog|words/\"" - :face default) - (:name "H" :query "tag:new and tag:hacking and not tag:\"/emacs/\"") - (:name "E" :query "tag:new and tag:\"/emacs/\"") - (:name "l" :query "tag:new and tag:local") - (:name "F" :query "tag:new and tag:feeds and not tag:\"/emacs/\""))) - - (defun jao-notmuch-notify () - (let ((cnts (notmuch-hello-query-counts jao-notmuch-minibuffer-queries))) - (setq jao-notmuch-minibuffer-string - (mapconcat (lambda (c) - (propertize (format "%s%s" - (plist-get c :name) - (plist-get c :count)) - 'face (or (plist-get c :face) - 'jao-themes-dimm))) - cnts - " ")) - (jao-minibuffer-refresh))) - - (when jao-notmuch-enabled - (jao-minibuffer-add-variable 'jao-notmuch-minibuffer-string -20)) - #+end_src -* saved searches - #+begin_src emacs-lisp - (defvar jao-notmuch--new "tag:\"/^(unread|new)$/\"") - (defvar jao-notmuch--newa (concat jao-notmuch--new " AND ")) - - (defun jao-notmuch--q (d0 d1 &optional k qs st) - (let ((q (or (when qs (mapconcat #'identity qs " AND ")) - (concat jao-notmuch--newa - (mapconcat (lambda (d) (when d (concat "tag:" d))) - (list d0 d1) " AND "))))) - (list :name (concat d0 (when (and d1 (not (string= "" d1))) "/") d1) - :key k :query q :search-type (or st 'tree) - :sort-order 'oldest-first))) - - (defun jao-notmuch--qn (d0 d1 k qs &optional st) - (jao-notmuch--q d0 d1 k (cons jao-notmuch--new qs) st)) - - (defun jao-notmuch--sq (tag &optional k d0 d1) - (jao-notmuch--qn (or d0 "feeds") (or d1 tag) k (list (concat "tag:" tag)))) - - (defvar jao-notmuch--shared-tags - '("new" "unread" "flagged" "signed" "sent" "attachment" "forwarded" - "encrypted" "gmane" "gnus" "feeds" "rss" "mce" "trove" "prog" "emacs")) - - (defun jao-notmuch--subtags (tag &rest excl) - (let* ((cmd (concat "notmuch search --output=tags tag:" tag)) - (ts (split-string (shell-command-to-string cmd)))) - (seq-difference ts (append jao-notmuch--shared-tags (cons tag excl))))) - - (defvar jao-notmuch-feed-searches-news - (mapcar #'jao-notmuch--sq '("news" "fun" "words" "computers"))) - - (defvar jao-notmuch-feed-searches-hack - (mapcar #'jao-notmuch--sq - '("xmobar" "geiser" "mdk" "mailutils" "notmuch"))) - - (defvar jao-notmuch-feed-searches-lang - (append (mapcar #'jao-notmuch--sq - '( "lobsters" "clojure" "lisp" "scheme" - "haskell" "idris" "erlang" "pharo")) - `(,(jao-notmuch--qn "feeds" "prog" "fp" - '("tag:prog" "not tag:\"/emacs/\""))))) - - (defvar jao-notmuch-feed-searches-sci - (mapcar #'jao-notmuch--sq - '("philosophy" "math" "physics" "sci" "gr-qc" "quant-ph"))) - - (defvar jao-notmuch-feed-searches - (append jao-notmuch-feed-searches-news - jao-notmuch-feed-searches-hack - jao-notmuch-feed-searches-lang - jao-notmuch-feed-searches-sci)) - - (defvar jao-notmuch-bigml-searches - `(,(jao-notmuch--q "bigml" "inbox" "bi") - ,(jao-notmuch--q "bigml" "alba" "ba") - ,(jao-notmuch--q "bigml" "support" "bs") - ,(jao-notmuch--q "bigml" "bugs" "bb") - ,(jao-notmuch--q "bigml" "drivel" "bd") - ,(jao-notmuch--q "bigml" "lists" "bl"))) - - (defvar jao-notmuch-inbox-searches - `(,(jao-notmuch--q "jao" "inbox" "ji") - ,(jao-notmuch--q "jao" "bills" "jb") - ,(jao-notmuch--q "jao" "drivel" "jd") - ,(jao-notmuch--q "jao" "mdk" "jm") - ,(jao-notmuch--qn "jao" "hacking" "jh" - '("tag:hacking" "not tag:\"/emacs/\"")) - ,(jao-notmuch--qn "jao" "local" "jl" '("tag:local")))) - - (defvar jao-notmuch-mark-searches - `(,(jao-notmuch--q "jao" "drafts" "d" '("tag:draft")) - ,(jao-notmuch--q "bml" "flagged" "rb" '("tag:flagged" "tag:bigml")) - ,(jao-notmuch--q "jao" "flagged" "rj" '("tag:flagged" "tag:jao")) - ,(jao-notmuch--q "feeds" "flagged" "rf" '("tag:flagged" "tag:feeds")))) - - (defvar jao-notmuch-emacs-searches - `(,(jao-notmuch--sq "emacs" "ee" "emacs" "feeds") - ,(jao-notmuch--sq "emacs-help" "eh" "emacs" "help") - ,(jao-notmuch--sq "emacs-github" "eg" "emacs" "github") - ,(jao-notmuch--sq "emacs-devel" "ed" "emacs" "devel") - ,(jao-notmuch--sq "emacs-bugs" "eb" "emacs" "bugs") - ,(jao-notmuch--sq "emacs-diffs" "ec" "emacs" "diffs") - ,(jao-notmuch--sq "emacs-orgmode" "eo" "emacs" "org"))) - - (setq notmuch-saved-searches - (append jao-notmuch-inbox-searches - jao-notmuch-bigml-searches - jao-notmuch-mark-searches - jao-notmuch-feed-searches - jao-notmuch-emacs-searches)) - - (defvar jao-notmuch-dynamic-searches - `(,(jao-notmuch--q "bml" "today" "tb" '("tag:bigml" "date:24h..")) - ,(jao-notmuch--q "jao" "today" "tj" - '("tag:jao" "date:24h.." - "not tag:\"/(feeds|spam|local)/\"")))) - - (defvar jao-notmuch-new-searches - `(,(jao-notmuch--q "new" nil "nn" '("tag:new" "not tag:draft")) - ,(jao-notmuch--q "unread" nil "nu" '("tag:unread")) - (:query "*" :name "messages"))) - - (defun jao-notmuch-tree-widen-search () - (interactive) - (when-let ((query (notmuch-tree-get-query))) - (let ((notmuch-show-process-crypto (notmuch-tree--message-process-crypto))) - (notmuch-tree-close-message-window) - (notmuch-tree (string-replace jao-notmuch--newa "" query))))) - - (defun jao-notmuch-widen-searches (searches &optional extra) - (mapcar (lambda (s) - (let* ((q (plist-get s :query)) - (qs (string-replace jao-notmuch--newa "" q))) - (plist-put (copy-sequence s) :query (concat qs extra)))) - searches)) - - (defvar jao-notmuch-widened-searches - (jao-notmuch-widen-searches notmuch-saved-searches)) - - (defvar jao-notmuch-flagged-searches - (let ((s (seq-difference notmuch-saved-searches - jao-notmuch-mark-searches))) - (jao-notmuch-widen-searches s " AND tag:flagged"))) - - (defun jao-notmuch-jump-search (&optional widen) - (interactive "P") - (let ((notmuch-saved-searches - (if widen jao-notmuch-widened-searches notmuch-saved-searches))) - (notmuch-jump-search))) - - #+end_src -* tags - #+begin_src emacs-lisp - (setq notmuch-archive-tags '("+trove" "-new" "-inbox") - notmuch-show-mark-read-tags '("-new" "-unread") - notmuch-tag-formats - (let ((d `(:foreground ,(face-attribute 'jao-themes-dimm :foreground))) - (e `(:foreground ,(face-attribute 'jao-themes-error :foreground) - :weight bold))) - `(("unread") - ("signed") - ("new" "N") - ("replied" "↩" (propertize tag 'face '(:family "Fira Code"))) - ("sent" "S") - ("attachment" "📎") - ("deleted" "🗙" (propertize tag 'face '(:underline nil ,@e))) - ;; ("attachment" "+") - ;; ("deleted" "xxx" (propertize tag 'face '(:underline nil ,@e))) - ("flagged" "!" (propertize tag 'face ',e)) - ("jao" "j") - ("bigml" "b") - ("feeds" "f") - ("gmane" "g"))) - notmuch-tag-deleted-formats - '(("unread") - ("new") - (".*" (notmuch-apply-face tag 'notmuch-tag-deleted)))) - - (with-eval-after-load "notmuch-tag" - (advice-add #'notmuch-read-tag-changes - :filter-return (lambda (x) (mapcar #'string-trim x)))) - #+end_src -* package - #+begin_src emacs-lisp - (add-to-list 'load-path "/usr/local/share/emacs/site-lisp/") - - (use-package notmuch - :init - (setq notmuch-address-use-company t - notmuch-address-command (if jao-notmuch-enabled 'internal 'as-is) - notmuch-always-prompt-for-sender t - notmuch-draft-folder "local" - notmuch-draft-quoted-tags '("part") - notmuch-address-internal-completion '(received nil) - notmuch-fcc-dirs - '(("\\(support\\|education\\)@bigml.com" . nil) - (".*@bigml.com" . "bigml/trove +bigml +sent -new -unread") - (".*" . "jao/trove +jao +sent +trove -new -unread")) - notmuch-maildir-use-notmuch-insert t) - - :config - - (add-hook 'message-send-hook #'notmuch-mua-attachment-check) - - (when jao-notmuch-enabled - (define-key message-mode-map (kbd "C-c C-d") #'notmuch-draft-postpone) - (setq message-directory "~/var/mail/" - message-auto-save-directory "/tmp" - mail-user-agent 'message-user-agent)) - - :bind (:map notmuch-common-keymap - (("E" . jao-notmuch-open-enclosure) - ("B" . notmuch-show-resend-message) - ("b" . jao-notmuch-browse-urls)))) - - (use-package jao-notmuch :demand t) - - #+end_src -* hello - #+begin_src emacs-lisp - (defun jao-notmuch-hello--insert-searches (searches title) - (when-let (searches (notmuch-hello-query-counts searches)) - (let* ((cnt (when title - (seq-reduce (lambda (c q) - (+ c (or (plist-get q :count) 0))) - searches - 0))) - (title (if title (format "[ %d %s ]\n\n" cnt title) "\n"))) - (widget-insert (propertize title 'face 'jao-themes-f00)) - (let ((notmuch-column-control 1.0) - (start (point))) - (notmuch-hello-insert-buttons searches) - (indent-rigidly start (point) notmuch-hello-indent)) - cnt))) - - (defun jao-notmuch-hello-insert-inbox-searches () - (jao-notmuch-hello--insert-searches jao-notmuch-inbox-searches "inbox")) - - (defun jao-notmuch-hello-insert-bigml-searches () - (jao-notmuch-hello--insert-searches jao-notmuch-bigml-searches "bigml")) - - (defun jao-notmuch-hello-insert-mark-searches () - (jao-notmuch-hello--insert-searches jao-notmuch-mark-searches "marks") - (jao-notmuch-hello--insert-searches jao-notmuch-flagged-searches nil)) - - (defun jao-notmuch-hello-insert-feeds-searches () - (let ((sect "feeds")) - (dolist (s `(,jao-notmuch-feed-searches-news - ,jao-notmuch-feed-searches-hack - ,jao-notmuch-feed-searches-lang - ,jao-notmuch-feed-searches-sci)) - (let ((i (funcall #'jao-notmuch-hello--insert-searches s sect))) - (setq sect (unless i sect)))))) - - (defun jao-notmuch-hello-insert-emacs-searches () - (jao-notmuch-hello--insert-searches jao-notmuch-emacs-searches "emacs")) - - (defun jao-notmuch-hello-insert-dynamic-searches () - (jao-notmuch-hello--insert-searches jao-notmuch-dynamic-searches "dynamic") - (jao-notmuch-hello--insert-searches jao-notmuch-new-searches nil)) - - (defun jao-notmuch-refresh-agenda () - (interactive) - (save-window-excursion (org-agenda-list)) - (let ((b (current-buffer))) - (pop-to-buffer "*Calendar*") - (goto-char (point-min)) - (calendar-goto-today) - (pop-to-buffer b))) - - (defun jao-notmuch-hello-first () - (interactive) - (let ((inhibit-message t)) - (beginning-of-buffer) - (widget-forward 1))) - - (defun jao-notmuch-refresh-hello (&optional agenda) - (interactive "P") - (ignore-errors - (when (and (string= "Mail" (jao-afio-current-frame)) - (derived-mode-p 'notmuch-hello-mode)) - (when (not (string-blank-p jao-notmuch-minibuffer-string)) - (let ((notmuch-hello-auto-refresh nil)) (notmuch-hello))) - (when agenda (jao-notmuch-refresh-agenda)) - (unless (widget-at) (jao-notmuch-hello-first))))) - - (defvar jao-notmuch-hello--sec-rx "^\\(\\[ [0-9]+\\|All tags:.+\\)") - - (defun jao-notmuch-hello-next-section () - (interactive) - (when (re-search-forward jao-notmuch-hello--sec-rx nil t) - (widget-forward 1))) - - (defun jao-notmuch-hello-prev-section () - (interactive) - (beginning-of-line) - (unless (looking-at-p jao-notmuch-hello--sec-rx) - (re-search-backward jao-notmuch-hello--sec-rx nil t)) - (when (re-search-backward jao-notmuch-hello--sec-rx nil t) - (end-of-line) - (widget-forward 1))) - - (defun jao-notmuch-hello-next () - (interactive) - (if (widget-at) - (widget-button-press (point)) - (jao-notmuch-hello-next-section))) - - (use-package notmuch-hello - :init - (setq notmuch-column-control t - notmuch-hello-sections '(jao-notmuch-hello-insert-bigml-searches - jao-notmuch-hello-insert-inbox-searches - jao-notmuch-hello-insert-feeds-searches - jao-notmuch-hello-insert-emacs-searches - jao-notmuch-hello-insert-mark-searches - jao-notmuch-hello-insert-dynamic-searches - notmuch-hello-insert-alltags) - notmuch-hello-hide-tags nil - notmuch-hello-thousands-separator "," - notmuch-hello-auto-refresh t - notmuch-show-all-tags-list nil - notmuch-show-logo nil - notmuch-show-empty-saved-searches nil) - - :hook ((notmuch-hello-refresh . jao-notmuch-notify) - (jao-afio-switch . jao-notmuch-refresh-hello)) - - :bind (:map notmuch-hello-mode-map - (("a" . jao-notmuch-refresh-agenda) - ("j" . jao-notmuch-jump-search) - ("n" . jao-notmuch-hello-next) - ("p" . widget-backward) - ("S" . consult-notmuch) - ("g" . jao-notmuch-refresh-hello) - ("." . jao-notmuch-hello-first) - ("SPC" . widget-button-press) - ("[" . jao-notmuch-hello-prev-section) - ("]" . jao-notmuch-hello-next-section)))) - - #+end_src -* show - #+begin_src emacs-lisp - (defun jao-notmuch-open-enclosure (add) - (interactive "P") - (with-current-notmuch-show-message - (goto-char (point-min)) - (if (not (search-forward "Enclosure:" nil t)) - (user-error "No enclosure in message body") - (re-search-forward "https?://" nil t) - (if-let (url (thing-at-point-url-at-point)) - (progn - (message "%s %s ..." (if add "Adding" "Playing") url) - (unless add (jao-mpc-clear)) - (jao-mpc-add-url url) - (unless add (jao-mpc-play))) - (error "Found an enclosure, but not a link!"))))) - - (defconst jao-mail-clean-rx - (regexp-opt '("ElDiario.es - ElDiario.es: " "The Guardian: " - "The Conversation – Articles (UK): "))) - - (defun jao-mail-clean-address (args) - (when-let ((address (car args))) - (list (if (string-match ".+ updates on arXiv.org: \\(.+\\)" address) - (with-temp-buffer - (insert (match-string 1 address)) - (let ((shr-width 1000)) - (shr-render-region (point-min) (point-max))) - (replace-regexp-in-string "\"" "" (buffer-string))) - (replace-regexp-in-string jao-mail-clean-rx "" address))))) - - (use-package notmuch-show - :init - (setq gnus-blocked-images "." - notmuch-message-headers - '("To" "Cc" "Date" "Reply-To" "List-Id" "X-RSS-Feed") - notmuch-show-only-matching-messages t - notmuch-show-part-button-default-action 'notmuch-show-view-part - notmuch-wash-signature-lines-max 0 - notmuch-wash-wrap-lines-length 80 - notmuch-wash-citation-lines-prefix 10 - notmuch-wash-citation-lines-suffix 20 - notmuch-show-text/html-blocked-images "." - notmuch-show-header-line #'jao-notmuch-message-header-line) - - :config - - (advice-add 'notmuch-clean-address :filter-args #'jao-mail-clean-address) - - :bind - (:map notmuch-show-mode-map - (("h" . jao-notmuch-goto-tree-buffer) - ("TAB" . jao-notmuch-show-next-button) - ([backtab] . jao-notmuch-show-previous-button) - ("RET" . jao-notmuch-show-ret)))) - #+end_src -* search - #+begin_src emacs-lisp - (use-package notmuch-search - :init (setq notmuch-search-result-format - '(("date" . "%12s ") - ("count" . "%-7s ") - ("authors" . "%-35s") - ("subject" . " %-100s") - (jao-notmuch-format-tags . " (%s)")) - notmuch-search-buffer-name-format "*%s*" - notmuch-saved-search-buffer-name-format "*%s*") - :bind (:map notmuch-search-mode-map - (("RET" . notmuch-tree-from-search-thread) - ("M-RET" . notmuch-search-show-thread)))) - - #+end_src -* tree - #+begin_src emacs-lisp - (defun jao-notmuch-tree--forward (&optional prev) - (interactive) - (forward-line (if prev -1 1)) - (when prev (forward-char 2)) - (jao-notmuch-tree-scroll-or-next)) - - (defun jao-notmuch-tree--backward () - (interactive) - (jao-notmuch-tree--forward t)) - - (defun jao-notmuch--via-url () - (when (window-live-p notmuch-tree-message-window) - (with-selected-window notmuch-tree-message-window - (goto-char (point-min)) - (when (re-search-forward "^Via: http" nil t) - (thing-at-point-url-at-point))))) - - (defun jao-notmuch-browse-url (ext) - (interactive "P") - (when-let (url (or (jao-notmuch--via-url) - (car (last (jao-notmuch-message-urls))))) - (funcall (if ext browse-url-secondary-browser-function #'browse-url) - url))) - - (defun jao-notmuch-adjust-tree-fonts (&optional family) - (let ((fg (face-attribute 'jao-themes-dimm :foreground)) - (family (or family "Source Code Pro"))) - (dolist (f '(notmuch-tree-match-tree-face - notmuch-tree-no-match-tree-face)) - (if family - (set-face-attribute f nil :family family :foreground fg) - (set-face-attribute f nil :foreground fg))))) - - (use-package notmuch-tree - :init - (setq notmuch-tree-result-format - `(("date" . "%12s ") - (jao-notmuch-format-author . 25) - (jao-notmuch-msg-ticks . ,jao-mails-regexp) - (jao-notmuch-tree-and-subject . "%>-85s") - (jao-notmuch-format-tags . " (%s)")) - notmuch-unthreaded-result-format notmuch-search-result-format - consult-notmuch-result-format - `((jao-notmuch-msg-ticks . ,jao-mails-regexp) - ("date" . "%12s ") - ("authors" . "%-35s") - ("subject" . " %-100s") - (jao-notmuch-format-tags . " (%s)")) - ;; notmuch-tree-thread-symbols - ;; '((prefix . "─") (top . "─") (top-tee . "┬") - ;; (vertical . "│") (vertical-tee . "├") (bottom . "╰") - ;; (arrow . "")) - notmuch-tree-thread-symbols - '((prefix . " ") (top . " ") (top-tee . " ") - (vertical . " ") (vertical-tee . " ") (bottom . " ") - (arrow . ""))) - :config - - (jao-notmuch-adjust-tree-fonts - (when (string-prefix-p "Hack" jao-themes-default-face) "Source Code Pro")) - - (jao-notmuch-tree-setup "T") - - (defun jao-notmuch-before-tree (&rest args) - (when (string= (buffer-name) "*notmuch-hello*") - (split-window-right 40) - (other-window 1))) - - (defvar jao-notmuch--visits 0) - - (defun jao-notmuch-after-tree-quit (&optional both) - (when (and (not (derived-mode-p 'notmuch-tree-mode 'notmuch-hello-mode)) - (save-window-excursion (other-window -1) - (derived-mode-p 'notmuch-hello-mode))) - (delete-window) - (jao-notmuch-refresh-hello (= 0 (mod (cl-incf jao-notmuch--visits) 10))))) - - (advice-add 'notmuch-tree :before #'jao-notmuch-before-tree) - (advice-add 'notmuch-tree-quit :after #'jao-notmuch-after-tree-quit) - - :bind (:map notmuch-tree-mode-map - (("b" . jao-notmuch-browse-urls) - ("d" . jao-notmuch-tree-toggle-delete) - ("D" . jao-notmuch-tree-toggle-delete-thread) - ("h" . jao-notmuch-goto-message-buffer) - ("H" . jao-notmuch-click-message-buffer) - ("i" . jao-notmuch-toggle-images) - ("K" . jao-notmuch-tag-jump-and-next) - ("k" . jao-notmuch-tree-read-thread) - ("n" . jao-notmuch-tree-next) - ("N" . jao-notmuch-tree--forward) - ("O" . notmuch-tree-toggle-order) - ("o" . jao-notmuch-tree-widen-search) - ("p" . jao-notmuch-tree-previous) - ("P" . jao-notmuch-tree--backward) - ("r" . notmuch-tree-reply) - ("R" . notmuch-tree-reply-sender) - ("s" . jao-notmuch-tree-toggle-spam) - ("u" . jao-notmuch-tree-toggle-flag) - ("v" . notmuch-tree-scroll-message-window) - ("V" . notmuch-tree-scroll-message-window-back) - ("x" . jao-notmuch-arXiv-capture) - ("<" . jao-notmuch-tree-beginning-of-buffer) - (">" . jao-notmuch-tree-end-of-buffer) - ("\\" . notmuch-tree-view-raw-message) - ("." . jao-notmuch-toggle-mime-parts) - ("=" . jao-notmuch-tree-toggle-message) - ("RET" . jao-notmuch-tree-show-or-scroll) - ("SPC" . jao-notmuch-tree-scroll-or-next) - ("M-g" . jao-notmuch-browse-url) - ("M-u" . jao-notmuch-tree-reset-tags)))) - #+end_src -* org mode - Stolen and adapted from [[https://gist.github.com/fedxa/fac592424473f1b70ea489cc64e08911][Fedor Bezrukov]]. - #+begin_src emacs-lisp - (defvar jao-org-notmuch-last-subject nil) - (defun jao-org-notmuch-last-subject () jao-org-notmuch-last-subject) - - (defun jao-notmuch--add-tags (tags) - (if (derived-mode-p 'notmuch-show-mode) - (notmuch-show-add-tag tags) - (notmuch-tree-add-tag tags))) - - (defun org-notmuch-store-link () - "Store a link to a notmuch mail message." - (cl-case major-mode - ((notmuch-show-mode notmuch-tree-mode) - ;; Store link to the current message - (let* ((id (notmuch-show-get-message-id)) - (link (concat "notmuch:" id)) - (subj (notmuch-show-get-subject)) - (description (format "Mail: %s" subj))) - (setq jao-org-notmuch-last-subject subj) - (when (y-or-n-p "Archive message? ") - (jao-notmuch--add-tags '("+trove"))) - (when (y-or-n-p "Flag message as todo? ") - (jao-notmuch--add-tags '("+flagged"))) - (org-store-link-props - :type "notmuch" - :link link - :description description))) - (notmuch-search-mode - ;; Store link to the thread on the current line - (let* ((id (notmuch-search-find-thread-id)) - (link (concat "notmuch:" id)) - (subj (notmuch-search-find-subject)) - (description (format "Mail: %s" subj))) - (setq jao-org-notmuch-last-subject subj) - (org-store-link-props - :type "notmuch" - :link link - :description description))))) - - (with-eval-after-load "org" - (org-link-set-parameters "notmuch" - :follow 'notmuch-show - :store 'org-notmuch-store-link)) - #+end_src -* arXiv - #+begin_src emacs-lisp - (use-package org-capture - :config - (add-to-list 'org-capture-templates - '("X" "arXiv" entry (file "notes/physics/arxiv.org") - "* %(jao-org-notmuch-last-subject)\n %i" - :immediate-finish t) - t) - (org-capture-upgrade-templates org-capture-templates)) - - (defun jao-notmuch-arXiv-capture () - (interactive) - (save-window-excursion - (jao-notmuch-goto-message-buffer) - (save-excursion - (goto-char (point-min)) - (re-search-forward "\\[ text/html \\]") - (forward-paragraph) - (setq-local transient-mark-mode 'lambda) - (set-mark (point)) - (goto-char (point-max)) - (org-capture nil "X")))) - - #+end_src -* html render - #+begin_src emacs-lisp - (when jao-notmuch-enabled (setq mm-text-html-renderer 'shr)) - #+end_src -* consult - #+begin_src emacs-lisp - (jao-load-path "consult-notmuch") - (require 'consult-notmuch) - (consult-customize consult-notmuch :preview-key 'any) - - (defvar jao-consult-notmuch-history nil) - - (defvar jao-mailbox-folders '("bigml" "jao")) - - (defun jao-consult-notmuch-folder (&optional tree folder) - (interactive "P") - (let ((folder (if folder - (file-name-as-directory folder) - (completing-read "Group: " - jao-mailbox-folders - nil nil nil - jao-consult-notmuch-history - "."))) - (folder (replace-regexp-in-string "/\\(.\\)" ".\\1" folder)) - (init (read-string "Initial query: ")) - (init (format "folder:/%s/ %s" folder init))) - (if tree (consult-notmuch-tree init) (consult-notmuch init)))) - - (with-eval-after-load "notmuch-hello" - (define-key notmuch-hello-mode-map "f" #'jao-consult-notmuch-folder)) - #+end_src -* link hint - #+begin_src emacs-lisp - (with-eval-after-load "link-hint" - (defun jao-link-hint--notmuch-next-part (&optional bound) - (when-let (p (next-single-property-change (point) :notmuch-part nil bound)) - (and (< p (or bound (point-max))) p))) - - (defun jao-link-hint--notmuch-part-p () - (and (get-text-property (point) :notmuch-part) - (when-let (b (button-at (point))) (button-label b)))) - - (link-hint-define-type 'notmuch-part - :next #'jao-link-hint--notmuch-next-part - :at-point-p #'jao-link-hint--notmuch-part-p - :vars '(notmuch-show-mode) - :open #'push-button - :open-message "Toggled" - :open-multiple t) - - (push 'link-hint-notmuch-part link-hint-types)) - - #+end_src diff --git a/org.org b/org.org deleted file mode 100644 index bcce3e1..0000000 --- a/org.org +++ /dev/null @@ -1,316 +0,0 @@ -#+property: header-args :lexical t :tangle yes :comments yes :results silent :shebang ";; -*- lexical-binding: t" :tangle-mode (identity #o644) -#+title: Org (and related) mode configuration - -* General configuration - #+begin_src emacs-lisp - (use-package org - :ensure t - :custom ((org-export-backends '(ascii html latex texinfo))) - :init - (defalias 'jao-open-gnus-frame 'jao-afio--goto-mail) - - (setq org-adapt-indentation t - org-catch-invisible-edits 'smart - org-complete-tags-always-offer-all-agenda-tags t - org-cycle-separator-lines 0 ;; no blank lines when all colapsed - org-deadline-warning-days 14 - org-default-notes-file (expand-file-name "inbox.org" org-directory) - org-directory jao-org-dir - org-display-remote-inline-images 'download ;; 'skip 'cache - org-ellipsis " .." ;; ↴ - org-email-link-description-format "Email %c: %s" - org-enforce-todo-dependencies t - org-fast-tag-selection-single-key 'expert - ;; org-list-demote-modify-bullet '(("+" . "-") ("-" . "+") ("*" . "+")) - org-link-frame-setup - '((gnus . (lambda (&optional x) (jao-open-gnus-frame))) - (file . find-file-other-window)) - org-log-done nil - org-modules '(bibtex info eww eshell git-link) - org-odd-levels-only t - org-outline-path-complete-in-steps nil - org-refile-allow-creating-parent-nodes 'confirm - org-refile-targets '((nil :maxlevel . 5) - (org-agenda-files :maxlevel . 5)) - org-refile-use-outline-path 'file - org-return-follows-link t - org-reverse-note-order t - org-special-ctrl-a/e t - org-src-fontify-natively t - org-startup-folded t - org-tag-alist nil - org-tags-column -75 - org-todo-keywords - '((sequence "TODO(t)" "STARTED(s!)" "|" "DONE(d!)") - (sequence "REPLY(r)" "WAITING(w!)" "|" "DONE(d!)") - (sequence "TOREAD(T)" "READING(R!)" "|" "READ(a!)") - (sequence "|" "CANCELLED(x!)" "SOMEDAY(o!)" "DONE(d!)")) - org-use-fast-todo-selection t - org-use-speed-commands nil ;; t and then ? to see help - org-gnus-prefer-web-links nil)) - (require 'org) - #+end_src -* Agenda - #+begin_src emacs-lisp - (setq ;; org-agenda-custom-commands - ;; '(("w" todo "WAITING" nil) - ;; ("W" agenda "" ((org-agenda-ndays 21)))) - org-agenda-files (mapcar (lambda (f) - (expand-file-name f jao-org-dir)) - '("inbox.org" "bigml.org")) - org-agenda-block-separator " " - org-agenda-breadcrumbs-separator "•" - org-agenda-current-time-string "•" ;; "*" - org-agenda-time-grid - '((daily today require-timed) - (800 1000 1200 1400 1600 1800 2000) "" "·") - org-agenda-include-diary t - org-agenda-include-inactive-timestamps t - org-agenda-inhibit-startup nil - org-agenda-restore-windows-after-quit t - org-agenda-show-all-dates t - org-agenda-skip-deadline-if-done t - org-agenda-skip-scheduled-if-done nil - org-agenda-span 14 - org-agenda-start-on-weekday nil - org-agenda-window-setup 'current-window) - #+end_src -* Capture templates - #+BEGIN_SRC emacs-lisp - (setq org-capture-templates - '(("t" "TODO" entry - (file+headline "inbox.org" "Todo") - "* TODO %?\n %i%a" :prepend t) - ("r" "REPLY" entry - (file+headline "inbox.org" "Todo") - "* REPLY %:subject%?\n %t\n %i%a" :prepend t) - ("a" "Appointment" entry - (file+olp "inbox.org" "Appointments") - "* %^T %?\n %a" :time-prompt t) - ("i" "Inbox note" entry (file+headline "inbox.org" "Notes") - "* %a\n %i%?(added on: %u)" :prepend t))) - ;; (org-capture-upgrade-templates org-capture-templates) - #+END_SRC -* MIME and file apps - #+BEGIN_SRC emacs-lisp - (setq org-file-apps - '((system . mailcap) - (".*\\.djvu" . system) - (t . emacs))) - #+END_SRC -* Appearance - #+begin_src emacs-lisp - ;; Show hidden emphasis markers - (use-package org-appear - :ensure t - :hook (org-mode . org-appear-mode)) - - ;; #+caption: Image caption. - ;; #+attr_org: :width 100 - ;; [[file:path/to/image.png]] - - (setq org-startup-indented nil - org-pretty-entities nil - org-hide-emphasis-markers t - org-hide-leading-stars t - org-startup-with-inline-images t - org-image-actual-width '(300)) - - (use-package org-modern - :ensure t - :init (setq org-modern-label-border 1)) - - ;; (unless (display-graphic-p) (global-org-modern-mode 1)) - - #+end_src -* LaTeX - #+begin_src emacs-lisp - (use-package org-fragtog - :after org - :ensure t - :hook ((org-mode . org-fragtog-mode))) - - (require 'org-fragtog) - - (setq org-format-latex-options - `(:foreground default - :background - ,(if (jao-colors-scheme-dark-p) "black" "white") - :scale 1.25 - :html-foreground "black" - :html-background "Transparent" - :html-scale 1.0 - :matchers ("begin" "$1" "$" "$$" "\\(" "\\[")) - org-preview-latex-image-directory - (expand-file-name "~/.emacs.d/cache/ltximg/") - org-latex-hyperref-template nil - org-highlight-latex-and-related '(latex script entities)) - - (require 'ox-latex) - #+end_src - -* Export (minted) - - #+begin_src emacs-lisp - (setq org-latex-listings 'minted - org-latex-packages-alist '(("" "minted")) - org-latex-pdf-process - '("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f" - "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f")) - #+end_src - -* Babel and literate programming - - [[http://cachestocaches.com/2018/6/org-literate-programming][Literate Programming with Org-mode]] - - [[http://howardism.org/Technical/Emacs/literate-devops.html][Literate DevOps]] - - #+begin_src emacs-lisp - (setq org-src-window-setup 'other-window) ;; current-window - (require 'org-tempo nil t) ;;