dotemacs/init.org

118 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.

init.org

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
(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-x" 'backward-word ;; !!!
  "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-def ivy-minibuffer-map
  "C-c" 'previous-line
  "C-r" 'next-line
  "M-c" 'ivy-previous-history-element
  "M-r" 'ivy-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

(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.kbd"))
(my-start-process "transmission-gtk")
(my-start-process "gajim")
(my-start-process "/usr/lib/notification-daemon/notification-daemon")

volume

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

user interface

(use-package cp-ui
  :demand
  :load-path "~/.emacs.d/contrapunctus/")

(add-to-list 'load-path "~/.emacs.d/user/")

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

atomic-chrome

(require 'atomic-chrome)
(atomic-chrome-start-server)
(setq atomic-chrome-url-major-mode-alist
      '(("wikisource" . mediawiki-mode)))

emacs settings

(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)
  (setq-default undo-limit (* 80 1000)))

esup, the Emacs StartUp Profiler

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

doc-view

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

gnutls

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

Searching

ag, the Silver Searcher

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

Swiper

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

**

(load "cp")

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

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
  :ensure t
  :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
  :ensure t
  :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)))

(require 'cp-god)

Applications

(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
  :hook (chronometrist-mode . chronometrist-spark-minor-mode)
  :config (setq chronometrist-spark-length (* 7 4)))
chronometrist
(use-package chronometrist
  ;; :disabled t
  :load-path "~/.emacs.d/contrapunctus/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)
               ("d" . chronometrist-details))
         (:map chronometrist-report-mode-map
               ("h" . chronometrist-report-previous-week)
               ("s" . chronometrist-report-next-week)))
  :config
  (setq 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   'contrapunctus-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)]))
activity-indicator
(defun contrapunctus-chronometrist-activity-indicator ()
  (--> (plist-put (chronometrist-last)
                  :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
(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"))
    ("Arrangement/new edition"
     (delete-other-windows)
     ;; (find-file "/media/data/anon/1-music-scores/4-my-arrangements/2020/2020-11-27 Winterreise/01 Gute Nacht/music/")
     ;; (launch-file "/media/data/anon/1-music-scores/4-my-arrangements/2020/2020-11-27 Winterreise/01 Gute Nacht/output/01 Gute Nacht-pacON.pdf")
     ;; (launch-file "/media/data/anon/Sync/Scores/voice/Schubert, Franz/IMSLP570459-PMLP2203-D_911,_Winterreise.pdf")
     (my-find-file*
      "/media/data/anon/1-music-scores/4-my-arrangements/2020/2020-10-14 An die ferne Geliebte/music/"
      "/media/data/anon/1-music-scores/4-my-arrangements/2020/2020-10-14 An die ferne Geliebte/output/2020-10-14 An die ferne Geliebte-pacON.pdf"
      "/home/anon/Sync/Scores/voice/Beethoven, Ludwig van/IMSLP47561-PMLP11572-Beethoven_Lieder_Peters_9535_Op_98_An_die_ferne_Geliebte.pdf"))
    ("Aural exercises"
     (find-file-other-window "/media/data/anon/Documents/Text Files/music_stuff/harmonic-analysis.org"))
    ("Composing"
     ;; "/media/data/anon/1-music-scores/2-my-compositions/2017/2017-02 The Rainbow Flower/1 Chhutti Ka Din/music/"
     (my-find-file*
      "/media/data/anon/1-music-scores/2-my-compositions/2019/2019-03 Kahe Natak Karte Ho Ji?/2 Adi Kal Se/"
      "/media/data/anon/1-music-scores/2-my-compositions/2019/2019-03 Kahe Natak Karte Ho Ji?/2 Adi Kal Se/output/2 Adi Kal Se-pacON.pdf")
     (start-process "qtractor" nil "qtractor" "/media/data/anon/8-music-production/1-my-creations/2019/natyashastra/2-adi-kal-se/adi-kal-se.qtr"))
    ("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-file (chronometrist-active-backend)))
    ("Guitar"
     (let* ((path-1 "/home/anon/Sync/Scores/guitar-solo/repertoire.org")
            (path-2 "/home/anon/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)
       (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"
     ;; (async-shell-command "java -jar ~/josm-tested.jar" " *JOSM*" " *JOSM errors*")
     ;; (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
      "/media/data/phone/anon/Nokia 6.1/Documents/Markor/Computers/todo.md"))
    ("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
      "/home/anon/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
(use-package request)
(use-package esxml-query)

(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)
           (chronometrist-loop-records for plist in backend
             ;; the very latest OSM plist is the one we just created -
             ;; we want the one before that
             initially do (backward-list)
             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))))
    (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-plist-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
  :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.

(chronometrist-loop-records for plist in chronometrist-file
  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 %s days."
            (ts-human-format-duration seconds)
            count)))

Intervals and durations for task Exercise.

(chronometrist-loop-records for plist in chronometrist-file
  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"

(chronometrist-loop-records for plist in chronometrist-file
  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))))

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
  :after hydra
  :bind
  ("<f2> e"   . #'contrapunctus-emms-hydra/body)
  ("<f2> E"   . #'emms)
  (:map dired-mode-map
        ("E" . #'contrapunctus-emms-hydra/body))
  :commands
  (emms-minimalistic emms emms-play-dired emms-add-dired)
  :config
  (emms-minimalistic)
  (emms-default-players)
  (setq emms-player-mpv-parameters
        (lambda ()
          (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))))))
  ;; ;; 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)))

(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)
        ("M-c"      . #'emms-playlist-mode-center-current)
        ("SPC"      . #'emms-pause)
        ("K"        . #'emms-playlist-clear))
  :config
  (setq emms-playlist-buffer-name "EMMS Playlist"))

(use-package emms-info-tinytag
  :init
  (setq emms-info-functions '(emms-info-tinytag))
  :config
  (setq emms-info-tinytag-python-name "python3"))

Internet

eww
(use-package eww
  :hook
  (eww-after-render . visual-line-mode)
  (eww-after-render . visual-fill-column-mode)
  :config
  (setq shr-image-animate nil
        shr-width -1)
  :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
  :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)))
elfeed
(use-package elfeed
  :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
(use-package jabber
  :load-path "/home/anon/.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)))

**

;; (load "cp-adb")

**

(require 'cp-editing)
;; (load "cp-evil")
(require 'cp-lily)
(require 'cp-sfz)
(require 'cp-lisp)

;; (ispell-change-dictionary "en")
(setq ispell-dictionary "en")

TODO emacsshot

PR ideas

  1. create directories in save path if they don't exist
  2. grammar - "written /path/to/file"
(use-package emacsshot
  :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))))

(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))))
(use-package contrasync
  :load-path "~/.emacs.d/contrapunctus/contrasync/"
  :config
  (setq contrasync-disk-path "/media/anon/kash-sg-2tb/"
        contrasync-source-paths
        `("~/.emacs.d/"
          "/media/data/anon/1-music-notation/"
          "/media/data/anon/Documents/"
          "~/.config/"
          "~/.local/"
          ("/media/data/anon/phone/Nokia 6.1/" ,contrasync-disk-path "phone/Nokia 6.1/"))))

(use-package peep-dired
  :disabled
  ;; ;; didn't work too well 🤔
  ;; :config
  ;; (setq peep-dired-cleanup-eagerly t)
  :hook
  (dired-mode . peep-dired))

mail

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

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

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

pdf-tools

(use-package pdf-tools
  :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)))

proced

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

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

company-emoji

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

counsel

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

;; (use-package elsa
;;   :commands flycheck-elsa-setup)

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)

easy-kill   disabled editing

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

TODO hydra [83%]

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

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

Search

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

General

(require 'anaphora)

(defun my-compile-project (file &optional prefix cmd)
  "Switch to ancestor directory with FILE and run compile command CMD.
If CMD is not supplied, run `compile-command'.
With PREFIX argument and CMD not supplied - prompt for command."
  (interactive "fDominating File: \nP")
  (save-window-excursion
    (awhen (locate-dominating-file default-directory file)
      (cd it)
      (compile
       (cond (prefix
              (compilation-read-command
               (or cmd "")))
             (cmd cmd)
             (t compile-command))))))
(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 nil
        "-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 ()
  (interactive)
  (my-compile-project
   "Makefile"
   (format "ORG_PATH=%S make -k %s"
           (shell-quote-argument
            (expand-file-name "~/.emacs.d/elisp-git/org-mode/lisp/"))
           "compile")))

(my-defhydra my-org-hydra (:color blue)
  "Org"
  ("L" (literate-elisp-load (buffer-file-name)) "literate-elisp-load")
  ("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")
  ("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"))
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-promote-subtree "promote")
  ("S" org-demote-subtree "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-project "Makefile" 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"))
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 "file")
  ("r" byte-recompile-file "recompile")
  ("R" byte-force-recompile "force recompile"))

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"))
eval
(defhydra my-cl-hydra-eval (:color blue)
  ("b" slime-eval-buffer "buffer")
  ("e" slime-eval-defun  "defun"))
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

(my-defhydra my-lilypond-hydra (:color blue)
  "Lilypond"
  ("b" (my-compile-project "main.ly" nil "./mkly dev") "Compile")
  ("B" (my-compile-project "main.ly" t "./mkly dev") "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))
         (cond ((eq 'chicken geiser-scheme-implementation)
                (my-chicken-hydra/body))
               ((eq 'guile geiser-scheme-implementation)
                (cp-guile/body))
               (t (contrapunctus-general-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))
        (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"))

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

eshell

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

image-mode

(use-package image-mode
  :bind
  (:map image-map
        ("o" . nil)
        ("r" . nil))
  (:map image-mode-map
        ("o" . nil)
        ("r" . nil)))

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
;;   :ensure t
;;   :init (global-flycheck-mode))

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

environment variables

(setenv "PATH" (concat "/home/anon/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)
;; (add-to-list 'load-path "~/.emacs.d/elisp-git/yafolding.el/")

general (keybindings)

(use-package general
  :commands general-define-key)

;; 2017-06-09T00:24:36+0530
;; my laptop's X, W, and G keys gave up the ghost
;; temporary bindings, till my laptop keyboard is fixed

;; 2020-01-07T12:43:41+0530
;; update to use general, add M-f8 binding

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

UTF-8 incantations

(setq locale-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
  :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))

Navigation

;; Vim-style prompt-less tag jumping
;; http://stackoverflow.com/questions/12074897/automatically-jump-to-tag-in-emacs
(global-unset-key (kbd "M-t"))
(defun find-tag-no-prompt-other-window ()
  "Jump to the tag at point in the other window without prompting."
  (interactive)
  (find-tag-other-window (find-tag-default)))
(defun find-tag-no-prompt ()
  "Jump to the tag at point without prompting."
  (interactive)
  (find-tag (find-tag-default)))

(cp-set-keys
 :bindings
 `((,(kbd "M-t M-t") find-tag-no-prompt-other-window)
   (,(kbd "M-t M-g") find-tag-no-prompt)
   (,(kbd "M-t M-p") pop-tag-mark)
   (,(kbd "M-t M-f") find-tag)
   (,(kbd "M-t M-s") tags-search)
   (,(kbd "M-t M-c") tags-loop-continue)))

;; (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
(cp-set-keys
 :bindings
 `((,(kbd "C-x C-1") delete-other-windows)
   (,(kbd "C-x C-2") split-window-below)
   (,(kbd "C-x C-3") split-window-right)
   (,(kbd "C-x C-0") delete-window)
   (,(kbd "C-x C-4 C-f") find-file-other-window)
   ;; (,(kbd "C-`") point-to-register)
   ;; (,(kbd "C-'") jump-to-register)
   (,(kbd "C-x C-d") dired-jump)
   ;; I usually keep Super for the window manager and global hotkeys...
   (,(kbd "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
  :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)
              ("C-7" . select-window-7)
              ("C-8" . select-window-8)
              ("C-9" . select-window-9)))

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

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

scrolling

(setq scroll-conservatively 10000
      scroll-preserve-screen-position t
      auto-window-vscroll nil)

Recenter screen on isearch matches

(add-hook 'isearch-mode-hook 'recenter)
(add-hook 'isearch-update-post-hook 'recenter)
(defadvice isearch-repeat-forward
    (after isearch-repeat-forward-recenter activate) (recenter))
(defadvice isearch-repeat-backward
    (after isearch-repeat-backward-recenter activate) (recenter))
(ad-activate 'isearch-repeat-forward)
(ad-activate 'isearch-repeat-backward)

**

(global-set-key (kbd "C-s") 'isearch-forward-regexp)

**

(global-set-key (kbd "C-r") 'isearch-backward-regexp)

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

helpful

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

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.

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

**

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

help-mode

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

Unicode keys

(general-auto-unbind-keys)
(general-def
  :prefix "M--"
  "r"   [?₹]
  "- -"   [?—]
  "- L"   [] "L -"   []
  "t" [?™]

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

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

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

  "b" [?♭]

  "e" [?€]
  "< -" [?←]
  "- >" [?→]
  "^ |" [?↑]  "| ^" [?↑]
  "v |" [?↓]  "| v" [?↓]
  ", \\" [] "l" []
  "C-' F"   (kbd "ƒ"))
(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)))

backup configuration

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

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

Ivy

(use-package ivy
  :commands ivy-mode
  :init (ivy-mode)
  :bind (:map ivy-minibuffer-map
              ("C-h" . ivy-backward-delete-char))
  :config
  (setq ivy-re-builders-alist
        '((t . ivy--regex-ignore-order))))

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
  :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 (next-line)))
(use-package git-commit
  :bind
  (:map git-commit-mode-map
        ("M-c" . git-commit-prev-message)
        ("M-r" . git-commit-next-message)))

markup

Org

(use-package org
  :load-path "~/.emacs.d/elisp-git/org-mode"
  :hook (org-mode . contrapunctus-disable-nameless-key)
  :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"  'cp/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)
          ("\\(?:gif\\|jpe?g\\|png\\)" . "sxiv -f %s")
          ;; (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 ?\ ))
         (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" 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 cp/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 contrapunctus-org-insert-timestamp ()
  (save-excursion
    (insert "\n" "<" (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
  :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))))))

**

(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
  :hook (gemini-mode . (lambda ()
                         (make-local-variable 'outline-regexp)
                         (setq outline-regexp "[#]+")))
  :bind (:map gemini-mode-map
              ("TAB" . contrapunctus-outline-indent-or-toggle-children))
  :config)

TODO LaTeX

  1. merge personal commands with those in Lilypond mode
(use-package auctex
  :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)))

midnight-mode

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

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
  :hook (prog-mode . projectile-mode)
  :bind (:map boon-command-map
              ("\\" . projectile-command-map)))
(use-package rainbow-delimiters
  :hook (prog-mode . rainbow-delimiters-mode))
(use-package company
  :diminish company-mode
  :hook (prog-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-backends (remove 'company-etags company-backends)
        company-show-quick-access t)
  (customize-set-variable company-quick-access-modifier 'control))
(use-package feature-mode
  :mode "\\.feature$")
smartparens
(use-package smartparens
  :load-path "~/.emacs.d/elisp-git/smartparens/"
  :init
  (smartparens-global-mode)
  :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
  :hook
  (emacs-lisp-mode . lispy-mode)
  (inferior-emacs-lisp-mode . lispy-mode)
  (lisp-mode . lispy-mode)
  (scheme-mode . lispy-mode)
  ;; Boon-style keys on Dvorak
  :bind
  (:map lispy-mode-map
        ;; change HJKL to Boon-like (QWERTY) KIOL
        ("t" . special-lispy-left)  ;; QWERTY K
        ("n" . special-lispy-right) ;; QWERTY L
        ("c" . special-lispy-up)    ;; QWERTY I
        ("r" . special-lispy-down)  ;; QWERTY O
        ;; bind the replaced commands to Dvorak HJKL
        ("h" . special-lispy-teleport)
        ("j" . special-lispy-occur)
        ("k" . special-lispy-clone)
        ("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)))
treemacs
(use-package treemacs
  :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
  :config (setq imenu-auto-rescan t))
side-hustle
(use-package side-hustle
  :bind
  (:map side-hustle-mode-map
        (("r" . next-line)
         ("c" . previous-line))))

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)))
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
  :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)))
explain-pause-mode
(use-package explain-pause-mode
  :load-path "/home/anon/.emacs.d/elisp-git/explain-pause-mode/"
  :disabled t
  :diminish
  :commands explain-pause-mode
  :init (explain-pause-mode))
async-tangle
(defun contrapunctus-async-tangle (&optional prefix)
  (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)))
    (start-process
     "async-tangle" proc-buffer "emacs" "-q" "-Q" "--batch"
     "--eval=(require 'ob-tangle)"
     (format "--eval=(org-babel-tangle-file \"%s\")"
             file-name file-name-no-ext))
    ;; don't create window if buffer already visible
    (unless (get-buffer-window-list proc-buffer)
      ;; to avoid messing up my usual two-windows-same-buffer setup
      (select-window (split-window-below))
      (switch-to-buffer proc-buffer)
      ;; so I can access my Hydra to switch back
      (boon-mode))
    ;; (select-window old-win)
    ))

Common Lisp

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

(use-package slime
  :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))
  (: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))
  :config
  (slime-setup t)
  (setq inferior-lisp-program
        ;; "/usr/bin/ecl"
        "/home/anon/bin/sbcl"
        ;; "/usr/bin/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")))

(defun set-cl-repl-keys ()
  (if (or (cp-buffer-name-match-p "^\*inferior-lisp\*\\(<[0-9]*>\\)?")
          (eq major-mode 'lisp-mode))
      (progn
        (local-set-key (kbd "TAB") 'completion-at-point)
        (local-set-key (kbd "M-'") 'sp-indent-defun))))

(add-hook 'slime-connected-hook 'set-cl-repl-keys)
(add-hook 'comint-mode-hook     'set-cl-repl-keys)
(add-hook 'lisp-mode-hook       'set-cl-repl-keys)

Scheme

CHICKEN Scheme
Guile

Lilypond

Prolog

(use-package ediprolog
  :commands ediprolog-dwim)

c

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

(use-package cc-mode)

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

nov.el

(use-package nov
  :mode ("\\.epub\\'" . nov-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)))

package

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

feather

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

**

**

mode line

powerline   disabled

(use-package powerline)

undo-tree

(use-package undo-tree
  :commands global-undo-tree-mode
  :if (not (featurep 'evil))
  :diminish undo-tree-mode
  :init (global-undo-tree-mode))

**

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

**

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

**

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

**

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

**

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

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 "/home/anon/.emacs.d/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")

**

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

custom.el

(setq custom-file "~/.emacs.d/custom.el")
(load custom-file)

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)

enabling disabled commands

(mapc (lambda (command)
        (put command 'disabled nil))
      '(downcase-region
        upcase-region
        set-goal-column
        scroll-left
        erc-remove-text-properties-region))

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)

**

(put 'list-timers 'disabled nil)

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