From 8b6a44f89550665cce3ec7c7ffab7a9cecf59a4f Mon Sep 17 00:00:00 2001 From: jao Date: Tue, 2 Feb 2021 05:18:42 +0000 Subject: configuration orgs --- .gitignore | 2 + blog.org | 224 +++ consult.org | 353 ++++ counsel.org | 228 +++ data/clock-world-icon.png | Bin 0 -> 9901 bytes data/eshell.alias | 21 + data/music-player-icon.png | Bin 0 -> 6469 bytes data/slack.svg | 15 + exwm.org | 506 +++++ gnus.org | 660 +++++++ init.org | 4393 ++++++++++++++++++++++++++++++++++++++++++++ org.org | 326 ++++ readme.org | 68 + 13 files changed, 6796 insertions(+) create mode 100644 blog.org create mode 100644 consult.org create mode 100644 counsel.org create mode 100644 data/clock-world-icon.png create mode 100644 data/eshell.alias create mode 100644 data/music-player-icon.png create mode 100644 data/slack.svg create mode 100644 exwm.org create mode 100644 gnus.org create mode 100644 init.org create mode 100644 org.org create mode 100644 readme.org 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 "%s" + 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 + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n") + + org-static-blog-page-preamble + (concat + "
" + " programming (and other) musings" + "
" + " about" + " | hacking" + " | archive" + " |
" + " tags" + "
" + (mapconcat #'identity jao-org-blog-tags "") + "
" + "
" + " | rss" + "
" + "
")) + #+end_src +*** Footer + #+begin_src html :tangle ~/.emacs.d/commons.html :comments no +
+ + Creative Commons License + +
+ jao.io by + jao + is licensed under a + + Creative Commons Attribution-ShareAlike 3.0 Unported License. +
+ #+end_src + + #+begin_src emacs-lisp + (setq org-static-blog-page-postamble + (with-temp-buffer + (insert-file-contents "~/.emacs.d/commons.html") + (buffer-string))) + #+end_src +* Package + #+begin_src emacs-lisp + (use-package org-static-blog + :ensure t + :init + (setq org-static-blog-use-preview 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 "mail@jao.io\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) + (" 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 Binary files /dev/null and b/data/clock-world-icon.png 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 Binary files /dev/null and b/data/music-player-icon.png 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 @@ + + + + + + + + + + + + + + + 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 ") '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: (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 "") #'jao-toggle-inactive-mode-line) + (global-set-key (kbd "") #'jao-toggle-mode-line) + (global-set-key (kbd "") #'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 (("" . 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 "") #'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 (("" . bm-next) + ("S-" . bm-previous) + ("C-" . 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: \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 (("" . 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) ;;