Skip to content

Latest commit

 

History

194 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 

Repository files navigation

#+PROPERTY: header-args:elisp :tangle ./init.el :results none

#+begin_quote
1. Clone this repo to ~/.emacs.d
2. Open =readme.org= in emacs
3. Press =C-c C-v t= to tangle all the blocks
4. Restart Emacs. 
#+end_quote

This setup make certain assumptions regarding its environment:
1. Machine in use is Linux or MacOS
2. Shell in use is =fish=

* Spook max 👻
:PROPERTIES:
:ID:       D06520AB-2147-4FA7-83D4-6F3349B4743C
:END:

- Feature Flags

  Feature flags to change the configuration based on different scenarios. Right now I run my Emacs
  on two machines, but instead for adding checks for which machine I am on right now, I want to
  create a default configuration, and modify it based on which feature-flags are enabled.

  A profile is a cons cell of =(name . metadata)=

  #+begin_src elisp
    (defvar my--feature-flags '())
  #+end_src

  - On mac without external monitor

    #+begin_src elisp
      (when (eq system-type 'darwin)
        (add-to-list 'my--feature-flags '(small-screen . t)))
    #+end_src

  - When Emacs' packages are handled by guix

    #+begin_src elisp
      (when (file-exists-p "/gnu/store")
        (add-to-list 'my--feature-flags '(powered-by-guix . t)))
    #+end_src

  - When Emacs' packages are handled by straight.el

    #+begin_src elisp
      (unless (assoc 'powered-by-guix my--feature-flags)
        (add-to-list 'my--feature-flags '(powered-by-straightel . t)))
    #+end_src

- Bootstrap

  #+begin_src elisp :tangle ./early-init.el
    ;; -*- lexical-binding: t -*-
    ;;; Bootstrap
    ;; Contrary to what many Emacs users have in their configs, you don't need
    ;; more than this to make UTF-8 the default coding system:
    (set-language-environment "UTF-8")

    ;; set-language-enviornment sets default-input-method, which is unwanted
    (setq default-input-method nil
          package-enable-at-startup nil)
  #+end_src


#+begin_src elisp :tangle ./init.el
  ;; -*- lexical-binding: t -*-

  (when (assoc 'powered-by-guix my--feature-flags)
    ;; Must use :ensure nil until this issue is fixed
    ;; https://codeberg.org/guix/guix/issues/736
    (setq use-package-ensure-function (lambda () nil)))

  ;; Bootstrap straight.el
  (defvar bootstrap-version)
  (let ((bootstrap-file
         (expand-file-name
          "straight/repos/straight.el/bootstrap.el"
          (or (bound-and-true-p straight-base-dir)
              user-emacs-directory)))
        (bootstrap-version 7))
    (unless (file-exists-p bootstrap-file)
      (with-current-buffer
          (url-retrieve-synchronously
           "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
           'silent 'inhibit-cookies)
        (goto-char (point-max))
        (eval-print-last-sexp))))

  ;; use straight with use-package by default
  (when (assoc 'powered-by-straightel my--feature-flags)
    (setf straight-use-package-by-default t))
#+end_src

** Helper utilities
:PROPERTIES:
:ID:       675D81B1-9A5A-44E8-BA29-888C967974F9
:END:

- Ergonomically create keymaps

  #+begin_src elisp
    (defmacro my--defkeymap (name prefix &rest bindings)
      "Create a new NAME-keymap bound to PREFIX, with BINDINGS.

    Usage:
      (my--defkeymap \"my-git\" \"C-c g\"
        '(\"s\" . magit-status))
    "
      (let* ((keymap-name (intern (concat name "-keymap")))
             (keymap-alias (intern name))
             (keymap-bindings (mapcar
                               (lambda (binding)
                                 (let ((binding (eval binding)))
                                   `(define-key ,keymap-name (kbd ,(car binding)) #',(cdr binding))))
                               bindings)))
        `(progn
           (defvar ,keymap-name (make-sparse-keymap))
           (defalias ',keymap-alias ,keymap-name)
           (global-set-key (kbd ,prefix) ',keymap-alias)
           ,@keymap-bindings)))
  #+end_src

- Utilities for helping with scratch buffer

  This exactly function was introduced at or before Emacs version 29.1.
  #+begin_src elisp
    (defun my--get-or-create-scratch ()
      "Switch to *scratch* buffer. Create if it doesn't already exist"
      (interactive)
      (let ((s-buf (get-buffer "*scratch*")))
        (switch-to-buffer (get-buffer-create "*scratch*"))
        (when (not s-buf) (emacs-lisp-mode))))
  #+end_src

  For firefox, I want to capture whatever I am reading and open the captured
  content in baby-window. But there is a fuck-up here that I've been unable to
  fix. On aborting the capture, the window layout gets messed up. 

  #+begin_src elisp
    (defun my--baby-window (&optional baby-size)
      "Create a baby of active window of BABY-SIZE height.
    A baby-window is a small window below active-window, like
    DevConsole in a browser. Depending on the active-window,
    baby-window contains a different application. If the prefix-arg
    is nil, baby-window always open *scratch* buffer."
      (interactive)
      (let ((baby-window (split-window-below (or baby-size -20))))
        (select-window baby-window)
        (my--get-or-create-scratch)))
  #+end_src
  
** Preliminary setup
:PROPERTIES:
:ID:       704db7c8-f339-48cc-8e2c-d680da5899fd
:END:

- Start emacs as a server

  #+begin_src elisp
    (server-start)
  #+end_src
  
- Unset annoying keybindings

  #+begin_src elisp
    (global-unset-key (kbd "C-x C-z"))
    (global-unset-key (kbd "C-z"))
    (global-unset-key (kbd "C-h h"))
  #+end_src

- Set a custom-file so Emacs won't put customized entries in my =init.el= which
  gets overwritten every time I tangle spookmax.d

  #+BEGIN_SRC elisp
    (setq custom-file (concat user-emacs-directory "custom.el"))
  #+END_SRC

- Disable the ugly-ass toolbar, scroll-bars and menu-bar

  #+begin_src elisp :tangle ./init.el
    (setq inhibit-startup-screen t
          ring-bell-function #'ignore
          use-dialog-box nil)
    (tool-bar-mode -1)
    (scroll-bar-mode -1)
    (menu-bar-mode -1)
    (tooltip-mode -1)
  #+end_src

- Make emacs a little transparent
 #+begin_src elisp :tangle ./init.el
   (set-frame-parameter (selected-frame) 'alpha '(98 . 95))
   (add-to-list 'default-frame-alist '(alpha . (98 . 95)))
 #+end_src

- Disable native-comp warnings

  #+begin_src elisp
    (setq native-comp-async-report-warnings-errors 'silent)
  #+end_src

- UI fixes copied from Doom
  https://github.com/hlissner/doom-emacs/blob/develop/core/core-ui.el

  - Scrolling
    #+begin_src elisp
      ;;; Scrolling

      (setq hscroll-margin 2
            hscroll-step 1
            ;; Emacs spends too much effort recentering the screen if you scroll the
            ;; cursor more than N lines past window edges (where N is the settings of
            ;; `scroll-conservatively'). This is especially slow in larger files
            ;; during large-scale scrolling commands. If kept over 100, the window is
            ;; never automatically recentered.
            scroll-conservatively 101
            scroll-margin 0
            scroll-preserve-screen-position t
            ;; Reduce cursor lag by a tiny bit by not auto-adjusting `window-vscroll'
            ;; for tall lines.
            auto-window-vscroll nil
            ;; mouse
            mouse-wheel-scroll-amount '(2 ((shift) . hscroll))
            mouse-wheel-scroll-amount-horizontal 2)
    #+end_src

  - Cursors

    #+begin_src elisp
      ;;; Cursor
      (blink-cursor-mode -1)

      ;; Don't blink the paren matching the one at point, it's too distracting.
      (setq blink-matching-paren nil)

      ;; Don't stretch the cursor to fit wide characters, it is disorienting,
      ;; especially for tabs.
      (setq x-stretch-cursor nil)
    #+end_src

  - Window/Frame

    #+begin_src elisp
      ;; A simple frame title
      (setq frame-title-format '("%b")
            icon-title-format frame-title-format)

      ;; Don't resize the frames in steps; it looks weird, especially in tiling window
      ;; managers, where it can leave unseemly gaps.
      (setq frame-resize-pixelwise t)

      ;; But do not resize windows pixelwise, this can cause crashes in some cases
      ;; when resizing too many windows at once or rapidly.
      (setq window-resize-pixelwise nil)

      ;; Favor vertical splits over horizontal ones. Monitors are trending toward
      ;; wide, rather than tall.
      (setq split-width-threshold 160
            split-height-threshold nil)
    #+end_src

  - Minibuffer

    #+begin_src elisp
      ;;
      ;;; Minibuffer

      ;; Allow for minibuffer-ception. Sometimes we need another minibuffer command
      ;; while we're in the minibuffer.
      (setq enable-recursive-minibuffers t)

      ;; Show current key-sequence in minibuffer ala 'set showcmd' in vim. Any
      ;; feedback after typing is better UX than no feedback at all.
      (setq echo-keystrokes 0.02)

      ;; Expand the minibuffer to fit multi-line text displayed in the echo-area. This
      ;; doesn't look too great with direnv, however...
      (setq resize-mini-windows 'grow-only)

      ;; Typing yes/no is obnoxious when y/n will do
      (setf use-short-answers t)

      ;; Try to keep the cursor out of the read-only portions of the minibuffer.
      (setq minibuffer-prompt-properties '(read-only t intangible t cursor-intangible t face minibuffer-prompt))
      (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)

      ;; Don't resize the frames in steps; it looks weird, especially in tiling window
      ;; managers, where it can leave unseemly gaps.
      (setq frame-resize-pixelwise t)

      ;; But do not resize windows pixelwise, this can cause crashes in some cases
      ;; when resizing too many windows at once or rapidly.
      (setq window-resize-pixelwise nil)
    #+end_src

- Allow selection to be deleted, generally expected behavior during
  editing. I tried to not have this on by default, but I am finding
  that to be increasingly annoying.

  #+begin_src elisp
    (delete-selection-mode +1)
  #+end_src

- Indentation and whitespace

  #+begin_src elisp
    (setq my--indent-width 2)
    (setq-default tab-width my--indent-width)
    (setq-default indent-tabs-mode nil)
  #+end_src

  From: https://github.com/susam/emfy/blob/main/.emacs#L26
  #+begin_src elisp
    (setq-default indicate-empty-lines t)
    (setq-default indicate-buffer-boundaries 'left)

    ;; Consider a period followed by a single space to be end of sentence.
    (setq sentence-end-double-space nil)

    (setq create-lockfiles nil)
  #+end_src

  I got sick of manually calling whitespace cleanup all the trim.
  Cleanup whitespace.

  #+begin_src elisp
    (use-package whitespace-cleanup-mode
      :config
      (global-whitespace-cleanup-mode +1))
  #+end_src

- Fill column for auto-formatting/filling paragraphs.

  #+begin_src elisp
    (setq-default fill-column 80)
  #+end_src

- Introspection
  :PROPERTIES:
  :ID:       e17d83de-251c-4407-b2ea-ca9c428e5ea1
  :END:

  Setup =which-key= for easy keys discovery
  
  #+begin_src elisp
    (use-package which-key
      :config
      (which-key-mode t))
  #+end_src

- Highlighting
  :PROPERTIES:
  :ID:       79c1e2a9-c52e-4660-ba70-f6f1f98f7d4e
  :END:

  #+begin_src elisp
    (global-hl-line-mode +1)

    (use-package highlight-symbol
      :hook (prog-mode . highlight-symbol-mode)
      :config
      (setq highlight-symbol-idle-delay 0.3))
  #+end_src

- Line numbers
  :PROPERTIES:
  :ID:       2b554619-a8c0-4bd0-8ab0-8107c52a6e7e
  :END:

  #+begin_src elisp
    (global-display-line-numbers-mode 1)
  #+end_src

- Fix control characters in compilation-mode

  Otherwise weird characters appear when fancy-ass CLI tools are executed.

  #+begin_src elisp
    (add-hook 'compilation-filter-hook 'ansi-color-compilation-filter)
  #+end_src

- Window management

  - Custom window keybindings

    #+begin_src elisp
      (my--defkeymap "my-windows" "C-c s-w"
        '("-" . split-window-below)
        '("_" . my--baby-window)
        '("/" . split-window-right)
        '("d" . delete-window)
        '("m" . delete-other-windows)
        '("o" . other-window)
        '("h" . windmove-left)
        '("j" . windmove-down)
        '("k" . windmove-up)
        '("l" . windmove-right)
        '("w" . ace-window))
    #+end_src

  - Install [[https://github.com/abo-abo/ace-window][ace-window]] for some nice utilities.

    #+begin_src elisp
      (defun my--aw-kill-buffer-in-window (win)
        "Kill the buffer shown in window WIN."
        (kill-buffer (window-buffer win)))

      (defun my--aw-kill-buffer-and-window (win)
        "Kill the buffer shown in window WIN and window itself."
        (kill-buffer (window-buffer win))
        (delete-window win))

      (use-package ace-window
        :config
        (setq aw-dispatch-always t)
        (global-set-key (kbd "C-c w") 'ace-window)
        (setq aw-dispatch-alist
              '((?d my--aw-kill-buffer-in-window "Kill buffer in window")
                (?s aw-swap-window "Swap Windows")
                (?S aw-move-window "Move Window")
                (?c aw-copy-window "Copy Window")
                (?w aw-flip-window)
                (?b aw-switch-buffer-in-window "Select Buffer")
                (?B aw-switch-buffer-other-window "Switch Buffer Other Window")
                (?k aw-delete-window "Delete Window")
                (?K my--aw-kill-buffer-and-window "Kill buffer in window")
                (?= aw-split-window-fair "Split Fair Window")
                (?- aw-split-window-vert "Split Vert Window")
                (?/ aw-split-window-horz "Split Horz Window")
                (?m delete-other-windows "Delete Other Windows")
                (?? aw-show-dispatch-help))
              aw-keys '(?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)))
    #+end_src

- Buffer management

  #+begin_src elisp
    (my--defkeymap
     "my-buffers" "C-c b"
     '("b" . switch-to-buffer)
     '("n" . next-buffer)
     '("p" . previous-buffer)
     '("n" . next-buffer)
     '("d" . kill-current-buffer)
     '("s" . my--get-or-create-scratch))
  #+end_src

- Font size

  #+begin_src elisp
    (defvar my--font-size 11)
    (when (assoc 'small-screen my--feature-flags)
      (setq my--font-size 14))
    (set-face-attribute 'default nil :height (* 10 my--font-size))
  #+end_src

- [Ma]git

  Magit uses =project-switch-commands= which are present only in more recent
  project.el project.

  #+begin_src elisp
    (require 'project)
    (use-package magit
      :config
      (setq magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1))
  #+end_src

  - [[https://github.com/sshaw/git-link][git-link]] so I can copy link to lines in files because evidently, I am doing that a lot

    #+begin_src elisp
      (use-package git-link
        :config
        (setf git-link-use-commit t))
    #+end_src

  - Buncha nice keybindings.

    #+begin_src elisp
      (my--defkeymap "my-git" "C-c g"
        '("s" . magit-status)
        '("b" . magit-blame)
        '("g" . magit-dispatch))
    #+end_src

  - Magit Forge

    #+begin_src elisp
      ;; (use-package forge
      ;;   :after magit)
    #+end_src    

- Ability to select both parts from a diff (from [[https://emacs.stackexchange.com/questions/19339/how-to-use-both-variants-in-ediff][SO]])

  #+begin_src elisp
    (defun my--ediff-copy-both-to-C ()
      (interactive)
      (ediff-copy-diff ediff-current-difference nil 'C nil
                       (concat
                        (ediff-get-region-contents ediff-current-difference 'A ediff-control-buffer)
                        (ediff-get-region-contents ediff-current-difference 'B ediff-control-buffer))))

    (defun add-d-to-ediff-mode-map () (define-key ediff-mode-map "d" 'my--ediff-copy-both-to-C))
    (add-hook 'ediff-keymap-setup-hook 'add-d-to-ediff-mode-map)
  #+end_src

- Keep backup/auto-save files out of my vc

  #+begin_src elisp
    (setq
     backup-dir "~/.emacs.d/bakups"
     backup-directory-alist `((".*" . ,backup-dir))
     auto-save-file-name-transforms `((".*" ,backup-dir t))
     create-lockfiles nil)
  #+end_src

- Setup PATH from shell

  #+begin_src elisp
    (use-package exec-path-from-shell
      :config
      (exec-path-from-shell-initialize))
  #+end_src

** Libraries
:PROPERTIES:
:ID:       23e29ffc-70a8-4882-8a9b-86d66908e157
:END:

Libraries used elsewhere.

#+begin_src elisp
  (use-package plz)
#+end_src

** Org mode
:PROPERTIES:
:ID:       8b2528d8-3fd2-4076-8b1e-791df8ed9a67
:END:

  #+begin_src elisp
    (use-package org
      :config
      (eval-after-load 'org-mode
        (org-link-set-parameters
         "yt"
         :follow #'my-org--follow-yt-link
         :export #'my-org--export-yt-link))
      (add-hook
       'org-mode-hook
       (lambda () (display-line-numbers-mode -1))))
  #+end_src

- Other settings

  #+begin_src elisp
    (setq
     org-startup-indented t
     org-startup-folded nil
     org-agenda-window-setup "only-window"
     org-directory "~/Documents/org"
     org-agenda-diary-file (concat org-directory "/diary.org.gpg")
     org-inbox-file (concat org-directory "/TODOs.org")
     org-agenda-files (list org-inbox-file
                            (expand-file-name "work/on.org" org-directory))
     ;;Todo keywords I need
     org-todo-keywords '((sequence "TODO(t!)" "DOING(n!)" "|" "DONE(d!)" "CANCELED(c@)"))
     org-todo-keyword-faces '(("DOING" . "DeepSkyBlue")
                              ("CANCELED" . org-done))
     org-default-notes-file (concat org-directory "/refile.org")
     org-refile-targets '((org-agenda-files . (:maxlevel . 6)))
     org-capture-templates
     '(("i" "Idea" entry (file+headline org-inbox-file "Inbox") "* %?\t\t:idea:\n")
       ("t" "Todo" entry (file+headline org-inbox-file "Inbox") "* TODO %?\n"))
     org-log-into-drawer "LOGBOOK"
     org-log-done "time"
     org-clock-report-include-clocking-task t
     org-clock-into-drawer t
     org-fontify-done-headline t
     org-enforce-todo-dependencies t
     org-agenda-overriding-columns-format "%80ITEM(Task) %6Effort(Est){:} %6CLOCKSUM_T(Today) %6CLOCKSUM(Total)"
     org-columns-default-format org-agenda-overriding-columns-format
     org-agenda-columns-add-appointments-to-effort-sum t
     org-use-property-inheritance t
     org-confirm-babel-evaluate nil
     org-id-link-to-org-use-id t
     org-fold-catch-invisible-edits 'show
     org-cycle-separator-lines 0
     org-export-allow-bind-keywords t
     ;; let's try these out
     org-use-speed-commands t
     ;; Pressing `l' in org-agenda-mode show the log of all state changes. Something I need because
     ;; otherwise old-scheduled and repeated tasks that I do, or tasks I cancel gets lost from my agenda
     ;; view
     org-agenda-log-mode-items '(closed clock state))

    ;; org-mode settings
    (with-eval-after-load 'org
      (org-indent-mode t)
      (require 'org-id))
  #+end_src

- Keybindings

  #+begin_src elisp
    (global-set-key (kbd "C-c c") #'org-capture)

    (my--defkeymap
     "my-org" "C-c o"
     '("a" . org-agenda-list)
     '("A" . org-agenda)
     '("c" . org-capture)
     '("C" . org-clock-goto)
     '("o" . consult-org-agenda))

    (with-eval-after-load 'org
      (define-key org-mode-map (kbd "C-c >") #'org-timestamp-inactive))
  #+end_src

- org-super-agenda
  :PROPERTIES:
  :ID:       06dd246b-30f0-4c17-ab47-8128d49f7f69
  :END:

  More/better structure in agenda view.

  #+begin_src elisp
    (use-package org-super-agenda
      :config
      (org-super-agenda-mode t)
      (setq org-super-agenda-groups
            '((:name "In Progress" :todo "DOING" :order 1)
              (:name "🪫" :tag "system" :order 1)
              (:name "👶🏼" :category "Baby" :tag "baby" :order 2)
              (:name "Work" :tag "work" :order 2)
              (:name "🏯" :category "Home 🏯" :tag "apartment" :order 3)
              (:name "Projects" :tag "project" :order 3)
              (:name "Self ❤️" :tag "health" :category "Self" :order 3)
              (:name "Inbox" :tag "inbox" :order 4)
              (:name "Study 📚" :tag ("study" "reading") :order 4)
              (:name "Finance 💶" :tag "finance" :order 4)
              (:name "Habits" :tag "habit" :order 6))))
  #+end_src

- org-babel

  #+begin_src elisp
    (with-eval-after-load 'org
      (org-babel-do-load-languages
       'org-babel-load-languages
       '((emacs-lisp . t)
         (plantuml . t)
         (shell . t)
         (sql . t)
         (sqlite . t)
         (lisp . t)
         (js . t))))
  #+end_src

- Allow adding HTML class/id to exported src blocks

  Org mode don't allow adding custom HTML class or id to exported src
  blocks, but I've found myself in need of this functionality when
  customizing published projects.

  #+begin_src elisp
    (defun my--org-src-block-html-attrs-advice (oldfun src-block contents info)
      "Add class, id or data-* CSS attributes to html source block output.

    Allows class, id or data  attributes to be added to a source block using
    #attr_html:

        ,#+ATTR_HTML: :class myclass :id myid
        ,#+begin_src python
        print(\"Hi\")
        ,#+end_src
    "
      (let* ((old-ret (funcall oldfun src-block contents info))
             (class-tag (org-export-read-attribute :attr_html src-block :class))
             (data-attr (let ((attr (org-export-read-attribute :attr_html src-block :data)))
                          (when attr (split-string attr "="))))
             (id-tag (org-export-read-attribute :attr_html src-block :id)))
        (if (or class-tag id-tag  data-attr)
            (concat
             "<div "
             (if class-tag (format "class=\"%s\" " class-tag))
             (if id-tag (format "id=\"%s\" " id-tag))
             (if data-attr (format "data-%s=\"%s\" " (car data-attr) (cadr data-attr)))
             ">"
             old-ret
             "</div>")
          old-ret)))

    (advice-add 'org-html-src-block :around #'my--org-src-block-html-attrs-advice)
  #+end_src

- Support exporting code blocks with syntax-highlighting

  #+begin_src elisp
    (use-package htmlize)
  #+end_src

- Custom links

  - =yt://=  links

    - Open =yt://= links in =mpv= if mpv is present
    - Open =yt://= links in browser if mpv isn't installed or prefix-argument is
      provided with =org-open-at-point= (i.e =C-c C-o=)

    #+begin_src elisp
      (defun my-org--follow-yt-link (path prefix)
        (let* ((url (format "https:%s" path))
               (proc-name (format "*yt://%s*" url)))
          (if (and prefix (executable-find "mpv"))
              (browse-url url)
            (make-process :name proc-name :buffer proc-name :command `("mpv" ,url))
            (message "Launched mpv in buffer: %s" proc-name))))

      (defun my-org--export-yt-link (path desc backend)
        (when (eq backend 'html)
          (let* ((video-id (cadar (url-parse-query-string path)))
                 (url (if (string-empty-p video-id) path
                        (format "//youtube.com/embed/%s" video-id))))
            (format
             "<iframe width=\"560\" height=\"315\" src=\"%s\" title=\"%s\" frameborder=\"0\" allowfullscreen></iframe>"
             url desc))))
    #+end_src

- Reset checkboxes when a subtree is repeated

  Taken from [[https://emacs.stackexchange.com/a/70721][SO]]

  #+begin_src elisp
    (add-hook 'org-todo-repeat-hook #'org-reset-checkbox-state-subtree)
  #+end_src

- Set tab-width for column mode

  =org-columns= is complaining that tab-width in org-mode buffers should be 8 not 2.

  #+begin_src elisp
    (defun my--set-org-vars ()
      (setf tab-width 8))

    (add-hook 'org-agenda-mode-hook #'my--set-org-vars)
  #+end_src

- Lib

  Utility functions for working with org/denote that are used elsewhere in configuration.

  #+begin_src elisp
    (defun my--denote-files-with-tags (tags)
      "Return denote files which have all TAGS."
      (seq-filter (lambda (file)
                    (null (seq-difference tags (denote-extract-keywords-from-path file))))
                  (denote-directory-files)))
  #+end_src

** Modal editing with Meow
:PROPERTIES:
:ID:       17c2eeec-133f-49f3-b2ce-95bf3dab1188
:END:

Let's get some modal editing with some spice. I have used Evil mode
with Spacemacs, I was going to configure Evil, but let's give meow a
shot!

- Meow qwerty setup copied from https://github.com/meow-edit/meow/blob/master/KEYBINDING_QWERTY.org
  #+begin_src elisp
    (defun meow-setup ()
      (setq meow-cheatsheet-layout meow-cheatsheet-layout-qwerty)
      (meow-motion-overwrite-define-key
       '("j" . meow-next)
       '("k" . meow-prev)
       '("<escape>" . ignore))
      (meow-leader-define-key
       ;; SPC j/k will run the original command in MOTION state.
       '("j" . "H-j")
       '("k" . "H-k")
       ;; Use SPC (0-9) for digit arguments.
       '("1" . meow-digit-argument)
       '("2" . meow-digit-argument)
       '("3" . meow-digit-argument)
       '("4" . meow-digit-argument)
       '("5" . meow-digit-argument)
       '("6" . meow-digit-argument)
       '("7" . meow-digit-argument)
       '("8" . meow-digit-argument)
       '("9" . meow-digit-argument)
       '("0" . meow-digit-argument)
       ;; '("/" . meow-keypad-describe-key)
       '("?" . meow-cheatsheet))

      (meow-normal-define-key
       '("0" . meow-expand-0)
       '("9" . meow-expand-9)
       '("8" . meow-expand-8)
       '("7" . meow-expand-7)
       '("6" . meow-expand-6)
       '("5" . meow-expand-5)
       '("4" . meow-expand-4)
       '("3" . meow-expand-3)
       '("2" . meow-expand-2)
       '("1" . meow-expand-1)
       '("-" . negative-argument)
       '(";" . meow-reverse)
       '("," . meow-inner-of-thing)
       '("." . meow-bounds-of-thing)
       '("[" . meow-beginning-of-thing)
       '("]" . meow-end-of-thing)
       '("a" . meow-append)
       '("A" . meow-open-below)
       '("b" . meow-back-word)
       '("B" . meow-back-symbol)
       '("c" . meow-change)
       '("d" . meow-delete)
       '("D" . meow-backward-delete)
       '("e" . meow-next-word)
       '("E" . meow-next-symbol)
       '("f" . meow-find)
       '("g" . meow-cancel-selection)
       '("G" . meow-grab)
       '("h" . meow-left)
       '("H" . meow-left-expand)
       '("i" . meow-insert)
       '("I" . meow-open-above)
       '("j" . meow-next)
       '("J" . meow-next-expand)
       '("k" . meow-prev)
       '("K" . meow-prev-expand)
       '("l" . meow-right)
       '("L" . meow-right-expand)
       '("m" . meow-join)
       '("n" . meow-search)
       '("o" . meow-block)
       '("O" . meow-to-block)
       '("p" . meow-yank)
       ;; '("q" . meow-quit)
       ;; '("Q" . meow-goto-line)
       '("r" . meow-replace)
       '("R" . meow-swap-grab)
       '("s" . meow-kill)
       '("t" . meow-till)
       '("u" . meow-undo)
       '("U" . meow-undo-in-selection)
       '("v" . meow-visit)
       '("w" . meow-mark-word)
       '("W" . meow-mark-symbol)
       '("x" . meow-line)
       ;; '("X" . meow-goto-line)
       '("y" . meow-save)
       '("Y" . meow-sync-grab)
       '("z" . meow-pop-selection)
       '("'" . repeat)
       '("<escape>" . ignore)))
  #+end_src
  
#+begin_src elisp
  (use-package meow
    :config
    (setf meow-use-clipboard t)
    (meow-global-mode)
    (meow-setup))
#+end_src

- Normal mode-keybindings. Mostly mimicking vim

  #+begin_src elisp
    (meow-normal-define-key
     '("z" . my-fold)
     '("/" . "C-s")
     '("?" . "C-r"))
  #+end_src

- Leader keybindings

  #+begin_src elisp
    (meow-leader-define-key
     '("/" . consult-git-grep)
     '("p" . "C-x p")
     '("e" . flycheck-command-map)
     '("w" . ace-window)
     '("b" . my-buffers)
     '("G" . my-git)
     '("o" . my-org)
     '("n" . my-notes))
  #+end_src

- Keychords

  #+begin_src elisp
    (use-package key-chord
      :config
      (setf key-chord-two-keys-delay 0.1)
      (key-chord-mode 1)
      (key-chord-define meow-insert-state-keymap "fd" #'meow-insert-exit))
  #+end_src

** Completion UI
:PROPERTIES:
:ID:       4b16f866-dede-4d72-8fbf-95044ed1e378
:END:
- Orderlies adds matches completion candidates by space-separated patterns in
  any order

  #+begin_src elisp
    (use-package orderless
      :config
      (setq completion-styles '(orderless)))
  #+end_src

- Vertico for completion UI

  #+begin_src elisp
    (use-package vertico)
    (require 'vertico)
    (with-eval-after-load 'vertico
      (vertico-mode +1)
      (define-key vertico-map (kbd "C-c ?") #'minibuffer-completion-help))

    (use-package vertico-directory
      :after vertico
      ;; More convenient directory navigation commands
      :bind (:map vertico-map
      ("C-h" . vertico-directory-delete-word))
      ;; Tidy shadowed file names
      :hook (rfn-eshadow-update-overlay . vertico-directory-tidy))

    (use-package vertico-quick
      
      :after vertico
      :bind (:map vertico-map
      ("C-q" . vertico-quick-insert))
      ;; Tidy shadowed file names
      :hook (rfn-eshadow-update-overlay . vertico-directory-tidy))

    ;; Persist history over Emacs restarts. Vertico sorts by history position.
    (use-package savehist
      
      :init
      (savehist-mode +1))

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

- Marginalia adds pretty information to completions. It's pretty, useful, and
  recommended by =embark= (it provides extra information to =embark=)

  #+begin_src elisp
    ;; Enable richer annotations using the Marginalia package
    (use-package marginalia)
    (require 'marginalia)
    (with-eval-after-load 'marginalia
        (define-key minibuffer-local-map (kbd "M-A") #'marginalia-cycle)
        (marginalia-mode +1))
  #+end_src

- Consult for enhanced commands

  #+begin_src elisp
    (use-package consult
      :config
      (consult-customize consult-theme :preview-key '(:debounce 0.5 any))

      (global-set-key (kbd "C-s") #'isearch-forward-regexp)
      (global-set-key (kbd "C-S-s") #'consult-line)
      (global-set-key (kbd "C-r") #'isearch-backward-regexp)
      (global-set-key (kbd "C-S-r") #'isearch-backward-regexp)
      (global-set-key (kbd "C-x b") #'consult-buffer)
      (define-key my-buffers-keymap (kbd "b") #'consult-buffer)
      (define-key my-buffers-keymap (kbd "B") #'consult-buffer-other-window)

      ;; better yank which show kill-ring for selection
      (global-set-key (kbd "C-y") #'consult-yank-pop)
      (meow-leader-define-key
       '("/" . consult-ripgrep))
      (meow-normal-define-key
       '("p" . consult-yank-pop)
       '("Q" . consult-goto-line)
       '("X" . consult-focus-lines)))

    (setq xref-show-xrefs-function #'consult-xref
          xref-show-definitions-function #'consult-xref)

    (recentf-mode +1)

    (use-package consult-flycheck
      :config
      (define-key flycheck-command-map (kbd "l") #'consult-flycheck))

    (use-package embark-consult
      :after (embark consult)
      :demand t
      :hook
      (embark-collect-mode . consult-preview-at-point-mode))
  #+end_src

** Contextual actions
:PROPERTIES:
:ID:       8AF4F0B4-6688-439D-87D8-A70FD320B547
:END:

- [[https://github.com/oantolin/embark][embark]] allow contextual actions, like opening buffers in other window from
  minibuffer and a lot more

  #+begin_src elisp
    (defun my--embark-act-no-quit ()
      "(embark-act), but don't quit the minibuffer"
      (interactive)
      (let ((embark-quit-after-action nil))
        (embark-act)))

    (use-package embark
      :bind
      (("C-," . embark-act)
       ("C->" . embark-act-all)
       ("C-." . embark-dwim)
       ("C-h b" . embark-bindings)
       ("C-<" . my--embark-act-no-quit)))
  #+end_src

** More powerful editing
:PROPERTIES:
:ID:       968A1471-8B1D-4DD9-B871-E5B44EE2EF69
:END:

- =wgrep= for editing grep buffers

  #+begin_src elisp
    (use-package wgrep)
  #+end_src
  
- =undo-tree-mode= for more powerful undo

  #+begin_src elisp
    (use-package undo-tree
      :config
      (global-undo-tree-mode t) 
      (global-set-key (kbd "C-/") #'undo)
      (global-set-key (kbd "C-S-/") #'undo-tree-redo)
      (setq undo-tree-history-directory-alist `(("." . ,(expand-file-name ".cache" user-emacs-directory)))))
  #+end_src

- =embrace= for wrapping pair manipulation

  #+begin_src elisp
    (let ((embrace-dir (expand-file-name "~/code/emacs/embrace.el")))
      (when (file-exists-p embrace-dir)
        (add-to-list 'load-path embrace-dir)
        (require 'embrace)
        (with-eval-after-load 'embrace
          (add-hook 'org-mode-hook 'embrace-org-mode-hook))
        (meow-normal-define-key
         '("S" . embrace-commander))))
  #+end_src

- [[https://github.com/joaotavora/yasnippet][yasnippet]] for templates

  #+begin_src emacs-lisp
    (require 'yasnippet)
    (with-eval-after-load 'yasnippet (add-hook 'prog-mode-hook #'yas-minor-mode))

    (use-package yasnippet-snippets
      :after yasnippet)
  #+end_src

  #+RESULTS:

** Programming
:PROPERTIES:
:ID:       f88fd5b1-1170-43e3-b2b9-e3060edd7442
:END:

- Show trailing whitespace in programming files

  #+begin_src elisp
    (add-hook 'prog-mode-hook #'(lambda () (setq-local show-trailing-whitespace t)))
  #+end_src

- Wrapping text in parens, quotes etc

  #+begin_src elisp
    (show-paren-mode 1)
    (electric-pair-mode 1)
  #+end_src

- Code folding

  #+begin_src elisp
    (my--defkeymap
     "my-fold" "C-c f"
     '("b" . hs-hide-block)
     '("O" . hs-show-block)
     '("l" . hs-hide-level)
     '("L" . hs-show-block)
     '("a" . hs-hide-all)
     '("A" . hs-show-all)
     '("z" . hs-toggle-hiding))

    (add-hook 'prog-mode-hook 'hs-minor-mode)
  #+end_src

- Flycheck for getting those in-buffer warnings errors.

  #+begin_src elisp
    (use-package flycheck
      :init
      (global-flycheck-mode t)
      ;; alias is needed for using the keymap in meow
      (defalias 'flycheck-command-map flycheck-command-map))
  #+end_src

  Flycheck eglot integration

  #+begin_src elisp
    (let ((feglot-dir (expand-file-name "~/code/emacs/flycheck-eglot")))
      (when (file-exists-p feglot-dir)
        (add-to-list 'load-path feglot-dir)
        (require 'flycheck-eglot)
        (global-flycheck-eglot-mode 1)))
  #+end_src

- Completions

  I think I have a general idea of what it does, but still fuzzy on
  details. This stuff is usually taken for granted; I've been taking
  it for granted with Spacemacs for a while now I suppose.

  #+begin_src elisp
    (let ((pkg-dir (expand-file-name "~/code/emacs/nerd-icons-corfu")))
      (when (file-exists-p pkg-dir)
        (add-to-list 'load-path pkg-dir)
        (require 'nerd-icons-corfu)))

    (use-package corfu
      :config
      (setf corfu-auto t)
      (global-corfu-mode t)
      (corfu-popupinfo-mode t)
      (add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter))
  #+end_src

- [[https://github.com/purcell/emacs-reformatter][Reformatter]] allow creating buffer/region formatters from any command.

  #+begin_src elisp
    (use-package reformatter
      :config
      (reformatter-define prettier-format
        :program (expand-file-name "node_modules/.bin/prettier"
                                   (locate-dominating-file (buffer-file-name) "node_modules/.bin/prettier"))
        :args `("--stdin-filepath" ,(buffer-file-name)))
      :hook ((web-mode . prettier-format-on-save-mode)
             (typescript-ts-mode . prettier-format-on-save-mode)))
  #+end_src

- Eglot to provide LSP support.

  #+begin_src elisp
    ;; Need to be manuall installed so we get latest version
    ;; (use-package jsonrpc
    ;;   :ensure '(jsonrpc :repo "https://git.savannah.gnu.org/git/emacs.git"
    ;;                     :files ("lisp/jsonrpc.el")))
    ;; (use-package eldoc
    ;;   :ensure '(eldoc :repo "https://git.savannah.gnu.org/git/emacs.git"
    ;;                   :files ("lisp/emacs-lisp/eldoc.el")))

    (use-package eglot
      )

    ;; Looks like jsonrpc logging make eglot super laggy for typescript.
    ;; https://old.reddit.com/r/emacs/comments/1447fy2/looking_for_help_in_improving_typescript_eglot/
    ;; https://www.reddit.com/r/emacs/comments/16vixg6/how_to_make_lsp_and_eglot_way_faster_like_neovim/
    (fset #'jsonrpc--log-event #'ignore)
    (setq eglot-events-buffer-size 0
          eglot-sync-connect nil
          eglot-connect-timeout nil
          company-idle-delay 0
          company-minimum-prefix-length 1)
    (add-hook 'focus-out-hook 'garbage-collect)
  #+end_src

*** Treesitter
:PROPERTIES:
:ID:       780140a8-6b04-414b-8e49-bc8c9fbe9b17
:END:

#+begin_src elisp
  (setq treesit-language-source-alist
        '((bash "https://github.com/tree-sitter/tree-sitter-bash")
          (cmake "https://github.com/uyha/tree-sitter-cmake")
          (css "https://github.com/tree-sitter/tree-sitter-css")
          (elisp "https://github.com/Wilfred/tree-sitter-elisp")
          (go "https://github.com/tree-sitter/tree-sitter-go")
          (html "https://github.com/tree-sitter/tree-sitter-html")
          (javascript "https://github.com/tree-sitter/tree-sitter-javascript" "master" "src")
          (json "https://github.com/tree-sitter/tree-sitter-json")
          (make "https://github.com/alemuller/tree-sitter-make")
          (markdown "https://github.com/ikatyang/tree-sitter-markdown")
          (python "https://github.com/tree-sitter/tree-sitter-python")
          (toml "https://github.com/tree-sitter/tree-sitter-toml")
          (tsx "https://github.com/tree-sitter/tree-sitter-typescript" "master" "tsx/src")
          (typescript "https://github.com/tree-sitter/tree-sitter-typescript" "master" "typescript/src")
          (yaml "https://github.com/ikatyang/tree-sitter-yaml")))
#+end_src

- Structured movement

  #+begin_src elisp
    (use-package combobulate
      :custom
      ;; You can customize Combobulate's key prefix here.
      ;; Note that you may have to restart Emacs for this to take effect!
      (setf combobulate-key-prefix "C-c j")

      ;; Optional, but recommended.
      ;;
      ;; You can manually enable Combobulate with `M-x
      ;; combobulate-mode'.
      :hook
      ((python-ts-mode . combobulate-mode)
       (js-ts-mode . combobulate-mode)
       (html-ts-mode . combobulate-mode)
       (css-ts-mode . combobulate-mode)
       (yaml-ts-mode . combobulate-mode)
       (typescript-ts-mode . combobulate-mode)
       (json-ts-mode . combobulate-mode)
       (tsx-ts-mode . combobulate-mode)))
  #+end_src

*** Lisp
:PROPERTIES:
:ID:       828dd6e7-a386-415c-b4e1-cb5515138109
:END:

Lispy for some nasty lisp structural editing.

#+begin_src elisp
  (use-package lispy
    :hook ((emacs-lisp-mode . lispy-mode)
           (lisp-mode . lispy-mode)
           (scheme-mode . lispy-mode))
    :config
    (setf lispy-colon-p nil))
#+end_src

Pretty cool auto-indentation.

#+begin_src elisp
  (use-package aggressive-indent
    :hook ((emacs-lisp-mode . aggressive-indent-mode)
           (lisp-mode . aggressive-indent-mode)))
#+end_src

- Common Lisp

  Sly for interactive development.

  #+begin_src elisp
    (use-package sly
      :hook ((lisp-mode . sly-mode))
      :config
      (setq org-babel-lisp-eval-fn #'sly-eval
            inferior-lisp-program "sbcl")
      (add-hook
       'sly-mrepl-hook
       (lambda () (set-face-foreground 'sly-mrepl-output-face "khaki3"))))
  #+end_src

  [[https://github.com/mmgeorge/sly-asdf][sly-asdf]] add asdf integration to sly.

  #+begin_src elisp
    (use-package sly-asdf
      :config
      (add-to-list 'sly-contribs 'sly-asdf 'append))
  #+end_src

*** Web dev
:PROPERTIES:
:ID:       62e08f15-d996-48fd-90c3-fd6d348555be
:END:

- Helper utilities
  
  - Are we using nvm?
    
    #+begin_src elisp
      (defun my--nvm-p ()
        (when-let* ((node (string-trim (shell-command-to-string "fish -c 'readlink (which node)'")))
                    (nvm-bin-dir
                     (and (string-match-p "\/nvm\/" node)
                          (file-name-directory node))))
          nvm-bin-dir))
    #+end_src

#+begin_src elisp
  (setq css-indent-offset my--indent-width)

  (use-package js-ts
    :mode "\\.js'"
    
    :config
    (setq js-indent-level my--indent-width)
    :hook
    (((js-ts-mode
       typescript-ts-mode) . subword-mode)))

  (use-package web-mode
    :mode (("\\.html?\\'" . web-mode))
    :config
    (setq web-mode-markup-indent-offset my--indent-width)
    (setq web-mode-code-indent-offset my--indent-width)
    (setq web-mode-css-indent-offset my--indent-width)
    (setq web-mode-content-types-alist '(("jsx" . "\\.js[x]?\\'"))))

  (use-package emmet-mode
    :hook ((html-mode           . emmet-mode)
           (css-mode            . emmet-mode)
           (js-ts-mode          . emmet-mode)
           (js-jsx-mode         . emmet-mode)
           (typescript-ts-mode  . emmet-mode)
           (tsx-ts-mode         . emmet-mode)
           (web-mode            . emmet-mode))
    :config
    (setq emmet-insert-flash-time 0.001)	; effectively disabling it
    (add-hook 'js-jsx-mode-hook #'(lambda ()
                                    (setq-local emmet-expand-jsx-className? t)))   
    (add-hook 'web-mode-hook #'(lambda ()
                                 (setq-local emmet-expand-jsx-className? t))))

  (my--defkeymap
   "my-errors" "C-c e"
   '("n" . flycheck-next-error)
   '("p" . flycheck-previous-error)
   '("l" . flycheck-list-errors)
   '("e" . flycheck-explain-error-at-point))

  (defun my--setup-ts-js ()
    "Setup Javascript and Typescript for current buffer."
    ;; Add node_modules/.bin of current project to exec-path.
    (if-let (nvm-bin (my--nvm-p))
        (add-to-list 'exec-path nvm-bin)
      (let ((bin-dir
             (expand-file-name
              "node_modules/.bin/"
              (locate-dominating-file default-directory "node_modules"))))
        (when (file-exists-p bin-dir)
          (add-to-list 'exec-path bin-dir))))

    (subword-mode)

    ;; For 95% of cases this is what I want
    (prettier-format-on-save-mode)
    (setf flymake-eslint-project-root
          (locate-dominating-file default-directory "package.json"))
    (eglot-ensure))

  (add-hook 'js-ts-mode-hook #'my--setup-ts-js)

  (use-package typescript-ts-mode
    :mode "\\.ts\\'"
    :config
    (setq-default typescript-indent-level my--indent-width)
    (add-hook 'typescript-mode-hook #'my--setup-ts-js)
    (add-hook 'typescriptt-ts-mode-hook #'my--setup-ts-js))

  (use-package tsx-ts-mode
    :mode "\\.tsx\\'"
    
    :hook ((tsx-ts-mode . subword-mode)
           (tsx-ts-mode . my--setup-ts-js))
    :config
    (setq typescript-indent-level my--indent-width))

  (use-package css-ts-mode
    
    :mode "\\.s?css\\'")
#+end_src

- JSON support

  #+begin_src elisp
    (use-package json-ts-mode
      :mode "\\.json\\'")
  #+end_src

*** Rust
:PROPERTIES:
:ID:       9A781996-6C6F-439D-B75D-E9F05BAD99F0
:END:

#+begin_src elisp
  ;; (use-package rustic
  ;;   :init
  ;;   (setq rustic-cargo-bin "cargo")
  ;;   (push 'rustic-clippy flycheck-checkers))
#+end_src

*** Haskell
:PROPERTIES:
:ID:       3720944C-C928-4ECF-9B35-7620EEF7C682
:END:
#+begin_src elisp
  (use-package haskell-mode
    :mode "\\.hs\\'"
    :config
    (add-hook 'haskell-mode-hook #'subword-mode)

    (define-key haskell-mode-map (kbd "C-c , c") #'haskell-process-load-or-reload)
    (define-key haskell-mode-map (kbd "C-c , s") #'haskell-interactive-switch)
    (define-key haskell-mode-map (kbd "C-c , l") #'haskell-interactive-mode-clear)
    (define-key haskell-mode-map (kbd "C-c , T") #'haskell-doc-show-type)
    (define-key haskell-mode-map (kbd "C-c , t") #'haskell-mode-show-type-at))
#+end_src
*** Yaml
:PROPERTIES:
:ID:       C204CCCB-D27E-48F2-BEBB-A9A913C763A4
:END:

#+begin_src elisp
  (use-package yaml-mode
    :mode "\\.ya?ml\\'")
#+end_src

*** Graphql
:PROPERTIES:
:ID:       204E2F4A-9F0A-4458-8135-DD3861052AE3
:END:

#+begin_src elisp
  (use-package graphql-mode
    :mode "\\.graphql\\'")
#+end_src

** Niceties
:PROPERTIES:
:ID:       8e8563f8-2161-4af3-b072-fc3b81cc57a6
:END:

Nice to have features but not necessary.

- Ace Jump for quickly jumping around in a buffer

  #+begin_src elisp
    (my--defkeymap
     "my-jump" "C-c q"
     '("q" . ace-jump-mode)
     '("w" . ace-jump-word-mode))

    (use-package ace-jump-mode)
  #+end_src

- Treemacs for easy code exploration

  #+begin_src elisp
    (use-package treemacs
      :defer t
      :init
      (with-eval-after-load 'winum
        (define-key winum-keymap (kbd "M-0") #'treemacs-select-window))
      :config
      (progn
        (setq treemacs-collapse-dirs                   (if treemacs-python-executable 3 0)
              treemacs-deferred-git-apply-delay        0.5
              treemacs-directory-name-transformer      #'identity
              treemacs-display-in-side-window          t
              treemacs-eldoc-display                   'simple
              treemacs-file-event-delay                2000
              treemacs-file-extension-regex            treemacs-last-period-regex-value
              treemacs-file-follow-delay               0.2
              treemacs-file-name-transformer           #'identity
              treemacs-follow-after-init               t
              treemacs-expand-after-init               t
              treemacs-find-workspace-method           'find-for-file-or-pick-first
              treemacs-git-command-pipe                ""
              treemacs-goto-tag-strategy               'refetch-index
              treemacs-header-scroll-indicators        '(nil . "^^^^^^")
              treemacs-hide-dot-git-directory          t
              treemacs-indentation                     2
              treemacs-indentation-string              " "
              treemacs-is-never-other-window           nil
              treemacs-max-git-entries                 5000
              treemacs-missing-project-action          'ask
              treemacs-move-forward-on-expand          nil
              treemacs-no-png-images                   nil
              treemacs-no-delete-other-windows         t
              treemacs-project-follow-cleanup          nil
              treemacs-persist-file                    (expand-file-name ".cache/treemacs-persist" user-emacs-directory)
              treemacs-position                        'left
              treemacs-read-string-input               'from-child-frame
              treemacs-recenter-distance               0.1
              treemacs-recenter-after-file-follow      nil
              treemacs-recenter-after-tag-follow       nil
              treemacs-recenter-after-project-jump     'always
              treemacs-recenter-after-project-expand   'on-distance
              treemacs-litter-directories              '("/node_modules" "/.venv" "/.cask")
              treemacs-show-cursor                     nil
              treemacs-show-hidden-files               t
              treemacs-silent-filewatch                nil
              treemacs-silent-refresh                  nil
              treemacs-sorting                         'alphabetic-asc
              treemacs-select-when-already-in-treemacs 'move-back
              treemacs-space-between-root-nodes        t
              treemacs-tag-follow-cleanup              t
              treemacs-tag-follow-delay                1.5
              treemacs-text-scale                      nil
              treemacs-user-mode-line-format           nil
              treemacs-user-header-line-format         nil
              treemacs-wide-toggle-width               70
              treemacs-width                           35
              treemacs-width-increment                 1
              treemacs-width-is-initially-locked       t
              treemacs-workspace-switch-cleanup        nil)

        ;; The default width and height of the icons is 22 pixels. If you are
        ;; using a Hi-DPI display, uncomment this to double the icon size.
        ;;(treemacs-resize-icons 44)

        (treemacs-follow-mode t)
        (treemacs-filewatch-mode t)
        (treemacs-fringe-indicator-mode 'always)
        ;; (when treemacs-python-executable
        ;;   (treemacs-git-commit-diff-mode t))

        (pcase (cons (not (null (executable-find "git")))
                     (not (null treemacs-python-executable)))
          (`(t . t)
           (treemacs-git-mode 'deferred))
          (`(t . _)
           (treemacs-git-mode 'simple)))

        (treemacs-hide-gitignored-files-mode nil))
      :bind
      (:map global-map
            ("M-0"       . treemacs-select-window)
            ("C-x t 1"   . treemacs-delete-other-windows)
            ("C-x t t"   . treemacs)
            ("C-x t d"   . treemacs-select-directory)
            ("C-x t B"   . treemacs-bookmark)
            ("C-x t C-t" . treemacs-find-file)
            ("C-x t M-t" . treemacs-find-tag)))

    (use-package treemacs-magit
      :after (treemacs magit))

    (use-package treemacs-all-the-icons
      :config
      (treemacs-load-theme "all-the-icons"))
  #+end_src

- Highlight indentation

  #+begin_src elisp
    (use-package highlight-indent-guides
      :config
      (setf highlight-indent-guides-method 'bitmap)
      (add-hook 'prog-mode-hook 'highlight-indent-guides-mode))
  #+end_src

- Move text around

  #+begin_src elisp
    (use-package move-text
      :config
      (move-text-default-bindings))
  #+end_src

** Looks
:PROPERTIES:
:ID:       baaa3b17-3676-4759-b2a0-dc792897862b
:END:

#+begin_src elisp
  ;; (use-package doom-themes
  ;;   :config
  ;;   (setq doom-rouge-brighter-modeline t
  ;;         doom-rouge-brighter-comments t)
  ;;   (load-theme 'doom-rouge t))

  ;; (use-package nimbus-theme
  ;;   :config
  ;;   (load-theme 'nimbus t))

  (use-package modus-themes
    :config
    (load-theme 'modus-vivendi-tinted :no-confirm))
#+end_src

Modeline

#+begin_src elisp
  ;; (use-package doom-modeline
  ;;   :config
  ;;   (setq doom-modeline-height 24)
  ;;   (doom-modeline-mode 1))
#+end_src

Let's also try smooth-scrolling.

#+begin_src elisp
  (pixel-scroll-precision-mode t)
#+end_src

** Applications
:PROPERTIES:
:ID:       9061cb70-e3e7-49d5-8fec-476f36ea3d47
:END:

Non crucial things which should be loaded last. If they fail, nothing crucial is
blocked.

#+begin_src elisp
  (my--defkeymap "my-apps" "C-c a")
#+end_src

- Spell checking

  #+begin_src elisp
    (with-eval-after-load "ispell"
      (setq ispell-program-name "hunspell")
      (setq ispell-dictionary "en_US,de_DE")
      (ispell-set-spellchecker-params)
      (ispell-hunspell-add-multi-dic "en_US,de_DE")
      (setq ispell-personal-dictionary "~/.emacs.d/.hunspell_per_dic"))
  #+end_src

  #+begin_src elisp
    (use-package flyspell
      :hook
      (text-mode . flyspell-mode)
      (prog-mode . flyspell-prog-mode)
      :config
      (define-key flyspell-mode-map (kbd "C-,") nil)
      (define-key flyspell-mode-map (kbd "C-.") nil)
      (define-key flyspell-mode-map (kbd "C-;") #'flyspell-correct-wrapper))

    (use-package flyspell-correct
      :after (flyspell)
      :commands (flyspell-correct-at-point flyspell-correct-wrapper))

    (define-key my-apps-keymap (kbd "s")
                (define-keymap
                  :parent my-apps-keymap
                  :name "spellcheck"
                  (kbd ".") #'flyspell-correct-at-point
                  (kbd ",") #'flyspell-correct-before-point))
  #+end_src  

- Notes using denotes

  #+begin_src elisp
    (setq denote-directory (expand-file-name "denotes" org-directory)
	  denote-date-prompt-use-org-read-date t)
    (use-package denote
      :config
      (add-hook 'dired-mode-hook #'denote-dired-mode))
  #+end_src

  - Enhance denote a bit, don't know why these aren't a part of denote itself.

    #+begin_src elisp
      (defun my--denote-split-org-subtree (&optional prefix)
        "Create new Denote note as an Org file using current Org subtree."
        (interactive "P")
        (let ((text (org-get-entry))
              (heading (org-get-heading :no-tags :no-todo :no-priority :no-comment))
              (tags (org-get-tags))
              (subdir (when prefix (denote-subdirectory-prompt))))
          (delete-region (org-entry-beginning-position) (org-entry-end-position))
          (denote heading tags 'org subdir)
          (insert text)))
    #+end_src

  - Setup for taking notes for reading/video-watching I do in Firefox.

    /Moved to denote/

  - CRM   

    #+begin_src elisp
      (defvar crm-directory (expand-file-name "crm" denote-directory))

      (defun my-crm--open-or-create ()
        "Find or create CRM entry."
        (interactive)
        (let ((denote-directory crm-directory))
          (call-interactively #'denote-open-or-create)))

      (defun my-crm--link-or-create ()
        "Find or create CRM entry."
        (interactive)
        (let ((denote-directory crm-directory))
          (call-interactively #'denote-link-or-create)))
    #+end_src

  - Keyboard shortcuts for fluent note-taking/reading

    #+begin_src elisp
      (my--defkeymap
       "my-notes" "C-c n"
       '("n" . denote-open-or-create)
       '("N" . denote-link-or-create)
       '("b" . denote-backlinks)
       '("d" . my--diary-today)
       '("p" . my-crm--open-or-create)
       '("P" . my-crm--link-or-create)
       '("m" . my--micro-post))
    #+end_src

  - Diary

    #+begin_src elisp
      (defun my--find-habit (title)
        "Find the habit with TITLE in current buffer."
        (cl-block 'my--find-habit
          (org-map-entries
           (lambda ()
             (let ((el (org-element-at-point-no-context)))
               (when (and (seq-contains-p (org-get-tags el) "habit" #'equal)
                          (equal (downcase (org-element-property :raw-value el))
                                 (downcase title)))
                 (cl-return-from 'my--find-habit el)))))))

      (defun my--mark-habit-as-done (habit file)
        "Mark HABIT as done."
        (with-current-buffer (find-file-noselect file)
          (org-mode)
          (let ((el (cl-case habit
                      (diary (my--find-habit "write diary entry")))))
            (goto-char (org-element-property :begin el))
            (org-todo 'done))))

      (defun my--diary-today ()
        "Go to today's diary entry."
        (interactive)
        (let* ((diary-habit-file (denote-get-path-by-id "20241231T055317"))
               (denote-directory (expand-file-name "diary" denote-directory))
               (title (format-time-string "%Y-%m-%d")))
          (if-let ((file (seq-find
                          (lambda (f) (string-match-p title f))
                          (directory-files denote-directory))))
              (progn
                (find-file (expand-file-name file denote-directory))
                (goto-char (point-max)))
            (my--mark-habit-as-done 'diary diary-habit-file)
            (denote title '("diary")))))
    #+end_src

  - Work notes

    #+begin_src elisp
      (defun my--workday-notes (prefix)
        "Go to work notes for today plus PREFIX days."
        (interactive "P")
        (let* ((days (if prefix (prefix-numeric-value prefix) 0))
               (denote-directory (expand-file-name "work" denote-directory))
               (date (time-add (current-time) (days-to-time days)))
               (title (format-time-string "%Y-%m-%d" date)))
          (if-let ((file (seq-find
                          (lambda (f) (string-match-p title f))
                          (directory-files denote-directory)))
                   (file (expand-file-name file denote-directory)))
              (progn
                (find-file file)
                ;; Remove any other denotes/work file from agenda
                ;; Assuming that this will always remove older workday files
                (setf org-agenda-files
                      (seq-filter
                       (lambda (file)
                         (not (string-match-p "denotes/work" file)))
                       org-agenda-files))

                (org-agenda-file-to-front file)

                (goto-char (point-max)))
            (denote title '("work") "org" nil title))))

      (my--defkeymap
       "workday" "C-c n w"
       '("w" . my--workday-notes)
       '("i" . on-issue-note-open-or-create)
       '("I" . on-issue-note-link-or-create))
    #+end_src

- dirvish for more powerful dired

  #+begin_src elisp
    (use-package all-the-icons)
    (use-package dirvish)
    (with-eval-after-load 'dirvish
      (dirvish-override-dired-mode)
      ;; (setq dirvish-attributes
      ;;       '(vc-state subtree-state all-the-icons collapse file-size))
      (global-set-key (kbd "C-c f") #'dirvish-fd)
      (define-key dirvish-mode-map (kbd "/") #'dirvish-narrow)
      (define-key dirvish-mode-map (kbd "a") #'dirvish-quick-access)
      (define-key dirvish-mode-map (kbd "f") #'dirvish-file-info-menu)
      (define-key dirvish-mode-map (kbd "y") #'dirvish-yank-menu)
      (define-key dirvish-mode-map (kbd "N") #'dirvish-narrow)
      (define-key dirvish-mode-map (kbd "^") #'dirvish-history-last)
      (define-key dirvish-mode-map (kbd "h") #'dirvish-history-jump) ; remapped `describe-mode'
      (define-key dirvish-mode-map (kbd "s") #'dirvish-quicksort)    ; remapped `dired-sort-toggle-or-edit'
      ;; (define-key dirvish-mode-map (kbd "v") #'dirvish-vc-menu)      ; remapped `dired-view-file'
      (define-key dirvish-mode-map (kbd "TAB") #'dirvish-subtree-toggle)
      (define-key dirvish-mode-map (kbd "M-f") #'dirvish-history-go-forward)
      (define-key dirvish-mode-map (kbd "M-b") #'dirvish-history-go-backward)
      (define-key dirvish-mode-map (kbd "M-l") #'dirvish-ls-switches-menu)
      (define-key dirvish-mode-map (kbd "M-m") #'dirvish-mark-menu)
      (define-key dirvish-mode-map (kbd "M-t") #'dirvish-layout-toggle)
      (define-key dirvish-mode-map (kbd "M-s") #'dirvish-setup-menu)
      (define-key dirvish-mode-map (kbd "M-e") #'dirvish-emerge-menu)
      (define-key dirvish-mode-map (kbd "M-j") #'dirvish-fd-jump))
  #+end_src

- Ledger

  #+begin_src elisp
    (use-package ledger-mode
      :mode "\\.ledger\\'"
      :config
      (setq ledger-default-date-format ledger-iso-date-format))
  #+end_src

- spookfox

  #+begin_src elisp
    (require 'spookfox)
    (require 'spookfox-tabs)
    (require 'spookfox-js-injection)

    (eval-after-load 'spookfox
      (spookfox-start-server))

    (defun my--switch-tab-and-focus ()
      "Switch to browser tab and bring browser in focus."
      (interactive)
      (spookfox-switch-tab)
      (when (eq 'darwin system-type)
        (ns-do-applescript "tell application \"Firefox\"\n\tactivate\n\tend tell")))

    (define-key my-buffers-keymap (kbd "t") #'my--switch-tab-and-focus)
  #+end_src

- saunf
  
  Use the local repo; very risky, should change.

  #+begin_src elisp
    (let ((saunf-dir (expand-file-name "~/code/saunf/src")))
      (when (file-exists-p saunf-dir)
        (add-to-list 'load-path saunf-dir)
        (require 'saunf)))
  #+end_src

- org-noter

  #+begin_src elisp
    (use-package nov
      :mode ("\\.epub\\'" . nov-mode)
      :config
      (setq nov-shr-rendering-functions '((img . nov-render-img) (title . nov-render-title)))
      (setq nov-shr-rendering-functions (append nov-shr-rendering-functions shr-external-rendering-functions)))

    (when (assoc 'powered-by-guix my--feature-flags)
      (require 'org-pdftools)
      (require 'pdf-tools)
      (require 'org-noter))
  #+end_src

- eshell

  I sometimes open eshell buffers. Following kbd actually clears the eshell buffer, which is
  something I like to do in my terminals.

  #+begin_src elisp
    (require 'esh-mode)
    (with-eval-after-load 'esh-mode
      (define-key eshell-mode-map (kbd "C-c M-o")
                  (lambda () (interactive)
                    (eshell/clear-scrollback)
                    (eshell-send-input 
))))
  #+end_src

- Irc

  Small utility to quickly connect to irc.

  #+begin_src elisp
    (use-package erc
      :config
      (setopt erc-modules (seq-union '(sasl) erc-modules))
      (setq
       erc-nick "bitspook"
       erc-auth-source-server-function (lambda (a b)
                                         (auth-source-pass-get 'secret "libera.chat/bitspook"))
       erc-sasl-user :nick
       erc-autojoin-channels-alist '(("libera.chat"
                                      "#commonlisp"
                                      "#emacs"
                                      "#emacs-berlin"
                                      "#clschool"
                                      "#whereiseveryone"
                                      "#guix"
                                      "#lisp"
                                      "#coalton"
                                      ;; just explorin'
                                      "#berlin"
                                      "##deutsch")))
      (setf erc-track-exclude-types '("JOIN" "KICK" "NICK" "PART" "QUIT" "MODE" "333" "353")))

    (use-package erc-track
      
      ;; Prevent JOINs and PARTs from lighting up the mode-line.
      :config (setopt erc-track-faces-priority-list
                      (remq 'erc-notice-face erc-track-faces-priority-list))

      :custom (erc-track-priority-faces-only 'all))
  #+end_src

- Terraform

  #+begin_src elisp
    (use-package terraform-mode
      :config
      (add-hook 'terraform-mode-hook #'terraform-format-on-save-mode))
  #+end_src

- Eww

  #+begin_src elisp
    (setf eww-readable-urls '(".*"))

    (add-hook
     'eww-mode-hook
     (lambda ()
       (display-line-numbers-mode -1)))
  #+end_src

  Syntax-highlighting for code blocks in HTML.

  #+begin_src elisp
    (use-package shr-tag-pre-highlight
      :after shr
      :config
      (add-to-list 'shr-external-rendering-functions
                   '(pre . shr-tag-pre-highlight)))
  #+end_src

  Make EWW buffer niceer with org-outline like features.

  #+begin_src elisp
    (use-package shrface
      :config
      (shrface-basic)
      (shrface-trial)
      (shrface-default-keybindings)         ; setup default keybindings
      (setq shrface-href-versatile t)

      ;; configure eww
      (add-hook 'eww-after-render-hook #'shrface-mode))
  #+end_src

- org-download to easily attach images in my notes

  #+begin_src elisp
    (use-package org-download
      :init
      (setq-default org-download-method 'attach
                    org-download-image-dir (expand-file-name "data" org-directory))
      :config
      ;; Drag-and-drop to `dired`
      (add-hook 'dired-mode-hook 'org-download-enable))
  #+end_src
  
- Ement

  #+begin_src elisp
    (use-package ement)

    (define-key
     my-apps-keymap
     (kbd "e") (define-keymap
                 :parent my-apps-keymap
                 :name "Ement"
                 (kbd "e") #'ement-room-list
                 (kbd "n") #'ement-notifications))
  #+end_src

** System management
:PROPERTIES:
:ID:       e951d611-5827-41d7-b4b4-5f2b1bfcfbef
:END:

- Libretranslate for translating Deutsch from Emacs

  #+begin_src elisp
    (let ((libretrans-dir (expand-file-name "~/code/emacs/libretrans.el")))
      (when (file-exists-p libretrans-dir)
        (add-to-list 'load-path libretrans-dir)
        (require 'libretrans)
        (setq libretrans-instance "http://red:5005/translate"
              libretrans-source "de"
              libretrans-target "en")))
  #+end_src

** Private work related config 
:PROPERTIES:
:ID:       6CA859BF-001A-48A8-8FAF-A522EE7FC8B1
:END:

#+begin_src elisp
  (let ((private-config (expand-file-name "./private.el" user-emacs-directory)))
    (when (file-exists-p private-config)
      (load-file private-config)))
#+end_src

** My workflows
:PROPERTIES:
:ID:       5acc224e-0d3c-4962-8b47-10e319286b9b
:END:

I am experimenting with putting my configuration in blog posts (called "workflows"). These posts
are the source of truth for the configuration, which is tangled to =~/.emacs.d/workflow-*.el=
files. Let's load all of 'em.

  #+begin_src elisp
    (let ((root-workflow (expand-file-name "init-workflows.el" user-emacs-directory)))
      (when (file-exists-p root-workflow)
        (load-file root-workflow)))
  #+end_src

** Finally

- Direnv is pretty essential for my dev workflow.

  #+begin_src elisp
    (use-package envrc
      :hook (after-init . envrc-global-mode))

    ;; for emacs libraries I use within dev-envs created with guix
    (add-hook 'envrc-mode-hook
              (lambda ()
                (setq-local load-path load-path)
                (when (getenv "EMACSLOADPATH")
                  (let ((paths (split-string (getenv "EMACSLOADPATH") ":" t)))
                    (dolist (path (reverse paths))
                      (add-to-list 'load-path path))))

                (when (require 'guix-emacs nil t)
                  (guix-emacs-autoload-packages))))
  #+end_src



#+begin_src elisp
  (message "Took %s to start Emacs" (emacs-init-time))
#+end_src

About

My Emacs configuration

Resources

Stars

19 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors