dotemacs/init.org

163 KiB
Raw Blame History

org-babel-tangle takes nearly 27 seconds to tangle this file, at the time of writing. So for a brief while I used this sed script instead -

But after ironing out some issues, I switched back to literate-elisp. It does interoperate with the rest of Emacs, just be sure to not get any errors in your init!

Ever since I discovered Kmonad and used it to have Space trigger Ctrl when held, consequently making Ctrl the modifier requiring the least movement to hold, I've been using a lot more Ctrl-based bindings. Keep that in mind as you evaluate this configuration.

use-package

(eval-when-compile
  (add-to-list 'load-path "~/.emacs.d/elisp-git/use-package/")
  (require 'use-package))

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))

(general-auto-unbind-keys)
;; Most if not all of these are translations of their earlier QWERTY bindings.
(general-def
  "M-h" 'default-indent-new-line ;; see also org-mode
  "M-'" 'fill-paragraph
  "C-j" 'ctl-x-map
  "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

  ;; ;; Emacs-like
  "M-e" 'kill-word
  "C-e" 'delete-char
  "M-u" 'forward-word
  "M-q" 'execute-extended-command
  ;; "C-x" 'forward-char

  ;; ;; Boon-like
  ;; "M-h" 'forward-word
  ;; "M-s" 'forward-word
  "C-n" 'forward-char
  "C-t" 'backward-char)
(bind-keys
 ("C-h" . backward-delete-char) ;; see also ivy, company
 :map text-mode-map
 ;; Boon already binds xref-find-definitions to f
 ;; see also latex-mode
 ("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
(general-def 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)
(general-def 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)
(general-auto-unbind-keys t)

ido-mini

This needs to be before boon=/=exwm, or you get a "failed to define function ido-mini" error when you press the keybinding.

(use-package ido-mini
  :demand
  :load-path "~/.emacs.d/contrapunctus/ido-mini/"
  :bind ("C-x C-l" . ido-mini)
  :config
  (ivy-mode))

exwm   disabled

(setq exwm-input-global-keys
      '(([?\C-1] . select-window-1)
        ([?\C-2] . select-window-2)
        ([?\C-3] . select-window-3)
        ([?\C-4] . select-window-4)

        ([?\C-7] . select-window-1)
        ([?\C-8] . select-window-2)
        ([?\C-9] . select-window-3)
        ([?\C-0] . select-window-4)

        ([?\C-\\] . toggle-input-method)
        ([?\C-`] . shell)

        ([?\s-q] . exwm-input-toggle-keyboard)
        ([?\s-j] . split-window-below)
        ([?\s-k] . split-window-right)
        ([?\s-x] . delete-other-windows)

        ([?\s-b] . delete-other-windows)
        ([?\s-m] . contrapunctus-general-hydra/body)
        ([?\s-w] . ido-mini)
        ([?\s-v] . exwm-input-toggle-keyboard)))

Use window title for buffer names.

(require 'exwm)
(add-hook 'exwm-update-title-hook
          (lambda ()
            (exwm-workspace-rename-buffer exwm-title)))
(exwm-enable)

Helpers

A less repetitive way to start processes. Also enables Boon in the process buffer, so I can easy navigate it, copy text in it, or switch away from it.

(defun my-start-process (program &optional name &rest args)
  "Run PROGRAM with ARGS in buffer NAME."
  (interactive "P")
  (let* ((name        (if name name program))
         (proc-buffer (generate-new-buffer-name name)))
    (apply #'start-process name proc-buffer program args)
    (with-current-buffer proc-buffer (boon-mode))))

This is useful even without EXWM - switching does not work (…yet?), but I can launch apps via Hydra, and the output goes into the familiar environment of an Emacs buffer.

(defun my-start-app-or-switch (program &optional class-re title-re name &rest args)
  "Switch to EXWM buffer matching CLASS-RE or TITLE-RE, or run PROGRAM with ARGS.
NAME is the name of the process and its buffer."
  (let* ((name   (if name name program))
         (regex  (or class-re title-re program))
         (buffer (when (featurep 'exwm)
                   (seq-find
                    (lambda (buffer)
                      (with-current-buffer buffer
                        (if title-re
                            (and exwm-title
                                 (string-match-p title-re exwm-title))
                          (and exwm-class-name
                               (string-match-p regex exwm-class-name)))))
                    (buffer-list)))))
    (if buffer (switch-to-buffer buffer)
      (apply #'my-start-process program name args))))

Startup programs

(my-start-process (expand-file-name "~/bin/kmonad") "kmonad" (expand-file-name "~/kmonad-tvs.kbd"))
(my-start-process (expand-file-name "~/bin/kmonad") "kmonad" (expand-file-name "~/kmonad-legion.kbd"))
(my-start-process "transmission-gtk")
(my-start-process "gajim")
(my-start-process "/usr/lib/notification-daemon/notification-daemon")

volume

(use-package volume
  :ensure t
  :bind
  (:map volume-mode-map
        ("c" . volume-raise)
        ("r" . volume-lower)
        ("C-c" . volume-raise-10)
        ("C-r" . volume-lower-10)))

Emacs 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"
        edebug-print-length       nil
        ispell-dictionary         "en"
        scroll-conservatively     10000
        scroll-preserve-screen-position t
        auto-window-vscroll       nil)
  (setq-default undo-limit (* 80 1000))
  (load custom-file))

package

(use-package package
  :bind
  (:map package-menu-mode-map
        ("k" . package-autoremove)
        ("c" . previous-line)
        ("r" . next-line))
  :config
  (when (featurep 'boon)
    (general-def package-menu-mode-map
      "X" 'package-menu-execute)))

feather

(use-package feather
  :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

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

helpful

(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)

shackle

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

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
  :ensure t
  :config
  (setq x-underline-at-descent-line t)
  (moody-replace-mode-line-buffer-identification)
  (moody-replace-vc-mode))

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
  )

powerline

(use-package powerline
  :ensure t)

**

(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))

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)

**

(require 'cp-hindi)
;; (require 'cp-parens)

keyfreq   disabled

(use-package keyfreq
  :disabled
  :init
  (keyfreq-mode 1)
  (keyfreq-autosave-mode 1))
;; ;; disabled on 2017-08-18T19:39:21+0530, no longer interested
;; (open-dribble-file (concat "~/.emacs.d/keylogs/"
;;                            (format-time-string "%Y%m%d-%H%M%S")
;;                            ".txt"))

;; 2017-10-14T15:22:56+0530 - I suspect devanagari-itrans tires the
;; left hand faster than the right - let's find out!

;; ;; 2020-08-05T16:28:07+0530 commented out, no longer interested
;; (add-hook
;;  'input-method-activate-hook
;;  (lambda ()
;;    (open-dribble-file
;;     (concat
;;      "~/.emacs.d/keylogs/"
;;      (format-time-string "%Y%m%d-%H%M%S")
;;      "-"
;;      current-input-method
;;      ".txt"))))
;; (add-hook
;;  'input-method-deactivate-hook
;;  (lambda () (open-dribble-file nil)))

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-,"   'backward-paragraph
 "C-."   'forward-paragraph
 "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 cp/delete-trailing-whitespace ()
  (unless (derived-mode-p 'markdown-mode)
    (delete-trailing-whitespace)))

(add-hook 'before-save-hook 'cp/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)))
(with-eval-after-load 'text-mode
  (define-key text-mode-map (kbd "M-p") 'org-drag-line-backward)
  (define-key text-mode-map (kbd "M-n") '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))

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)

easy-kill   disabled

(use-package easy-kill
  :disabled
  :bind (("M-w" . easy-kill)
         ("M-d" . easy-kill-delete-region)))

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 \"" []

  ;; ;; " 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" []
  "q o" [?“] "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)
  :load-path "~/.emacs.d/elisp-git/boon"
  :bind
  (:map boon-command-map
        ("C-l" . recenter-top-bottom)
        ("p"   . swiper)
        ("I"   . join-line)
        ;; ("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)
        ("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'
        ("("  . boon-navigate-backward)
        (")"  . boon-navigate-forward)
        (". p" . swiper-thing-at-point)
        ("M"   . ido-mini)
        ("H"   . ido-mini)
        ;; these I prefer in their Dvorak positions rather than their QWERTY positions
        ("/" . undo-tree-undo)
        ("?" . undo-tree-redo)
        ("z" . boon-repeat-command))
  (: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))
  (dolist (var '((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)))
    (add-to-list 'boon-special-conditions var))
  (setq hi-lock-auto-select-face t)
  ;; (define-key boon-command-map (kbd "x d f") nil)
  (general-def boon-command-map
    "M"   'ido-mini
    "H"   'ido-mini
    ;; "x d" 'dired-jump
    )
  ;; :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))

experimental boon+modalka config   disabled

(use-package boon
  :disabled
  :bind
  ("C-d" . 'boon-take-region)
  ("C-a" . 'boon-beginning-of-line)
  ("C-e" . 'boon-end-of-line)
  ("M-f" . 'boon-smarter-forward)
  ("M-b" . 'boon-smarter-backward))

experimental Emacs-flavored-Boon config   disabled

(use-package boon
  :disabled
  :bind
  (:map boon-command-map
        ("n"   . next-line)
        ("p"   . previous-line)
        ("f"   . 'forward-char)
        ("b"   . 'backward-char)
        ("a"   . 'boon-beginning-of-line)
        ("e"   . 'boon-end-of-line)
        ("y"   . 'boon-splice)
        ("r"   . swiper)
        ("x l" . ido-mini)
        ("/"   . undo-tree-undo)
        ("?"   . undo-tree-redo))
  (:map boon-x-map
        ("s" . #'save-buffer)
        ("d" . #'dired-jump))
  :config
  (use-package boon-qwerty)
  (boon-mode))

modalka   disabled

<2019-11-03 Sun> I'm pretty much using this to emulate `god-mode', which was great, but is no longer actively developed and had no support for non-Latin input methods.

(use-package modalka
  :disabled
  :bind
  (("<escape>" . #'modalka-mode)
   :map modalka-mode-map
   ("J"   . #'join-line)
   ("P"   . #'backward-paragraph)
   ("N"   . #'forward-paragraph)
   ("M"   . mark-sexp))
  :config
  (let ((keybind-re (rx-to-string '(group-n 1 (or (and bow (1+ (char graph)) eow)
                                                  (and (1+ (char graph))))))))
    (mapc
     (lambda (actual)
       (let ((target (replace-regexp-in-string keybind-re "C-\\1" actual)))
         (modalka-define-kbd actual target)))
     ;; no "t", "x", or "c", because they are prefix keys used later
     '("`" "1" "2" "3" "4" "5" "6" "7" "8" "9" "0" "-" "="
           "q" "w" "e" "r"     "y" "u" "i" "o" "p" "[" "]" "\\"
            "a" "s" "d" "f"   "h" "j" "k" "l" ";" "'"
             "z"         "v" "b" "n" "m" "," "." "/"
                                         "<" ">" "?"
                           "SPC"
       ;; (2019-11-03) It's a little unfortunate that these cannot be
       ;; elided by entering ("x" "C-x") ("t" "C-t") ("c" "C-c") just
       ;; once :\ (I did try)
       "x =" "x -" "x e" "x s" "x d"
       "x f" "x l" "x x" "x c" "x v" "x b"
       "t w" "t e" "t l"
       "c n" "c ," "c ." "c p"
       "c c n" "c h u" "c h m")))
  (modalka-define-kbd "O" "C-S-o")
  (setq-default cursor-type '(bar . 1))
  (setq modalka-cursor-type 'box)
  :hook
  ((text-mode . modalka-mode)
   (prog-mode . modalka-mode)))

god mode   disabled

TODO - make mode-line indicator prominent by placing it first, like in Evil

(use-package god-mode
  :disabled t
  :bind ("<escape>" . god-mode-all)
  :init (god-mode-all)
  :config
  ;; from https://github.com/chrisdone/god-mode#change-modeline-color
  (defun c/god-mode-update-cursor ()
    (cond (god-local-mode
           (progn
             (setq cursor-type 'box)
             (set-face-background 'mode-line "black")
             (set-face-background 'mode-line-inactive "black")))
          (t
           (progn
             (setq cursor-type 'bar)
             (set-face-background 'mode-line "dark red")
             (set-face-background 'mode-line-inactive "dark red")))))
  (add-hook 'god-mode-enabled-hook #'c/god-mode-update-cursor)
  (add-hook 'god-mode-disabled-hook #'c/god-mode-update-cursor)
  :custom
  (god-exempt-major-modes nil)
  (god-exempt-predicates nil))

;; for helm
;; (define-key helm-map (kbd "<escape>") 'god-local-mode)

(use-package god-mode-isearch
  :disabled t
  :bind
  ((:map isearch-mode-map
         ("<escape>" . god-mode-isearch-activate))
   (:map god-mode-isearch-map
         ("<escape>" . god-mode-isearch-disable))))

Applications

Things not directly pertaining to text editing.

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

time tracking - chronometrist

choice.el is required by chronometrist-key-values

goal

(use-package chronometrist-goal
  :load-path "~/.emacs.d/contrapunctus/chronometrist-goal/"
  :hook (chronometrist-mode . chronometrist-goal-minor-mode)
  :config
  (setq chronometrist-goal-list
        '((30  "Arrangement/new edition")
          (15  "Aural exercises")
          (15  "Transcription" "Theory")
          ;; was 30 before, but that was too little for composing
          (60  "Composing" "Writing" "Recording")
          (15  "Data organization" "Physical organization" "Khilona archiving")
          (60  "Exercise")
          (120 "Guitar")
          (90  "Reading")
          (60  "Singing")
          (20  "Subtitles")
          (15  "Acting")
          (30  "Keyboard")
          (15  "Wikisource"))
        alert-default-style 'libnotify))

spark

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

chronometrist

(use-package chronometrist
  ;; :disabled t
  :load-path "/media/data/anon/Documents/programming/elisp/chronometrist/elisp/"
  :init
  (chronometrist-goal-minor-mode)
  (chronometrist-spark-minor-mode)
  :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)))
  :config
  (setq chronometrist-debug-enable t
        chronometrist-active-backend       :plist-group
        chronometrist-before-in-functions  '()
        chronometrist-after-in-functions   '(;; chronometrist-tags-add
                                  ;; chronometrist-kv-add
                                  contrapunctus-start-project)
        chronometrist-before-out-functions '(contrapunctus-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  '(contrapunctus-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)]
        chronometrist-task-list '("Acting" "Arrangement/new edition" "Aural exercises"
                       "Composing" "Cooking"
                       "Data organization" "Digitization"
                       "Exercise" "Guitar" "Housekeeping" "Keyboard" "Khilona archiving"
                       "OSM" "Performance" "Physical organization" "Programming"
                       "Reading" "Recording" "Sequencing" "Singing" "Subtitles"
                       "Teaching" "Theatre" "Theory" "Transcription"
                       "Video editing" "Voice" "Wikisource" "Wiktionary" "Writing")))
activity-indicator
(defun my-chronometrist-activity-indicator ()
  (--> (chronometrist-latest-record (chronometrist-active-backend))
       (plist-put it :stop (chronometrist-format-time-iso8601))
       (list it)
       (chronometrist-events-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))
start-project
(defvar my-arrangement-frameset
  [frameset
   1 (24907 10263 320473 920000) nil nil nil nil
   ((((minibuffer . t)
      (undecorated)
      (override-redirect)
      (font . "-PfEd-DejaVu Sans Mono-normal-normal-normal-*-14-*-*-*-m-0-iso10646-1")
      (font-parameter . "DejaVu Sans Mono-10.5")
      (border-width . 0)
      (internal-border-width . 0)
      (right-divider-width . 0)
      (bottom-divider-width . 0)
      (vertical-scroll-bars)
      (horizontal-scroll-bars)
      (foreground-color . "#d6d6d4")
      (background-color . "#1c1e1f")
      (mouse-color . "black")
      (border-color . "black")
      (screen-gamma)
      (line-spacing)
      (left-fringe . 4)
      (right-fringe . 4)
      (no-special-glyphs)
      (scroll-bar-foreground)
      (scroll-bar-background)
      (menu-bar-lines . 1)
      (tab-bar-lines . 0)
      (height . 40)
      (tool-bar-lines . 0)
      (title)
      (wait-for-wm . t)
      (tool-bar-position . top)
      (inhibit-double-buffering)
      (icon-type . t)
      (auto-raise)
      (auto-lower)
      (cursor-type . box)
      (scroll-bar-width . 16)
      (scroll-bar-height . 16)
      (alpha)
      (no-focus-on-map)
      (no-accept-focus)
      (fullscreen . maximized)
      (visibility . t)
      (skip-taskbar)
      (z-group)
      (display-type . color)
      (background-mode . dark)
      (cursor-color . "#fb2874")
      (sticky)
      (environment)
      (last-focus-update . t)
      (powerline-cache)
      (frameset--id . "6557-E2CE-3D63-0FD0")
      (frameset--mini t . t)
      (width . 169)
      (modeline . t)
      (unsplittable)
      (left . 0)
      (top . 0)
      (icon-name)
      (display . ":0.0")
      (explicit-name))
     ((min-height . 8)
      (min-width . 20)
      (min-height-ignore . 4)
      (min-width-ignore . 6)
      (min-height-safe . 2)
      (min-width-safe . 4)
      (min-pixel-height . 136)
      (min-pixel-width . 160)
      (min-pixel-height-ignore . 68)
      (min-pixel-width-ignore . 48)
      (min-pixel-height-safe . 34)
      (min-pixel-width-safe . 32))
     vc (pixel-width . 1366)
     (pixel-height . 669)
     (total-width . 171)
     (total-height . 39)
     (normal-height . 1.0)
     (normal-width . 1.0)
     (combination-limit)
     (hc
      (pixel-width . 1366)
      (pixel-height . 507)
      (total-width . 171)
      (total-height . 29)
      (normal-height . 0.7661169415292354)
      (normal-width . 1.0)
      (combination-limit)
      (leaf (pixel-width . 686)
        (pixel-height . 507)
        (total-width . 86)
        (total-height . 29)
        (normal-height . 1.0)
        (normal-width . 0.5)
        (buffer "2021-09-12 Songs of Travel-pacON.pdf"
                (selected)
                (hscroll . 0)
                (fringes 4 4 nil nil)
                (margins nil)
                (scroll-bars nil 0 t nil 0 t nil)
                (vscroll . 0)
                (dedicated)
                (point . 1)
                (start . 1)))
      (leaf (last . t)
        (pixel-width . 680)
        (pixel-height . 507)
        (total-width . 85)
        (total-height . 29)
        (normal-height . 1.0)
        (normal-width . 0.5)
        (buffer "IMSLP89688-PMLP183796-SongsOfTravel.pdf"
                (selected)
                (hscroll . 0)
                (fringes 4 4 nil nil)
                (margins nil)
                (scroll-bars nil 0 t nil 0 t nil)
                (vscroll . 0)
                (dedicated)
                (point . 1)
                (start . 1))))
     (leaf (last . t)
       (pixel-width . 1366)
       (pixel-height . 162)
       (total-width . 171)
       (total-height . 9)
       (normal-height . 0.23388305847076465)
       (normal-width . 1.0)
       (buffer "guitar.ly"
               (selected . t)
               (hscroll . 0)
               (fringes 4 4 nil nil)
               (margins nil)
               (scroll-bars nil 0 t nil 0 t nil)
               (vscroll . 0)
               (dedicated)
               (point . 1)
               (start . 1)))))])
(defvar my-composition-frameset
  [frameset 1
          (24995 986 559662 42000)
          nil nil nil nil
          ((((minibuffer . t)
             (undecorated)
             (override-redirect)
             (font . "-PfEd-DejaVu Sans Mono-normal-normal-normal-*-14-*-*-*-m-0-iso10646-1")
             (font-parameter . "DejaVu Sans Mono-10.5")
             (border-width . 0)
             (internal-border-width . 0)
             (right-divider-width . 0)
             (bottom-divider-width . 0)
             (vertical-scroll-bars)
             (horizontal-scroll-bars)
             (foreground-color . "#d6d6d4")
             (background-color . "#1c1e1f")
             (mouse-color . "black")
             (border-color . "black")
             (screen-gamma)
             (line-spacing)
             (left-fringe . 4)
             (right-fringe . 4)
             (no-special-glyphs)
             (scroll-bar-foreground)
             (scroll-bar-background)
             (menu-bar-lines . 1)
             (tab-bar-lines . 0)
             (height . 58)
             (tool-bar-lines . 0)
             (title)
             (wait-for-wm . t)
             (tool-bar-position . top)
             (inhibit-double-buffering)
             (icon-type . t)
             (auto-raise)
             (auto-lower)
             (cursor-type . box)
             (scroll-bar-width . 16)
             (scroll-bar-height . 16)
             (alpha)
             (no-focus-on-map)
             (no-accept-focus)
             (fullscreen . maximized)
             (visibility . t)
             (skip-taskbar)
             (z-group)
             (display-type . color)
             (background-mode . dark)
             (cursor-color . "#fb2874")
             (sticky)
             (environment)
             (last-focus-update . t)
             (powerline-cache)
             (frameset--id . "3226-BFF7-1499-D0C7")
             (frameset--mini t . t)
             (modeline . t)
             (unsplittable)
             (icon-name)
             (display . ":0.0")
             (explicit-name)
             (width . 235)
             (left . 31)
             (top . 0))
            ((min-height . 8)
             (min-width . 20)
             (min-height-ignore . 4)
             (min-width-ignore . 6)
             (min-height-safe . 2)
             (min-width-safe . 4)
             (min-pixel-height . 136)
             (min-pixel-width . 160)
             (min-pixel-height-ignore . 68)
             (min-pixel-width-ignore . 48)
             (min-pixel-height-safe . 34)
             (min-pixel-width-safe . 32))
            hc
            (pixel-width . 1889)
            (pixel-height . 981)
            (total-width . 236)
            (total-height . 58)
            (normal-height . 1.0)
            (normal-width . 1.0)
            (combination-limit)
            (leaf
              (pixel-width . 945)
              (pixel-height . 981)
              (total-width . 118)
              (total-height . 58)
              (normal-height . 1.0)
              (normal-width . 0.5)
              (buffer "2 Mera Mera Kyon-pacON.pdf"
                      (selected)
                      (hscroll . 0)
                      (fringes 4 4 nil nil)
                      (margins nil)
                      (scroll-bars nil 0 t nil 0 t nil)
                      (vscroll . 0)
                      (dedicated)
                      (point . 1)
                      (start . 1))
              (prev-buffers
               ("music" 1 521)
               ("2 Mera Mera Kyon-pacON.pdf" 1 1)
               ("composition-portfolio.org" 1 1)
               ("init.org[emacs-lisp]" 28440 29961)
               ("specifications.org" 1 1)
               ("doc<magrathea>" 1 183)
               ("magrathea" 1 181)
               ("src<magrathea>" 1 184)
               ("strata" 1 431)
               ("skylab.lisp" 851 2757)
               ("Inception (2010) [1080p]" 1 379)
               ("chronometrist.el" 33732 34395)
               ("*helpful variable: timer-list*" 1 802)))
            (vc
             (last . t)
             (pixel-width . 944)
             (pixel-height . 981)
             (total-width . 118)
             (total-height . 58)
             (normal-height . 1.0)
             (normal-width . 0.5)
             (combination-limit)
             (leaf
               (pixel-width . 944)
               (pixel-height . 482)
               (total-width . 118)
               (total-height . 28)
               (normal-height . 0.5)
               (normal-width . 1.0)
               (buffer "music"
                       (selected . t)
                       (hscroll . 0)
                       (fringes 4 4 nil nil)
                       (margins nil)
                       (scroll-bars nil 0 t nil 0 t nil)
                       (vscroll . 0)
                       (dedicated)
                       (point . 444)
                       (start . 1))
               (prev-buffers
                ("2 Mera Mera Kyon" 1 213)
                ("guitar.ly" 1 1)
                ("music" 1 444)))
             (leaf
               (last . t)
               (pixel-width . 944)
               (pixel-height . 499)
               (total-width . 118)
               (total-height . 30)
               (normal-height . 0.5)
               (normal-width . 1.0)
               (buffer "*compilation*"
                       (selected)
                       (hscroll . 0)
                       (fringes 4 4 nil nil)
                       (margins nil)
                       (scroll-bars nil 0 t nil 0 t nil)
                       (vscroll . 0)
                       (dedicated)
                       (point . 1)
                       (start . 1))
               (prev-buffers
                ("*compilation*" 1 1)
                ("music" 1 521))))))])
(defun contrapunctus-start-project (project)
  (delete-other-windows)
  (pcase project
    ("Acting"
     ;; (find-file
     ;;  "/media/data/anon/Documents/sync/Wilde, Oscar/The Importance of Being Earnest/gutenberg-script.txt")
     (find-file "/media/data/anon/Documents/sync/hindi/Harishankar Parsai - Nithalle Ki Diary.pdf")
     )
    ("Arrangement/new edition"
     (my-find-file*
      "/media/data/anon/1-music-scores/4-my-arrangements/2021/2021-09-12 Songs of Travel/1 The Vagabond/music/guitar.ly"
      "/media/data/anon/1-music-scores/4-my-arrangements/2021/2021-09-12 Songs of Travel/output/2021-09-12 Songs of Travel-pacON.pdf"
      "~/Sync/Scores/voice/Vaughan Williams/IMSLP89688-PMLP183796-SongsOfTravel.pdf")
     (frameset-restore my-arrangement-frameset :reuse-frames t))
    ("Aural exercises"
     (find-file-other-window "/media/data/anon/Documents/Text Files/music_stuff/harmonic-analysis.org"))
    ((or "Sequencing" "Composing")
     (my-find-file*
      "~/1-music-scores/2-my-compositions/composition-portfolio.org"
      "~/1-music-scores/2-my-compositions/2016/2016-07 Sab Ka Sooraj/2 Mera Mera Kyon/output/2 Mera Mera Kyon-pacON.pdf"
      "~/1-music-scores/2-my-compositions/2016/2016-07 Sab Ka Sooraj/2 Mera Mera Kyon/music/")
     (frameset-restore my-composition-frameset :reuse-frames t))
    ("Data organization"
     (find-dired "/media/data/anon/" "-name \\'dl\\' -size +0c"))
    ("Digitization"
     (my-find-file*
      "/media/data/anon/Documents/Text Files/latex/Don't, Mr. Disraeli!/dont-mr-disraeli.tex"
      "/media/data/anon/Documents/Text Files/latex/Don't, Mr. Disraeli!/dont-mr-disraeli.pdf"))
    ("Exercise" (chronometrist-key-values-unified-prompt "Exercise")
     (chronometrist-edit-backend (chronometrist-active-backend)))
    ("Guitar"
     (let* ((path-1 "~/Sync/Scores/guitar-solo/repertoire.org")
            (path-2 "~/Sync/Scores/guitar-duo/repertoire.org")
            (weekday (elt (decode-time) 6))
            (week    (string-to-number (format-time-string "%U"))))
       ;; (contrapunctus-find-two-files path-1 path-2)
       (find-file-other-window path-1)
       (select-window (get-buffer-window (get-file-buffer path-1)))
       (org-match-sparse-tree nil "perform")))
    ("Keyboard"
     (find-file-other-window "/media/data/anon/Documents/Text Files/music_stuff/piano.org")
     (outline-show-subtree))
    ("Khilona archiving"
     (find-file-other-window "/media/data/anon/Documents/sync/Khilona/")
     (split-window-below)
     (other-window 1)
     (find-file "~/Khilona/Videos/Me?/")
     (other-window 1)
     (find-file "/media/data/anon/Documents/Text Files/khilona/2011 Me?/script/script.tex"))
    ("OSM"
     ;; (delete-window (get-buffer-window " *JOSM*"))
     (contrapunctus-find-two-files "/media/data/phone/anon/Nokia 6.1/Android/data/net.osmand.plus/files/"
                      ;; "/media/data/phone/anon/Nokia 6.1/external/DCIM/OpenCamera/osm/"
                      "/media/data/phone/anon/Nokia 6.1/Documents/Markor/OSM/TODO.md"))
    ("Programming"
     ;; (eww-open-file "/media/data/anon/git/cl/McCLIM/Documentation/Manual/mcclim.html")
     (find-file-other-window "~/Documents/Text Files/programming/projects.org"))
    ("Reading"
     (find-file-other-window "/media/data/anon/Documents/sync/"))
    ("Recording"
     (find-file-other-window
      "/media/data/anon/8-music-production/1-my-creations/BWV 1006a/1 Prelude/"))
    ("Singing"
     (find-file-other-window
      "~/Sync/Scores/voice/repertoire.org"))
    ("Subtitles"
     ;; (find-file-other-window "/media/data/anon/Music/0-classical/vocal/musical/Company/")
     (start-process "subtitleeditor" nil "subtitleeditor" "/media/data/khilona/Videos/Peer Gynt/peer-gynt.srt"))
    ("Teaching"
     (find-file-other-window "/media/data/anon/Documents/Text Files/students/")
     ;; (launch-file "/media/data/anon/Sync/Scores/voice/jingle-bell-rock.pdf")
     )
    ("Theatre rehearsal"
     (contrapunctus-find-two-files
      "/media/data/anon/Documents/Text Files/khilona/voices.org"
      "/media/data/anon/1-music-scores/2-my-compositions/2019/2019-03 Kahe Natak Karte Ho Ji/Kahe Natak Karte Ho Ji.org"))
    ("Transcription"
     ;; (find-file-other-window
     ;;  "/media/data/phone/anon/Nokia 6.1/Documents/Markor/Music/transcriptions.md")
     ;; (emms-play-file "/media/data/anon/Music/Hallelujah Rufus Wainwright-xR0DKOGco_o.opus")
     (my-find-file*
      "/media/data/anon/1-music-scores/4-my-arrangements/2021/2021-03-11 Hallelujah/output/2021-03-11 Hallelujah-pacON.pdf"
      "/media/data/anon/1-music-scores/4-my-arrangements/2021/"))
    ("Video editing"
     (start-process "kdenlive" (generate-new-buffer-name "kdenlive")
                    ;; "flatpak" "run" "org.kde.kdenlive"
                    "kdenlive"
                    ;; "/home/khilona/Videos/ghar ghar theatre 3/ggt3.kdenlive"
                    "/media/data/khilona/Videos/podcast/episode 3/episode 3.kdenlive")
     (find-file-other-window ;; "/home/khilona/Videos/ghar ghar theatre 3/"
      "/media/data/khilona/Videos/podcast/"))
    ("Wiktionary"
     (find-file-other-window
      "/media/data/phone/anon/Nokia 6.1/Documents/Markor/Languages/hindi.md"))
    ("Writing"
     (find-file-other-window
      "/media/data/phone/anon/Nokia 6.1/Documents/Markor/Songs or Poems/"))))
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)))
before-project-stop
(defun contrapunctus-before-project-stop (project)
  (if (member project '("Composing" "Khilona archiving" "Programming"))
      (contrapunctus-commit-prompt)
    ;; all functions in `chronometrist-before-project-stop-functions'
    ;; must return t for successful clock-out
    t))
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)
(use-package request
  :ensure t)
(use-package esxml
  :ensure t)

(defun my-get-changeset-comment (changeset)
  (->> (esxml-query "[k=comment]" changeset)
       (esxml-node-attributes)
       (alist-get 'v)))

(defun my-make-osm-url (id)
  (concat "https://www.openstreetmap.org/changeset/" id))

(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 "/"))))))

(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)
    (chronometrist-replace-last (chronometrist-active-backend) new-plist)))

(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))

(defun contrapunctus-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)))

key-values

(use-package chronometrist-key-values
  :ensure t
  :after chronometrist
  :load-path "~/.emacs.d/contrapunctus/chronometrist/elisp/")

count-expressions

tangling

Wrote these two as potential alternatives to `org-babel-tangle', which was far slower than I'd like (took around 20s for chronometrist.org when I checked during the migration process, and 43s after the migration was complete.) These, on the other hand, are almost instant, but I don't use them anywhere because I run a sed script as a file local variable.

(defun chronometrist-tangle ()
  (goto-char (point-min))
  (cl-loop with source
    while (not (eobp))
    when (looking-at-p (rx (and line-start (zero-or-more blank) line-end)))
    concat (progn
             (forward-line 1)
             (buffer-substring-no-properties
              (point)
              (cl-loop while (not (eobp))
                if (looking-at-p (rx (and line-start
                                          (zero-or-more blank)
                                          line-end)))
                do (cl-return (point))
                else do (forward-line 1)))) into source
    do (forward-line 1)
    finally do
    (with-current-buffer (find-file-noselect "chronometrist.el")
      (delete-region (point-min) (point-max))
      (insert source)
      (save-buffer))))

(defun chronometrist-tangle-sed ()
  (let* ((file-path (buffer-file-name
                     (current-buffer)))
         (base      (file-name-base file-path)))
    (when (equal "chronometrist.org" (file-name-nondirectory file-path))
      (start-process-shell-command
       "sed-tangle"
       (generate-new-buffer-name "sed-tangle")
       (format "sed -n '/#+BEGIN_SRC emacs-lisp$/,/#+END_SRC$/{//!p;}' ~s.org > ~s.el" base base)))))

querying data

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

(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, over a period of %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 for plist in (chronometrist-to-list (chronometrist-active-backend))
  when (equal (plist-get plist :name) "Exercise")
  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 "%s %s" distance-int (cdr distance))))
      (format "%s%s in %s (%s)\n"
              (if (not first-line)
                  (make-string (+ 3 (length date-iso)) ?\s)
                (setq first-line nil)
                (concat date-iso " - "))
              distance-string
              (ts-human-format-duration duration-seconds)
              (format "%.2f kmph" (/ distance-km duration-hours)))))
  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)
  ;; (emms-default-players)
  (setq 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"
     ;; "-ao=jack,alsa"
     ;; "--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

(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)
        ("s" . #'eww-forward-url)
        ("c" . #'shr-previous-link)
        ("r" . #'shr-next-link)
        ("v" . nil))
  ;; 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))))

url-cookie

Ask for confirmation before saving cookies. I'd rather just disallow them all though 🤔

(use-package url-cookie
  :config
  (setq url-cookie-confirmation t))

elpher

(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))

jabber   disabled

(use-package jabber
  :disabled t
  :load-path "~/.emacs.d/elisp-git/emacs-jabber-wgreenhouse/"
  :commands jabber-connect
  :config (global-unset-key (kbd "C-x C-j"))
  (global-set-key (kbd "C-x C-j") 'join-line)
  (setq jabber-history-enabled t
        jabber-history-muc-enabled t
        jabber-alert-presence-message-function nil
        jabber-account-list '(("contrapunctus@jabjab.de")))
  (add-to-list 'jabber-post-connect-hooks 'jabber-enable-carbons))

sxiv

(use-package sxiv
  :load-path "~/.emacs.d/contrapunctus/sxiv/"
  :config (setq sxiv-exclude-strings '("meh" "\\.NEF$"))
  :bind ("<f2> s"  . sxiv)
  (:map dired-mode-map
        ("I" . sxiv)))

#+END_SRC

TODO emacsshot

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/anon/Pictures/screenshots/emacsshot/emacsshot.png"))

File management

(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"
        )
  :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"     . cp/corresponding-text-file)
   ("h"           . dired-hide-dotfiles-mode)
   ("H"           . dired-omit-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
  :bind
  ("C-x C-d" . dired-jump))
;; 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))
(defun cp/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 cp/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 contrapunctus-delete-file-at-point (&optional prefix)
  (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 contrapunctus-file-at-point-exists-p ()
  (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))))

peep-dired   disabled

(use-package peep-dired
  :disabled
  ;; ;; didn't work too well 🤔
  ;; :config
  ;; (setq peep-dired-cleanup-eagerly t)
  :hook
  (dired-mode . peep-dired))
(defun contrapunctus-rename-this-file ()
  (interactive)
  (rename-file ;; message "will rename %s to %s"
           (buffer-file-name (current-buffer))
           (read-file-name "New name: " nil nil nil
                           (file-name-nondirectory
                            (buffer-file-name (current-buffer)))))
  ;; (rename-file (buffer-file-name (current-buffer)) new-name)
  )

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)

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

mail

wanderlust   disabled

(use-package wl
  :disabled
  :config
  (setq wl-icon-directory "~/.emacs.d/wl/icons"
        wl-smtp-posting-server "disroot.org"))

mew   disabled

(use-package mew
  :disabled
  :config
  (setq mew-user "contrapunctus"
        mew-name mew-user
        mew-mail-domain "disroot.org"
        mew-proto "%"
        mew-smtp-server mew-mail-domain
        mew-imap-server mew-mail-domain))

gnus

(use-package gnus
  :init ;; I composed an email without loading Gnus, and it wasn't
  ;; saved in my sent folder; here's hoping that changing :config to
  ;; :init will do it.
  (setq gnus-select-method '(nnnil "")
        gnus-secondary-select-methods '((nnml "")
                                        (nnimap "disroot.org")
                                        ;; (nnimap "imap.gmail.com"
                                        ;;         (nnimap-stream starttls))
                                        )
        user-mail-address "contrapunctus@disroot.org"
        user-full-name    "contrapunctus"
        gnus-mime-display-multipart-related-as-mixed t
        smiley-style 'medium
        message-send-mail-function 'smtpmail-send-it
        ;; gnus-message-archive-method '(nnimap "disroot.org")
        gnus-message-archive-group "nnimap+disroot.org:Sent"))

(use-package message
  :hook
  (message-mode . (lambda () (auto-fill-mode -1)))
  (message-mode . visual-fill-column-mode))

doc-view

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

pdf-tools

(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-s" . isearch-forward)
        ("g" . pdf-view-first-page)
        ("l" . pdf-view-last-page))
  :config
  (setq-default pdf-view-display-size 'fit-width))

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

nov.el

(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)))

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))))

#+END_SRC

comint

(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

(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

(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" . contrapunctus-rename-this-file)))

TODO magit [0%]

  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

(use-package git-commit
  :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 typying (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
        completion-styles '(initials partial-completion basic emacs22)))

counsel

(use-package counsel
  :ensure t
  :bind ("M-x" . counsel-M-x)
  :config
  (setq counsel-find-file-ignore-regexp "\\`\\."))

Ivy

(use-package ivy
  :ensure t
  :commands ivy-mode
  :init (ivy-rich-mode)                 ;; display command docstrings in `counsel-M-x'
  :bind (:map ivy-minibuffer-map
              ("C-h" . ivy-backward-delete-char)
              ("C-c" . previous-line)
              ("C-r" . next-line)
              ("M-c" . ivy-previous-history-element)
              ("M-r" . ivy-next-history-element))
  :config
  (setq ivy-re-builders-alist
        '((t . ivy--regex-ignore-order))))

ivy-xref

(use-package ivy-xref
  :ensure t
  :config
  (setq xref-show-definitions-function #'ivy-xref-show-defs))

flx-ido   disabled

(use-package flx-ido
  :disabled
  :init (flx-ido-mode 1)
  (setq ido-enable-flex-matching t
        ido-use-faces nil))

flx-isearch   disabled

(use-package flx-isearch
  :disabled
  :bind
  ("C-s" . #'flx-isearch-forward)
  ("C-r" . #'flx-isearch-backward))

;; (use-package flycheck
;;   :init (global-flycheck-mode))

;; (use-package flycheck-elsa
;;   :hook (emacs-lisp-mode . flycheck-elsa-setup))

flex-isearch

(use-package flex-isearch
  :disabled t
  :init (flex-isearch-mode 1)
  :bind
  ("C-M-s" . flex-isearch-forward)
  ("C-M-r" . flex-isearch-backward))

company

(use-package company
  :ensure t
  :diminish company-mode
  :hook (prog-mode . company-mode)
  (special-mode . company-mode)
  (comint-mode . company-mode)
  (slime-repl-mode . company-mode)
  :bind ;; ("TAB" . company-indent-or-complete-common)
  (:map emacs-lisp-mode-map
        ("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))
  ;; 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)
  (customize-set-variable company-quick-access-modifier 'control))

company-emoji

(use-package company-emoji
  :ensure t
  :hook (text-mode . company-emoji-init)
  :config (add-to-list 'company-backends 'company-emoji))

company-prescient

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

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)

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)
    ("+" 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" contrapunctus-line-display-hydra/body "line display")
    ("o" save-buffer "save")
    ("s" imenus "imenus")
    ("S" imenu-list "sidebar")
    ("T" cp-insert-timestamp "timestamp")
    ("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")
  ("k" helpful-key "key")
  ("h" helpful-at-point "here")
  ("m" man "man page")
  ("c" describe-char "character"))

Search

(defhydra my-search-hydra (:color blue)
  "Search command:"
  ("f" find-dired "find-dired")
  ("g" grep "grep")
  ("r" rgrep "rgrep")
  ("a" ag "ag")
  ("h" find-grep "find-grep"))

General

(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))
(my-defhydra contrapunctus-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" elpher "elpher")
  ("i" sxiv "sxiv")
  ("l" proced "list processes")
  ("m" 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" contrapunctus-emms-hydra/body "emms")

  ;; external
  ("g" (my-start-app-or-switch "gajim" nil "^Gajim$") "Gajim")
  ("b" (my-start-app-or-switch
        "/media/data/anon/ext/tor-browser_en-US/Browser/start-tor-browser"
        "^Tor Browser$")
   "Tor Browser")
  ("B" (my-start-app-or-switch
        "/media/data/anon/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"))

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"
  ("L" my-literate-elisp-hydra/body "literate-elisp")
  ("p" org-set-property "property")
  ("l" my-org-hydra-block/body "source block")
  ("t" my-org-hydra-set-tags "tags")
  ("C-t" org-todo "todo" :color red)
  ("n" 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")

  ("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 source block"
  ("e" (my-org-hydra-insert-block "SRC" "emacs-lisp") "Emacs Lisp")
  ("t" (my-org-hydra-insert-block "SRC" "emacs-lisp :tangle test :load test") "Emacs Lisp test")
  ("E" (my-org-hydra-insert-block "SRC" "emacs-lisp :tangle no :load no") "Emacs Lisp example")
  ("h" (my-org-hydra-insert-block "SRC" "sh") "Shell")
  ("o" (my-org-hydra-insert-block "QUOTE") "quote")
  ("v" (my-org-hydra-insert-block "VERSE") "verse")
  ("s" (my-org-hydra-insert-block "SRC" "scheme") "Scheme")
  ("l" (my-org-hydra-insert-block "SRC" "lisp") "Common Lisp"))

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")
  ;; 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")
  ("n" my-elisp-insert-hydra/body "Insert")
  ("L" my-literate-elisp-hydra/body "literate-elisp"))

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-cl-hydra (:color blue)
  "Common Lisp"
  ("R" slime-connect "connect")
  ("h" my-cl-help-hydra/body "Documentation")
  ("e" my-cl-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"))

eval

(defhydra my-cl-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"))

help

(defhydra my-cl-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"))

Programming

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-cl-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))
        (t (contrapunctus-general-hydra/body))))

(define-key boon-command-map (kbd "m") #'my-dispatch-hydra)
(global-set-key (kbd "C-s") #'my-dispatch-hydra)
(define-key boon-command-map (kbd "C-s") #'my-dispatch-hydra)

shell

(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

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

(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"))

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)

general (keybindings)

(general-define-key
 "<f8>"    'keyboard-quit
 "M-<f8>"  'eval-defun
 "M-<f9>"  'dired-jump
 "<f10>"   'save-buffer
 "M-<f10>" 'find-file
 "<f11>"   'ido-mini
 "M-<f11>" 'ibuffer
 "<f12>"   'execute-extended-command
 "M-<f12>" 'text-scale-adjust
 "C-c C-j" 'join-line
 "C-c C-r" (lambda () (interactive) (revert-buffer t t))
 ;; for swapped parenthesis and square brackets layout
 "C-)"     'abort-recursive-edit)

UTF-8 incantations

(setq locale-coding-system 'utf-8
      file-name-coding-system 'utf-8
      buffer-file-coding-system 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-selection-coding-system 'utf-8)
(prefer-coding-system 'utf-8)

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))

(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))
  :hook (ibuffer-mode . ibuffer-set-filter-groups-by-mode))

;; (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)

(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)))

(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))

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

(setq )

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)

date and time

(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)))))

(use-package time
  :config
  (setq display-time-next-load-average t)
  (add-to-list 'zoneinfo-style-world-list '("Europe/Berlin" "Berlin")))
(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))))

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

(use-package ibuffer
  :bind
  (:map ibuffer-mode-map
        ("X" . 'ibuffer-do-kill-on-deletion-marks))) ;; Boon hijacks the x key.

**

(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
 )

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
  :load-path "~/.emacs.d/elisp-git/org-mode"
  :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
  :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
    "M-w"      'cp-copy-line-or-link
    "C-M-x"    'cp/eval-sexp
    "C-c C-o"  'my-org-open
    ;; 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://lists.gnu.org/archive/html/emacs-orgmode/2018-02/msg00082.html
  (defun my-export-link-helper (link desc format)
    (cond
     ((eq format 'html)
      (format "<a href=\"%s\">%s</a>" link desc))
     ((eq format 'latex)
      ;; (format "\\href{%s}{%s}" link desc)
      (format "\\url{%s}" link))
     (t ;; `ascii', `md', `hugo', etc.
      (format "[%s](%s)" desc link))))

  (defun my-export-gemini-link (link desc format)
    "Create export version of LINK and DESC to FORMAT."
    (let ((link (concat "gemini:" link)))
      (my-export-link-helper link desc format)))

  (org-link-set-parameters "gemini" :export #'my-export-gemini-link)

  (defun my-export-xmpp-link (link desc format)
    "Create export version of LINK and DESC to FORMAT."
    (let ((link (concat "xmpp:" link)))
      (my-export-link-helper link desc format)))

  (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)))

(defun my-org-hydra-insert-block (type &optional lang)
  "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\"."
  (let* ((column       (- (point) (point-at-bol)))
         (indent       (make-string column ?\s))
         (region-start (region-beginning))
         (region-end   (region-end))
         (start-string (format "#+BEGIN_%s %s\n" type
                               (if (stringp lang) lang "")))
         (end-string   (format "%s#+END_%s\n" indent type)))
    ;; create a block around a region - preserve position of point
    (cond ((region-active-p)
           (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))))))

;; (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)
  ;;   ())

;; 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))

(defun cp-copy-line-or-link (prefix-arg)
  "Copy address of org-mode link after point, ignoring whitespace,
link description (if any) and org-mode header and list syntax. If
not before a link, or with a prefix arg, call
`whole-line-or-region-kill-ring-save' instead.

BUG - improper behaviour with checkboxes.
2018-03-17T21:15:17+0530 - hopefully fixed now."
  (interactive "P")
  (let ((point-a (point)))
    (cl-flet ((copy-to-closing-bracket
               ()
               (let ((point-b (point)))
                 (re-search-forward "\\]")
                 (copy-region-as-kill point-b
                                      (- (point) 1)))))

      (if (save-excursion
            (or (use-region-p)
                prefix-arg
                (cp/re-search-line "\\[[-X ]\\]")))
          (whole-line-or-region-kill-ring-save prefix-arg)

        (cond ( ;; (cp/org-link-ahead-p)
               (cp/re-search-line "\\[")
               ;; (if (looking-at "\\[")
               ;;     (forward-char))
               (forward-char)
               (copy-to-closing-bracket)
               (goto-char point-a))

              ( ;; (cp/org-link-ahead-p 'implicit)
               (cp/re-search-line "http")
               (backward-word)
               (let ((point-b (point)))
                 (re-search-forward (rx (or eol (and printing " "))))
                 (copy-region-as-kill point-b
                                      (point)))
               (goto-char point-a))

              ;; TODO - org-previous-link will land you at the start
              ;; of the DESCRIPTION of the previous link, if it has
              ;; one, but to the user it will look like they are at
              ;; the start of the link. Add a case to handle this.

              ;; Does not work if there is an org TODO marker in a
              ;; header.

              (t (whole-line-or-region-kill-ring-save prefix-arg)))))))

(defun cp/org-table-convert-tsv ()
  (interactive)
  (with-output-to-temp-buffer "cp/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 "cp/org-table-convert-tsv"
    (remove-hook 'before-save-hook 'delete-trailing-whitespace)
    (write-file (read-from-minibuffer "Output filename: "))))

(defun my-org-hydra-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))
(defun contrapunctus-org-insert-timestamp ()
  (save-excursion
    (org-set-property "CREATED" (format-time-string "%FT%T%z"))))
(add-hook 'org-insert-heading-hook #'contrapunctus-org-insert-timestamp)

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

(use-package ox-texinfo)

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))

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/anon/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/anon/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)
  )

markdown-mode   editing

(use-package markdown-mode
  :ensure t
  :mode "\\.md\\'"
  :hook
  (markdown-mode . (lambda ()
                     (make-local-variable 'before-save-hook)
                     (add-hook 'before-save-hook 'markdown-cleanup-list-numbers)))
  (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)))

(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))))))
  (bind-keys
   (:map markdown-mode-map
         ("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-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
        ;; TeX-view-program-selection '(((output-dvi has-no-display-manager)
        ;;                               "dvi2tty")
        ;;                              ((output-dvi style-pstricks)
        ;;                               "dvips and gv")
        ;;                              (output-dvi "xdvi")
        ;;                              (output-pdf "Zathura")
        ;;                              (output-html "xdg-open"))
        )
  ;; 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)))

