diff options
Diffstat (limited to 'attic/elisp/misc.el')
-rw-r--r-- | attic/elisp/misc.el | 951 |
1 files changed, 951 insertions, 0 deletions
diff --git a/attic/elisp/misc.el b/attic/elisp/misc.el new file mode 100644 index 0000000..6484310 --- /dev/null +++ b/attic/elisp/misc.el @@ -0,0 +1,951 @@ +;; -*- lexical-binding: t; -*- + +;;; programming languages +;;;; Erlang +(use-package erlang + :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.*") + +;;;; 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)))) + +;;; smart scan +(use-package smartscan + :ensure t + :commands smartscan-mode + :init (add-hook 'prog-mode-hook #'smartscan-mode) + :diminish) + +;;; easy escape +(use-package easy-escape + :ensure t + :config + (set-face-attribute 'easy-escape-face nil :underline t) + (set-face-attribute 'easy-escape-delimiter-face nil :underline t) + :hook (emacs-lisp-mode . easy-escape-minor-mode) + :diminish (easy-escape-minor-mode . "^")) + +;;; vterm +(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 + + (defun jao-vterm-send-C-c () (interactive) (vterm-send-key "c" nil nil t)) + + (jao-define-attached-buffer "\\*vterm\\*" 0.5) + + :bind (:map vterm-mode-map ("C-c C-c" . jao-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)))) +;;; 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))) + +;;; switch window +(use-package switch-window + :ensure t + :custom ((switch-window-minibuffer-shortcut ?z) + (switch-window-background t) + (switch-window-shortcut-style 'qwerty) + (switch-window-shortcut-appearance 'text) + (switch-window-timeout 7) + (switch-window-threshold 2)) + :config + (defun jao-switch-window--then (prompt cmd) + (let ((f `(lambda () + (let ((default-directory ,default-directory)) + (call-interactively ',cmd))))) + (switch-window--then prompt f f))) + + (defun jao-switch-window-then-dired () + (interactive) + (jao-switch-window--then "Find directory" 'dired)) + + (defun jao-switch-window-then-find-file () + (interactive) + (jao-switch-window--then "Find file" 'find-file)) + + (defun jao-switch-window-then-consult-buffer () + (interactive) + (jao-switch-window--then "Switch to buffer" 'consult-buffer)) + + :bind (("s-o" . switch-window) + ("C-x 4 d" . jao-switch-window-then-dired) + ("C-x 4 f" . jao-switch-window-then-find-file) + ("C-x 4 b" . jao-switch-window-then-consult-buffer))) + +;;; git helpers +(use-package dired-git-info + :ensure t + :bind (:map dired-mode-map (")" . dired-git-info-mode))) + +(use-package gist :ensure t) + +;;; json +(use-package json-mode :ensure t) +(use-package json-navigator :ensure t) + +;;; deft +(use-package deft + :ensure t + :after jao-org-notes + :commands deft + :init (setq deft-extensions '("org" "md") + deft-directory jao-org-notes-dir + deft-use-filename-as-title nil + deft-use-filter-string-for-name t + deft-file-naming-rules '((noslash . "-") + (nospace . "-") + (case-fn . downcase)) + deft-org-mode-title-prefix t + deft-recursive t + deft-recursive-ignore-dir-regexp (regexp-opt '("." ".." "attic")) + deft-strip-summary-regexp + (concat "\\([\n\t]" + "\\|^#\\+\\(title\\|created\\|date\\|author\\):.*$" + "\\|^#\\+\\(file\\)?tags: *\\)")) + :config (setq deft-strip-title-regexp + (concat "\\(^#\\+title: *\\)\\|" deft-strip-title-regexp)) + :bind (("<f9>" . deft))) + +;;; detached +(use-package detached + :ensure t + :init + (detached-init) + :config + (transient-define-prefix jao-transient-detached () + ["Detached sessions" + ("v" "view session output" detached-view-session) + ("a" "attach to a session" detached-attach-session) + ("=" "diff a session with another session" detached-diff-session) + ("c" "open the session output in compilation mode" detached-compile-session) + ("r" "rerun a session" detached-rerun-session) + ("i" "insert the session's command at point" detached-insert-session-command) + ("w" "copy the session's shell command" detached-copy-session-command) + ("W" "copy the session's output" detached-copy-session) + ("k" "kill an active session" detached-kill-session)]) + + :bind (;; Replace `async-shell-command' with `detached-shell-command' + ([remap async-shell-command] . detached-shell-command) + ;; Replace `compile' with `detached-compile' + ([remap compile] . detached-compile) + ([remap recompile] . detached-compile-recompile) + ;; Replace built in completion of sessions with `consult' + ([remap detached-open-session] . detached-consult-session) + ("s-d" . jao-transient-detached)) + :custom ((detached-show-output-on-attach t) + (detached-terminal-data-command system-type))) + +(defun jao-detached-exec (command) + (if (fboundp 'detached-create-session) + (detached-create-session command) + (jao-shell-exec command))) + +;;; 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)))))) +;;; mu4e +(jao-load-path "mu4e") +(use-package mu4e + :init + (setq mu4e-attachment-dir (expand-file-name "~/var/download/attachments") + mu4e-change-filenames-when-moving nil + mu4e-completing-read-function 'completing-read + mu4e-display-update-status-in-modeline nil + mu4e-get-mail-command "true" ;; "run-mb.sh || [ $? -eq 1 ]" + mu4e-headers-show-threads t + mu4e-headers-sort-direction 'ascending + mu4e-headers-visible-columns 100 + mu4e-headers-visible-lines 12 + mu4e-hide-index-messages t + mu4e-index-cleanup t ;; don't do a full cleanup check + mu4e-index-lazy-check t ;; don't consider up-to-date dirs + mu4e-maildir "~/var/mail" + mu4e-split-view 'horizontal ;; 'vertical + mu4e-update-interval 300 + mu4e-use-fancy-chars nil + mu4e-user-mail-address-list jao-mails + mu4e-view-show-addresses t + mu4e-view-show-images t + mu4e-maildir-shortcuts '((:maildir "/jao/inbox" :key ?j) + (:maildir "/bigml/inbox" :key ?b)) + jao-mu4e-uninteresting-mail-query + (concat + "flag:unread AND NOT flag:trashed" + " AND NOT (maildir:/bigml/inbox OR maildir:/bigml/bugs OR" + " maildir:/bigml/support OR maildir:/jao/inbox)") + jao-mu4e-interesting-mail-query + (concat + "flag:unread AND NOT flag:trashed" + " AND (maildir:/bigml/inbox OR maildir:/bigml/bugs OR" + " maildir:/bigml/support OR maildir:/jao/inbox)") + mu4e-bookmarks + `((:name "Inbox" :query ,jao-mu4e-interesting-mail-query :key ?i) + (:name "Other messages" + :query ,jao-mu4e-uninteresting-mail-query + :key 117) + (:name "Today's messages" :query "date:today..now" + :key 116) + (:name "Last 7 days" :query "date:7d..now" :hide-unread t + :key 119) + (:name "Messages with PDFs" + :query "mime:application/pdf OR mime:x-application/pdf" + :key 112))) + + :config + (defun jao-mu4e--maildir (msg) + (when msg + (let ((md (mu4e-message-field msg :maildir))) + (when (string-match "/\\([^/]+\\)/.*" md) + (match-string 1 md))))) + + (defun jao-mu4e--refile-folder (name) + (lambda (msg) + (let ((md (jao-mu4e--maildir msg))) + (if (string= md name) + (concat "/jao/" name) + (format "/%s/%s" md name))))) + + (setq mu4e-sent-folder (jao-mu4e--refile-folder "sent")) + (setq mu4e-drafts-folder (jao-mu4e--refile-folder "drafts")) + (setq mu4e-trash-folder (jao-mu4e--refile-folder "trash")) + (setq mu4e-refile-folder (jao-mu4e--refile-folder "trove")) + + (setq mu4e-contexts nil) + + (setq mu4e-view-show-images t) + (when (fboundp 'imagemagick-register-types) + (imagemagick-register-types)) + + (define-key mu4e-view-mode-map [remap mu4e-view-verify-msg-popup] + 'epa-mail-verify) + + ;; View html message in browser (type aV) + (add-to-list 'mu4e-view-actions + '("ViewInBrowser" . mu4e-action-view-in-browser) t)) + +;;; twtxt +(use-package twtxt + :ensure t + :init (setq twtxt-file (expand-file-name "~/doc/jao.io/twtxt") + twtxt-following + '(("yarn" "https://twtxt.net/user/news/twtxt.txt")))) +;;; corfu bits +(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) + +;;; gnus bits + +(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-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")) + +;;;; startup and kill +;; 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: +(advice-add 'save-buffers-kill-emacs :before (lambda () + (run-hooks 'before-kill-emacs-hook))) + +(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)) + +(advice-remove 'ad-Advice-save-buffers-kill-emacs 'save-buffers-kill-emacs) + +;;;; delayed expiry +(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) +(define-key gnus-group-mode-map "\C-x\C-s" #'gnus-group-save-newsrc) + +(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)) + +;;;; remove HTML from From contents (arxiv with r2e) +(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) + +;;;; find message id +(defun jao-gnus-file-message-id (filename) + (with-temp-buffer + (insert-file filename) + (goto-char (point-min)) + (when (re-search-forward "^[Mm]essage-[Ii][Dd]: <?\\([^><]+\\)>?" nil t) + (match-string 1)))) +;;; old volume controls +(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)))) + +;;; corfu +(use-package corfu + :ensure 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-no-auto () (setq-local corfu-auto nil) (corfu-mode)) + + (add-hook 'eshell-mode-hook #'jao-corfu-no-auto) + + (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-<return>" . 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)) + (setq-local corfu-echo-documentation nil) + (corfu-mode 1))) + +(defun jao-corfu-maybe-enable () + (when (and (not jao-wayland-enabled) (display-graphic-p)) + (add-hook 'minibuffer-setup-hook #'corfu-in-minibuffer 1) + (global-corfu-mode 1))) + +(add-hook 'after-init-hook #'jao-corfu-maybe-enable) + +;;; 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 + + ("<tab>" . company-complete-common-or-cycle) + ("TAB" . company-complete-common-or-cycle) + + ("C-h" . company-show-doc-buffer) + ("M-." . company-show-location) + ("C-<return>" . company-complete-selection) + ([remap return] . company-abort) + ("RET" . company-abort) + + :filter (or (not (derived-mode-p 'eshell-mode)) + (company-explicit-action-p)) + ("<return>" . company-complete-selection) + ("RET" . company-complete-selection)) + :diminish) + +(unless (display-graphic-p) (global-company-mode 1)) + + +;;; 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)) + +;;; outline mode for notmuch tree view + +(defun jao-notmuch-tree--msg-prefix (msg) + (insert (propertize (if (plist-get msg :first) "> " " ") 'display " "))) + +(defun jao-notmuch-tree--mode-setup () + (setq-local outline-regexp "^> \\|^En") + (outline-minor-mode t)) + +(defun jao-notmuch-tree-hide-others (&optional and-show) + (interactive) + (outline-hide-body) + (outline-show-entry) + (when and-show (notmuch-tree-show-message nil))) + +(defsubst jao-notmuch-tree--message-open () + (and (buffer-live-p notmuch-tree-message-buffer) + (get-buffer-window notmuch-tree-message-buffer))) + +(defsubst jao-notmuch--get-prop (prop &optional props) + (or (and props (plist-get props prop)) + (notmuch-tree-get-prop prop) + (notmuch-show-get-prop prop))) + +(defun jao-notmuch--looking-at-match-p () + (and (jao-notmuch--get-prop :match) + (equal (jao-notmuch--get-prop :orig-tags) + (jao-notmuch--get-prop :tags)))) + +(defun jao-notmuch-tree--next (prev thread no-exit &optional ignore-new) + (let ((line-move-ignore-invisible nil)) + (cond ((and (not ignore-new) + (jao-notmuch--looking-at-match-p) + (not (jao-notmuch-tree--message-open)))) + (thread + (notmuch-tree-next-thread prev) + (unless (or (not (notmuch-tree-get-message-properties)) + (jao-notmuch--looking-at-match-p)) + (notmuch-tree-matching-message prev (not no-exit)))) + (t (notmuch-tree-matching-message prev (not no-exit))))) + (when (notmuch-tree-get-message-id) + (jao-notmuch-tree-hide-others t)) + (when prev (forward-char 2))) + +(defvar jao-notmuch-tree--prefix-map + (let ((m (make-keymap "Thread operations"))) + (define-key m (kbd "TAB") #'outline-cycle) + (define-key m (kbd "t") #'outline-toggle-children) + (define-key m (kbd "s") #'outline-show-entry) + (define-key m (kbd "S") #'outline-show-all) + (define-key m (kbd "h") #'outline-hide-entry) + (define-key m (kbd "H") #'outline-hide-body) + (define-key m (kbd "o") #'jao-notmuch-tree-hide-others) + (define-key m (kbd "n") #'outline-hide-other) + m)) + +(defun jao-notmuch-tree-outline-setup (&optional prefix) + (define-key notmuch-tree-mode-map (kbd (or prefix "T")) + jao-notmuch-tree--prefix-map) + (define-key notmuch-tree-mode-map (kbd "TAB") #'outline-cycle) + (define-key notmuch-tree-mode-map (kbd "M-TAB") #'outline-cycle-buffer) + (add-hook 'notmuch-tree-mode-hook #'jao-notmuch-tree--mode-setup) + (advice-add 'notmuch-tree-insert-msg :before #'jao-notmuch-tree--msg-prefix)) + +(defun jao-notmuch-tree-next (thread &optional no-exit) + "Next message or thread in forest, taking care of thread visibility." + (interactive "P") + (jao-notmuch-tree--next nil thread no-exit)) + +(defun jao-notmuch-tree-next-thread (&optional exit) + "Next thread in forest, taking care of thread visibility." + (interactive "P") + (jao-notmuch-tree--next nil t exit)) + +(defun jao-notmuch-tree-previous (thread) + "Previous message or thread in forest, taking care of thread visibility." + (interactive "P") + (jao-notmuch-tree--next t thread t)) + +(defun jao-notmuch-tree-previous-thread (&optional exit) + "Previous thread in forest, taking care of thread visibility." + (interactive "P") + (jao-notmuch-tree--next t t exit)) + + +;;; elpher/gemini +(use-package elpher :ensure t) +(defun jao-elpher--browse (url &rest _) (elpher-go url)) +(add-to-list 'browse-url-handlers + '("^\\(gemini\\|gopher\\)://.*" . jao-elpher--browse)) + +;;; fontsets +(defun jao--set-fontsets (_f) + (when (and (display-graphic-p) (fboundp 'set-fontset-font)) + (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) + +;;; 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) +;;; enwc +(use-package enwc + :ensure t + :custom ((enwc-default-backend 'nm) + (enwc-wired-device "wlp164s0") + (enwc-wireless-device "wlp164s0") + (enwc-display-mode-line nil))) + + +;;; tidal/mpc +(defconst jao-mpc--search-cmd + "-f '%%album%% - %%artist%% :::%%file%%' search %s '%s'|grep :::tidal:album") + +(defun jao-mpc--search-albums (query) + (let* ((cmd (format jao-mpc--search-cmd "any" query)) + (str (jao-mpc--cmd cmd)) + (res (split-string str "\n" t))) + (message "%s" str) + (mapcar (lambda (s) (split-string s ":::" t " ")) res))) + +(defun jao-mpc-select-tidal-album (&optional query port) + (interactive "sSearch terms: ") + (let* ((jao-mpc-port (or port jao-mpc-port)) + (resa (jao-mpc--search-albums query))) + (if (null resa) + (user-error "No results") + (when-let* ((a (completing-read "Play album: " resa nil t)) + (s (car (alist-get a resa nil nil 'string=)))) + (jao-mpc--add-and-play s port t))))) +;;; dogears +(use-package dogears + :ensure t + :enabled nil + :bind (:map global-map + ("M-g d" . dogears-go) + ("M-g M-b" . dogears-back) + ("M-g M-f" . dogears-forward) + ("M-g M-d" . dogears-list) + ("M-g M-D" . dogears-sidebar))) + +(dogears-mode) +;;; pulsar +(use-package pulsar + :ensure t + :demand t + :diminish + :custom ((pulsar-pulse t) + (pulsar-delay 0.1) + (pulsar-iterations 10) + (pulsar-face 'pulsar-yellow) + (pulsar-highlight-face 'jao-themes--hilite)) + :config + (dolist (f '(jao-prev-window + jao-tracking-next-buffer + smartscan-symbol-go-forward + smartscan-symbol-go-backward)) + (add-to-list 'pulsar-pulse-functions f)) + + :hook ((jao-afio-switch . pulsar-pulse-line) + (consult-after-jump . pulsar-reveal-entry) + (imenu-after-jump . pulsar-reveal-entry) + (next-error . pulsar-pulse-line))) + +(pulsar-global-mode 1) +;;;; mouse +(use-package disable-mouse + :ensure t + :diminish ((disable-mouse-global-mode . ""))) + +(global-disable-mouse-mode) +;;; tmr +(use-package tmr + :ensure t + :init + (setq tmr-sound-file "/usr/share/sounds/freedesktop/stereo/message.oga")) +;;; 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))))) + + :hook ((pdf-view-mode . jao-doc-session-mark)) + + :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)))) +;;; slack +(eval-and-compile + (defvar jao-slack-dir (expand-file-name "emacs-slack" jao-local-lisp-dir))) + +(use-package slack + :commands (slack-start) + :vc t + :load-path jao-slack-dir + :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 'buffer ;; 'never, 'buffer, 'frame + slack-thread-also-send-to-room t + 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))) + :hook ((slack-file-info-buffer-mode . view-mode)) + :config + + (defun my-slack-nobreak-mrkdwn () + "Return non-nil (don't break line) if point is in markdown code face." + (seq-find (lambda (ov) + (eq 'slack-mrkdwn-code-block-face (overlay-get ov 'face))) + (overlays-at (point)))) + (add-hook 'slack-message-buffer-mode-hook + (lambda () + (add-hook 'fill-nobreak-predicate #'my-slack-nobreak-mrkdwn + nil 'local))) + + (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-tracking-cleaner "logstash-\\([^-]+\\)-\\(.+\\)" "\\2-\\1") + (jao-tracking-cleaner + "^\\*Slack - .*? : \\(MPIM: \\)?\\([^ ]+\\)\\( \\(T\\)\\)?.*" "\\2\\4") + (jao-define-attached-buffer "\\*Slack .+ Edit Message [0-9].+" 20)) + +;;; snippets +(defun jao-org-notes-open-tags () + "Search for a note file, matching all tags with completion." + (let* ((tags (jao-org-notes--read-tags)) + (fn (lambda () + (prog1 (jao-org-notes--find-tag (car tags)) + (setq tags (cdr tags))))) + (res (funcall fn))) + (while (and res tags) (setq res (seq-intersection res (funcall fn)))) + (unless res (user-error "No notes found")) + (when-let (f (completing-read "Select file: " (mapcar #'car res))) + (find-file (cadr (assoc f res)))))) + +(defun jao-sway-run-or-focus-tidal () + (interactive) + (if (jao-shell-running-p "tidal-hifi") + (jao-swaymsg "[app_id=tidal-hifi] scratchpad show") + (let ((c + "tidal-hifi --enable-features=UseOzonePlatform --ozone-platform=wayland &")) + (start-process-shell-command "tidal-hifi" nil c)) + (jao-sway-run-or-focus-tidal))) + +;; + +(defun jao-afio--set-mode-line () + (when (and window-system (fboundp 'jao-mode-line-hide-inactive)) + (if (string= "docs" (jao-afio-frame-name)) + (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)) + +;; + +(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)))) + +;; + +(defun jao-notmuch-format-author (width msg) + (let* ((headers (plist-get msg :headers)) + (auth (notmuch-tree-clean-address (plist-get headers :From))) + (awidth (string-width auth)) + (auth (if (> awidth width) + (substring auth 0 width) + (concat auth (make-string (- width awidth) 32)))) + (face (if (plist-get msg :match) + 'notmuch-tree-match-author-face + 'notmuch-tree-no-match-author-face))) + (propertize auth 'face face))) |