Compare commits
6 commits
1250e152c6
...
ef61ce1e37
Author | SHA1 | Date | |
---|---|---|---|
|
ef61ce1e37 | ||
|
fcb1fdb11d | ||
|
631bdecab1 | ||
|
cc84bff616 | ||
|
14e023c730 | ||
|
2cd9a78e29 |
12 changed files with 511 additions and 82 deletions
22
README.org
22
README.org
|
@ -1,8 +1,8 @@
|
|||
#+title: My Emacs Configuration
|
||||
#+author: Howard X. Abrams
|
||||
#+date: 2021-11-01
|
||||
#+filetags: emacs readme
|
||||
#+startup: inlineimages
|
||||
#+TITLE: My Emacs Configuration
|
||||
#+AUTHOR: Howard X. Abrams
|
||||
#+DATE: 2021-11-01
|
||||
#+FILETAGS: emacs readme
|
||||
#+STARTUP: inlineimages
|
||||
** Introduction
|
||||
I’ve crafted my Emacs configuration, I cheekily call /hamacs/, in a literate programming model, heavily inspired by my recent journey into [[https://www.youtube.com/watch?v=LKegZI9vWUU][Henrik Lissner's]] [[https://github.com/hlissner/doom-emacs][Doom Emacs]] and [[https://www.spacemacs.org/][Spacemacs]]. While I used both extensively, I decided I would /roll my own/ as Emacs people like myself, tend to be /control freaks/ (at least a little bit).
|
||||
|
||||
|
@ -12,7 +12,7 @@ Using [[https://howardism.org/Technical/Emacs/literate-devops.html][literate pro
|
|||
|
||||
I’ve separated my configuration into /chapters/ around particular subjects, applications and programming languages. This feature allows you, dear reader, to jump our to items of interest, but allows me to /selectively load/ individual chapters. For instance, if I’m not doing much with Ruby at work, I can remove that chapter from the list in my [[file:bootstrap.org::*Load the Rest][bootstrap]]. I also don’t load my [[file:ha-display.org][UI configuration]] when I am using the Terminal (doesn’t happen much, actually).
|
||||
|
||||
Hit me up with questions on Mastodon: [[https://emacs.ch/@howard][@howard@emacs.ch]].
|
||||
Hit me up with questions on Mastodon: [[https://emacs.ch/@howard][@howard@pdx.social]].
|
||||
|
||||
If you want to try the entire process, after installing Emacs (see my instructions for [[file:README-MacOS.org][both MacOS]] and [[file:README-Linux.org][Linux]]), clone this repo with:
|
||||
#+begin_src sh
|
||||
|
@ -29,15 +29,15 @@ To create [[file:~/.emacs.d/init.el][~/.emacs.d/init.el]] which starts the proce
|
|||
- [[file:bootstrap.org][Bootstrap]] :: configures =straight= and loads basic libraries the rest of the code depends on. It then loads the following files in order.
|
||||
- [[file:ha-config.org][Configuration]] :: contains /most/ of my configuration, setting up my sequence key menus, evil, etc.
|
||||
- [[file:ha-evil.org][Evilness]] :: configuration for using VI, er, ~vim~ keybindings in Emacs.
|
||||
- [[file:ha-general.org][Leader]] :: using the ~SPC~ to kick off a hierarchal order of functions.
|
||||
- [[file:ha-display.org][GUI Display]] :: sets up the visual aspects of an Emacs GUI, including themes and fonts.
|
||||
- [[file:ha-general.org][Leader]] :: using the ~SPC~ to kick off a hierarchical order of functions.
|
||||
- [[file:ha-display.org][GUI Display]] :: sets up the visual aspects of an Emacs GUI, including font specification and [[file:ha-theme.org][my theme]].
|
||||
- [[file:ha-dashboard.org][Dashboard]] :: sets up initial window layout of the =main= project with a dashboard.
|
||||
- [[file:ha-data.org][Data]] :: functions for dealing with a buffer-full of data.
|
||||
|
||||
** Org Mode Configuration
|
||||
- [[file:ha-org.org][Initial Org Configuration]] :: configures the basics for org-mode formatted files. Specific features come from their own files.
|
||||
- [[file:ha-org-word-processor.org][Word Processing]] :: attempts to make Org files /visually/ look like a word processor, including turning off the colors for headers, and instead increasing their size.
|
||||
- [[file:ha-org-literate.org][Literate Programming]] :: functions to support literate programming techniques. I use this most prevalently with this Emacs configuration.
|
||||
- [[file:ha-org-literate.org][Literate Programming]] :: functions to support literate programming techniques. I use this with my Emacs configuration.
|
||||
- [[file:ha-org-clipboard.org][Clipboard]] :: automatically converting HTML from a clipboard into Org-formatted content.
|
||||
- [[file:ha-org-journaling.org][Journaling]] :: for writing journal entries and tasks.
|
||||
- [[file:ha-org-publishing.org][Publishing]] :: code for publishing my website, [[http://howardism.org][www.howardism.org]].
|
||||
|
@ -82,3 +82,7 @@ Other functions and files come from essays written on [[http://www.howardism.org
|
|||
#+options: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
|
||||
#+options: skip:nil author:nil email:nil creator:nil timestamp:nil
|
||||
#+infojs_opt: view:nil toc:t ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|
||||
|
||||
# Local Variables:
|
||||
# jinx-local-words: "bitlbee rcirc supe"
|
||||
# End:
|
||||
|
|
|
@ -32,6 +32,19 @@ I'm installing everything using the [[https://github.com/raxod502/straight.el#ge
|
|||
See the details in [[https://dev.to/jkreeftmeijer/emacs-package-management-with-straight-el-and-use-package-3oc8][this essay]].
|
||||
|
||||
* Initial Settings
|
||||
** Garbage Collection Settings
|
||||
GC has always been a contentious subject in Emacs. Lot less of an issue now, but let’s not slow the startup (any more than I already do by checking all the packages to see if anything new needs to be downloaded).
|
||||
|
||||
Limit garbage collection before startup and then go back to the default value (8 MiB) after startup.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(setq gc-cons-threshold most-positive-fixnum)
|
||||
|
||||
(add-hook 'emacs-startup-hook
|
||||
(lambda ()
|
||||
(setq gc-cons-threshold (* 100 1024 1024))))
|
||||
#+END_SRC
|
||||
|
||||
** OS Path and Native Compilation
|
||||
Helper functions to allow code for specific operating systems:
|
||||
#+begin_src emacs-lisp
|
||||
|
|
|
@ -33,7 +33,7 @@ Glad to see the 2FA feature is working on the [[https://codeberg.org/martianh/ma
|
|||
(use-package mastodon
|
||||
:straight (:host codeberg :repo "martianh/mastodon.el")
|
||||
:init
|
||||
(setq mastodon-instance-url "https://pdx.sh"
|
||||
(setq mastodon-instance-url "https://pdx.social"
|
||||
mastodon-active-user "howard"))
|
||||
#+end_src
|
||||
|
||||
|
@ -97,15 +97,26 @@ Yet another encrypted chat/VoIP client-server, but unlike Signal and Telegram, [
|
|||
(use-package ement
|
||||
:straight (:host github :repo "alphapapa/ement.el")
|
||||
:config
|
||||
(major-mode-hydra-define ement-room-mode (:quit-key "q")
|
||||
("Send"
|
||||
(("c" ement-room-compose-message "Compose Message")
|
||||
("d" ement-send-direct-message "Direct Message")
|
||||
("S" ement-room-dispatch-reply-to-message "Message Reply")
|
||||
("s" ement-room-send-message "Send (or <Return>)"))
|
||||
"Other"
|
||||
(("r" ement-room-send-reaction "React")
|
||||
("e" ement-room-send-emote "Emote")
|
||||
("R" ement-view-room "Jump to Room"))))
|
||||
|
||||
(defun ha-ement-connect ()
|
||||
(interactive)
|
||||
(let* ((auth-results (auth-source-search :host "howardism-matrix"))
|
||||
(let* ((auth-results (auth-source-search :host "matrix"))
|
||||
(auth-first (first auth-results))
|
||||
(username (plist-get auth-first :user))
|
||||
(password (funcall (plist-get auth-first :secret))))
|
||||
(ement-connect :user-id username
|
||||
:password password
|
||||
:uri-prefix "https://howardism.org"))
|
||||
:uri-prefix "https://matrix.org"))
|
||||
|
||||
(ha-leader
|
||||
"a x S" '("send" . ement-send-direct-message)
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
#+title: General Emacs Configuration
|
||||
#+author: Howard X. Abrams
|
||||
#+date: 2020-09-10
|
||||
#+tags: emacs
|
||||
#+TITLE: General Emacs Configuration
|
||||
#+AUTHOR: Howard X. Abrams
|
||||
#+DATE: 2020-09-10
|
||||
#+FILETAGS: emacs
|
||||
#+STARTUP: inlineimages
|
||||
|
||||
A literate programming file for configuring Emacs.
|
||||
|
||||
|
@ -24,12 +25,15 @@ A literate programming file for configuring Emacs.
|
|||
;;
|
||||
;;; Commentary:
|
||||
;;
|
||||
;; Basic configuration of Emacs. Start early in the loading sequence.
|
||||
;; Basic Emacs configuration settings, ran near the beginning of the
|
||||
;; loading sequence.
|
||||
;;
|
||||
;;; Code:
|
||||
#+end_src
|
||||
* Basic Configuration
|
||||
I begin configuration of Emacs that isn’t /package-specific/. For instance, I hate to fat-finger a single letter that could stop Emacs:
|
||||
I begin configuration of Emacs that isn’t /package-specific/.
|
||||
|
||||
I hate to fat-finger a single letter that could stop Emacs:
|
||||
#+begin_src emacs-lisp
|
||||
(setq confirm-kill-emacs 'yes-or-no-p)
|
||||
#+end_src
|
||||
|
|
|
@ -22,6 +22,12 @@ A literate programming file to configure the Emacs UI.
|
|||
;; ~/src/hamacs/ha-display.org
|
||||
;; Using `find-file-at-point', and tangle the file to recreate this one .
|
||||
;;
|
||||
;;; Commentary:
|
||||
;;
|
||||
;; Configuration settings related to graphical display. Ran when the setting
|
||||
;; `display-graphic-p' is non-nil. Change the fonts, colors and ligatures;
|
||||
;; settings unavailable when ran from a Terminal emulator.
|
||||
;;
|
||||
;;; Code:
|
||||
#+end_src
|
||||
|
||||
|
@ -288,19 +294,27 @@ brew tap homebrew/cask-fonts
|
|||
brew install --cask font-hack-nerd-font
|
||||
#+end_src
|
||||
** Specifying a Font
|
||||
My /current/ favorite font is actually the top list of fonts that may be installed on my system:
|
||||
|
||||
My /current/ favorite /coding/ font changes often…call me /font-curious/. Since I may/may not have each font installed, I make a list, and pick the first one installed, so I order them:
|
||||
|
||||
- While I like Microsoft’s [[https://github.com/microsoft/cascadia-code][Cascadia]], I’m using [[https://github.com/eliheuer/caskaydia-cove][Caskaydia Cove]] from our beloved [[https://www.nerdfonts.com/font-downloads][NerdFonts]] as it has:
|
||||
- A dot in the 0
|
||||
- Good distinguishing aspects between parens, brackets and braces
|
||||
- Medium level of ligatures, like -> for arrows, but triple === signs don’t make three lines
|
||||
- Less serifs mean less letters
|
||||
- [[https://github.com/emersion/nanum-gothic-coding][Nanum Gothic Coding]] won the [[https://www.codingfont.com][CodingFont Challenge]] for me, like Hack (a fav) but with ligatures
|
||||
- [[https://github.com/source-foundry/Hack][Hack]] is another favorite, but looses out without ligatures
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defvar ha-fixed-font
|
||||
(when window-system
|
||||
(or
|
||||
(seq-first
|
||||
(seq-filter (lambda (font) (when (x-list-fonts font) font))
|
||||
'("CaskaydiaCove Nerd Font" ; finally found it
|
||||
;; funky font with litagures and a dotted 0
|
||||
"Cascadia Code PL"
|
||||
;; clean font, but no litagures!?
|
||||
"Hack Nerd Font"
|
||||
"FiraCode Nerd Font" ; has litagures
|
||||
'("CaskaydiaCove Nerd Font" ; Best Nerd-based font
|
||||
"NanumGothicCoding" ; Winner of codingfont.com
|
||||
"Hack Nerd Font" ; no litagures!?
|
||||
"FiraCode Nerd Font" ; has too much ligatures
|
||||
"Cousine Nerd Font"
|
||||
"Iosevka Nerd Font"
|
||||
"FantasqueSansMono Nerd Font"
|
||||
|
@ -311,21 +325,19 @@ My /current/ favorite font is actually the top list of fonts that may be install
|
|||
"My fixed width font based on what I have installed.")
|
||||
#+end_src
|
||||
|
||||
I probably don't need to have such a ranking system, as chances are good I have them all installed.
|
||||
While I like [[https://www.brailleinstitute.org/freefont/][Atkinson Hyperlegible]] a lot (oh, and [[https://fontesk.com/xcharter-typeface/][Literata]]), I found that [[https://supernotes.app/open-source/sn-pro][SN Pro]] is great for headers as well as matches my monospace font, [[https://github.com/eliheuer/caskaydia-cove/][Caskaydia Cove]].
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defvar ha-variable-font
|
||||
(when window-system
|
||||
(or
|
||||
(seq-first
|
||||
(seq-filter (lambda (font) (when (x-list-fonts font) font))
|
||||
'("Atkinson Hyperlegible"
|
||||
"SN Pro" ; https://supernotes.app/open-source/sn-pro
|
||||
"Literata" ; Clean, readable with litagures
|
||||
;; Next best can be downloaded here:
|
||||
;; https://fontesk.com/xcharter-typeface/
|
||||
'("SN Pro"
|
||||
"Atkinson Hyperlegible"
|
||||
"Literata"
|
||||
"XCharter"
|
||||
"Charter"
|
||||
;; Interesting idea: "Iosevka Comfy Motion Duo"
|
||||
"Serif")))
|
||||
(warn "Cannot find a Serif Font. Install Source Sans Pro."))))
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ Tell straight to use the built-in =eshell=:
|
|||
#+begin_src emacs-lisp
|
||||
(use-package eshell
|
||||
:straight (:type built-in)
|
||||
:hook (eshell-mode . 'ha-eshell-setup))
|
||||
:hook (eshell-mode . ha-eshell-setup))
|
||||
#+end_src
|
||||
|
||||
After reading [[https://xenodium.com/my-emacs-eye-candy/][this essay]], I decided to try hiding the mode line in eshell windows … at least, until I get the mode line to display more important information. Note that hiding the mode line is fairly straight-forward, but others might want to use the [[https://github.com/hlissner/emacs-hide-mode-line][hide-mode-line]] package that turns that /mode-line definition/ into a minor mode that can be toggled.
|
||||
|
@ -39,10 +39,54 @@ After reading [[https://xenodium.com/my-emacs-eye-candy/][this essay]], I decide
|
|||
I like =debug-on-error=, but not in Eshell, so I set this up when entering Eshell:
|
||||
#+begin_src emacs-lisp
|
||||
(defun ha-eshell-setup ()
|
||||
(make-local-variable 'debug-on-error)
|
||||
(setq mode-line-format nil
|
||||
debug-on-error nil))
|
||||
(set (make-local-variable 'debug-on-error) nil)
|
||||
(setq mode-line-format nil))
|
||||
#+end_src
|
||||
** Directory Notification
|
||||
While most people put the “current directory” in their shell prompt, I’ve always found that pretty distracting, difficult when copy/pasting commands, and cuts out on the length of my commands (which might be a good thing, tbh).
|
||||
|
||||
I use the =header-line= for this.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun ha-eshell-header-line ()
|
||||
"Update the buffer-local `header-line-format' with the current directory.
|
||||
This also calls `ha-eshell-git-branch' to format the Git repo string."
|
||||
(let* ((branch (ha-eshell-git-branch))
|
||||
(git-icon (all-the-icons-octicon "git-branch"))
|
||||
(git-line (if branch (format "%s %s" git-icon branch) ""))
|
||||
(home-rx (rx (literal (getenv "HOME"))))
|
||||
(dir-line (thread-last default-directory
|
||||
(replace-regexp-in-string home-rx "~")
|
||||
(directory-file-name))))
|
||||
(setq header-line-format
|
||||
(format " %s %s" dir-line git-line))))
|
||||
#+END_SRC
|
||||
|
||||
Which depends on a function to get the current branch and git status. How else but shelling out for this information?
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun ha-eshell-git-branch ()
|
||||
"Return simplified Git branch for current directory."
|
||||
(ignore-errors
|
||||
(thread-last "git status --short --branch --ahead-behind 2>/dev/null"
|
||||
(shell-command-to-list)
|
||||
(first)
|
||||
(replace-regexp-in-string
|
||||
(rx "## "
|
||||
(group (zero-or-more not-newline))
|
||||
(zero-or-more anychar))
|
||||
"\\1")
|
||||
(replace-regexp-in-string
|
||||
(rx "...") " → "))))
|
||||
#+END_SRC
|
||||
|
||||
Need to hook this when we change the directory.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(use-package eshell
|
||||
:hook (eshell-directory-change . ha-eshell-header-line))
|
||||
#+END_SRC
|
||||
|
||||
** Navigation and Keys
|
||||
Along with the regular Emacs keybindings, Eshell comes with some interesting features:
|
||||
- ~M-RET~ gives you a prompt, even when you are running another command. Since =eshell= passes all input to subprocesses, there is no automatic input queueing as there is with other shells.
|
||||
|
@ -1326,7 +1370,7 @@ The [[https://codeberg.org/akib/emacs-eat][Emulate a Terminal]] project provides
|
|||
(use-package eat
|
||||
:after eshell
|
||||
:straight (:repo "https://codeberg.org/akib/emacs-eat")
|
||||
:hook (eshell-load . #'eat-eshell-visual-command-mode))
|
||||
:hook (eshell-load . eat-eshell-visual-command-mode))
|
||||
#+end_src
|
||||
|
||||
Note that the =eat-eshell-visual-command-mode= also kicks off the global minor mode, =eat-eshell-mode=. The big advantage of Eat is the /three input modes/, however, in Eshell with Evil, I can just type ~Escape~ to go into Emacs Mode, and ~G A~ to return to typing Terminal commands.
|
||||
|
@ -1495,41 +1539,18 @@ The [[http://projects.ryuslash.org/eshell-fringe-status/][eshell-fringe-status]]
|
|||
:hook (eshell-mode . eshell-fringe-status-mode))
|
||||
#+end_src
|
||||
** Opening Banner
|
||||
Whenever I open a shell, I instinctively type =ls= … so why not do that automatically? The [[elisp:(describe-variable 'eshell-banner-message)][eshell-banner-message]] variable, while defaults to a string, this variable can be a /form/ (an s-expression) that calls a function, so I made a customized =ls= that can be attractive:
|
||||
|
||||
Whenever I open a shell, I instinctively type =ls= … so why not do that automatically? The [[elisp:(describe-variable 'eshell-banner-message)][eshell-banner-message]] variable, while defaults to a string, this variable can be a /form/ (an s-expression) that calls a function, so I call my customized =lsd= to be more attractive:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun ha-eshell-banner ()
|
||||
"Return a string containing the files in the current directory."
|
||||
(let ((fg (face-attribute 'default :background))
|
||||
(bg (face-attribute 'default :foreground))
|
||||
(bg "#c09644")
|
||||
(dd (replace-regexp-in-string (getenv "HOME") "~" default-directory))
|
||||
(gs (or (ha-eshell-banner-git-branch) "")))
|
||||
(condition-case err
|
||||
(concat
|
||||
;; Line 1
|
||||
(propertize
|
||||
(format " %s • ⑆ %s " dd gs)
|
||||
'face `(:background ,bg :foreground ,fg))
|
||||
"\n"
|
||||
;; Line 2
|
||||
(ha-dad-joke)
|
||||
"\n\n")
|
||||
(error "🐚 Welcome to Eshell\n\n"))))
|
||||
|
||||
(defun ha-eshell-banner-git-branch ()
|
||||
"Return simplified Git branch for current directory."
|
||||
(ignore-errors
|
||||
(thread-last "git status --short --branch --ahead-behind 2>/dev/null"
|
||||
(shell-command-to-list)
|
||||
(first)
|
||||
(replace-regexp-in-string
|
||||
(rx "## "
|
||||
(group (zero-or-more not-newline))
|
||||
(zero-or-more anychar))
|
||||
"\\1")
|
||||
(replace-regexp-in-string
|
||||
(rx "...") " → "))))
|
||||
(ha-eshell-header-line)
|
||||
(condition-case err
|
||||
(eshell/lsd)
|
||||
(error "🐚 Welcome to Eshell\n\n")))
|
||||
#+end_src
|
||||
|
||||
* Shell Windows
|
||||
Now that I often need to pop into remote systems to run a shell or commands, I create helper functions to create those buffer windows. Each buffer begins with =eshell=: allowing me to have more than one eshells, typically, one per project.
|
||||
** Shell There
|
||||
|
@ -1745,7 +1766,6 @@ Here is where we associate all the functions and their hooks with =eshell=, thro
|
|||
(use-package eshell
|
||||
:straight (:type built-in)
|
||||
:custom (eshell-banner-message '(ha-eshell-banner))
|
||||
|
||||
:init
|
||||
(setq eshell-error-if-no-glob t
|
||||
;; This jumps back to the prompt:
|
||||
|
@ -1762,7 +1782,7 @@ Here is where we associate all the functions and their hooks with =eshell=, thro
|
|||
;; Me neither, so this makes it act a bit more shell-like:
|
||||
eshell-prefer-lisp-functions nil)
|
||||
|
||||
:hook ((eshell-pred-load . ha-eshell-add-predicates))
|
||||
;; :hook ((eshell-pred-load . ha-eshell-add-predicates))
|
||||
|
||||
:bind (("M-!" . eshell-command)
|
||||
:map eshell-mode-map
|
||||
|
@ -1770,6 +1790,7 @@ Here is where we associate all the functions and their hooks with =eshell=, thro
|
|||
("C-d" . ha-eshell-quit-or-delete-char)))
|
||||
#+end_src
|
||||
Note that the default list to [[elisp:(describe-variable 'eshell-visual-commands)][eshell-visual-commands]] is good enough, but some of my /newer/ Rust-based apps need to be added:
|
||||
|
||||
#+begin_src emacs-lisp :tangle no
|
||||
(use-package eshell
|
||||
:config
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#+author: Howard Abrams
|
||||
#+date: 2024-07-07
|
||||
#+filetags: emacs hamacs
|
||||
#+lastmod: [2024-11-11 Mon]
|
||||
#+lastmod: [2025-01-28 Tue]
|
||||
|
||||
A literate programming file for literate programming in Emacs Org Files.
|
||||
|
||||
|
@ -578,7 +578,7 @@ At this point, we can jump to functions and variables that I define in my org fi
|
|||
|
||||
I can jump around my literate code as if they were =.el= files. I may want to think about expanding the definitions to figure out the language of the destination.
|
||||
** Noweb References
|
||||
A noweb definition, e.g. =<<something-something>>= should /jump/ to the =#name= definition.
|
||||
A noweb definition, e.g. =<<something-something>>= should /jump/ to the =#name= definition.
|
||||
|
||||
Since [[https://github.com/BurntSushi/ripgrep][ripgrep]] is pretty fast, I’ll call it instead of attempting to build a [[https://stackoverflow.com/questions/41933837/understanding-the-ctags-file-format][CTAGS]] table. Oooh, the =rg= takes a =—json= option, which makes it easier to parse.
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ Need the IDE features associated with [[https://github.com/clojure-emacs/cider][
|
|||
;; backward-word, etc) in the REPL is quite useful since we often have
|
||||
;; to deal with Java class and method names. The built-in Emacs minor
|
||||
;; mode subword-mode provides such functionality
|
||||
:hook (cider-repl-mode . #'subword-mode)
|
||||
:hook (cider-repl-mode . subword-mode)
|
||||
|
||||
:config
|
||||
(ha-local-leader :keymaps clojure-mode-map
|
||||
|
|
|
@ -55,7 +55,7 @@ The [[https://github.com/mihaimaruseac/hindent][hindent package]] looks interest
|
|||
#+begin_src emacs-lisp
|
||||
(use-package hindent
|
||||
:custom (hindent-style "johan-tibell")
|
||||
:hook (haskell-mode . #'hindent-mode))
|
||||
:hook (haskell-mode . hindent-mode))
|
||||
#+end_src
|
||||
* Haskell and Org
|
||||
#+begin_src emacs-lisp
|
||||
|
|
|
@ -252,6 +252,7 @@ Color definition injects the /named/ lists defined above (using Org’s =noweb=
|
|||
)
|
||||
"A list of named colors available in theme.")
|
||||
#+END_SRC
|
||||
|
||||
* Theme Support
|
||||
Stole the following macro from Zenburn, which converts color references defined above, but only within the body of the macro. Sweet way to trim down a lot of boilerplate:
|
||||
|
||||
|
@ -341,16 +342,16 @@ Let’s make a /theme/:
|
|||
`(line-number ((t (:foreground ,gray-50 :background ,gray-10))))
|
||||
`(line-number-current-line ((t (:foreground ,gray-95 :background ,gray-20 :weight ultra-bold))))
|
||||
|
||||
`(header-line ((t (:foreground ,gray-80))))
|
||||
`(header-line ((t (:foreground ,almond :background ,inactive :extend t))))
|
||||
`(help-key-binding ((t (:foreground ,gray-80 :weight ultra-bold))))
|
||||
`(bold ((t (:foreground ,gray-90 :weight ultra-bold))))
|
||||
`(italics ((t (:foreground ,gray-95))))
|
||||
`(bold-italic ((t (:foreground "white"))))
|
||||
`(italics ((t (:foreground ,gray-95 :slant italic))))
|
||||
`(bold-italic ((t (:foreground "white" :slant italic :weight ultra-bold))))
|
||||
|
||||
`(link ((t (:foreground ,link-color))))
|
||||
`(link-visited ((t (:foreground ,visited-color))))
|
||||
|
||||
`(font-lock-comment-face ((t (:foreground ,gray-60))))
|
||||
`(font-lock-comment-face ((t (:foreground ,gray-60 :slant italic))))
|
||||
`(font-lock-comment-delimiter-face ((t (:foreground ,gray-50))))
|
||||
`(font-lock-string-face ((t (:foreground ,gray-75))))
|
||||
`(font-lock-type-face ((t (:foreground ,green-lt))))
|
||||
|
|
24
initialize
24
initialize
|
@ -31,6 +31,9 @@ cat > "$HAMACS_DEST/early-init.el" <<EOF
|
|||
;;
|
||||
;;; Code:
|
||||
|
||||
;; Need the package system near the front:
|
||||
(require 'package)
|
||||
|
||||
;; We'll be using straight. So, we don't want duplicated package loading:
|
||||
(setq package-enable-at-startup nil)
|
||||
|
||||
|
@ -68,9 +71,9 @@ cat > "$HAMACS_DEST/init.el" <<EOF
|
|||
;;
|
||||
;;; Commentary:
|
||||
;;
|
||||
;; This is my Emacs Bootloader. Simply put, I initialize the package management
|
||||
;; system, and then tangle my literate files. This simple idea came from
|
||||
;; https://github.com/susamn/dotfiles
|
||||
;; This is my Emacs Bootloader. Simply put, I initialize the package
|
||||
;; management system, and then tangle my literate files. This simple
|
||||
;; idea came from https://github.com/susamn/dotfiles
|
||||
;;
|
||||
;;; Code:
|
||||
|
||||
|
@ -79,9 +82,22 @@ cat > "$HAMACS_DEST/init.el" <<EOF
|
|||
;; Bug fixes for ORG (there always seems to be something):
|
||||
(defvar native-comp-deferred-compilation-deny-list nil)
|
||||
|
||||
;; Allow the installation of unsigned packages, but verify the signature if possible:
|
||||
;; Allow the installation of unsigned packages, but verify the
|
||||
;; signature if possible:
|
||||
|
||||
(setq package-check-signature 'allow-unsigned)
|
||||
|
||||
;; While using Straight with direct Github repos,
|
||||
;; adding Melpa and others isn't a bad idea:
|
||||
|
||||
(require 'use-package)
|
||||
|
||||
(add-to-list 'package-archives
|
||||
'("melpa" . "https://melpa.org/packages/"))
|
||||
|
||||
(add-to-list 'package-archives
|
||||
'("elpa-dev" . "https://elpa.gnu.org/devel/"))
|
||||
|
||||
;; Configure straight https://github.com/raxod502/straight.el#getting-started
|
||||
|
||||
(defvar bootstrap-version)
|
||||
|
|
347
pud.org
Normal file
347
pud.org
Normal file
|
@ -0,0 +1,347 @@
|
|||
#+title: Moss and Puddles
|
||||
#+author: Howard X. Abrams
|
||||
#+date: 2025-01-18
|
||||
#+filetags: emacs hamacs
|
||||
#+lastmod: [2025-01-20 Mon]
|
||||
|
||||
A literate programming file for a Comint-based MUD client.
|
||||
|
||||
#+begin_src emacs-lisp :exports none
|
||||
;;; pud --- a MUD client -*- lexical-binding: t; -*-
|
||||
;;
|
||||
;; © 2025 Howard X. Abrams
|
||||
;; Licensed under a Creative Commons Attribution 4.0 International License.
|
||||
;; See http://creativecommons.org/licenses/by/4.0/
|
||||
;;
|
||||
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
|
||||
;; Maintainer: Howard X. Abrams
|
||||
;; Created: January 18, 2025
|
||||
;;
|
||||
;; While obvious, GNU Emacs does not include this file or project.
|
||||
;;
|
||||
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
|
||||
;; /Users/howard/src/hamacs/pud.org
|
||||
;; And tangle the file to recreate this one.
|
||||
;;
|
||||
;;; Code:
|
||||
#+end_src
|
||||
|
||||
* Introduction
|
||||
|
||||
This project is a simple MUD client for Emacs, based on COM-INT MUD client based on Mickey Petersen’s [[https://www.masteringemacs.org/article/comint-writing-command-interpreter][essay on Comint]].
|
||||
|
||||
The default connects to *Moss ‘n Puddles*, my own MUD which I invite you to join.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defvar pud-default-world "moss-n-puddles"
|
||||
"The default world to connect.")
|
||||
#+END_SRC
|
||||
|
||||
This uses =telnet= (at the moment) for the connection, so you will need to install that first. On Mac, this would be:
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
brew install telent
|
||||
#+END_SRC
|
||||
|
||||
And use a similar command on Linux.
|
||||
* User Credentials
|
||||
|
||||
You may want to customize your connections to more worlds.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defgroup pud nil
|
||||
"An overly simplistic MUD client that works with Evennia."
|
||||
:group 'processes)
|
||||
|
||||
(defcustom pud-worlds
|
||||
(list (vector pud-default-world "localhost" "4000" "guest" "guest"))
|
||||
"List of worlds you play in.
|
||||
You need to define the worlds you play in before you can get
|
||||
started. In most worlds, you can start playing using a guest account.
|
||||
|
||||
Each element WORLD of the list has the following form:
|
||||
|
||||
\[NAME HOST PORT CHARACTER PASSWORD]
|
||||
|
||||
NAME identifies the connection, HOST and PORT specify the network
|
||||
connection, CHARACTER and PASSWORD are used to connect automatically.
|
||||
|
||||
Note that this will be saved in your `custom-file' -- including your
|
||||
passwords! If you don't want that, specify nil as your password."
|
||||
:type '(repeat
|
||||
(vector :tag "World"
|
||||
(string :tag "Name")
|
||||
(string :tag "Host")
|
||||
(integer :tag "Port")
|
||||
(string :tag "Char" :value "guest")
|
||||
(string :tag "Pwd" :value "guest")))
|
||||
:group 'pud)
|
||||
#+END_SRC
|
||||
|
||||
Next, open [[file:~/.authinfo.gpg][your authinfo file]], and insert the following line, substituting =[user]= and =[pass]= with your credentials as well as the first entry to match your world:
|
||||
|
||||
#+BEGIN_SRC conf :tangle no :eval no
|
||||
machine moss-n-puddles login [name] password [pass]
|
||||
#+END_SRC
|
||||
|
||||
Now, let’s play! Type =run-pud=, and optionally select a world. If you get disconnected, re-run it, or even =pud-reconnect=.
|
||||
|
||||
The rest of this file describes the code to implement this project.
|
||||
* Code
|
||||
The following function will return the default world:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun pud-get-default-world ()
|
||||
"Return the connection information for the `pud-default-world'.
|
||||
If only one world listed in `pud-worlds', return that."
|
||||
(if (length= pud-worlds 1)
|
||||
(seq-first pud-worlds)
|
||||
(seq-find
|
||||
(lambda (w) (string-equal (aref w 0) pud-default-world))
|
||||
pud-worlds)))
|
||||
#+END_SRC
|
||||
|
||||
And accessibility functions.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defsubst pud-world-name (&optional world)
|
||||
"Return the name for WORLD as a string."
|
||||
;; (concat (aref world 3) "@" (aref world 0))
|
||||
(if (vectorp world)
|
||||
(aref world 0)
|
||||
world))
|
||||
|
||||
(defsubst pud-world-network (&optional world)
|
||||
"Return the network details for WORLD as a cons cell (HOST . PORT)."
|
||||
(unless world
|
||||
(setq world (pud-get-default-world)))
|
||||
(list (aref world 1) (aref world 2)))
|
||||
|
||||
(defsubst pud-world-character (&optional world)
|
||||
"Return the character for WORLD as a string.
|
||||
Override the customized setting if the world has an entry in authinfo."
|
||||
(unless world
|
||||
(setq world (pud-get-default-world)))
|
||||
|
||||
(if-let ((auth-results (auth-source-search :host (aref world 0))))
|
||||
(thread-first auth-results
|
||||
(first)
|
||||
(plist-get :user))
|
||||
(aref world 3)))
|
||||
|
||||
(defsubst pud-world-password (&optional world)
|
||||
"Return the password for WORLD as a string."
|
||||
(unless world
|
||||
(setq world (pud-get-default-world)))
|
||||
(if-let ((auth-results (auth-source-search :host (aref world 0))))
|
||||
(thread-first auth-results
|
||||
(first)
|
||||
(plist-get :secret)
|
||||
(funcall))
|
||||
(aref world 4)))
|
||||
#+END_SRC
|
||||
|
||||
And some basic functions that really need to be expanded.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp :tangle no
|
||||
(ert-deftest pud-world-name-test ()
|
||||
(should (string-equal (pud-world-name "foobar") "foobar"))
|
||||
(should (string-equal (pud-world-name ["foobar" "localhost" "4000" "guest" "guest"]) "foobar")))
|
||||
|
||||
(ert-deftest pud-world-network-test ()
|
||||
(should (equal (pud-world-network) '("localhost" "4000")))
|
||||
(should (equal (pud-world-network ["foobar" "overthere" "4000" "guest" "guest"]) '("overthere" "4000"))))
|
||||
|
||||
(ert-deftest pud-world-character-test ()
|
||||
(should (equal (pud-world-character) "guest")))
|
||||
#+END_SRC
|
||||
|
||||
Choosing a world… er, connection using a =completing-read= allowing you to choose a world. If =pud-worlds= contains a single value, might as well just return that.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defvar pud-world-history nil
|
||||
"History for `pud-get-world'.")
|
||||
|
||||
(defun pud-get-world ()
|
||||
"Let the user choose a world from `pud-worlds'.
|
||||
The return value is a cons cell, the car is the name of the connection,
|
||||
the cdr holds the connection defails from `pud-worlds'."
|
||||
(if (length= pud-worlds 1)
|
||||
(seq-first pud-worlds))
|
||||
|
||||
(let ((world-completions
|
||||
(mapcar (lambda (w)
|
||||
(cons (pud-world-name w) w))
|
||||
pud-worlds)))
|
||||
(cond
|
||||
((and world-completions (length= world-completions 1))
|
||||
(thread-first world-completions
|
||||
(first)
|
||||
(cdr)))
|
||||
(world-completions
|
||||
(thread-first
|
||||
(completing-read "World: " world-completions nil t nil pud-world-history)
|
||||
(assoc world-completions)
|
||||
(cdr)))
|
||||
(t (customize-option 'pud-worlds)))))
|
||||
#+END_SRC
|
||||
|
||||
And a function for the full credentials, which just happens to be what we need to pass to =telnet=.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun pud-credentials (&optional world)
|
||||
"Reset the credentials from WORLD from the authinfo system."
|
||||
(setf (elt 3 world) (pud-world-character world))
|
||||
(setf (elt 4 world) (pud-world-password world))
|
||||
world)
|
||||
#+END_SRC
|
||||
|
||||
* Basics
|
||||
Using Comint, and hoping to have the ANSI colors displayed.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(require 'comint)
|
||||
(load "ansi-color" t)
|
||||
#+END_SRC
|
||||
|
||||
I’m going to use good ‘ol fashion =telnet= for the connection:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defvar pud-cli-file-path "/usr/local/bin/telnet"
|
||||
"Path to the program used by `run-pud'")
|
||||
#+END_SRC
|
||||
|
||||
The pud-cli-arguments, holds a list of commandline arguments: the port.
|
||||
|
||||
The empty and currently disused mode map for storing our custom keybindings inherits from =comint-mode-map=, so we get the same keys exposed in =comint-mode=.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defvar pud-mode-map
|
||||
(let ((map (nconc (make-sparse-keymap) comint-mode-map)))
|
||||
;; example definition
|
||||
(define-key map "\t" 'completion-at-point)
|
||||
map)
|
||||
"Basic mode map for `run-pud'.")
|
||||
#+END_SRC
|
||||
|
||||
This holds a regular expression that matches the prompt style for the MUD. Not sure if this is going to work, since MUDs typically don’t have prompts.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defvar pud-prompt-regexp "" ; "^\\(?:\\[[^@]+@[^@]+\\]\\)"
|
||||
"Prompt for `run-pud'.")
|
||||
#+END_SRC
|
||||
|
||||
The name of the buffer:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defvar pud-buffer-name "*Moss and Puddles*"
|
||||
"Name of the buffer to use for the `run-pud' comint instance.")
|
||||
#+END_SRC
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun pud-buffer-name (&optional world)
|
||||
"Return the buffer name associated with WORLD."
|
||||
(format "*%s*" (if world
|
||||
(pud-world-name world)
|
||||
pud-default-world)))
|
||||
#+END_SRC
|
||||
|
||||
The main entry point to the program is the =run-pud= function:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun run-pud (world)
|
||||
"Run an inferior instance of `pud-cli' inside Emacs."
|
||||
(interactive (list (pud-get-world)))
|
||||
|
||||
(let* ((pud-program pud-cli-file-path)
|
||||
(pud-args (pud-world-network world))
|
||||
(buffer (get-buffer-create (pud-buffer-name world)))
|
||||
(proc-alive (comint-check-proc buffer))
|
||||
(process (get-buffer-process buffer)))
|
||||
;; if the process is dead then re-create the process and reset the
|
||||
;; mode.
|
||||
(unless proc-alive
|
||||
(with-current-buffer buffer
|
||||
(apply 'make-comint-in-buffer "Pud" buffer pud-program nil pud-args)
|
||||
(pud-mode)
|
||||
(visual-line-mode 1)
|
||||
(pud-reconnect world)))
|
||||
;; Regardless, provided we have a valid buffer, we pop to it.
|
||||
(when buffer
|
||||
(pop-to-buffer buffer))))
|
||||
#+END_SRC
|
||||
|
||||
Connection and/or re-connection:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun pud-reconnect (world)
|
||||
"docstring"
|
||||
(interactive (list (pud-get-world)))
|
||||
(pop-to-buffer (pud-buffer-name world))
|
||||
(sit-for 1)
|
||||
(let* ((username (pud-world-character world))
|
||||
(password (pud-world-password world))
|
||||
(conn-str (format "connect %s %s\n" username password))
|
||||
(process (get-buffer-process (current-buffer))))
|
||||
(if process
|
||||
(comint-send-string process conn-str)
|
||||
(insert conn-str))))
|
||||
#+END_SRC
|
||||
* Pud Mode
|
||||
The previous snippet of code dealt with creating and maintaining the buffer and process, and this piece of code enriches it with font locking and mandatory setup. Namely comint-process-echoes which, depending on the mode and the circumstances, may result in prompts appearing twice. Setting it to t is usually a requirement, but do experiment.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun pud--initialize ()
|
||||
"Helper function to initialize Pud."
|
||||
(setq comint-process-echoes t)
|
||||
(setq comint-use-prompt-regexp nil))
|
||||
|
||||
(define-derived-mode pud-mode comint-mode "Pud"
|
||||
"Major mode for `run-pud'.
|
||||
|
||||
\\<pud-mode-map>"
|
||||
;; this sets up the prompt so it matches things like: [foo@bar]
|
||||
;; (setq comint-prompt-regexp pud-prompt-regexp)
|
||||
|
||||
;; this makes it read only; a contentious subject as some prefer the
|
||||
;; buffer to be overwritable.
|
||||
(setq comint-prompt-read-only t)
|
||||
|
||||
;; this makes it so commands like M-{ and M-} work.
|
||||
;; (set (make-local-variable 'paragraph-separate) "\\'")
|
||||
;; (set (make-local-variable 'font-lock-defaults) '(pud-font-lock-keywords t))
|
||||
;; (set (make-local-variable 'paragraph-start) pud-prompt-regexp)
|
||||
)
|
||||
|
||||
(add-hook 'pud-mode-hook 'pud--initialize)
|
||||
|
||||
(defconst pud-keywords
|
||||
'("connect" "get" "look" "use")
|
||||
"List of keywords to highlight in `pud-font-lock-keywords'.")
|
||||
|
||||
(defvar pud-font-lock-keywords
|
||||
(list
|
||||
;; highlight all the reserved commands.
|
||||
`(,(concat "\\_<" (regexp-opt pud-keywords) "\\_>") . font-lock-keyword-face))
|
||||
"Additional expressions to highlight in `pud-mode'.")
|
||||
#+END_SRC
|
||||
|
||||
|
||||
* Technical Artifacts :noexport:
|
||||
|
||||
Let's =provide= a name so we can =require= this file:
|
||||
|
||||
#+begin_src emacs-lisp :exports none
|
||||
(provide 'pud)
|
||||
;;; pud.el ends here
|
||||
#+end_src
|
||||
|
||||
#+DESCRIPTION: a MUD client
|
||||
|
||||
#+PROPERTY: header-args:sh :tangle no
|
||||
#+PROPERTY: header-args:emacs-lisp :tangle yes
|
||||
#+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes
|
||||
|
||||
#+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
|
||||
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
|
||||
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|
Loading…
Reference in a new issue