yasnippet

(use-package yasnippet
  :ensure t
  :commands (yas-expand-snippet)
  :hook (emacs-lisp-mode . yas-global-mode))

Programming

FIXME common [%]

swap keys   disabled

  1. keyswap-mode swaps numeric keys with symbols by default. We want to swap just () with [] (to begin with), but the code below doesn't work, for some reason.
  2. Does not always work in the minibuffer, namely eval-expression

Swap [] with () in programming and text modes.

pjb suggested a different approach here, which is basically -

(defun contrapunctus-swap-brackets-parens ()
  (interactive)
  (keyboard-translate ?\( ?\[)
  (keyboard-translate ?\) ?\])
  (keyboard-translate ?\[ ?\()
  (keyboard-translate ?\] ?\)))

(add-hook 'prog-mode-hook 'contrapunctus-swap-brackets-parens)
(add-hook 'text-mode-hook 'contrapunctus-swap-brackets-parens)

(defun normal-brackets-parens ()
  (interactive)
  (keyboard-translate ?\( ?\()
  (keyboard-translate ?\) ?\))
  (keyboard-translate ?\[ ?\[)
  (keyboard-translate ?\] ?\]))

…which I tried, but experienced some subtle bugs, and luckily I got keyswap mode to work the way I wanted - no number-symbol switching, just parens and brackets.

