dotemacs/init.org

182 KiB
Raw Blame History

;; -*- lexical-binding: t -*-

Some background is important to understand my keybinding choices -

  1. I use modifier-less bindings wherever possible. Hydra and modal editing make up most of my interaction with Emacs.
  2. I use Kmonad to have Space act as Control when held. This makes Control the modifier which disrupts the typing position the least. It is also held with the thumbs - the strongest digits - reducing fatigue. So if I must use a modifier, I use Control.
  3. I use the Dvorak layout.
  4. I've used Vim and Evil for the longest time, but I like Boon's movement layout best -

    • QWERTY IO move up and down
    • QWERTY KL move left and right in small units (characters), and
    • QWERTY J; move left and right in larger units (words or s-expressions)

    This has several benefits over HJKL -

    1. To move left, the wrist need not to be angled awkwardly to sit on QWERTY HJKL (which also means the keyboard tactile nub on J does not sit under the index finger),
    2. …nor need the finger make an awkward jump to QWERTY H on each left-moving operation.
    3. The relaxed position of the fingers placed on a flat surface is a curl, not a line, which is reflected in Boon's default JIO; position.

package.el   package

(require 'package)
;; ;; when GNU ELPA is down
;; (setq package-archives (assoc-delete-all "gnu" package-archives))
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
;; Comment/uncomment this line to enable MELPA Stable if desired. See
;; `package-archive-priorities` and `package-pinned-packages`. Most
;; users will not need or want to do this.
;; (add-to-list 'package-archives '("melpa-stable" . "https://stable.melpa.org/packages/") t)
(package-initialize)

use-package   package

(add-to-list 'load-path "~/.emacs.d/elisp-git/use-package/")
(require 'use-package)
(setq use-package-compute-statistics t)

quelpa

(unless (package-installed-p 'quelpa)
  (with-temp-buffer
    (url-insert-file-contents "https://github.com/quelpa/quelpa/raw/master/quelpa.el")
    (eval-buffer)
    (quelpa-self-upgrade)))
(quelpa
 '(quelpa-use-package
   :fetcher git
   :url "https://github.com/quelpa/quelpa-use-package.git"))
(require 'quelpa-use-package)
;; Don't upgrade Quelpa packages if there's no connectivity.
;; Courtesy https://emacs.stackexchange.com/a/18515
(defun my-internet-up-p (host)
  (= 0 (call-process "ping" nil nil nil "-c" "1" "-W" "1" host)))
(setq quelpa-checkout-melpa-p nil
      quelpa-upgrade-p (my-internet-up-p "github.com"))

TODO dvorak [60%]

At first I decided to remap all Emacs, Boon, and Hydra bindings, but it turned out to not be my idea of fun. I wrote boon-dvorak, and let [most of] the others be.

(QWERTY) C-w, C-u, and C-h are probably best left as they are, since they are also present in other applications.

  1. M-n, M-p, -> M-r, M-c
  2. M-q -> M-'
  3. M
  4. multiple-cursors
  5. boon-c-god is bound to "j", which emulates QWERTY "c", but the bindings after it do not. Thus, to input "C-c C-c", one must press "j c" in Dvorak. Yuck.
  6. M-n M-n not getting bound to font-lock-fontify-block ?
  7. binding C-d to keyboard-quit in the minibuffer
(use-package general
  :commands (general-auto-unbind-keys general-def general-define-key))

Emacs-wide settings and Emacs-specific packages

(use-package emacs
  :config
  (setq gc-cons-threshold         100000000
        delete-by-moving-to-trash t
        trash-directory           "~/.trash/"
        history-length            10000
        use-file-dialog           nil
        load-prefer-newer         t
        ;; disable the disabled commands behavior
        disabled-command-function nil
        custom-file               "~/.emacs.d/custom.el"
        print-length              nil
        eval-expression-print-length nil
        edebug-print-length       nil
        ispell-dictionary         "en"
        scroll-conservatively     10000
        scroll-preserve-screen-position t
        auto-window-vscroll       nil
        locale-coding-system      'utf-8
        file-name-coding-system   'utf-8
        buffer-file-coding-system 'utf-8
        inhibit-startup-screen    t
        save-interprogram-paste-before-kill t
        large-file-warning-threshold (* 50 1000 1000))
  (set-terminal-coding-system 'utf-8)
  (set-keyboard-coding-system 'utf-8)
  (set-selection-coding-system 'utf-8)
  (prefer-coding-system 'utf-8)
  (setq-default undo-limit (* 80 1000))
  (load custom-file)
  :bind
  ("M-h"  . default-indent-new-line) ;; QWERTY M-j ;; see also org-mode
  ("M-'" . fill-paragraph)           ;; QWERTY M-q
  ("C-j" . ctl-x-map)                ;; QWERTY C-c
  ("M-n M-n" . font-lock-fontify-block)
  ;; basic Modifier-based editing commands
  ;; "C-d" 'backward-delete-char ;; unnecessary - QWERTY C-h is merely next to your index finger, but Dvorak C-h is directly under it
  ("C-," . backward-kill-word) ;; QWERTY C-w
  ;; ;; Emacs-like
  ("M-e" . kill-word)            ;; QWERTY M-d
  ("C-e" . delete-char)          ;; QWERTY C-d
  ("M-u" . forward-word)         ;; QWERTY M-f
  ("C-." . end-of-line)          ;; QWERTY C-e
  ;; new
  ("C-h" . backward-delete-char) ;; Dvorak C-h = QWERTY C-j (an improvement)
  ("C-c RET" . #'consult-mark)   ;; c m/c RET with boon-c-god
  ("C-c S-RET" . #'consult-global-mark)   ;; c M/c S-RET with boon-c-god
  ;; for swapped parenthesis and square brackets layout
  ("C-)"  . abort-recursive-edit)
  (:map text-mode-map ("M-." . forward-sentence))
  ;; ;; The old C-n/C-p/M-n/M-p were not the most comfortable to begin with, so I take this opportunity to bind them to something consistent with Boon
  (:map minibuffer-local-map
        ("C-c" . previous-line)
        ("C-r" . next-line)
        ("M-c" . previous-history-element)
        ("M-r" . next-history-element)
        ("M-p" . previous-matching-history-element))
  (:map read-expression-map ;; not sure if this works
        ("C-c" . previous-line)
        ("C-r" . next-line)
        ("M-c" . previous-history-element)
        ("M-r" . next-history-element)))

package.el   package

(use-package package
  :bind
  (:map package-menu-mode-map
        ("k" . package-autoremove)
        ("c" . previous-line)
        ("r" . next-line)
        ("X" . package-menu-execute)))

feather   package

(use-package feather
  :disabled t
  :ensure t
  :diminish
  :hook (package-menu-mode . feather-mode)
  :bind ("<f5> p " . list-packages))

esup, the Emacs StartUp Profiler

(use-package esup
  :ensure t
  :config (setq esup-depth 0))

gnutls

(use-package gnutls
  :config
  ;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=36749
  (setq gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3"))

help-mode   viewer

(use-package help-mode
  :bind
  (:map help-mode-map
        ("b" . help-go-back)
        ("f" . help-go-forward)))

helpful   viewer

(use-package helpful
  :ensure t
  :bind (("<f1> <f1>" . #'helpful-at-point)
         ("<f1> f"    . #'helpful-callable)
         ("<f1> c"    . #'helpful-command)
         ("<f1> k"    . #'helpful-key)
         ("<f1> v"    . #'helpful-variable)))

User interface

;; Simplify the GUI, thanks
;; http://www.masteringemacs.org/articles/2010/10/04/beginners-guide-to-emacs/
;; (menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)
(horizontal-scroll-bar-mode -1)
(display-time-mode t)
(setq focus-follows-mouse     t
      mouse-autoselect-window t
      display-time-format     "%a, %d %h %Y %T"
      display-time-interval   1
      use-dialog-box          nil)

;; Highlight current line
(global-hl-line-mode 1)

(defalias 'yes-or-no-p 'y-or-n-p)

font size

(setq default-frame-alist '((font . "DejaVu Sans Mono-12")))
;; (set-face-attribute 'default nil :height 120)

time

(use-package time
  :config
  (setq display-time-next-load-average t)
  (add-to-list 'zoneinfo-style-world-list '("Europe/Berlin" "Berlin")))

shackle

(use-package shackle
  :ensure t
  :init (shackle-mode)
  :config (setq shackle-rules '((Info-mode :same t))))

Theme

Must put this after loading the custom file, or I get prompted about the theme each time.

(require 'doom-themes)
(load-theme 'doom-acario-dark)

;; Similar to doom-acario-dark, with paler colors. But Org headlines are all much too similar for my liking.
(load-theme 'doom-material)
(load-theme 'doom-molokai)
;(load-theme 'distinguished t)
;(require 'github-theme)
;(require 'heroku-theme)

;;;; Good theme but uses different font sizes in org-mode, and (I think) some weird non-monospace font
;; (require 'monokai-theme)

;; ;; disabled on 2021-02-11T19:58:37+0530 - was coloring Org source blocks as comments? :\
;; (require 'molokai-theme)
;; (enable-theme 'molokai)

;; (require 'fullscreen-mode)
;; (fullscreen-mode 1)

;; (require 'relative-line-numbers)
;; ;; old
;; ;; (setq relative-line-numbers-motion-function 'vertical-motion)
;; ;; new
;; (setq relative-line-numbers-motion-function 'forward-visible-line)
;; (global-relative-line-numbers-mode 1)

;; (add-hook 'shell-mode-hook 'previous-buffer)
;; ;; is this what deletes windows when you run a shell command?
;; (add-hook 'shell-mode-hook 'delete-other-windows)

;; 2018-08-26T22:39:09+0530
(defun cp/change-split ()
  "Switch between two windows in horizontal layout and vice-versa."
  (interactive)
  (let (()))
  ;; if height of window > width, we're in a vertical split
  ;; otherwise we're in a horizontal split
  )

**

(autoload 'byte-recompile-file "bytecomp" "byte-recompile-file" t)
;; (2017-12-29T13:21:57+0530
;;  TODO - watch Org and MD files and recompile it if they are newer
;;  than their associated HTML files (e.g. I edited the source on a
;;  phone and synced it back to the laptop)
;;  see (info "(elisp) File Notifications")
;;  and (describe-function 'file-newer-than-file-p)
;;  )

**

(defun cp/after-save ()
  (let* ((file-path       (buffer-file-name))
         (file-path-shell (shell-quote-argument file-path)))
    (cl-case major-mode
      ;; ;; This would be more useful if it was only displayed when
      ;; ;; tests failed. But even a constantly failing test result
      ;; ;; being shown each time you save can be annoying.
      ;; ('emacs-lisp-mode (let ((project-dir (locate-dominating-file file-path "Cask")))
      ;;                     (when project-dir
      ;;                       (cd project-dir)
      ;;                       (compile "cask exec buttercup -L . --traceback pretty"))))
      ;; ;; Handy as long as my only means of viewing Org data on mobile was the HTML export; not so much since I have Orgzly
      ;; ('org-mode
      ;;  (pcase (file-name-nondirectory
      ;;          (buffer-file-name
      ;;           (current-buffer)))
      ;;    ((or "chronometrist.org" "chronometrist-key-values.org" "init.org") t)
      ;;    (_ (org-html-export-to-html))))
      ('LilyPond-mode
       (my-compile-project "mkly" nil "./mkly dev"))
      ('latex-mode
       (if (file-exists-p "Makefile")
           (compile (car compile-history))
         (compile (concat "xelatex " file-path-shell))))
      ;; ('markdown-mode (markdown-export))
      ('c-mode
       (compile (concat "gcc -static -o "
                        (shell-quote-argument
                         (file-name-base))
                        " "
                        file-path-shell))))))

(add-hook 'after-save-hook 'cp/after-save)

compile

(use-package compile
  :config
  ;; (add-hook 'compilation-start-hook
  ;;           (lambda (proc) (delete-other-windows)))
  (setq compilation-always-kill t))

my-compile-project

(defun my-compile-project (file &optional prefix cmd)
  "Enter ancestor directory containing FILE and run compile command CMD.
If CMD is not supplied, use `compile-command'.
With PREFIX argument and omitted CMD, prompt for command."
  (interactive "fDominating File: \nP")
  (when-let ((dir (locate-dominating-file default-directory file)))
    (cd dir))
  (compile
   (cond (prefix
          (compilation-read-command
           (or cmd "")))
         (cmd cmd)
         (t compile-command)))
  ;; (select-window (split-window-below))
  ;; (switch-to-buffer (compilation-find-buffer))
  (boon-mode))

jump to Org LP from compilation output

(defun my-org-lp-goto-error (oldfn &optional prefix &rest args)
  "Make `compile-goto-error' lead to an Org literate program, if present.
This is meant to be used as `:around' advice for `compile-goto-error'.
OLDFN is `compile-goto-error'.
With PREFIX arg, just run `compile-goto-error' as though unadvised.
ARGS are ignored."
  (interactive "P")
  (if prefix
      (funcall oldfn)
    (let (buffer position column tangled-file-exists-p)
      (save-window-excursion
        (funcall oldfn)
        (setq column (- (point) (point-at-bol)))
        ;; `compile-goto-error' might be called from the output of
        ;; `literate-elisp-byte-compile-file', which means
        ;; `org-babel-tangle-jump-to-org' would error
        (when (ignore-errors (org-babel-tangle-jump-to-org))
          (setq buffer         (current-buffer)
                position       (point)
                tangled-file-exists-p t)))
      ;; back to where we started - the `compilation-mode' buffer
      (if tangled-file-exists-p
          (let ((org-window (get-buffer-window buffer)))
            ;; if the Org buffer is visible, switch to its window
            (if (window-live-p org-window)
                (select-window org-window)
              (switch-to-buffer buffer))
            (goto-char (+ position column)))
        (funcall oldfn)))))

(advice-add 'compile-goto-error :around #'my-org-lp-goto-error)
;; (advice-remove 'compile-goto-error #'my-org-lp-goto-error)

**

(add-to-list 'load-path "~/.emacs.d/contrapunctus/")
(require 'cp-hindi)

TODO Hydra [0%]

I started off using Hydra for programming modes, when I noticed that Elisp, Common Lisp, and Scheme all had some semantically-analogous operations with different names, which could be abstracted away behind a generic interface. Then, around the time I got into using Org for literate programs, I added an Org hydra, and then a general hydra for frequently-used operations.

Add these common operations to the hydra -

  1. enlarge-window, enlarge-window-horizontally
  2. save-buffer, kill-buffer
  3. switch to last buffer, such that pressing it twice brings you back to the original buffer. Bind to "m" (same key as the one to launch the Hydra), moving magit to "M".

    • Trickier to implement than I thought.
  4. toggle-debug-on-error
  5. "insert" hydra, for timestamps, dates; in Elisp, insert as strings; in Org, in angular brackets; etc
  6. remove duplication
(use-package hydra
  :ensure t
  :commands defhydra)

common hydra heads

(defvar my-hydra-common-heads
  '(("0" delete-window "delete this" :color red)
    ("1" delete-other-windows "delete others" :color red)
    ("2" split-window-below "split below" :color red)
    ("3" split-window-right "split right" :color red)

    ("7" delete-other-windows "delete others" :color red)
    ("8" split-window-below "split below" :color red)
    ("9" split-window-right "split right" :color red)

    ("]" text-scale-increase "zoom in" :color red)
    ("[" text-scale-decrease "zoom out" :color red)
    ("\\" (text-scale-increase 0) "zoom reset" :color red)
    ("a" my-app-hydra/body "applications")
    ("z" my-app-hydra/body "applications")
    ("C" contrapunctus-mc-hydra/body "multiple cursors")
    ("d" dired-jump "dired-jump")
    ("D" (cp-insert-timestamp t) "date")
    ("f" my-search-hydra/body "find")
    ("i" (find-file "~/.emacs.d/init.org") "open init")
    ("I" contrapunctus-info-hydra/body "Info")
    ("k" (kill-buffer (current-buffer)) "kill" :color red)
    ("m" my-buffer-switch "Switch buffers")
    ("n" imenus "navigation")
    ("N" contrapunctus-line-display-hydra/body "line display")
    ("o" save-buffer "save") ;; QWERTY s
    ("s" save-buffer "save")
    ("S" imenu-list "sidebar")
    ("T" cp-insert-timestamp "timestamp")
    ("C-t" tempel-insert "template")
    ("u" find-file "new")
    ("U" launch-file "launch-file")
    ("V" volume "volume")
    ("C-v" find-alternate-file "revert")
    ("w" contrapunctus-window-hydra/body "window")))

(defmacro my-defhydra (name body docstring &rest unique-forms)
  (declare (indent defun))
  `(defhydra ,name ,body
     ,docstring
     ,@unique-forms
     ,@my-hydra-common-heads))

Line display

(defhydra contrapunctus-line-display-hydra (:color red)
  "Line display"
  ("a" adaptive-wrap-prefix-mode "adaptive-prefix-wrap")
  ("c" visual-fill-column-mode "visual-fill-column")
  ("k" visual-line-mode "visual-line")
  ("o" org-indent-mode "org-indent-mode")
  ("t" toggle-truncate-lines "truncate"))

Window

(defhydra contrapunctus-window-hydra (:color red)
  "Window"
  ("e" delete-window "delete this") ;; QWERTY d
  ("o" delete-other-windows "delete others")
  ("z" delete-window "delete this")
  ("v" delete-other-windows "delete others")
  ("t" split-window-below "split below")
  ("n" split-window-right "split right")
  ("c" enlarge-window "increase height")
  ("r" shrink-window "decrease height")
  ("h" enlarge-window-horizontally "increase width")
  ("s" shrink-window-horizontally "decrease width")
  ("b" balance-windows "balance"))

multiple cursors

(defhydra contrapunctus-mc-hydra (:color red :hint none)
  "
_a_: previous word   ^^^^                   _h_: previous whole symbol
_,_: all words       ^_i_: insert letters^  _c_: all symbols
_._: words in defun  ^_d_: insert numbers^  _r_: symbols in defun
_u_: next word       ^_l_: edit lines^      _s_: next whole symbol
"
  ("l" mc/edit-lines)

  ("a" mc/mark-previous-word-like-this)
  ("," mc/mark-all-words-like-this)
  ("." mc/mark-all-words-like-this-in-defun)
  ("u" mc/mark-next-word-like-this)
  ;; ("a" mc/mark-previous-like-this-word "previous word")
  ;; ("u" mc/mark-next-like-this-word "next word")
  ("h" mc/mark-previous-symbol-like-this)
  ("c" mc/mark-all-symbols-like-this)
  ("r" mc/mark-all-symbols-like-this-in-defun)
  ("s" mc/mark-next-symbol-like-this)
  ;; ("s" mc/mark-previous-like-this-symbol "previous symbol")
  ;; ("h" mc/mark-next-like-this-symbol "next symbol")
  ("d" mc/insert-numbers)
  ("i" mc/insert-letters))

info

(defhydra contrapunctus-info-hydra (:color blue)
  "Info"
  ("i" info "info")
  ("a" info-apropos "apropos")
  ("E" (info "(emacs)") "Emacs")
  ("e" (info "(elisp)") "Elisp")
  ("c" (info "(cl)") "CL-Lib")
  ("l" (info "(lilypond-notation)") "Lilypond notation")
  ("L" (info "(lilypond-learning)") "Lilypond learning")
  ("o" (info "(org)") "Org")
  ("g" (info "(guile)") "Guile")
  ("t" (info "(texinfo)") "Texinfo"))

Help

(defhydra my-help-hydra (:color blue)
  "Help"
  ("l" find-library "library")
  ("v" helpful-variable "variable")
  ("f" helpful-callable "function")
  ("F" describe-face "face")
  ("k" helpful-key "key")
  ("h" helpful-at-point "here")
  ("m" man "man page")
  ("c" describe-char "character")
  ("i" (eww-open-file "/media/data/contrapunctus/Documents/web/www.lispworks.com/documentation/lww42/CLIM-W/html/climguide.htm")
   "CLIM User Guide"))

General

(my-defhydra my-general-hydra (:color blue)
  "What command?"
  ("b" (my-compile-project "Makefile") "compile")
  ("B" (my-compile-project "Makefile" t) "compile (prompt)")
  ("h" my-help-hydra/body "Help")
  ("E" toggle-debug-on-error "tdoe")
  ("Q" toggle-debug-on-quit "tdoq"))

applications

(defhydra my-app-hydra (:color blue)
  "What application?"
  ;; built-in
  ("c" chronometrist "chronometrist")
  ("e" contrapunctus-emms-hydra/body "emms")
  ("i" sxiv "sxiv")
  ("l" proced "list processes")
  ("m" mu4e "mu4e")
  ("v" magit-status "magit")
  ("p" list-packages "packages")
  ("j" (my-start-app-or-switch
        "java" nil nil "josm"
        "-jar" (expand-file-name "~/josm-tested.jar"))
   "JOSM")
  ;; less-used
  ("J" my-jabber-hydra/body "Jabber")
  ("E" elpher "elpher")

  ;; external
  ("g" (my-start-app-or-switch "gajim" nil "^Gajim$") "Gajim")
  ("b" (my-start-app-or-switch
        "/media/data/contrapunctus/ext/tor-browser_en-US/Browser/start-tor-browser"
        "^Tor Browser$")
   "Tor Browser")
  ("B" (my-start-app-or-switch
        "/media/data/contrapunctus/ext/firefox/firefox" "firefox")
   "Firefox")
  ("k" (my-start-app-or-switch "keepassxc") "KeePassXC")
  ("t" (my-start-app-or-switch "xfce4-terminal") "terminal")
  ("T" (my-start-app-or-switch "transmission-gtk") "bittorrent")
  ("s" (my-start-app-or-switch "xfce4-screenshooter") "screenshot")
  ("q" (my-start-app-or-switch "qtractor") "Qtractor")
  ("W" my-eww "eww")
  ("w" eww-list-bookmarks "eww bookmarks"))

org

(defun my-compile-org-lp (&optional prefix)
  (interactive)
  (my-compile-project "Makefile" prefix))

(defhydra my-literate-elisp-hydra (:color blue)
  "Literate Elisp"
  ("l" (literate-elisp-load (buffer-file-name)) "load")
  ("b" (literate-elisp-byte-compile-file (buffer-file-name)) "byte-compile"))

(my-defhydra my-org-hydra (:color blue)
  "Org"
  ("c" org-toggle-checkbox "checkbox")
  ("L" my-literate-elisp-hydra/body "literate-elisp")
  ("p" org-set-property "property")
  ("l" my-org-hydra-block/body "source block")
  ("t" my-org-set-tags "tags")
  ("C-t" org-todo "todo" :color red)
  ("v" my-org-hydra-nav/body "navigation")
  ("b" (my-compile-org-lp) "compile")
  ("B" (my-compile-org-lp t) "compile (prompt)")
  ("C-b" contrapunctus-async-tangle "babel-tangle")
  ("e" my-org-eval-hydra/body "eval")
  ("n" consult-org-heading "consult-org-heading")

  ("h" my-help-hydra/body "Help")
  ("E" toggle-debug-on-error "tdoe")
  ("Q" toggle-debug-on-quit "tdoq"))
eval
(defhydra my-org-eval-hydra (:color blue)
  "Eval"
  ("e" org-babel-execute-src-block "eval block"))
org block
(defhydra my-org-hydra-block (:color blue)
  "Org block"
  ("l" (my-org-insert-block "SRC" nil) "Source (current language)")
  ("L" (my-org-insert-block "SRC" nil nil t) "Source (prompt)")
  ("t" (my-org-insert-block "SRC" nil ":tangle test :load test") "Source test")
  ("e" (my-org-insert-block "SRC" nil ":tangle no :load no") "Source example")
  ("o" (my-org-insert-block "QUOTE") "quote")
  ("v" (my-org-insert-block "VERSE") "verse")
  ("x" (my-org-insert-block "EXPORT" nil nil t) "Export"))
org navigation
(defhydra my-org-hydra-nav (:color red)
  "Navigation"
  ;; movement
  ("c" org-previous-visible-heading "previous heading")
  ("r" org-next-visible-heading "next heading")
  ("h" outline-up-heading "up heading")
  ("t" org-backward-heading-same-level "backward heading")
  ("n" org-forward-heading-same-level "forward heading")
  ;; folding
  ("k" outline-show-branches "branches")
  ("<tab>" org-cycle "cycle")
  ("<C-tab>" org-global-cycle "global cycle")
  ;; modification
  ("C" org-metaup "drag backward")
  ("R" org-metadown "drag forward")
  ("H" org-metaleft "promote")
  ("S" org-metaright "demote"))

jabber

(my-defhydra my-jabber-hydra (:color blue)
  "Jabber"
  ("c" jabber-connect "connect")
  ("r" jabber-display-roster "roster")
  ("n" jabber-activity-switch-to "next" :color red)

  ("E" toggle-debug-on-error "tdoe")
  ("Q" toggle-debug-on-quit "tdoq"))

Emacs Lisp

(my-defhydra my-elisp-hydra (:color blue)
  "Emacs Lisp"
  ("c" my-elisp-bytecomp-hydra/body "byte compile")
  ("b" my-compile-org-lp "compile")
  ("B" (my-compile-org-lp t) "compile (prompt)")
  ("C-b" contrapunctus-async-tangle "babel-tangle")
  ("e" my-elisp-hydra-eval/body "Eval")
  ("E" my-elisp-hydra-debug/body "Debug")
  ("h" my-help-hydra/body "Help")
  ("j" xref-find-definitions "Jump to definition")
  ("J" org-babel-tangle-jump-to-org "Jump to definition (Org)")
  ("r" ielm "REPL")
  ("t" contrapunctus-el-test/body "Test")
  ("L" my-literate-elisp-hydra/body "literate-elisp")
  ("n" imenu "imenu"))
eval
(defhydra my-elisp-hydra-eval (:color blue)
  ("b" eval-buffer "buffer")
  ("e" eval-defun  "defun")
  ("l" eval-last-sexp "last sexp"))
unit testing
(defhydra contrapunctus-el-test (:color blue)
  ("e" (my-compile-project "Cask" "cask exec buttercup -L . --traceback pretty") "buttercup")
  ("r" ert  "ert"))
debug
(defhydra my-elisp-hydra-debug (:color blue)
  "Debug"
  ("e" (funcall-interactively #'eval-defun t) "edebug")
  ("o" toggle-debug-on-error "tdoe")
  ("u" toggle-debug-on-quit "tdoq"))
byte-compile
(defhydra my-elisp-bytecomp-hydra (:color blue)
  "Byte compile"
  ("d" byte-recompile-directory "directory")
  ("c" (byte-compile-file (buffer-file-name)) "file")
  ("r" (byte-recompile-file (buffer-file-name)) "recompile")
  ("R" byte-force-recompile "force recompile"))
insert
(defhydra my-elisp-insert-hydra (:color blue)
  "Insert"
  ("g" (yas-expand-snippet (yas-lookup-snippet "cl-defgeneric")) "defgeneric")
  ("m" (yas-expand-snippet (yas-lookup-snippet "cl-defmethod")) "defmethod"))

Common Lisp

(my-defhydra my-common-lisp-hydra (:color blue)
  "Common Lisp"
  ("R" slime-connect "connect")
  ("h" my-common-lisp-help-hydra/body "Documentation")
  ("e" my-common-lisp-hydra-eval/body "Eval")
  ("E" toggle-debug-on-error "tdoe")
  ("Q" toggle-debug-on-quit "tdoq")
  ("r" slime "REPL")
  ("j" slime-edit-definition "Jump to definition")
  ("p" slime-repl-set-package "Set package")
  ("n" imenus "imenus")
  ("v" slime-inspect-presentation-at-point "inspect"))
eval
(defhydra my-common-lisp-hydra-eval (:color blue)
  "Eval (CL)"
  ("b" slime-eval-buffer "buffer")
  ("e" slime-eval-defun  "defun")
  ("r" slime-eval-region "region")
  ("l" slime-eval-last-expression "last expression")
  ("m" slime-macroexpand-1 "macroexpand"))
help
(defhydra my-common-lisp-help-hydra (:color blue)
  ("s" slime-documentation "slime")
  ("h" slime-documentation-lookup "CLHS")
  ("l" find-library "library")
  ("v" helpful-variable "variable")
  ("f" helpful-function "function")
  ("k" helpful-key "key")
  ("m" man "man page"))

Scheme

eval
(defhydra cp-scm-eval (:color blue)
  ("b" geiser-eval-buffer "buffer")
  ("e" geiser-eval-definition "defun")
  ("l" geiser-eval-last-sexp "last sexp")
  ("E" toggle-debug-on-error "tdoe")
  ("Q" toggle-debug-on-quit "tdoq"))
CHICKEN Scheme
(my-defhydra my-chicken-hydra (:color blue)
  "CHICKEN Scheme"
  ("e" cp-scm-eval/body "Eval")
  ("r" run-chicken "REPL")

  ("h" my-help-hydra/body "Help")
  ("E" toggle-debug-on-error "tdoe")
  ("Q" toggle-debug-on-quit "tdoq"))
Guile
(my-defhydra cp-guile (:color blue)
  "Guile"
  ("e" cp-scm-eval/body "Eval")
  ("r" run-guile "REPL")

  ("h" my-help-hydra/body "Help")
  ("E" toggle-debug-on-error "tdoe")
  ("Q" toggle-debug-on-quit "tdoq"))

Lilypond

(defun my-compile-ly (&optional prefix)
  (interactive)
  (my-compile-project "main.ly" prefix "./mkly dev"))

(my-defhydra my-lilypond-hydra (:color blue)
  "Lilypond"
  ("b" my-compile-ly "Compile")
  ("B" (my-compile-ly t) "Compile")

  ("h" my-help-hydra/body "Help")
  ("E" toggle-debug-on-error "tdoe")
  ("Q" toggle-debug-on-quit "tdoq"))

Prolog

(my-defhydra my-prolog-hydra (:color blue)
  "Prolog"
  ("r" ediprolog-dwim "REPL")
  ("E" toggle-debug-on-error "tdoe")
  ("Q" toggle-debug-on-quit "tdoq"))

my-dispatch-hydra

Don't try to check if there are files with a certain extension…it will lead to false positives.

(defun my-dispatch-hydra ()
  (interactive)
  (cond ((derived-mode-p 'emacs-lisp-mode
                         'inferior-emacs-lisp-mode
                         'debugger-mode)   (my-elisp-hydra/body))
        (;; (or (locate-dominating-file default-directory "build.scm")
         ;;     (locate-dominating-file default-directory "main.ly"))
         (derived-mode-p 'LilyPond-mode)
         (my-lilypond-hydra/body))
        ((and (featurep 'geiser) (bound-and-true-p geiser-mode))
         (if (eq 'guile geiser-scheme-implementation)
             (cp-guile/body)
           (my-chicken-hydra/body)))
        ((derived-mode-p 'lisp-mode
                         'slime-repl-mode) (my-common-lisp-hydra/body))
        ((derived-mode-p 'prolog-mode)     (my-prolog-hydra/body))
        ((derived-mode-p 'org-mode)        (my-org-hydra/body))
        ((derived-mode-p 'sh-mode)         (my-shell-hydra/body))
        ((derived-mode-p 'sql-mode)        (my-sql-hydra/body))
        ((derived-mode-p 'comint-mode)     (my-comint-hydra/body))
        ((derived-mode-p 'tex-mode)        (my-tex-hydra/body))
        ((derived-mode-p 'python-mode)     (my-python-hydra/body))
        (t (my-general-hydra/body))))

(use-package boon
  :bind (:map boon-command-map
              ("m" . my-dispatch-hydra)
              ("C-s" . my-dispatch-hydra)))
(global-set-key (kbd "C-s") #'my-dispatch-hydra)

shell script

(my-defhydra my-shell-hydra (:color blue)
  "Shell"
  ("e" my-shell-eval-hydra/body "eval")
  ("h" my-help-hydra/body "Help"))

(defhydra my-shell-eval-hydra (:color blue)
  "Evaluate"
  ("b" org-babel-execute-src-block "source block"))

SQL

(my-defhydra my-sql-hydra (:color blue)
  "SQL"
  ("r" sql-sqlite "REPL")
  ("e" my-sql-eval-hydra/body "eval")
  ("h" my-help-hydra/body "Help"))

(defhydra my-sql-eval-hydra (:color blue)
  "Eval"
  ("e" sql-send-paragraph "paragraph")
  ("b" sql-send-buffer "buffer"))

comint

(my-defhydra my-comint-hydra (:color blue)
  "comint"
  ("v" my-comint-nav-hydra/body "Navigation")
  ("h" my-help-hydra/body "Help"))

(defhydra my-comint-nav-hydra (:color red)
  "Navigation"
  ("c" comint-previous-prompt "previous")
  ("r" comint-next-prompt "next"))

LaTeX

(my-defhydra my-tex-hydra (:color blue)
  ("j" xref-find-definitions "Jump to definition")
  ("h" my-help-hydra/body "Help")
  ("b" (my-compile-project "Makefile") "compile")
  ("B" (my-compile-project "Makefile" t) "compile (prompt)"))

Python

(my-defhydra my-python-hydra (:color blue)
  ("r" run-python "REPL"))

marginalia

(use-package marginalia
  :ensure t
  :init (marginalia-mode))

mode line

moody

nicked from https://github.com/tarsius/moody

;; (use-package solarized-theme
;;   :config
;;   (load-theme 'solarized-light t)
;;   (let ((line (face-attribute 'mode-line :underline)))
;;     (set-face-attribute 'mode-line          nil :overline   line)
;;     (set-face-attribute 'mode-line-inactive nil :overline   line)
;;     (set-face-attribute 'mode-line-inactive nil :underline  line)
;;     (set-face-attribute 'mode-line          nil :box        nil)
;;     (set-face-attribute 'mode-line-inactive nil :box        nil)
;;     (set-face-attribute 'mode-line-inactive nil :background "#f9f2d9")))

(use-package moody
  :disabled t
  :ensure t
  :config
  (setq x-underline-at-descent-line t)
  (moody-replace-mode-line-buffer-identification)
  (moody-replace-vc-mode))

doom-modeline

(use-package doom-modeline
  :ensure t
  :init (doom-modeline-mode))

Searching

isearch

(use-package isearch
  :bind (:map isearch-mode-map
              ("C-c" . #'isearch-repeat-backward)
              ("C-r" . #'isearch-repeat-forward)
              ("M-c" . #'isearch-ring-retreat)
              ("M-r" . #'isearch-ring-advance)))

ag, the Silver Searcher

(use-package ag
  :ensure t
  :bind
  ("<f2> p" . ag)
  ("<f2> P" . ag-project-regexp)
  :config
  (setq ag-highlight-search t))

Swiper

(use-package swiper
  :disabled t
  :ensure t
  :bind (("C-s" . swiper)
         ("C-r" . swiper-backward))
  (:map swiper-map
        ("C-c" . previous-line)
        ("C-r" . next-line))
  :config
  (setq swiper-action-recenter t))

wgrep

(use-package wgrep
  :commands (wgrep-change-to-wgrep-mode))

Recenter screen on isearch matches

(add-hook 'isearch-mode-hook 'recenter)
(add-hook 'isearch-update-post-hook 'recenter)
(defadvice isearch-repeat-forward
    (after isearch-repeat-forward-recenter activate) (recenter))
(defadvice isearch-repeat-backward
    (after isearch-repeat-backward-recenter activate) (recenter))
(ad-activate 'isearch-repeat-forward)
(ad-activate 'isearch-repeat-backward)
(global-set-key (kbd "C-s") 'isearch-forward-regexp)
(global-set-key (kbd "C-r") 'isearch-backward-regexp)

**

;; (if (not (server-running-p)) (server-start))
(server-start)

grep

(use-package grep
  :bind (:map grep-mode-map
              ("c" . #'previous-error-no-select)
              ("r" . #'next-error-no-select)))

occur

(use-package replace
  :bind (:map occur-mode-map
              ("c" . #'occur-prev)
              ("r" . #'occur-next)))

Editing

(setq sentence-end-double-space nil)

(general-define-key
 "C-h"   'backward-delete-char
 "C-w"   'backward-kill-word
 "C-u"   'cp-kill-line-0
 "C-<"   'beginning-of-buffer
 "C->"   'end-of-buffer)

(define-key isearch-mode-map (kbd "C-h") 'isearch-delete-char)
(defun cp-kill-line-0 ()
  (interactive)
  (kill-line 0))

(global-unset-key (kbd "C-x C-r"))
(general-define-key
 :prefix "C-x C-r"
 "C-i" 'string-insert-rectangle
 "C-r" 'replace-rectangle
 "C-k" 'kill-rectangle)

(defun cp/downcase-dwim (arg)
  "Like `downcase-word', but if region is active, run
`downcase-region' instead. Unlike `downcase-region', rectangular
regions are handled correctly as well.")

;; open-line should always move to the beginning of the current line
;; first, so one can run it anywhere. I also want it to indent it to
;; the next or the previous line...

;; These affect org-meta-return and org-insert-heading-respect-content
;;

;; (defadvice open-line
;;     (before open-line-bol activate)
;;   (beginning-of-visual-line))
;; (defadvice open-line
;;     (after open-line-indent activate)
;;   (if (not (looking-at "\\** "))
;;       (indent-for-tab-command)))

;; (global-set-key (kbd "C-o") 'open-line)
(defun cp/open-line ()
  "The opposite of `cp/open-line-before'. Start a new line below
the current line, regardless of where point is in the current
line. Will not affect the content of the current line. Applies
the correct indentation according to the mode."
  (interactive)
  (progn
    (end-of-line)
    (electric-indent-just-newline t)
    (indent-according-to-mode)))

(defun cp/open-line-before (arg)
  "Like `open-line' but a bit more sane.

In org-mode, run org-open-line.

In other modes, insert a newline above the current line,
regardless of where point is in current line. Will not affect
content of current line. Applies the correct indentation
according to the mode."
  (interactive "p")
  (if (and (equal major-mode 'org-mode)
           (org-at-table-p))
      (org-open-line arg)
    (progn

      (beginning-of-line)
      (electric-indent-just-newline t)
      (forward-char -1)
      (indent-according-to-mode))))
(global-set-key (kbd "C-o") 'cp/open-line-before)

;; I wish you could press e.g. S-o in god-mode to get C-S-o.
;; 10/10 would make life better.
(global-set-key (kbd "C-S-o") 'cp/open-line)

(setq dired-bind-jump nil)
(global-set-key (kbd "C-x C-j") 'join-line)

;; C-z (suspend-frame) is utterly useless and disruptive - good place to put universal-argument
(global-unset-key (kbd "C-z"))
(global-set-key (kbd "C-z") 'universal-argument)
(global-set-key (kbd "C-x C-z") 'repeat)
(global-set-key (kbd "C-x C-;") 'repeat-complex-command)

;; (defun cp-kill-line (&optional arg)
;;   (interactive)
;;   (if arg
;;       (if mark-active
;;           (kill-region arg)
;;         (kill-line arg))
;;     (if mark-active
;;       (kill-region)
;;       (kill-line))))
;; (global-set-key (kbd "C-k") 'cp-kill-line)
;; Chris Done (god-mode) recommends this, but Emacs' repeat command is almost useless...
;; (global-set-key (kbd "C-.") 'repeat)

(defun my-delete-trailing-whitespace ()
  (unless (derived-mode-p 'markdown-mode)
    (delete-trailing-whitespace)))

(add-hook 'before-save-hook 'my-delete-trailing-whitespace)

;; BUG - (dired-get-marked-files) returns file name at point if none are marked
;; BUG - (dired-get-marked-files) always returns an alphabetically-sorted list, even when the Dired buffer is sorted by date
(defun cp/marked-files->markup-links ()
  "From a Dired buffer, insert the file at point or marked files
as links into an Org or Markdown document."
  (interactive)
  (if (derived-mode-p 'dired-mode)
      (let* ((filenames (dired-get-marked-files 'no-dir))
             (other-buffer  (->> (window-list)
                                 (cadr)
                                 (window-buffer)))
             (other-buffer-mode (with-current-buffer other-buffer major-mode))
             (two-window-op (and (= (length (window-list)) 2)
                                 (member other-buffer-mode
                                         '(org-mode markdown-mode))))
             (output-buffer (if two-window-op
                                other-buffer
                              (read-buffer "Insert links in buffer: ")))
             (output-mode (with-current-buffer output-buffer major-mode))
             (output-text (cl-case output-mode
                            ('org-mode
                             (cp/marked-files->markup-links-org filenames))
                            ('markdown-mode
                             (cp/marked-files->markup-links-md filenames))
                            (t (error "Only Markdown and Org are currently supported.")))))
        (if two-window-op
            (other-window 1)
          (switch-to-buffer output-buffer))
        (mapc #'insert output-text))
    (user-error "Run this command from a Dired buffer with some marked files."))
  (when markdown-inline-image-overlays
    (markdown-display-inline-images)))
(defun cp-insert-timestamp (&optional date-only-p)
  "Insert current date and time in ISO-8601 format in the current buffer."
  (interactive)
  (let ((timestamp (format-time-string
                    (if date-only-p "%F" "%FT%T%z"))))
    (cond ((derived-mode-p 'emacs-lisp-mode)
           (insert "\"" timestamp "\""))
          (t (insert timestamp)))))

text-mode

(use-package text-mode
  :bind
  (:map text-mode-map
        ("M-c" . 'org-drag-line-backward)
        ("M-r" . 'org-drag-line-forward)))

undo-tree

(use-package undo-tree
  :ensure t
  :commands global-undo-tree-mode
  :if (not (featurep 'evil))
  :diminish undo-tree-mode
  :init (global-undo-tree-mode)
  :hook (wdired-mode . undo-tree-mode)
  :config (setq undo-tree-auto-save-history nil))

ediff

(use-package ediff
  :config
  (setq ediff-window-setup-function 'ediff-setup-windows-plain)
  ;; boon-like bindings
  (add-hook 'ediff-keymap-setup-hook
            (lambda ()
              (define-key ediff-mode-map (kbd "c") 'ediff-previous-difference)
              (define-key ediff-mode-map (kbd "r") 'ediff-next-difference))))

iedit

(use-package iedit
  :ensure t
  :bind (("C-;" . iedit-mode)
         ("C-:" . iedit-mode-toggle-on-function)
         (:map iedit-mode-keymap
               ("C-h" . backward-delete-char))))

multiple cursors

(use-package multiple-cursors
  :ensure t
  :bind (("C-M-l" . #'mc/mark-previous-like-this-symbol)
         ("C-M-;" . #'mc/mark-next-like-this-symbol)
         ("C-M-'" . #'mc/mark-all-symbols-like-this)

         ("C-M-," . #'mc/mark-previous-word-like-this)
         ("C-M-." . #'mc/mark-next-word-like-this)
         ("C-M-/" . #'mc/mark-all-words-like-this)
         ("C-M-<" . #'mc/unmark-previous-like-this)
         ("C-M->" . #'mc/unmark-next-like-this)

         ("C-M-]" . #'mc-hide-unmatched-lines-mode)))

transpose commands

(global-unset-key (kbd "C-t"))
(general-define-key
 :prefix "C-t"
 "C-t C-c" 'transpose-chars
 "C-t C-w" 'transpose-words
 "C-t C-l" 'transpose-lines
 "C-t C-s" 'transpose-sentences
 "C-t C-e" 'transpose-sexps
 "C-t C-p" 'transpose-paragraphs)

atomic-chrome

Doesn't work in my current main browser (Tor Browser), and thus barely used of late…

(use-package atomic-chrome
  :ensure t
  :init (atomic-chrome-start-server)
  :config
  (setq atomic-chrome-url-major-mode-alist
        '(("wikisource" . mediawiki-mode))))

Unicode keys

(general-auto-unbind-keys)
(general-def
  :prefix "C-t"
  ;; currency
  "r" [?₹] "- L" [] "L -" [] "e" [?€]
  "." [?…]

  "- m" [?—]  "- n" [?] "m -" [?—]  "n -" [?]
  "t" [?™]

  ;; German
  "\" a" [] "a \"" [] "\" A" [] "A \"" []
  "\" e" [] "e \"" [] "\" E" [] "E \"" []
  "\" o" [] "o \"" [] "\" O" [] "O \"" []
  "\" u" [] "u \"" [] "\" U" [] "U \"" []
  "\" i" [] "i \"" [] "\" I" [] "I \"" []

  ;; ;; " a/e/o/u are slow to type...
  ;; "a" [?ä] "A" [?Ä]
  ;; "e" [?ë] "E" [?Ë]
  ;; "o" [?ö] "O" [?Ö]
  ;; "u" [?ü] "U" [?Ü]

  ;; but "o" conflicts with "o o" for °, so...
  "u a" [] "u A" []
  "u e" [] "u E" []
  "u o" [] "u O" []
  "u u" [] "u U" []
  "s s" []

  ;; French
  ", c" [] "c ," [] ", C" [] "C ," []
  "' e" [] "e '" [] "' E" [] "E '" []
  "e `" [] "` e" [] "E `" [] "` E" []
  "e ^" [] "^ e" [] "E `" [] "` E" []
  "' a" [] "a '" [] "' A" [] "A '" []
  "a `" [] "` a" [] "A `" [] "` A" []
  "a ^" [] "^ a" [] "A ^" [] "^ A" []
  "~ n" [] "n ~" [] "~ N" [] "N ~" []

  "e e" []

  "o e" [] "O E" []
  "o o" [] "x" [?×]
  "1 2" [] "1 3" [?⅓] "1 4" []
  "2 3" [?⅔] "2 5" [?⅖]
  "3 4" [] "3 5" [?⅗]
  "4 5" [?⅘]

  "b" [?♭] "#" [?♯]

  "< -" [?←] "- >" [?→]
  "^ |" [?↑]  "| ^" [?↑]
  "v |" [?↓]  "| v" [?↓]
  "l" []
  "f" []
  "d q o" [?“]
  "d q c" [?”]
  "s q o" [?]
  "s q c" [?])
(general-auto-unbind-keys t)
(setq default-input-method "devanagari-itrans")

Suggestion by lampilelo for extending iso-transl-ctl-x-8-map (https://dpaste.com/BAQUXSDVL.txt)

(eval-after-load 'iso-transl
  '(let ((map (make-sparse-keymap)))
     (define-key map (kbd "c") [])
     (define-key map (kbd "C") [])
     (define-key iso-transl-ctl-x-8-map (kbd "v") map)))

modal editing

active boon config

(use-package boon
  :ensure t
  :commands (boon-mode)
  :quelpa (boon :fetcher github :repo "jyp/boon")
  :bind
  (:map boon-command-map
        ("C-l" . recenter-top-bottom)
        ("p"   . consult-line)
        (". p" . my-consult-line)
        (". c" . #'isearch-backward-regexp)
        (". r" . #'isearch-forward-regexp)
        (", r" . #'iy-go-up-to-char)
        (", c" . #'iy-go-up-to-char-backward)
        ("I"   . join-line)             ;; QWERTY G
        ("U"   . #'consult-yank-pop)
        ;; ("TAB" . 'company-indent-or-complete-common) ;; this works,
        ;; but also breaks unfolding in org mode :\ ("<tab>" .
        ;; 'company-indent-or-complete-common)
        ("J"   . 'boon-toggle-comment)
        ("y"   . nil)                   ;; QWERTY t
        ("y w" . 'transpose-words)
        ("y e" . 'transpose-sexps)
        ("y l" . 'transpose-lines)
        ("y p" . 'transpose-paragraphs)
        ("y c" . 'transpose-chars)
        ;; ("\\" . projectile-command-map) ;; error - define this in projectile's `use-package'
        ;; are these being buggy?
        ;; ("("  . boon-navigate-backward)
        ;; (")"  . boon-navigate-forward)
        ("M"   . consult-buffer)
        ("H"   . consult-buffer)
        ;; these I prefer in their Dvorak positions rather than their QWERTY positions
        ("/" . undo-tree-undo)
        ("?" . undo-tree-redo)
        ("z" . boon-repeat-command)
        ("DEL" . #'my-buffer-switch)
        ("RET" . #'consult-buffer))
  (:map boon-x-map
        ("," . 'write-file)
        ("o" . 'save-buffer)
        ("e" . 'dired-jump)
        ("." . 'eval-last-sexp)
        ("u" . 'find-file)
        ("j" . 'kill-emacs)
        ("x" . 'ibuffer)
        (")" . text-scale-adjust)
        ("(" . text-scale-adjust))
  :config
  (require 'boon-dvorak)
  ;; (add-to-list 'boon-enclosures `(40 "(" ")")) ;; 40 = (
  ;; (use-package boon-powerline)
  ;; (boon-powerline-theme)
  (mapc (lambda (mode)
          (add-to-list 'boon-special-mode-list mode))
        '(emms-playlist-mode
          emms-browser-mode
          edebug-mode
          sldb-mode
          macrostep-mode
          view-mode
          slime-popup-buffer-mode
          finder-mode
          inferior-emacs-lisp-mode
          shell-mode))
  (add-to-list 'boon-special-conditions
               '(or (bound-and-true-p edebug-mode)
                    (bound-and-true-p view-mode)
                    (bound-and-true-p macrostep-mode)
                    (bound-and-true-p slime-popup-buffer-mode)
                    (derived-mode-p 'comint-mode)))
  (setq hi-lock-auto-select-face t)
  ;; (define-key boon-command-map (kbd "x d f") nil)
  ;; :hook
  ;; ((text-mode . turn-on-boon-mode)
  ;;  (prog-mode . turn-on-boon-mode)
  ;;  (shell-mode . turn-on-boon-mode)
  ;;  (helpful-mode . turn-on-boon-mode)
  ;;  (fundamental-mode . turn-on-boon-mode))
  :init
  (boon-mode))

Evil mode

(use-package evil
  :disabled
  :init (evil-mode 1)
  ;; Want something to switch between evil and god-mode, instead of this.
  ;; They say there's a plugin for it...
  :bind
  (("C-x e" evil-mode)
   ;;(global-set-key (kbd "g C-g") 'count-words-region)
   (:map evil-normal-state-map
         (;; ("<tab>" forward-button)
          ("zR" . 'yafolding-show-all)
          ("zM" . 'yafolding-hide-all)
          ("zo" . 'yafolding-show-element)
          ("zc" . 'yafolding-hide-element)

          ;; The story so far - in evil-normal-state-map, I set 'i' to
          ;; evil-previous-visual-line, 'k' to evil-next-visual-line,
          ;; 'j' to evil-backward-char. Note that I forgot to set 'h'
          ;; to insert at first...but that's not all - later I realized
          ;; this didn't affect Visual mode :p 4 more lines for that.
          ;; Next...d/c didn't work with ijkl. 4 more lines for
          ;; evil-operator-state-map. After that - you usually want
          ;; visual-line up/down movement, but _not_ when you hit d-j/k
          ;; (or d-i/k)! So, in evil-operator-state-map, i and k are
          ;; now vanilla evil-previous/next-line. Set gi/gk to
          ;; evil-previous/next-line in normal and visual states, and
          ;; to evil-previous/next-visual-line in operator state - 2
          ;; new lines to each group of 4.
          ("gi" . evil-previous-visual-line)
          ("gk" . evil-next-visual-line)
          ( "i" . evil-previous-line)
          ( "k" . evil-next-line)
          ( "j" . evil-backward-char)
          ( "h" . evil-insert)))
   :map evil-visual-state-map
   ("gi" evil-previous-visual-line)
   ("gk" evil-next-visual-line)
   ("i" evil-previous-line)
   ("k" evil-next-line)
   ("j" evil-backward-char)
   ("h" evil-insert)

   :map evil-operator-state-map
   ("gi" evil-previous-visual-line)
   ("gk" evil-next-visual-line)
   ("i" evil-previous-line)
   ("k" evil-next-line)
   ("j" evil-backward-char)
   ("h" evil-insert)
   ;; evil-inner-tag ?
   ("hW" evil-inner-WORD)
   ("hw" evil-inner-word)
   ("h\(" evil-inner-paren)
   ("h\)" evil-inner-paren)
   ("h\{" evil-inner-curly)
   ("h\}" evil-inner-curly)
   ("h<" evil-inner-angle)
   ("h>" evil-inner-angle)
   ("h\[" evil-inner-bracket)
   ("h\]" evil-inner-bracket)
   ("hs" evil-inner-sentence)
   ("hp" evil-inner-paragraph)
   ("h`" evil-inner-back-quote)
   ("h\"" evil-inner-double-quote)
   ("h'" evil-inner-single-quote)

   :map evil-motion-state-map
   ("gi" evil-previous-visual-line)
   ("gk" evil-next-visual-line))

;;;; set C-h to backspace when editing commands and searching
  (defun kill-start-of-line ()
    "Kill from point to start of line."
    (interactive)
    (kill-line 0))
  (define-key evil-insert-state-map (kbd "C-u") 'kill-start-of-line)

;;;; evil-snipe
  (with-eval-after-load 'evil-snipe
    (setq evil-snipe-count-scope 'letters)
    ;; This doesn't take two chars by default, which combined with
    ;; unimplemented evil-snipe-count-scope 'vertical makes it nearly
    ;; useless as a replacement for fFtT;
    (evil-snipe-replace-evil)
    (global-evil-snipe-mode 1)))

Applications

Things not directly pertaining to text editing.

(defun my-find-file* (&rest paths)
  (mapc #'find-file (mapcar #'expand-file-name paths)))

time tracking - chronometrist   creator

choice.el is required by chronometrist-key-values

chronometrist

(use-package chronometrist
  ;; :disabled t
  :load-path "/media/data/contrapunctus/Documents/Text Files/programming/elisp/chronometrist/elisp/"
  :hook
  (chronometrist-kv-read-mode . visual-line-mode)
  (chronometrist-sexp-mode . auto-revert-mode)
  (chronometrist-sexp-mode . visual-line-mode)
  (kill-emacs-query-functions . chronometrist-query-stop)
  :bind (("<f9>"        . chronometrist)
         ("<kp-insert>" . chronometrist)
         (:map chronometrist-mode-map
               ("c" . previous-line)
               ("r" . next-line)
               ("R" . chronometrist-report))
         (:map chronometrist-report-mode-map
               ("h" . chronometrist-report-previous-week)
               ("s" . chronometrist-report-next-week))
         (:map chronometrist-details-mode-map
               ("t" . #'chronometrist-details-previous-range)
               ("n" . #'chronometrist-details-next-range)))
  :config
  (chronometrist-goal-minor-mode)
  (chronometrist-spark-minor-mode)
  (setq chronometrist-debug-enable t
        chronometrist-active-backend       :plist-group
        chronometrist-before-in-functions  '()
        chronometrist-after-in-functions   '(;; chronometrist-tags-add
                                  ;; chronometrist-kv-add
                                  my-start-project)
        chronometrist-before-out-functions '(;; my-before-project-stop
                                  ;; chronometrist-tags-add
                                  ;; chronometrist-tag-choice
                                  chronometrist-key-values-unified-prompt
                                  ;; chronometrist-kv-add
                                  ;; chronometrist-skip-query-reset
                                  )
        chronometrist-after-out-functions  '(my-after-project-stop)
        chronometrist-activity-indicator   'my-chronometrist-activity-indicator
        chronometrist-details-display-key-values #'contrapunctus-display-key-values-function
        chronometrist-details-schema `[("#" 3 t)
                            ("Task" 20 t)
                            ("Tags" 20 t)
                            ("Details" 55 t)
                            ("Duration" 20 t :right-align t :pad-right 3)
                            ("Time" 10 t)]))

Some Chronometrist configuration I prefer to keep private.

(require 'my-chronometrist)
activity-indicator
(defun my-chronometrist-activity-indicator ()
  (--> (chronometrist-latest-record (chronometrist-active-backend))
       (plist-put it :stop (chronometrist-format-time-iso8601))
       (list it)
       (chronometrist-plists-to-durations it)
       (-reduce #'+ it)
       (truncate it)
       (chronometrist-format-duration it)))
find-two-files
(defun contrapunctus-find-two-files (file-1 file-2)
  "Open FILE-1 and FILE-2 in new windows.
FILE-1 will appear above FILE-2."
  (find-file-other-window file-2)
  (split-window-below)
  (find-file file-1))
outline-open-heading
(defun cp-outline-open-heading (n)
  (goto-char (point-min))
  (outline-next-visible-heading n)
  (outline-show-subtree))
commit-prompt
(autoload 'magit-anything-modified-p "magit")

(defun contrapunctus-commit-prompt ()
  "Prompt user if `default-directory' is a dirty Git repository.
Return t if the user answers yes, if the repository is clean, or
if there is no Git repository.

Return nil (and run `magit-status') if the user answers no."
  (cond ((not (magit-anything-modified-p)) t)
        ((yes-or-no-p
          (format "You have uncommitted changes in %S. Really clock out? "
                  default-directory)) t)
        (t (magit-status) nil)))
after-project-stop

FIXME -

  1. instead of changing the last plist, change the last plist with name "OSM" and without tags or key-values (because a new plist may have been inserted in the meantime)

When I clock out of the "OSM" task, this code stores changeset details in it.

Requirements
(use-package request :ensure t)
(use-package esxml :ensure t)
(use-package esxml-query)
my-get-changeset-comment
(defun my-get-changeset-comment (changeset)
  (->> (esxml-query "[k=comment]" changeset)
       (esxml-node-attributes)
       (alist-get 'v)))
my-make-osm-url
(defun my-make-osm-url (id)
  (concat "https://www.openstreetmap.org/changeset/" id))
my-get-changeset-comment
(defun my-get-saved-changeset-id (backend)
  (-let* (((&plist :changesets saved-changesets)
           (cl-loop for plist in
             ;; the very latest OSM plist is the one we just created -
             ;; we want the one before that
             (rest (chronometrist-to-list backend))
             when (and (equal "OSM" (plist-get plist :name))
                       ;; ignore plists without a :changesets keyword
                       (plist-get plist :changesets))
             return plist))
          (((&plist :osm-url last-url)) (last saved-changesets)))
    (when last-url
      (first (last (split-string last-url "/"))))))
my-save-osm-details
(cl-defun my-save-osm-details (&key data &allow-other-keys)
  (-let* ((backend (chronometrist-active-backend))
          (last-id (my-get-saved-changeset-id backend))
          (response-changesets (esxml-node-children data))
          (new-changesets
           (if last-id
               (cl-loop with id
                 for changeset in response-changesets do
                 (setq id (alist-get 'id
                                     (esxml-node-attributes changeset)))
                 if (equal id last-id)
                 return (reverse changesets)
                 else collect
                 (list :osm-url (my-make-osm-url id)
                       :osm-comment (my-get-changeset-comment changeset))
                 into changesets)
             ;; no changeset information in file - just use
             ;; the latest changeset from the response
             (let* ((changeset (first response-changesets))
                    (id (alist-get 'id (esxml-node-attributes changeset)))
                    (comment   (my-get-changeset-comment changeset)))
               `((:osm-url ,(my-make-osm-url id)
                           :osm-comment ,comment)))))
          (new-plist (chronometrist-plist-update
                      (chronometrist-latest-record (chronometrist-active-backend))
                      `(:changesets ,new-changesets))))
    ;; (message "Last changeset ID - %s" last-id)
    ;; don't update if the latest changeset ID was already recorded
    (when new-changesets
      (chronometrist-replace-last (chronometrist-active-backend) new-plist))))
my-save-osm-changeset-details
(defun my-save-osm-changeset-details ()
  "Save OSM changeset details in the Chronometrist file."
  (request
    "https://api.openstreetmap.org/api/0.6/changesets"
    :params '(("display_name" . "contrapunctus"))
    :parser (lambda () (libxml-parse-xml-region (point) (point-max)))
    :success #'my-save-osm-details))
my-after-project-stop
(defun my-after-project-stop (project)
  (pcase project
    ("OSM"
     (delete-other-windows)
     ;; What should we do when there's no network connectivity?
     ;; Ideally - note the clock-out time, and retry every five
     ;; minutes. When connected, request the changesets, look for the
     ;; first changeset with a "created_at" which is less than the
     ;; clock-out time.
     (my-save-osm-changeset-details))
    (_ (delete-other-windows))))
display-key-values

Schema I use for plists - values can be -

  1. integer
  2. "string" | ("list" "of" "strings")
  3. (INTEGER . "string")
  4. mixed alist - elements can be either #1 or #2
  5. a plist, or a list of plists

Programming - :project, [ :component,] :feature

What if…

  1. (a b c) -> "a, b, c"
  2. (a . b) -> "a b" (e.g. (a (b . c) d) -> "a, b c, d")
  3. sublist - recurse
;; (contrapunctus-objects-to-string " - " nil) => ""
;; (contrapunctus-objects-to-string " - " 1) => "1"
(defun contrapunctus-objects-to-string (separator &rest args)
  "Return ARGS as a string, removing nil values."
  (mapconcat (lambda (elt)
               (format "%s" elt))
             (flatten-list
              (seq-filter #'identity args))
             separator))

(defun contrapunctus-display-key-values-helper (list)
  (cl-loop for elt in list
    collect
    (cond ((stringp elt) elt)
          ((chronometrist-pp-pair-p elt)
           (format "%s %s" (car elt) (cdr elt)))
          ((listp elt)
           (contrapunctus-display-key-values-function elt))
          (t "")) into strings
    finally return
    (mapconcat #'identity (seq-filter #'identity strings) ", ")))

(defun contrapunctus-display-key-values-function (plist)
  "Function used to print key-values in `chronometrist-details' buffers."
  (let ((values (seq-remove #'keywordp (chronometrist-plist-key-values plist))))
    (contrapunctus-display-key-values-helper values)))
my-chronometrist-copy-exercise-data
(defun my-chronometrist-copy-exercise-data ()
  "Add exercise data from OsmAnd tracks to Chronometrist."
  (interactive)
  ;; Date for which to get data
  (if-let*
      ((date-iso (read-string "Date (ISO-8601): " (format-time-string "%F")))
       (date     (parse-iso8601-time-string date-iso))
       (dir      (concat
                  "/media/data/phone/contrapunctus/"
                  "Nokia 6.1/Android/data/net.osmand.plus/files/tracks/rec/"
                  (format-time-string "%+4Y-%m/" date)))
       (dir-check (file-exists-p dir))
       (plists    (cl-loop with activity
                    for file in (directory-files dir)
                    when (and (string-match-p (format-time-string "^%F_" date)
                                              file)
                              (or (and (string-match-p "(walk)" file)
                                       (setq activity :walking))
                                  (and (string-match-p "(run)" file)
                                       (setq activity :running))))
                    collect
                    (-let* ((file-name file)
                            ((from to)
                             (progn
                               (string-match
                                (rx (group (+? num) "-" (= 2 num) "-" (= 2 num)
                                           "_" (= 2 num) "-" (= 2 num)
                                           "_" (= 3 (any alpha)))
                                    " "
                                    (group "(" (or "walk" "run") ")")
                                    " "
                                    (group (+? (any printing)))
                                    (group "\.gpx"))
                                file-name)
                               (split-string
                                (match-string 3 file-name) " - "))))
                      (with-current-buffer
                          (get-buffer-create
                           (find-file-noselect (concat dir file)))
                        (append '(:name "Exercise")
                                (list activity
                                      (cons (floor (my-gpx-distance file))
                                            "meters"))
                                (if to
                                    (list :from from :to to)
                                  (list :location from))
                                (my-get-gpx-timestamps))
                        ;; (chronometrist-insert (chronometrist-active-backend))
                        ))))
       (string (mapconcat (lambda (plist)
                            (chronometrist-plist-pp plist))
                          plists "\n")))
      (progn (kill-new string)
             (message "Copied %s to kill ring" string))
    (message "No suitable GPX files found")))
my-great-circle-distance

https://stackoverflow.com/questions/365826/calculate-distance-between-2-gps-coordinates https://www.movable-type.co.uk/scripts/latlong.html

(defun my-great-circle-distance (lat1 lon1 lat2 lon2)
  (let* ((earth-radius-km (* 6371 1000))
         (dlat (degrees-to-radians (- lat2 lat1)))
         (dlon (degrees-to-radians (- lon2 lon1)))
         (lat1 (degrees-to-radians lat1))
         (lat2 (degrees-to-radians lat2))
         (a    (+ (* (sin (/ dlat 2))
                     (sin (/ dlat 2)))
                  (* (sin (/ dlon 2))
                     (sin (/ dlon 2))
                     (cos lat1)
                     (cos lat2))))
         (c    (* 2 (atan (sqrt a)
                          (sqrt (- 1 a))))))
    (* earth-radius-km c)))

;; (my-great-circle-distance 51.5 0 38.8 -77.1)
my-gpx-distance
(defun my-gpx-distance (file)
  "Return distance covered (in meters) by GPX track FILE."
  (with-current-buffer (get-buffer-create (find-file-noselect file))
    (let* ((dom (libxml-parse-xml-region (point-min) (point-max))))
      (cl-flet ((attr-as-num (key attributes)
                             (string-to-number
                              (alist-get key attributes))))
        (loop with previous-attributes
          for point in (esxml-query-all "trkpt" dom)
          if previous-attributes
          sum (let* ((attributes (xml-node-attributes point))
                     (lat2 (attr-as-num 'lat attributes))
                     (lon2 (attr-as-num 'lon attributes))
                     (lat1 (attr-as-num 'lat previous-attributes))
                     (lon1 (attr-as-num 'lon previous-attributes)))
                (my-great-circle-distance lat1 lon1 lat2 lon2))
          do (setq previous-attributes (xml-node-attributes point)))))))

goal

(use-package chronometrist-goal
  :commands (chronometrist-goal-minor-mode)
  :load-path "~/.emacs.d/contrapunctus/chronometrist-goal/"
  :hook (chronometrist-mode . chronometrist-goal-minor-mode)
  :config
  (setq alert-default-style 'libnotify))

spark

(use-package chronometrist-spark
  :commands (chronometrist-spark-minor-mode)
  :load-path "/media/data/contrapunctus/Documents/Text Files/programming/elisp/chronometrist/elisp/"
  :hook (chronometrist-mode . chronometrist-spark-minor-mode)
  :config (setq chronometrist-spark-length (* 7 4)))

key-values

(use-package chronometrist-key-values
  :ensure t
  :after chronometrist
  :load-path "/media/data/contrapunctus/Documents/Text Files/programming/elisp/chronometrist/elisp/")
my-get-gpx-timestamps
(defun my-get-gpx-timestamps ()
  "Return ISO-8601 timestamps from GPX data as ISO-8601 timestamps in local time."
  (interactive)
  (let* ((dom         (libxml-parse-xml-region (point-min) (point-max)))
         (trkseg      (esxml-query "trkseg" dom))
         (first-trkpt (third trkseg))
         (first-iso   (second (alist-get 'time first-trkpt)))
         (first-unix  (parse-iso8601-time-string first-iso))
         (last-trkpt  (first (last trkseg)))
         (last-iso    (second (alist-get 'time last-trkpt)))
         (last-unix   (parse-iso8601-time-string last-iso)))
    (list :start (chronometrist-format-time-iso8601 first-unix)
          :stop  (chronometrist-format-time-iso8601 last-unix))))
my-copy-gpx-timestamps
(defun my-copy-gpx-timestamps ()
  "Copy ISO-8601 timestamps from GPX data as ISO-8601 timestamps in local time."
  (interactive)
  (let* ((plist  (my-get-gpx-timestamps))
         (string (apply #'format "%S %S\n%S %S" plist)))
    (kill-new string)
    (apply #'message "Copied %S to kill ring" plist)))

Third Time

(use-package chronometrist-third
  :load-path "/media/data/contrapunctus/Documents/Text Files/programming/elisp/chronometrist/elisp/"
  :commands (chronometrist-third-minor-mode)
  :init (chronometrist-third-minor-mode))

querying data

An example of querying the Chronometrist file data - finding out how much time I've spent on tasks matching a certain criteria.

;; XXX - day count is incorrect
(cl-loop for plist in (chronometrist-to-list (chronometrist-active-backend))
  with count = 0
  when (and (equal (plist-get plist :name) "Composing")
            (equal (plist-get plist :song) "आदि काल से आज तलक"))
  sum (chronometrist-interval plist) into seconds
  and do (cl-incf count)
  finally return
  (unless (zerop seconds)
    (format "%s, in %s days."
            (format-seconds "%Y, %D, %H, %M, and %S%z" seconds)
            count)))
Intervals and durations for task Exercise
(cl-loop for plist in (chronometrist-to-list (chronometrist-active-backend))
  with count = 0
  when (equal (plist-get plist :name) "Exercise")
  collect
  (let* ((plist            (copy-list plist))
         (interval-seconds (chronometrist-interval plist))
         (start            (plist-get plist :start)))
    (plist-put plist :duration (ts-human-format-duration interval-seconds))
    (plist-put plist :date (seq-take start 10))
    (chronometrist-plist-remove plist :start :stop)))
Unique key-values for task "Exercise"
(cl-loop with task = "Exercise"
  for plist in (chronometrist-to-list (chronometrist-active-backend))
  when (equal (plist-get plist :name) task)
  collect (chronometrist-plist-key-values plist) into key-values
  finally do
  (let ((buffer (get-buffer-create (generate-new-buffer-name "chronometrist-query"))))
    (with-current-buffer buffer
      (->> (cl-remove-duplicates key-values)
           (seq-filter #'identity)
           (format "%S")
           (insert))
      (emacs-lisp-mode)
      (switch-to-buffer buffer))))
Time spent running since the 13th of December
(cl-loop with start = (parse-iso8601-time-string "2021-12-13")
  with buffer = (get-buffer-create "*chronometrist-query*")
  with output
  for plists being the hash-values of (chronometrist-to-hash-table (chronometrist-active-backend))
  using (hash-keys date-iso)
  when
  ;; create a list of strings for each date on which I ran
  (cl-loop with first-line = t
    with date = (parse-iso8601-time-string date-iso)
    for plist in plists
    when (and (plist-get plist :running)
              (or (time-less-p start date)
                  (time-equal-p start date)))
    collect
    (let* ((plist            (copy-list plist))
           (duration-seconds (chronometrist-interval plist))
           (start            (plist-get plist :start))
           (distance         (plist-get plist :running))
           (distance-int     (car distance))
           (distance-km      (/ (float distance-int) 1000))
           (duration-hours   (/ (float duration-seconds) 60 60))
           (distance-string  (format "% 4s %s" distance-int (cdr distance)))
           (speed-string     (format "%.2f" (/ distance-km duration-hours))))
      (format "%s%s  in % 5s  (% 5s kmph)\n"
              (if (not first-line)
                  (make-string (+ 3 (length date-iso)) ?\s)
                (setq first-line nil)
                (concat date-iso " - "))
              distance-string
              (format-seconds "%m:%02s" duration-seconds)
              speed-string)))
  collect it into lists
  ;; Add indices for each date, and indentation when there are >1
  ;; instances of running in a date
  finally do
  (setq output
        (cl-loop for list in (reverse lists)
          count list into index
          concat
          (let* ((index-string (format "%2s. " index))
                 (indent       (if (> (length list) 1)
                                   (make-string (length index-string) ?\s)
                                 "")))
            (concat index-string (first list) indent
                    (mapconcat #'identity (rest list) indent)))))
  (with-current-buffer buffer
    (erase-buffer)
    (insert output)
    (text-mode)
    (switch-to-buffer-other-window buffer)))

WISH emms

  1. make toggle command for emms-start/emms-stop
  2. change mode line display - don't show the whole file path, just the name
(use-package emms
  :ensure t
  :after hydra
  :init (setq emms-info-functions '(emms-info-tinytag))
  :bind
  ("<f2> e"   . #'contrapunctus-emms-hydra/body)
  ("<f2> E"   . #'emms)
  (:map dired-mode-map
        ("E" . #'contrapunctus-emms-hydra/body))
  :commands
  (emms-all emms emms-play-dired emms-add-dired)
  :config
  (emms-all)
  (setq emms-player-list              '(emms-player-mpv)
        emms-player-mpv-parameters    #'my-emms-mpv-parameters
        emms-info-tinytag-python-name "python3")
  ;; ;; This won't work for `emms-random', because it runs in a `save-excursion'
  ;; (add-to-list 'emms-playlist-selection-changed-hook 'emms-playlist-mode-center-current)
  ;; (--map (add-to-list 'emms-player-mpv-parameters it)
  ;;        '("--fs"))
  (defun contrapunctus-emms-toggle-player ()
    (interactive)
    (if emms-player-stopped-p
        (emms-start)
      (emms-stop)))
  :init (defhydra contrapunctus-emms-hydra ()
          ("e"       #'emms                    "EMMS")
          ("n"       #'emms-next               "Next")
          ("p"       #'emms-previous           "Previous")
          ("SPC"     #'emms-pause              "Pause")
          ("s"       #'contrapunctus-emms-toggle-player     "Start/Stop")
          ("0"       #'emms-volume-raise)
          ("9"       #'emms-volume-lower)
          ("<up>"    #'emms-volume-raise)
          ("<down>"  #'emms-volume-lower)
          ("<left>"  #'emms-seek-backward)
          ("<right>" #'emms-seek-forward)
          ("l"       #'emms-play-dired         "Play file (dired)")
          ("a"       #'emms-add-dired          "Add file (dired)")
          ("A"       #'emms-add-directory-tree "Add directory")
          ("u"       #'emms-play-url)))

(defun my-emms-mpv-parameters ()
  (append
   '("--quiet"
     "--really-quiet"
     ;; "--vid=no"
     "--force-window=yes"
     ;; "--loop-file=inf"
     )
   (let* ((dir (->> (emms-playlist-current-selected-track)
                    (alist-get 'name)
                    (file-name-directory)))
          (subs-in-dir    (f-glob "*.srt" dir))
          (subs-in-subdir (f-glob "*/*.srt" dir)))
     (->> (append subs-in-dir subs-in-subdir)
          (-interpose ":")
          (append '("--sub-files="))
          (apply #'concat)
          (list)))))

(use-package emms-playlist-mode
  :bind
  (:map emms-playlist-mode-map
        ("0"        . #'emms-volume-raise)
        ("9"        . #'emms-volume-lower)
        ("<up>"     . #'emms-volume-raise)
        ("<down>"   . #'emms-volume-lower)
        ("<left>"   . #'emms-seek-backward)
        ("<right>"  . #'emms-seek-forward)
        ("r"        . #'next-line)
        ("c"        . #'previous-line)
        ("R"        . #'emms-next)
        ("C"        . #'emms-previous)
        ("C-r"      . #'emms-toggle-random-playlist)
        ("M-r"      . #'emms-random)
        ("C-c"      . #'emms-playlist-mode-center-current)
        ("SPC"      . #'emms-pause)
        ("K"        . #'emms-playlist-clear))
  :config (setq emms-playlist-buffer-name "EMMS Playlist"))

Internet

eww   viewer

(use-package eww
  :config
  (setq shr-image-animate nil)
  :bind
  ;; start boon-specific config
  (:map shr-map
        ("v" . nil))
  (:map shr-image-map
        ("i" . nil)
        ("v" . nil))
  (:map eww-link-keymap
        ("i" . nil)
        ("v" . nil))
  (:map eww-mode-map
        ("h" . #'eww-back-url)
        ([mouse-3] . eww-back-url)
        ("s" . #'eww-forward-url)
        ("c" . #'shr-previous-link)
        ("r" . #'shr-next-link)
        ("v" . nil))
  (:map eww-bookmark-mode-map
        ("c" . #'previous-line)
        ("r" . #'next-line))
  ;; end boon-specific config
  )
(defun my-eww ()
  (interactive)
  (cond ((derived-mode-p 'dired-mode)
         (eww-open-file
          (dired-file-name-at-point)))
        ((derived-mode-p 'html-mode)
         (eww-open-file (buffer-file-name)))
        (t (call-interactively #'eww))))

elpher   viewer

(use-package elpher
  :ensure t
  :bind (:map elpher-mode-map
              ("r" . elpher-next-link)
              ("c" . elpher-prev-link)
              ("h" . elpher-back)
              ("s" . push-button)
              ("w" . elpher-copy-current-url)
              ("W" . elpher-copy-link-url)
              ("g" . elpher-reload)))

elfeed

(use-package elfeed
  :ensure t
  :bind (:map elfeed-show-mode-map
              ("v" . nil))
  :config
  (add-to-list 'boon-special-mode-list 'elfeed-show-mode)
  (add-to-list 'boon-special-mode-list 'elfeed-search-mode))

sxiv   viewer

(use-package sxiv
  :load-path "/media/data/contrapunctus/Documents/Text Files/programming/elisp/sxiv/"
  :config (setq sxiv-exclude-strings '("meh" "\\.NEF$"))
  :bind ("<f2> s"  . sxiv)
  (:map dired-mode-map
        ("I" . sxiv)))

TODO emacsshot   creator

PR ideas

  1. create directories in save path if they don't exist
  2. grammar - "written /path/to/file"
(use-package emacsshot
  :ensure t
  :bind
  ("<print> <f10>" . emacsshot-snap-window-filename)
  ("<print> <f11>" . emacsshot-snap-frame-filename)
  ("<print> <f12>" . emacsshot-snap-mouse-filename)
  :config
  (setq emacsshot-with-timestamp t
        emacsshot-snap-window-filename "/media/data/contrapunctus/Pictures/screenshots/emacsshot/emacsshot.png"))

dired   file

(use-package dired
  :init (add-hook 'dired-mode-hook 'turn-on-launch-mode)
  :config
  (setq dired-listing-switches
        ;; by date
        ;; "-cgGhlt --group-directories-first --time-style=long-iso"
        ;; by name
        "-Achl --group-directories-first --time-style=long-iso"
        ;; no -h
        ;; "-cgGl --group-directories-first --time-style=long-iso"

        ;; by date, no --group-directories-first
        ;; "-cgGhlt --time-style=long-iso"
        dired-bind-info nil)
  :bind
  (:map dired-mode-map
   ("W"           . wdired-change-to-wdired-mode)
   ("e"           . #'cp/dired-do-ediff)
   ;; after learning that this copies whole paths with null
   ;; argument, this became a whole lot more useful
   ("C-w"         . dired-copy-filename-as-kill)
   ("C-c C-f"     . my-corresponding-text-file)
   ("h"           . dired-hide-dotfiles-mode)
   ([mouse-2]     . cp/dired-launch-or-open)
   ("C-j"         . launch-files-dired)
   ("j"           . launch-files-dired)
   ("M-s r"       . dired-do-query-replace-regexp)
   ("M-s s"       . dired-do-isearch-regexp)
   ("P"           . emms-play-dired)
   ("X"           . dired-do-flagged-delete)
   ("M-n"         . dired-next-marked-file)
   ("M-p"         . dired-prev-marked-file)
   ("I" . sxiv)
   ("c" . dired-previous-line)
   ("r" . dired-next-line))
  :hook
  (dired-mode . (lambda () (dired-hide-details-mode t)))
  (dired-mode . auto-revert-mode))

(use-package dired-async
  :init (dired-async-mode 1))

(use-package dired-x
  :commands (dired-jump dired-omit-mode)
  :bind
  ("C-x C-d" . dired-jump)
  (:map dired-mode-map
        ("H" . dired-omit-mode)
        ("I" . sxiv)))
;; TODO - make launch-file suggest the path at point by default
(use-package launch
  :commands turn-on-launch-mode launch-files-dired
  :bind ("s-l" . launch-file))

my-open-random-file   command

(defun my-open-random-file (&optional find-args dir cmd)
  "Open a random file in DIR, prompting the user for it if not supplied."
  (interactive)
  (let* ((find-args (if find-args find-args " -type f "))
         (dir       (if dir dir
                      (read-directory-name "Directory: "
                                           (if file-name-history
                                               (car file-name-history)
                                             default-directory)
                                           nil t)))
         (file-name (--> (expand-file-name dir)
                         (concat "find " "\"" it "\" "
                                 find-args " | shuf | sed 1q")
                         (shell-command-to-string it)
                         (replace-regexp-in-string "\n" "" it))))
    (if cmd
        (async-shell-command (concat cmd " \"" file-name "\""))
      (find-file file-name))))
;; (with-eval-after-load 'project-explorer
;;   (global-set-key (kbd "<f5> e") 'project-explorer-toggle))

(use-package dired-hide-dotfiles
  :hook (dired-mode . (lambda () (dired-hide-dotfiles-mode))))

;; (require 'sudo-edit)

(defun cp/dired-do-ediff (&optional format)
  "Ediff (first two or three) marked files."
  (interactive)
  (let* ((files     (dired-get-marked-files t))
         (file-1    (car files))
         (files-dir default-directory))
    ;; 2018-04-08T11:31:20+0530 TODO - if there is only one marked
    ;; file, check the other window for a marked file and ediff with
    ;; that.
    (cl-case (length files)
      (1 (progn
           (other-window 1)
           (let ((files2     (dired-get-marked-files t))
                 (files2-dir default-directory))
             (if files2
                 (ediff (expand-file-name
                         (concat files-dir (car files)))
                        (expand-file-name
                         (concat files2-dir (car files2))))))))
      (2 (ediff file-1 (cadr files)))
      (t (ediff3 file-1 (cadr files) (elt files 2))))))

(defun cp/change-all-units ()
  "for fdupes output"
  (interactive)
  (while (re-search-forward "^[0-9]+" nil t)
    (shell-command-on-region (point-at-bol) (point) "numfmt --to=iec-i --suffix=B" nil t)
    (forward-word)
    (delete-region (point) (progn (forward-word) (point)))))

(defun cp/launch-file-archive ()
  (interactive)
  (launch-file
   (concat
    default-directory
    (aref (archive-get-descr) 0))))

;; (define-key archive-mode-map (kbd "j") 'cp/launch-file-archive)

;; 2018-02-28T21:00:57+0530
(defun my-corresponding-text-file ()
  (interactive)
  (save-excursion
    (end-of-line)
    (if (derived-mode-p 'dired-mode)
        ;; 2018-08-05T02:01:26+0530 - support directories too
        (let* ((file-or-dir (dired-file-name-at-point))
               (file        (if (file-directory-p file-or-dir)
                                (replace-regexp-in-string "/$" "" file-or-dir)
                              file-or-dir)))
          (find-file (concat file ".txt"))))))

;; 2018-07-09T23:22:17+0530
;; a little buggy wrt clicks
(defun cp/dired-launch-or-open (event)
  (interactive "e")
  ;; if point is on a folder, open it with dired
  ;; otherwise, call launch-files-dired
  (if (directory-name-p (dired-file-name-at-point))
      (dired-find-file)
    (launch-files-dired nil (dired-get-marked-files))))

(defun my-delete-file-at-point (&optional prefix)
  "In text buffers, delete the file corresponding to the path on the current line."
  (interactive "P")
  (let ((file (buffer-substring (point-at-bol) (point-at-eol))))
    (if (file-exists-p file)
        (progn
          (delete-file file)
          (if prefix
              ;; delete current line
              (delete-region (point-at-bol)
                             (1+ (point-at-eol)))
            ;; delete current paragraph
            (mark-paragraph)
            (delete-active-region)
            (forward-line 2))
          (message "Deleted %s" file))
      (error "File %s does not exist!" file))))

(defun my-file-at-point-exists-p ()
  "In text buffers, check if the file corresponding to the path on the current line exists."
  (interactive)
  (let ((file (buffer-substring (point-at-bol)
                                (point-at-eol))))
    (if (and (not (string-empty-p file))
             (file-exists-p file))
        (message "%s" t)
        (error "File %S does not exist!" file))))

backup configuration

(setq backup-by-copying t
      backup-directory-alist '(("." . "~/.emacs.d/saves/"))
      kept-new-versions 50
      kept-old-versions 50
      version-control t
      delete-old-versions t)

async-backup (backup on save)   file vc

(use-package async-backup
  :ensure t
  :config (setq async-backup-location
                "/media/data/contrapunctus/backups/emacs-async-backup/"))

mail   communication

(use-package emacs
  :config
  (setq user-mail-address  "contrapunctus@disroot.org"
        user-full-name     "contrapunctus"
        smtpmail-smtp-user "contrapunctus"
        auth-source-debug  'trivia
        ;; Use system hostname to prevent "Sending failed: ...: Helo
        ;; command rejected: need fully-qualified hostname" error when
        ;; sending mail
        mail-host-address  nil))

mu4e

(use-package mu4e
  :bind
  (:map mu4e-headers-mode-map
        ("c"       . #'mu4e-headers-prev)
        ("r"       . #'mu4e-headers-next)
        ("m"       . #'mu4e-headers-mark-for-something)
        ("u"       . #'mu4e-headers-mark-for-unmark)
        ("C-c C-c" . #'mu4e-mark-execute-all)
        ("+"       . #'my-mu4e-mkdir)
        ("t"       . #'mu4e-headers-prev-unread)
        ("n"       . #'mu4e-headers-next-unread))
  (:map mu4e-view-mode-map
        ("c" . #'mu4e-view-headers-prev)
        ("r" . #'mu4e-view-headers-next))
  (:map gnus-mime-button-map
        ("c" . nil)
        ("r" . nil))
  :init (mu4e t)
  :config
  (setq mu4e-get-mail-command "mbsync -aV"
        mu4e-drafts-folder    "/Drafts/"
        mu4e-sent-folder      "/Sent/"
        mu4e-trash-folder     "/Trash/"
        ;; Use vertico, not ido.
        mu4e-completing-read-function #'completing-read
        shr-use-colors        nil
        mu4e-use-fancy-chars  t))

(defun my-mu4e-mkdir ()
  "Make new maildir."
  (interactive)
  (start-process "mu-mkdir" (generate-new-buffer-name "mu-mkdir")
                 "mu" "mkdir"
                 (expand-file-name (read-file-name "Make maildir: "
                                                   (mu4e-root-maildir)))))
mu4e-alert

Even the patched version of mu4e-alert I'm using is not completely bug-free - many a times I get new mail, imapnotify detects it and runs mbsync, the mu4e-index-updated-hook is run (since the alert in my-mail-alert is displayed), and it contains mu4e-alert-notify-unread-mail-async…but I don't get an alert from mu4e-alert 🙁

(defun my-mail-alert ()
  (alert "You've got mail!"))
(add-hook 'mu4e-index-updated-hook #'my-mail-alert)

(use-package mu4e-alert
  :quelpa (mu4e-alert :fetcher github :repo "xzz53/mu4e-alert")
  :init (mu4e-alert-enable-notifications)
  :config (setq alert-fade-time 15))

doc-view   viewer

(use-package doc-view
  :config (setq doc-view-resolution 300))

pdf-tools   viewer

(use-package pdf-tools
  :ensure t
  :init (pdf-tools-install)
  :hook
  (pdf-view-mode . auto-revert-mode)
  (pdf-view-mode . pdf-view-midnight-minor-mode)
  :bind
  (:map pdf-view-mode-map
	    ("h" . pdf-history-backward)
	    ("s" . pdf-history-forward)
	    ("c" . pdf-view-scroll-down-or-previous-page)
	    ("r" . pdf-view-scroll-up-or-next-page)
        ("C" . #'pdf-view-previous-page)
        ("R" . #'pdf-view-next-page)
	    ("C-s" . isearch-forward)
	    ("g" . pdf-view-first-page)
	    ("l" . pdf-view-last-page)
        ("i" . pdf-view-revert-buffer)) ;; QWERTY g
  :config
  (setq-default pdf-view-display-size 'fit-width))

(use-package pdf-history
  :bind
  (:map pdf-history-minor-mode-map
        ("r" . nil)))

nov.el   viewer

(use-package nov
  :ensure t
  :mode ("\\.epub\\'" . nov-mode)
  :hook (nov-mode . visual-line-mode)
  :bind
  (:map nov-mode-map
        ;; make (Boon) c and r work even when point is in a link
        ("c" . nil)
        ("r" . nil)
        ("y" . nov-copy-url)
        ("c" . nov-previous-document)
        ("r" . nov-next-document)
        ("h" . nov-history-back)
        ("s" . nov-history-forward)
        ("T" . nov-goto-toc)
        ("m" . my-dispatch-hydra))
  (:map nov-button-map
        ("c" . nil)
        ("r" . nil)))

proced

(use-package proced
  :config
  (setq-default proced-auto-update-flag t))

webpaste

(use-package webpaste
  :ensure t
  :config (setq webpaste-provider-priority
                (delete "ix.io" (mapcar #'car webpaste-providers-alist))))

eshell   shell

(use-package eshell
  :config (setq eshell-history-size 999))

term

(use-package term
  :bind (:map term-mode-map
              ("M-c" . #'term-previous-input)
              ("M-r" . #'term-next-input)))

comint   shell

(use-package comint
  :bind (:map comint-mode-map
              ("M-p" . comint-history-isearch-backward-regexp) ;; QWERTY "r"
              ("M-c" . comint-previous-matching-input-from-input)
              ("M-r" . comint-next-matching-input-from-input)
              ("C-c C-s" . comint-next-prompt)
              ("C-c C-h" . comint-previous-prompt)))

info   viewer

(use-package info
  :config
  (info-initialize)
  (cl-loop for dir in
    '("~/.emacs.d/info/"
      "~/.emacs.d/elisp-git/geiser/doc/"
      "~/lilypond/usr/share/info/")
    do (add-to-list #'Info-directory-list dir))
  :bind
  (:map Info-mode-map
        ("b" . Info-history-back)
        ("f" . Info-history-forward)))

image-mode   viewer

(use-package image-mode
  :bind
  (:map image-map
        ("c" . nil)
        ("r" . nil)
        ("o" . nil))
  (:map image-mode-map
        ("c" . image-previous-file)
        ("r" . image-next-file)
        ("W" . image-transform-fit-to-width)
        ("H" . image-transform-fit-to-height)
        ("o" . nil)
	    ("R" . my-rename-this-file)
        ("D" . my-delete-this-file))
  :config (setq image-use-external-converter t))

TODO magit [0%]   vc

  1. It'd be really cool to (recenter 3) when you open a section, and (recenter) when you close a section
  2. binding "c" to magit-section-backward and "j" to magit-commit means I can't nonchalantly hit "c c" to commit like before…it becomes "j c" :\
(use-package magit
  :ensure t
  :bind (;; boon-like keys
         :map magit-mode-map
         ("r" . magit-section-forward)
         ("c" . magit-section-backward)
         ("C" . magit-commit)
         ("R" . magit-rebase)
         :map magit-diff-section-base-map
         ("C" . magit-commit)
         ("R" . magit-rebase)
         :map magit-status-mode-map
         ;; ([mouse-3] . 'magit-section-toggle)
         ([down-mouse-3] . 'mouse-set-point)
         ([up-mouse-3] . 'magit-section-toggle)
         ("C" . magit-commit))
  :commands magit-status
  :hook
  (magit-mode . visual-line-mode)
  (magit-post-stage . (lambda () (recenter)))
  :config
  (cl-loop for symbol in '(magit-section-toggle
                           magit-section-forward
                           magit-section-backward)
    do (advice-add symbol :after (lambda (&rest r) (recenter 3))))
  ;; (advice-add 'magit-unstage-item :after (lambda (&rest r) (next-line)))
  )

git-commit   vc

(use-package git-commit
  :ensure t
  :bind
  (:map git-commit-mode-map
        ("M-c" . git-commit-prev-message)
        ("M-r" . git-commit-next-message)))

Completion

The initials completion style is excellent! Instead of typing (w-o-t-t|<TAB>) to get (with-output-to-temp-buffer|), you can just type (wott|<TAB>).

But with initials, the desired completion is often buried in the results. That is where prescient shines - it remembers your input text and ranks previously-selected suggestions higher.

(use-package emacs
  :config
  (setq tags-add-tables   nil           ;; don't prompt me for tag tables
        completion-styles '(basic partial-completion emacs22 initials orderless substring)
        completion-cycle-threshold t))

intials is great for code (or at least Lisp code), but I prefer orderless for command names. It is basically the same as Ivy/Counsel completion, as far as I can make out.

orderless

(use-package orderless)

company

(use-package company
  :ensure t
  :diminish company-mode
  :hook
  (prog-mode       . company-mode)
  (tex-mode        . company-mode)
  (special-mode    . company-mode)
  (comint-mode     . company-mode)
  (slime-repl-mode . company-mode)
  :bind
  ("TAB" . #'company-indent-or-complete-common)
  ("C-i" . #'company-indent-or-complete-common)
  (:map company-active-map
        ("C-c" . company-select-previous)
        ("C-r" . company-select-next)
        ("C-h" . backward-delete-char)
        ("C-w" . backward-kill-word)
        ;; the following is necessary to shadow the window-switching
        ;; keybindings on the same keys
        ("C-1" . company-complete-quick-access)
        ("C-2" . company-complete-quick-access)
        ("C-3" . company-complete-quick-access)
        ("C-4" . company-complete-quick-access)
        ("C-5" . company-complete-quick-access)
        ("C-6" . company-complete-quick-access)
        ("C-7" . company-complete-quick-access)
        ("C-8" . company-complete-quick-access)
        ("C-9" . company-complete-quick-access)
        ("C-0" . company-complete-quick-access)
        ("<tab>" . #'company-complete-common-or-cycle)
        ("TAB" . #'company-complete-common-or-cycle)
        ("C-i" . #'company-complete-common-or-cycle))
  ;; Error (use-package): company/:catch: Symbols value as variable is void: c-mode-map
  ;; (:map c-mode-map
;;       ("TAB" . company-indent-or-complete-common)
;;       ("C-i" . company-indent-or-complete-common))
  :config
  (add-to-list 'company-backends 'company-irony)
  (setq company-show-quick-access t
        company-idle-delay        nil) ;; do not pop up suggestions automatically
  (customize-set-variable company-quick-access-modifier 'control))

company-emoji

(defun --set-emoji-font (frame)
  "Adjust the font settings of FRAME so Emacs can display emoji properly."
  (set-fontset-font t 'symbol (font-spec :family "Symbola") frame 'prepend))

(defun my-company-idle ()
  "Enable `company-idle-delay' in the current buffer."
  ;; (make-local-variable 'company-idle-delay)
  (with-current-buffer (current-buffer)
    (setq-local company-idle-delay 0.2)))

(use-package company-emoji
  :ensure t
  :hook
  (text-mode . company-mode)
  (text-mode . my-company-idle)
  :init
  (company-emoji-init)
  (--set-emoji-font nil) ;; For when Emacs is started in GUI mode:
  (add-hook 'after-make-frame-functions '--set-emoji-font)) ;; Hook for when a frame is created with emacsclient

company-prescient

(use-package company-prescient
  :ensure t
  :init (company-prescient-mode)
  (prescient-persist-mode))

yasnippet

(use-package yasnippet
  :disabled t
  :ensure t
  :commands (yas-expand-snippet)
  :hook
  (prog-mode       . yas-minor-mode-on)
  (comint-mode     . yas-minor-mode-on)
  (slime-repl-mode . yas-minor-mode-on)
  :bind (:map yas-minor-mode-map
              ("<tab>" . nil) ("TAB" . nil) ;; use TAB only for completion
              ("C-n" . #'yas-expand))
  (:map yas-keymap
        ("C-n" . #'yas-next-field-or-maybe-expand)))

tempel

(use-package tempel
  :bind
  ("M-+" . tempel-complete) ;; Alternative tempel-expand
  ("M-*" . tempel-insert)
  ("C-n" . tempel-expand)
  (:map tempel-map
        ("<tab>" . tempel-next)
        ("<C-tab>" . tempel-previous)))

vertico

(use-package vertico
  :ensure t
  :init (vertico-mode)
  (vertico-indexed-mode)
  (vertico-multiform-mode)
  (vertico-mouse-mode)
  ;; Different scroll margin
  ;; (setq vertico-scroll-margin 0)
  ;; Show more candidates
  ;; (setq vertico-count 20)
  ;; Grow and shrink the Vertico minibuffer
  ;; (setq vertico-resize t)
  :bind (:map vertico-map
              ("C-h"           . #'vertico-directory-delete-char)
              ("<backspace>"   . #'vertico-directory-delete-char)
              ("C-w"           . #'vertico-directory-delete-word)
              ("<C-backspace>" . #'vertico-directory-delete-word)
              ("<prior>"       . #'vertico-scroll-down)
              ("<next>"        . #'vertico-scroll-up)
              ("C-RET"         . #'vertico-exit-input)
              ("<C-return>"    . #'vertico-exit-input))
  :config
  (setq completion-category-defaults  nil
        completion-category-overrides nil
        vertico-multiform-commands
        '((chronometrist-toggle-task               (vertico-sort-function . nil))
          (chronometrist-toggle-task-no-hooks      (vertico-sort-function . nil))
          (chronometrist-toggle-task-button        (vertico-sort-function . nil))
          (chronometrist-key-values-unified-prompt (vertico-sort-function . nil))
          (consult-line                  (vertico-sort-function . nil))
          (my-consult-line               (vertico-sort-function . nil)))))
;; Persist history over Emacs restarts. Vertico sorts by history position.
(use-package savehist
  :init (savehist-mode))
;; A few more useful configurations...
(use-package emacs
  :init
  ;; Add prompt indicator to `completing-read-multiple'.
  ;; Alternatively try `consult-completing-read-multiple'.
  (defun crm-indicator (args)
    (cons (concat "[CRM] " (car args)) (cdr args)))
  (advice-add #'completing-read-multiple :filter-args #'crm-indicator)

  ;; Do not allow the cursor in the minibuffer prompt
  (setq minibuffer-prompt-properties
        '(read-only t cursor-intangible t face minibuffer-prompt))
  (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)

  ;; Emacs 28: Hide commands in M-x which do not work in the current mode.
  ;; Vertico commands are hidden in normal buffers.
  ;; (setq read-extended-command-predicate
  ;;       #'command-completion-default-include-p)

  ;; Enable recursive minibuffers
  (setq enable-recursive-minibuffers t))

consult

(use-package consult
  :ensure t)

(defun my-consult-line ()
  "Like `consult-line', but use symbol at point or active region."
  (interactive)
  (let ((string (cond ((region-active-p)
                       (buffer-substring-no-properties (region-beginning) (region-end)))
                      ((or (thing-at-point 'symbol)
                           (thing-at-point 'url)
                           (thing-at-point 'word)))
                      (t nil))))
    (consult-line string)))

bookmarks

(use-package bookmark
  :config (setq bookmark-save-flag 1))

recentf

(use-package recentf
  :init
  ;; default is to cleanup when `recentf-mode' is enabled, which we don't want
  (setq recentf-auto-cleanup 'never)
  (recentf-mode 1)
  :bind ("C-x C-r C-o" . recentf-open-files)
  :config
  (setq recentf-max-menu-items  500
        recentf-max-saved-items 1000
        recentf-save-file (locate-user-emacs-file "recentf")
        recentf-exclude '("\\.html\\(\\.orig\\)?$"
                          "\\.jpe?g$"
                          "\\.png$"
                          "\\.mp4$"
                          "\\.etc"
                          "\\.umstuff"))
  (advice-add 'recentf-save-list
              :before (lambda (&rest args)
                        (async-backup recentf-save-file)))
  ;; I'd rather run cleanup manually, if ever required; running
  ;; cleanup automatically is a recipe for periodically losing your
  ;; recentf entries when your disk happens to be unmounted.
  ;; :hook (kill-emacs . recentf-cleanup)
  )

misc keybindings

(global-set-key (kbd "M-w") 'kill-ring-save)
(define-key emacs-lisp-mode-map (kbd "M-w") nil)
;; ;; M-d is useful in the minibuffer
;; (define-key emacs-lisp-mode-map (kbd "M-d") nil)
;; (global-set-key (kbd "M-d") 'easy-kill-delete-region)

environment variables

(setenv "PATH" (concat "~/bin:" (getenv "PATH")))
(setenv "EDITOR" "emacsclient")

;; what on earth is this message after every init -
;; ad-handle-definition: `tramp-read-passwd' got redefined

;; ;; (profiler-start 'cpu)
;; (toggle-debug-on-error)
;; ;; (toggle-debug-on-quit)

Linewrapping

(add-hook 'erc-mode-hook 'visual-line-mode)
(add-hook 'text-mode-hook 'visual-line-mode)

(use-package visual-fill-column
  :ensure t
  :hook
  (markdown-mode . visual-fill-column-mode)
  (markdown-mode . visual-line-mode)
  (org-mode      . visual-line-mode)
  ;; (org-mode      . visual-fill-column-mode)
  (message-mode  . visual-fill-column-mode))

(use-package adaptive-wrap
  :ensure t
  :hook (markdown-mode . adaptive-wrap-prefix-mode))

Navigation

;; (require 'view)
;; (cp-set-keys
;;  :bindings
;;  `((,(kbd "C-v") View-scroll-half-page-forward)
;;    (,(kbd "M-v") View-scroll-half-page-backward)))
;; ;; (cp-set-keys
;; ;;  :bindings
;; ;;  `((,(kbd "C-v") scroll-up)
;; ;;    (,(kbd "M-v") scroll-down)))

;;;; Need to make maps for
;;;;    mark-* commands (-sexp, -page, -word, etc)
;;;;    mark ring

;;;; Other custom keys
(bind-keys
 ("C-x C-1" . delete-other-windows)
 ("C-x C-2" . split-window-below)
 ("C-x C-3" . split-window-right)
 ("C-x C-0" . delete-window)
 ("C-x C-4 C-f" . find-file-other-window)
 ;; ("C-`" . point-to-register)
 ;; ("C-'" . jump-to-register)
 ("C-x C-d" . dired-jump)
 ;; I usually keep Super for the window manager and global hotkeys...
 ("s-i" . imenu)
 ;; keypad

 ;; set this to run whatever command is bound to C-c C-c, or maybe just C-c
 ;; see (info "(elisp)Translation Keymaps")
 )

(with-eval-after-load 'dired
  (define-key dired-mode-map (kbd "b") 'dired-up-directory)
  ;; (add-hook 'dired-mode-hook 'visual-line-mode)
  ;(global-set-key (kbd "C-,") 'string-rectangle 'TAB)
)
(global-unset-key (kbd "C-x d"))

;; (require 'dired-toggle-sudo)
;; (define-key dired-mode-map (kbd "C-c C-s") 'dired-toggle-sudo)
;; (eval-after-load 'tramp
;;   '(progn
;;      ;; Allow to use: /sudo:user@host:/path/to/file
;;      (add-to-list 'tramp-default-proxies-alist
;;                   '(".*" "\\`.+\\'" "/ssh:%h:"))))
(global-set-key (kbd "C-x df") 'delete-frame)
;;;; make-frame key is after Helm's config

;(setq compilation-read-command nil)
(defun cp-open-init ()
  "Open the init.el file."
  (interactive)
  (find-file "~/.emacs.d/init.el"))
;; todo - store current file name → kill the buffer →
;; find-file-literally with the stored filename. also, find out if you
;; can open it without modes but with the proper encoding.
(defun cp-fcf-literally ()
  "Find the current file literally. Like find-file-literally but
  does not prompt for a file name."
  (interactive)
  (find-file-literally (buffer-file-name)))
(defun cp-kill-buffer ()
  "Kill the current buffer without prompting."
  (interactive)
  (kill-buffer nil))

;; (require 'win-switch)
;; (global-set-key "\C-xo" 'win-switch-dispatch)
;; (win-switch-setup-keys-ijkl)
;; (setq win-switch-idle-time 0.5)
;; (setq win-switch-other-window-first nil)
;; ;(setq win-switch-other-window-first nil)

(defun cp-maximize-window ()
  "Run maximize-window if more than one window is present."
  (interactive)
  (if (> (length (window-list)) 1)
      (maximize-window)))

(defun cp-minimize-window ()
  "Run minimize-window if more than one window is present."
  (interactive)
  (if (> (length (window-list)) 1)
      (minimize-window)))

(with-eval-after-load 'doc-view
  (define-key doc-view-mode-map (kbd "=") 'doc-view-fit-height-to-window))

(use-package view-mode
  :bind
  (:map view-mode-map
        ("x" . nil)
        ("<backspace>" . scroll-down-command)
        ("SPC" . scroll-up-command)))

(use-package avy
  :ensure t
  :config
  (setq avy-case-fold-search nil))

window-numbering

(use-package window-numbering
  :ensure t
  :commands (window-numbering-mode)
  :init (window-numbering-mode 1)
  :bind (:map window-numbering-keymap
              ("M-1" . nil) ("M-2" . nil) ("M-3" . nil)
              ("M-4" . nil) ("M-5" . nil) ("M-6" . nil)
              ("M-7" . nil) ("M-8" . nil) ("M-9" . nil)
              ("C-1" . select-window-1)
              ("C-2" . select-window-2)
              ("C-3" . select-window-3)
              ("C-4" . select-window-4)
              ("C-5" . select-window-5)
              ("C-6" . select-window-6)
              ;; for one-handed operation with the right hand
              ("C-7" . select-window-1)
              ("C-8" . select-window-2)
              ("C-9" . select-window-3)))

outline-minor-mode

(use-package outline
  :hook (adoc-mode . outline-minor-mode)
  (gemini-mode . outline-minor-mode)
  ;; Does this cause this error?
  ;;   Polymode error (pm--mode-setup org-mode): recentering a window that does not display current-buffer.
  ;; (outline-view-change . (lambda () (recenter 3)))
  :bind (:map outline-minor-mode-map
              ("TAB" . contrapunctus-outline-indent-or-toggle-children)
              ;; this (rather than `outline-show-children') seems to
              ;; be the equivalent of `org-show-subtree'
              ("C-c C-k" . outline-show-branches)
              ;; wow, in adoc-mode these also replace the first letter of the heading with an "e"... 😒
              ;; ("C-c ," . outline-promote)
              ;; ("C-c ." . outline-demote)
              )
  ("C-c ," . adoc-demote)
  ("C-c ." . adoc-promote)
  ("C-c C-c" . outline-previous-heading)
  ("C-c C-r" . outline-next-heading)
  ("C-c C-h" . nil)
  ("C-c C-h" . outline-up-heading))
(defun contrapunctus-outline-indent-or-toggle-children ()
  (interactive)
  (if (save-excursion
        (goto-char (point-at-bol))
        (looking-at-p outline-regexp))
      (outline-toggle-children)
    (indent-for-tab-command)))

outshine

(use-package outshine
  :commands (outshine-mode outshine-cycle)
  :hook (texinfo-mode . outshine-mode))

scrolling

While we're at it, let's add that to next-error as well (this affects jumping to match from M-x grep , too)

(add-hook 'next-error-hook 'recenter)

Buffer management

(defun my-buffer-switch ()
  (interactive)
  (let ((buffers (remove-if (lambda (buffer)
                              (or (string-match-p "^ " (buffer-name buffer))
                                  (get-buffer-window-list buffer)))
                            (buffer-list))))
    (switch-to-buffer (first buffers))))

ibuffer   buffer

(use-package ibuffer
  :bind (("C-x C-b" . ibuffer)
         :map ibuffer-mode-map
         ("m" . nil)
         ("U" . nil)
         ("m f" . ibuffer-mark-by-file-name-regexp)
         ("m m" . ibuffer-mark-by-mode-regexp)
         ("m n" . ibuffer-mark-by-name-regexp)
         ("M" .   ibuffer-mark-forward)
         ("r" .   ibuffer-do-replace-regexp)
         ("U" .   ibuffer-unmark-all)
         ("X" . 'ibuffer-do-kill-on-deletion-marks)) ;; Boon hijacks the x key.ibuffer-set-filter-groups-by-mode
  :config
  (setq ibuffer-show-empty-filter-groups nil)
  (setq-default ibuffer-current-format 2))
(general-define-key
 "C-x k"  'cp-kill-buffer
 "C-`"    'shell
 "M-`"    'eshell
 "M-<f2>" 'compile
 "M-<f3>" 'run-chicken
 "M-<f4>" 'run-lisp
 "M-<f5>" 'ielm)
(general-define-key
 :prefix "<f1>"
 "M"    'describe-mode
 "m"    'man
 "l"    'find-library)
(general-define-key
 :prefix "<f2>"
 ;; "<f2>" 'imenu
 "<f2>" 'xref-find-definitions
 "r"    'xref-find-references
 "m"    'imenu
 ;; "p" 'grep
 "o"    'find-grep
 "i"    'find-dired
 "h"    'proced)
(general-define-key
 :prefix "<f5>"
 "<f5>" 'eval-buffer
 "i"    'cp-open-init
 "v"    'visual-line-mode
 "f"    'cp-fcf-literally
 "f"    'fundamental-mode
 "t"    'text-mode
 "T"    'cp-insert-timestamp
 "d"    'cp-insert-date
 "c"    'calendar
 "p" 'list-packages)
(general-define-key
 ;; [down-mouse-1]   'mouse-set-point
 ;; [up-mouse-1]     'er/expand-region
 [s-mouse-3]      'bury-buffer
 [mouse-8]        'delete-window
 ;; (kbd "<mouse-9>")   'keyboard-quit
 ;; [mouse-9]        'buffer-menu
 [mouse-9]        'ibuffer
 [C-mouse-9]      'recentf-open-files
 [M-mouse-4]      'next-buffer
 [M-mouse-5]      'previous-buffer
 [M-mouse-8]      'split-window-right
 [M-mouse-9]      'split-window-below
 ;; quitting from helm-mini - whether with keyboard-quit or
 ;; keyboard-escape-quit - "banishes" the mouse pointer to the
 ;; top-right corner!? wtf, helm.
 ;; (kbd "s-<mouse-9>") 'helm-mini
 )

ibuffer-sidebar

(use-package ibuffer-sidebar
  :ensure t
  :bind (:map ibuffer-name-map
              ([(mouse-1)] . ibuffer-mouse-visit-buffer)
              ([down-mouse-3] . nil)
              ([(mouse-3)] . ibuffer-mouse-toggle-mark))
  :config (setq ibuffer-sidebar-width 25))

midnight-mode (automatic buffer cleanup)

(use-package midnight
  :init
  (midnight-mode)
  :config
  (setq clean-buffer-list-kill-regexps '("")
        clean-buffer-list-delay-general 1
        clean-buffer-list-delay-special (* 60 60 12))
  (add-to-list 'clean-buffer-list-kill-never-buffer-names "Gajim")
  :hook
  (midnight . clean-buffer-list))

browse-url

SLIME opens CLHS links in Firefox, but I'd rather open them in Tor Browser; Tor Browser, however, does not permit other applications to open tabs in a running instance. So I wrote this to copy the links automatically instead.

(use-package browse-url
  :config
  (defun cp-copy-url (url &rest args)
    (with-temp-buffer
      (insert url)
      (kill-ring-save (point-min) (point-max)))
    (message "Copied %s to kill ring" url))
  (setq browse-url-browser-function #'cp-copy-url))

Markup

Org

(use-package org
  :quelpa (org :fetcher git
               :url "https://git.savannah.gnu.org/git/emacs/org-mode.git"
               :files (:defaults "lisp"))
  :hook (org-mode . contrapunctus-disable-nameless-key)
  (org-mode . auto-revert-mode)
  :commands (org-drag-line-backward org-drag-line-forward)
  :bind (("C-c C-h" . nil)
         :map org-mode-map
         ("C-c C-c" . org-previous-visible-heading)
         ("C-c C-r" . org-next-visible-heading)
         ("C-c C-s" . org-forward-heading-same-level)
         ;; does not work...
         ("C-c C-h" . org-backward-heading-same-level)
         ("C-c C-j" . org-ctrl-c-ctrl-c) ;; "c c" in boon/dvorak
         ([mouse-1] . org-cycle)
         ("<C-tab>" . org-global-cycle))
  :config
  (general-def "<f5> o" 'org-mode)
  (general-def org-mode-map
    "C-,"      'nil
    "M-r"      'org-metadown
    "M-c"      'org-metaup
    "C-c C--"  'org-ctrl-c-minus
    "C-c C-,"  'org-metaleft
    "C-c C-."  'org-metaright
    ;; "C-j"      'org-return
    ;; "C-m"      'org-return-indent
    "C-c C-9"  'org-mark-ring-goto
    "C-c C-/"  'org-sparse-tree
    "C-M-x"    'cp/eval-sexp
    "C-c C-o" ;; 'my-org-open
    'org-open-at-point
    ;; boon
    "C-c C--"  'org-ctrl-c-minus
    "C-c ,"    'org-metaleft
    "C-c ."    'org-metaright
    "M-h"      'default-indent-new-line)
  (mapc (lambda (pair)
          (add-to-list 'org-file-apps pair))
        '(("txt" . emacs)
          ("org" . emacs)
          ("pdf" . emacs)
          ;; (t . "xdg-open %s")
          ))
  (setq org-todo-keywords '((sequence "TODO" "RESEARCH" "STARTED" "DONE"))
        org-image-actual-width                    400
        org-cycle-include-plain-lists             'integrate
        org-link-search-must-match-exact-headline nil
        org-html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />"
        org-html-self-link-headlines              t
        org-export-default-inline-image-rule
        `(("https" .
           ,(format "\\.%s\\'"
                    (regexp-opt
                     '("png" "jpeg" "jpg" "gif" "tiff" "tif" "xbm"
                       "xpm" "pbm" "pgm" "ppm" "webp") t)))
          ("file" .
           ,(format "\\.%s\\'"
                    (regexp-opt
                     '("png" "jpeg" "jpg" "gif" "tiff" "tif" "xbm"
                       "xpm" "pbm" "pgm" "ppm" "webp") t))))
        org-link-file-path-type                   'relative
        ;; https://emacs.stackexchange.com/questions/18877/how-to-indent-without-the-two-extra-spaces-at-the-beginning-of-code-blocks-in-or
        org-edit-src-content-indentation 0
        org-src-fontify-natively         t
        org-src-window-setup             'current-window
        org-src-strip-leading-and-trailing-blank-lines t
        org-src-preserve-indentation     t
        org-src-tab-acts-natively        t
        ;; https://emacs.stackexchange.com/questions/20759/all-org-subheadings-in-imenu
        ;; I ended up using consult-org-heading instead
        org-goto-interface               'outline-path-completion
        org-outline-path-complete-in-steps nil)
  (org-link-set-parameters "gemini" :export #'my-export-gemini-link)
  (org-link-set-parameters "xmpp" :export #'my-export-xmpp-link))

(defun contrapunctus-disable-nameless-key ()
    (define-key nameless-mode-map (kbd "C-c C--") nil))

org-emms

(use-package org-emms
  :after org)

my-org-insert-block

(defun my-org-insert-block (type &optional lang header-args ask)
  "Insert block of TYPE at point, or at beginning and end of region.
TYPE should be an Org block type, e.g. SRC, QUOTE, etc.

If TYPE is SRC, LANG should be the name of the language as a string, e.g. \"emacs-lisp\".

If TYPE is EXPORT, LANG should be the name of the export format as a string, e.g. \"html\".

If LANG is not supplied, use the value of
`my-org-src-default-lang'. If `my-org-src-default-lang' is nil,
or with ASK, prompt the user for a language."
  (let* ((src-block-p    (equal type "SRC"))
         (export-block-p (equal type "EXPORT"))
         (ask-lang-p     (or (not lang) ask))
         (lang (cond ((stringp lang) lang)
                     ((and (bound-and-true-p my-org-src-default-lang)
                           (stringp my-org-src-default-lang)
                           (not ask))
                      my-org-src-default-lang)
                     ((and src-block-p ask-lang-p)
                      (completing-read "Source block language: "
                                       (mapcar #'cl-first org-src-lang-modes)))
                     ((and export-block-p ask-lang-p)
                      (completing-read "Export block format: "
                                       (mapcar #'symbol-name org-export-backends)))
                     (t nil)))
         (column       (- (point) (point-at-bol)))
         (indent       (make-string column ?\s))
         (start-string (format "#+BEGIN_%s %s%s\n" type
                               (if (or src-block-p export-block-p)
                                   lang
                                   "")
                               (if (and src-block-p header-args)
                                   (format " %s" header-args)
                                 "")))
         (end-string   (format "%s#+END_%s\n" indent type)))
    ;; create a block around a region - preserve position of point
    (cond ((region-active-p)
           (let ((region-start (region-beginning))
                 (region-end   (region-end)))
             (save-excursion
               ;; inserting at region-start would make region-end
               ;; invalid, so we insert at the end first
               (goto-char region-end)
               (insert end-string "\n")
               (goto-char region-start)
               (insert start-string))))
          ;; new empty block - insert the block syntax and place point
          ;; inside the block
          (t (insert start-string)
             (let ((point-inside-block (point)))
               (insert "\n" end-string)
               (goto-char point-inside-block)
               (insert indent))))))
(add-to-list 'org-src-lang-modes '("lisp" . lisp-mode))
;; (use-package org-src-mode
;;   :hook (org-src-mode . (lambda () (when (derived-mode-p 'emacs-lisp-mode)))))

  ;; (defun my-org-hydra-expand-all ()
  ;;   (interactive)
  ;;   ;; todo - define inner recursive function
  ;;   (beginning-of-buffer)
  ;;   ;; todo - check if we are on a heading
  ;;   (org-forward-heading-same-level)
  ;;   ())

my-org-open

;; I dislike having to navigate within a line to reach a link - with
;; this command I just need to be on the same line as the link.
(defun my-org-open (&optional arg reference-buffer)
  (interactive "P")
  (beginning-of-visual-line)
  (unless (looking-at-p (rx-to-string '(or "http" "[")))
    (org-next-link))
  (org-open-at-point))

my-org-table-convert-tsv

(defun my-org-table-convert-tsv ()
  (interactive)
  (with-output-to-temp-buffer "my-org-table-convert-tsv"
    (->> (buffer-substring-no-properties (region-beginning) (region-end))
         (replace-regexp-in-string "^| *"  "")
         (replace-regexp-in-string " *| *" "	")
         (replace-regexp-in-string "^-.*$" "")))
  (with-current-buffer "my-org-table-convert-tsv"
    (remove-hook 'before-save-hook 'delete-trailing-whitespace)
    (write-file (read-from-minibuffer "Output filename: "))))

my-org-set-tags - using completing-read-multiple for setting tags

(defun my-org-set-tags ()
  (interactive)
  (let ((all-tags (org-get-buffer-tags))
        (current-tags (org-get-tags)))
    (save-excursion
      (org-back-to-heading)
      (org-set-tags
       (completing-read-multiple
        "Tag: " all-tags nil 'confirm
        (mapconcat #'identity current-tags ",")
        'org-tags-history)))))

*

(defun cp/marked-files->markup-links-org (filenames)
  (mapcar (lambda (filename)
            (let ((link-pre  "[[file:")
                  (link-post "][]]\n"))
              (concat link-pre filename link-post)))
          filenames))

my-org-insert-timestamp

(defun my-org-insert-timestamp ()
  (save-excursion
    (org-set-property "CREATED" (format-time-string "%FT%T%z"))))
(use-package org-indent
  :hook (org-mode . org-indent-mode))

(with-eval-after-load 'ox
  (require 'ox-texinfo)) ;; ideally, I'd load this in :before advice for org-export...

load org-babel languages on demand

From https://emacs.stackexchange.com/questions/20577/org-babel-load-all-languages-on-demand

(defadvice org-babel-execute-src-block (around load-language nil activate)
  "Load language if needed"
  (let ((language (org-element-property :language (org-element-at-point))))
    (unless (cdr (assoc (intern language)
                        org-babel-load-languages))
      (add-to-list 'org-babel-load-languages
                   (cons (intern language) t))
      (org-babel-do-load-languages 'org-babel-load-languages
                                   org-babel-load-languages))
    ad-do-it))

my-org-fix-newlines

(defun my-org-fix-newlines ()
  "Insert newlines around Org headings.
Newlines are inserted before headings (unless immediately
preceded by another heading) and after headings."
  (interactive)
  (unless (org-at-heading-p)
    (outline-next-heading))
  (org-map-tree
   (lambda ()
     (save-excursion
       (forward-line -1)
       (goto-char (point-at-bol))
       (unless (or (looking-at-p "^$")
                   (org-at-heading-p))
         (goto-char (point-at-eol))
         (insert "\n")))
     (save-excursion
       (forward-line)
       (while (org-at-drawer-p)
         (forward-line))
       (goto-char (point-at-bol))
       (when (looking-at-p "^$")
         (join-line)))))
  (outline-next-visible-heading 1))

org-html-themify

(use-package org-html-themify
  :disabled t
  :load-path "~/.emacs.d/elisp-git/org-html-themify/"
  :load-path "~/.emacs.d/elisp-git/hexrgb/"
  :hook (org-mode . org-html-themify-mode)
  :config
  (setq org-html-themify-themes
        '((dark . doom-molokai))))

ox-publish

(use-package ox-publish
  :config
  (require 'ox-gemini)
  (setq org-publish-project-alist
        '(("tilde.team-html"
           :base-directory
           "/media/data/contrapunctus/Documents/Text Files/homepage/contrapunctus/org/"
           :publishing-directory
           "/ssh:contrapunctus@tilde.team:/home/contrapunctus/public_html/"
           :recursive t
           :publishing-function org-html-publish-to-html)
          ("tilde.team-gmi"
           :base-directory
           "/media/data/contrapunctus/Documents/Text Files/homepage/contrapunctus/org/"
           :publishing-directory
           "/ssh:contrapunctus@tilde.team:/home/contrapunctus/public_gemini/"
           :recursive t
           :publishing-function org-gemini-publish-to-gemini))))

(defun my-org-publish-html (plist filename pub-dir)
  )

org-superstar

(use-package org-superstar
  :hook (org-mode . org-superstar-mode))

auto-id

(use-package auto-id
  :load-path "/media/data/contrapunctus/Documents/Text Files/programming/elisp/auto-id/"
  :commands (auto-id-mode))

markdown-mode   editing

(use-package markdown-mode
  :ensure t
  :mode "\\.md\\'"
  :hook
  (markdown-mode . (lambda ()
                     (add-hook 'before-save-hook
                               'markdown-cleanup-list-numbers
                               0 t)))
  (markdown-mode . markdown-display-inline-images)
  :config (setq markdown-command "cmark"
                markdown-css-paths '("style.css")
                markdown-display-remote-images t
                markdown-max-image-size '(500 . 500)
                ;; reflows text to suit different screens
                markdown-xhtml-header-content
                (concat "<meta name=\"viewport\" "
                        "content=\"width=device-width, "
                        "initial-scale=1.0, "
                        "user-scalable=yes\" />"))
  (when (featurep 'boon)
    (general-def markdown-mode-map
      "C-c ,"   'markdown-promote
      "C-c ."   'markdown-demote
      "C-c C-e" 'markdown-export))
  (setq-default ;; markdown-hide-markup t ;; has a bug with heading cycling
   markdown-hide-urls t)
  :bind
  (:map markdown-mode-map
        ;; ("M-n" . org-drag-element-forward)
        ;; ("M-p" . org-drag-element-backward)
        ;; ("C-c C-o" . markdown-follow-link-at-point)
        ("M-r" . markdown-move-down)
        ("M-c" . markdown-move-up)
        ("C-c C--" . org-cycle-list-bullet)
        ([mouse-1] . markdown-cycle)
        ("C-c C-x C-n" . markdown-next-link)
        ("C-c C-x C-p" . markdown-previous-link)

        ("C-c C-h C-u" . #'markdown-toggle-url-hiding)
        ("C-c C-h C-m" . #'markdown-toggle-markup-hiding)
        ("C-c C-h C-i" . #'markdown-toggle-inline-images)
        ("C-c C-r" . #'reverse-region)
        ("M-w" . cp/copy-md-link)
        ("TAB" . markdown-cycle)
        ("C-c C-." . markdown-demote)
        ("C-c C-," . markdown-promote)
        ("C-c C-l" . markdown-insert-link)))

(defun cp/copy-line-or-md-link (prefix-arg)
  (interactive "P")
  (save-excursion
    (beginning-of-line)
    (if (looking-at-p ".*http")
        (progn
          (cp/re-search-line "http")
          (forward-char -4)
          (kill-new (thing-at-point 'url))))))

(with-eval-after-load 'markdown-mode
  (defun cp/copy-md-link (prefix-arg)
    "Copy address of Markdown link after point in the current line.

If there is no link in the current line, or if the region is
active, or with a prefix arg - call
whole-line-or-region-kill-ring-save instead."
    (interactive "P")
    (save-excursion
      (if (or (use-region-p)
              prefix-arg
              (not (cp/re-search-line
                    ;; 2018-03-21T22:47:55+0530 - fix bug where a line with parenthesized text would not be copied
                    ;; "\("
                    ;; 2018-07-22T10:20:03+0530
                    ;; "\\[.*?\\](.*?)"
                    "\\[.*?\\]("
                    )))
          (whole-line-or-region-kill-ring-save prefix-arg)
        (let ((point-a (point)))
          (forward-char -1)
          (forward-sexp)
          (copy-region-as-kill point-a (- (point) 1)))))))

(defun cp/copy-bus-entry ()
  "For personal use, when working between Markdown and the OSM wiki."
  (interactive)
  (let ((point-a (region-beginning))
        (point-b (region-end))
        (point-b-line (line-number-at-pos)))
    (query-replace "[ ]" "☐" nil point-a point-b)
    (query-replace "[x]" "☑" nil point-a point-b)
    (query-replace-regexp "^[\\*-] " "::" nil point-a point-b)
    (query-replace-regexp "^### " ":" nil point-a point-b)
    (goto-char (point-min))
    (forward-line (- point-b-line 1))
    (copy-region-as-kill point-a (point-at-bol))))

;; 2018-08-21T03:41:47+0530
(defun cp/copy-md-link (prefix-arg)
  "Copy address of Markdown link after point in the current line.

If there is no link in the current line, or if the region is
active, or with a prefix arg - call
whole-line-or-region-kill-ring-save instead."
  (interactive "P")
  (save-excursion
    (cond
     ((or (use-region-p) prefix-arg)
      (whole-line-or-region-kill-ring-save prefix-arg))
     ((cp/re-search-line "\\[.*?\\](")
      (let ((point-a (point)))
        (forward-char -1)
        (forward-sexp)
        (copy-region-as-kill point-a (- (point) 1))))
     ((cp/re-search-line "http")
      (kill-new (thing-at-point 'url)))
     (t (whole-line-or-region-kill-ring-save prefix-arg)))))

(defun cp/marked-files->markup-links-md (filenames)
  (mapcar (lambda (filename)
            (if (member (downcase
                         (file-name-extension filename))
                        image-file-name-extensions)
                (let ((link-pre  "![](")
                      (link-post ")\n"))
                  (concat link-pre filename link-post))
              (let ((link-pre  "[](")
                    (link-post ")\n"))
                (concat link-pre filename link-post))))
          filenames))

**

(add-hook 'erc-mode-hook 'visual-line-mode)

**

(add-hook 'comint-mode-hook 'visual-line-mode)

;; commented out on 2018-03-19T14:18:34+0530
;; (add-hook 'markdown-mode-hook 'auto-fill-mode)
;; (add-hook 'text-mode-hook 'auto-fill-mode)

;; (add-hook 'paredit-mode-hook 'auto-fill-mode)

;;;; Tab settings
;; (setq default-tab-width 4)

**

(setq tab-width 4)
;(define-key text-mode-map (kbd "TAB") 'self-insert-command)

**

(setq-default indent-tabs-mode nil)

mediawiki-mode

(use-package mediawiki
  :commands mediawiki-mode)

asciidoc-mode

Used by Eldev documentation.

(use-package adoc-mode
  :mode "\\.adoc$")

gemini-mode

<2021-07-12T16:06:08+0530>

(use-package gemini-mode
  :ensure t
  :hook (gemini-mode . (lambda ()
                         (make-local-variable 'outline-regexp)
                         (setq outline-regexp "[#]+")))
  :bind (:map gemini-mode-map
              ("TAB" . contrapunctus-outline-indent-or-toggle-children)))

TODO LaTeX

  1. merge personal commands with those in Lilypond mode
(use-package auctex
  :ensure t
  :hook ((tex-mode . TeX-source-correlate-mode))
  :bind (:map LaTeX-mode-map
              ("M-c" . cp-backward-def)
              ("M-r" . cp-forward-def)
              ("M-." . forward-sentence))
  :config
  (setq ;; TeX-auto-save t
        ;; TeX-parse-self t
        TeX-engine 'xetex
        ;; 2017-10-14T18:55:05+0530
        )
  (add-to-list 'TeX-view-program-selection '(output-pdf "PDF Tools" TeX-pdf-tools-sync-view))
  ;; 2017-10-19T09:19:34+0530 - these should be merged with
  ;; cp-forward-def/cp-backward-def in cp-lily...
  (defun cp-backward-def ()
    (interactive)
    (push-mark)
    (re-search-backward "\\\\scene" nil t)
    (beginning-of-line)
    (recenter))
  (defun cp-forward-def ()
    (interactive)
    (push-mark)
    (if (not (re-search-forward "\\\\scene" nil t 2))
        (re-search-forward "\\end{document}" nil t))
    (beginning-of-line)
    (recenter)))

Programming

FIXME common [%]

xref

(use-package xref
  :bind (:map xref--xref-buffer-mode-map
              ("c" . #'xref-prev-line)
              ("r" . #'xref-next-line)))

other things

(use-package projectile
  :ensure t
  :hook (prog-mode . projectile-mode)
  :bind (:map boon-command-map
              ("\\" . projectile-command-map)))
(use-package rainbow-delimiters
  :ensure t
  :hook (prog-mode . rainbow-delimiters-mode))

feature-mode

(use-package feature-mode
  :mode "\\.feature$")

smartparens

(use-package smartparens
  :ensure t
  :init
  (smartparens-global-mode)
  :config
  (require 'smartparens-config)
  (add-to-list 'sp-no-reindent-after-kill-modes 'markdown-mode)
  ;; (sp-pair "(" ")" :trigger-wrap (kbd "M-(") :actions '(insert wrap
  ;; autoskip navigate escape))
  (sp-pair "(" ")" :wrap "M-(")
  (sp-pair "[" "]" :wrap "M-[")
  (sp-pair "\"" "\"" :wrap "M-\"")
  ;; (global-unset-key (kbd "M-\'")) (sp-pair "\'" "\'" :wrap "M-\'")
  ;; ;; buggy
  (sp-pair "<" ">" :wrap "M-<")
  (sp-pair "{" "}" :wrap "M-{")
  ;; (sp-pair "\\\\*" "\\\\*" :actions '(wrap)) (sp-pair "\\\\*" :wrap)
  ;; (sp-pair "\\\\/" :wrap)
  ;; Disable inserting pair if preceded by : (e.g. in IRC smileys)
  (defun cp-point-after-colon-p ()
    (equal (string (char-before)) ":"))
  ;; (sp-pair "(" nil :unless '(cp-point-after-colon-p))

  ;; sp-backward-kill-word and subword-backward-kill conflict
  ;; (define-key emacs-lisp-mode-map (kbd "C-p") 'sp-previous-sexp)
  ;; (define-key emacs-lisp-mode-map (kbd "C-n") 'sp-next-sexp)

  :bind
  (("M-<up>" . sp-splice-sexp-killing-backward)
   :map prog-mode-map
   ("M-'" . sp-indent-defun)
   :map smartparens-mode-map
   ("C-)" . sp-forward-slurp-sexp)
   ("C-(" . sp-backward-slurp-sexp)
   ("C-}" . sp-forward-barf-sexp)
   ("C-{" . sp-backward-barf-sexp)
   ("C-j" . sp-newline)
   ("C-|" . sp-join-sexp)

   ("C-k" . sp-kill-hybrid-sexp)
   ("C-h" . sp-backward-delete-char)
   ("<backspace>" . sp-backward-delete-char)
   ("C-w" . sp-backward-kill-word)
   ("<C-backspace>" . sp-backward-kill-word)
   ("M-DEL" . sp-backward-kill-word)
   ("M-e" . sp-kill-word)

   ("C-M-p" . sp-backward-down-sexp)
   ("C-M-n" . sp-backward-up-sexp)
   ("C-M-b" . sp-backward-sexp)
   ("C-M-f" . sp-forward-sexp)
   ("C-M-u" . sp-up-sexp)
   ("C-M-d" . sp-down-sexp)
   ("C-M-a" . sp-beginning-of-sexp)
   ("C-M-e" . sp-end-of-sexp)

   ("C-M-k" . sp-kill-sexp)
   ("C-M-w" . sp-copy-sexp)
   :map emacs-lisp-mode-map
   (";" . sp-comment))
  :hook
  (eval-expression-minibuffer-setup . smartparens-mode)
  (paredit-mode . turn-off-smartparens-mode)
  (erc-mode . smartparens-mode))

lispy

Create advice for lispy-pair - if lispy--in-string-or-comment-p is true, self-insert (which smartparens will add the closing pair for)

(use-package lispy
  :ensure t
  :hook
  (emacs-lisp-mode . lispy-mode)
  (inferior-emacs-lisp-mode . lispy-mode)
  (lisp-mode . lispy-mode)
  (scheme-mode . lispy-mode)
  (slime-repl-mode . lispy-mode)
  ;; Boon-style keys on Dvorak
  :bind
  (:map lispy-mode-map
        ;; essential movement
        ("h" . special-lispy-left)  ;; QWERTY J
        ("s" . special-lispy-right) ;; QWERTY ;
        ("c" . special-lispy-up)    ;; QWERTY I
        ("r" . special-lispy-down)  ;; QWERTY O
        ;; ;; defined in :config
        ;; ("t" . special-lispy-backward)
        ;; ("n" . special-lispy-forward)
        ("i" . special-lispy-flow) ;; "inwards"

        ("l" . special-lispy-teleport)
        ("j" . special-lispy-occur)
        ;; essential manipulation
        ("k" . special-lispy-clone)
        ("K" . special-lispy-convolute)
        ("C" . special-lispy-move-up)
        ("R" . special-lispy-move-down)
        ("o" . special-lispy-splice) ;; QWERTY S
        ("p" . special-lispy-raise-some)
        ;; ;; defined in :config
        ;; ("T" . special-lispy-splice-sexp-killing-forward)
        ;; ("N" . special-lispy-splice-sexp-killing-backward)
        ;; ("l" . special-lispy-new-copy)
        ;; ;; Lispy shadows this, but it's essential for Org literate programs
        ("M-o" . nil)
        ("M-o M-o" . font-lock-fontify-block)
        ;; It does not insert a pair in strings or comments. I want
        ;; that. I'll let smartparens take care of it.
        ("(" . self-insert-command))
  ;; :config (setq lispy-mode-map-c-digits nil)
  :config
  ;; (dolist (key '("C-1" "C-2" "C-3" "C-4"))
  ;;   (define-key lispy-mode-map-c-digits (kbd key) nil))
  (lispy-set-key-theme '(lispy special))
  ;; These keys do not have special-* variants defined by default, so
  ;; here we define them ourselves.
  (cl-loop for (key . cmd) in
    '(("t" . lispy-backward)
      ("n" . lispy-forward)
      ("T" . lispy-splice-sexp-killing-forward)
      ("N" . lispy-splice-sexp-killing-backward)
      ("?" . lispy-describe-inline))
    do (lispy-define-key lispy-mode-map (kbd key) cmd))
  (setq lispy-colon-p nil))

imenu

(use-package imenu
  :hook (imenu-after-jump . (lambda () (recenter 0))) ;; also applies to `imenus'
  :config (setq imenu-auto-rescan t
                org-imenu-depth   10))
imenus
(use-package imenus
  :ensure t
  :commands imenus)

dump-jump

(use-package dumb-jump
  :init (add-hook 'xref-backend-functions #'dumb-jump-xref-activate))

dired-sidebar

(use-package dired-sidebar
  :config (setq dired-sidebar-no-delete-other-windows t
                dired-sidebar-should-follow-file t
                dired-sidebar-follow-file-idle-delay 0.2
                dired-sidebar-follow-file-timer 0.2))

lisp

(defun contrapunctus-lisp-copy (arg)
  "Run `whole-line-or-region-copy-region-as-kill' if region is
active, else `sp-copy-sexp'."
  (interactive "P")
  (if (region-active-p)
      (whole-line-or-region-copy-region-as-kill arg)
    (sp-copy-sexp arg)))

(defun colorize-compilation-buffer ()
  (ansi-color-apply-on-region compilation-filter-start
			      (point)))

(add-hook 'compilation-filter-hook 'colorize-compilation-buffer)

;; Stopped calling `exec-path-from-shell-initialize' on init, but
;; won't this keep running it excessively?
;; `exec-path-from-shell-copy-envs' (which it calls internally)
;; doesn't seem idempotent, either.
(use-package exec-path-from-shell
  :ensure t
  :hook
  (compilation-mode . exec-path-from-shell-initialize)
  (shell-mode-hook . exec-path-from-shell-initialize)
  (minibuffer-setup-hook . exec-path-from-shell-initialize))

Emacs Lisp

(use-package elisp-mode
  :diminish
  :bind
  (:map emacs-lisp-mode-map
        ;; ("<tab>" . 'company-indent-or-complete-common)
        ;; ("<C-tab>" . 'outline-toggle-children)
        ("M-n" . 'outline-next-heading)
        ("M-p" . 'outline-previous-heading)
        ("M-m" . macrostep-expand))
  :config
  (put 'cl-loop 'lisp-indent-function 'defun))
(use-package ielm
  :bind (:map ielm-map
              ("M-'" . sp-indent-defun)))
(defun cp/eval-sexp (arg)
  "In emacs-lisp-mode, just run eval-defun.
In other modes - jump to first Lisp expression in current line
and eval it."
  (interactive "P")
  (save-excursion
    (cond ((or (equal major-mode 'emacs-lisp-mode)
               (equal major-mode 'lisp-interaction-mode))
           (eval-defun arg))
          ((cp/re-search-line "(")
           (progn
             (forward-char -1)
             (forward-sexp)
             (eval-last-sexp arg)))
          (t nil))))

literate-elisp

(use-package literate-elisp
  :ensure t
  :commands (literate-elisp-load))

eldoc

(use-package eldoc
  :diminish
  :if (featurep 'elisp-mode)
  :init (add-hook 'emacs-lisp-mode-hook 'eldoc-mode)
  :config (setq eldoc-idle-delay 0
                eldoc-message-commands
                (vconcat eldoc-message-commands [sp-backward-delete-char])))

emr - emacs refactor

(use-package emr
  :bind (:map prog-mode-map
              ("M-S-<return>" . emr-show-refactor-menu)))

nameless-mode

(use-package nameless
  :ensure t
  :commands nameless-mode
  :hook
  (ert-results-mode . nameless-mode)
  (emacs-lisp-mode . nameless-mode)
  (org-mode . nameless-mode)
  :bind (:map emacs-lisp-mode-map
              ("C-c C-n" . nameless-mode)
              ("C-c C--" . nameless-insert-name)))

WIP async-tangle

Adapted from https://stackoverflow.com/questions/16815598/run-commands-in-emacs-asynchronously-but-display-output-incrementally/16816575#16816575 and the Elisp manual

(defun my-start-process* (buffer &rest command-specs)
  "Execute COMMAND-SPECS sequentially.
All COMMAND-SPECS should be a list in the form
\(NAME COMMAND COMMAND-ARGS*\)"
  (with-current-buffer buffer
    (set (make-local-variable 'commands-list) command-specs)
    (boon-mode)
    (my-start-next-process)))

(defun my-start-next-process ()
  "Run the first command in the list."
  (if (null commands-list)
      (insert "\nDone.")
    (-let* [(command-spec                  (car commands-list))
            ((name command . command-args) command-spec)]
      (setq commands-list (cdr commands-list))
      (insert (format ">>> %s\n" command))
      (let ((process (funcall #'start-process name (current-buffer) command command-args)))
        (set-process-sentinel process 'my-sentinel)))))

(defun my-sentinel (process event)
  "After a process exited, call `my-start-next-process' again"
  (let ((buffer (process-buffer process)))
    (when (buffer-live-p buffer)
      (with-current-buffer buffer
        (let ((moving (= (point) (process-mark process))))
          (save-excursion
            ;; Insert the text, advancing the process marker.
            (goto-char (process-mark process))
            ;; (insert (format "Command `%s' %s" process event))
            (set-marker (process-mark process) (point))
            (my-start-next-process))
          (when moving (goto-char (process-mark process))))))))
(defun contrapunctus-async-tangle (&optional prefix)
  "Use `org-babel-tangle' on the file visited by the current buffer."
  (interactive "P")
  (let* ((proc-buffer (get-buffer-create "*async-tangle-process*"))
         (file-name   (buffer-file-name))
         (file-name-no-ext (file-name-sans-extension (buffer-file-name)))
         (old-win     (selected-window)) ; ?
         (process     (start-process
                       "async-tangle" proc-buffer "emacs" "-q" "-Q" "--batch"
                       "--eval=(require 'ob-tangle)"
                       (format "--eval=(org-babel-tangle-file \"%s\")"
                               file-name file-name-no-ext))))
    ;; don't create window if buffer already visible
    (unless (get-buffer-window-list proc-buffer)
      ;; to avoid messing up my usual two-windows-same-buffer setup
      (select-window (split-window-below))
      (switch-to-buffer proc-buffer)
      ;; so I can access my Hydra to switch back
      (boon-mode))
    ;; (select-window old-win)
    ))

Common Lisp

(use-package lisp-mode
  :interpreter ("cl" . lisp-mode)
  :config
  (modify-syntax-entry ?\[ "(]" lisp-mode-syntax-table)
  (modify-syntax-entry ?\] ")[" lisp-mode-syntax-table))      ;; cl-launch scripts

redshank

(use-package redshank
  :ensure t
  :hook (slime-mode . redshank-mode))

slime

(use-package slime
  :ensure t
  :commands (slime-eval-buffer slime-eval-defun)
  :mode ("\\.asd$" . asdf-mode)
  :bind
  (:map slime-mode-map
        ("M-n" . next-line)
        ("M-p" . previous-line)
        ("SPC" . self-insert-command)
        ("<f1> <f1>" . slime-documentation)
        ("TAB" . company-indent-or-complete-common)
        ("C-i" . company-indent-or-complete-common))
  (:map slime-repl-mode-map
        ("M-p" . slime-repl-previous-matching-input) ;; QWERTY "r"
        ("M-c" . slime-repl-previous-input)
        ("M-r" . slime-repl-next-input)
        ("TAB" . company-indent-or-complete-common)
        ("C-i" . company-indent-or-complete-common))
  :config (slime-setup '(slime-fancy slime-company slime-tramp))
  (setq inferior-lisp-program
        ;; "/usr/bin/ecl"
        "sbcl"
        slime-net-coding-system 'utf-8-unix)
  (defun cp-slime-completion-in-region (_fn completions start end)
    (funcall completion-in-region-function start end completions))
  (advice-add 'slime-display-or-scroll-completions
              :around #'cp-slime-completion-in-region)
  (add-to-list 'slime-filename-translations
               (slime-create-filename-translator
                :machine-instance "tilde"
                :remote-host "tilde.team"
                :username "contrapunctus"))
  (add-to-list 'company-backends 'company-slime))
slime-fancy-inspector
(use-package slime-fancy-inspector
  :bind (:map slime-inspector-mode-map
              ("c" . slime-inspector-previous-inspectable-object)
              ("r" . slime-inspector-next-inspectable-object)
              ("n" . slime-inspector-operate-on-point)
              ("t" . slime-inspector-pop)
              ("<mouse-3>" . slime-inspector-pop)))

slime-company

(use-package slime-company
  :ensure t
  :after (slime company)
  :config (setq slime-company-completion 'fuzzy
                slime-company-after-completion 'slime-company-just-one-space))

common-lisp-snippets

(use-package common-lisp-snippets
  :ensure t
  :init (common-lisp-snippets-initialize))

Scheme

(setq scheme-program-name "csi -:c")
(setq comint-prompt-read-only t)

(use-package geiser
  :mode ("\\.scm\\'" . geiser-mode)
  :commands (run-chicken run-guile geiser-mode)
  :custom (geiser-active-implementations '(chicken))
  :config (setq geiser-scheme-implementation 'chicken))
;; (with-eval-after-load 'geiser-mode
;;   (setq geiser-mode-smart-tab-p t)
;;   (define-key geiser-mode-map (kbd "C-.") nil)
;;   ;; (cp-set-keys
;;   ;;  :unset t
;;   ;;  :keymap geiser-mode-map
;;   ;;  :bindings
;;   ;;  `((,(kbd "C-."))))
;;   )

(use-package scheme-mode
  :mode ("\\.scm\\'" . scheme-mode)
  :interpreter "csi")

CHICKEN Scheme

Guile

Lilypond

(use-package lilypond-mode
  :load-path "elisp-git/lilypond/elisp"
  :bind
  (("M-]"     . set-selective-display)
   :map LilyPond-mode-map
   ("M-c"     . cp-backward-def)
   ("M-r"     . cp-forward-def)
   ("M-C"     . cp-upper-level)
   ("M-R"     . cp-lower-level)
   ("C-c C-w" . cp-ly-wrap-para))
  :commands LilyPond-mode
  :mode (("\\.ly$"  . LilyPond-mode)
         ("\\.ily$" . LilyPond-mode))
  :config
  (--map (add-hook 'LilyPond-mode-hook it)
         '(subword-mode
           (lambda () (turn-on-font-lock))))
  (defalias 'string-to-int #'string-to-number)

  (defvar cp/ly-definition-rx
    '(and bol
          (1+ (any "a-z" "A-Z" "\\\\"))
          (1+ (any "a-z" "A-Z" "\\\\" " "))
          (any "{" "=" "#")))

  (defun cp-backward-def ()
    (interactive)
    (unless (region-active-p)
      (push-mark))
    (re-search-backward (rx-to-string cp/ly-definition-rx)
                        nil t)
    (beginning-of-line)
    (recenter))

  (defun cp-forward-def ()
    (interactive)
    (let* ((regex (rx-to-string cp/ly-definition-rx))
           (count (if (looking-at-p regex) 2 1)))
      (unless (region-active-p)
        (push-mark))
      ;; (forward-char)
      (if (not (re-search-forward regex nil t count))
          (re-search-forward "^}" nil t))
      ;; (re-search-forward "^[\\a-zA-Z]" nil t)
      (beginning-of-line)
      (recenter)))

  ;; (defun cp-backward-def ()
  ;;   (interactive)
  ;;   (re-search-backward "\(^\\\\?[a-zA-Z]\|^  *\\[a-zA-Z]\)")
  ;;   (beginning-of-line))

  ;; (defun cp-forward-def ()
  ;;   (interactive)
  ;;   (forward-char)
  ;;   (re-search-forward "\(^\\\\?[a-zA-Z]\|^  *\\[a-zA-Z]\)")
  ;;   (beginning-of-line))

  (defun cp-upper-level ()
    (interactive)
    (re-search-backward "{"))

  (defun cp-lower-level ()
    (interactive)
    (if (equal (string (char-after)) "{")
        (forward-char))
    (if (not (re-search-forward "{"))
        (message "At deepest level."))
    (backward-char))

  ;; (defun cp-lilypond-enclose-<< ()
  ;;   (interactive)
  ;;   (if (equal (string (char-after)) "\\")
  ;;       (progn (insert "<< ")
  ;;              (search-forward "{")
  ;;              (backward-char)
  ;;              (forward-sexp))))

  ;; if at a \new ... block - enclose expression
  ;; otherwise, enclose current position and after the first bar check
  ;; found
  ;; if region is active, enclose beginning and end

  ;; (defun cp-lilypond-enclose-<< ()
  ;;   (interactive)
  ;;   (if (equal (thing-at-point 'sexp)
  ;;              "\\new")
  ;;       (progn ;; (insert "<< ")
  ;;         (newline-and-indent)
  ;;         (search-forward "{")
  ;;         (backward-char)
  ;;         (forward-list)
  ;;         ;; (forward-sexp))
  ;;         )
  ;;     ;; (let ((point1 (point)))
  ;;     ;;   (next-line)
  ;;     ;;   (goto-char point1))
  ;;     ))
  ;; (define-key LilyPond-mode-map (kbd "<<")
  ;;   'cp-lilypond-enclose-<<)

  ;; If I change files, it's still main.ly that gets compiled; this is
  ;; good most of the time, but many times I want to compile a part-*
  ;; file instead. If we compile both main.ly and the respective part-*
  ;; file every time, it's wasteful. Having to select means giving up
  ;; the 'effortless-compilation' behaviour.

  ;; 2017-03-14T00:52:07+0530 - commented out, see cp/after-save
  ;; (defadvice LilyPond-save-buffer
  ;;     (after lysb activate)
  ;;   ;; (compile "make")
  ;;   (cd (locate-dominating-file (buffer-file-name)
  ;;                               "main.ly"))
  ;;   (compile (car compile-history)))
  ;;
  ;; (defadvice compile
  ;;     (before compile activate)
  ;;   (if (equal major-mode 'LilyPond-mode)
  ;;       (cd (locate-dominating-file (buffer-file-name)
  ;;                                   "main.ly"))))

  ;; TODO - refactor into one COND, with one case per operation.
  ;; TODO - operate on region as well.
  (defun cp-ly-wrap-para (arg)
    "Wrap current paragraph with -
\\relative c { ... } with no args,
\\repeat { ... } with universal argument,
and only braces - { ... } - with null argument.

Numeric arg wraps that many paragraphs.

TODO - wrap region if region active"
    (interactive "P")
    (let ((point-a (point)))
      (beginning-of-line)
      (unless (looking-at "[[:blank:]]*$")
        ;; go to start of paragraph or block, or previous blank line
        (re-search-backward (rx (or (and bol (0+ blank) eol)
                                    (and "{" eol))))
        (end-of-line))
      (newline-and-indent)
      (insert (pcase arg
                (`(,x) "\\repeat  {")
                (0     "{")
                ;; nil
                (_     "\\relative c {")))
      (let ((indent-start (point)))
        (forward-paragraph (pcase arg
                             (`(,x) 1)
                             (_ (if (and arg (<= arg 0))
                                    1 arg))))
        (indent-region indent-start (point))
        (insert "}")
        (indent-for-tab-command)
        (newline)
        ;; FIXME
        (goto-char (pcase arg
                     (0 point-a)
                     (_ (- indent-start 2))))))))

;; TODO - cp-ly-new-var, bind to M-RET.
;; Exits current variable body, if in any, and inserts "| = \relative
;; c {\n\n \n}", where | is the cursor

Prolog

(use-package ediprolog
  :commands ediprolog-dwim)

C

(use-package cc-mode
  :bind (:map c-mode-map
              ("TAB" . company-indent-or-complete-common)
              ("C-i" . company-indent-or-complete-common)))

(use-package irony-eldoc
  :hook
  (c-mode . irony-eldoc))

(use-package irony
  :config
  (add-hook 'irony-mode-hook #'irony-eldoc))

(use-package company-irony)

(use-package rtags
  :hook
  (c-mode . rtags-call-rc)
  :config
  (setq rtags-rc-binary-name  "rtags-rc"
        rtags-rdm-binary-name "rtags-rdm")
  :bind
  (:map c-mode-map
        ("<f2> <f2>" . rtags-find-symbol-at-point)))

web development

https://emacs.cafe/emacs/javascript/setup/2017/04/23/emacs-setup-javascript.html

Potentially useful - https://www.draketo.de/software/emacs-javascript.html

js2-mode

(use-package js2-mode
  :mode ("\\.js\\'" . js2-mode)
  ;; Better imenu
  :hook (js2-mode-hook . js2-imenu-extras-mode))

js2-refactor

(use-package js2-refactor
  :hook (js2-mode-hook . js2-refactor-mode))

tern

(use-package tern)

company-tern

(use-package company-tern
  :load-path "~/.emacs.d/elisp-git/company-tern/"
  :init (add-to-list 'company-backends 'company-tern))

skewer-mode

(use-package skewer-mode
  :hook (js2-mode-hook . skewer-mode))

text size change nicked from wasamasa's init - https://github.com/wasamasa/dotemacs/blob/934d0b37692d62fe9af56b52accac5bcd4445ae3/init.org

;; (set-face-attribute 'default nil :font "-outline-Bitstream Vera Sans Mono-normal-normal-normal-mono-12-*-*-*-c-*-iso8859-1")

SQL

(use-package sql
  ;; History is not saved if you kill the buffer; use
  ;; `comint-quit-subjob' or `comint-kill-subjob' to do so
  :hook
  (sql-interactive-mode . (lambda ()
                            (setq-local sql-input-ring-file-name
                                        (locate-user-emacs-file "sql-input-ring")))))

Python

(use-package python
  :config (setq python-shell-interpreter "python3"))

The end

Reset the GC settings, so Emacs doesn't use up tons of RAM.

(setq gc-cons-threshold 400000)

;; (toggle-debug-on-quit)
;; (profiler-stop)
;; (emacs-init-time)
;; (profiler-report)
(provide 'init)
;;; init.el ends here