Emacs

emacs notes

  • toggle-debug-on-quit for stacktraces of hung commands
  • pgtk enabled broadwayd port 8080 with apps env var GDK_BACKEND=broadway (deprecated in GTK 5)
  • magit-generate-changelog for commit msg
  • gnus needs app password from google for 2fa or oauth token from gcloud
  • webkit environment variable(s) for built-in browser
    • WEBKIT_FORCE_SANDBOX="0" is obe use WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS=1
      • maybe set GST_PLUGIN_PATH
  • android port manual entry specifies enabling 'special app permissions' for all files to access /sdcard
    • android port of fdroid is old and branch is unmerged; newer builds are on sourceforge
      • custom build of termux for supporting utils
  • signal-cli integration init signal-cli link --name device_name | head -1 | qrencode --type=UTF8
  • guile scheme port of emacs lisp https://codeberg.org/ramin_hal9001/gypsum/
  • guile-emacs project https://git.hcoop.net/bpt/emacs.git
(use-package! signal-msg ; https://github.com/AsamK/signal-cli
  :commands (signal-msg-new-message) ; jam/signal-msg-rec
  :config (setq signal-msg-username (alist-get 'secret (auth-source-pass-parse-entry "signal/account"))
                signal-msg-number (alist-get 'secret (auth-source-pass-parse-entry "signal/phone")))
  (advice-add 'signal-msg-send :override #'jam/signal-msg-send))

;;;###autoload
(defun jam/signal-msg-send ()
  "Override to use -a account notation and stdin for sending buffer to signal-cli"
  (interactive)
  (let ((exit-code (call-process-region
                    (point-min)
                    (point-max)
                    "signal-cli"
                    nil                                  ; delete
                    nil                                  ; buffer
                    nil                                  ; display
                    "-a" signal-msg-number "send" "--message-from-stdin" signal-msg-dest-number)))
    (if (= exit-code 0)
        (kill-buffer)
      (warn (format "Something went wrong. signal-cli returned %d" exit-code)))))

;;;###autoload
(defun jam/signal-msg-rec ()
  "Reads all json encoded messages from signal-cli into *Signal* buffer"
  (interactive)
  (with-temp-buffer (progn (call-process "signal-cli" nil (current-buffer) nil "--output=json" "receive"); (call-process "cat" nil (current-buffer) nil "signals.json")
                           ;(message "current buffer is %s " (buffer-string))
                           (goto-char (point-min))
                           (unwind-protect
                               (while (not (eobp))
                                 (let* (;(message-json (json-read-file "signals.json"))
                                        (message-json (json-read))
                                        (message-content (alist-get 'envelope message-json ))
                                        (message-from (alist-get 'sourceName message-content))
                                        (message-data (alist-get 'dataMessage message-content))
                                        (message-text (alist-get 'message message-data)))
                                   ;(message "\nFrom: %s\nMessage: %s\n" message-from message-text)
                                   (with-current-buffer (get-buffer-create "*Signal*") (insert (format "\nFrom: %s\nMessage: %s\n" message-from message-text)))))))))
;;;###autoload
(defun jam/guix-env()
  "Guix variables for local guix daemon/client"
  (interactive)
  (require 'xdg)
  (setenv "GUIX_DAEMON_SOCKET" (concat (file-name-as-directory (xdg-data-home)) (file-name-as-directory "guix") (file-name-as-directory "var") (file-name-as-directory "guix") (file-name-as-directory "daemon-socket") "socket"))
  (setenv "GUIX_DATABASE_DIRECTORY" (concat (file-name-as-directory (xdg-data-home)) (file-name-as-directory "guix") (file-name-as-directory "var") (file-name-as-directory "guix") "db"))
  (setenv "GUIX_LOG_DIRECTORY" (concat (file-name-as-directory (xdg-data-home)) (file-name-as-directory "guix") (file-name-as-directory "var") (file-name-as-directory "log") "guix"))
  (setenv "GUIX_STATE_DIRECTORY" (concat (file-name-as-directory (xdg-data-home)) (file-name-as-directory "guix") (file-name-as-directory "var") "guix"))
  (setenv "GUIX_CONFIGURATION_DIRECTORY" (concat (file-name-as-directory (xdg-config-home))(file-name-as-directory "guix") "etc"))
  (setenv "GUIX_LOCPATH" (concat (file-name-as-directory (xdg-data-home)) (file-name-as-directory "guix") (file-name-as-directory "var") (file-name-as-directory "guix") (file-name-as-directory "profiles") (file-name-as-directory "per-user") (file-name-as-directory "root") (file-name-as-directory "guix-profile") (file-name-as-directory "lib") "locale"))
  (setenv "NIX_STORE" (concat (file-name-as-directory (xdg-data-home)) (file-name-as-directory "guix") (file-name-as-directory "gnu") "store")) ; NIX_STORE_DIR
  (setenv "PATH" (concat (getenv "PATH") path-separator (concat (file-name-as-directory (xdg-data-home)) (file-name-as-directory "guix") "bin"))))
(use-package newsticker
  :ensure nil ; built-in
  :commands (newsticker-start newsticker-treeview newsticker-plainview newsticker-stop)
  :bind (:map jam/open ("n" . newsticker-treeview)
         :map jam/toggle ("n" . newsticker-stop)
         :map newsticker-treeview-item-mode-map ("d" . (lambda () (interactive); Download the current newsticker enclosure to tmpdir/newsticker/feed/title
                                                         (let* ((item (newsticker--treeview-get-selected-item))
                                                                (feedname "newsticker")
                                                                (title (newsticker--title item))
                                                                (enclosure (newsticker--enclosure item))
                                                                (download-dir (file-name-as-directory
                                                                               (expand-file-name (newsticker--title (newsticker--treeview-get-selected-item))
                                                                                                 (expand-file-name feedname (expand-file-name "newsticker" temporary-file-directory))))))
                                                           (newsticker-download-enclosures feedname item)
                                                           (message download-dir)))))
  :init
  (setq newsticker-frontend 'newsticker-treeview
        newsticker-dir (concat user-emacs-directory (file-name-as-directory ".local") (file-name-as-directory "cache") "newsticker")
        newsticker-automatically-mark-items-as-old nil
        newsticker-automatically-mark-visited-items-as-old t
        newsticker-url-list-defaults nil
        newsticker-url-list '(("styx" "https://odysee.com/$/rss/@Styxhexenhammer666:2"))))

  • German air control ATC ran on emacs in the 90's
  • calc can plot with gnuplot
    • set yrange [0:50], set xrange [0:2200], plot 4 * sqrt(x + 10) / log10(x + 10) - 4 * sqrt(10)

org-mode notes

  • noweb can link code verbatium with or evaluate it with ()
  • #+INCLUDE "file.org::selector" :only-contents t to select heading contents to include from other files
  • disable tangling with :tangle no on src blocks
(ignore-errors
  (let* ((base-dir (file-name-directory (or load-file-name buffer-file-name)))
         (readme (concat base-dir (file-name-as-directory "..") (file-name-as-directory "..") "README.org"))
         (readme-time (file-attribute-modification-time (file-attributes readme)))
         (emacs-org (concat base-dir "emacs.org"))
         (emacs-org-time (file-attribute-modification-time (file-attributes emacs-org))))
    (if (time-less-p emacs-org-time readme-time); set timestamp of emacs.html when readme.org has newer edits
      (set-file-times (concat base-dir "emacs.html") (time-add readme-time (seconds-to-time 1))))))

emacs config notes

  • early-init will use guix-home packages if emacs package directory exists inside it otherwise uses use-package to download
  • Pure elisp instead of makefiles and shell scripts
    • sesquicolon 'shebang'
    • build system
      • runs any untangled elisp scripts
      • sets timestamps
      • exports custom elements for html/org-roam and expands INCLUDE directives
      • build site
        • git worktree
        • sitemap self untangles an org document
          • site search uses sqlite fts index and stores the db in opfs for reuse
        • copy js, css, png, sqlite db, etc by timestamp
          • migrated css from custom hand rolled to bulma then to pico for better defaults and less configuration
        • navbar contains index/sitemap links
          • INCLUDE in each page for css/navbar
      • wraps couple common commands
  • org roam dialies are a date tree journal
  • tangled files set own timestamps in scripts
  • defaults copied from doom/spacemacs when dropped
  • all keys bounds under C-c c
  • prefer defaults when possible
  • prefer eshell for tramp traversal
  • gnus over newsticker as more backends and own the frame
  • prefix named interactive commands over inline functions for M-x and binds
  • added mouse clicking to command buffer
    • special case handling of Files:
  • android support with termux build
  • save files locally under .local/cache and .local/etc
  • do not hardcode paths and use xdg
  • tricks to speed up init
    • defer package loading with use-package
    • skip packages at startup
    • add memory for skipping gc
    • unbinding file-handler matching
    • native code
    • package-quickstart
    • autoload functions
    • dump saves a little time
    • after-init hooks
    • setq inhibits-*
    • disable frame features without loading
  • gc with idle timer
  • cua for copy/paste
  • xterm/broadwayd/daemon support
  • transparent background
  • extra mouse support
  • sound alert
  • packages in README for extras
    • functions under M-x jam/ prefix
      • sudo-edit
      • auth-display
        • vendored totp implementation for authinfo password storage
      • draw
      • eshell
      • save-all (buffers)
      • mpv-play
      • screenshot
        • image-crop is a bind
        • ffmpeg for resize ffmpeg -i cropped.png -vf "scale=1280:720" scaled.png
      • replace unicode
        • ZERO WIDTH NO-BREAK SPACE(BOM), ZERO WIDTH SPACE, RIGHT-TO-LEFT MARK, RIGHT-TO-LEFT OVERRIDE, LEFT-TO-RIGHT MARK, OBJECT REPLACEMENT CHARACTER
      • compile-init-bytecode

emacs-config README

This configuration is for emacs >= 30

Third party packages:

guix: package/environment management. alternative is cli and setting exec-path-from-shell/environment

eat: graphical terminal emulation. alternative is built-in multi-term w/o visual commands

magit/forge/code-review: git/forge manipulation. alternative is built-in vc that lacks complex workflows like interactive rebase, forge pull requests or code review comments

debbugs: track emacs/guix bugs. alternative is bookmarking websites/mailing lists

transmission: manage torrent client. alternative is cli

org-roam: tag system for note database. alternative is org-node or self devised naming/search scheme with a datetree journal

undo-tree: undo edit persist restarts. alternative is built-in undo with session persistence redone

minions-mode: keeps the modeline visually brief with retained information. alternative is diminish/delight packages and config for each mode

osm: Map tile viewer. alternative is web browser viewing

gptel: interface with llms. alternative is llm package or cli

dape/geiser-guile/pyvenv/(rust,yaml,python)-tree-sitter: language specific modes with more than regex.

combobulate?: structural editing. alternative is regex

eglot-x?: rust-analyzer extensions for memory layout and structural-search-replace. alternative is to copy the functions

atomic-chrome?: counterpart to https://github.com/KarimAziev/chrome-emacs for using emacs to edit browser buffers. alternative is copy-paste

Clone

Branch names diverging and "." complicate fetching the correct heads.

git clone --single-branch --recurse-submodules --shallow-submodules --depth=1 git@github.com:jamartin9/emacs-config.git
# get branch heads
git submodule foreach 'git checkout master'

Tangle Install

Emacs untangles the script from the org file (using noweb to copy/eval code) while expanding INCLUDE directives. Tangling is rerun when the org file is newer than the script (if present) and the html file. Cleanup is done via the clean arguments to the make-el wrapper. The wrapper is needed for org-roam, org-ids and html export tags.

pwsh make-el.ps1 install

Link

Installable org files tangle to a script of the same name for installation. The script needs itself and emacs on the PATH or CWD.

(ignore-errors
      (make-symbolic-link (file-name-directory (or load-file-name buffer-file-name)) (concat (file-name-as-directory (if (getenv "XDG_CONFIG_HOME") (getenv "XDG_CONFIG_HOME") (concat (file-name-as-directory (getenv "HOME")) ".config"))) "emacs")))

Run

The one line org-mode sesquicolon 'shebang' is a multipart shell/powershell wrapper. Passed arguments to the elisp script are available by argv (ignoring “–” and “$@”). To run with powershell use the same wrapper format with a .ps1 extension.

":"; emacs -Q --script README.sh -- $@ $args ; exit $? # -*- mode: emacs-lisp; lexical-binding: t; -*-

Cherry Picking

Copy files by url with emacs

(url-copy-file "https://raw.githubusercontent.com/jamartin9/emacs-config/master/init.el" "init.el")
(url-copy-file "https://raw.githubusercontent.com/jamartin9/emacs-config/master/early-init.el" "early-init.el")
(call-process "git" nil t nil "clone" "https://github.com/jamartin9/emacs-config")

Site

Clone the site

git worktree add --track -b gh-pages ./gh-pages origin/gh-pages

Build the updated site

pwsh make-el.ps1 site