(use-package keyswap
  :disabled t
  :hook ((minibuffer-setup-hook . contrapunctus-swap-brackets-parens) ;; probably unnecessary
         (eval-expression-minibuffer-setup-hook . contrapunctus-swap-brackets-parens)
         (prog-mode . contrapunctus-swap-brackets-parens)
         (text-mode . contrapunctus-swap-brackets-parens)

         (emacs-lisp-mode . keyswap-colon-semicolon)
         (ielm-mode . contrapunctus-swap-brackets-parens)
         (ielm-mode . keyswap-colon-semicolon)

         (lisp-mode . keyswap-colon-semicolon)
         (slime-repl-mode . contrapunctus-swap-brackets-parens)
         (slime-repl-mode . keyswap-colon-semicolon)

         (scheme-mode . keyswap-colon-semicolon)
         (geiser-repl-mode . contrapunctus-swap-brackets-parens)
         (geiser-repl-mode . keyswap-colon-semicolon))
  :config
  (defun contrapunctus-keyswap-common ()
    (setq-local keyswap-pairs nil) ;; dont swap numbers and symbols
    (keyswap-mode))
  (defun contrapunctus-swap-brackets-parens ()
    ;; (message "keyswap-pairs is %s" keyswap-pairs)
    (contrapunctus-keyswap-common)
    (keyswap-add-pairs ?\[ ?\()
    (keyswap-add-pairs ?\] ?\))
    (keyswap-update-keys)))

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$")

