summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--blog.org224
-rw-r--r--consult.org353
-rw-r--r--counsel.org228
-rw-r--r--data/clock-world-icon.pngbin0 -> 9901 bytes
-rw-r--r--data/eshell.alias21
-rw-r--r--data/music-player-icon.pngbin0 -> 6469 bytes
-rw-r--r--data/slack.svg15
-rw-r--r--exwm.org506
-rw-r--r--gnus.org660
-rw-r--r--init.org4393
-rw-r--r--org.org326
-rw-r--r--readme.org68
13 files changed, 6796 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index e848970..1a131a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,5 @@
/lib/media/espotify-embark.el
/lib/media/espotify.el
/site
+/gnus.el
+/init.el
diff --git a/blog.org b/blog.org
new file mode 100644
index 0000000..831a6ff
--- /dev/null
+++ b/blog.org
@@ -0,0 +1,224 @@
+#+PROPERTY: header-args :tangle no :comments yes :results silent
+
+* 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-tags
+ (mapcar (lambda (f)
+ (string-match "tag-\\(.+\\)\\.html" f)
+ (format "<a href=\"/blog/%s\">%s</a>"
+ f (match-string 1 f)))
+ (directory-files (jao-blog-dir "blog") nil "tag-.*"))
+ jao-org-blog-tag-names
+ (mapcar (lambda (f)
+ (string-match "tag-\\(.+\\)\\.html" f)
+ (match-string 1 f))
+ (directory-files (jao-blog-dir "blog") nil "tag-.*")))
+ #+end_src
+* HTML headers and footers
+*** Header
+ #+begin_src emacs-lisp
+ (setq org-static-blog-page-header
+ (concat
+ "<meta name=\"author\" content=\"jao\">\n"
+ "<meta name=\"referrer\" content=\"no-referrer\">\n"
+ "<link rel=\"stylesheet\" href=\"/static/style.css\""
+ " type=\"text/css\">\n"
+ "<link rel=\"apple-touch-icon\" sizes=\"180x180\""
+ " href=\"/static/apple-touch-icon.png\" >\n"
+ "<link rel=\"icon\" type=\"image/png\""
+ " sizes=\"32x32\" href=\"/static/favicon-32x32.png\">\n"
+ "<link rel=\"icon\" type=\"image/png\""
+ " sizes=\"16x16\" href=\"/static/favicon-16x16.png\">\n"
+ "<link rel=\"icon\" href=\"/static/favicon.ico\">\n"
+ "<link rel=\"manifest\" href=\"/static/site.webmanifest\">\n")
+
+ org-static-blog-page-preamble
+ (concat
+ "<div class=\"header\">"
+ " <a href=\"https://jao.io\">programming (and other) musings</a>"
+ " <div class=\"sitelinks\">"
+ " <a href=\"/blog/about.html\">about</a>"
+ " | <a href=\"/blog/hacking.html\">hacking</a>"
+ " | <a href=\"/blog/archive.html\">archive</a>"
+ " | <div class=\"dropdown\">"
+ " <a href=\"/blog/tags.html\" class=\"dropbtn\">tags</a>"
+ " <div class=\"dropdown-content\">"
+ (mapconcat #'identity jao-org-blog-tags "")
+ " </div>"
+ " </div>"
+ " | <a href=\"/blog/rss.xml\">rss</a>"
+ " </div>"
+ "</div>"))
+ #+end_src
+*** Footer
+ #+begin_src html :tangle ~/.emacs.d/commons.html :comments no
+ <center>
+ <a rel="license" href="https://creativecommons.org/licenses/by-sa/3.0/">
+ <img alt="Creative Commons License" style="border-width:0"
+ src="https://i.creativecommons.org/l/by-sa/3.0/88x31.png" />
+ </a>
+ <br />
+ <span xmlns:dct="https://purl.org/dc/terms/"
+ href="https://purl.org/dc/dcmitype/Text" property="dct:title"
+ rel="dct:type">jao.io</span> by
+ <a xmlns:cc="https://creativecommons.org/ns#" href="https://jao.io"
+ property="cc:attributionName" rel="cc:attributionURL">jao</a>
+ is licensed under a
+ <a rel="license" href="https://creativecommons.org/licenses/by-sa/3.0/">
+ Creative Commons Attribution-ShareAlike 3.0 Unported License</a>.
+ </center>
+ #+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 nil
+ org-static-blog-preview-link-p t
+ org-static-blog-index-length 10
+ 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 "drafts/")
+ org-static-blog-publish-directory (jao-blog-dir "blog/")
+ org-static-blog-rss-extra "<author>mail@jao.io</author>\n"
+ org-static-blog-rss-max-entries 30
+ 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/.*" 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 (("H-s-b" . jao-hydra-org-blog/body)
+ :map org-mode-map (("C-c B" . jao-hydra-org-blog/body))))
+ #+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)))
+
+ (defun jao-org-static-blog-next-sundry ()
+ (require 'rst)
+ (let* ((nos (mapcar (lambda (d)
+ (string-match ".*-\\([ixvldc]+\\)\\.org" d)
+ (rst-roman-to-arabic (match-string 1 d)))
+ (directory-files org-static-blog-posts-directory
+ nil
+ "in-no-particular-order")))
+ (n (rst-arabic-to-roman (+ 1 (car (sort nos #'>))))))
+ (format "in no particular order %s" (downcase n))))
+
+ (defun jao-org-static-blog-create-new-post (&optional draft)
+ (interactive)
+ (let* ((kind (completing-read "Kind: " '(regular book sundry)))
+ (title (if (string= "sundry" kind)
+ (jao-org-static-blog-next-sundry)
+ (read-string "Title: ")))
+ (file (replace-regexp-in-string "\s" "-" (downcase title))))
+ (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: ")
+ (cond ((string= "book" kind)
+ (insert "books\n\n[[https://jao.io/img/" file ".jpg]]\n\n"))
+ ((string= "sundry" kind)
+ (insert "sundry\n\nInteresting bits elsewhere:\n\n- "))
+ (t (insert (completing-read "Tag: " jao-org-blog-tag-names)
+ "\n\n")))))
+ #+end_src
+*** Drafts
+ #+begin_src emacs-lisp
+ (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)
+ (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)))
+ (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
+* Hydras
+ #+begin_src emacs-lisp
+ (pretty-hydra-define jao-hydra-org-blog (:color blue :quit-key "q")
+ ("Edit"
+ (("n" jao-org-static-blog-create-new-post "create post")
+ ("d" jao-org-static-blog-create-new-draft "create draft")
+ ("e" jao-org-static-blog-edit-draft "edit draft"))
+ "Publish"
+ (("D" jao-org-static-blog-publish-draft "publish draft")
+ ("f" jao-org-blog-publish-file "publish single file")
+ ("p" jao-org-blog-publish "publish all")
+ ("r" jao-org-blog-republish "republish"))))
+
+ (major-mode-hydra-define+ org-mode nil
+ ("Utilities" (("b" jao-hydra-org-blog/body "Blog ops"))))
+ #+end_src
diff --git a/consult.org b/consult.org
new file mode 100644
index 0000000..c4bcc88
--- /dev/null
+++ b/consult.org
@@ -0,0 +1,353 @@
+#+title: Completion configuration using selectrum, consult and friends
+
+* completion styles
+ #+begin_src emacs-lisp
+ (setq completion-styles '(substring flex initials))
+ ;; '(basic partial-completion emacs22)
+ #+end_src
+* prescient
+ #+begin_src emacs-lisp
+ (use-package prescient :ensure t)
+ #+end_src
+* selectrum
+ #+begin_src emacs-lisp
+ (use-package selectrum
+ :ensure t
+ :custom ((selectrum-extend-current-candidate-highlight nil)
+ (selectrum-fix-minibuffer-height nil)
+ (selectrum-num-candidates-displayed 15)
+ (selectrum-show-indices nil)
+ (selectrum-count-style 'current/matches)) ;; 'matches
+ :bind (("C-R" . selectrum-repeat)))
+
+ (use-package selectrum-prescient :ensure t)
+ #+end_src
+* marginalia
+ #+begin_src emacs-lisp
+ (use-package marginalia
+ :ensure t
+ :bind (:map minibuffer-local-map ("C-M-a" . marginalia-cycle))
+
+ :custom (marginalia-annotators
+ '(marginalia-annotators-heavy marginalia-annotators-light nil))
+ :config
+ (with-eval-after-load "selectrum"
+ (advice-add #'marginalia-cycle
+ :after
+ (lambda ()
+ (when (bound-and-true-p selectrum-mode)
+ (selectrum-exhibit))))))
+ #+end_src
+* consult
+ #+begin_src emacs-lisp
+ (use-package consult
+ :ensure t
+ :bind (("C-x M-:" . consult-complex-command)
+ ("C-x b" . consult-buffer)
+ ("C-x 4 b" . consult-buffer-other-window)
+ ("C-x 5 b" . consult-buffer-other-frame)
+ ("C-c b" . consult-bookmark)
+ ("C-c B" . bookmark-set)
+ ("C-c h" . consult-history)
+ ("C-c i" . consult-imenu)
+ ("C-c k" . consult-ripgrep)
+ ("C-c K" . consult-git-grep)
+ ("C-c l" . consult-locate)
+ ("C-c m" . consult-mode-command)
+ ("C-c s" . consult-line)
+ ("C-x r x" . consult-register)
+ ("C-x r b" . consult-bookmark)
+ ("M-g b" . consult-bookmark)
+ ("M-g g" . consult-goto-line)
+ ("M-g M-g" . consult-goto-line)
+ ("M-g o" . consult-outline)
+ ("M-g s" . consult-line)
+ ("M-g m" . consult-man)
+ ("M-g M" . consult-mark)
+ ("M-g K" . consult-git-grep)
+ ("M-g k" . consult-ripgrep)
+ ("M-g i" . consult-imenu)
+ ("M-g I" . consult-project-imenu)
+ ("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)
+ ("<help> a" . consult-apropos))
+
+ :custom ((completion-in-region-function #'consult-completion-in-region)
+ (consult-locate-command '("locate" "--ignore-case" "--regexp"))
+ (consult-preview-key (kbd "`"))
+ (consult-config '((consult-mark :preview-key any)))
+ (consult-narrow-key (kbd "<"))
+ (consult-widen-key (kbd ">")))
+
+ :init
+ (fset 'multi-occur #'consult-multi-occur)
+
+ :config
+ (defun jao-consult-project-root ()
+ (expand-file-name (or (jao-compilation-root) (vc-root-dir) "")))
+
+ (setq consult-project-root-function #'jao-consult-project-root)
+
+ (define-key consult-narrow-map (vconcat consult-narrow-key "?")
+ #'consult-narrow-help))
+ #+end_src
+* consultors
+*** 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 "Go to hunk: " candidates
+ :category 'consult--encode-location
+ :sort nil
+ :require-match t
+ :lookup #'consult--line-match
+ :preview (consult--preview-position)))))
+
+ (with-eval-after-load "consult"
+ (add-to-list 'consult-config '(jao-consult-hunks :preview-key any)))
+ #+end_src
+* embark
+*** packages
+ #+begin_src emacs-lisp
+ (use-package embark
+ :ensure t
+ :custom ((embark-quit-after-action nil))
+ :bind (("C-;" . embark-act)
+ (:map minibuffer-local-map
+ (("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)))
+ (require 'embark)
+ #+end_src
+*** embark action indicator
+ #+begin_src emacs-lisp
+ (defvar jao-embark--actions-buffer "*Embark Actions*")
+
+ (defvar jao-embark--default-display
+ `((,(regexp-quote jao-embark--actions-buffer)
+ (display-buffer-at-bottom)
+ (window-parameters (mode-line-format . none))
+ (window-height . fit-window-to-buffer))))
+
+ (setq jao-embark--excluded
+ '(embark-collect-snapshot embark-collect-live embark-export
+ embark-become embark-isearch))
+
+ (defun jao-embark--bind-desc (descs x)
+ (let ((k (car x)) (c (cdr x)))
+ (cond ((memq c jao-embark--excluded) descs)
+ ((symbolp c)
+ (let* ((desc (if (numberp k)
+ (single-key-description k)
+ (key-description k)))
+ (desc (format "%s" desc))
+ (doc (car (split-string
+ (or (ignore-errors (documentation c)) "")
+ "\n")))
+ (fun (symbol-name c)))
+ (cons (max (length desc) (car descs))
+ (cons (max (length fun) (cadr descs))
+ (cons (list desc fun doc) (cddr descs))))))
+ (t descs))))
+
+ (defun jao-embark--keymap-descs (k)
+ (seq-reduce #'jao-embark--bind-desc (cdr (keymap-canonicalize k)) '(0 0)))
+
+ (defun jao-embark--dstr (d)
+ (let ((s (cadr d))) (if (string-prefix-p "embark" s) "" s)))
+
+ (defun jao-embark--show-keymap (keymap &optional target)
+ (with-current-buffer (get-buffer-create jao-embark--actions-buffer)
+ (read-only-mode -1)
+ (setq-local cursor-type nil)
+ (delete-region (point-min) (point-max))
+ (let* ((descs (jao-embark--keymap-descs keymap))
+ (fmt (format "%%-%ds %%-%ds %%s\n" (cadr descs) (car descs))))
+ (seq-each (lambda (desc)
+ (insert (format fmt
+ (propertize (cadr desc) 'face 'jao-themes-f00)
+ (propertize (car desc) 'face 'embark-keybinding)
+ (propertize (caddr desc) 'face 'italic))))
+ (seq-sort-by 'jao-embark--dstr 'string-greaterp (cddr descs))))
+ (if target (insert (format "Action for '%s'" target)) (delete-char -1))
+ (read-only-mode 1)
+ (let ((display-buffer-alist
+ (append display-buffer-alist jao-embark--default-display)))
+ (pop-to-buffer (current-buffer) nil t))
+ (lambda () (embark-kill-buffer-and-window jao-embark--actions-buffer))))
+
+ (setq embark-action-indicator #'jao-embark--show-keymap
+ embark-become-indicator embark-action-indicator)
+
+ #+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-any-re)
+ (let ((lnk (match-string-no-properties 2)))
+ (if (string-match-p "https?://.+" 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))
+
+ (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
+*** w3m 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 (w3m-anchor) w3m-current-url)))
+ (when (string-match-p "^https?.*" url)
+ (cons 'url url)))))
+
+ (add-to-list 'embark-target-finders #'jao-embark-targets--w3m-anchor)
+ (define-key embark-url-map (kbd "f") #'browse-url-firefox)
+ #+end_src
+*** video url targets
+ #+begin_src emacs-lisp
+ (defvar jao-embark-targets-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-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))))
+ (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)
+ ("m" 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))
+ #+end_src
+*** embark as selectrum lite
+ #+begin_src emacs-lisp
+ (defun jao-embark--shrink-selectrum ()
+ (when (eq embark-collect--kind :live)
+ (with-selected-window (active-minibuffer-window)
+ (when (bound-and-true-p selectrum-active-p)
+ (setq-local selectrum-num-candidates-displayed 1)))))
+
+ (add-to-list 'display-buffer-alist
+ '("\\`\\*Embark Collect.*\\*"
+ (display-buffer-below-selected)
+ (window-height . 0.25)
+ (window-parameters (mode-line-format . none))))
+
+ (defun jao-embark-select-first-completion ()
+ (interactive)
+ (embark-switch-to-collect-completions)
+ (push-button))
+
+ (defun jao-embark-prescient-candidates ()
+ (prescient-sort (cdr (embark-minibuffer-candidates))))
+
+ (defun jao-remember-candidate-on-exit ()
+ (prescient-remember (minibuffer-contents)))
+
+ (defun jao-embark-remember-target (_action target &optional _exit)
+ (prescient-remember target))
+
+ (defun jao-embark-compui-enable ()
+ (interactive)
+ (setq completion-styles '(flex initials substring))
+
+ (when (bound-and-true-p selectrum-mode)
+ (selectrum-mode -1))
+ (push #'jao-embark-prescient-candidates
+ embark-candidate-collectors)
+ (defalias 'switch-to-completions
+ 'embark-switch-to-collect-completions)
+ (add-hook 'minibuffer-exit-hook
+ #'jao-remember-candidate-on-exit)
+ (advice-add 'embark--act :before #'jao-embark-remember-target)
+ (define-key minibuffer-local-map (kbd "C-SPC")
+ #'embark-collect-completions)
+ (define-key minibuffer-local-map (kbd "M-SPC")
+ #'embark-collect-completions)
+ (define-key minibuffer-local-map (kbd "C-n")
+ #'embark-switch-to-collect-completions)
+ (define-key minibuffer-local-map (kbd "RET")
+ #'jao-embark-select-first-completion))
+
+ (defun jao-embark-compui-disable ()
+ (interactive)
+ (setq completion-styles '(basic partial-completion emacs22))
+ (remove-hook 'minibuffer-exit-hook
+ #'jao-remember-candidate-on-exit)
+ (advice-remove 'embark--act #'jao-embark-remember-target)
+ (when (boundp selectrum-mode)
+ (define-key minibuffer-local-map (kbd "C-n")
+ 'selectrum-next-candidate)
+ (selectrum-mode 1)))
+ #+end_src
+* startup
+ #+begin_src emacs-lisp
+ (selectrum-mode 1)
+ (selectrum-prescient-mode 1)
+ (prescient-persist-mode 1)
+ (marginalia-mode 1)
+ #+end_src
diff --git a/counsel.org b/counsel.org
new file mode 100644
index 0000000..07035d6
--- /dev/null
+++ b/counsel.org
@@ -0,0 +1,228 @@
+#+title: Completion configuration using ivy, counsel and friends
+
+* 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
+*** recoll
+ #+BEGIN_SRC emacs-lisp
+ (use-package jao-recoll)
+ (use-package jao-counsel-recoll
+ :after counsel
+ :bind (("C-c R" . jao-counsel-recoll)))
+ #+END_SRC
+*** 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
+* 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
+* dap
+ #+begin_src emacs-lisp
+ (jao-load-path "dap")
+ (use-package dap
+ :demand t
+ :bind (("C-'" . dap-dap)))
+ #+end_src
+*** prompter
+ #+begin_src emacs-lisp
+ (defun jao-dap--hide-help ()
+ (when-let ((w (get-buffer-window (help-buffer))))
+ (with-selected-window w (kill-buffer-and-window))))
+
+ (defun jao-dap--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-dap--prompter-done ()
+ (save-current-buffer (jao-dap--hide-help)))
+
+ (setq dap-prompter #'jao-dap--prompter)
+ (setq dap-prompter-done #'jao-dap--prompter-done)
+ #+end_src
+*** minibuffer actions
+ #+begin_src emacs-lisp
+ (defun jao-dap--completion-metadata ()
+ (completion-metadata
+ (buffer-substring-no-properties (field-beginning) (point))
+ minibuffer-completion-table
+ minibuffer-completion-predicate))
+
+ (defun jao-dap--completion-category ()
+ (completion-metadata-get (jao-dap--completion-metadata) 'category))
+
+ (dap-define-keymap jao-dap-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))
+
+ (dap-define-keymap espotify-item-keymap
+ "Actions for Spotify search results"
+ ("a" espotify--play-album)
+ ("h" espotify--show-info))
+
+ (defvar jao-dap--smaps
+ '((command . dap-command-map)
+ (espotify-search-item . espotify-item-keymap)
+ (function . dap-function-map)
+ (variable . dap-variable-map)
+ (face . dap-face-map)
+ (buffer . jao-dap-buffer-map)
+ (consult-buffer . jao-dap-buffer-map)))
+
+ (defun jao-dap-target-minibuffer-candidate ()
+ (when (minibuffer-window-active-p (selected-window))
+ (let ((cand (ivy-state-current ivy-last))
+ (cat (jao-dap--completion-category)))
+ (when-let (m (alist-get cat jao-dap--smaps))
+ (cons m cand)))))
+
+ (add-to-list 'dap-targets #'jao-dap-target-minibuffer-candidate)
+ #+end_src
+*** url / video actions
+ #+begin_src emacs-lisp
+ (defvar jao-dap-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-dap--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-dap-mpv (&optional url)
+ "Play video stream with mpv"
+ (interactive "sURL: ")
+ (jao-dap--play-video "mpv" url))
+
+ (defun jao-dap-vlc (&optional url)
+ "Play video stream with vlc"
+ (interactive "sURL: ")
+ (jao-dap--play-video "vlc" url))
+
+ (defun jao-dap-target-w3m-url ()
+ (when-let (url (or (thing-at-point-url-at-point)
+ (w3m-anchor)
+ w3m-current-url))
+ (cons 'dap-url-map url)))
+
+ (defun jao-dap-kill (&optional x)
+ "Save to kill ring"
+ (interactive "s")
+ (kill-new x))
+
+ (define-key dap-url-map "f" #'browse-url-firefox)
+ (define-key dap-url-map "w" #'jao-dap-kill)
+ (define-key dap-url-map [return] #'browse-url)
+
+ (defun jao-dap-target-video-url ()
+ (when-let (url (jao-dap-target-w3m-url))
+ (when (string-match-p jao-dap-video-url-rx (cdr url))
+ (cons 'jao-dap-video-url-map (cdr url)))))
+
+ (dap-define-keymap jao-dap-video-url-map
+ "Actions on URLs pointing to remote video streams."
+ ("v" jao-dap-vlc)
+ ("m" jao-dap-mpv))
+
+ (add-to-list 'dap-targets #'jao-dap-target-w3m-url)
+ (add-to-list 'dap-targets #'jao-dap-target-video-url)
+ #+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/data/clock-world-icon.png b/data/clock-world-icon.png
new file mode 100644
index 0000000..990d5ff
--- /dev/null
+++ b/data/clock-world-icon.png
Binary files differ
diff --git a/data/eshell.alias b/data/eshell.alias
new file mode 100644
index 0000000..e03507f
--- /dev/null
+++ b/data/eshell.alias
@@ -0,0 +1,21 @@
+alias df dfrs
+alias dir ls -Lla|grep ^d
+alias more vo $1
+alias vo view-file-other-window $1
+alias vf view-file-other-frame $1
+alias v view-file $1
+alias clear recenter 0
+alias la ls -a $*
+alias ll ls -l $*
+alias e find-file $1
+alias eo find-file-other-window $1
+alias ef find-file-other-frame $1
+alias lp2 lp -o number-up=2 $*
+alias lp4 lp -o number-up=4 $*
+alias lp22 lp -o number-up=2 -o sides=two-sided-short-edge $*
+alias lp42 lp -o number-up=4 -o sides=two-sided-long-edge $*
+alias ... cd ../..
+alias .. cd ..
+alias ls ls -F $*
+alias up eshell-up $1
+alias pk eshell-up-peek $1
diff --git a/data/music-player-icon.png b/data/music-player-icon.png
new file mode 100644
index 0000000..16dc7b3
--- /dev/null
+++ b/data/music-player-icon.png
Binary files differ
diff --git a/data/slack.svg b/data/slack.svg
new file mode 100644
index 0000000..f3efcb6
--- /dev/null
+++ b/data/slack.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" version="1.1" viewBox="0 0 64 64">
+ <path style="opacity:0.2" d="m 4.0000404,56.000021 0,1.999992 C 4.0000404,59.662006 5.3380346,61 7.0000275,61 l 49.9997855,0 c 1.661992,0 2.999987,-1.337994 2.999987,-2.999987 l 0,-1.999992 c 0,1.661993 -1.337995,2.999988 -2.999987,2.999988 l -49.9997855,0 c -1.6619929,0 -2.9999871,-1.337995 -2.9999871,-2.999988 z"/>
+ <rect style="opacity:0.2" width="56" height="56" x="-60" y="-60" rx="4.2" ry="4.2" transform="matrix(0,-1,-1,0,0,0)"/>
+ <rect style="fill:#f4f1ef;stroke:#f4f1ef;stroke-width:2.79998779" width="30.182" height="30.182" x="7.26" y="23.08" transform="matrix(0.96126169,-0.27563736,0.27563736,0.96126169,0,0)"/>
+ <path style="fill:#48c39e" d="m 5.6980331,19.800139 c -0.5790975,-8e-4 -1.1385351,0.028 -1.6980327,0.036 l 0,35.977846 c 0,0.310798 0.03766,0.610997 0.1012196,0.902396 6.200573,-0.552998 12.254747,-1.911992 17.551924,-7.434768 L 13.33258,20.267737 C 10.624192,19.961539 8.0790029,19.804139 5.6982131,19.800139 Z"/>
+ <path style="fill:#d81768" d="m 50.66784,40.800049 -29.013876,8.320764 c -0.924076,3.779984 -0.947575,7.332969 -0.349998,10.732354 l 34.495852,0 c 0.845836,0 1.628173,-0.254799 2.28599,-0.680997 C 57.864109,52.485199 57.265151,45.893227 50.66744,40.800049 Z"/>
+ <path style="fill:#ecb524" d="M 58.315807,4.8002039 C 51.778835,5.4262012 45.728461,6.7449956 42.347076,11.879373 L 50.66764,40.893249 C 53.982825,41.577646 57.070212,41.638046 60,41.262647 L 60,8.1467895 C 60,6.7697955 59.333502,5.5650006 58.315647,4.8000039 Z"/>
+ <path style="fill:#81d2e0" d="m 8.2000223,4.0002074 c -0.8401163,0 -1.617213,0.2519989 -2.2721902,0.6725971 C 7.3056062,10.054381 9.8524152,15.233759 13.3324,20.253337 L 42.346276,11.932573 C 43.51529,8.9853859 43.847829,6.3875971 43.67245,4.0002074 l -35.4738477,0 z"/>
+ <path style="fill:#390f39" d="m 21.653964,49.200013 -17.6531236,5.061178 0,1.471194 c 0,2.32679 1.8731919,4.199982 4.1999819,4.199982 l 16.5319287,0 -3.078787,-10.732354 z"/>
+ <path style="fill:#20a48e" d="m 8.2000223,4.0002074 c -2.32679,0 -4.1999819,1.8731919 -4.1999819,4.1999819 l 0,14.7299367 L 13.3324,20.253337 8.6730203,4.0002074 l -0.473038,0 z"/>
+ <path style="fill:#77971f" d="M 40.071885,4.0002074 42.346876,11.932573 59.786601,6.9343947 C 59.249303,5.2344021 57.68181,4.0002074 55.799818,4.0002074 l -15.728133,0 z"/>
+ <path style="fill:#d5100d" d="m 59.9998,38.20006 -9.33236,2.676789 5.452376,19.020318 C 58.293007,59.731168 59.9998,57.946975 59.9998,55.729985 l 0,-17.529925 z"/>
+ <path style="opacity:0.1;fill:#ffffff" d="M 7 4 C 5.3380071 4 4 5.3380071 4 7 L 4 8 C 4 6.3380071 5.3380071 5 7 5 L 57 5 C 58.661992 5 60 6.3380071 60 8 L 60 7 C 60 5.3380071 58.661992 4 57 4 L 7 4 z"/>
+ <path style="fill:#5a3856" d="m 38.96589,36.00007 c -2e-5,1.923391 -0.633397,3.398185 -1.900132,4.424381 -1.257514,1.026395 -3.115986,1.539593 -5.575576,1.539593 -1.701332,0 -3.180786,-0.281999 -4.438181,-0.845996 -1.248274,-0.563998 -2.26539,-1.474794 -3.051387,-2.732388 L 27.010201,36 c 1.293738,1.448202 2.88126,1.962575 4.563101,2.096298 1.063315,0 1.890852,-0.179999 2.482589,-0.540998 C 34.656889,37.185502 34.99998,37.026465 35,36.277469 c -2e-5,-0.425199 -0.148972,-0.781197 -0.361571,-1.067996 -0.203439,-0.286599 -0.513178,-0.531598 -0.929256,-0.734997 -0.416099,-0.212599 -1.137315,-0.452998 -2.163591,-0.721197 -0.998596,-0.268198 -2.341955,-0.536197 -3.229611,-0.804396 -0.878396,-0.277399 -1.641233,-0.633397 -2.28839,-1.067996 -0.637996,-0.434598 -1.141895,-0.980195 -1.511774,-1.636592 -0.369838,-0.656598 -0.554777,-1.483994 -0.554777,-2.48279 0,-1.821392 0.614877,-3.240786 1.844652,-4.257782 1.229775,-1.026395 3.39141,-1.539593 5.6846,-1.539593 3.319386,0 5.667976,1.137395 7.04577,3.411985 L 35.554065,27.30629 C 35.027027,26.492694 34.43525,25.910095 33.778752,25.558697 33.122255,25.207498 32.340959,25.0317 31.434762,25.0317 c -1.96062,0.02472 -3.114664,0.509197 -3.465449,2.3386 0,0.619599 0.295878,1.195802 0.887656,1.5286 0.591777,0.323599 2.022956,0.651997 3.493209,0.984796 0.813657,0.212599 1.618094,0.443798 2.41319,0.693397 0.795177,0.240399 1.502534,0.577998 2.121991,1.012596 0.628757,0.434598 1.132675,1.003195 1.511793,1.705792 0.379079,0.702797 0.568638,1.604393 0.568658,2.704589"/>
+</svg>
diff --git a/exwm.org b/exwm.org
new file mode 100644
index 0000000..3450a69
--- /dev/null
+++ b/exwm.org
@@ -0,0 +1,506 @@
+#+title: exwm configuration
+
+* Load and basic config
+ #+begin_src emacs-lisp
+ (use-package exwm
+ :ensure t
+ :init (setq 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
+* Frames 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-w3m (&optional init)
+ (interactive "P")
+ (exwm-workspace-switch-create 5)
+ (when (jao-exwm--mark-frame init)
+ (jao-afio-open-w3m)
+ (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-w3m-frame 'jao-exwm--goto-w3m)
+ (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-w3m)
+ (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 titles
+ #+BEGIN_SRC emacs-lisp
+ (defun jao-exwm--use-title-p ()
+ (or (string-prefix-p "sun-awt-X11-" exwm-instance-name)
+ (string= "Navigator" exwm-instance-name)
+ (string= "Spotify" exwm-class-name)
+ (string= "XTerm" exwm-class-name)
+ (string= "Zathura" exwm-class-name)))
+
+ (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-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"))
+
+ (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 and system tray
+ #+BEGIN_SRC emacs-lisp
+ (require 'exwm-systemtray)
+ (setq exwm-systemtray-height 14)
+ (exwm-systemtray-enable)
+
+ (setq jao-minibuffer-frame-width 271)
+ (add-hook 'exwm-workspace-switch-hook #'jao-minibuffer-refresh)
+
+ (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 (equalp jao-exwm--name name)
+ (equalp (buffer-name) name)
+ (equalp exwm-class-name name)
+ (equalp exwm-title name)))
+
+ (defun jao-exwm-switch-to-class/title (cln)
+ (interactive)
+ (when cln
+ (if (jao-exwm--check-name cln)
+ (current-buffer)
+ (let ((bfs (seq-filter `(lambda (b)
+ (and (not (eq b ,(current-buffer)))
+ (with-current-buffer b
+ (jao-exwm--check-name ,cln))))
+ (buffer-list))))
+ (when (car bfs) (pop-to-buffer (car (reverse bfs))))))))
+
+ (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 runners
+ #+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
+ (cl-case n
+ ((1) (jao-afio--goto-main))
+ ((2) (jao-afio--goto-gnus))
+ ((3) (jao-afio--goto-w3m))
+ ((4) (jao-afio--goto-docs))
+ ((5) (jao-afio--goto-scratch-1))
+ ((0) (jao-afio--goto-scratch)))
+ (exwm-workspace-switch-create n)))
+
+ (defmacro jao-def-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)
+ ,(or (stringp (car args)) 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))))
+
+ (jao-def-runner jao-exwm-spotify 6 "Spotify" "spotify")
+ (jao-def-runner jao-exwm-spt 6 "spt" "xterm" "-e" "spt")
+
+ (jao-def-runner jao-exwm-firefox 5 "Firefox" "firefox")
+
+ (defun jao-exwm-firefox-1 ()
+ (interactive)
+ (jao-exwm-firefox)
+ (delete-other-windows))
+
+ (jao-def-runner jao-exwm-vlc 4 "VLC" "vlc")
+
+ (jao-def-runner jao-exwm-slack 0 "Slack" "slack")
+ (jao-def-runner jao-exwm-signal 0 "Signal" "signal-desktop")
+
+ (jao-def-runner jao-exwm-proton-bridge 0 "*proton-bridge*" "protonmail-bridge")
+ (jao-def-runner jao-exwm-htop 0 "htop-xterm"
+ "xterm" "-title" "htop-xterm" "-e" "htop")
+
+ (jao-def-runner jao-exwm-aptitude 0 "aptitude-xterm"
+ "xterm" "-title" "aptitude-xterm" "-e" "aptitude")
+ (jao-def-runner jao-exwm-blueman 0 "Blueman-manager" "blueman-manager")
+ (jao-def-runner jao-exwm-ncmpcpp 0 "ncmpcpp" "xterm" "-e" "ncmpcpp" "-p" "6669")
+
+ (jao-def-runner jao-exwm-proton-vpn 0 "*pvpn*" proton-vpn-status)
+ (jao-def-runner jao-exwm-enwc 0 "*ENWC*" enwc)
+ (jao-def-runner jao-exwm-bluetooth 0 "*Bluetooth*" bluetooth-list-devices)
+ (jao-def-runner jao-exwm-paradox 0 "*Packages*" paradox-list-packages nil)
+ (jao-def-runner jao-exwm-proced 0 "*Proced*" proced)
+
+ (defun jao-exwm-kill-xmobar ()
+ (interactive)
+ (shell-command "killall xmobar-exwm"))
+
+ (defun jao-exwm-xmobar ()
+ (interactive)
+ (jao-exwm-kill-xmobar)
+ (start-process "" nil "xmobar-exwm" "-d"))
+
+ (jao-def-runner jao-exwm-open-with-zathura nil nil "zathura" (buffer-file-name))
+ (jao-def-runner jao-exwm-open-with-mupdf nil nil "mupdf" (buffer-file-name))
+ (jao-def-runner jao-exwm-xterm 0 nil "xterm")
+
+ (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--buffers ()
+ (seq-filter (lambda (b)
+ (string= "Zathura"
+ (or (buffer-local-value 'exwm-class-name b) "")))
+ (buffer-list)))
+
+ (defun jao-zathura--file-info (b)
+ (with-current-buffer b
+ (when (string-match "\\(.+\\) \\[\\(.+\\) (\\([0-9]+\\)/\\([0-9]+\\))\\]"
+ (or exwm-title ""))
+ (list (expand-file-name (match-string 1 exwm-title))
+ (string-to-number (match-string 3 exwm-title))
+ (string-to-number (match-string 4 exwm-title))
+ (match-string 2 exwm-title)))))
+
+ (defun jao-exwm--send-str (str)
+ (dolist (k (string-to-list (kbd str)))
+ (exwm-input--fake-key k)))
+
+ (defun jao-zathura-open-doc (file-name &optional page-no height)
+ (let* ((file-name (expand-file-name file-name))
+ (buffer (seq-find `(lambda (b)
+ (string= ,file-name
+ (car (jao-zathura--file-info b))))
+ (jao-zathura--buffers))))
+ (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)
+ (jao-exwm--send-str (format "%sg" page-no)))))
+
+ (defun jao-exwm-pdf-zathura-close-all ()
+ (interactive)
+ (dolist (b (jao-zathura--buffers))
+ (ignore-errors (with-current-buffer b (jao-exwm--send-str "q"))))
+ t)
+
+ (defun jao-exwm-pdf-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-pdf-goto-org* ()
+ (interactive)
+ (jao-exwm-pdf-goto-org t))
+
+ (defun jao-exwm-org-store-zathura-link ()
+ (when-let ((info (jao-zathura--file-info (current-buffer))))
+ (jao-org-links-store-pdf-link (car info)
+ (cadr info)
+ (jao-org--pdf-title (car info)))))
+
+ (defun jao-exwm-pdf-enable-zathura ()
+ (interactive)
+ (add-hook 'kill-emacs-query-functions #'jao-exwm-pdf-zathura-close-all)
+ (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)
+ (remove-hook 'kill-emacs-query-functions #'jao-exwm-pdf-zathura-close-all)
+ (setq jao-org-open-pdf-fun nil)
+ (setq jao-org-links-pdf-store-fun nil)
+ (setq jao-open-doc-fun #'jao-find-or-open))
+
+ (when (not jao-browse-doc-use-emacs-p)
+ (jao-exwm-pdf-enable-zathura))
+
+ #+end_src
+* Hydras
+ #+begin_src emacs-lisp
+ (major-mode-hydra-define pdf-view-mode nil
+ ("Elsewhere"
+ (("b" (jao-buffer-same-mode 'pdf-view-mode) "other PDFs")
+ ("o" jao-org-pdf-goto-org "notes file")
+ ("O" (jao-org-pdf-goto-org 4) "notes file, linking"))
+ "External"
+ (("p" doc-view-presentation "presentation")
+ ("z" jao-exwm-open-with-zathura "open with zathura")
+ ("m" jao-exwm-open-with-mupdf "open with mupdf"))))
+
+ (defhydra jao-hydra-float (:color blue)
+ "Float"
+ ("f" exwm-floating-toggle-floating "float")
+ ("F" exwm-layout-toggle-fullscreen "full")
+ ("tl" (jao-exwm--float-to 20 20))
+ ("tr" (jao-exwm--float-to -20 20))
+ ("bl" (jao-exwm--float-to 20 -20))
+ ("br" (jao-exwm--float-to -20 -20))
+ ("c" jao-exwm--center-float)
+ ("k" (exwm-floating-move 0 -5) :color red)
+ ("j" (exwm-floating-move 0 5) :color red)
+ ("h" (exwm-floating-move -5 0) :color red)
+ ("l" (exwm-floating-move 5 0) :color red)
+ ("K" (exwm-layout-enlarge-window 5) :color red)
+ ("J" (exwm-layout-enlarge-window -5) :color red)
+ ("H" (exwm-layout-enlarge-window 5 t) :color red)
+ ("L" (exwm-layout-enlarge-window -5 t) :color red)
+ ("q" nil ""))
+
+ (pretty-hydra-define jao-hydra-exwm-misc (:color blue :quit-key "q")
+ ("Jump around"
+ (("s" jao-slack-download "slack downloads")
+ ("h" (jao-buffer-same-mode 'helpful) "helpful buffer")
+ ("g" exwm-workspace-switch nil))
+ "X-Windows"
+ (("f" exwm-floating-toggle-floating "toggle floating window")
+ ("F" exwm-layout-toggle-fullscreen "toggle fullscreen mode")
+ ("m" exwm-workspace-move-window nil))
+ "Zathura"
+ (("o" jao-exwm-pdf-goto-org "Go to org notes")
+ ("O" (jao-exwm-pdf-goto-org t) "Go to org notes, linking"))
+ "Exwm utilities"
+ (("k" exwm-input-release-keyboard "release keyboard")
+ ("x" exwm-reset "reset exwm"))))
+
+ (pretty-hydra-define jao-hydra-exwm
+ (global-map "s-w" :color blue :quit-key "q")
+ ("Notes"
+ (("n" org-roam-capture "capture note")
+ ("N" org-roam-find-file-immediate "go to note"))
+ "Packages"
+ (("a" jao-vterm-aptitude "aptitude")
+ ("l" jao-exwm-paradox "package list")
+ ("s-w" jao-hydra-exwm/body nil))
+ "Network"
+ (("s" jao-ssh "ssh")
+ ("b" jao-exwm-bluetooth "bluetooth")
+ ;; ("N" jao-exwm-enwc "networks")
+ )
+ "Proton"
+ (("v" jao-exwm-proton-vpn "proton vpn")
+ ("m" run-proton-bridge "proton bridge"))
+ "Monitors"
+ (("p" jao-vterm-htop "htop")
+ ("P" jao-exwm-proced "proced")
+ ("t" jao-time-echo-times "current time"))
+ "Apps"
+ (("f" jao-exwm-firefox-1 "firefox")
+ ("x" jao-exwm-xmobar "restart xmobar")
+ ("X" jao-exwm-kill-xmobar "kill xmobar"))
+ "Looks"
+ (("T" jao-toggle-transparency "toggle transparency"
+ :toggle (jao-transparent-p) :color red)
+ ("w" jao-set-wallpaper "set wallpaper")
+ ("W" jao-set-random-wallpaper "set radom wallpaper"))
+ "Helpers"
+ (("r" org-reveal "org reveal")
+ ("k" jao-kb-toggle "toggle keyboard"
+ :toggle (jao-kb-toggled-p) :color red)
+ ("M" jao-minibuffer-toggle "toggle minibuffer"
+ :toggle jao-minibuffer-enabled-p))))
+ #+end_src
+* Keybindings
+ #+BEGIN_SRC emacs-lisp
+ (defun jao-exwm-x-kbs ()
+ (when (and exwm-class-name (string= exwm-class-name "XTerm"))
+ (exwm-input-set-local-simulation-keys '(([?\C-c ?\C-c] . [?\C-c])
+ ([?\C-x ?\C-c] . [?\C-c])))))
+
+ ;; (add-hook 'exwm-manage-finish-hook #'jao-exwm-x-kbs)
+
+ ;; To add a key binding only available in line-mode, simply define it in
+ ;; `exwm-mode-map'. The following example shortens 'C-c q' to 'C-q'.
+ (define-key exwm-mode-map [?\C-q] #'exwm-input-send-next-key)
+ (define-key exwm-mode-map [?\s-k] #'exwm-input-release-keyboard)
+ (define-key exwm-mode-map [?\s-f] #'jao-hydra-float/body)
+ (define-key exwm-mode-map [?\s-m] #'jao-hydra-media/body)
+ (define-key exwm-mode-map [?\s-s] #'jao-hydra-spotify/body)
+ (define-key exwm-mode-map [?\s-w] #'jao-hydra-exwm/body)
+ (define-key exwm-mode-map [?\s-p] #'jao-exwm-pdf-goto-org)
+ (define-key exwm-mode-map [?\s-P] #'jao-exwm-pdf-goto-org*)
+ ;; (define-key exwm-mode-map [?\s-w] #'jao-hydra-exwm/body)
+ (setq
+ exwm-input-global-keys
+ '(([?\s-0] . jao-afio--goto-scratch)
+ ([?\s-1] . jao-afio--goto-main)
+ ([?\s-2] . jao-afio--goto-gnus)
+ ([?\s-3] . jao-afio--goto-w3m)
+ ([?\s-4] . jao-afio--goto-docs)
+ ([?\s-A] . org-agenda-list)
+ ([?\s-a] . jao-first-window)
+ ([?\s-b] . jao-hydra-org-blog/body)
+ ([?\s-c] . jao-hydra-chats/body)
+ ([?\s-r] . counsel-linux-app)
+ ([?\s-t] . jao-vterm-here-toggle)
+ ([?\s-n] . jao-hydra-ednc/body)
+ ([?\s-O] . ace-swap-window)
+ ([?\s-o] . ace-window)
+ ([?\s-x] . jao-hydra-exwm-misc/body)
+ ([?\s-z] . jao-hydra-sleep/body)
+ ([XF86AudioMute] . jao-mixer-master-toogle)
+ ([XF86AudioPlay] . jao-player-toggle)
+ ([XF86AudioPause] . jao-player-toggle)
+ ([XF86AudioNext] . jao-player-next)
+ ([XF86AudioPrev] . jao-player-previous)
+ ([XF86AudioRaiseVolume] . jao-mixer-master-up)
+ ([XF86AudioLowerVolume] . jao-mixer-master-dow)
+ ([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)))
+
+ ;; (customize-set-variable 'exwm-input-global-keys exwm-input-global-keys)
+
+ #+END_SRC
diff --git a/gnus.org b/gnus.org
new file mode 100644
index 0000000..6d87741
--- /dev/null
+++ b/gnus.org
@@ -0,0 +1,660 @@
+#+PROPERTY: header-args :tangle yes :comments yes :results silent
+
+* Feature switching vars
+ #+BEGIN_SRC emacs-lisp
+ (defvar jao-gnus-use-local-imap t)
+ (defvar jao-gnus-use-leafnode t)
+ (defvar jao-gnus-use-pm-imap nil)
+ (defvar jao-gnus-use-gmail nil)
+ (defvar jao-gnus-use-gmane nil)
+ (defvar jao-gnus-use-nnml nil)
+ (defvar jao-gnus-use-maildirs nil)
+ (defvar jao-gnus-use-nnreddit nil)
+#+END_SRC
+* Looks
+*** Verbosity
+ #+begin_src emacs-lisp
+ (setq gnus-verbose 5)
+ #+end_src
+*** Geometry
+ #+BEGIN_SRC emacs-lisp
+ ;;; geometry:
+ (defvar jao-gnus-use-three-panes t)
+
+ (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 190))
+ (gnus-add-configuration
+ `(article
+ (horizontal 1.0
+ (vertical 63 (group 1.0))
+ (vertical 127
+ (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 63 (group 1.0))
+ (vertical 127 (summary 1.0 point))
+ ,side-bar)))
+
+ (gnus-add-configuration
+ `(reply
+ (horizontal 1.0
+ (message 90 point)
+ (article 100)
+ ,side-bar)))))
+
+ (defun jao-gnus--summary-done ()
+ (save-window-excursion (org-agenda-list)))
+
+ (add-hook 'gnus-summary-prepared-hook #'jao-gnus--summary-done)
+ #+END_SRC
+*** No blue icon
+ #+begin_src emacs-lisp
+ ;; (defalias 'gnus-mode-line-buffer-identification 'identity)
+ (advice-add 'gnus-mode-line-buffer-identification :override #'identity)
+ (setq gnus-mode-line-image-cache nil)
+ #+end_src
+* Search
+ [[info:gnus#Searching][info:gnus#Searching]]
+ #+begin_src emacs-lisp
+ (setq gnus-search-use-parsed-queries t
+ jao-gnus-search-prefix (expand-file-name "~/var/mail/"))
+
+ (defun jao-gnus-search-engine (engine)
+ `(gnus-search-engine ,engine (remove-prefix ,jao-gnus-search-prefix)))
+ #+end_src
+* News server
+ #+begin_src emacs-lisp
+ (setq gnus-select-method
+ (cond (jao-gnus-use-leafnode
+ `(nntp "localhost"
+ ,(jao-gnus-search-engine 'gnus-search-notmuch)))
+ (jao-gnus-use-gmane '(nntp "news.gmane.io"))
+ (t '(nnnil ""))))
+ (setq gnus-secondary-select-methods '())
+
+ (setq gnus-ignored-newsgroups
+ "^to\\.\\|^[0-9. ]+\\( \\|$\\)\\|^[\"]]\"[#'()]")
+
+ ;; nntp options
+ (setq nnheader-read-timeout 0.02)
+ #+end_src
+* IMAP servers
+ #+begin_src emacs-lisp
+ ;; archiving messages
+ (setq gnus-message-archive-group nil)
+
+ ;; imap
+ (when jao-gnus-use-local-imap
+ (add-to-list 'gnus-secondary-select-methods
+ `(nnimap ""
+ (nnimap-expunge immediately)
+ (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-gmail
+ (add-to-list 'gnus-secondary-select-methods
+ '(nnimap "gmail" (nnimap-address "imap.gmail.com")))
+ (add-to-list 'gnus-secondary-select-methods
+ '(nnimap "bigml" (nnimap-address "imap.gmail.com"))))
+ #+end_src
+* Mailbox and maildir servers
+ #+begin_src emacs-lisp
+ (setq mail-sources '((file :path "/var/mail/jao")))
+
+ (setq nnml-get-new-mail t
+ nnmail-treat-duplicates 'delete
+ nnmail-scan-directory-mail-source-once t
+ nnmail-cache-accepted-message-ids t
+ nnmail-message-id-cache-length 50000
+ nnmail-cache-ignore-groups ".*\\(trove\\.\\|feeds\\.\\|spamish\\).*"
+ nnmail-split-fancy-with-parent-ignore-groups nil
+ nnmail-crosspost t)
+
+ (setq nnmail-resplit-incoming t
+ nnmail-mail-splitting-decodes t
+ nnmail-split-methods 'nnmail-split-fancy)
+
+ (when jao-gnus-use-nnml
+ (add-to-list 'gnus-secondary-select-methods `(nnml "")))
+
+ (when jao-gnus-use-maildirs
+ (add-to-list 'gnus-secondary-select-methods
+ '(nnmaildir "bml" (directory "/home/jao/var/maildir/bigml/")))
+ (add-to-list 'gnus-secondary-select-methods
+ '(nnmaildir "jao" (directory "/home/jao/var/maildir/jao/")))
+ (add-to-list 'gnus-secondary-select-methods
+ '(nnmaildir "gmail" (directory "/home/jao/var/maildir/gmail/"))))
+
+ #+end_src
+* RSS servers
+ #+begin_src emacs-lisp
+ (setq nnrss-use-local nil
+ nnrss-directory (jao-gnus-dir "rss"))
+ (setq nnrss-wash-html-in-text-plain-parts t)
+ (setq nnrss-ignore-article-fields '(description
+ comments
+ dc:date
+ slash:comments
+ slash:description))
+
+ ;; (use-package nnreddit
+ ;; :ensure t
+ ;; :bind (:map gnus-group-mode-map (("R R" . gnus-group-restart))))
+
+ ;; (when jao-gnus-use-nnreddit
+ ;; (add-to-list 'gnus-secondary-select-methods '(nnreddit "")))
+
+ #+end_src
+* Agents, demons, synchronicity
+ #+BEGIN_SRC emacs-lisp
+ ;; gnus agent(s) and demons
+ ;; (setq gnus-agent nil)
+ (setq mail-user-agent 'gnus-user-agent)
+ (require 'gnus-demon)
+ (when (featurep 'gnus-desktop-notify)
+ (gnus-desktop-notify-mode 1)
+ (gnus-demon-add-scanmail))
+
+ ;; 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)
+
+ (setq gnus-gcc-mark-as-read t)
+ #+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 45)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 nil)
+
+ (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))))
+
+ (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"))
+
+
+ ;; To limit expiration to the `g' count, `jao-gnus--get-count':
+ ;; (remove-hook 'gnus-summary-prepare-exit-hook 'gnus-summary-expire-articles)
+
+ (define-key gnus-group-mode-map "g" 'jao-gnus-get-new-news)
+ (define-key gnus-group-mode-map "Z" 'jao-gnus-restart-servers)
+ #+END_SRC
+* Group parameters
+ #+begin_src emacs-lisp
+ (setq jao-gnus-expirable
+ (format (concat "^nnimap:\\("
+ "\\(\\(bigml\\|bml\\)/%s\\)\\|"
+ "\\(\\(jao\\|pm\\)/%s\\)\\|"
+ "\\(feeds/.+\\)\\|trash"
+ "\\)")
+ (regexp-opt '("support" "reports" "deploys"
+ "lists" "drivel" "bugs"))
+ (regexp-opt '("books" "think" "local" "drivel"
+ "lists" "emacs" "lobsters"))))
+
+ (setq gnus-parameters
+ `(("^nnimap:jao/.*"
+ (jao-gnus--trash-group "nnimap:trash")
+ (jao-gnus--archiving-group "nnimap:trove/jao"))
+ ("^nnimap:pm/.*"
+ (jao-gnus--trash-group "nnimap:pm/trash")
+ (jao-gnus--spam-group "nnimap:pm/spam")
+ (jao-gnus--archiving-group "nnimap:pm/archive"))
+ ("^nnimap:\\(jao\\|pm\\)/\\(trash\\|spam\\)"
+ (gcc-self . nil)
+ (auto-expire . t)
+ (total-expire . t)
+ (expiry-wait . 0.1)
+ (jao-gnus--trash-group nil)
+ (expiry-target . delete))
+ ("^nnimap:jao/inbox"
+ (gcc-self . t)
+ (posting-style (gcc "nnimap:trove/jao")))
+ ("^nnimap:b\\(ig\\)?ml/inbox"
+ (gcc-self . t)
+ (auto-expire . t)
+ (total-expire . t)
+ (expiry-wait . 7.0)
+ (jao-gnus--trash-group "nnimap:trash")
+ (expiry-target . "nnimap:bigml/trove"))
+ ("^nnimap:bml/inbox"
+ (jao-gnus--trash-group "nnimap:bml/trash")
+ (expiry-target . "nnimap:bml/trove"))
+ ("^nnimap:b\\(ig\\)?ml/.*"
+ (posting-style (address "jao@bigml.com"))
+ (jao-gnus--archiving-group "nnimap:bml/trove"))
+ ("^nnimap:b\\(ig\\)?ml/support"
+ (posting-style (address "support@bigml.com")))
+ (,jao-gnus-expirable
+ (jao-gnus--trash-group nil)
+ (gcc-self . nil)
+ (auto-expire . t)
+ (total-expire . t)
+ (expiry-wait . 3)
+ (expiry-target . delete))
+ ("^nnimap:feeds/podcasts"
+ (auto-expire . nil)
+ (total-expire . nil))
+ ("^nnimap:feeds/fun"
+ ;; (mm-text-html-renderer 'shr)
+ (gnus-inhibit-images nil))
+ ("^nnimap:feeds/\\(papers\\|programming\\|math\\|physics\\)$"
+ (expiry-wait . 30)
+ (jao-gnus--archiving-group "nnimap:trove/tech")
+ (posting-style (address "jao@gnu.org")))
+ ("^nnimap:feeds/\\(physics\\|math\\|papers\\)$"
+ (jao-gnus--archiving-group "nnimap:trove/sci"))
+ ("\\(gmane\\|gwene\\)\\..*"
+ (jao-gnus--archiving-group "nnimap:trove/tech")
+ (posting-style (address "jao@gnu.org")))))
+ #+end_src
+* Summary buffer
+*** Configuration, summary line
+ #+BEGIN_SRC emacs-lisp
+ (setq gnus-summary-ignore-duplicates t
+ gnus-suppress-duplicates t
+ gnus-summary-ignored-from-addresses jao-mails-regexp)
+
+ (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-face-1 'jao-gnus-face-tree)
+
+ (setq gnus-not-empty-thread-mark ?·) ; ↓)
+ (setq jao-gnus--summary-line-fmt
+ (concat "%%U %%*%%R %%uj "
+ "[ %%~(max-right 20)~(pad-right 20)n "
+ " %%I%%~(pad-left 2)t ] %%s"
+ "%%-%s="
+ "%%~(max-right 8)~(pad-left 8)&user-date;"
+ "\n"))
+
+ (defun jao-gnus--set-summary-line ()
+ (let* ((d (if jao-gnus-use-three-panes 75 12))
+ (w (- (window-width) d)))
+ (setq gnus-summary-line-format (format jao-gnus--summary-line-fmt w))))
+
+ (add-hook 'gnus-group-prepare-hook 'jao-gnus--set-summary-line)
+
+ (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)))
+ "¬" ;; "~"
+ " "))))
+
+ (setq gnus-summary-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")))
+
+ ;; old name, for emacs 23
+ (setq gnus-user-date-format-alist gnus-summary-user-date-format-alist)
+ #+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 <backspace>") '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
+*** Writing 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+olp "misc.org" "Physics" "arXiv")
+ "* %: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-treat-display-smileys nil)
+ (setq gnus-treat-fill-long-lines nil)
+ (setq gnus-treat-fill-article nil)
+ (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
+ "\\|^X-Newsreader:\\|^X-Mailer:\\|User-Agent:\\|X-User-Agent:")))
+ #+END_SRC
+*** HTML email
+ #+BEGIN_SRC emacs-lisp
+ ;; use w3m for html mail
+ (defun jao-gnus-html-renderer (handle)
+ (let ((w3m-message-silent t))
+ (condition-case nil
+ (mm-inline-text-html-render-with-w3m handle)
+ (error (delete-region (point) (point-max))
+ (mm-shr handle)))))
+
+ (setq gnus-button-url 'browse-url-generic
+ gnus-inhibit-images t
+ mm-text-html-renderer 'jao-gnus-html-renderer ;; 'w3m ;; 'shr
+ shr-use-colors nil
+ shr-use-fonts nil
+ mm-w3m-safe-url-regexp nil
+ mm-discouraged-alternatives nil ;; '("text/html" "text/richtext")
+ mm-inline-large-images 'resize)
+
+ ;; no html in From: (washing articles from arxiv feeds)
+ (require 'shr)
+ (defun jao-gnus-remove-anchors ()
+ (save-excursion
+ (goto-char (point-min))
+ (when (re-search-forward "updates on arXiv.org: <a" nil t)
+ (let ((begin (- (point) 3)))
+ (when (re-search-forward "^\\(To\\|Subject\\):" nil t)
+ (beginning-of-line)
+ (let ((shr-width 1000))
+ (shr-render-region begin (- (point) 1))
+ (goto-char begin)
+ (insert " ")))))))
+
+ (add-hook 'gnus-part-display-hook 'jao-gnus-remove-anchors)
+
+ ;; show images
+ (defun jao-gnus-show-image (&optional external)
+ (interactive "P")
+ (when (eq major-mode 'gnus-summary-mode)
+ (gnus-summary-select-article-buffer))
+ (let ((pos (next-single-property-change (point) 'w3m-image)))
+ (if (not pos)
+ (gnus-article-show-images)
+ (goto-char pos)
+ (if external (w3m-view-image) (w3m-toggle-inline-image)))))
+
+ (defun jao-gnus-show-images (&optional external)
+ (interactive "P")
+ (save-window-excursion
+ (gnus-summary-select-article-buffer)
+ (save-excursion
+ (let ((pos (next-single-property-change (point) 'w3m-image)))
+ (if (not pos)
+ (gnus-article-show-images)
+ (goto-char pos)
+ (w3m-toggle-inline-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)
+ (w3m-safe-view-this-url)))))
+
+ (defun jao-gnus-open-enclosure (&optional playp)
+ (interactive "P")
+ (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)))
+ (message "%s %s ..." (if playp "Playing" "Adding") url)
+ (if playp (emms-play-url url) (emms-add-url url))
+ (when playp
+ (sit-for 1)
+ (jao-emms-echo))))))
+ #+end_src
+*** Tweets and toots
+ #+BEGIN_SRC emacs-lisp
+ (defun jao-gnus--find-link ()
+ (when (eq major-mode 'gnus-summary-mode)
+ (gnus-summary-select-article-buffer))
+ (goto-char (point-max))
+ (or (search-backward-regexp "^Link\\b" nil t)
+ (search-backward-regexp "^URL: h" nil t)
+ (search-backward-regexp "^Via: h"))
+ (goto-char (match-end 0))
+ (jao-w3m--toot-text nil nil ""))
+
+ (defun jao-gnus-tweet-link (&optional toot)
+ (interactive "P")
+ (save-excursion
+ (jao-gnus--find-link)
+ (if toot
+ (jao-w3m-toot nil nil (elt gnus-current-headers 1))
+ (jao-w3m-tweet nil nil (elt gnus-current-headers 1)))))
+
+ (defun jao-gnus-toot-link ()
+ (interactive)
+ (jao-gnus-tweet-link t))
+
+ (defun jao-gnus-mail-link ()
+ (interactive)
+ (let ((txt (jao-gnus--find-link)))
+ (when txt
+ (message-mail-other-window)
+ (message-goto-body)
+ (insert "\n\n" txt)
+ (message-goto-to))))
+ #+END_SRC
+* Keyboard shortcuts
+ #+BEGIN_SRC emacs-lisp
+ (define-key gnus-article-mode-map "\C-ci" 'w3m-view-image)
+ (define-key gnus-article-mode-map "\C-cb" 'jao-w3m-do-browse)
+ (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 "z" 'w3m-lnum-zoom-in-image)
+ (define-key gnus-article-mode-map "\C-ct" 'jao-gnus-tweet-link)
+ (define-key gnus-summary-mode-map "\C-ct" 'jao-gnus-tweet-link)
+ (define-key gnus-article-mode-map "\C-cT" 'jao-gnus-toot-link)
+ (define-key gnus-summary-mode-map "\C-cT" 'jao-gnus-toot-link)
+ (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)
+
+ (major-mode-hydra-define gnus-summary-mode nil
+ ("Browse"
+ (("g" jao-gnus-follow-link "Follow link in emacs")
+ ("G" (lambda () (interactive) (jao-gnus-follow-link t))
+ "Follow link in external browser"))
+ "Capture"
+ (("x" jao-gnus-arXiv-capture "Capture arXiv entry")
+ ("e" jao-gnus-open-enclosure "Add enclosure to playlist")
+ ("E" (jao-gnus-open-enclosure t) "Play enclosure"))
+ "Images"
+ (("i" jao-gnus-show-images "Show images"))
+ "Toot"
+ (("t" jao-gnus-tweet-link "Tweet article")
+ ("T" jao-gnus-toot-link "Toot article"))))
+
+ (major-mode-hydra-define gnus-article-mode nil
+ ("Browse"
+ (("g" jao-gnus-follow-link "Follow link in emacs")
+ ("G" (lambda () (interactive) (jao-gnus-follow-link t))
+ "Follow link in external browser"))
+ "Capture"
+ (("x" jao-gnus-arXiv-capture "Capture arXiv entry")
+ ("e" jao-gnus-open-enclosure "Add enclosure to playlist")
+ ("E" (jao-gnus-open-enclosure t) "Play enclosure"))
+ "Images"
+ (("z" w3m-lnum-zoom-in-image "Zoom image at point")
+ ("I" w3m-view-image "View image at point")
+ ("i" jao-gnus-show-images "Show images"))
+ "Toot"
+ (("t" jao-gnus-tweet-link "Tweet article")
+ ("T" jao-gnus-toot-link "Toot article"))))
+ #+END_SRC
diff --git a/init.org b/init.org
new file mode 100644
index 0000000..9cf40a2
--- /dev/null
+++ b/init.org
@@ -0,0 +1,4393 @@
+#+PROPERTY: header-args :tangle yes :comments yes :results silent
+
+* Packages
+*** Use package
+ Bootstrap `use-package' (from [[http://www.lunaryorn.com/2015/01/06/my-emacs-configuration-with-use-package.html][here]])
+
+ #+begin_src emacs-lisp
+ (unless (package-installed-p 'use-package)
+ (package-refresh-contents)
+ (package-install 'use-package))
+ (require 'use-package)
+ #+end_src
+*** Paradox
+ [[https://github.com/Malabarba/paradox][GitHub - Malabarba/paradox]]
+ #+begin_src elisp
+ (use-package paradox
+ :ensure t
+ :init (setq paradox-execute-asynchronously t)
+ :config
+ (defun package-menu-filter (&optional fltr)
+ (interactive)
+ (let* ((cmps (split-string (or fltr "") ":"))
+ (op (car cmps))
+ (arg (cadr cmps)))
+ (cond ((string= op "status") (package-menu-filter-by-status arg))
+ ((null fltr)
+ (call-interactively #'package-menu-filter-by-keyword))
+ ((string= op "arc")
+ (call-interactively #'package-menu-filter-by-archive))))))
+ (paradox-enable)
+ #+end_src
+*** Literate elisp
+ #+BEGIN_SRC emacs-lisp
+ (use-package poly-org :ensure t)
+ (use-package literate-elisp :ensure t)
+ #+END_SRC
+*** ELPA Keyring
+ #+BEGIN_SRC emacs-lisp
+ (use-package gnu-elpa-keyring-update :ensure t)
+ #+END_SRC
+*** Loading .el files
+
+ Let's prefer .el files over .elc when they former are newer
+
+ #+begin_src emacs-lisp
+ (setq load-prefer-newer t)
+ #+end_src
+
+* Initialisation
+*** Portability macros
+
+ From when we want our configurations to work on an apple tree...
+
+ #+begin_src emacs-lisp
+ (defmacro jao-syscase (clauses)
+ (let ((cls (assoc system-type clauses)))
+ (when cls `(progn ,@(cdr cls)))))
+
+ (defmacro jao-d-l (darw linux)
+ `(jao-syscase ((darwin ,darw) (gnu/linux ,linux))))
+
+ (defmacro jao-when-darwin (&rest body)
+ `(jao-syscase ((darwin ,@body))))
+
+ (defmacro jao-when-linux (&rest body)
+ `(jao-syscase ((gnu/linux ,@body))))
+ #+end_src
+
+*** Paths (load, info, exec)
+ #+begin_src emacs-lisp
+ (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-add-info-path (path)
+ (require 'info)
+ (add-to-list 'Info-directory-list path) )
+
+ (defun jao-load-path (subdir)
+ (let ((path (expand-file-name subdir local-lisp-dir)))
+ (when (file-directory-p path) (add-to-list 'load-path path))))
+ #+end_src
+***** Access to local directories and files
+
+ #+begin_src emacs-lisp
+ (defconst jao-info-dir
+ (expand-file-name (jao-d-l "~/.emacs.d/info" "~/doc/info")))
+
+ ;; Defined in bootstrap, just the same in all systems
+ ;; (defconst jao-emacs-dir
+ ;; (expand-file-name (jao-d-l "~/.emacs.d/config" "~/etc/emacs")))
+
+ (defconst jao-custom-dir (expand-file-name "custom" jao-emacs-dir))
+
+ (defconst local-lisp-dir (jao-d-l "~/.emacs.d/lib" "~/lib/elisp"))
+
+ (defconst jao-docs-dir (jao-d-l "~/Documents" "~/doc"))
+
+ (defconst jao-data-dir (expand-file-name "data" jao-emacs-dir))
+ (defun jao-data-file (file) (expand-file-name file jao-data-dir))
+
+ (defconst jao-notes-dir (expand-file-name "~/org"))
+
+ (defconst jao-sink-dir
+ (file-name-as-directory (expand-file-name "sink" jao-docs-dir)))
+
+ ;; (defvar jao-site-name (if (string-match "^\\([^.]+\\)\\.?" system-name)
+ ;; (match-string 1 system-name)
+ ;; system-name))
+
+ (defvar jao-site-name "site")
+ (defvar jao-site-dir nil)
+ (setq jao-site-dir (expand-file-name jao-site-name jao-emacs-dir))
+
+ (defun jao-site-custom-dir ()
+ (expand-file-name "custom" jao-site-dir))
+
+ (defun jao-local-el (basename)
+ (expand-file-name (concat basename ".el") jao-site-dir))
+
+ (defun jao-lib-file (basename)
+ (expand-file-name basename local-lisp-dir))
+
+ (defun jao-load-local-el (basename &optional no-def)
+ (let ((f (concat basename ".el"))
+ (lf (jao-local-el basename)))
+ (if (file-exists-p lf) (load lf)
+ (if (and (not no-def) (file-exists-p f)) (load f)))))
+
+ (defun jao-load-org (file)
+ (let ((file (format "%s.org" (file-name-sans-extension file))))
+ (literate-elisp-load-file (expand-file-name file jao-emacs-dir))))
+ #+end_src
+
+***** Load path initialisation
+
+ #+begin_src emacs-lisp
+ (add-to-list 'load-path local-lisp-dir)
+ (add-to-list 'load-path jao-custom-dir)
+ (add-to-list 'load-path (expand-file-name "lib" jao-emacs-dir))
+ #+end_src
+
+ Let's also add to our load path all the 'jao' library files which
+ we publish in a separate repo:
+
+ #+BEGIN_SRC emacs-lisp
+ (dolist (f (directory-files (expand-file-name "jao" local-lisp-dir) t "^[^.]+$"))
+ (when (file-directory-p f) (add-to-list 'load-path f)))
+ #+END_SRC
+
+***** Info paths
+
+ And extend the info paths to include local stuff:
+
+ #+begin_src emacs-lisp
+ (jao-add-info-path jao-info-dir)
+ ;; (jao-add-info-path "/usr/local/info")
+ ;; (jao-add-info-path "/usr/local/share/info")
+ ;; (jao-add-info-path "/usr/share/info")
+ #+end_src
+*** Preamble (pre.el)
+ Private variables defined in pre.el
+ #+begin_src emacs-lisp
+ (defvar jao-mails "")
+ (defvar jao-mails-regexp)
+
+ (defvar jao-irc-channels '())
+
+ (defvar jao-slack-client-id)
+ (defvar jao-slack-token)
+ #+end_src
+
+ Loading pre.el
+ #+begin_src emacs-lisp
+ (jao-load-local-el "pre" t)
+ (setq jao-mails-regexp (regexp-opt jao-mails))
+ #+end_src
+*** Custom location of custom.el and co.
+ #+begin_src emacs-lisp
+ (defvar jao-custom-file (jao-local-el "custom"))
+ (setq custom-file jao-custom-file)
+ (load custom-file)
+ (setq custom-unlispify-tag-names nil)
+ (setq custom-buffer-done-kill t)
+ (setq widget-image-enable nil)
+ #+end_src
+*** Bookmarks
+ #+BEGIN_SRC emacs-lisp
+ (setq bookmark-default-file "~/.emacs.d/emacs.bmk")
+ #+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
+ (fset 'yes-or-no-p 'y-or-n-p)
+ (setq inhibit-startup-message t)
+ (setq visible-bell t)
+ #+END_SRC
+*** Server
+ #+BEGIN_SRC emacs-lisp
+ (setenv "EDITOR" "emacsclient")
+ (when (not (daemonp)) (server-start))
+ #+END_SRC
+* System utilities
+*** Status, minibuffer
+ #+begin_src emacs-lisp
+ (use-package jao-minibuffer
+ :commands (jao-minibuffer-add-variable jao-minibuffer-refresh))
+ #+end_src
+*** Sleep/awake
+ #+begin_src emacs-lisp
+ (use-package jao-sleep)
+ (jao-sleep-dbus-register)
+ #+end_src
+*** Process runners
+ #+begin_src emacs-lisp
+ (defun jao-exec-string (fmt &rest args)
+ (string-trim (shell-command-to-string (apply 'format fmt args))))
+
+ (defun jao-exec (command)
+ (interactive
+ (list (read-shell-command "$ "
+ (if current-prefix-arg
+ (cons (concat " " (buffer-file-name)) 0)
+ ""))))
+ (start-process-shell-command command nil command))
+
+ (defmacro jao-def-exec (name &rest args)
+ `(defun ,name (&rest other-args)
+ (interactive)
+ (start-process-shell-command
+ ,(car args)
+ "* jao-exec - console *"
+ (string-join (append (list ,@args) other-args) " "))))
+ #+end_src
+*** Brightness control
+ #+begin_src emacs-lisp
+ (jao-def-exec jao-bright-up "brightnessctl" "-q" "s" "5%+")
+ (jao-def-exec jao-bright-down "brightnessctl" "-q" "s" "5%-")
+ #+end_src
+*** Memory usage
+ #+begin_src emacs-lisp
+ (use-package memory-usage :ensure t)
+ #+end_src
+*** Keyboard
+ #+begin_src emacs-lisp
+
+ (use-package hydra :ensure t)
+ (use-package major-mode-hydra
+ :ensure t
+ :custom (major-mode-hydra-invisible-quit-key "q")
+ :bind (("s-SPC" . major-mode-hydra)
+ ("H-s-SPC" . major-mode-hydra)
+ ("C-s-SPC" . major-mode-hydra)))
+ (require 'major-mode-hydra)
+
+ (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"))))
+ #+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 "))
+ ;; (display-battery-mode 1)
+ #+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
+*** Pinentry
+ #+begin_src emacs-lisp
+ (use-package pinentry :ensure t)
+ (pinentry-start)
+ #+end_src
+* Colours and themes
+*** Transparency
+ #+begin_src emacs-lisp
+ (defvar jao-frames-default-alpha (if (eq window-system 'pgtk) 96 90))
+
+ (defun jao-sway-set-transparency ()
+ (when jao-sway-enabled-p
+ (let ((alpha (/ (or (cadr (frame-parameter nil 'alpha)) 100) 100.0)))
+ (jao-swaymsg (format "[app_id=emacs] opacity %s" alpha)))))
+
+ (defun jao-toggle-transparency (&optional all)
+ (interactive "P")
+ (if all
+ (let* ((trans (/= jao-frames-default-alpha 100))
+ (new-alpha (if trans 100 jao-frames-default-alpha)))
+ (modify-all-frames-parameters `((alpha . (,new-alpha ,new-alpha)))))
+ (if (/= (or (cadr (frame-parameter nil 'alpha)) 100) 100)
+ (set-frame-parameter nil 'alpha '(100 100))
+ (set-frame-parameter nil 'alpha (list jao-frames-default-alpha
+ jao-frames-default-alpha))))
+ (jao-sway-set-transparency))
+
+ (defun jao-transparent-p ()
+ (/= (or (cadr (frame-parameter nil 'alpha)) 100) 100))
+
+ (defun jao-set-transparency (&optional level)
+ (interactive "nOpacity (0-100): ")
+ (set-frame-parameter nil 'alpha (list level level))
+ (jao-sway-set-transparency))
+ #+end_src
+*** Faces, fonts, fontsets
+***** General
+ #+BEGIN_SRC emacs-lisp
+ ;;; Font lock
+ (global-font-lock-mode 1)
+
+ ;; customize buttons
+ (setq custom-raised-buttons nil)
+
+ ;; language environment
+ (set-language-environment "UTF-8")
+ (set-keyboard-coding-system 'latin-1)
+
+ (setq default-input-method "catalan-prefix")
+ (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)
+
+ (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
+***** Unicode fonts
+ 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 (frame)
+ ;; (set-fontset-font t 'unicode "Hack-9")
+ (set-fontset-font t 'greek "GFS Didot")
+ (set-fontset-font t 'mathematical "FreeSerif")
+ (set-fontset-font t 64257 "Quivira")
+ (set-fontset-font t 65039 nil)
+ (set-fontset-font t '(9472 . 9599) "Symbola")
+ (set-fontset-font t 'symbol "Symbola-12") ;; "Noto Sans Symbols-10"
+ ;; (set-fontset-font t 'symbol "Unifont-12")
+ (set-fontset-font t 'egyptian "Noto Sans Egyptian Hieroglyphs")
+ (set-fontset-font t 'hangul "NanumGothicCoding"))
+ (add-to-list 'after-make-frame-functions 'jao--set-fontsets)
+
+ ;; (use-package unicode-fonts :ensure t)
+ ;; (unicode-fonts-setup)
+
+ #+END_SRC
+*** Themes
+ #+BEGIN_SRC emacs-lisp
+ (defvar jao-colors-use-light-scheme t)
+ (defvar jao-colors-theme nil)
+
+ (defun jao-colors-scheme-dark-p ()
+ (equal "dark" (getenv "JAO_COLOR_SCHEME")))
+
+ (setq custom-theme-directory
+ (expand-file-name "jao/themes" local-lisp-dir))
+
+ (setq jao-frames-default-font "Hack-9") ;; "Iosevka-10"
+
+ (if (or (not window-system) (jao-colors-scheme-dark-p))
+ (setq jao-colors-use-light-scheme nil
+ jao-colors-theme nil ;; 'doom
+ jao-colors-theme
+ (if (or (daemonp) window-system) jao-colors-theme 'console)
+ jao-colors-doom-theme 'doom-tomorrow-night
+ jao-colors-doom-theme 'doom-wilmersdorf
+ jao-colors-doom-theme 'doom-nord
+ jao-colors-doom-theme 'doom-sourcerer
+ jao-frames-fringe-mode nil
+ jao-vc-use-diff-hl t)
+ (setq jao-colors-use-light-scheme t
+ jao-colors-theme nil ;; 'doom
+ ;; jao-colors-theme 'solarized-light
+ jao-colors-doom-theme 'doom-solarized-light
+ jao-vc-use-diff-hl t
+ jao-frames-fringe-mode nil))
+
+ ;; notification colors
+ (setq jao-osd-cat-color-bg
+ (if jao-colors-use-light-scheme "grey30" "white")
+ jao-osd-cat-color-fg
+ (if jao-colors-use-light-scheme "white" "grey30"))
+
+ (require 'jao-themes)
+
+ (when (eq jao-colors-theme 'solarized)
+ (use-package solarized-theme
+ :ensure t
+ :init (setq solarized-distinct-doc-face t
+ solarized-distinct-fringe-background nil
+ solarized-scale-org-headlines nil
+ solarized-scale-outline-headlines nil
+ solarized-use-variable-pitch nil
+ solarized-use-less-bold t
+ solarized-use-more-italic t)))
+
+ (when (eq jao-colors-theme 'doom)
+ (use-package doom-themes
+ :ensure t
+ :init (setq doom-nord-region-highlight 'frost
+ doom-nord-brighter-modeline t
+ doom-nord-light-region-highlight 'frost
+ doom-nord-light-brighter-modeline t)))
+
+ (when (eq jao-colors-theme 'modus)
+ (use-package modus-operandi-theme
+ :ensure t
+ :init (setq modus-operandi-theme-completions 'opinionated
+ modus-operandi-theme-diffs 'desaturated
+ modus-operandi-theme-mode-line nil))
+
+ (use-package modus-vivendi-theme
+ :ensure t
+ :init (setq modus-vivendi-theme-completions 'opinionated
+ modus-vivendi-theme-diffs 'desaturated
+ modus-vivendi-theme-mode-line nil)))
+
+ (defun jao-themes-setup ()
+ (cond ((not jao-colors-theme)
+ (let ((light jao-colors-use-light-scheme))
+ (load-theme (if light 'jao-light 'jao-greenish) t)))
+ ((eq jao-colors-theme 'modus)
+ (let ((light jao-colors-use-light-scheme))
+ (load-theme (if light 'modus-operandi 'modus-vivendi) t)))
+ ((eq jao-colors-theme 'doom)
+ (load-theme jao-colors-doom-theme t)
+ ;; (load-theme 'jao-doom t)
+ )
+ ((eq jao-colors-theme 'solarized-light)
+ (load-theme 'solarized-light t))
+ ((eq jao-colors-theme 'zenburn)
+ (require 'jao-zenburn-theme)
+ (load-theme 'zenburn t))))
+
+ (when (not (eq window-system 'pgtk)) (jao-themes-setup))
+ #+END_SRC
+* Help system
+*** Echos and suggestions
+ #+begin_src emacs-lisp
+
+ (setq suggest-key-bindings 5
+ echo-keystrokes 2)
+
+ #+end_src
+*** Helpful
+ #+begin_src emacs-lisp
+ (use-package helpful
+ :ensure t
+ :custom ((helpful-max-buffers 20))
+ :config
+ (setq helpful-switch-buffer-function #'pop-to-buffer-same-window)
+
+ :bind (("C-h k" . helpful-key)
+ ("C-h v" . helpful-variable)
+ ("C-h f" . helpful-callable)
+ ("C-h o" . helpful-symbol)))
+ #+end_src
+*** Inform (links in info buffers)
+ #+begin_src emacs-lisp
+ (use-package inform :ensure t)
+ #+end_src
+*** Man pages
+ #+begin_src emacs-lisp
+ (setq Man-notify-method 'aggressive) ;; pushy - same window
+ #+end_src
+*** Recoll
+ #+begin_src emacs-lisp
+ (use-package jao-recoll)
+ #+end_src
+* Window manager helpers
+*** exwm
+ #+begin_src emacs-lisp
+ (setq exwm-workspace-current-index 0)
+ (defvar jao-exwm-enabled-p nil)
+ (defvar jao-exwm--use-afio t)
+
+ (defun jao-exwm-enable ()
+ (jao-load-org "exwm.org")
+ (setq jao-exwm-enabled-p t)
+ (jao-ednc-setup 90)
+ (exwm-enable)
+ (jao-toggle-inactive-mode-line t)
+ (add-hook 'after-init-hook #'jao-trisect t)
+ (message "Welcome to exwm"))
+ #+end_src
+
+ #+begin_src shell :tangle ~/etc/config/X/exwm/xinitrc
+ unclutter &
+ xmobar-exwm -d &
+ emacs=${emacs:-emacs}
+ $emacs -mm -f jao-exwm-enable
+ #+end_src
+*** sway
+ #+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-p nil)
+
+ (defun jao-sway-run-or-focus (cmd &optional ws)
+ (if (not (string-blank-p (shell-command-to-string (format "pidof %s" cmd))))
+ (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-enable ()
+ (setq jao-sway-enabled-p t)
+ (setq jao-browse-doc-use-emacs-p t)
+ ;; (setq frame-background-mode (if (jao-colors-scheme-dark-p) 'dark 'light))
+ ;; (frame-set-background-mode nil)
+ (jao-themes-setup)
+ (jao-toggle-inactive-mode-line t)
+ (jao-trisect)
+ (display-battery-mode -1)
+ (jao-set-transparency 96)
+ (message "Welcome to sway"))
+ #+end_src
+*** wallpaper
+ #+begin_src emacs-lisp
+ (defvar jao-wallpaper-dir "~/etc/config/X/wallpapers/")
+
+ (defvar jao-wallpaper-random-candidates '("pattern.jpg"
+ "pattern2.jpg"
+ "bluscher.jpg"
+ "castle.jpg"
+ "einsteinstable.jpg"
+ "leaf.jpg"
+ "galaxy2.jpg"
+ "galaxy3.jpg"
+ "polyhedra.jpg"
+ "blade-runner2.jpg"
+ "abstract-blue.jpg"))
+
+ (setq jao-wallpaper-random-candidates-light
+ '("white-owl.jpg"
+ "moon.jpg"
+ "einsteinstable.jpg"
+ "city-lighter.jpg"))
+
+ (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-p
+ (jao-swaymsg (format "output * bg %s fill" current))
+ (shell-command (format "xwallpaper --zoom %s" f))))))
+
+ (defun jao-set-random-wallpaper ()
+ (interactive)
+ (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-exec-string "xdg-screensaver status") "enabled"))
+
+ (defun jao-screensaver-toggle ()
+ (interactive)
+ (let ((wid (jao-exec-string "xdotool getwindowfocus")))
+ (if (jao-screensaver-enabled)
+ (jao-exec-string "xdg-screensaver suspend %s" wid)
+ (jao-exec-string "xdg-screensaver resume %s" wid))
+ (jao-notify (format "Using '%s'"
+ (jao-exec-string "xdotool getwindowname %s" wid))
+ (format "Screensaver %s"
+ (jao-exec-string "xdg-screensaver status")))))
+
+ (jao-def-exec jao-xlock-screen "xdg-screensaver" "activate")
+ (jao-def-exec jao-suspend "sudo" "/usr/sbin/pm-suspend")
+
+ (defun jao-lock-screen ()
+ (interactive)
+ (if jao-sway-enabled-p
+ (shell-command "swaylock -i ~/.lockimage")
+ (jao-xlock-screen)))
+
+ (pretty-hydra-define jao-hydra-sleep (:color blue)
+ ("Sleep"
+ (("l" jao-lock-screen "lock screen")
+ ("z" jao-suspend "sleep")
+ ("u" jao-screensaver-toggle "enable/disable screensaver")
+ ("q" nil "cancel"))))
+ #+end_src
+* Mode line
+*** 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)
+ (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)
+ (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))))))
+ #+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"))
+ (line-number-mode 1)
+ (column-number-mode 1)
+ #+end_src
+*** Mode line time
+ #+begin_src emacs-lisp
+ (setq display-time-string-forms '(24-hours ":" minutes))
+ (display-time-mode -1)
+ #+end_src
+*** Mode line toggle
+ #+begin_src emacs-lisp
+ (defun jao-toggle--face-height (face &optional all)
+ (let* ((h (face-attribute face :height (window-frame)))
+ (nh (if (eq 'unspecified h) 1 'unspecified)))
+ (set-face-attribute face (when (not all) (window-frame)) :height nh)))
+
+ (defun jao-toggle-mode-line (&optional all)
+ (interactive "P")
+ (jao-toggle--face-height 'mode-line all))
+
+ (defun jao-toggle-inactive-mode-line (&optional all)
+ (interactive "P")
+ (jao-toggle--face-height 'mode-line-inactive all))
+
+ (defun jao-echo-mode-line ()
+ (interactive)
+ (message "%s" (format-mode-line mode-line-format)))
+
+ (defun jao--hide-inactive-mode-line (frame)
+ (set-face-attribute 'mode-line-inactive frame :height 1))
+
+ (add-to-list 'after-make-frame-functions #'jao--hide-inactive-mode-line)
+
+ (global-set-key (kbd "<home>") #'jao-toggle-inactive-mode-line)
+ (global-set-key (kbd "<end>") #'jao-toggle-mode-line)
+ (global-set-key (kbd "<insert>") #'jao-echo-mode-line)
+
+ #+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 "eldoc" '(diminish 'eldoc-mode ""))
+ (eval-after-load "autorevert" '(diminish 'auto-revert-mode "")))
+ #+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)
+ (setq jao-notify-use-messages-p t)
+ ;; "/usr/share/icons/Papirus/64x64/mimetypes/audio-x-generic.svg"
+ ;; "/usr/share/icons/Tango/scalable/mimetypes/audio-x-generic.svg"
+ (defvar jao-notify-audio-icon (jao-data-file "music-player-icon.png"))
+ #+end_src
+*** tracking
+***** Package
+ #+begin_src emacs-lisp
+ (use-package tracking
+ :ensure 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))
+
+ (require 'tracking)
+
+ (defhydra jao-hydra-tracking (global-map "C-c")
+ ("C-SPC" tracking-next-buffer "next")
+ ("SPC" tracking-next-buffer "next"))
+
+ (defun jao-shorten-modes (&rest modes)
+ (dolist (m modes) (add-to-list 'tracking-shorten-modes m)))
+
+ (defun jao-tracking-face (face)
+ (add-to-list 'tracking-faces-priorities face))
+ #+end_src
+***** Shorten
+ #+begin_src emacs-lisp
+ (require 'shorten)
+
+ (defun jao-tracking--clean-slack (s)
+ (let ((s (replace-regexp-in-string
+ "^\\*Slack - .*? : \\(mpdm-\\)?\\([^ ]+\\)\\( \\(T\\)\\)?.*" "#\\2\\4" s)))
+ (replace-regexp-in-string "^[^a-zA-Z#]+" "#" s)))
+
+ (defun jao-tracking-shorten-aggressively (lst tail-count)
+ (let* ((s (shorten-join-sans-tail lst tail-count)))
+ (if (string-match-p "^#" s) (substring s 1 nil) s)))
+
+ (defun jao-tracking-split-clean (s)
+ (shorten-split (jao-tracking--clean-slack s)))
+
+ (defun jao-tracking-shorten (old-func &rest args)
+ (let ((shorten-join-function #'jao-tracking-shorten-aggressively)
+ (shorten-split-function #'jao-tracking-split-clean))
+ (apply old-func args)))
+
+ (advice-add #'tracking-shorten :around #'jao-tracking-shorten)
+ #+end_src
+***** Minibuffer / proplog
+ #+begin_src emacs-lisp
+ (defvar jao-tracking-string "")
+
+ (setq jao-tracking-bkg
+ (if (jao-colors-scheme-dark-p) "grey20" "grey98"))
+
+ (defface jao-tracking-minibuffer `((t :background ,jao-tracking-bkg)) "")
+ (defface jao-tracking-minibuffer-sep
+ `((t :foreground ,jao-tracking-bkg :background ,jao-tracking-bkg)) "")
+
+ (defun jao-tracking-set-log (v)
+ (when (member window-system '(x))
+ (x-change-window-property "_EMACS_LOG" v nil nil nil nil 0)))
+
+ (jao-tracking-set-log "")
+
+ (defun jao-tracking--buffer-str (s)
+ (if (listp s)
+ `(:propertize ,(plist-get s :propertize)
+ face
+ (jao-tracking-minibuffer
+ ,@(when-let ((f (plist-get s 'face)))
+ (jao-tracking-set-log " * ")
+ (list f))))
+ `(:propertize "|" face jao-tracking-minibuffer-sep)))
+
+ (defun jao-tracking-build-str (new-val)
+ (jao-tracking-set-log "")
+ (if (listp new-val)
+ (mapcar #'jao-tracking--buffer-str new-val)
+ new-val))
+
+ (defun jao-tracking-echo (sym new-val op where)
+ (setq jao-tracking-string (jao-tracking-build-str new-val))
+ (jao-minibuffer-refresh))
+
+ (jao-minibuffer-add-variable 'jao-tracking-string -10)
+ (add-variable-watcher 'tracking-mode-line-buffers #'jao-tracking-echo)
+ ;; since we're using the minibuffer, forget the mode line
+ (advice-add #'tracking-mode :override
+ (lambda (&optional _) (interactive)))
+ #+end_src
+***** Additional highlighting
+ #+begin_src emacs-lisp
+ (defvar jao-tracking-highlight-rx "$^")
+
+ (defun jao-tracking-add-buffer (old-func &rest args)
+ (let* ((buffer (car args))
+ (faces (if (and buffer
+ (string-match-p jao-tracking-highlight-rx
+ (buffer-name buffer)))
+ (cons 'lui-highlight-face (cadr args))
+ (cadr args))))
+ (funcall old-func buffer faces)))
+
+ (advice-add #'tracking-add-buffer :around #'jao-tracking-add-buffer)
+ (jao-tracking-face 'lui-highlight-face)
+ #+end_src
+*** notification server
+ #+begin_src emacs-lisp
+ (use-package ednc
+ :ensure t
+ :diminish)
+
+ (use-package jao-ednc
+ :after ednc
+ :commands (jao-ednc-setup)
+ :config
+ (defhydra jao-hydra-ednc (:color blue)
+ "Notifications"
+ ("s" jao-ednc-show "show last")
+ ("d" jao-ednc-dismiss "dismiss last")
+ ("D" jao-ednc-dismiss-all "dismiss all")
+ ("i" jao-ednc-invoke-last-action "invoke last action")
+ ("n" jao-ednc-pop "show all"))
+ (jao-ednc-ignore-app "Spotify")
+ :bind (("s-n" . jao-hydra-ednc/body)
+ ("H-s-n" . jao-hydra-ednc/body)))
+
+ #+end_src
+* Completion
+*** company
+ #+begin_src emacs-lisp
+ (use-package company
+ :ensure t
+ :custom
+ ((company-global-modes '(clojure-mode
+ clojurec-mode
+ emacs-lisp-mode
+ eshell-mode
+ lisp-interaction-mode
+ haskell-mode
+ scheme-mode racket-mode
+ message-mode
+ org-mode))
+ (company-idle-delay 0.5)
+ (company-lighter "")
+ (company-lighter-base "")
+ (company-show-numbers nil)
+ (company-tooltip-limit 15))
+ :config
+ (add-hook 'company-mode-hook
+ (lambda () (setq company-lighter-base "")))
+ :bind (("C-c ." . company-complete)
+ ;; ("C-c C-." . company-complete)
+ ;; ("C-c s s" . company-yasnippet)
+ :map company-active-map
+ ;; ("C-n" . company-select-next)
+ ;; ("C-p" . company-select-previous)
+ ("C-h" . company-show-doc-buffer)
+ ("M-." . company-show-location))
+ :diminish)
+
+ (use-package company-math :ensure t :after company)
+
+ (global-company-mode 1)
+
+ #+end_src
+*** completion engine
+ #+begin_src emacs-lisp
+ (defvar jao-completion-engine 'consult)
+ (jao-load-org (format "%s" jao-completion-engine))
+ #+end_src
+* Calendar, diary, weather
+*** Diary
+ #+BEGIN_SRC emacs-lisp
+ (setq diary-file (expand-file-name "diary" jao-notes-dir)
+ diary-display-function 'diary-fancy-display
+ diary-mail-addr "jao@localhost")
+
+ (add-hook 'diary-list-entries-hook 'diary-sort-entries t)
+ #+END_SRC
+*** Calendar
+ #+BEGIN_SRC emacs-lisp
+ (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)))))
+ #+END_SRC
+*** Weather
+***** Metar
+ #+BEGIN_SRC emacs-lisp
+ (use-package metar
+ :ensure t
+ :config
+ (setq jao-metar-station
+ (car (metar-find-station-by-latitude/longitude (calendar-latitude)
+ (calendar-longitude))))
+ (defun jao-metar-wind-direction (degrees pref)
+ (if (numberp degrees)
+ (concat pref (cond ((< 350 degrees 10) "N")
+ ((<= 10 degrees 80) "NE")
+ ((< 80 degrees 100) "E")
+ ((<= 100 degrees 170) "SE")
+ ((< 170 degrees 190) "S")
+ ((<= 190 degrees 260) "SW")
+ ((< 260 degrees 280) "W")
+ ((<= 280 degrees 350) "NW")
+ (t (number-to-string degrees))))
+ ""))
+
+ (defvar jao-metar-phenomena-alist
+ '(("clear" . "🌣")
+ ("sunny" . "🌣")
+ ("fair" . "🌣")
+ ("ice crystals" . "❄")
+ ("light snow" . "🌨")
+ ("snow" . "🌨")
+ ("mostly clear" . "🌤")
+ ("mostly sunny" . "🌤")
+ ("partly sunny" . "⛅")
+ ("obscured" . "🌁")
+ ("cloudy" . "☁")
+ ("overcast" . "☁")
+ ("partly cloudy" . "⛅")
+ ("mostly cloudy" . "☁")
+ ("considerable cloudiness" . "⛈")))
+
+ (defun jao-metar--phenomena (ph)
+ (if-let ((i (cdr (assoc-string (downcase (or ph ""))
+ jao-metar-phenomena-alist))))
+ (format "%s %s" ph i)
+ ph))
+
+ (defun jao-metar ()
+ (interactive)
+ (let* ((rec (metar-get-record jao-metar-station))
+ (info (metar-decode rec))
+ (ph (or (metar-phenomena (cdr rec))
+ (cdr (assoc 'phenomena info))))
+ (tm (when info
+ (float-time (time-since (cdr (assoc 'timestamp info))))))
+ (mins (when tm (/ (truncate tm) 60))))
+ (message "%s" info)
+ (if info
+ (message "%s: %d°%c, %s%d%% humidity, %d km/h%s, %.1f %S (%s)"
+ jao-metar-station
+ (cadr (assoc 'temperature info))
+ (cond
+ ((eq (cdr (assq 'temperature metar-units)) 'degC) ?C)
+ ((eq (cdr (assq 'temperature metar-units)) 'degF) ?F))
+ (if ph (format "%s " (jao-metar--phenomena ph)) "")
+ (cadr (assoc 'humidity info))
+ (truncate (car (plist-get (cdr (assoc 'wind info)) :speed)))
+ (jao-metar-wind-direction
+ " "
+ (car (plist-get (cdr (assoc 'wind info)) :direction)))
+ (cadr (assoc 'pressure info))
+ (cddr (assoc 'pressure info))
+ (format "%d mins old" mins))
+ (message "No weather information found, sorry."))))
+ :bind (("<f5>" . jao-metar)))
+ #+END_SRC
+***** winttr
+ #+begin_src emacs-lisp
+ (defun jao-weather (&optional winttr)
+ (interactive "P")
+ (if (not winttr)
+ (jao-metar)
+ (jao-afio--goto-scratch)
+ (if-let ((b (get-buffer "*wttr*")))
+ (progn (pop-to-buffer b)
+ (vterm-send-string "clear;curl wttr.in\n"))
+ (jao-exec-in-vterm "curl wttr.in" "*wttr*"))))
+ #+end_src
+***** keybidings
+ #+begin_src emacs-lisp
+ (global-set-key (kbd "<f5>") #'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)
+ ;; Try C-x C-q in an occur-ag buffer to activate
+ (use-package wgrep-ag :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
+ (require 'dired)
+ (require 'dired-x nil t)
+ (put 'dired-find-alternate-file 'disabled nil)
+
+ (setq dired-recursive-deletes 'top)
+ (setq dired-recursive-copies 'top)
+ (setq dired-listing-switches "-alhF --group-directories-first")
+ (setq dired-free-space-args "-Ph")
+ (setq ls-lisp-dirs-first t)
+ (setq dired-dwim-target t)
+ (setq wdired-create-parent-directories t)
+
+ (use-package find-dired
+ :init
+ (setq find-ls-option '("-print0 | xargs -0 ls -ld" . "-ld")))
+
+ (add-hook 'dired-mode-hook #'turn-on-gnus-dired-mode)
+ (define-key dired-mode-map [(control meta ?m)] #'gnus-dired-attach)
+ (define-key dired-mode-map (kbd "C-c C-r") #'wdired-change-to-wdired-mode)
+ #+END_SRC
+***** Dired packages
+ #+BEGIN_SRC emacs-lisp
+ ;; colorful dired
+ (use-package diredfl
+ :ensure t
+ :disabled t
+ :config (diredfl-global-mode 1))
+
+ (use-package dired-git-info
+ :ensure t
+ :bind (:map dired-mode-map (")" . dired-git-info-mode)))
+
+ #+END_SRC
+* General editing
+*** 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) ;; 'complete
+ (setq kill-read-only-ok t)
+ (setq view-read-only nil)
+ #+end_src
+*** Trailing whitespace
+ #+BEGIN_SRC emacs-lisp
+ (defvar jao-crappy-dirs-rx nil)
+
+ (defun jao-mostly-delete-tw ()
+ (interactive)
+ (unless (and jao-crappy-dirs-rx
+ (string-match jao-crappy-dirs-rx (buffer-file-name)))
+ (delete-trailing-whitespace)))
+
+ (add-hook 'write-file-functions 'delete-trailing-whitespace)
+ #+END_SRC
+*** Filling
+ Some variables to control where fci mode will be or is being used
+ #+BEGIN_SRC emacs-lisp
+ (setq fill-column 78)
+ (setq comment-auto-fill-only-comments nil)
+ (add-hook 'prog-mode-hook #'display-fill-column-indicator-mode)
+ (setq-default display-fill-column-indicator-column 80)
+ #+END_SRC
+*** Visible mode
+ #+begin_src emacs-lisp
+ (use-package visible-mode
+ :bind (("s-v" . visible-mode)))
+ #+end_src
+*** X Clipboard
+ #+BEGIN_SRC emacs-lisp
+ (setq x-select-enable-clipboard t
+ x-select-enable-primary nil
+ x-selection-timeout 100)
+ #+END_SRC
+*** Changes
+ #+BEGIN_SRC emacs-lisp
+ (use-package goto-chg
+ :ensure t
+ :bind (("C-." . goto-last-change)
+ ("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
+* Buffers
+*** autosave
+ #+BEGIN_SRC emacs-lisp
+ (setq auto-save-list-file-prefix "~/.emacs.d/auto-save-list/.saves-"
+ auto-save-no-message t)
+ #+END_SRC
+*** autoinsert
+ #+BEGIN_SRC emacs-lisp
+ (use-package autoinsert
+ :config
+ (setq auto-insert-directory "~/.emacs.d/autoinsert/"
+ auto-insert t
+ auto-insert-quert t)
+ (setf (alist-get 'html-mode auto-insert-alist nil t) nil))
+ (add-hook 'find-file-hooks #'auto-insert)
+ #+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)
+ `(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)
+ #+end_src
+*** misc
+ #+begin_src emacs-lisp
+ (require 'uniquify)
+ (setq uniquify-buffer-name-style 'forward)
+ (require 'paren)
+ (show-paren-mode t)
+ (require 'cus-edit)
+ (setq custom-buffer-done-function 'kill-buffer)
+
+ (setq cursor-in-non-selected-windows nil)
+
+ (blink-cursor-mode -1)
+ (transient-mark-mode -1)
+
+ (defun jao-kill-matching-buffers (rex)
+ (interactive (list (read-regexp "Filenames matching: "
+ (or (file-name-directory (buffer-file-name))
+ ""))))
+ (dolist (b (buffer-list))
+ (when (string-match rex (or (buffer-file-name b) ""))
+ (kill-buffer b))))
+ #+end_src
+*** same mode
+ #+begin_src emacs-lisp
+ (defun jao-buffer-same-mode (&optional mode)
+ (interactive)
+ (let* ((mode (or mode major-mode))
+ (pred `(lambda (b)
+ (let ((b (get-buffer (if (consp b) (car b) b))))
+ (and ;; (not (eq b ,(current-buffer)))
+ (eq ',mode (buffer-local-value 'major-mode b)))))))
+ (pop-to-buffer (read-buffer "Buffer: " nil t pred))))
+ (global-set-key (kbd "C-c C-b") #'jao-buffer-same-mode)
+ #+end_src
+*** buffer quit function (the triple ESC)
+ #+begin_src emacs-lisp
+ (setq buffer-quit-function (lambda () t))
+ #+end_src
+*** bm.el (per buffer bookmarks)
+ #+begin_src emacs-lisp
+ (use-package bm
+ :ensure t
+ :demand t
+
+ :init
+ ;; style (only fringe)
+ (setq bm-highlight-style 'bm-highlight-only-fringe)
+
+ ;; restore on load (even before you require bm)
+ (setq bm-restore-repository-on-load t)
+
+ :config
+ ;; Allow cross-buffer 'next'
+ (setq bm-cycle-all-buffers t)
+
+ ;; where to store persistant files
+ (setq bm-repository-file "~/.emacs.d/cache/bm-repository")
+
+ ;; save bookmarks
+ (setq-default bm-buffer-persistence t)
+
+ ;; Loading the repository from file when on start up.
+ ;; (add-hook 'after-init-hook 'bm-repository-load)
+
+ ;; Saving bookmarks
+ (add-hook 'kill-buffer-hook #'bm-buffer-save)
+
+ ;; Saving the repository to file when on exit.
+ ;; kill-buffer-hook is not called when Emacs is killed, so we
+ ;; must save all bookmarks first.
+ (add-hook 'kill-emacs-hook #'(lambda ()
+ (bm-buffer-save-all)
+ (bm-repository-save)))
+
+ ;; The `after-save-hook' is not necessary to use to achieve persistence,
+ ;; but it makes the bookmark data in repository more in sync with the file
+ ;; state.
+ (add-hook 'after-save-hook #'bm-buffer-save)
+
+ ;; Restoring bookmarks
+ (add-hook 'find-file-hooks #'bm-buffer-restore)
+ (add-hook 'after-revert-hook #'bm-buffer-restore)
+
+ ;; The `after-revert-hook' is not necessary to use to achieve persistence,
+ ;; but it makes the bookmark data in repository more in sync with the file
+ ;; state. This hook might cause trouble when using packages
+ ;; that automatically reverts the buffer (like vc after a check-in).
+ ;; This can easily be avoided if the package provides a hook that is
+ ;; called before the buffer is reverted (like `vc-before-checkin-hook').
+ ;; Then new bookmarks can be saved before the buffer is reverted.
+ ;; Make sure bookmarks is saved before check-in (and revert-buffer)
+ ;; (add-hook 'vc-before-checkin-hook #'bm-buffer-save)
+
+ :config (bm-repository-load)
+
+ :bind (("<f4>" . bm-next)
+ ("S-<f4>" . bm-previous)
+ ("C-<f4>" . bm-toggle)))
+ #+end_src
+* Windows
+*** Scrolling
+ #+BEGIN_SRC emacs-lisp
+ (setq scroll-preserve-screen-position 'always
+ scroll-conservatively most-positive-fixnum
+ scroll-margin 0
+ scroll-step 2)
+
+ (setq line-move-visual t)
+
+ (use-package iscroll :ensure t :diminish)
+ (with-eval-after-load "w3m"
+ (add-hook 'w3m-mode-hook #'iscroll-mode))
+ (with-eval-after-load "gnus-art"
+ (add-hook 'gnus-article-mode #'iscroll-mode))
+ #+END_SRC
+*** Splitting and switch
+ #+begin_src emacs-lisp
+ (setq split-height-threshold 80
+ split-width-threshold 144)
+
+ (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 visible in
+ ;; same frame
+
+ (global-set-key (kbd "C-x _") #'delete-other-windows-vertically)
+ #+end_src
+*** ace-window
+ #+begin_src emacs-lisp
+ (use-package ace-window
+ :ensure t
+ :init (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l)
+ aw-char-position 'left ;; 'top-left
+ aw-ignore-current nil
+ aw-dispatch-when-more-than 3 ;; 2
+ aw-leading-char-style 'path
+ aw-display-mode-overlay t
+ aw-scope 'frame)
+ :bind (("M-o" . ace-window)
+ ("M-O" . ace-swap-window)
+ ("C-x p" . ace-window)))
+ #+end_src
+*** first window and transient other window
+ #+begin_src emacs-lisp
+
+ (defvar jao-first-window--from nil)
+
+ (defun jao-first-window ()
+ (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))))
+
+ (global-set-key (kbd "s-a") #'jao-first-window)
+ (global-set-key (kbd "H-s-a") #'jao-first-window)
+
+ (defhydra jao-hydra-other-window (global-map "C-x")
+ ("o" other-window "next window")
+ ("p" (other-window -1) "previous window")
+ ("O" (other-window -1) "previous window"))
+
+ #+end_src
+*** winner mode
+ #+begin_src emacs-lisp
+ (winner-mode 1)
+ #+end_src
+* Frames
+*** Frame geometry
+ #+begin_src emacs-lisp
+ (defvar jao-frames-default-font "Hack-9")
+ (setq frame-resize-pixelwise nil)
+
+ ;;; modeline, toolbars and co.
+ (modify-all-frames-parameters
+ `((horizontal-scroll-bars . nil)
+ (vertical-scroll-bars . nil)
+ (scroll-bar-width . 11)
+ (menu-bar . nil)
+ (alpha . (,jao-frames-default-alpha ,jao-frames-default-alpha))
+ (font . ,jao-frames-default-font)))
+ #+end_src
+*** Frame layout, title, etc.
+ #+BEGIN_SRC emacs-lisp
+ (defvar jao-frames-fringe-mode 0)
+ (setq frame-title-format '("%b"))
+ (use-package fringe)
+ (fringe-mode jao-frames-fringe-mode)
+ (menu-bar-mode (jao-d-l 1 -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 ()
+ (interactive)
+ (let ((fw (frame-width)))
+ (delete-other-windows)
+ (cond ((> fw 242)
+ (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)
+ (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)
+ (add-hook 'jao-afio-switch-hook #'jao-minibuffer-refresh t)
+ (jao-minibuffer-add-variable '(jao-current--frame-id) 100)
+ #+end_src
+* Writing and writing modes
+*** Org mode
+ #+begin_src emacs-lisp
+ (jao-load-org "org")
+ (jao-load-org "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"))
+ ;;; 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)))
+
+ ;; text mode, autoinserts and write hooks
+ ;;; misc
+ (setq default-major-mode 'text-mode)
+
+ (add-hook 'text-mode-hook 'turn-on-auto-fill)
+ (add-hook 'write-file-functions 'copyright-update)
+ #+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)
+ ("C-c D" . jao-word-definition-lookup)))
+
+ (setq ispell-personal-dictionary
+ (expand-file-name "~/.emacs.d/ispell.dict"))
+
+ #+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 (progn (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
+ (jao-d-l
+ '((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"))
+ (add-hook 'LaTeX-mode-hook 'turn-on-reftex)
+ (setq LaTeX-command "latex -shell-escape")
+ (setq LaTeX-biblatex-use-Biber t)
+ (setq bibtex-dialect 'biblatex)
+ (jao-when-darwin
+ (jao-exec-path "/usr/local/texlive/2016/bin/x86_64-darwin"))))
+
+ ;; (use-package ebib
+ ;; :ensure t
+ ;; :config (setq ebib-bibtex-dialect 'biblatex))
+
+ ;; for M-x biblio-lookup
+ ;; (use-package biblio :ensure t)
+ #+END_SRC
+* PDFs
+ - ~M-x doc-view-presentation~
+*** pdf-tools &co.
+ #+begin_src emacs-lisp
+ (use-package pdf-tools
+ :ensure t
+ :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))))
+
+ (require 'pdf-tools)
+
+ (defun jao--refocus (&rest _i) (other-window 1) (other-window -1))
+
+ (advice-add 'pdf-view-previous-line-or-previous-page :after #'jao--refocus)
+ (advice-add 'pdf-view-scroll-up-or-next-page :after #'jao--refocus)
+ (advice-add 'pdf-view-scroll-down-or-previous-page :after #'jao--refocus)
+
+ ;; (advice-remove 'pdf-view-previous-line-or-previous-page #'jao--refocus)
+ ;; (advice-remove 'pdf-view-scroll-up-or-next-page #'jao--refocus)
+ ;; (advice-remove 'pdf-view-scroll-down-or-previous-page #'jao--refocus)
+
+ (use-package saveplace-pdf-view
+ :ensure t)
+
+ (require 'saveplace-pdf-view)
+ #+end_src
+*** open pdf and session
+ #+BEGIN_SRC emacs-lisp
+ (when (require 'jao-doc-view nil t)
+ (jao-doc-view-start-session-timer))
+
+ (defvar jao-open-doc-fun 'jao-find-or-open)
+
+ (defun jao-find-or-open (file)
+ (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))))
+
+ (defun jao-open-doc (file)
+ (message "Opening %s" file)
+ (funcall jao-open-doc-fun file))
+
+ (defun jao-open-pdf-session ()
+ (interactive)
+ (dolist (doc (jao-doc-view-session))
+ (when (and (file-exists-p doc) (y-or-n-p (format "Open %s? " doc)))
+ (jao-find-or-open doc))))
+ #+END_SRC
+* Email
+*** message mode
+***** Customization
+ #+begin_src emacs-lisp
+ (require 'message)
+ (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 jao-mails-regexp)
+ (setq message-dont-reply-to-names
+ (format "%s\\|%s" jao-mails-regexp (regexp-opt '("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: <http://jao.io/>\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
+***** Encryption
+ #+BEGIN_SRC emacs-lisp
+ (require 'gnutls)
+ ;; 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"/home/jao/.emacs.d/gnus/Mail/certs/")
+
+ ;; Tells Gnus to inline the part
+ (eval-after-load "mm-decode"
+ '(add-to-list 'mm-inlined-types "application/pgp$"))
+ ;; Tells Gnus how to display the part when it is requested
+ (eval-after-load "mm-decode"
+ '(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.
+ (eval-after-load "mm-decode"
+ '(add-to-list 'mm-automatic-display "application/pgp$"))
+ ;; But don't display the signatures, please.
+ (eval-after-load "mm-decode"
+ (quote (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)))
+
+ ;; (define-key message-mode-map [f7] 'mml-secure-message-sign-pgp)
+ (define-key message-mode-map [f8] 'mml-secure-message-encrypt-pgp)
+ #+END_SRC
+***** Attach image to message
+ Use ~C-c C-p~ in message-mode.
+***** Org-mime
+ #+BEGIN_SRC emacs-lisp
+ (use-package org-mime :ensure t
+ :init (setq org-mime-beautify-quoted-mail t)
+ :bind (:map message-mode-map ("C-c M-o" . org-mime-htmlize)))
+ #+END_SRC
+***** 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 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 (message-fetch-field "Gcc")
+ (not (y-or-n-p "Archive? ")))
+ (message-remove-header "Gcc")))))
+
+ (defun jao-message-toggle-gcc ()
+ "Insert or remove the \"Gcc\" header."
+ (interactive)
+ (save-excursion
+ (save-restriction
+ (message-narrow-to-headers)
+ (if (message-fetch-field "Gcc")
+ (message-remove-header "Gcc")
+ (gnus-inews-insert-gcc)))))
+
+ ;; (define-key message-mode-map [(f6)] 'jao-message-toggle-gcc)
+ #+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
+ (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)
+ (eval-after-load "gnus-sum"
+ '(define-key gnus-summary-save-map "-" 'gnus/randomsig-summary-read-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-attachment
+ jao-message-check-recipient
+ jao-message-maybe-sign))
+ (add-hook 'message-send-hook h))
+ #+END_SRC
+*** directories
+ #+BEGIN_SRC emacs-lisp
+ (setq gnus-home-directory "~/.emacs.d/gnus"
+ gnus-directory gnus-home-directory)
+
+ (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-auto-save-directory (jao-gnus-dir "Mail/drafts/")
+ 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
+*** sendmail/smtp
+ #+BEGIN_SRC emacs-lisp
+ (require 'smtpmail)
+
+ (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 smtpmail-auth-supported nil) ;; (cram-md5 plain login)
+ (setq smtpmail-smtp-server "127.0.0.1")
+ (setq smtpmail-smtp-service 25))
+
+ ;; (jao-sendmail-gmail)
+ (jao-sendmail-local)
+ #+END_SRC
+*** Gnus
+ #+BEGIN_SRC emacs-lisp
+ (defalias 'jao-open-gnus-frame 'jao-afio--goto-gnus)
+
+ (setq gnus-init-file (jao-maybe-tangle "gnus"))
+
+ ;;;;; close gnus when closing emacs, but ask when exiting
+ (setq gnus-interactive-exit t)
+
+ (defun jao-gnus-started-hook ()
+ "use that hook for its purpose "
+ (add-hook 'before-kill-emacs-hook 'gnus-group-exit))
+
+ (add-hook 'gnus-started-hook 'jao-gnus-started-hook)
+
+ (defun jao-gnus-after-exiting-hook ()
+ "how about removing this hook when exiting gnus in the conventional way?"
+ (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
+*** mbox listing
+ #+begin_src emacs-lisp
+ (defun jao-list-mailboxes (base)
+ (let ((dir (expand-file-name base "~/var/mail")))
+ (cl-remove-if (lambda (x)
+ (member x '("." ".." "sent" "inbox" "trash")))
+ (directory-files dir))))
+ #+end_src
+*** notmuch
+***** Package configuration
+ #+BEGIN_SRC emacs-lisp
+ (defun jao-notmuch--mboxes-search (box)
+ (mapcar (lambda (m)
+ `(:name ,m
+ :search-type tree
+ :query ,(format "folder:%s/%s and tag:unread"
+ box m)))
+ (jao-list-mailboxes box)))
+ (when (file-exists-p "~/var/mail")
+ (use-package notmuch
+ :ensure t
+ :init
+ (setq notmuch-fcc-dirs '(("jao@bigml.com" . "bigml/sent")
+ (".*" . "jao/sent"))
+ notmuch-tagging-keys
+ '(("a" notmuch-archive-tags "Archive")
+ ("u" notmuch-show-mark-read-tags "Mark read")
+ ("f" ("+flagged") "Flag")
+ ("s" ("+spam" "-inbox") "Mark as spam")
+ ("d" ("+deleted" "-inbox") "Delete"))
+ notmuch-saved-searches
+ `((:name "jao" :key "j"
+ :query "folder:jao/inbox"
+ :search-type tree
+ :count-query "folder:jao/inbox and tag:unread")
+ (:name "bigml" :key "b"
+ :count-query "folder:bigml/inbox and tag:unread"
+ :search-type tree
+ :query "folder:bigml/inbox")
+ ,@(jao-notmuch--mboxes-search "jao")
+ ,@(jao-notmuch--mboxes-search "bigml")
+ ,@(jao-notmuch--mboxes-search "feeds")
+ (:name "unread" :query "tag:unread" :key "u" :search-type tree)
+ (:name "sent" :query "tag:sent" :key "t" :search-type tree)
+ (:name "drafts" :query "tag:draft" :key "d" :search-type tree)
+ (:name "all mail" :query "*" :count-query "tag:unread" :key "a"
+ :search-type tree))
+ notmuch-hello-sections
+ '(notmuch-hello-insert-header
+ notmuch-hello-insert-saved-searches
+ notmuch-hello-insert-alltags
+ notmuch-hello-insert-footer))
+ :bind (:map notmuch-show-mode-map
+ ("C-c C-c" . jao-notmuch-goto-message-in-gnus))))
+ #+END_SRC
+***** Go notmuch -> gnus (and use notmuch as default search)
+ #+begin_src emacs-lisp
+ (defun jao-notmuch-file-to-group (file)
+ "Calculate the Gnus group name from the given file name.
+
+ Example:
+
+ IN: /home/jao/var/mail/jao/foo/cur/1259184569.M4818P3384.localhost,W=6921:2,S
+ OUT: nnimap:jao/foo
+
+ IN: /home/jao/var/mail/gmane/foo/bar/100
+ OUT: nntp+localhost:gmane.foo.bar
+
+ IN: /home/jao/var/mail/bigml/cur/1259176906.M17483P24679.localhost,W=2488:2,S
+ OUT:nnimap:bigml/inbox"
+ (let* ((g (directory-file-name (file-name-directory file)))
+ (g (replace-regexp-in-string "/home/jao/var/mail/" "" g))
+ (nntp (string-match-p "^\\(gmane\\|gwene\\)/" g))
+ (g (if nntp
+ (concat "nntp+localhost:" g)
+ (replace-regexp-in-string "^\\([^/]+\\)/" "nnimap:\\1/"
+ (file-name-directory g) t)))
+ (g (if nntp (replace-regexp-in-string "/" "." g) g))
+ (g (replace-regexp-in-string "[/.]$" "" g)))
+ (cond ((string-match ":$" g) (concat g "inbox"))
+ (nntp g)
+ (t (replace-regexp-in-string ":\\." ":" g)))))
+
+ (defun jao-notmuch-goto-message-in-gnus ()
+ "Open a summary buffer containing the current notmuch article."
+ (interactive)
+ (let ((group (jao-notmuch-file-to-group (notmuch-show-get-filename)))
+ (message-id (replace-regexp-in-string "^id:"
+ ""
+ (notmuch-show-get-message-id))))
+ (if (and group message-id)
+ (org-gnus-follow-link group message-id)
+ (message "Couldn't get relevant infos for switching to Gnus."))))
+ #+end_src
+*** visual message fill column
+ #+begin_src emacs-lisp
+ (use-package visual-fill-column
+ :ensure t
+ :init
+ (setq-default fringes-outside-margins nil
+ visual-fill-column-width 80
+ visual-fill-column-fringes-outside-margins nil)
+ (setq gnus-treat-fill-long-lines nil)
+ :config
+ (setq split-window-preferred-function
+ #'visual-fill-column-split-window-sensibly)
+ :bind ((:map ctl-x-map ("M-f" . visual-fill-column-mode))))
+
+ ;; (add-hook 'gnus-article-mode-hook #'visual-line-mode)
+ ;; (add-hook 'gnus-article-mode-hook #'visual-fill-column-mode)
+
+ ;; Name may be misleading, it does not set `fill-column' (which
+ ;; is still used by M-q) in `message-mode', but enables
+ ;; auto-filling on a given column.
+ ;; (setq message-fill-column nil)
+ ;; FIXME: There is no proper way to make fill commands to unfill.
+ ;; (add-hook 'message-mode-hook
+ ;; (lambda ()
+ ;; (setq-local fill-column most-positive-fixnum)))
+ ;; (when-require 'visual-fill-column
+ ;; (add-hook 'message-mode-hook #'visual-fill-column-mode))
+ #+end_src
+*** mail this buffer
+ #+BEGIN_SRC emacs-lisp
+ (defun jao-mail-this-file ()
+ (interactive)
+ (let ((file (buffer-file-name)))
+ (compose-mail)
+ (save-excursion
+ (message-goto-subject)
+ (insert (file-name-nondirectory file))
+ (message-goto-body)
+ (newline 2)
+ (mml-attach-file file (mm-default-file-encoding file)))))
+ #+END_SRC
+*** mailcap
+ #+BEGIN_SRC emacs-lisp
+ (require 'mailcap)
+
+ (add-to-list 'mailcap-mime-extensions '(".JPEG" . "image/jpeg"))
+ (add-to-list 'mailcap-mime-extensions '(".JPG" . "image/jpeg"))
+
+ (let* ((apps (cdr (assoc "application" mailcap-mime-data)))
+ (apps (cl-remove-if (lambda (x) (string= (car x) "pdf")) apps)))
+ (setcdr (assoc "application" mailcap-mime-data) apps)
+ (mailcap-parse-mailcaps nil t))
+ #+END_SRC
+*** frm
+ #+begin_src emacs-lisp
+ (use-package jao-frm
+ :init (setq jao-frm-mail-command 'jao-open-gnus-frame))
+
+ (defun jao-frm--formatter (mbox n)
+ (apply #'format "%s/%s: %s" `(,@(last (split-string mbox "/") 2) ,n)))
+
+ (defun jao-frm--show ()
+ (interactive)
+ (jao-frm-show-mail-numbers #'jao-frm--formatter))
+
+ (global-set-key [(f12)] 'jao-frm--show)
+ (global-set-key [(f8)] 'jao-frm)
+
+ #+end_src
+*** maildirs
+ #+begin_src emacs-lisp
+ (defvar jao-maildir-maildirs nil)
+ (defvar jao-maildir-tracked-maildirs nil)
+ (use-package jao-maildir
+ :config
+ (defun jao-maildir--ensure-counts ()
+ (when gnus-newsgroup-name
+ (when (string-match "^nnimap.*:\\(.+\\)" gnus-newsgroup-name)
+ (let ((mbox (format "/home/jao/var/mail/%s"
+ (match-string 1 gnus-newsgroup-name))))
+ (jao-maildir-update-info-string mbox)))))
+ (with-eval-after-load "gnus-sum"
+ (add-hook 'gnus-exit-group-hook #'jao-maildir--ensure-counts)))
+
+ (jao-maildir-setup jao-maildir-maildirs
+ jao-maildir-tracked-maildirs
+ -20)
+ #+end_src
+*** BBDB
+ #+BEGIN_SRC emacs-lisp
+ (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)
+ (eval-after-load "gnus-sum"
+ '(progn
+ (define-key gnus-summary-mode-map ":" 'bbdb-mua-annotate-sender)
+ (define-key gnus-summary-mode-map ";" 'bbdb-mua-annotate-recipients))))
+
+ (require 'bbdb)
+ (bbdb-initialize 'gnus 'message 'pgp 'mail)
+ (bbdb-mua-auto-update-init 'gnus)
+ (setq bbdb-file (expand-file-name "bbdb" gnus-home-directory))
+
+ #+END_SRC
+* Browsing
+ #+BEGIN_SRC emacs-lisp
+ (require 'browse-url)
+
+ (defun jao-url-around-point ()
+ (or (and (fboundp 'w3m-anchor) (w3m-anchor))
+ (shr-url-at-point nil)
+ (ffap-url-at-point)
+ (thing-at-point 'url)))
+
+ (defun jao-browse-with-external-browser (&rest url)
+ (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-p (fboundp 'jao-sway-firefox))
+ (jao-sway-firefox))
+ (browse-url-generic url))))
+
+ (setq browse-url-generic-program (jao-d-l "open" "~/bin/firehog"))
+ (setq jao-browse-url-function 'jao-w3m-browse-url)
+ (setq jao-browse-url-external-function 'jao-browse-with-external-browser)
+
+ (defvar jao-browse-download-dir jao-sink-dir)
+ (defvar jao-browse-doc-use-emacs-p t)
+
+ (with-eval-after-load "embark"
+ (global-set-key (kbd "C-c M")
+ (lambda () (interactive) (message "Use embark!")))
+ (define-key embark-url-map "m" 'jao-browse-with-external-browser))
+
+ (with-eval-after-load "jao-dap"
+ (global-set-key (kbd "C-c M")
+ (lambda () (interactive) (message "Use dap!")))
+ (define-key dap-url-map "m" #'jao-browse-with-external-browser))
+ #+END_SRC
+*** Helpers
+ #+BEGIN_SRC emacs-lisp
+ (defun jao--fln (url)
+ (shell-quote-argument
+ (if (string-match "^[^:]*:/*?\\(/?[^/].*\\)" url)
+ (match-string-no-properties 1 url)
+ url)))
+
+ (defun jao--run (cmd url &rest flags)
+ (start-process-shell-command
+ cmd nil (format "%s %s %s"
+ cmd
+ (or (mapconcat 'shell-quote-argument flags " ") "")
+ (jao--fln url))))
+
+ (defun jao--view-pdf/ps (url)
+ (when (not window-system)
+ (select-frame (make-frame-on-display (getenv "DISPLAY")
+ '((height . 55)))))
+ (find-file (jao--fln url)))
+
+ (defun jao-wget--get-title (filename)
+ (let ((fn (file-name-sans-extension (file-name-nondirectory filename))))
+ (subst-char-in-string ?- ? (capitalize fn))))
+
+ (defun jao--url-prompt ()
+ (let* ((def (or (w3m-anchor)
+ (thing-at-point 'url)
+ w3m-current-url))
+ (prompt (concat "URL"
+ (if def (format " (%s): " def) ": "))))
+ (read-string prompt nil nil def)))
+
+ (defvar jao-browse-url-wget-exts
+ '("ps" "pdf" "dvi" "djvu" "zip" "gz" "tgz" "mp4" "mp3" "flv")
+ "Extensions of HTTP(S) URLs to be downloaded using wget.")
+
+ (defun jao-see-current-file ()
+ (interactive)
+ (jao--run (jao-d-l "open" "see") (buffer-file-name)))
+ #+END_SRC
+*** Downloaders
+***** Using wget
+ #+BEGIN_SRC emacs-lisp
+ (defun jao-wget--regexp ()
+ (concat "^http[s]?://.+\\(\\."
+ (mapconcat 'identity jao-browse-url-wget-exts "\\|\\.")
+ "\\)\\'"))
+
+ (defun jao-wget (url &optional user pwd &rest ignored)
+ "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-browse-download-dir nil nil def)))
+ (src-url w3m-current-url)
+ (auth (when (and user pwd) `(,(format "--http-user=%s" user)
+ ,(format "--http-password=%s" pwd)))))
+ (switch-to-buffer-other-window (get-buffer-create "*downloads*"))
+ (setq jao-browse-download-dir (file-name-directory dest))
+ (erase-buffer)
+ (when (equalp (file-name-directory dest)
+ (file-name-as-directory jao-sink-dir))
+ (kill-new (format "[[doc:%s][%s]] (from [[%s][here]])"
+ (file-name-nondirectory dest)
+ (read-string "Title: " (jao-wget--get-title dest))
+ (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
+***** See/open
+ #+BEGIN_SRC emacs-lisp
+ (defun jao--see (url &rest r)
+ (jao--run "see" url))
+
+ (defun jao--open (url &rest r) (jao--remote-run url "open"))
+ #+END_SRC
+***** Video downloads and playing
+ #+BEGIN_SRC emacs-lisp
+ (defvar jao-video--url-rx
+ (format "^https?://\\(?:www\\.\\)?%s/.+"
+ (regexp-opt '("youtube.com" "blip.tv" "vimeo.com" "infoq.com")
+ t)))
+
+ (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
+*** File extensions
+ #+BEGIN_SRC emacs-lisp
+ (defun jao--make-file-rx (exts)
+ (format "file:/?/?.+\\.%s$" (regexp-opt exts)))
+ (setq jao--music-exts (jao--make-file-rx '("mp3" "m4a" "ogg")))
+ (setq jao--see-exts
+ (jao--make-file-rx '("jpg" "jpeg" "png" "mov" "wmv" "avi" "mp4")))
+ (setq jao--doc-exts
+ (jao--make-file-rx '("ps" "ps.gz" "pdf" "dvi" "djvu" "chm")))
+ #+END_SRC
+*** eww
+ #+BEGIN_SRC emacs-lisp
+ (setq eww-download-directory "~/var/download")
+ #+END_SRC
+*** Browse URL
+ #+BEGIN_SRC emacs-lisp
+ (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))
+ (if window-system
+ (jao-open-doc file)
+ (set-process-sentinel (jao--run "see" file)
+ (jao--browse-sentinel url))))))
+
+ (defun jao--browse-music (url &rest r)
+ (when (string-match "music:\\(.+\\)" url)
+ (let ((path (match-string-no-properties 1 url)))
+ (cond ((file-directory-p path) (emms-play-directory-tree path))
+ ((file-exists-p path) (emms-play-file path))))))
+
+ (setq browse-url-handlers
+ (jao-d-l
+ `(("^file://?.+\\.htm[l]?\\'" . ,jao-browse-url-function)
+ ("^file://?" . (lambda (url &rest r)
+ (find-file-other-window (jao--fln url))))
+ ("^info://" . (lambda (url &rest r)
+ (info-other-window (jao--fln url))))
+ ("^https?://.+" . ,jao-browse-url-function)
+ ("." . jao--open))
+
+ `((,jao--doc-exts . jao--browse-doc)
+ (,jao--see-exts . jao--see)
+ ("^file://?.+\\.htm[l]?\\'" . ,jao-browse-url-function)
+ ("^file://?" . (lambda (url &rest r)
+ (find-file-other-window (jao--fln url))))
+ ("^info://" . (lambda (url &rest r)
+ (info-other-window (jao--fln url))))
+ ("^https?://bigml\\.slack\\..*" . browse-url-generic)
+ ("^https?://github\\.com/.*issues.*" .
+ ,jao-browse-url-external-function)
+ ("^https?://.*\\.gotomeeting\\.com\\.*" . browse-url-chrome)
+ ("^https?://meet\\.google\\.com\\.*" .
+ ,jao-browse-url-external-function)
+ (,(jao-wget--regexp) . jao-wget)
+ ("^mailto:.+" . browse-url-mail)
+ (,jao-video--url-rx . jao-maybe-view-video)
+ ("." . ,jao-browse-url-function))))
+
+ ;; (setq browse-url-browser-function 'browse-url-firefox)
+ ;; (setq browse-url-handlers nil)
+
+ (setq browse-url-browser-function
+ (if (< emacs-major-version 28)
+ browse-url-handlers
+ jao-browse-url-function))
+
+ #+END_SRC
+*** Emacs-w3m
+***** Custom variables:
+ #+BEGIN_SRC emacs-lisp
+ (use-package w3m
+ :ensure t
+ :custom ((w3m-key-binding 'info)
+ (w3m-display-mode 'dual-pane))
+ :init
+ (setq w3m-add-user-agent nil
+ w3m-command (jao-d-l "/usr/local/bin/w3m" "w3m")
+ w3m-confirm-leaving-secure-page nil
+ w3m-cookie-accept-bad-cookies t
+ w3m-use-tab nil
+ w3m-display-mode 'dual-pane
+ w3m-do-cleanup-temp-files t
+ w3m-doc-view-content-types ()
+ w3m-fill-column 110
+ w3m-form-input-textarea-buffer-lines 40
+ w3m-history-minimize-in-new-session t
+ w3m-history-reuse-history-elements nil
+ w3m-icon-directory (expand-file-name "w3m/icons/" local-lisp-dir)
+ w3m-image-no-idle-timer t
+ w3m-make-new-session t
+ w3m-profile-directory "~/.w3m"
+ w3m-redisplay-pages-automatically-p nil
+ w3m-safe-url-regexp nil
+ w3m-search-default-engine "duckduckgo" ; "google-en"
+ w3m-select-buffer-horizontal-window nil
+ w3m-select-buffer-window-ratio '(20 . 40)
+ w3m-session-load-last-sessions t
+ w3m-session-load-crashed-sessions 'ask
+ w3m-show-graphic-icons-in-header-line t
+ w3m-space-before-favicon "|"
+ w3m-tab-separator ""
+ w3m-use-cookies t
+ w3m-use-favicon nil
+ w3m-use-header-line nil
+ w3m-use-refresh nil)
+ :config (defalias 'jao-goto-w3m-frame 'jao-afio--goto-w3m)
+ :bind (:map w3m-mode-map (("C-c C-@" . tracking-next-buffer)
+ ("C-c C-SPC" . tracking-next-buffer))))
+ (require 'w3m)
+ #+END_SRC
+***** Coding systems and content type
+ #+begin_src emacs-lisp
+ (mapc (lambda (v) (set v 'utf-8))
+ '(w3m-default-coding-system
+ w3m-bookmark-file-coding-system
+ w3m-coding-system
+ w3m-file-coding-system
+ w3m-file-name-coding-system
+ w3m-terminal-coding-system))
+
+ (jao-when-linux
+ (setq w3m-content-type-alist
+ '(("text/plain" "\\.\\(?:txt\\|tex\\|el\\)\\'" nil nil)
+ ("text/html" "\\.s?html?\\'" jao-browse-with-external-browser nil)
+ ("text/html" "." jao-browse-with-external-browser nil)
+ ("text/sgml" "\\.sgml?\\'" nil "text/plain")
+ ("text/xml" "\\.xml\\'" nil "text/plain")
+ ("image/jpeg" "\\.jpe?g\\'" ("emacsclient" file) nil)
+ ("image/png" "\\.png\\'" ("emacsclient" file) nil)
+ ("image/gif" "\\.gif\\'" ("emacsclient" file) nil)
+ ("image/tiff" "\\.tif?f\\'" ("emacsclient" file) nil)
+ ("image/x-xwd" "\\.xwd\\'" ("emacsclient" file) nil)
+ ("image/x-xbm" "\\.xbm\\'" ("emacsclient" file) nil)
+ ("image/x-xpm" "\\.xpm\\'" ("emacsclient" file) nil)
+ ("image/x-bmp" "\\.bmp\\'" ("emacsclient" file) nil)
+ ("video/mpeg" "\\.mpe?g\\'" jao-maybe-view-video nil)
+ ("video/quicktime" "\\.mov\\'" jao-maybe-view-video nil)
+ ("video/*" "\\.mpe?g\\'" jao-maybe-view-video nil)
+ ("application/dvi" "\\.dvi\\'" ("xdvi" file) nil)
+ ("application/postscript" "\\.e?ps\\'" ("/usr/bin/see" file) nil)
+ ("application/pdf" "\\.pdf\\'" ("viewpdf.sh" file) nil)
+ ("application/xml" "\\.xml\\'" nil w3m-detect-xml-type)
+ ("application/rdf+xml" "\\.rdf\\'" nil "text/plain")
+ ("application/rss+xml" "\\.rss\\'" nil "text/plain")
+ ("application/xhtml+xml" nil nil "text/html")
+ ("unknown" nil nil "text/plain"))))
+ #+end_src
+***** Filters
+ #+begin_src emacs-lisp
+ (setq w3m-use-filter t
+ w3m-filter-configuration
+ '((t "Strip Google's click-tracking"
+ "\\`https?://[a-z]+\\.google\\."
+ w3m-filter-google-click-tracking)
+ (t "Align table columns vertically to shrink the table width in Google"
+ "\\`http://\\(www\\|images\\|news\\|maps\\|groups\\)\\.google\\."
+ w3m-filter-google-shrink-table-width)
+ (t "Add name anchors that w3m can handle in all pages"
+ ""
+ w3m-filter-add-name-anchors)
+ (t "Substitute disabled attr with readonly attr in forms"
+ ""
+ w3m-filter-subst-disabled-with-readonly)
+ (t "Filter top and bottom cruft for stackexchange.com"
+ "\\`https://\\(?:[0-9A-Za-z_~-]+\\.\\)*stackexchange\\.com\\(?:\\'\\|/\\)"
+ w3m-filter-stackexchange)
+ (t "filter for github.com repository main page"
+ "\\`http[s]?://github\\.com/[^/]+/[^/]+[/]?\\'"
+ w3m-filter-github-repo-main-page)
+ (t "xkcd filter" "\\`http[s]?://xkcd.com/" w3m-filter-xkcd)
+ (nil "Prefer a lazy image specified with data-src= in img tags"
+ ""
+ w3m-filter-prefer-lazy-images)))
+ #+end_src
+***** Symbols and drawing
+ #+BEGIN_SRC emacs-lisp
+ (setq w3m-default-symbol '("┼" "├" "┬" "┌" "┤" "│" "┐" ""
+ "┴" "└" "─" "" "┘" "" "" ""
+ "┼" "├" "┬" "┌" "┤" "│" "┐" ""
+ "┴" "└" "─" "" "┘" "" "" ""
+ "•" "•" "•" "•" " • "
+ "•" "•" "•" "•" "•" "•" "•" "•"
+ "≪ ↑ ↓ "))
+ (setq w3m-symbol w3m-default-symbol)
+ (setq-default w3m-use-symbol t)
+ #+END_SRC
+***** Browse url (with unique buffer support)
+ #+BEGIN_SRC emacs-lisp
+ (defvar jao-w3m-unique-buffer-rxs
+ '("^file:.*hyperspec/.*"
+ "^file:.*/usr/\\(local/\\)?share/doc/racket/.*"
+ "^file:///home/jao/src/racket/doc/.*"
+ "^file:.*/usr/local/plt/doc/.*"
+ "^file:.*/\\(usr/share/doc/ghc6-doc/html/.*\\|\\.cabal/\\).*"
+ "^file:.*/doc/hyperspec/.*"
+ ("/tmp/jde_meta\.html" . "^file:.*/java/docs/.*")))
+
+ (defun jao-w3m-url-matcher (url)
+ (let* ((url (w3m-canonicalize-url url))
+ (urx (format "^%s$" url)))
+ (dolist (rx jao-w3m-unique-buffer-rxs)
+ (let ((m (if (consp rx) (car rx) rx))
+ (r (if (consp rx) (cdr rx) rx)))
+ (when (string-match m url) (setq urx r))))
+ urx))
+
+ (defun jao-w3m-find-url (url)
+ ;; (message "finding %s" url)
+ (let ((urx (jao-w3m-url-matcher url))
+ (buffers (w3m-list-buffers))
+ (found nil))
+ (save-current-buffer
+ (while (not (or found (null buffers)))
+ (let ((b (car buffers)))
+ (set-buffer b)
+ (if (and w3m-current-url
+ (string-match urx
+ (w3m-canonicalize-url w3m-current-url)))
+ (setq found b)
+ (setq buffers (cdr buffers))))))
+ (when found
+ (let ((pop-up-windows nil)
+ (display-buffer-reuse-frames nil))
+ (pop-to-buffer found)))
+ found))
+
+ (defun jao-w3m-browse-url (url &rest r)
+ "Browse URL using w3m.
+
+ If a frame running w3m already exists, reuses it creating
+ a new tab. If the URL is already open, though, the tab
+ containing it is selected."
+ (jao-goto-w3m-frame)
+ (select-window (frame-first-window))
+ (or (jao-w3m-find-url url)
+ (w3m-goto-url-new-session url)))
+
+
+
+ (defun jao-w3m-do-browse ()
+ "Use the generic browse-url with URL at point."
+ (interactive)
+ (let ((uri (or (w3m-anchor) (w3m-image) w3m-current-url)))
+ (browse-url uri)))
+
+
+
+ (defun jao-w3m-download (arg)
+ (interactive "P")
+ (jao-download (w3m-anchor) arg))
+
+ (setq w3m-goto-article-function 'jao-w3m-browse-url)
+ #+END_SRC
+***** Subscribe rss using r2e
+ #+begin_src emacs-lisp
+ (defconst jao-w3m-rss-rx
+ (concat "type=\"application/\\(?:atom\\|rss\\)\\+xml\" +"
+ "\\(?:title=\"\\([^\n\"]+\\)\" +\\)?href=\"\\([^\n\"]+\\)\""))
+
+ (defun jao-w3m-find-rss ()
+ (when (eq major-mode 'w3m-mode)
+ (save-excursion
+ (w3m-view-source)
+ (goto-char (point-min))
+ (let* ((m (re-search-forward jao-w3m-rss-rx nil t))
+ (url (and m (match-string 2)))
+ (title (and m (match-string 1))))
+ (w3m-view-source)
+ (cons url (or title ""))))))
+
+ (defun jao-w3m-subscribe-rss ()
+ (interactive)
+ (let* ((url (or (w3m-anchor) (ffap-url-at-point)))
+ (url+title (if url (cons url "") (jao-w3m-find-rss)))
+ (url (car url+title))
+ (title (cdr url+title)))
+ (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 "Name: " title))
+ (cat (completing-read "Category: "
+ (jao-list-mailboxes "feeds")
+ nil t)))
+ (shell-command
+ (format "r2e add %s %s jao+feeds_%s@localhost && r2e run %s"
+ name url cat name)))))
+ (message "No feeds found"))))
+ #+end_src
+***** Tweeting and tooting
+ #+BEGIN_SRC emacs-lisp
+ (defun jao-w3m--toot-text (from to title)
+ (let ((a (or (jao-url-around-point)
+ w3m-current-url
+ (error "No URL to tweet!")))
+ (txt (or title
+ (if (and from to)
+ (buffer-substring from to)
+ (w3m-current-title)))))
+ (if (string-empty-p (or txt ""))
+ a
+ (format "'%s' -- %s" txt a))))
+
+ (defun jao-w3m-tweet (&optional from to title buff)
+ (interactive (when (use-region-p) (list (region-beginning) (region-end))))
+ (let ((txt (jao-w3m--toot-text from to title)))
+ (pop-to-buffer (or buff "#twitter_jaotwits"))
+ (goto-char (point-max))
+ (insert "post " txt)))
+
+ (defun jao-w3m-toot (&optional from to title)
+ (interactive (when (use-region-p) (list (region-beginning) (region-end))))
+ (jao-w3m-tweet from to title "#mastodon"))
+ #+END_SRC
+***** Cookies
+ #+begin_src emacs-lisp
+ ;; for reddit http://i.reddit.com is non-js friendly
+ (setq w3m-cookie-reject-domains '(".")
+ w3m-cookie-accept-domains '(".github.com"
+ ".librarything.com"
+ ".goodreads.com"
+ ".sr.ht"
+ ".gnu.org"
+ ".codeberg.org"
+ "codeberg.org"
+ ".bookshop.org"
+ ".reddit.com"))
+ #+end_src
+***** Email
+ #+BEGIN_SRC emacs-lisp
+ (defun jao-w3m-gmail-mark-all (unmark)
+ (interactive "P")
+ (goto-char (point-min))
+ (when (search-forward (if unmark "[*]" "[ ]") nil t)
+ (backward-char 4)
+ (w3m-form-goto-next-field)
+ (while (looking-at (if unmark "\\*\\]" " \\]"))
+ (w3m-view-this-url)
+ (w3m-form-goto-next-field))))
+
+ (defun jao-w3m-mail-page ()
+ (interactive)
+ (let ((wb (w3m-alive-p)))
+ (when wb
+ (compose-mail nil
+ (read-string "Subject: " (w3m-buffer-title wb)))
+ (message-goto-body)
+ (insert "\n\n<" (with-current-buffer wb w3m-current-url) ">\n")
+ (message-goto-to))))
+ #+END_SRC
+***** Proxies
+ #+BEGIN_SRC emacs-lisp
+ (defun jao-w3m-activate-proxy ()
+ (interactive)
+ (setq w3m-command-arguments
+ (nconc w3m-command-arguments
+ '("-o" "http_proxy=http://localhost:8888"))))
+
+ (defun jao-w3m-deactivate-proxy ()
+ (interactive)
+ (setq w3m-command-arguments (cdr w3m-command-arguments)))
+ #+END_SRC
+***** Switch buffers
+ #+BEGIN_SRC emacs-lisp
+ (defun jao-w3m-switch-buffers (dist)
+ (interactive "p")
+ (let* ((dist (if (zerop dist) 1 dist))
+ (current (current-buffer))
+ (current-no (w3m-buffer-number current))
+ (next (progn (w3m-next-buffer dist)
+ (current-buffer)))
+ (next-no (w3m-buffer-number next)))
+ (with-current-buffer current
+ (rename-buffer "*w3m*<*>")
+ (w3m-buffer-set-number next current-no)
+ (w3m-buffer-set-number current next-no)
+ (w3m-pack-buffer-numbers))
+ (switch-to-buffer current)))
+ #+END_SRC
+***** Capture page
+ #+BEGIN_SRC emacs-lisp
+ (defun jao-w3m-capture-page ()
+ (interactive)
+ (let* ((title (w3m-current-title))
+ (url w3m-current-url)
+ (html (y-or-n-p "Save as HTML (y) or PS (n)? "))
+ (basename (concat (read-string "File name: ")
+ (if html ".html" ".ps")))
+ (name (expand-file-name basename jao-sink-dir)))
+ (if html
+ (progn
+ (w3m-view-source)
+ (write-region (point-min) (point-max) name nil nil nil t)
+ (w3m-view-source))
+ (progn
+ (split-window-horizontally 85)
+ (w3m-redisplay-this-page)
+ (ps-print-buffer name)
+ (delete-other-windows)
+ (w3m-redisplay-this-page)))
+ (kill-new (format "[[doc:%s][%s]] ([[%s][original]])"
+ basename title url))))
+
+ (defun jao-w3m-get-link ()
+ (let ((wb (w3m-alive-p)))
+ (when wb
+ (let ((url (with-current-buffer wb w3m-current-url))
+ (title (w3m-buffer-title wb)))
+ (cons url title)))))
+
+ (defun jao-insert-w3m-link ()
+ (interactive)
+ (let ((link (jao-w3m-get-link)))
+ (when link (insert "[[" (car link) "][" (cdr link) "]]"))))
+ #+END_SRC
+***** Keybindings
+ #+BEGIN_SRC emacs-lisp
+ (define-key w3m-mode-map "B" 'jao-w3m-do-browse)
+ (define-key w3m-mode-map "d" 'jao-w3m-download)
+ (define-key w3m-mode-map (kbd "\C-ck") 'jao-w3m-gmail-mark-all)
+ (define-key w3m-mode-map (kbd "\C-c\C-f") 'jao-w3m-switch-buffers)
+ (define-key w3m-mode-map (kbd "\C-cc") 'jao-w3m-capture-page)
+ (define-key w3m-mode-map "\M-\r" 'w3m-view-this-url-new-session)
+ (define-key w3m-mode-map "+" 'w3m-zoom-in-image)
+ (define-key w3m-mode-map "-" 'w3m-zoom-out-image)
+ (define-key w3m-mode-map "m" 'jao-w3m-mail-page)
+ (define-key w3m-mode-map "M" 'w3m-view-url-with-external-browser)
+ (define-key w3m-mode-map "t" 'jao-w3m-tweet)
+ (define-key w3m-mode-map "T" 'jao-w3m-toot)
+ (define-key w3m-mode-map "f" 'w3m-lnum-follow)
+ (define-key w3m-mode-map "c" 'w3m-print-this-url)
+ (define-key w3m-mode-map "v" 'jao-view-video)
+ (define-key w3m-mode-map "V" 'w3m-download)
+ (define-key w3m-mode-map "x" 'jao-w3m-subscribe-rss)
+ (define-key w3m-mode-map "Y" 'w3m-print-current-url)
+ #+END_SRC
+* Shells
+*** 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
+
+ #+begin_src emacs-lisp :load no
+ (use-package shx :ensure t)
+ #+end_src
+*** vterm
+ #+begin_src emacs-lisp
+ (use-package vterm
+ :ensure t
+ :init
+ (setq vterm-kill-buffer-on-exit t
+ vterm-copy-exclude-prompt t
+ vterm-use-vterm-prompt-detection-method t
+ vterm-buffer-name-string nil)
+ :config
+ (define-key vterm-mode-map [(f1)] nil))
+
+ (defun jao-exec-in-vterm (cmd &optional name)
+ (vterm)
+ (when name (vterm-send-string "unset PROMPT_COMMAND\n"))
+ (vterm-send-string cmd)
+ (vterm-send-return)
+ (when name (rename-buffer name)))
+
+ (defvar-local jao-vterm--cmd nil)
+
+ (defun jao-vterm--find (cmd)
+ (seq-find (lambda (b)
+ (and (eq (buffer-local-value 'major-mode b) 'vterm-mode)
+ (string= (or (buffer-local-value 'jao-vterm--cmd b) "")
+ cmd)))
+ (buffer-list)))
+
+ (defmacro jao-def-exec-in-vterm (name cmd &rest prelude)
+ `(defun ,(intern (format "jao-vterm-%s" name)) ()
+ (interactive)
+ ,@prelude
+ (if-let ((b (jao-vterm--find ,cmd)))
+ (pop-to-buffer b)
+ (jao-exec-in-vterm (format "%s; exit" ,cmd)
+ ,(when name (format "%s" name)))
+ (setq-local jao-vterm--cmd ,cmd))))
+
+ (jao-def-exec-in-vterm aptitude "aptitude" (jao-afio--goto-scratch))
+ (jao-def-exec-in-vterm htop "htop" (jao-afio--goto-scratch))
+ #+end_src
+*** Eshell
+***** Basic custom
+ #+BEGIN_SRC emacs-lisp
+ (setq eshell-directory-name "~/.emacs.d/eshell")
+ (setq eshell-aliases-file (jao-data-file "eshell.alias"))
+ #+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"
+ "ssh" "autossh" "zmore" "pager" "aptitude" "su" "htop" "top"
+ "screen" "whizzml" "iex"))
+ (add-to-list 'eshell-visual-commands c))
+
+ (setq eshell-visual-subcommands '(("git" "log" "diff" "show")
+ ("sudo" "vim"))
+ eshell-destroy-buffer-when-process-dies t
+ eshell-escape-control-x t)
+ #+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 ()
+ (bash-completion-dynamic-complete-nocomint
+ (save-excursion (eshell-bol) (point))
+ (point)
+ t))
+
+ (defun jao-eshell--add-bash-completion ()
+ (setq completion-at-point-functions
+ '(jao-eshell-completion-capf
+ pcomplete-completions-at-point t)))
+
+ (use-package bash-completion
+ :ensure t
+ :hook (eshell-mode . jao-eshell--add-bash-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
+***** 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))
+ ;; 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
+*** Shell here
+ #+begin_src emacs-lisp
+ (defun jao-shell-here--find-window (b)
+ (when-let ((w (car (cl-remove-if-not
+ `(lambda (w) (eq (window-buffer w) ,b))
+ (window-list)))))
+ (select-window w)))
+
+ (defvar jao-shell-here-use-vterm nil)
+
+ (defun jao-shell-frame-buffer (&optional b vt)
+ (let ((p (if (or vt jao-shell-here-use-vterm)
+ 'jao-vterm-buffer
+ 'jao-eshell-buffer)))
+ (if b (set-frame-parameter nil p b) (frame-parameter nil p))))
+
+ (defun jao-shell-here (&optional stay)
+ (interactive)
+ (let* ((dir default-directory)
+ (b (jao-shell-frame-buffer)))
+ (if (buffer-live-p b)
+ (pop-to-buffer b nil t)
+ (if jao-shell-here-use-vterm
+ (jao-with-attached-buffer "^vterm$" 35 (vterm))
+ (eshell (when (and (boundp 'jao-exwm--use-afio)
+ (not jao-exwm--use-afio))
+ exwm-workspace-current-index)))
+ (jao-shell-frame-buffer (current-buffer)))
+ (when (not jao-shell-here-use-vterm)
+ (eshell-save-some-history))
+ (when (not stay)
+ (if jao-shell-here-use-vterm
+ (progn (vterm-send-C-a)
+ (vterm-send-C-k)
+ (vterm-send-string (format "cd %s\n" dir)))
+ (eshell-kill-input)
+ (eshell/cd dir)
+ (insert "\n")
+ (eshell-send-input)))))
+
+ (jao-define-attached-buffer "^\\*eshell\\*" 35)
+
+ (defun eshell/x ()
+ (when (derived-mode-p 'eshell-mode)
+ (when (fboundp 'eshell-autojump-save)
+ (eshell-autojump-save))
+ (eshell-save-some-history))
+ (if (> (window-height) (1+ (/ (frame-height) 2)))
+ (bury-buffer)
+ (delete-window)))
+
+ (with-eval-after-load "vterm"
+ (add-to-list 'vterm-eval-cmds '("x" eshell/x)))
+
+ (defun jao-shell-here-toggle ()
+ (interactive)
+ (if (or (eq (current-buffer) (jao-shell-frame-buffer))
+ (eq (current-buffer) (jao-shell-frame-buffer nil t)))
+ (eshell/x)
+ (jao-shell-here t)))
+
+ (defun jao-vterm-here-toggle ()
+ (interactive)
+ (let ((jao-shell-here-use-vterm t))
+ (call-interactively 'jao-shell-here-toggle)))
+
+ (global-set-key (kbd "C-!") 'jao-shell-here)
+ (global-set-key [(f1)] 'jao-shell-here-toggle)
+
+ #+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
+ #+BEGIN_SRC emacs-lisp
+ (use-package diff-hl
+ :ensure t
+ :config
+ (setq diff-hl-draw-borders nil
+ diff-hl-side 'right)
+ (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh))
+ (global-diff-hl-mode)
+ #+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-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" . 2))
+ magit-save-repository-buffers 'dontask
+ magit-status-buffer-switch-function 'switch-to-buffer
+ magit-status-show-hashes-in-headers t)
+ :bind (("<f2>" . magit-status)
+ :map magit-status-mode-map
+ ("C-c C-r" . github-review-forge-pr-at-point)))
+
+ (use-package forge
+ :ensure t
+ :after magit
+ :init
+ (setq forge-database-file
+ (format "~/.emacs.d/forge-database-%s.sqlite" emacs-major-version)
+ forge-topic-list-limit (cons 25 10))
+ ;; workaround for ghub problems (shoudn't be needed in 27.x)
+ (when (< emacs-major-version 27)
+ (setq gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3")
+ (setq ghub-use-workaround-for-emacs-bug nil))
+ :bind (:map forge-topic-mode-map ("M-w" . copy-region-as-kill))
+ :config
+ (remove-hook 'magit-status-sections-hook #'forge-insert-issues)
+ ;; (add-hook 'magit-status-sections-hook #'forge-insert-notifications t)
+ (add-hook 'magit-status-sections-hook #'forge-insert-requested-reviews t)
+ (add-hook 'magit-status-sections-hook #'forge-insert-assigned-issues t)
+ (add-hook 'magit-status-sections-hook #'forge-insert-issues t))
+
+ (use-package orgit-forge
+ :ensure t
+ :after (org))
+ #+end_src
+*** Other git packages
+ #+BEGIN_SRC emacs-lisp
+ (use-package git-messenger
+ :ensure t
+ :bind (("C-x v p" . git-messenger:popup-message)))
+
+ (use-package gist :ensure t :disabled t)
+
+ (use-package git-timemachine :ensure t)
+
+ ;; git config --local git-link.remote / git-link.branch
+ (use-package git-link :ensure t)
+
+ (use-package gitconfig-mode :ensure t)
+
+ (use-package github-review
+ :ensure t
+ :config
+ (defun jao-gnus-github-review ()
+ (interactive)
+ (gnus-summary-select-article-buffer)
+ (goto-char (point-min))
+ (when (re-search-forward "https://github\.com/.*/pull/.*" nil t)
+ (let ((url (match-string-no-properties 0)))
+ (when (yes-or-no-p (format "Start review for %s" url))
+ (github-review-start url)))))
+ (eval-after-load "gnus-art"
+ '(progn
+ (define-key gnus-summary-mode-map "\C-cG" 'jao-gnus-github-review)
+ (define-key gnus-article-mode-map "\C-cG" 'jao-gnus-github-review)))
+ :mode-hydra
+ (github-review-mode nil
+ ("Review"
+ (("a" github-review-approve "Approve")
+ ("r" github-review-reject "Reject")
+ ("c" github-review-reject "Comment"))
+ "Quit"
+ (("Q" bury-buffer "Bury buffer")
+ ("k" kill-buffer "Kill buffer")))))
+ #+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)
+ :bind (:map jenkins-job-view-mode-map
+ (("n" . next-line)
+ ("p" . previous-line)
+ ("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
+*** Vterm repls
+ #+begin_src emacs-lisp
+ (use-package jao-vterm-repl)
+ (jao-define-attached-buffer "^\\* vrepl - .+ \\*.*")
+ #+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
+ #+begin_src emacs-lisp
+ (use-package paredit
+ :ensure t
+ :commands paredit-mode
+ :diminish ((paredit-mode . " þ")))
+
+ (add-hook 'scheme-mode-hook #'paredit-mode)
+ (add-hook 'clojure-mode-hook #'paredit-mode)
+ (add-hook 'emacs-lisp-mode-hook #'paredit-mode)
+ (add-hook 'eval-expression-minibuffer-setup-hook #'paredit-mode)
+ (add-hook 'lisp-interaction-mode-hook (lambda () (paredit-mode -1)))
+ #+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
+ (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
+***** Flycheck
+ #+BEGIN_SRC emacs-lisp
+ (use-package flycheck
+ :ensure t
+ :init
+ (jao-define-attached-buffer "^\\*Flycheck error.*\\*\\'")
+ (setq flycheck-mode-line-prefix ""))
+ #+END_SRC
+***** Next error
+ #+begin_src emacs-lisp
+ (setq next-error-find-buffer-function
+ #'next-error-buffer-on-selected-frame
+ next-error-verbose t)
+
+ (defhydra jao-hydra-errors (global-map "M-g")
+ ("n" next-error "next error")
+ ("p" previous-error "previous error"))
+ #+end_src
+*** Language servers
+ #+begin_src emacs-lisp
+ (use-package yasnippet
+ :ensure t
+ :diminish ((yas-minor-mode . "")))
+
+ (use-package lsp-mode
+ :commands (lsp lsp-deferred)
+ :custom (lsp-auto-guess-root t)
+ :ensure t
+ :diminish " †")
+
+ (jao-define-attached-buffer "\\*lsp-help\\*" 33)
+
+ (use-package company-lsp
+ :ensure t
+ :commands company-lsp)
+ #+end_src
+* Programming languages
+*** Elisp
+ Reporting bugs
+ #+begin_src emacs-lisp
+ (use-package debbugs :ensure t)
+ #+end_src
+ Some helper packages
+ #+BEGIN_SRC emacs-lisp
+ (use-package edit-list :ensure t)
+ #+END_SRC
+
+ Functions to operate on elisp sexps a la slime or geiser:
+
+ #+BEGIN_SRC emacs-lisp
+ (add-hook 'emacs-lisp-mode-hook 'eldoc-mode)
+
+ ;; (add-hook 'emacs-lisp-mode-hook
+ ;; (lambda ()
+ ;; (when (eq major-mode 'emacs-lisp-mode)
+ ;; (paredit-mode 1))))
+
+ (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 (cl-macroexpand-all form)))
+
+ (defun elisp-push-point-marker ()
+ (require 'etags)
+ (cond ((featurep 'xemacs)
+ (push-tag-mark))
+ (t (ring-insert find-tag-marker-ring (point-marker)))))
+
+ (defun elisp-pop-found-function ()
+ (interactive)
+ (cond ((featurep 'xemacs) (pop-tag-mark nil))
+ (t (pop-tag-mark))))
+
+ (defun elisp-find-definition (name)
+ "Jump to the definition of the function (or variable) at point."
+ (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)
+ (elisp-push-point-marker)
+ (funcall search 'find-function-noselect symbol))
+ ((boundp symbol)
+ (elisp-push-point-marker)
+ (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))))
+
+ #+END_SRC
+
+ Bindinging the functions above to "natural" keys in elisp buffers:
+
+ #+BEGIN_SRC emacs-lisp
+ (defvar elisp-extra-keys
+ '(
+ ;; ((kbd "C-c d") 'elisp-disassemble)
+ ((kbd "C-c C-m") 'elisp-macroexpand)
+ ((kbd "C-c C-M") 'elisp-macroexpand-all)
+ ((kbd "C-c C-c") 'compile-defun)
+ ((kbd "C-c C-k") 'elisp-bytecompile-and-load)
+ ((kbd "C-c C-l") 'load-file)
+ ((kbd "C-c C-p") 'pp-eval-last-sexp)
+ ((kbd "M-.") 'elisp-find-definition)
+ ((kbd "M-,") 'elisp-pop-found-function)
+ ((kbd "C-c <") 'list-callers)))
+
+ (dolist (binding elisp-extra-keys)
+ (let ((key (eval (car binding))) (val (eval (cadr binding))))
+ (define-key emacs-lisp-mode-map key val)
+ (define-key lisp-interaction-mode-map key val)))
+ #+END_SRC
+*** Erlang
+ #+begin_src emacs-lisp
+ (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)
+
+ (add-hook 'erlang-mode-hook #'yas-minor-mode-on)
+ (add-hook 'erlang-mode-hook #'lsp))
+ #+end_src
+*** Elixir
+***** packages
+ #+begin_src emacs-lisp
+ (use-package elixir-mode
+ :ensure t
+ :custom (lsp-clients-elixir-server-executable
+ (expand-file-name "~/usr/share/elixir-ls/language_server.sh"))
+ :bind (:map elixir-mode-map (("C-c C-z" . jao-vterm-repl-pop-to-repl)))
+ :config
+ (add-hook 'elixir-mode-hook #'lsp)
+ (add-hook 'elixir-mode-hook #'yas-minor-mode))
+
+ (use-package mix
+ :ensure t
+ :hook (elixir-mode . mix-minor-mode)
+ :init (jao-vterm-repl-register "mix.exs" "iex -S mix" "^iex([0-9]+)> ")
+ :diminish ((mix-minor-mode . "")))
+
+ (use-package exunit
+ :ensure t)
+ #+end_src
+***** hydra
+ #+begin_src emacs-lisp
+ (major-mode-hydra-define elixir-mode nil
+ ("Doc"
+ (("dd" lsp-describe-thing-at-point "Describe thing at point"))
+ "Xref"
+ (("xd" xref-find-definitions "Definitions")
+ ("xo" xref-find-definitions-other-window "-> other win")
+ ("xr" xref-find-references "References"))
+ "LSP"
+ (("lg" lsp-goto-implementation "Implementation")
+ ("lf" lsp-format-buffer "Format buffer")
+ ("ls" lsp-describe-session "Describe session"))
+ "LSP modes"
+ (("Tl" lsp-lens-mode "Lens mode" :toggle t)
+ ("Td" lsp-modeline-diagnostics-mode "Modeline diagnostics" :toggle t)
+ ("Tb" lsp-headerline-breadcrumb-mode "Header breadcrumb" :toggle t)
+ ("Ti" lsp-toggle-trace-io "Trace I/O" :toggle lsp-print-io))
+ "Mix"
+ (("me" mix-execute-task "Execute mix task")
+ ("mt" mix-test "Run tests")
+ ("mc" mix-compile "Compile")
+ ("mm" jao-elixir-pop-to-iex "Pop to iex"))
+ "Mix subproject"
+ (("Me" (lambda () (interactive) (mix-execute-task nil t))
+ "Execute task")
+ ("Mt" (lambda () (interactive) (mix-compile nil t)) "Compile")
+ ("Mt" (lambda () (interactive) (mix-test nil t)) "Run tests"))
+ "Tests"
+ (("tt" exunit-verify-single "Run single test")
+ ("tr" exunit-rerun "Re-run tests")
+ ("ta" exunit-verify "Run test suites")
+ ("tA" exunit-verify-all "Run all test suites"))))
+ #+end_src
+*** Clojure
+ #+BEGIN_SRC emacs-lisp
+ (use-package clojure-mode
+ :ensure t
+ :config
+ (add-hook 'clojure-mode-hook (lambda () (setq mode-name "¢"))))
+
+ (use-package cider
+ :ensure t
+ :commands cider-mode
+ ;; :pin melpa-stable
+ :init (setq cider-annotate-completion-candidates t
+ cider-auto-select-error-buffer nil
+ clojure-docstring-fill-column 72
+ cider-show-error-buffer 'except-in-repl
+ cider-lein-parameters "repl :headless :host localhost"
+ cider-mode-line " ÷"
+ cider-prompt-for-symbol nil
+ cider-repl-pop-to-buffer-on-connect nil
+ cider-repl-history-file
+ (expand-file-name "~/.emacs.d/cache/cider.history")
+ cider-repl-use-pretty-printing t
+ cider-test-show-report-on-success nil
+ cider-auto-select-test-report-buffer nil
+ cider-use-overlays nil
+ cider-use-fringe-indicators nil
+ cider-eldoc-display-for-symbol-at-point t
+ eldoc-echo-area-use-multiline-p nil
+ nrepl-prompt-to-kill-server-buffer-on-quit nil)
+ :hook ((cider-mode . cider-company-enable-fuzzy-completion)
+ (cider-mode . cider-eldoc-setup)
+ (cider-mode . eldoc-mode)))
+
+ (eval-after-load "cider-test"
+ '(progn
+ (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 ((cider-macroexpansion-mode . " µ")))
+
+ #+END_SRC
+*** Scheme
+ #+BEGIN_SRC emacs-lisp
+ ;;; racket
+ (defun jao-racket-help (file)
+ (jao-w3m-browse-url (url-unhex-string file)))
+
+ ;;; safe variables
+ (put 'package 'safe-local-variable 'symbolp)
+ (put 'Package 'safe-local-variable 'symbolp)
+ (put 'syntax 'safe-local-variable 'symbolp)
+ (put 'Syntax 'safe-local-variable 'symbolp)
+ (put 'Base 'safe-local-variable 'integerp)
+ (put 'base 'safe-local-variable 'integerp)
+
+ ;;;; guile test suite keywords
+ (put 'with-test-prefix 'scheme-indent-function 1)
+ (put 'expect-fail 'scheme-indent-function 1)
+ (put 'pass-if 'scheme-indent-function 1)
+ (put 'pass-if-exception 'scheme-indent-function 2)
+ ;;;; testeez
+ (put 'test-true 'scheme-indent-function 1)
+ (put 'test-false 'scheme-indent-function 1)
+ (put 'test-define 'scheme-indent-function 2)
+ (put 'test/equal 'scheme-indent-function 1)
+ (put 'test/eq 'scheme-indent-function 1)
+ (put 'test/eqv 'scheme-indent-function 1)
+ (put 'testeez 'scheme-indent-function 1)
+ ;;;; scsh test utilities
+ (put 'add-test! 'scheme-indent-function 2)
+ ;;;; schemeunit
+ (put 'test-case 'scheme-indent-function 1)
+ (put 'test-suite 'scheme-indent-function 1)
+ (put 'test-eq? 'scheme-indent-function 1)
+ (put 'test-eqv? 'scheme-indent-function 1)
+ (put 'test-equal? 'scheme-indent-function 1)
+ ;;;; more
+ (put 'lambda* 'scheme-indent-function 1)
+ (put 'with-current-directory 'scheme-indent-function 1)
+ (put 'with-working-directory 'scheme-indent-function 1)
+ #+END_SRC
+*** Geiser
+ #+BEGIN_SRC emacs-lisp
+ (when (file-exists-p "~/usr/jao/geiser/src")
+ (setq geiser-repl-history-filename "~/.emacs.d/cache/geiser-history")
+ (setq geiser-repl-startup-time 20000)
+ (setq geiser-debug-auto-display-images-p t)
+ (setq geiser-chez-binary "scheme")
+ (load-file (expand-file-name "~/usr/jao/geiser/src/elisp/geiser.el")))
+ #+END_SRC
+*** Lisp
+ #+begin_src emacs-lisp
+ (use-package sly
+ :ensure t
+ :init (setq inferior-lisp-program "sbcl")
+ :config (sly-setup))
+
+ (use-package sly-quicklisp
+ :after (sly)
+ :ensure t)
+ #+end_src
+*** Haskell
+ #+BEGIN_SRC emacs-lisp
+ (use-package haskell-mode
+ :ensure t
+ :init
+ (setq inferior-haskell-find-project-root t)
+ (setq haskell-check-remember-last-command-p nil)
+ (setq haskell-program-name "ghci")
+ (setq haskell-font-lock-symbols nil)
+ (setq haskell-process-suggest-remove-import-lines t
+ haskell-process-auto-import-loaded-modules t
+ haskell-process-type 'cabal-repl
+ haskell-process-log t)
+
+ :config
+ (add-hook 'haskell-mode-hook
+ (lambda ()
+ (set (make-local-variable 'compile-command) "cabal install")))
+
+ (require 'haskell-interactive-mode)
+ (require 'haskell-process)
+
+ :hook (;; (haskell-mode . interactive-haskell-mode)
+ (haskell-mode . haskell-doc-mode)
+ (haskell-mode . haskell-indentation-mode)
+ (haskell-mode . flycheck-mode))
+
+ :bind (:map haskell-mode-map
+ ("C-c h" . haskell-hoogle)))
+
+ ;; (use-package hlint-refactor
+ ;; :ensure t
+ ;; :diminish " |"
+ ;; :config (add-hook 'haskell-mode-hook 'hlint-refactor-mode))
+
+ (use-package dante
+ :ensure t
+ :after haskell-mode
+ :commands 'dante-mode
+ :config (setq dante-methods '(new-build))
+ :hook ((haskell-mode . dante-mode)))
+ #+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
+*** 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
+***** Python notebooks (ein)
+ #+BEGIN_SRC emacs-lisp
+ (use-package ein :ensure t :disabled t)
+ #+END_SRC
+*** Coq
+ #+BEGIN_SRC emacs-lisp :tangle no :load no
+ (load (expand-file-name "pg/generic/proof-site" local-lisp-dir))
+
+ (use-package company-coq
+ :ensure t
+ :init (add-hook 'coq-mode-hook #'company-coq-mode))
+ #+END_SRC
+*** Ruby
+ #+BEGIN_SRC emacs-lisp :tangle no :load no
+ (setq ruby-program-name "/home/jao/bin/irb --inf-ruby-mode")
+ (require 'ruby-mode)
+ (add-to-list 'auto-mode-alist '("\\.rb$" . ruby-mode))
+ (add-to-list 'interpreter-mode-alist '("ruby" . ruby-mode))
+
+ (when (require 'ruby-electric nil t)
+ (add-hook 'ruby-mode-hook (lambda () (ruby-electric-mode))))
+
+ (when (jao-load-path "ri-emacs")
+ (setq ri-ruby-script
+ (expand-file-name "ri-emacs/ri-emacs.rb" local-lisp-dir))
+ (setq ri-ruby-program "ruby")
+ (autoload 'ri (expand-file-name "ri-emacs/ri-ruby.el" local-lisp-dir))
+ (add-hook 'ruby-mode-hook
+ (lambda ()
+ (local-set-key [f1] 'ri)
+ (local-set-key [f2] 'ri-ruby-complete-symbol)
+ (local-set-key [f4] 'ri-ruby-show-args))))
+ #+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
+*** Bluetooth
+ #+BEGIN_SRC emacs-lisp
+ (use-package bluetooth :ensure t)
+ #+END_SRC
+*** Enwc (network monitor)
+ #+BEGIN_SRC emacs-lisp
+ (use-package enwc
+ :ensure t
+ :init (setq enwc-default-backend 'nm
+ enwc-backend 'nm
+ enwc-display-mode-line nil
+ enwc-wired-device "enp3s0f0"
+ enwc-wireless-device "wlp1s0"))
+ #+END_SRC
+*** Proton
+ #+BEGIN_SRC emacs-lisp
+ (require 'jao-proton-utils)
+ (defalias 'proton-vpn 'proton-vpn-status)
+ #+END_SRC
+*** ssh
+ #+begin_src emacs-lisp
+ (use-package tramp)
+ (defun jao-tramp-hosts ()
+ (remove-duplicates
+ (mapcan (lambda (x)
+ (remove* nil
+ (mapcar 'cadr (apply (car x) (cdr x)))))
+ (tramp-get-completion-function "ssh"))
+ :test #'string=))
+
+ (defun jao-ssh ()
+ (interactive)
+ (let ((h (completing-read "Host: " (jao-tramp-hosts))))
+ (jao-exec-in-vterm (format "ssh %s" h) (format "* %s *" h))))
+ #+end_src
+* Chats
+*** Circe
+***** General configuration
+ #+begin_src emacs-lisp
+ (use-package circe
+ :ensure t
+ :bind (:map circe-channel-mode-map
+ (("C-c C-a" . lui-track-jump-to-indicator)))
+ :init
+ (setq 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 'fringe
+ lui-track-behavior 'before-tracking-next-buffer)
+ :config
+ (setq circe-network-options
+ (let ((up (jao--get-user/password "freenode"))
+ (bup (jao--get-user/password "bitlbee")))
+ `(("Freenode" :nick ,(car up) :channels ,jao-irc-channels
+ :tls t :sasl-username ,(car up) :sasl-password ,(cadr up))
+ ("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)
+
+ (circe-lagmon-mode)
+ (enable-circe-color-nicks)
+ (enable-circe-display-images)
+ (enable-lui-track))
+ #+end_src
+***** Commands (recover &co.)
+ #+begin_src emacs-lisp
+ (with-eval-after-load "circe"
+ (defun circe-command-NICKNO (&rest ignore)
+ (message "%s nicks" (length (circe-channel-nicks))))
+
+ (advice-add 'circe-command-NAMES :after #'circe-command-NICKNO)
+
+ (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))))
+ #+end_src
+***** Follow twitter/mastodon threads
+ #+begin_src emacs-lisp
+ (defun jao-twitter-find-ref ()
+ (interactive)
+ (when-let (no (save-excursion
+ (move-end-of-line nil)
+ (when (re-search-backward
+ "\\[[0-9a-f]+->\\([0-9a-f]+\\)\\]" nil t)
+ (match-string-no-properties 1))))
+ (push-mark (point))
+ (re-search-backward (format "\\[%s[]-]" no))))
+ (with-eval-after-load "circe"
+ (define-key circe-channel-mode-map "\C-c\C-p" 'jao-twitter-find-ref))
+ #+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
+ :ensure t
+ :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 "/tmp/slack-files/"
+ 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)))
+ :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)
+ (jao-tracking-face 'warning))
+ #+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-root-show-avatars nil
+ telega-chat-prompt-show-avatar-for nil
+ telega-emoji-use-images nil
+ telega-temp-dir "/tmp/telega"
+ telega-symbol-checkmark "·"
+ telega-symbol-heavy-checkmark "×"
+ telega-symbol-verified "*"
+ telega-mode-line-string-format
+ '(" " (:eval (telega-mode-line-unread-unmuted))))
+ (with-eval-after-load "tracking"
+ (jao-shorten-modes 'telega-chat-mode)
+ (jao-tracking-face 'telega-tracking))
+ (telega-mode-line-mode 1))
+ #+end_src
+*** Signel
+ #+begin_src emacs-lisp
+ (literate-elisp-load-file (jao-lib-file "jao/net/signel.org"))
+ (with-eval-after-load "tracking"
+ (jao-tracking-face 'signel-notification)
+ (jao-shorten-modes 'signel-chat-mode))
+ (setq signel-report-deliveries t)
+ #+end_src
+*** Startup
+ #+begin_src emacs-lisp
+ (defun jao-circe (&optional p)
+ (interactive "P")
+ (when (or p (y-or-n-p "Connect to freenode using circe? "))
+ (circe "Freenode"))
+ (when (or p (y-or-n-p "Connect to bitlbee using circe? "))
+ (circe "Bitlbee")))
+
+ (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 (and (not (signel-signal-cli-process))
+ ;; (or p (y-or-n-p "Start signel? ")))
+ ;; (signel-start))
+ (jao-circe p))
+
+ (global-set-key
+ (kbd "s-c")
+ (pretty-hydra-define jao-hydra-chats (global-map "s-c" :color blue :quit-key "q")
+ ("Connect"
+ (("A" (jao-chats t) "all chats -y")
+ ("a" jao-chats "all chats")
+ ("S" slack-start "slack")
+ ("C" (jao-circe t) "circe -y")
+ ("s-c" jao-hydra-chats/body nil))
+ "Go"
+ (("c" (jao-buffer-same-mode 'circe-channel-mode) "circe buffer")
+ ("s" (jao-buffer-same-mode 'slack-message-buffer-mode) "slack buffer")
+ ("t" (jao-buffer-same-mode 'telega-chat-mode) "telega buffer")
+ ("T" telega "telega rooster")))))
+
+ #+end_src
+* Multimedia
+*** mixer
+ #+begin_src emacs-lisp
+ (defun jao-mixer-set (dev v)
+ (start-process "amixer" nil "amixer" "sset" dev v))
+
+ (defun jao-mixer-master-toogle ()
+ (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%-"))
+ #+end_src
+*** emms
+***** configuration
+ #+BEGIN_SRC emacs-lisp
+ (use-package emms
+ :pin gnu
+ :ensure t
+ :init
+ (setq emms-score-file "~/.emacs.d/score"
+ emms-stream-bookmarks-file "~/.emacs.d/streams"
+ emms-history-file "~/.emacs.d/emms-history"
+ emms-cache-file "~/.emacs.d/emms-cache"
+ emms-show-format "%s")
+
+ (setq emms-source-file-default-directory "/home/jao/var/lib/music/"
+ emms-player-list '(emms-player-mpd)
+ emms-player-mpd-server-name "localhost"
+ emms-player-mpd-server-port "6600"
+ emms-player-mpd-music-directory emms-source-file-default-directory)
+
+ (setq emms-volume-change-function 'emms-volume-mpd-change
+ emms-volume-change-amount 10
+ emms-info-ogginfo-coding-system 'utf-8)
+
+ ;; from http://www.shellarchive.co.uk/index.html#%20Prettify%20emms
+ (setq emms-browser-info-genre-format "%i· %n"
+ emms-browser-info-artist-format "%i· %n"
+ emms-browser-info-album-format "%i◨ %n"
+ emms-browser-info-title-format "%i♪ %n")
+
+ (setq emms-last-played-format-alist
+ '(((emms-last-played-seconds-today) . "Today at %H:%M")
+ (604800 . "%a at %H:%M")
+ ((emms-last-played-seconds-month) . "%d")
+ ((emms-last-played-seconds-year) . "%m-%d")
+ (t . "")))
+
+ :hook ((emms-player-started . emms-player-mpd-show))
+
+ :config
+ (eval-after-load "emms-info"
+ '(add-to-list 'emms-info-functions 'emms-info-mpd)))
+
+ (emms-all)
+ (emms-mode-line -1)
+ (emms-playing-time 1)
+ (emms-playing-time-disable-display)
+
+ (use-package jao-emms-random-album
+ :after emms
+ :commands (jao-emms-random-album-next)
+ :init (setq jao-emms-random-album-notify-icon jao-notify-audio-icon))
+
+ (use-package jao-emms-info-track
+ :after emms
+ :init (setq jao-emms-show-icon jao-notify-audio-icon)
+ :config (jao-emms-info-setup 50))
+
+ (use-package jao-emms-lyrics
+ :after emms
+ :init (setq jao-lyrics-info-function 'jao-emms-lyrics-track-data))
+
+ (defvar jao-emms-random-album-notify--pause-icon
+ "/usr/share/icons/Tango/scalable/actions/media-playback-pause.svg")
+
+ (defun jao-emms--show-status (s status)
+ (jao-notify (format "%s%s%s"
+ (cadr s)
+ (cdr (assoc (car s) status))
+ (caddr s))
+ "emms"
+ (if (string= "pause" (cdr (assoc "state" status)))
+ jao-emms-random-album-notify--pause-icon
+ jao-notify-audio-icon)))
+
+ (defun jao-emms--osd-status (s &optional pref suff)
+ (emms-player-mpd-get-status (list s (or pref "") (or suff ""))
+ 'jao-emms--show-status))
+
+ #+END_SRC
+***** helper functions
+ #+begin_src emacs-lisp
+ (defun jao-emms-volume-delta (d)
+ (funcall emms-volume-change-function d))
+
+ (defun jao-emms-show-volume ()
+ (jao-emms--osd-status "volume" "Volume " "%"))
+
+ (defalias 'jao-emms-update-cache 'emms-player-mpd-update-all-reset-cache)
+
+ (defun jao-emms-load-streams ()
+ (interactive)
+ (emms-play-playlist (expand-file-name "~/var/lib/music/streams.list")))
+
+ (defun jao-emms-search ()
+ (interactive)
+ (let ((by (completing-read "Search by: "
+ '("artist"
+ "composer"
+ "performer"
+ "title"
+ "album"
+ "names"))))
+ (if (string= "names" by)
+ (emms-browser-search-by-names)
+ (emms-browser-search (list (intern (concat "info-" by)))))))
+
+ (defun jao-emms-echo ()
+ (interactive)
+ (emms-show)
+ (jao-emms-update-echo-string)
+ (emms-show))
+ #+end_src
+***** Media global aliases
+ #+begin_src emacs-lisp
+ (defalias 'jao-player-connect 'emms-player-mpd-connect)
+ (defalias 'jao-player-toggle 'emms-pause)
+ (defalias 'jao-player-next 'emms-next)
+ (defalias 'jao-player-previous 'emms-previous)
+ (defalias 'jao-player-stop 'emms-stop)
+ (defalias 'jao-player-start 'emms-start)
+ (defalias 'jao-player-seek-forward 'emms-seek-forward)
+ (defalias 'jao-player-seek-backward 'emms-seek-backward)
+ (defalias 'jao-player-play 'emms-start)
+ (defalias 'jao-player-search 'jao-emms-search)
+ (defalias 'jao-player-vol-delta 'jao-emms-volume-delta)
+ (defalias 'jao-player-volume 'jao-emms-show-volume)
+ (defalias 'jao-player-osd 'jao-emms-show-osd)
+ (defalias 'jao-player-echo 'jao-emms-echo)
+ (defalias 'jao-player-list 'emms-playlist-mode-go)
+ (defalias 'jao-player-browse 'emms-browser)
+ (defalias 'jao-player-random-album 'jao-emms-random-album-next)
+ #+end_src
+*** mpris
+ #+begin_src emacs-lisp
+ (use-package jao-mpris
+ :config
+ (jao-mpris-minibuffer-order 50)
+ (dolist (name '("spotifyd" "spotify" "mopidy"))
+ (jao-mpris-minibuffer-register name)
+ (jao-mpris-minibuffer-register name :system)))
+ #+end_src
+*** spotify
+ #+begin_src emacs-lisp
+ (jao-maybe-tangle (jao-lib-file "jao/media/espotify"))
+
+ (use-package espotify)
+
+ (when (eq 'consult jao-completion-engine)
+ (use-package espotify-consult :demand t)
+ (use-package espotify-embark :demand t)
+ (defalias 'jao-spotify-album #'espotify-consult-album)
+ (defalias 'jao-spotify-track #'espotify-consult-track)
+ (defalias 'jao-spotify-artist #'espotify-consult-artist)
+ (defalias 'jao-spotify-playlist #'espotify-consult-playlist))
+
+ (when (eq 'counsel jao-completion-engine)
+ (use-package espotify-counsel :demand t)
+ (defalias 'jao-spotify-album #'espotify-counsel-album)
+ (defalias 'jao-spotify-track #'espotify-counsel-track)
+ (defalias 'jao-spotify-artist #'espotify-counsel-artist)
+ (defalias 'jao-spotify-playlist #'espotify-counsel-playlist))
+ #+end_src
+*** mpdel
+ #+BEGIN_SRC emacs-lisp
+ (jao-load-path "libmpdel")
+ (jao-load-path "mpdel")
+ (jao-load-path "navigel")
+
+ (use-package navigel
+ :init (setq navigel-display-messages nil))
+
+ (defun jao-mpdel-dup-dir-p (dir)
+ (and (libmpdel-directory-p dir)
+ (string-match-p "\\[.+\\]\\b"
+ (or (libmpdel--directory-path dir) ""))))
+
+ (defun jao-mpdel--filter (dirs)
+ (cl-remove-if 'jao-mpdel-dup-dir-p dirs))
+
+ (use-package libmpdel
+ :init (setq libmpdel-port 6669))
+
+ (use-package mpdel
+ :diminish
+ :init
+ (setq mpdel-browser-list-clean-up-function 'identity
+ mpdel-browser-top-level-entries
+ '(directories
+ empty-line
+ stored-playlists current-playlist
+ empty-line
+ "Spotify/Top Lists/Top artists/Personal"
+ "Spotify/Playlists/Featured"
+ empty-line
+ search-album search-artist search-title))
+
+ (defun jao-mpdel--show-osd (data song ml)
+ (let* ((no (1+ (string-to-number (or (cdr (assq 'song data)) "0"))))
+ (to (or (cdr (assq 'playlistlength data)) "0"))
+ (album (or (libmpdel-album-name song) ""))
+ (artist (or (libmpdel-artist-name song) ""))
+ (tms (split-string (or (cdr (assq 'time data)) "0/0") ":"))
+ (tm (format "%s/%s"
+ (libmpdel-time-to-string (car tms))
+ (libmpdel-time-to-string (cadr tms))))
+ (title (format "%s %s/%s. %s" tm no to
+ (or (libmpdel-entity-name song) ""))))
+ (if ml
+ (message "%s %s - %s (%s)" tm title artist album)
+ (jao-notify (format "%s (%s)" artist album)
+ title
+ jao-notify-audio-icon))))
+
+ (defun jao-mpdel-show-osd (&optional ml)
+ (interactive "P")
+ (let ((song (libmpdel-current-song)))
+ (when song
+ (libmpdel-send-command "status"
+ `(lambda (data)
+ (jao-mpdel--show-osd data ,song ,ml))))))
+
+ (defun jao-mpdel-search (&optional type)
+ (interactive
+ (list (completing-read "Search by: " '("album" "artist" "title"))))
+ (let* ((func (cond ((string= type "album") 'mpdel-core-search-by-album)
+ ((string= type "artist") 'mpdel-core-search-by-artist)
+ ((string= type "title") 'mpdel-core-search-by-title)))
+ (thing (read-from-minibuffer (format "Search for %s: " type))))
+ (mpdel-core-open (libmpdel-search-criteria-create :type type :what thing)))))
+
+ (use-package mpdel-browser)
+ (defalias 'mpdel-pop-to-browser 'mpdel-browser-open)
+
+ (mpdel-mode)
+ (define-key mpdel-browser-mode-map (kbd "n") #'next-line)
+ (define-key mpdel-browser-mode-map (kbd "p") #'previous-line)
+ #+END_SRC
+*** hydras
+ #+begin_src emacs-lisp
+ (require 'jao-lyrics)
+
+ (defun jao-show-some-lyrics (arg)
+ (interactive "P")
+ (if (string-blank-p (or jao-mpris-track-string ""))
+ (jao-show-lyrics arg 'jao-emms-lyrics-track-data)
+ (jao-show-lyrics arg 'jao-mpris-artist-title)))
+
+ (defalias 'jao-player-show-lyrics 'jao-show-some-lyrics)
+
+ (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))))
+
+ (use-package jao-emms-random-album)
+
+ (pretty-hydra-define jao-hydra-spotify
+ (global-map "s-s" :color blue :quit-key "q")
+ ("Search"
+ (("a" jao-spotify-album "album")
+ ("A" jao-spotify-artist "artist")
+ ("t" jao-spotify-track "track")
+ ("P" jao-spotify-playlist "playlist"))
+ "Play"
+ (("s" libmpdel-playback-play-pause "toggle")
+ ("n" libmpdel-playback-next "next")
+ ("p" libmpdel-playback-previous "previous")
+ ("w" jao-mpdel-show-osd "currently playing"))
+ "Browse"
+ (("b" mpdel-pop-to-browser "browser")
+ ("l" mpdel-playlist-open "playing list")
+ ;; ("m" jao-counsel-spotify-change-mpris "change mpris provider")
+ ("c" (mpdel-core-replace-current-playlist) "clear list" :color red)
+ ("s-s" jao-hydra-spotify/body nil))))
+
+ (pretty-hydra-define jao-hydra-media
+ (global-map "s-m" :color blue :quit-key "q")
+ ("Play"
+ (("m" jao-player-toggle "toogle")
+ ("n" jao-player-next "next")
+ ("f" jao-player-seek-forward "seek fwd")
+ ("F" jao-player-seek-backward "seek bwd")
+ ("p" jao-player-previous "previous"))
+ "Browse"
+ (("b" jao-player-browse "browse")
+ ("l" jao-player-list "show play list")
+ ("L" jao-player-show-lyrics "show lyrics")
+ ("w" jao-player-echo "now playing (text)")
+ ("s" jao-player-search "search"))
+ "Volume"
+ (("M" jao-mixer-master-toogle "master toggle")
+ ("d" jao-mixer-master-down "master down")
+ ("u" jao-mixer-master-up "master up")
+ ("D" jao-mixer-capture-down "capture down")
+ ("U" jao-mixer-capture-up "capture up"))
+ "Utilities"
+ (("C" jao-emms-update-cache "refresh cache")
+ ("c" jao-player-connect "reconnect to mpd")
+ ("r" jao-emms-random-album-toggle "toggle random album"
+ :toggle jao-emms-random-album-p)
+ ("N" jao-player-random-album "random album")
+ ("s-m" jao-hydra-media/body nil))))
+
+ #+end_src
+* Key bindings
+ #+begin_src emacs-lisp
+ (global-set-key "\M-\\" 'hippie-expand)
+ (global-set-key "\C-c." 'goto-last-change)
+ (global-set-key "\C-cj" 'join-line)
+ (global-set-key "\C-co" 'ff-find-other-file)
+ (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)
+
+ (pretty-hydra-define jao-hydra-emacs-utils (:color blue :quit-key "q")
+ ("Misc"
+ (("a" jao-vterm-aptitude "aptitude")
+ ("l" (progn (jao-afio--goto-scratch) (list-packages)) "package list")
+ ("f" (jao-sway-run-or-focus "firefox") "switch to firefox"))
+ "Network"
+ (("s" jao-ssh "ssh")
+ ("v" proton-vpn "proton vpn")
+ ("m" run-proton-bridge "proton bridge"))
+ "Devices"
+ (("b" bluetooth-list-devices "bluetooth")
+ ("n" enwc "networks"))
+ "Monitors"
+ (("p" jao-vterm-htop "htop")
+ ("P" (jao-sway-run-or-focus "pavucontrol") "pavucontrol")
+ ("t" jao-time-echo-times "current time"))
+ "Looks"
+ (("T" jao-toggle-transparency "toggle transparency"
+ :toggle (jao-transparent-p) :color red)
+ ("w" jao-set-wallpaper "set wallpaper")
+ ("W" jao-set-random-wallpaper "set radom wallpaper"))
+ "Sleep"
+ (("L" jao-lock-screen "lock screen")
+ ("z" jao-suspend "sleep")
+ ("u" jao-screensaver-toggle "toggle screensaver"
+ :toggle (jao-screensaver-enabled)))
+ "Helpers"
+ (("r" org-reveal "org reveal")
+ ("k" jao-kb-toggle "toggle keyboard"
+ :toggle (jao-kb-toggled-p) :color red)
+ ("M" jao-minibuffer-toggle "toggle minibuffer"
+ :toggle jao-minibuffer-enabled-p))))
+
+ #+end_src
+* Last minute (post.el)
+ #+begin_src emacs-lisp
+ (jao-load-local-el "post" t)
+ #+end_src
diff --git a/org.org b/org.org
new file mode 100644
index 0000000..254ee39
--- /dev/null
+++ b/org.org
@@ -0,0 +1,326 @@
+* General configuration
+ #+BEGIN_SRC emacs-lisp
+ (use-package org
+ :ensure t
+ :init
+ (setq org-catch-invisible-edits 'smart
+ org-complete-tags-always-offer-all-agenda-tags t
+ org-completion-use-ido nil
+ org-cycle-separator-lines 0 ;; no blank lines when all colapsed
+ org-deadline-warning-days 14
+ org-directory jao-notes-dir
+ org-default-notes-file (expand-file-name "inbox.org" org-directory)
+ org-ellipsis " .." ;; ↴
+ org-email-link-description-format "Email %c: %s"
+ org-enforce-todo-dependencies t
+ org-extend-today-until 0
+ org-fast-tag-selection-single-key 'expert
+ org-hide-emphasis-markers t
+ org-hide-leading-stars t
+ ;; 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 '(bbdb bibtex gnus info w3m)
+ 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 t
+ 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 (list jao-notes-dir)
+ 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 7
+ 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)
+ ("w" "Wintermute TODO" entry
+ (file+olp "bigml.org" "Wintermute" "Tasks")
+ "* TODO %?\n %i%a" :prepend t)
+ ("i" "Inbox note" entry (file+headline "inbox.org" "Notes")
+ "* %a\n %i%?(added on: %u)" :prepend t)
+ ("x" "Clipboard" entry (file+headline "inbox.org" "Notes")
+ "* %?\n %a\n %x\n (added: %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
+* Calendar
+ #+BEGIN_SRC emacs-lisp
+ (setq gnus-icalendar-org-capture-file
+ (expand-file-name "inbox.org" org-directory)
+ gnus-icalendar-org-capture-headline '("Appointments"))
+ (eval-after-load "gnus"
+ '(progn (require 'org-agenda)
+ (require 'gnus-icalendar)
+ (gnus-icalendar-org-setup)))
+ #+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)
+ (add-to-list 'org-latex-classes
+ '("bmlarticle"
+ "\\documentclass{bmlarticle}\n[NO-DEFAULT-PACKAGES]\n[EXTRA]"
+ ("\\section{%s}" . "\\section*{%s}")
+ ("\\subsection{%s}" . "\\subsection*{%s}")
+ ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
+ ("\\paragraph{%s}" . "\\paragraph*{%s}")
+ ("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
+
+ (use-package cdlatex
+ :ensure t
+ :hook ((org-mode . org-cdlatex-mode))
+ :diminish ((cdlatex-mode . " £")
+ (org-cdlatex-mode . " £")))
+
+ #+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
+ - [[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) ;; <s TAB for 9.2 and later
+
+ ;; (use-package ob-elixir
+ ;; :ensure t
+ ;; :after org)
+
+ (use-package ob-prolog
+ :ensure t
+ :after org)
+
+ (org-babel-do-load-languages
+ 'org-babel-load-languages
+ '((calc . t)
+ (clojure . t)
+ ;; (elixir . t)
+ (emacs-lisp .t)
+ (gnuplot .t)
+ (haskell . t)
+ (makefile . t)
+ (ocaml . t)
+ (org . t)
+ (python . t)
+ (scheme .t)
+ (shell . t)
+ (prolog . t)))
+ #+end_src
+
+* Org cliplink (link from clipboard)
+ [[https://github.com/rexim/org-cliplink][GitHub - rexim/org-cliplink: Insert org-mode links from clipboard]]
+
+ #+BEGIN_SRC emacs-lisp
+ (use-package org-cliplink
+ :ensure t
+ :bind (:map org-mode-map ("C-c C-f" . org-cliplink))
+ :config
+ (add-to-list 'org-capture-templates
+ '("k" "Cliplink capture task" entry
+ (file+headline "inbox.org" "Todo")
+ "* TODO %(org-cliplink-capture) %?" :prepend t)
+ t))
+ #+END_SRC
+
+* Org download
+
+ #+begin_src emacs-lisp
+ (use-package org-download
+ :ensure t
+ :after org
+ :init (setq org-download-screenshot-method "import %s")
+ :bind
+ (:map org-mode-map
+ (("C-c S" . org-download-screenshot)
+ ("C-c I" . org-download-yank))))
+ #+end_src
+
+* Org roam
+ #+begin_src emacs-lisp
+ (use-package org-roam
+ :ensure t
+ :init
+ (defun jao-roam--slug (slug) (replace-regexp-in-string "_" "-" slug))
+
+ (defun jao-roam--cat ()
+ (let* ((cats (seq-difference (directory-files org-roam-directory)
+ '("." ".." "attic")))
+ (cat (completing-read "Top level cat: " cats)))
+ (cond ((file-exists-p (expand-file-name cat org-roam-directory)) cat)
+ ((yes-or-no-p "New category, create?") cat)
+ (t (jao-roam--cat)))))
+
+ (setq org-roam-capture-templates
+ `(("d" "default" plain #'org-roam-capture--get-point
+ "%a %i"
+ :file-name "%(jao-roam--cat)/%(jao-roam--slug \"${slug}\")"
+ :head ,(concat "#+title: ${title}"
+ "\n#+created: %T"
+ "\n#+roam_tags: %?"
+ "\n#+roam_ref: %:url\n\n"))))
+
+ :custom ((org-roam-directory (expand-file-name "~/org/notes"))
+ (org-roam-encrypt-files nil)
+ (org-roam-buffer-position nil)
+ (org-roam-buffer-height nil)
+ (org-roam-buffer-window-parameters nil)
+ (org-roam-link-use-custom-faces t)
+ (org-roam-link-auto-replace t)
+ (org-roam-tag-sources '(prop vanilla all-directories))
+ (org-roam-file-exclude-regexp ".+/code\\|attic/.*"))
+
+ :config
+ (defvar org-roam-consult-flags
+ '("--null" "--ignore-case" "--type=org" "--line-buffered"
+ "--color=always" "--max-columns=250" "--no-heading" "--line-number"))
+
+ (defun consult-org-roam ()
+ "Search org-roam directory using consult-ripgrep. With live-preview."
+ (interactive)
+ (let ((consult-ripgrep-command
+ (format "rg %s . -e ARG OPTS"
+ (mapconcat 'identity org-roam-consult-flags " "))))
+ (consult-ripgrep org-roam-directory)))
+
+ (major-mode-hydra-define+ org-mode ()
+ ("Roam"
+ (("i" org-roam-insert "insert")
+ ("f" org-roam-find-file "find-file")
+ ("o" consult-org-roam "consult")
+ ("v" org-roam-buffer-toggle-display "toggle backlinks"))))
+
+ (jao-define-attached-buffer (regexp-quote org-roam-buffer) 0.33)
+
+ :hook ((after-init . org-roam-mode))
+ :bind (("C-c n" . org-roam-capture)
+ ("C-c N" . org-roam-find-file))
+ :diminish)
+ #+end_src
+* Links
+ #+begin_src emacs-lisp
+ (require 'org-gnus nil t)
+ (require 'ol-gnus nil t)
+ (require 'ol-w3m nil t)
+ (require 'ol-eshell nil t)
+ (require 'ol-bbdb nil t)
+ (require 'ol-docview nil t)
+ (require 'ol-info nil t)
+
+ (setq org-link-abbrev-alist
+ '(("jao.io" "https://jao.io/")))
+ #+end_src
+* jao-org
+ #+begin_src emacs-lisp
+ (use-package jao-org-utils)
+
+ (use-package jao-org-links
+ :commands jao-org-links-setup
+ :bind (("C-c T" . jao-org-insert-doc)))
+
+ (jao-org-utils-setup)
+ (jao-org-utils-eldoc-setup)
+ (jao-org-links-setup jao-sink-dir)
+
+ (with-eval-after-load "pdf-view"
+ (define-key pdf-view-mode-map (kbd "C-c o") #'jao-org-pdf-goto-org)
+ (define-key pdf-view-mode-map (kbd "C-c O") #'jao-org-pdf-goto-org*))
+ #+end_src
+* Geiser and org
+ #+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)
+ #+end_src
+* Keybindings
+ #+begin_src emacs-lisp
+ (define-key mode-specific-map [?a] 'org-agenda)
+ (define-key org-mode-map "\C-cv" 'jao-org-copy-link-at-point)
+ (define-key org-mode-map [(control ?c) tab] 'org-force-cycle-archived)
+ (define-key org-mode-map [(f7)] 'org-archive-to-archive-sibling)
+ (define-key org-mode-map "\C-cW" 'jao-insert-w3m-link)
+ (define-key org-mode-map "\C-c'" 'org-edit-src-code)
+ (define-key org-mode-map "\C-co" 'outline-hide-other)
+ (global-set-key "\C-cr" 'org-capture)
+ (global-set-key "\C-c\C-l" 'org-store-link)
+ (global-set-key "\C-cL" 'org-insert-link-global)
+ (global-set-key "\C-cO" 'org-open-at-point-global)
+ (global-set-key "\C-ca" 'org-agenda)
+ (global-set-key [(f3)] #'org-capture-goto-last-stored)
+ #+end_src
diff --git a/readme.org b/readme.org
new file mode 100644
index 0000000..2e067f7
--- /dev/null
+++ b/readme.org
@@ -0,0 +1,68 @@
+#+PROPERTY: header-args :tangle ~/.emacs.d/init.el :comments yes :results silent
+
+* Bootstrap
+ This is the emacs standard init file, which will load (maybe
+ tangled) [[./init.org][init.org]] the file, checking first whether a fresh tangle is
+ needed. Note that the rest of elisp tangling in init.org goes to a
+ different file (namely, the one that is loaded by
+ =~/.emacs.d/init.el=). However, also note that if [[https://github.com/jingtaozf/literate-elisp/blob/master/literate-elisp.org][literate-elisp]] is
+ installed, we load instead directly the org file. It's because of
+ that that we start by setting up packages.
+
+ Here's the directory where a checkout of this repo live:
+
+ #+begin_src emacs-lisp
+ (defvar jao-emacs-dir (expand-file-name "~/etc/emacs"))
+ #+end_src
+
+ followed by package.el's initialisation:
+
+ #+begin_src emacs-lisp
+ (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/")
+ ("org" . "https://orgmode.org/elpa/")))
+ (add-to-list 'package-archives a t))
+
+ (package-initialize)
+ #+end_src
+
+ and a tangling helper:
+
+ #+BEGIN_SRC emacs-lisp
+ (defun jao-maybe-tangle (basename)
+ (let ((el (expand-file-name (format "%s.el" basename) jao-emacs-dir))
+ (org (expand-file-name (format "%s.org" basename) jao-emacs-dir)))
+ (when (file-newer-than-file-p org el)
+ (require 'ob-tangle)
+ (org-babel-tangle-file org el))
+ el))
+ #+end_src
+
+ Finally, we load either init.org or its tangled version from
+ ~jao-emacs-dir~:
+
+ #+begin_src emacs-lisp
+ (if (require 'literate-elisp nil t)
+ (literate-elisp-load-file (expand-file-name "init.org" jao-emacs-dir))
+ (load-file (jao-maybe-tangle "init")))
+ #+end_src
+
+ You can tangle this readme to generate the minimal init.el file above.
+
+* Emacs configuration as a set of literate files
+
+- [[./init.org][init.org]]: main configuration as a literate org file; it uses
+ (besides lots of packages), many of my libraries in [[./libs][libs]], and loads
+ on demand the other org files below.
+- [[./org.org][org.org]] org mode configuration.
+- [[./consult.org][consult.org]]: completion setup using selectrum, consult and friends.
+- [[./counsel.org][counsel.org]]: completion setup using ivy, counsel and friends.
+- [[./blog.org][blog.org]]: blogging using org-static-blog.
+- [[./gnus.org][gnus.org]]: tangled to gnus.el automatically by init.org, so that it's
+ ready for loading by Gnus.
+- [[./exwm.org][exwm.org]]: configuration for exwm, loaded when ~jao-exwmn-enable~ is
+ called.