config.org 34 KB

#+TITLE:Doomed Chicken

Preface

This is another attempt at making a decent Emacs configuration for the chicken I am. Having multiple files with a poor consistence or a directed goal always bit me in the back relatively quickly. So this time, I'll try to write something documenting my process to warn my future me about my current configuration mishaps.

Because I'm trying to make this setup my daily driver for personal programming and writing, I'm going to list here the programming/writing languages I might use in the future with that setup, so expect this configuration to be revolving around this list:

  • Python
  • Lua
  • Markdown

    • CSS/HTML (to tweak my blog's skeleton)
  • Org
  • Bash (eventually zsh)
  • and of course, emacs-lisp

TODO Packages

I heard I could manage my packages.el file directly from here. So why not doing it?

The file header

First, the header so Doom shouldn't byte-compile this file. It'd make the updates harder due to requiring a manual doom build invocation for every change done. I'm directly stealing it from the example provided by Doom Emacs.

;; -*- no-byte-compile: t; -*-
;;; .doom.d/packages.el

TODO The packages

Vterm

It's rather solid for a terminal emulator. Too bad it doesn't seem to work on Windows. (Actually I haven't tried so hard, I just don't always have the time to find a way to compile it)

(package! vterm :ignore (eq system-type 'windows-nt))

Graphviz

Making graphs is fun

(package! graphviz-dot-mode)

Python pacakges

Python X allows me to have a notebook-ish workflow. More about this in its configuration section

(package! python-x)

Ob-async

Not having emacs be stuck when doing heavier stuff seems great. Let's use that.

(package! ob-async)

Mixed-Pitch

On some computers I have Iosevka installed and using both Iosevka Extended and the regular version is neat.

(package! mixed-pitch)

Ascii-art-to-Unicode

I love using box drawing characters, so using them when possible is cool. Also it makes me use emacs as a drawing tool as it helps avoiding looking up charmap for the right character all the time.

Note that with evil, digraphs are back.

(package! ascii-art-to-unicode)

TODO Buffer-local variables to make stuff easily tweakable

Helper functions

I had issues before dealing with specific patterns at configuration time. At this moment, I don't have all the packages nor do I want to deal with them manually. So I'm reinventing the wheel at some places to make the configuration easier to use.

A filter function

I haven't found one bundled with emacs yet. I'm using a homebrew filter function directly taken from a cookbook page here so I can skip requiring cl or seq at launch.

(defun chicken/filter (condp lst)
  (delq nil
        (mapcar (lambda (x) (and (funcall condp x) x)) lst)))

TODO Detect Dark Mode

Because why not? I'm planning to use that around launch to toggle between two themes but I worry about the actual boot time impact.

Currently untangled because it doesn't serve any use for now.

(defun chicken/uses-dark-mode-p()
  (let ((windows-get-mode-command "powershell \"Get-ItemPropertyValue -Path HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize -Name SystemUsesLightTheme\""))
    (cond
     ((eq system-type 'windows-nt)
      (string= "0\n" (shell-command-to-string windows-get-mode-command)))
     (t (message "Ok")))))

Measure time of a function call

Based on a code found here. It's not really used here yet, but it could be useful to diagnose long configuration setting in my configuration.

(defmacro chicken/measure-time-m (&rest body)
  "Measure the time it takes to evaluate BODY."
  `(let ((time (current-time)))
     ,@body
     (message "%.06f" (float-time (time-since time)))))

Look and Feel

The colors

Lately, I have been partial towards Gruvbox, for multiple reasons, the first being that I was tired of all those blue themes. It's a warm colorscheme, it has a good contrast in most of the situations and I built my whole's old laptop UI based on Gruvbox. Good thing Doom Emacs has a preset for this specific colorscheme.

(setq doom-theme 'doom-gruvbox)

(add-to-list 'custom-safe-themes
             ;; Gruvbox-light
             "ff984792cce90b274d29d59e63c8a51ce91ff0ab01d6504f7d407ef545aa1a82"
             ;; Gruvbox-dark
             "8141c11fa933b60d4ae77bd74187d7ff8a510c3237515a68c79cf997277bd3b4"
             )

The fonts

The other basic configuration I usually do first too is setting a font. PragmataPro has been my newest typographic friend as it's really good for what it's made for: displaying text in a flexible yet consistent way. Wait, I just described fonts. Yet, Pragmata is neicely designed and made in a way I'm still discovering little details after weeks of daily usage, like properly managing combining characters like F⃣. I really like those small touches and of course the mighty ligatures.

On the other hand, I don't always want to install all the fonts everywhere, so let's build first a font selector so I can gracefully fallback on other fonts when needed.

(defun chicken/select-first-available-font (fonts-to-select)
  (let ((available-fonts (font-family-list)))
    (car (chicken/filter (lambda (elt) (member elt available-fonts)) fonts-to-select))))

Now the font proper selection

(let ((base-size 16)
      (code-font (chicken/select-first-available-font
                  '("PragmataPro Liga"
                    "PragmataPro"
                    "Iosevka"
                    "Iosevka SS08"
                    "InputMonoCompressed"
                    "Consolas"
                    "Courrier New")))
      (variable-font (chicken/select-first-available-font
                      '("PragmataPro Liga"
                        "PragmataPro"
                        "Iosevka Extended"
                        "Iosevka SS08 Extended"
                        "InputSansCompressed"
                        "Segoe UI")))
      (unicode-font (chicken/select-first-available-font
                     '("PragmataPro Liga"
                       "PragmataPro"
                       "Segoe Color Emoji"
                       "InputMonoCompressed"
                       ;;"Iosevka SS08 Light"
                       "Iosevka SS08"
                       "Iosevka"
                       "Segoe UI")))
      (serif-font (chicken/select-first-available-font
                   '("Iosevka Slab"
                     "InputSerifCompressed"
                     "Courrier New"
                     "Courrier New"))))
  (setq doom-font
        (font-spec :family code-font :size base-size)
        doom-variable-pitch-font
        (font-spec :family variable-font :size base-size)
        doom-unicode-font
        (font-spec :family unicode-font :size base-size)
        doom-big-font
        (font-spec :family code-font :size (* base-size 2))
        doom-serif-font
        (font-spec :family serif-font :size base-size)))

A note about the serif font: I can't set the styling set for a given font on emacs, so I can't use Pragmata for the serif font. I'm currently having issues with fixed-pitch-serif as it doesn't properly load the doom-font, so I am resorting to use a custom serif font, Iosevka Slab, from the Iosevka forge, my other favorite programming font. Actually, they blend well together, even if Iosevka Slab feels ever so slightly too thick against Pramgata.

PragmataPro's ligatures have been enabled within init.el though the pretty-code layer and it's optional specialization for this font. No more setting is to be done for this font. Yet I wonder if I want to determine how to add specific replacements (like lambda → λ ).

TODO Implement a font switch on light/dark theme switch

Gruvbox-light doesn't need a font as think as its dark alternative.

TODO Visual tweaks

(custom-set-faces!
  '(line-number nil :slant 'italic)
  '(org-quote nil :inherit 'fixed-pitch-serif))

Tweaks

Window borders

Nothing really fancy yet, I'm content with how Doom looks right now. In the future I might add something to change the windows' border colors. Window dividers (located in the window-divider group) are inheriting from the vertical-border face, so editing the later allows me to quickly change the border color if needed.

(custom-set-faces!
  '(vertical-border :foreground "red"))

STRT Doom Modeline

I'm trying to slightly tweak the modeline to show some info like the word count and forcing the usage of icons for situations where I use emacs in daemon mode, which occured quite often on Windows with Spacemacs, due to the latter's increased boot time.

I'm disabling the font icons when one of the fonts (here GitHub's) isn't present so I can directly fallback on the unicode symbols. Allows me to tone down the visual noise.

(after! doom-modeline
  (if (member "github-octicons" (font-family-list))
      (progn
        (setq doom-modeline-icon t)
        (setq doom-modeline-major-mode-icon t))
    (progn
      (setq doom-modeline-icon nil)
      (setq doom-modeline-unicode-fallback t)))

  ;; FIXME What does it lack to work?
  ;;(setq doom-modeline-enable-word-count t)
  ;;(setq doom-modeline-continuous-word-count-modes
  ;;      '(markdown-mode gfm-mode org-mode))

  ;; HACK @work No really need right now to hack with encoding yet.
  (setq doom-modeline-buffer-encoding nil))

TODO Top-modeline

Inspired from Elegant-Emacs.

Currently disabled until I get it done properly. It suffers from messy code and bad performance.

(after! doom-themes
  (custom-set-faces!
    `(header-line
      :inherit 'variable-pitch
      :underline t
      :foreground ,(doom-color 'base6))))


(defun staging/reload-header-line-theme()
  (setq chicken--header-line-width
        (window-font-width (selected-window) 'header-line)))
(staging/reload-header-line-theme)
(define-key mode-line-major-mode-keymap [header-line]
  (lookup-key mode-line-major-mode-keymap [mode-line]))

(add-hook 'doom-load-theme-hook 'staging/reload-header-line-theme)

(defun mode-line-render (left right)
  (let* ((available-width (-
                           (/
                            (window-width (selected-window) t)
                            chicken--header-line-width)
                           (length left) )))
    (format (format "%%s  %%%ds" available-width) left right)))
(setq-default header-line-format
              '((:eval
                 (mode-line-render
                  (format-mode-line (list
                                     (propertize "☰" 'face `(:inherit mode-line-buffer-id)
                                                 'help-echo "Mode(s) menu"
                                                 'mouse-face 'mode-line-highlight
                                                 'local-map   mode-line-major-mode-keymap)
                                     (propertize " %b " 'face `(:underline nil :foreground ,(doom-color 'base7)))
                                     (if (and buffer-file-name (buffer-modified-p))
                                         (propertize "*" 'face `(:inherit header-line)))))
                  (format-mode-line
                   (propertize "%4l:%2c  " 'face `(:inherit header-line)))))))
(setq-default mode-line-format nil)

TODO Hide line numbers for org

(after! org
  (add-hook 'org-mode-hook (lambda () (setq-local display-line-numbers nil))))

Python

Python-x

Python-x allows me to have the same cell-based workflow than on Jupter or Spyder. I'd like also to use similar separators to what I currently use (# %%).

(after! python-x
  (python-x-setup)
  (setq python-section-delimiter "# %%"))

Org-Mode

STRT Org location

I have usually two locations for my own note repository. On Windows, I like to put it in a folder located in the root folder of a hard drive while on Linux, I prefer having it at the root of my home folder. This prevents having spaces in the base folder path.

Right now, the Windows' version is relatively bare, I only have setups where I use C: or D: as drives to store my notes folder, directly on the root, but maybe in the future I'd like to move it, so I should plan to support future configurations later.

(let ((base-folder-name "Notes"))
  (when (eq system-type 'windows-nt)
    ;; HACK I usually have two disk drives
    (if (file-directory-p (concat "C:/" base-folder-name))
        (setq org-directory (concat "C:/" base-folder-name))
      (setq org-directory (concat "D:/" base-folder-name))))
  (when (eq system-type 'gnu/linux)
    ;; TODO Factorize with let
    (let ((notes-folder (concat
                         (file-name-as-directory (getenv "HOME"))
                         base-folder-name)))
      (if (file-directory-p notes-folder)
          (setq org-directory notes-folder)
        (message "Can't find the org note folder.")))))

TODO Org Bullets

Doom's default bullets, using org-superstar, for org doesn't work perfectly in Windows. Let's use another set that'll work on my computers.

(setq org-superstar-headline-bullets-list '("■" "▩" "▦" "▨" "▤" "□"))

The new one

(setq org-superstar-headline-bullets-list '("⧫"))

TODO Todo lists

I'd like to track when I finish tasks. I'm not feeling that adding a note each time I close an item might be the best thing as I'd get tired of the prompt really quickly. I'll reserve this to manual additions.

(setq org-log-done 'time)

TODO Org Brain

Here will be located a routine to determine which folder we want to write the default brain(s) down.

;; TODO proper folder
(when (eq system-type 'windows-nt)
  (add-hook! org-brain
  (setq org-brain-path "c:/Notes/Work/Brain")))

A small fix to avoid org-brain's key bindings be overwritten by evil.

(after! org-brain
  (evil-set-initial-state 'org-brain-visualize-mode 'emacs))

Working icon display with all-the-icons to have a small icon before various kind of resource links.

(after! org-brain
  (defun org-brain-insert-resource-icon (link)
    "Insert an icon, based on content of org-mode LINK."
    (let ((starts_with_http (string-prefix-p "http" link)))
      (insert (format "%s "
                      (cond
                       ;; File extensions

                       ;; TODO use string-suffix-p everywhere or string-match?
                       ;; The latter might give wrong results on filenames with
                       ;; multiple extensions.
                       ((string-suffix-p ".pdf" link)
                        (all-the-icons-octicon "file-pdf"))
                       ((string-match "\\.cpp" link)
                        (all-the-icons-alltheicon "cplusplus"))
                       ((string-match "\\.cs" link)
                        (all-the-icons-alltheicon "csharp"))
                       ((string-match "\\.java" link)
                        (all-the-icons-alltheicon "java"))
                       ((string-match "\\.lua" link)
                        (all-the-icons-fileicon "lua"))
                       ;; [TODO] This will break http file links to header files.
                       ;; Use instead a more clever regex match
                       ((and (string-match "\\.h" link)
                             (not starts_with_http))
                        (all-the-icons-alltheicon "cplusplus-line"))
                       ((string-match "\\.hpp" link)
                        (all-the-icons-alltheicon "cplusplus-line"))
                       ;; URL
                       (starts_with_http (cond
                                          ((string-match "wikipedia\\.org" link)
                                           (all-the-icons-faicon "wikipedia-w"))
                                          ((string-match "app\\.nuclino\\.com" link)
                                           (all-the-icons-fileicon "brain"))
                                          ((string-match "github\\.com" link)
                                           (all-the-icons-octicon "mark-github"))
                                          ((string-match "vimeo\\.com" link)
                                           (all-the-icons-faicon "vimeo"))

                                          ((string-match "youtube\\.com" link)
                                           (all-the-icons-faicon "youtube"))
                                          ((string-match "bitbucket\\.org" link)
                                           (all-the-icons-faicon "bitbucket"))
                                          ((string-match "stackoverflow\\.com" link)
                                           (all-the-icons-faicon "stack-overflow"))
                                          ((string-match "twitter\\.com" link)
                                           (all-the-icons-faicon "twitter"))
                                          ((string-match "reddit\\.com" link)
                                           (all-the-icons-faicon "reddit"))
                                          (t (all-the-icons-faicon "globe"))))
                       ((string-prefix-p "brain:" link)
                        (all-the-icons-fileicon "brain"))
                       (t (all-the-icons-icon-for-file link)))))))
    (add-hook 'org-brain-after-resource-button-functions #'org-brain-insert-resource-icon))

TODO Convenience narrow keybindings

Here's some keybindings vaguely inspired from my experience with Spacemacs.

(map! :mode org-mode
      :leader
      ;; Narrow
      :desc "narrow" "mN" nil
      :desc "Narrow to subtree" "mNs" #'org-narrow-to-subtree
      :desc "Toggle subtree narrowing" "mNt" #'org-toggle-narrow-to-subtree
      :desc "Narrow to element" "mNe" #'org-narrow-to-element
      :desc "Narrow to element" "mNb" #'org-narrow-to-block
      :desc "Widen" "mNw" #'widen)

TODO Org-brain

TODO Ascii-art-to-unicode decoration

This snippet, slightly edited from the code available on org-brain's repository page uses ascii-art-to-unicode to draw the inheritance diagram with box drawing characters.

(after! org-brain
  (defface aa2u-face '((t . nil))
    "Face for aa2u box drawing characters")
  (advice-add #'aa2u-1c :filter-return
              (lambda (str) (propertize str 'face 'aa2u-face)))
  (defun aa2u-org-brain-buffer ()
    (let ((inhibit-read-only t))
      (make-local-variable 'face-remapping-alist)
      (add-to-list 'face-remapping-alist
                   '(aa2u-face . org-brain-wires))
      (ignore-errors (aa2u (point-min) (point-max)))))
  (add-hook 'org-brain-after-visualize-hook #'aa2u-org-brain-buffer))

TODO Org-Agenga

TODO French day names

(after! org
  (setq calendar-week-start-day 1
        calendar-day-name-array ["Dimanche" "Lundi" "Mardi" "Mercredi"
                                 "Jeudi" "Vendredi" "Samedi"]
        calendar-month-name-array ["Janvier" "Février" "Mars" "Avril" "Mai"
                                   "Juin" "Juillet" "Août" "Septembre"
                                   "Octobre" "Novembre" "Décembre"]))

Blog writing

I have a blog. I want to use emacs as my platform to type bits and bobs on it.

Org mode is awesome, that's a fact. Minus the fact that I have to deal with a few weirds things around the UI, I love each instant I handle notes or todos with that mode. I tried to look into making org my blog framework/builder but I couldn't do the jump for many reasons, the most important ones were about having to do the templates for org, another one being having to convert Markdown blog posts. I switched to Hugo and extracted my blog files from the v3's database into markdown files.

Now I'll just enjoy the blog writing from a Hugo-powered blog git repo. The v4 is out for a few weeks now and I feel content with the workflow I'm having with it right now. But we can do better.

Draft jump list

Hugo has a few utilities, notably listing draft posts. I wrote a while ago a function to list drafts and feed them to helm. Doom Emacs seems to use ivy as file browser, so I had to rewrite one to feed Ivy with my draft list. Here's how it works. First we have a function that calls Hugo's draft listing based on the project's directory.

(defun chicken/list-hugo-drafts ()
  "Fetches the current drafts in a hugo project when available.
Assumes the current project is a Hugo one."
  (let
      ((root (projectile-project-root)))
    (counsel--split-string
     (counsel--call
      (list "hugo" "-s" root "list" "drafts")))))

Then I have an interactive function I can call that will process the list given by list-hugo-drafts and either opens the file or does nothing if the user cancels the search. Something to note here is how I have to rebuild the whole path based on the project's root. If the user was in another folder, which can happen for instance when opening another file, the action callback would have tried to open the selected file relatively to the current folder instead of the project folder. There is also a check to detect first the presence of a config file before attmepting to invoke hugo to give a sensible message.

;;;###autoload
(defun chicken/hugo-goto-draft ()
  "Opens an ivy-powered search helper to quickly jump on a draft
if the current project is the root of a Hugo-powered site."
  (interactive)
  (if (file-exists-p! "config.toml" (projectile-project-root))
      (ivy-read "Open a draft: " (chicken/list-hugo-drafts)
                :require-match t
                :history 'chicken/hugo-goto-history
                :action (lambda (file)
                          (with-ivy-window
                            (when file
                              (let*
                                  ((root (projectile-project-root))
                                   (full-file (concat root file)))
                                (find-file full-file)))))
                :unwind #'counsel-delete-process
                :caller 'chicken/hugo-goto-draft)
    (message "The current project doesn't have a config.toml in the root directory.")))

PROJ Convert GIF to videos

I like GIFs, that's a fact. But neither your bandwidth nor your CPU will appreciate them. It's past 2020 and GIFs are one of the most known ways to share a small video but it is one of the heaviest way one could do on the internet. Why keep continuing using it, except for pixel perfect (256) colors when most of the time a little encoding will lose little details but a lot of filesize?

I have a recipe to convert GIFs (and anything video related) into webm or mp4 with the help of FFMPEG, here it is.

# base
ffmpeg -i input-file.mp4 -c:v libvpx -crf 10 -b:v 1M -c:a libvorbis output-file.webm
# This worked not so badly on voxathrone gif
# ---
# auto-alt-ref is needed because the webm format will throw a tantrum due to
# transparency in the gif.
ffmpeg -i .\collision_benchmark.gif -c:v libvpx -crf 10 -auto-alt-ref 0 -b:v 1M -an .\collision_benchmark.webm

It might be nice to have some functions in emacs to convert a picture when working on a post. I'm still randomly trying to figure what I need, but here what I have for now:

  • Codec selection (vpx, mp4)
  • Sane quality presets
  • Customizable variables (crf quality for MP4, crf quality and video bandwidth for webm)
  • Detect when working on a post to automatically filter the compatible pictures

    • I'm usually working with an "images" folder. That could be a thing to detect.
    • I don't know how it works, but I could detect the {{< video >}} template so I can extract the link to the file and locate the file.
    • Meanwhile I can still only list gifs in the content folder.
  • GPU codec selection?

Of course, an automatic script could help too, but I think this is kind of situation where I prefer handling myself the conversion as I might still need GIFs in some situations.

DONE A first version

I somehow found out how to make a first usable function that allows me to quickly find a gif in any post and convert it. There is no smarter filtering or a way to ask for quality parameters, but it's already usable as is. Looking back to the earlier comment block, we can figure an argument listing which will be apply'd to the process creation routine

(defun chicken/mp4-command (in-file out-file)
  "Returns a list composed of my default command to make MP4 out
of gifs."
  (list "ffmpeg" "-i" in-file
        "-crf" "10"
        "-pix_fmt" "yuv420p"
        "-y"
        out-file))

(defun chicken/webm-command (in-file out-file)
  "Returns a list composed of my default command to make WEBM out
of gifs. This one was found by experiment. The ~auto-alt-ref~
argument was found required because ffmpeg would fail with GIFs
with alpgha channels otherwise."
  (list "ffmpeg" "-i" in-file
        "-c:v" "libvpx"
        "-crf" "10"
        "-b:v" "1M"
        "-auto-alt-ref" "0"
        "-c:a" "libvorbis"
        "-y"
        out-file))

Given a path to a .gif file, it's relatively easy to derive the location of the video files and to run the processes with the precedent part. It's good to note that currently there is no way to guess the result value nor the errors except by checking the processes' buffers. This is something to work on but for now, it'll do. I don't know if the ivy window wrapper is pertinent as the processes run in their own buffer.

(defun chicken/process-gif(file)
  "Given a .gif file, calls ffmpeg twice to convert once in a
webm and once in a mp4."
  (with-ivy-window
    (when file
      (let* ((default-directory (projectile-project-root))
             (input-file file)
             (noext-file (file-name-sans-extension file))
             (mp4-file (concat noext-file ".mp4"))
             (webm-file (concat noext-file ".webm"))
             (mp4-command-line (chicken/mp4-command input-file mp4-file))
             (webm-command-line (chicken/webm-command input-file webm-file))
             (mp4-process-args (-concat '("ffmpeg") '("*chicken/convert-mp4*") mp4-command-line))
             (webm-process-args (-concat '("ffmpeg") '("*chicken/convert-webm*") webm-command-line)))
        (start-process "pwd" "*pwd*" "pwd")
        (apply 'start-process mp4-process-args)
        (apply 'start-process webm-process-args)))))

Then comes the counsel-powered wrapper. I haven't made it as resilient as the draft lister, but for now it just wraps over one of counsel's built-in file listers to filter it and only keep the .gif located in the content folder. Simple but rather effective.

(defun chicken/counsel-filter-gif(regex candidates)
  "Wraps counsel--find-file-matcher to filter files to only keep
gifs located in the post's folder."
  (let ((filtered (chicken/filter
                   (lambda (file)
                     (and (s-ends-with-p ".gif" file t)
                          t))
                   candidates)))
    (counsel--find-file-matcher regex filtered)))

;;;###autoload
(defun chicken/convert-post-gif()
  "Askes the user for a gif located in the content folder to
convert it automatically into mp4 and webm."
  (interactive)
  (ivy-read "The GAME:" (counsel--find-return-list
                         counsel-file-jump-args)
            :matcher #'chicken/counsel-filter-gif
            :require-match t
            :history 'chicken/convert-post-gif-history
            :action #'chicken/process-gif
            :unwind #'counsel-delete-process
            :caller 'chicken/convert-post-gif))

TODO A shortcut mapping

I would like to make a shortcut to launch the conversion function to quickly get things done. I need to find a proper wrapper for that and to read more Doom Emacs' documentation to figure how and where to scope the mapping.

(map! (:when
        (functionp 'chicken/search-everything))
      :leader
      :desc "Everything" "sE" #'chicken/search-everything)

PROJ Character/sequences insertion

STRT Basic principle

;; Note, I could build the list by concating the character's unicode name and itself.

;; The variable that'll contain the character maps. A alist binding a symbol to
;; an alist of name <-> symbols. Sadly, I couldn't figure how to avoid having
;; character names to be strings. So the mapping will be a bit less readable for
;; now.
(setq chicken/unicode-helper-map '(
                                   (misc . (("link" . )))))

(defun chicken/get-character-map (id)
  (cdr (assoc* id chicken/unicode-helper-map)))

(defun chicken/get-character-from-map (character-map-id)
  """Wrapper over a simple ivy-read call to automatically select
the proper character map to list."""
  (let* ((map (chicken/get-character-map character-map-id))
         (entry (ivy-read
                 "Prompt:"
                 map
                 :require-match t)))
    (message (symbol-name (cdr (assoc entry map))))))

(defun chicken/insert-user-character (id)
  """Another level of wrapper. This one should be pretty much
useable as-is as an user-editable replacement of insert-char
after giving an argument."""
  (insert-char
   (string-to-char
    (chicken/get-character-from-map id))))

(defun chicken/create-user-character-inserter (id)
  (lexical-let ((inner-id id))
    #'(lambda () (interactive) (chicken/insert-user-character inner-id))))

(map! (:when (boundp 'lexical-let)
       :leader ; Use leader key from now on
       :desc "Misc character insert" "iM" (chicken/create-user-character-inserter 'misc)))

TODO Kaomoji shortcut mapping

Windows' emoji panel is great but not convenient enough when focusing on the keyboard. Let's add a mapping to quickly insert various kaomojis.

(map! (:leader ; Use leader key from now on
       :desc "Kaomoji" "iK" nil
       :desc "Shrug" "iKs" (lambda ()(interactive) (insert "¯\\_(ツ)_/¯"))
       :desc "Shrug (clip)" "iKS" (lambda ()(interactive) (kill-new "¯\\_(ツ)_/¯"))))

I have further questions adressed to myself regarding the mental sanity behind such feature.

TODO Pragmata's own characters

Unsorted Functions

Search Everything with Everything

Everything is a very useful tool for Windows that indexes all the available in the system and provides a insanely fast search popup to directly find the file you want based on either globbing or regexes. On Windows, I just can't live without it. It also provides a command line utility designed to search on a console but also works very well as part of a scripting toolbox.

Inspired by a friend's desire to use it on his own Emacs setup, I wrote a small function based on Ivy to let me jump on any file with the command line version. First, the function that will feed ivy-read. It assumes es.exe is in your PATH.

(when (eq system-type 'windows-nt)
  (defun chicken/counsel-es-function (str)
    (or
     (ivy-more-chars)
     (progn
       (counsel--async-command
        (format "es.exe %s" str))
       '("" "working...")))))

Then the search function. It is defined when the executable es.exe can be found in your PATH environment variable. The function is largely inspired by Ivy's own examples.

(when (and
       (eq system-type 'windows-nt)
       (executable-find "es.exe")
       (featurep! :completion ivy))
  (defun chicken/search-everything ()
    "Call the \"es\" shell command.
INITIAL-INPUT can be given as the initial minibuffer input."
    (interactive)
    (ivy-read "Everything: " #'chicken/counsel-es-function
              :dynamic-collection t
              :require-match t
              :history 'chicken/search-everything-history
              :action (lambda (file)
                        (with-ivy-window
                          (when file
                            (find-file file))))
              :unwind #'counsel-delete-process
              :caller 'chicken/search-everything )))

Finally, let's set up a shortcut to directly search a file globally with that tool. I'm going to map it on <leader> s E, as I hope it won't be used in the near future by Doom Emacs' default configuration. The mapping makes sure that you have the executable available by checking if the function to be called is available.

(map! (:when
        (functionp 'chicken/search-everything)
      :leader
      :desc "Everything" "sE" #'chicken/search-everything))

Unsorted Tweaks

Treat the underscore as a word character

According to Doom Emacs' FAQ, the Evil mode considers the underscore as a word delimiter because that's the default Emacs behiavour, not following vim's own motion. To fix this, there is the possibility of changing the underscore's entry in the syntax table:

(modify-syntax-entry ?_ "w")

But again, the FAQ suggests using hooks to limit the tweak to specific modes.

(defun chicken/alter-underscore-entry()
  (modify-syntax-entry ?_ "w"))

(add-hook! (python-mode lua-mode markdown-mode) #'chicken/alter-underscore-entry)