paredit   disabled

(require 'paredit)

(add-hook 'emacs-lisp-mode-hook 'paredit-mode)
(add-hook 'lisp-mode-hook 'paredit-mode)
(add-hook 'scheme-mode-hook 'paredit-mode)
(add-hook 'inferior-scheme-mode-hook 'paredit-mode)
(add-hook 'inferior-lisp-mode-hook 'paredit-mode)
(add-hook 'ielm-mode-hook 'paredit-mode)

(global-set-key (kbd "C-x C-p") 'paredit-mode)

(cp-set-keys
 :keymap paredit-mode-map
 :bindings
 `((,(kbd "C-p") paredit-backward-down)
   (,(kbd "C-n") paredit-forward-up)
   (,(kbd "C-b") paredit-backward)
   (,(kbd "C-f") paredit-forward)
   (,(kbd "C-u") paredit-backward-up)
   (,(kbd "C-d") paredit-forward-down)

   (,(kbd "M-b") backward-char)
   (,(kbd "M-f") forward-char)
   (,(kbd "M-p") previous-line)
   (,(kbd "M-n") next-line)
   (,(kbd "M-u") paredit-kill-0)
   (,(kbd "M-d") paredit-forward-delete)

   (,(kbd "C-M-b") backward-word)
   (,(kbd "C-M-f") forward-word)
   (,(kbd "C-M-u") upcase-word)
   (,(kbd "C-M-d") paredit-forward-kill-word)
   (,(kbd "C-M-p") nil)
   (,(kbd "C-M-n") nil)

   (,(kbd "M-w") paredit-copy-as-kill)
   (,(kbd "C-h") paredit-backward-delete)
   (,(kbd "C-w") paredit-backward-kill-word)))

(cp-set-keys
 :keymap comint-mode-map
 :bindings
 `((,(kbd "C-d") paredit-forward-down)
   (,(kbd "C-M-p") comint-previous-input)
   (,(kbd "C-M-n") comint-next-input)))

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) ;; QWERTY C
        ("C" . special-lispy-move-up)
        ("R" . special-lispy-move-down)
        ("o" . special-lispy-splice) ;; QWERTY S
        ("p" . special-lispy-raise)
        ;; ;; 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))
    do (lispy-define-key lispy-mode-map (kbd key) cmd)))

