#+PROPERTY: header-args:emacs-lisp :tangle "~/.emacs.d/config-ext.el" :comments both

* Augmentations

** vterm
#+begin_src emacs-lisp
  (require 'vterm-aux)
#+end_src

* posting source code
#+begin_src emacs-lisp
  (use-package webpaste)

  (setq webpaste-paste-confirmation t)
  (setq webpaste-provider-priority '("ix.io"))
#+end_src

* Notifications
#+begin_src emacs-lisp
  (use-package ednc
    :config
    ;; (defun stack-notifications (&optional hide)
    ;;   (mapconcat (lambda (notification)
    ;;                (let ((app-name (ednc-notification-app-name notification)))
    ;;                  (unless (member app-name hide)
    ;;                    (push app-name hide)
    ;;                    (ednc-format-notification notification))))
    ;;              (ednc-notifications) ""))
    ;; (nconc global-mode-string '((:eval (list-notifications))))
    ;; (add-hook 'ednc-notification-presentation-functions
    ;;           (lambda (&rest _) (force-mode-line-update t)))

    (defun show-notification-in-buffer (old new)
      (let ((name (format "Notification %d" (ednc-notification-id (or old new)))))
        (with-current-buffer (get-buffer-create name)
          (if (not new)
              (kill-buffer)
            (let ((inhibit-read-only t))
              (if old (erase-buffer) (ednc-view-mode))
              (insert (ednc-format-notification new t))
              (my/display-buffer-in-side-window
               'bottom
               (current-buffer)
               nil
               '(window-height . 6)))))))
    (add-hook 'ednc-notification-presentation-functions
              #'show-notification-in-buffer))
#+end_src
* Mail
#+begin_src emacs-lisp
  (when (and
         (ec/load-or-ask-pred 'my-ec/enable-mail "Enable mail? ")
         my-ec/authinfo-exists)
    (require 'emacs-mail))
#+end_src
* Ace jump
#+BEGIN_SRC emacs-lisp
  (use-package ace-jump-mode
    :bind (("C-c j" . 'ace-jump-line-mode)
           :map *root-map*
           ("SPC" . 'ace-jump-mode)))
#+END_SRC
* Various tools
** debbugs
#+begin_src emacs-lisp
  (use-package debbugs)

  (defun my/debbugs-gnu-select-report ()
    "Select the report on the current line."
    (interactive)
    (when (mouse-event-p last-input-event) (mouse-set-point last-input-event))
    ;; We open the report messages.
    (let* ((status (debbugs-gnu-current-status))
           (id (alist-get 'id status))
           (merged (alist-get 'mergedwith status)))
      (setq merged (if (listp merged) merged (list merged)))
      (cond
       ((not id)
        (message "No bug report on the current line"))
       ((eq debbugs-gnu-mail-backend 'rmail)
        (debbugs-gnu-read-emacs-bug-with-rmail id status merged))
       ((eq debbugs-gnu-mail-backend 'gnus)
        (debbugs-gnu-read-emacs-bug-with-gnus id status merged))
       ((eq debbugs-gnu-mail-backend 'notmuch)
        (notmuch-tree (concat "tag:bug-gnu-emacs " (format "subject:\"bug#%s\"" id))))
       (t (error "No valid mail backend specified")))))

  (setq debbugs-gnu-mail-backend 'notmuch)

  (advice-add #'debbugs-gnu-select-report
              :override
              #'my/debbugs-gnu-select-report)
#+end_src
** erc
#+begin_src emacs-lisp
  (use-package erc)
  (use-package erc-hl-nicks)
  (use-package erc-colorize)
  (require 'netrc)
  (erc-hl-nicks)
  (erc-colorize-mode)
  (setq erc-user-full-name "Benson Chu"
        erc-kill-buffer-on-part t
        erc-track-exclude-types
        '("JOIN" "QUIT" "PART" "NICK" "333" "353"))
  (setq erc-autojoin-channels-alist
        '(("freenode.net" "#emacs" "#org-mode"
           ;; "##linux" "#compilers" "#pltclub"
           ;; "##cs" "##computerscience" "##programming" "#lisp" "##lisp"
           ;; "#sbcl" "#ecl"
           )
          ("libera.chat" "#emacs" "#org-mode" "#archlinux"
           "#commonlisp" "#sbcl" "#compilers" "#nixos"
           ;; "##linux" "#pltclub"
           ;; "##cs" "##computerscience" "##programming" "#lisp" "##lisp"
           ;; "#ecl"
           )))

  (defun get-authinfo (host port &optional user)
    (let* ((hostentry (car (auth-source-search :host host :port port :user user))))
      (when hostentry (funcall (plist-get hostentry :secret)))))

  (defun freenode-connect (nick password)
    (erc :server "irc.freenode.net" :port 6667
         :password password :nick nick))

  (defun libera-connect (nick password)
    (erc-tls :server "irc.libera.chat" :port 6697
             :password password :nick nick))

  (defun irc-connect ()
    (interactive)
    (when (y-or-n-p "Connect to IRC? ")
      (libera-connect "pestctrl" (get-authinfo "irc.libera.chat" "6697" "pestctrl"))))
#+end_src
** font-lock-studio
#+begin_src emacs-lisp
  (use-package font-lock-studio)
#+end_src
* pdf-tools use isearch
#+BEGIN_SRC emacs-lisp
  (when (and (not (eq system-type 'windows-nt))
             (not my-ec/at-ti))
    (use-package pdf-tools)
    (pdf-tools-install)
    (define-key pdf-view-mode-map (kbd "C-s") 'isearch-forward)
    (define-key pdf-view-mode-map (kbd "d") (lambda () (interactive) (pdf-view-next-line-or-next-page 8)))
    (define-key pdf-view-mode-map (kbd "u") (lambda () (interactive) (pdf-view-previous-line-or-previous-page 8))))
  #+END_SRC
* transpose-frame
#+begin_src emacs-lisp
  (use-package transpose-frame)
#+end_src
* e2wm
#+begin_src emacs-lisp
  (use-package e2wm
    :bind (("M-+" . e2wm:start-management)))
#+end_src
* Helpful view-mode
#+begin_src emacs-lisp
  (defun helpful--navigate-view-mode (orig button)
    (let ((w (window-parameter (selected-window) 'quit-restore)))
      (funcall orig button)
      (view-mode)
      (setq-local view-exit-action
                  `(lambda (&rest args)
                     (set-window-parameter (selected-window) 'quit-restore ',w)))))

  (advice-add #'helpful--navigate
              :around
              #'helpful--navigate-view-mode)
#+end_src
* pavucontrol switch speakers headphones
#+begin_src emacs-lisp
  (require 'cl)

  (defvar laptop-sink-index 0)
  (defvar hdmi-pcie-interface nil)

  (defun setup-headphone-stuff ()
    (interactive)
    (let* ((result (shell-command-to-string "pactl list short sinks")))
      (when (string-match "\\([0-9]\\).*analog-stereo" result)
        (setq laptop-sink-index
              (string-to-number
               (match-string 1 result))))
      (when (string-match "[0-9].*\\(pci-.*\\)\\.hdmi-stereo" result)
        (setq hdmi-pcie-interface
              (match-string 1 result))))

    (when hdmi-pcie-interface
      (let* ((result (shell-command-to-string "pacmd list-modules"))
             (split (cdr (split-string result "index: "))))
        (cl-loop for mod in split
                 while (not
                        (string-match (format "\\([0-9]+\\)\n.*\n.*name=\"%s\"" hdmi-pcie-interface)
                                      mod))
                 finally
                 do (shell-command
                     (format "pactl unload-module %s"
                             (match-string 1 mod)))))))

  (defun current-speakers ()
    (let ((string (shell-command-to-string "pactl list sinks | grep 'Active Port: '")))
      (if (string-match-p "headphones" string)
          'headphones
        'speakers)))

  (defun toggle-audio-output ()
    (interactive)
    (if (eq (current-speakers)
            'headphones)
        (shell-command (format "pactl set-sink-port %d analog-output-speaker"
                               laptop-sink-index))
      (shell-command (format "pactl set-sink-port %d analog-output-headphones"
                             laptop-sink-index)))
    (message (format "Switched to: %s" (current-speakers))))

  (exwm-global-set-key (kbd "s-s") #'toggle-audio-output)

  ;; (use-exwm
  ;;   :config
  ;;   (add-hook 'exwm-init-hook #'setup-headphone-stuff))
#+end_src
* rmsbolt
#+begin_src emacs-lisp
(use-package rmsbolt)
#+end_src
* ivy-posframe
#+begin_src emacs-lisp
  (require 'cl)

  (unless my-ec/at-ti
    (use-package ivy-posframe
      :config
      (setq ivy-posframe-display-functions-alist
            '((swiper          . ivy-posframe-display-at-frame-center)
              (complete-symbol . ivy-posframe-display-at-point)
              (iwc-switch-to-wc . nil)
              (t               . ivy-posframe-display-at-window-top-center)))

      (defun ivy-posframe-display-at-window-top-center (str)
        (ivy-posframe--display str #'posframe-poshandler-window-top-center))

      (defun posframe-poshandler-window-top-center (info)
        "Posframe's position handler.

    Get a position which let posframe stay onto current window's
    center.  The structure of INFO can be found in docstring
    of `posframe-show'."
        (let* ((frame-width (plist-get info :parent-frame-width))
               (window-left (plist-get info :parent-window-left))
               (window-top (plist-get info :parent-window-top))
               (window-width (plist-get info :parent-window-width))
               (posframe-width (plist-get info :posframe-width)))
          (cons (min (- frame-width posframe-width)
                     (+ window-left (max 0
                                         (/ (- window-width posframe-width) 2))))
                (+ window-top 50))))

      (defun disable-ivy-posframe-on-exwm-windows (orig &rest args)
        (if (not (eq major-mode 'exwm-mode))
            (apply orig args)
          (cl-letf (((symbol-function 'display-graphic-p) (lambda (&optional display) nil)))
            (apply orig args))))

      (advice-add #'ivy-posframe--read
                  :around
                  #'disable-ivy-posframe-on-exwm-windows))


    (unless (eq 'hash-table (type-of face-new-frame-defaults))

      ;; (def-face-copier my/posframe-faces (sym)
      ;;   (let ((name (symbol-name sym)))
      ;;     (string-match-p "^ivy-.*"
      ;;                     name)))

      ;;(setq ivy-posframe-min-height 0)

      ;; (setq ivy-posframe-height 24)

      ;; (setq ivy-height-alist
      ;;       '((t . 24)))
      ;; (setq ivy-posframe-height-alist
      ;;       '((counsel-M-x . 8)
      ;;         (t . 24)))
      ;; '((swiper . 24)))
      ))

#+end_src
* Elfeed
#+begin_src
  (require 'elfeed)
  (setq elfeed-use-curl t)
  (elfeed-set-timeout 36000)
  (setq elfeed-curl-extra-arguments '("--insecure"))

  ;; enable elfeed-protocol
  (elfeed-protocol-enable)
#+end_src
* shell-command+
#+begin_src emacs-lisp
  (use-package shell-command+
    :bind ("M-!" . shell-command+))
#+end_src
* shackle-mode
#+begin_src emacs-lisp
  (use-package shackle)

  (defun shackle--display-buffer-reuse (buffer alist)

    (let ((window (display-buffer-reuse-window buffer
                                               ;; Reuse frames
                                               (cons '(reusable-frames . t) alist))))
      (prog1 window
        (when (and window (window-live-p window)
                   shackle-select-reused-windows)
          (select-window window)))))

  (setq switch-to-buffer-obey-display-actions t
        shackle-select-reused-windows t)
  (setq shackle-rules '(("the_plan" :select t)))

  (shackle-mode 1)

  (defun get-the-plan ()
    (with-current-buffer (find-file-noselect (my/agenda-file "plan.org"))
      (rename-buffer "the_plan")
      (current-buffer)))

  (defun the-plan ()
    (interactive)
    (switch-to-buffer (get-the-plan)))

  (exwm-global-set-key (kbd "s-p") #'the-plan)
#+end_src
* Emojis!
#+begin_src emacs-lisp
  (use-package emojify)
#+end_src
* dired-rsync
#+begin_src emacs-lisp
  (use-package dired-rsync
    :config
    (bind-key "C-c C-r" 'dired-rsync dired-mode-map)
    (add-to-list 'global-mode-string 'dired-rsync-modeline-status t))
#+end_src
* keyfreq
#+begin_src emacs-lisp
  (use-package keyfreq
    :init
    (setq keyfreq-excluded-commands
          '(self-insert-command
            org-self-insert-command
            exwm-input-send-simulation-key
            tab-bar-mouse-1
            abort-recursive-edit
            forward-char
            backward-char
            previous-line
            next-line))
    (keyfreq-mode 1)
    (keyfreq-autosave-mode 1))
#+end_src
* Hammy?
#+begin_src emacs-lisp#
  (use-package hammy
    :quelpa (hammy :fetcher github :repo "alphapapa/hammy.el"))

  (hammy-define "Move"
    :documentation "Don't forget to stretch your legs."
    :intervals
    ;; A list of intervals, each defined with the `interval' function.
    (list (interval
           ;; The name of the interval is a string, used when selecting
           ;; hammys and shown in the mode line.
           :name "💺"
           ;; The duration of the interval: a number of seconds, a string
           ;; passed to `timer-duration', or a function which returns such.
           :duration "10 seconds"
           ;; Optionally, a face in which to show the
           ;; interval's name in the mode line.
           :face 'font-lock-type-face
           ;; A list of actions to take before starting the interval
           ;; (really, one or a list of functions to call with the hammy
           ;; as the argument).  The `do' macro expands to a lambda,
           ;; which the interval's `before' slot is set to.  In its
           ;; body, we call two built-in helper functions.
           :before (do (announce "Whew!")
                       (notify "Whew!"))
           ;; We want this interval to not automatically advance to the
           ;; next one; rather, we want the user to call the
           ;; `hammy-next' command to indicate when the standing-up is
           ;; actually happening.  So we provide a list of actions to
           ;; take when it's time to advance to the next interval.  We
           ;; wrap the list in a call to the built-in `remind' function,
           ;; which causes the actions to be repeated every 10 minutes
           ;; until the user manually advances to the next interval.
           :advance (remind "2 seconds"
                            ;; Every 10 minutes, while the hammy is waiting
                            ;; to be advanced to the next interval, remind
                            ;; the user by doing these things:
                            (do (announce "Time to stretch your legs!")
                                (notify "Time to stretch your legs!")
                                (play-sound-file "~/Misc/Sounds/mooove-it.wav"))))
          (interval :name "🤸"
                    :duration "2 seconds"
                    :face 'font-lock-builtin-face
                    :before (do (announce "Mooove it!")
                                (notify "Mooove it!"))
                    ;; Again, the interval should not advance automatically
                    ;; to the next--the user should indicate when he's
                    ;; actually sat down again.  (If we omitted the
                    ;; `:advance' slot, the interval would automatically
                    ;; advance when it reached its duration.)
                    :advance (do (announce "Time for a sit-down...")
                                 (notify "Time for a sit-down...")
                                 (play-sound-file "~/Misc/Sounds/relax.wav")))))
#+end_src
* Auto dim buffers
#+begin_src emacs-lisp
  (use-package auto-dim-other-buffers)

  (set-face-attribute 'auto-dim-other-buffers-face nil :background "#700CB3")
#+end_src
* Visualization tools
#+begin_src emacs-lisp
  (use-package graphviz-dot-mode)
  ;; TODO: There seems to be an issue with my face setup and svg
  ;; rendering.
  (use-package pair-tree)
#+end_src
* Launch vterm
#+begin_src emacs-lisp
  (require 'vterm)
  (require 'dired)

  (defun vterm--my-new ()
    (save-window-excursion
      (vterm t)))

  (defun dired-open-with-vterm (command &optional arg file-list)
    (interactive
     (let ((files (dired-get-marked-files t current-prefix-arg nil nil t)))
       (list
        ;; Want to give feedback whether this file or marked files are used:
        (dired-read-shell-command "& on %s: " current-prefix-arg files)
        current-prefix-arg
        files)))
    (let ((buffer (vterm--my-new))
          (buffer-name (format "<vterm-%s>"
                               (string-replace " " "-" command)))
          (command (dired-shell-stuff-it command file-list t arg)))
      (when-let ((existing (get-buffer buffer-name)))
        (kill-buffer existing))
      (switch-to-buffer buffer)
      (sit-for 0.1)
      (rename-buffer buffer-name)
      (vterm-insert command)
      (vterm-send-return)))

  (define-key dired-mode-map (kbd "V") #'dired-open-with-vterm)
#+end_src
* direnv
#+begin_src emacs-lisp
  (use-package direnv
   :config
   (direnv-mode))
#+end_src
* ledger
#+begin_src emacs-lisp
  (require 'my-ledger)
#+end_src
