From 8b6a44f89550665cce3ec7c7ffab7a9cecf59a4f Mon Sep 17 00:00:00 2001 From: jao Date: Tue, 2 Feb 2021 05:18:42 +0000 Subject: configuration orgs --- init.org | 4393 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 4393 insertions(+) create mode 100644 init.org (limited to 'init.org') diff --git a/init.org b/init.org new file mode 100644 index 0000000..9cf40a2 --- /dev/null +++ b/init.org @@ -0,0 +1,4393 @@ +#+PROPERTY: header-args :tangle yes :comments yes :results silent + +* Packages +*** Use package + Bootstrap `use-package' (from [[http://www.lunaryorn.com/2015/01/06/my-emacs-configuration-with-use-package.html][here]]) + + #+begin_src emacs-lisp + (unless (package-installed-p 'use-package) + (package-refresh-contents) + (package-install 'use-package)) + (require 'use-package) + #+end_src +*** Paradox + [[https://github.com/Malabarba/paradox][GitHub - Malabarba/paradox]] + #+begin_src elisp + (use-package paradox + :ensure t + :init (setq paradox-execute-asynchronously t) + :config + (defun package-menu-filter (&optional fltr) + (interactive) + (let* ((cmps (split-string (or fltr "") ":")) + (op (car cmps)) + (arg (cadr cmps))) + (cond ((string= op "status") (package-menu-filter-by-status arg)) + ((null fltr) + (call-interactively #'package-menu-filter-by-keyword)) + ((string= op "arc") + (call-interactively #'package-menu-filter-by-archive)))))) + (paradox-enable) + #+end_src +*** Literate elisp + #+BEGIN_SRC emacs-lisp + (use-package poly-org :ensure t) + (use-package literate-elisp :ensure t) + #+END_SRC +*** ELPA Keyring + #+BEGIN_SRC emacs-lisp + (use-package gnu-elpa-keyring-update :ensure t) + #+END_SRC +*** Loading .el files + + Let's prefer .el files over .elc when they former are newer + + #+begin_src emacs-lisp + (setq load-prefer-newer t) + #+end_src + +* Initialisation +*** Portability macros + + From when we want our configurations to work on an apple tree... + + #+begin_src emacs-lisp + (defmacro jao-syscase (clauses) + (let ((cls (assoc system-type clauses))) + (when cls `(progn ,@(cdr cls))))) + + (defmacro jao-d-l (darw linux) + `(jao-syscase ((darwin ,darw) (gnu/linux ,linux)))) + + (defmacro jao-when-darwin (&rest body) + `(jao-syscase ((darwin ,@body)))) + + (defmacro jao-when-linux (&rest body) + `(jao-syscase ((gnu/linux ,@body)))) + #+end_src + +*** Paths (load, info, exec) + #+begin_src emacs-lisp + (defun jao-exec-path (file) + (let ((fn (expand-file-name file))) + (add-to-list 'exec-path fn nil) + (setenv "PATH" (concat fn ":" (getenv "PATH"))))) + + (defun jao-add-info-path (path) + (require 'info) + (add-to-list 'Info-directory-list path) ) + + (defun jao-load-path (subdir) + (let ((path (expand-file-name subdir local-lisp-dir))) + (when (file-directory-p path) (add-to-list 'load-path path)))) + #+end_src +***** Access to local directories and files + + #+begin_src emacs-lisp + (defconst jao-info-dir + (expand-file-name (jao-d-l "~/.emacs.d/info" "~/doc/info"))) + + ;; Defined in bootstrap, just the same in all systems + ;; (defconst jao-emacs-dir + ;; (expand-file-name (jao-d-l "~/.emacs.d/config" "~/etc/emacs"))) + + (defconst jao-custom-dir (expand-file-name "custom" jao-emacs-dir)) + + (defconst local-lisp-dir (jao-d-l "~/.emacs.d/lib" "~/lib/elisp")) + + (defconst jao-docs-dir (jao-d-l "~/Documents" "~/doc")) + + (defconst jao-data-dir (expand-file-name "data" jao-emacs-dir)) + (defun jao-data-file (file) (expand-file-name file jao-data-dir)) + + (defconst jao-notes-dir (expand-file-name "~/org")) + + (defconst jao-sink-dir + (file-name-as-directory (expand-file-name "sink" jao-docs-dir))) + + ;; (defvar jao-site-name (if (string-match "^\\([^.]+\\)\\.?" system-name) + ;; (match-string 1 system-name) + ;; system-name)) + + (defvar jao-site-name "site") + (defvar jao-site-dir nil) + (setq jao-site-dir (expand-file-name jao-site-name jao-emacs-dir)) + + (defun jao-site-custom-dir () + (expand-file-name "custom" jao-site-dir)) + + (defun jao-local-el (basename) + (expand-file-name (concat basename ".el") jao-site-dir)) + + (defun jao-lib-file (basename) + (expand-file-name basename local-lisp-dir)) + + (defun jao-load-local-el (basename &optional no-def) + (let ((f (concat basename ".el")) + (lf (jao-local-el basename))) + (if (file-exists-p lf) (load lf) + (if (and (not no-def) (file-exists-p f)) (load f))))) + + (defun jao-load-org (file) + (let ((file (format "%s.org" (file-name-sans-extension file)))) + (literate-elisp-load-file (expand-file-name file jao-emacs-dir)))) + #+end_src + +***** Load path initialisation + + #+begin_src emacs-lisp + (add-to-list 'load-path local-lisp-dir) + (add-to-list 'load-path jao-custom-dir) + (add-to-list 'load-path (expand-file-name "lib" jao-emacs-dir)) + #+end_src + + Let's also add to our load path all the 'jao' library files which + we publish in a separate repo: + + #+BEGIN_SRC emacs-lisp + (dolist (f (directory-files (expand-file-name "jao" local-lisp-dir) t "^[^.]+$")) + (when (file-directory-p f) (add-to-list 'load-path f))) + #+END_SRC + +***** Info paths + + And extend the info paths to include local stuff: + + #+begin_src emacs-lisp + (jao-add-info-path jao-info-dir) + ;; (jao-add-info-path "/usr/local/info") + ;; (jao-add-info-path "/usr/local/share/info") + ;; (jao-add-info-path "/usr/share/info") + #+end_src +*** Preamble (pre.el) + Private variables defined in pre.el + #+begin_src emacs-lisp + (defvar jao-mails "") + (defvar jao-mails-regexp) + + (defvar jao-irc-channels '()) + + (defvar jao-slack-client-id) + (defvar jao-slack-token) + #+end_src + + Loading pre.el + #+begin_src emacs-lisp + (jao-load-local-el "pre" t) + (setq jao-mails-regexp (regexp-opt jao-mails)) + #+end_src +*** Custom location of custom.el and co. + #+begin_src emacs-lisp + (defvar jao-custom-file (jao-local-el "custom")) + (setq custom-file jao-custom-file) + (load custom-file) + (setq custom-unlispify-tag-names nil) + (setq custom-buffer-done-kill t) + (setq widget-image-enable nil) + #+end_src +*** Bookmarks + #+BEGIN_SRC emacs-lisp + (setq bookmark-default-file "~/.emacs.d/emacs.bmk") + #+END_SRC +*** Session and history + #+BEGIN_SRC emacs-lisp + (setq backup-directory-alist (quote (("." . "~/.emacs.d/backups")))) + (setq delete-old-versions t + kept-new-versions 3 + kept-old-versions 2) + + (require 'saveplace) + (setq save-place-file (expand-file-name "~/.emacs.d/cache/places")) + (save-place-mode 1) + + (setq recentf-save-file (expand-file-name "~/.emacs.d/cache/recentf") + recentf-max-saved-items 2000 + recentf-exclude '("/home/jao/\\.emacs\\.d/elpa.*/.*" + ".*/.git/COMMIT_EDITMSG")) + (require 'recentf) + (recentf-mode 1) + + ;; Command history + (setq savehist-file (expand-file-name "~/.emacs.d/cache/history")) + (require 'savehist) + (savehist-mode t) + (setq savehist-additional-variables + '(kill-ring search-ring regexp-search-ring) + savehist-ignored-variables + '(ido-file-history)) + #+END_SRC +*** yes/no, bell, startup message + #+BEGIN_SRC emacs-lisp + ;;; change yes/no for y/n + (fset 'yes-or-no-p 'y-or-n-p) + (setq inhibit-startup-message t) + (setq visible-bell t) + #+END_SRC +*** Server + #+BEGIN_SRC emacs-lisp + (setenv "EDITOR" "emacsclient") + (when (not (daemonp)) (server-start)) + #+END_SRC +* System utilities +*** Status, minibuffer + #+begin_src emacs-lisp + (use-package jao-minibuffer + :commands (jao-minibuffer-add-variable jao-minibuffer-refresh)) + #+end_src +*** Sleep/awake + #+begin_src emacs-lisp + (use-package jao-sleep) + (jao-sleep-dbus-register) + #+end_src +*** Process runners + #+begin_src emacs-lisp + (defun jao-exec-string (fmt &rest args) + (string-trim (shell-command-to-string (apply 'format fmt args)))) + + (defun jao-exec (command) + (interactive + (list (read-shell-command "$ " + (if current-prefix-arg + (cons (concat " " (buffer-file-name)) 0) + "")))) + (start-process-shell-command command nil command)) + + (defmacro jao-def-exec (name &rest args) + `(defun ,name (&rest other-args) + (interactive) + (start-process-shell-command + ,(car args) + "* jao-exec - console *" + (string-join (append (list ,@args) other-args) " ")))) + #+end_src +*** Brightness control + #+begin_src emacs-lisp + (jao-def-exec jao-bright-up "brightnessctl" "-q" "s" "5%+") + (jao-def-exec jao-bright-down "brightnessctl" "-q" "s" "5%-") + #+end_src +*** Memory usage + #+begin_src emacs-lisp + (use-package memory-usage :ensure t) + #+end_src +*** Keyboard + #+begin_src emacs-lisp + + (use-package hydra :ensure t) + (use-package major-mode-hydra + :ensure t + :custom (major-mode-hydra-invisible-quit-key "q") + :bind (("s-SPC" . major-mode-hydra) + ("H-s-SPC" . major-mode-hydra) + ("C-s-SPC" . major-mode-hydra))) + (require 'major-mode-hydra) + + (defun jao-kb-toggle (&optional lyt) + (interactive) + (shell-command-to-string (or lyt + (if (jao-kb-toggled-p) + "setxkbmap us" + "setxkbmap us -variant intl")))) + + (defun jao-kb-toggled-p () + (not (string-empty-p + (shell-command-to-string "setxkbmap -query|grep variant")))) + #+end_src +*** Battery + #+BEGIN_SRC emacs-lisp + (use-package battery + :init (setq battery-load-low 15 + battery-load-critical 8 + battery-mode-line-limit 40 + battery-echo-area-format + "%L %r %B (%p%% load, remaining time %t)" + battery-mode-line-format "%b%p ")) + ;; (display-battery-mode 1) + #+END_SRC +* Crypto +*** PGP, EPG, passwords + #+begin_src emacs-lisp + (setq epg-pinentry-mode 'loopback) + (setq auth-source-debug nil) + + (require 'auth-source) + (add-to-list 'auth-source-protocols '(local "local")) + (setq auth-sources '("~/.emacs.d/authinfo.gpg" "~/.netrc")) + + (use-package epa-file + :init (setq epa-file-cache-passphrase-for-symmetric-encryption t) + :config (epa-file-enable)) + (require 'epa-file) + + (defun jao--get-user/password (h) + (let ((item (car (auth-source-search :type 'netrc :host h :max 1)))) + (when item + (let ((user (plist-get item :user)) + (pwd (plist-get item :secret))) + (list user (when pwd (funcall pwd))))))) + #+end_src +*** Pinentry + #+begin_src emacs-lisp + (use-package pinentry :ensure t) + (pinentry-start) + #+end_src +* Colours and themes +*** Transparency + #+begin_src emacs-lisp + (defvar jao-frames-default-alpha (if (eq window-system 'pgtk) 96 90)) + + (defun jao-sway-set-transparency () + (when jao-sway-enabled-p + (let ((alpha (/ (or (cadr (frame-parameter nil 'alpha)) 100) 100.0))) + (jao-swaymsg (format "[app_id=emacs] opacity %s" alpha))))) + + (defun jao-toggle-transparency (&optional all) + (interactive "P") + (if all + (let* ((trans (/= jao-frames-default-alpha 100)) + (new-alpha (if trans 100 jao-frames-default-alpha))) + (modify-all-frames-parameters `((alpha . (,new-alpha ,new-alpha))))) + (if (/= (or (cadr (frame-parameter nil 'alpha)) 100) 100) + (set-frame-parameter nil 'alpha '(100 100)) + (set-frame-parameter nil 'alpha (list jao-frames-default-alpha + jao-frames-default-alpha)))) + (jao-sway-set-transparency)) + + (defun jao-transparent-p () + (/= (or (cadr (frame-parameter nil 'alpha)) 100) 100)) + + (defun jao-set-transparency (&optional level) + (interactive "nOpacity (0-100): ") + (set-frame-parameter nil 'alpha (list level level)) + (jao-sway-set-transparency)) + #+end_src +*** Faces, fonts, fontsets +***** General + #+BEGIN_SRC emacs-lisp + ;;; Font lock + (global-font-lock-mode 1) + + ;; customize buttons + (setq custom-raised-buttons nil) + + ;; language environment + (set-language-environment "UTF-8") + (set-keyboard-coding-system 'latin-1) + + (setq default-input-method "catalan-prefix") + (defun jao--set-kb-system (frame) + (select-frame frame) + (set-keyboard-coding-system 'latin-1) + t) + + (add-to-list 'after-make-frame-functions 'jao--set-kb-system) + + (defun list-fonts-display (&optional matching) + "Display a list of font-families available via font-config, in a new buffer. + If the optional argument MATCHING is non-nil, only font families + matching that regexp are displayed; interactively, a prefix + argument will prompt for the regexp. + The name of each font family is displayed using that family, as + well as in the default font (to handle the case where a font + cannot be used to display its own name)." + (interactive + (list + (and current-prefix-arg + (read-string "Display font families matching regexp: ")))) + (let (families) + (with-temp-buffer + (shell-command "fc-list : family" t) + (goto-char (point-min)) + (while (not (eobp)) + (let ((fam (buffer-substring (line-beginning-position) + (line-end-position)))) + (when (or (null matching) (string-match matching fam)) + (push fam families))) + (forward-line))) + (setq families + (sort families + (lambda (x y) (string-lessp (downcase x) (downcase y))))) + (let ((buf (get-buffer-create "*Font Families*"))) + (with-current-buffer buf + (erase-buffer) + (dolist (family families) + ;; We need to pick one of the comma-separated names to + ;; actually use the font; choose the longest one because some + ;; fonts have ambiguous general names as well as specific + ;; ones. + (let ((family-name + (car (sort (split-string family ",") + (lambda (x y) (> (length x) (length y)))))) + (nice-family (replace-regexp-in-string "," ", " family))) + (insert (concat (propertize nice-family + 'face (list :family family-name)) + " (" nice-family ")")) + (newline))) + (goto-char (point-min))) + (display-buffer buf)))) + #+END_SRC +***** Unicode fonts + See [[https://emacs.stackexchange.com/questions/251/line-height-with-unicode-characters/5386#5386][fonts - Line height with unicode characters]] for a good + discussion. + #+BEGIN_SRC emacs-lisp + (defun jao--set-fontsets (frame) + ;; (set-fontset-font t 'unicode "Hack-9") + (set-fontset-font t 'greek "GFS Didot") + (set-fontset-font t 'mathematical "FreeSerif") + (set-fontset-font t 64257 "Quivira") + (set-fontset-font t 65039 nil) + (set-fontset-font t '(9472 . 9599) "Symbola") + (set-fontset-font t 'symbol "Symbola-12") ;; "Noto Sans Symbols-10" + ;; (set-fontset-font t 'symbol "Unifont-12") + (set-fontset-font t 'egyptian "Noto Sans Egyptian Hieroglyphs") + (set-fontset-font t 'hangul "NanumGothicCoding")) + (add-to-list 'after-make-frame-functions 'jao--set-fontsets) + + ;; (use-package unicode-fonts :ensure t) + ;; (unicode-fonts-setup) + + #+END_SRC +*** Themes + #+BEGIN_SRC emacs-lisp + (defvar jao-colors-use-light-scheme t) + (defvar jao-colors-theme nil) + + (defun jao-colors-scheme-dark-p () + (equal "dark" (getenv "JAO_COLOR_SCHEME"))) + + (setq custom-theme-directory + (expand-file-name "jao/themes" local-lisp-dir)) + + (setq jao-frames-default-font "Hack-9") ;; "Iosevka-10" + + (if (or (not window-system) (jao-colors-scheme-dark-p)) + (setq jao-colors-use-light-scheme nil + jao-colors-theme nil ;; 'doom + jao-colors-theme + (if (or (daemonp) window-system) jao-colors-theme 'console) + jao-colors-doom-theme 'doom-tomorrow-night + jao-colors-doom-theme 'doom-wilmersdorf + jao-colors-doom-theme 'doom-nord + jao-colors-doom-theme 'doom-sourcerer + jao-frames-fringe-mode nil + jao-vc-use-diff-hl t) + (setq jao-colors-use-light-scheme t + jao-colors-theme nil ;; 'doom + ;; jao-colors-theme 'solarized-light + jao-colors-doom-theme 'doom-solarized-light + jao-vc-use-diff-hl t + jao-frames-fringe-mode nil)) + + ;; notification colors + (setq jao-osd-cat-color-bg + (if jao-colors-use-light-scheme "grey30" "white") + jao-osd-cat-color-fg + (if jao-colors-use-light-scheme "white" "grey30")) + + (require 'jao-themes) + + (when (eq jao-colors-theme 'solarized) + (use-package solarized-theme + :ensure t + :init (setq solarized-distinct-doc-face t + solarized-distinct-fringe-background nil + solarized-scale-org-headlines nil + solarized-scale-outline-headlines nil + solarized-use-variable-pitch nil + solarized-use-less-bold t + solarized-use-more-italic t))) + + (when (eq jao-colors-theme 'doom) + (use-package doom-themes + :ensure t + :init (setq doom-nord-region-highlight 'frost + doom-nord-brighter-modeline t + doom-nord-light-region-highlight 'frost + doom-nord-light-brighter-modeline t))) + + (when (eq jao-colors-theme 'modus) + (use-package modus-operandi-theme + :ensure t + :init (setq modus-operandi-theme-completions 'opinionated + modus-operandi-theme-diffs 'desaturated + modus-operandi-theme-mode-line nil)) + + (use-package modus-vivendi-theme + :ensure t + :init (setq modus-vivendi-theme-completions 'opinionated + modus-vivendi-theme-diffs 'desaturated + modus-vivendi-theme-mode-line nil))) + + (defun jao-themes-setup () + (cond ((not jao-colors-theme) + (let ((light jao-colors-use-light-scheme)) + (load-theme (if light 'jao-light 'jao-greenish) t))) + ((eq jao-colors-theme 'modus) + (let ((light jao-colors-use-light-scheme)) + (load-theme (if light 'modus-operandi 'modus-vivendi) t))) + ((eq jao-colors-theme 'doom) + (load-theme jao-colors-doom-theme t) + ;; (load-theme 'jao-doom t) + ) + ((eq jao-colors-theme 'solarized-light) + (load-theme 'solarized-light t)) + ((eq jao-colors-theme 'zenburn) + (require 'jao-zenburn-theme) + (load-theme 'zenburn t)))) + + (when (not (eq window-system 'pgtk)) (jao-themes-setup)) + #+END_SRC +* Help system +*** Echos and suggestions + #+begin_src emacs-lisp + + (setq suggest-key-bindings 5 + echo-keystrokes 2) + + #+end_src +*** Helpful + #+begin_src emacs-lisp + (use-package helpful + :ensure t + :custom ((helpful-max-buffers 20)) + :config + (setq helpful-switch-buffer-function #'pop-to-buffer-same-window) + + :bind (("C-h k" . helpful-key) + ("C-h v" . helpful-variable) + ("C-h f" . helpful-callable) + ("C-h o" . helpful-symbol))) + #+end_src +*** Inform (links in info buffers) + #+begin_src emacs-lisp + (use-package inform :ensure t) + #+end_src +*** Man pages + #+begin_src emacs-lisp + (setq Man-notify-method 'aggressive) ;; pushy - same window + #+end_src +*** Recoll + #+begin_src emacs-lisp + (use-package jao-recoll) + #+end_src +* Window manager helpers +*** exwm + #+begin_src emacs-lisp + (setq exwm-workspace-current-index 0) + (defvar jao-exwm-enabled-p nil) + (defvar jao-exwm--use-afio t) + + (defun jao-exwm-enable () + (jao-load-org "exwm.org") + (setq jao-exwm-enabled-p t) + (jao-ednc-setup 90) + (exwm-enable) + (jao-toggle-inactive-mode-line t) + (add-hook 'after-init-hook #'jao-trisect t) + (message "Welcome to exwm")) + #+end_src + + #+begin_src shell :tangle ~/etc/config/X/exwm/xinitrc + unclutter & + xmobar-exwm -d & + emacs=${emacs:-emacs} + $emacs -mm -f jao-exwm-enable + #+end_src +*** sway + #+begin_src emacs-lisp + (defun jao-swaymsg (msg) + (shell-command (format "swaymsg '%s' >/dev/null" msg))) + + (defmacro jao-def-swaymsg (name msg) + `(defun ,(intern (format "jao-sway-%s" name)) () + (interactive) + (jao-swaymsg ,msg))) + (jao-def-swaymsg firefox "[app_id=firefox] focus") + + (defvar jao-sway-enabled-p nil) + + (defun jao-sway-run-or-focus (cmd &optional ws) + (if (not (string-blank-p (shell-command-to-string (format "pidof %s" cmd)))) + (jao-swaymsg (format "[app_id=%s] focus" cmd)) + (jao-swaymsg (format "workspace %s" (or ws 2))) + (start-process-shell-command cmd nil cmd))) + + (defun jao-sway-enable () + (setq jao-sway-enabled-p t) + (setq jao-browse-doc-use-emacs-p t) + ;; (setq frame-background-mode (if (jao-colors-scheme-dark-p) 'dark 'light)) + ;; (frame-set-background-mode nil) + (jao-themes-setup) + (jao-toggle-inactive-mode-line t) + (jao-trisect) + (display-battery-mode -1) + (jao-set-transparency 96) + (message "Welcome to sway")) + #+end_src +*** wallpaper + #+begin_src emacs-lisp + (defvar jao-wallpaper-dir "~/etc/config/X/wallpapers/") + + (defvar jao-wallpaper-random-candidates '("pattern.jpg" + "pattern2.jpg" + "bluscher.jpg" + "castle.jpg" + "einsteinstable.jpg" + "leaf.jpg" + "galaxy2.jpg" + "galaxy3.jpg" + "polyhedra.jpg" + "blade-runner2.jpg" + "abstract-blue.jpg")) + + (setq jao-wallpaper-random-candidates-light + '("white-owl.jpg" + "moon.jpg" + "einsteinstable.jpg" + "city-lighter.jpg")) + + (defun jao-set-wallpaper (&optional path) + (interactive) + (let ((current (format "~/.wallpaper.%s" + (if (jao-colors-scheme-dark-p) "dark" "light")))) + (when-let ((f (or path + (read-file-name "Image: " + jao-wallpaper-dir + (file-symlink-p current) + t)))) + (make-symbolic-link (expand-file-name f) current t) + (if jao-sway-enabled-p + (jao-swaymsg (format "output * bg %s fill" current)) + (shell-command (format "xwallpaper --zoom %s" f)))))) + + (defun jao-set-random-wallpaper () + (interactive) + (let* ((ws (if (jao-colors-scheme-dark-p) + jao-wallpaper-random-candidates + jao-wallpaper-random-candidates-light)) + (f (seq-random-elt ws))) + (jao-set-wallpaper (expand-file-name f jao-wallpaper-dir)) + (message "%s" f))) + + (add-to-list 'jao-sleep-awake-functions #'jao-set-random-wallpaper) + + #+end_src +*** screensaver and lock + #+begin_src emacs-lisp + (defun jao-screensaver-enabled () + (string= (jao-exec-string "xdg-screensaver status") "enabled")) + + (defun jao-screensaver-toggle () + (interactive) + (let ((wid (jao-exec-string "xdotool getwindowfocus"))) + (if (jao-screensaver-enabled) + (jao-exec-string "xdg-screensaver suspend %s" wid) + (jao-exec-string "xdg-screensaver resume %s" wid)) + (jao-notify (format "Using '%s'" + (jao-exec-string "xdotool getwindowname %s" wid)) + (format "Screensaver %s" + (jao-exec-string "xdg-screensaver status"))))) + + (jao-def-exec jao-xlock-screen "xdg-screensaver" "activate") + (jao-def-exec jao-suspend "sudo" "/usr/sbin/pm-suspend") + + (defun jao-lock-screen () + (interactive) + (if jao-sway-enabled-p + (shell-command "swaylock -i ~/.lockimage") + (jao-xlock-screen))) + + (pretty-hydra-define jao-hydra-sleep (:color blue) + ("Sleep" + (("l" jao-lock-screen "lock screen") + ("z" jao-suspend "sleep") + ("u" jao-screensaver-toggle "enable/disable screensaver") + ("q" nil "cancel")))) + #+end_src +* Mode line +*** Time display + #+BEGIN_SRC emacs-lisp + (setq display-time-world-list + '(("Europe/Paris" "Barcelona") + ("America/Los_Angeles" "Los Angeles") + ("America/New_York" "New York") + ("Europe/London" "London") + ("Asia/Calcutta" "Bangalore") + ("Asia/Tokyo" "Tokyo"))) + + (defun jao-time--pdt-hour () + (jao-time-at-zone "%H" "America/Los_Angeles")) + + (defun jao-time--chicago-hour () + (jao-time-at-zone "%H" "America/Chicago")) + + (defun jao-time-at-zone (format zone) + (set-time-zone-rule zone) + (prog1 (format-time-string format) + (set-time-zone-rule nil))) + + (defun jao-time-echo-la-time () + (interactive) + (message (jao-time-at-zone "LA %H:%M" "America/Los_Angeles"))) + + (defun jao-time-echo-times () + (interactive) + (let ((msg (format "%s (%s)" + (format-time-string "%a, %e %B - %H:%M") + (jao-time-at-zone "%H:%M" "America/Los_Angeles")))) + (jao-notify msg "" (jao-data-file "clock-world-icon.png")))) + + (defun jao-time-to-epoch (&optional s) + (interactive) + (let ((s (or s (read-string "Time string: " (thing-at-point 'string))))) + (message "%s = %s" + s + (round (* 1000 (time-to-seconds (parse-time-string s))))))) + + (defun jao-epoch-to-time (&optional v) + (interactive) + (let ((v (or v (read-number "Milliseconds: " (thing-at-point 'number))))) + (message "%s = %s" v + (format-time-string "%Y-%m-%d %H:%M:%S" + (seconds-to-time (/ v 1000.0)))))) + #+END_SRC +*** Mode line format + #+begin_src emacs-lisp + (setq line-number-display-limit-width 250) + (setq mode-line-position-column-format '(",%c") + mode-line-position-line-format '(" %l,%c")) + (line-number-mode 1) + (column-number-mode 1) + #+end_src +*** Mode line time + #+begin_src emacs-lisp + (setq display-time-string-forms '(24-hours ":" minutes)) + (display-time-mode -1) + #+end_src +*** Mode line toggle + #+begin_src emacs-lisp + (defun jao-toggle--face-height (face &optional all) + (let* ((h (face-attribute face :height (window-frame))) + (nh (if (eq 'unspecified h) 1 'unspecified))) + (set-face-attribute face (when (not all) (window-frame)) :height nh))) + + (defun jao-toggle-mode-line (&optional all) + (interactive "P") + (jao-toggle--face-height 'mode-line all)) + + (defun jao-toggle-inactive-mode-line (&optional all) + (interactive "P") + (jao-toggle--face-height 'mode-line-inactive all)) + + (defun jao-echo-mode-line () + (interactive) + (message "%s" (format-mode-line mode-line-format))) + + (defun jao--hide-inactive-mode-line (frame) + (set-face-attribute 'mode-line-inactive frame :height 1)) + + (add-to-list 'after-make-frame-functions #'jao--hide-inactive-mode-line) + + (global-set-key (kbd "") #'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 -- cgit v1.2.3