treemacs   disabled

(use-package treemacs
  :disabled t
  :bind (:map treemacs-mode-map
              ([mouse-1] . #'treemacs-single-click-expand-action))
  :config
  (treemacs-tag-follow-mode)
  (treemacs-toggle-fixed-width)
  (setq treemacs-tag-follow-delay 0))

imenu

(use-package imenu
  :ensure t
  :config
  (setq imenu-auto-rescan t
        org-imenu-depth 5))
imenus
(use-package imenus
  :ensure t
  :commands imenus)

side-hustle   disabled

(use-package side-hustle
  :disabled t
  :bind
  (:map side-hustle-mode-map
        (("r" . next-line)
         ("c" . previous-line))))

orderless   disabled

(use-package orderless
  :disabled t
  :custom (completion-styles '(orderless))
  :config (setq orderless-component-separator "[ \-]"))

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)
  (setq print-length nil
        eval-expression-print-length nil))
(use-package ielm
  :bind (:map ielm-map
              ("M-'" . sp-indent-defun)))
;; (use-package elsa
;;   :commands flycheck-elsa-setup)

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))

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)))

explain-pause-mode

(use-package explain-pause-mode
  :load-path "~/.emacs.d/elisp-git/explain-pause-mode/"
  :diminish
  :commands (explain-pause-mode)
  :init (explain-pause-mode))

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

redshank

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

slime

(use-package slime
  :ensure t
  :commands (slime-eval-buffer slime-eval-defun)
  :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-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))

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)))

nodejs-repl   disabled

(use-package nodejs-repl
  :disabled
  :config (setq nodejs-repl-command "nodejs"))

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))

recentf

(use-package recentf
  :init (recentf-mode 1)
  :bind ("C-x C-r C-o" . recentf-open-files)
  :config
  (setq recentf-auto-cleanup 'never
        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"))
  :hook
  (kill-emacs . recentf-cleanup))

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

(setq default-frame-alist '((font . "DejaVu Sans Mono-10.5")))
(defun my-fix-emojis (&optional frame)
  (set-fontset-font "fontset-default" nil "Symbola" frame 'append))
(my-fix-emojis)
(add-hook 'after-make-frame-functions 'my-fix-emojis)
;; (set-face-attribute 'default nil :font "-outline-Bitstream Vera Sans Mono-normal-normal-normal-mono-12-*-*-*-c-*-iso8859-1")

desktop - session management

(use-package desktop
  :init
  (desktop-save-mode t)
  (desktop-auto-save-enable)
  :config
  (setq desktop-dirname "~/.emacs.d/desktop-save/"
        desktop-save 'ask-if-new
        ;; don't save buffers, just the history
        desktop-files-not-to-save ""
        desktop-buffers-not-to-save ""
        desktop-restore-frames nil)
  (cl-loop for var in
    '(grep-history
      grep-find-history
      find-args-history
      extended-command-history
      read-expression-history
      default-input-method
      input-method-history
      query-replace-history
      compile-history
      string-rectangle-history
      regexp-history
      dired-shell-command-history
      dired-regexp-history
      shell-command-history
      org-tags-history
      minibuffer-history
      erc-server-history-list
      cp/activity-history
      LaTeX-environment-history
      swiper-history
      counsel-M-x-history)
    do (add-to-list 'desktop-globals-to-save var)))

;; (add-to-list 'desktop-locals-to-save 'comint-input-ring)

(defun cp/backup-desktop-file ()
  (let ((new-filename (->> (shell-command-to-string "date -Is")
                           (replace-regexp-in-string "\n" "")
                           (concat "~/.emacs.d/.emacs.desktop."))))
    (copy-file "~/.emacs.d/.emacs.desktop" new-filename)))
(defun cp/cleanup-desktop-backups ()
  (let ((desktop-file-backups (-> (concat "find ~/.emacs.d/ -maxdepth 1 -type f |"
                                          " grep -E \"\.emacs\.desktop\.[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}[-+]?[0-9]{2}:?[0-9]{2}\"")
                                  (shell-command-to-string )) )))
  (if (> 10 )))
;; ;; this one created loads and loads of backups
;; (add-hook 'desktop-save-hook 'cp/backup-desktop-file)
;; ;; not enough space for this - wasteful
;; (add-hook 'kill-emacs-hook 'cp/backup-desktop-file)

GC reset

(setq gc-cons-threshold 400000)

;; (toggle-debug-on-quit)
;; (profiler-stop)
;; (emacs-init-time)
;; (profiler-report)

The End

(provide 'init)
;;; init.el ends here