Compare commits
No commits in common. "30af02232a42a024e58d93d5703f0804cb1dea4f" and "6923e14916b7643f08783ed1aab00412f88b3198" have entirely different histories.
30af02232a
...
6923e14916
2 changed files with 147 additions and 220 deletions
|
@ -820,7 +820,6 @@ I like Charles Choi’s [[https://github.com/kickingvegas/casual][Casual Suite]]
|
||||||
|
|
||||||
Pressing ~C-g~ aborts, and ~Return~ exits leaving the point in place. Typing ~C-s~ in the menu stops the menu to continue searching.
|
Pressing ~C-g~ aborts, and ~Return~ exits leaving the point in place. Typing ~C-s~ in the menu stops the menu to continue searching.
|
||||||
|
|
||||||
#+ATTR_HTML: :width 800
|
|
||||||
[[file:screenshots/isearch-hydra.png]]
|
[[file:screenshots/isearch-hydra.png]]
|
||||||
|
|
||||||
How did I figure out the available bindings to make this? To see /all/ the bindings, start the =isearch= minor mode (using ~C-s~), then type ~F1~ twice. This fancy feature lets you select ~b~ to see all the keybindings (along with other features).
|
How did I figure out the available bindings to make this? To see /all/ the bindings, start the =isearch= minor mode (using ~C-s~), then type ~F1~ twice. This fancy feature lets you select ~b~ to see all the keybindings (along with other features).
|
||||||
|
|
366
pud.org
366
pud.org
|
@ -2,7 +2,7 @@
|
||||||
#+author: Howard X. Abrams
|
#+author: Howard X. Abrams
|
||||||
#+date: 2025-01-18
|
#+date: 2025-01-18
|
||||||
#+filetags: emacs hamacs
|
#+filetags: emacs hamacs
|
||||||
#+lastmod: [2025-03-01 Sat]
|
#+lastmod: [2025-02-14 Fri]
|
||||||
|
|
||||||
A literate programming file for a Comint-based MUD client.
|
A literate programming file for a Comint-based MUD client.
|
||||||
|
|
||||||
|
@ -28,7 +28,14 @@ A literate programming file for a Comint-based MUD client.
|
||||||
|
|
||||||
* Introduction
|
* Introduction
|
||||||
|
|
||||||
This project is a simple MUD client for Emacs, based on COM-INT MUD client I learn about on Mickey Petersen’s [[https://www.masteringemacs.org/article/comint-writing-command-interpreter][essay on Comint]].
|
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:
|
This uses =telnet= (at the moment) for the connection, so you will need to install that first. On Mac, this would be:
|
||||||
|
|
||||||
|
@ -37,10 +44,9 @@ This uses =telnet= (at the moment) for the connection, so you will need to insta
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
And use a similar command on Linux.
|
And use a similar command on Linux.
|
||||||
** Customization
|
* User Credentials
|
||||||
|
|
||||||
You may want to customize your connections to more worlds.
|
You may want to customize your connections to more worlds.
|
||||||
The default connects to *Moss ‘n Puddles*, my own MUD which I invite you to join.
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defgroup pud nil
|
(defgroup pud nil
|
||||||
|
@ -48,82 +54,108 @@ The default connects to *Moss ‘n Puddles*, my own MUD which I invite you to jo
|
||||||
:group 'processes)
|
:group 'processes)
|
||||||
|
|
||||||
(defcustom pud-worlds
|
(defcustom pud-worlds
|
||||||
'(["Moss-n-Puddles" "howardabrams.com" 4000 "" ""])
|
(list (vector pud-default-world "howardabrams.com" "4000" "guest" "guest"))
|
||||||
"List of worlds you play in.
|
"List of worlds you play in.
|
||||||
You need to define the worlds you play in before you can get
|
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.
|
started. In most worlds, you can start playing using a guest account.
|
||||||
|
|
||||||
Each element WORLD of the list has the following form:
|
Each element WORLD of the list has the following form:
|
||||||
|
|
||||||
\[NAME HOST PORT CHARACTER PASSWORD CONNECTION-STR]
|
\[NAME HOST PORT CHARACTER PASSWORD]
|
||||||
|
|
||||||
NAME identifies the connection, HOST and PORT specify the network
|
NAME identifies the connection, HOST and PORT specify the network
|
||||||
connection, CHARACTER and PASSWORD are used to connect automatically.
|
connection, CHARACTER and PASSWORD are used to connect automatically.
|
||||||
|
|
||||||
The CONNECTION-STR is a string with two `%s' where this substitutes
|
Note that this will be saved in your `custom-file' -- including your
|
||||||
the username and password respectively. Sends this to the server after
|
passwords! If you don't want that, specify nil as your password."
|
||||||
establishing a connection. This can be blank for the default.
|
|
||||||
If given, make sure to have a trailing `\n' to automatically send.
|
|
||||||
|
|
||||||
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
|
:type '(repeat
|
||||||
(vector :tag "Server World"
|
(vector :tag "World"
|
||||||
(string :tag "Name")
|
(string :tag "Name")
|
||||||
(string :tag "Host")
|
(string :tag "Host")
|
||||||
(integer :tag "Port")
|
(integer :tag "Port")
|
||||||
(string :tag "Char" :value "guest")
|
(string :tag "Char" :value "guest")
|
||||||
(string :tag "Pass")
|
(string :tag "Pwd" :value "guest")))
|
||||||
(string :tag "Connect String" :value "connect %s %s")))
|
|
||||||
:group 'pud)
|
:group 'pud)
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
For instance:
|
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 emacs-lisp :tangle no :eval no
|
|
||||||
(use-package pud
|
|
||||||
:custom
|
|
||||||
(pud-worlds
|
|
||||||
'(["Remote Moss-n-Puddles" "howardabrams" 4000 "bobby"]
|
|
||||||
; ↑ No password? Should be in .authinfo.gpg
|
|
||||||
|
|
||||||
["Local Root" "localhost" 4000 "suzy" "some-pass"]
|
|
||||||
; ↑ This has the password in your custom settings.
|
|
||||||
|
|
||||||
; ↓ Password from authinfo, special connection string:
|
|
||||||
["Local User" "localhost" 4000 "rick" nil "login %s %s"])))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Hidden:
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no :eval no
|
|
||||||
(setq pud-worlds
|
|
||||||
'(["moss-n-puddles" "howardabrams.com" 4000 "howard"]
|
|
||||||
["moss-n-puddles" "howardabrams.com" 4000 "rick"]
|
|
||||||
["moss-n-puddles" "howardabrams.com" 4000 "darol"]
|
|
||||||
["local-evennia" "localhost" 4000 "howard"]
|
|
||||||
["local-evennia" "localhost" 4000 "rick"]))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Seems like MUDs have a standard login sequence, but they don’t have to. Here is the default that a user can override in their =pud-worlds= listing:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defcustom pud-default-connection-string "connect %s %s\n"
|
|
||||||
"The standard connection string to substitute the username and password."
|
|
||||||
:type '(string :tag "Connect String" :value "connect %s %s\n")
|
|
||||||
:group 'pud)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
** User Credentials
|
|
||||||
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 each host name in your =pud-worlds= entries:
|
|
||||||
|
|
||||||
#+BEGIN_SRC conf :tangle no :eval no
|
#+BEGIN_SRC conf :tangle no :eval no
|
||||||
machine howardabrams.com login [name] port 4000 password [pass]
|
machine moss-n-puddles login [name] password [pass]
|
||||||
#+END_SRC
|
#+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=.
|
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 implementing this project.
|
The rest of this file describes the code to implement this project.
|
||||||
* Code
|
* 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
|
||||||
|
(defun 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))
|
||||||
|
|
||||||
|
(defun 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)))
|
||||||
|
|
||||||
|
(defun 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)))
|
||||||
|
|
||||||
|
(defun 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.
|
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
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
@ -154,61 +186,14 @@ Choosing a world… er, connection using a =completing-read= allowing you to cho
|
||||||
(t (customize-option 'pud-worlds)))))
|
(t (customize-option 'pud-worlds)))))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
And a function for the full credentials, which just happens to be what we need to pass to =telnet=.
|
||||||
The following functions are accessibility functions to the world entry.
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun pud-world-name (world)
|
(defun pud-credentials (&optional world)
|
||||||
"Return the name for WORLD as a string."
|
"Reset the credentials from WORLD from the authinfo system."
|
||||||
(if (vectorp world)
|
(setf (elt 3 world) (pud-world-character world))
|
||||||
(if (or (length< world 4) (null (aref world 3)) (string-blank-p (aref world 3)))
|
(setf (elt 4 world) (pud-world-password world))
|
||||||
(aref world 0)
|
world)
|
||||||
(concat (aref world 3) "@" (aref world 0)))
|
|
||||||
world))
|
|
||||||
|
|
||||||
(defun pud-world-network (world)
|
|
||||||
"Return the network details for WORLD as a cons cell (HOST . PORT)."
|
|
||||||
(list (aref world 1) (format "%s" (aref world 2))))
|
|
||||||
|
|
||||||
(defun pud-world-creds (world)
|
|
||||||
"Return the username and password from WORLD.
|
|
||||||
Multiple search queries for the .authinfo file."
|
|
||||||
(seq-let (label host port user) world
|
|
||||||
(if-let ((auth-results (first (auth-source-search
|
|
||||||
:host host
|
|
||||||
:port port
|
|
||||||
:user user
|
|
||||||
:max 1))))
|
|
||||||
(list (plist-get auth-results :user)
|
|
||||||
(funcall (plist-get auth-results :secret)))
|
|
||||||
;; No match? Just return values from world:
|
|
||||||
(list (aref world 3) (aref world 4)))))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
And some basic functions I should expand.
|
|
||||||
|
|
||||||
#+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"]) "foobar"))
|
|
||||||
(should (string-equal (pud-world-name ["foobar" "localhost" "4000" nil]) "foobar"))
|
|
||||||
(should (string-equal (pud-world-name ["foobar" "localhost" "4000" ""]) "foobar"))
|
|
||||||
(should (string-equal (pud-world-name ["foobar" "localhost" "4000" "guest" "guest"]) "guest@foobar")))
|
|
||||||
|
|
||||||
(ert-deftest pud-world-network-test ()
|
|
||||||
(should (equal (pud-world-network ["foobar" "overthere" "4000" "guest" "guest"]) '("overthere" "4000")))
|
|
||||||
(should (equal (pud-world-network ["foobar" "overthere" 4000 "guest" "guest"]) '("overthere" "4000"))))
|
|
||||||
|
|
||||||
(ert-deftest pud-world-creds-test ()
|
|
||||||
;; Test with no match in authinfo!
|
|
||||||
(should (equal
|
|
||||||
(pud-world-creds ["first" "some-home" 4000 "a-user" "a-pass"])
|
|
||||||
'("a-user" "a-pass")))
|
|
||||||
;; This test works if the following line is in .authinfo:
|
|
||||||
;; machine localhost port 4000 login george password testpass
|
|
||||||
(should (equal
|
|
||||||
(pud-world-creds ["first" "localhost" 4000 "george"])
|
|
||||||
'("george" "testpass"))))
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
* Basics
|
* Basics
|
||||||
|
@ -222,14 +207,14 @@ Using Comint, and hoping to have the ANSI colors displayed.
|
||||||
I’m going to use good ‘ol fashion =telnet= for the connection:
|
I’m going to use good ‘ol fashion =telnet= for the connection:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defvar pud-cli-file-path "telnet" ; ssh!?
|
(defvar pud-cli-file-path "ssh"
|
||||||
"Path to the program used by `run-pud'.")
|
"Path to the program used by `run-pud'.")
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
The pud-cli-arguments, holds a list of commandline arguments: the port.
|
The pud-cli-arguments, holds a list of commandline arguments: the port.
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defvar pud-cli-arguments nil
|
(defvar pud-cli-arguments '("gremlin.howardabrams.com" "telnet")
|
||||||
"A list of arguments to use before the telnet location.")
|
"A list of arguments to use before the telnet location.")
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
@ -238,6 +223,7 @@ The empty and currently disused mode map for storing our custom keybindings inhe
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defvar pud-mode-map
|
(defvar pud-mode-map
|
||||||
(let ((map (nconc (make-sparse-keymap) comint-mode-map)))
|
(let ((map (nconc (make-sparse-keymap) comint-mode-map)))
|
||||||
|
;; example definition
|
||||||
(define-key map "\t" 'completion-at-point)
|
(define-key map "\t" 'completion-at-point)
|
||||||
map)
|
map)
|
||||||
"Basic mode map for `run-pud'.")
|
"Basic mode map for `run-pud'.")
|
||||||
|
@ -253,12 +239,18 @@ This holds a regular expression that matches the prompt style for the MUD. Not s
|
||||||
The name of the buffer:
|
The name of the buffer:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun pud-buffer-name (world)
|
(defvar pud-buffer-name "*Moss and Puddles*"
|
||||||
"Return the buffer name associated with WORLD."
|
"Name of the buffer to use for the `run-pud' comint instance.")
|
||||||
(format "*%s*" (pud-world-name world)))
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
** Run and Connect
|
#+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:
|
The main entry point to the program is the =run-pud= function:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
@ -271,6 +263,7 @@ The main entry point to the program is the =run-pud= function:
|
||||||
- username (can be overridden)
|
- username (can be overridden)
|
||||||
- password (should be overridden)"
|
- password (should be overridden)"
|
||||||
(interactive (list (pud-get-world)))
|
(interactive (list (pud-get-world)))
|
||||||
|
|
||||||
(let* ((pud-program pud-cli-file-path)
|
(let* ((pud-program pud-cli-file-path)
|
||||||
(pud-args (append pud-cli-arguments (pud-world-network world)))
|
(pud-args (append pud-cli-arguments (pud-world-network world)))
|
||||||
(buffer (get-buffer-create (pud-buffer-name world)))
|
(buffer (get-buffer-create (pud-buffer-name world)))
|
||||||
|
@ -294,27 +287,19 @@ Connection and/or re-connection:
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun pud-reconnect (world)
|
(defun pud-reconnect (world)
|
||||||
"Collect and send a `connect' sequence to WORLD.
|
"Collect and send a `connect' sequence to WORLD.
|
||||||
Where WORLD is a vector of world information. NOP if the buffer has no
|
Where WORLD is a vector of world information."
|
||||||
connection or no password could be found."
|
|
||||||
(interactive (list (pud-get-world)))
|
(interactive (list (pud-get-world)))
|
||||||
(when (called-interactively-p)
|
(pop-to-buffer (pud-buffer-name world))
|
||||||
(pop-to-buffer (pud-buffer-name world)))
|
|
||||||
(sit-for 1)
|
(sit-for 1)
|
||||||
|
;; (setq world (pud-get-world))
|
||||||
(message "Attempting to log in...")
|
(let* ((username (pud-world-character world))
|
||||||
(seq-let (username password) (pud-world-creds world)
|
(password (pud-world-password world))
|
||||||
(let* ((conn-str (if (length> world 5)
|
(conn-str (format "connect %s %s\n" username password))
|
||||||
(aref world 5)
|
(process (get-buffer-process (current-buffer))))
|
||||||
pud-default-connection-string))
|
(if process
|
||||||
(conn-full (format conn-str username password))
|
(comint-send-string process conn-str)
|
||||||
(process (get-buffer-process (current-buffer))))
|
(insert conn-str))))
|
||||||
|
#+END_SRC
|
||||||
(message "proc: %s str: '%s'" process conn-full)
|
|
||||||
(goto-char (point-max))
|
|
||||||
(if process
|
|
||||||
(comint-send-string process conn-full)
|
|
||||||
(insert conn-full)))))
|
|
||||||
#+END_SRC
|
|
||||||
* Pud Mode
|
* Pud Mode
|
||||||
Note that =comint-process-echoes=, depending on the mode and the circumstances, may result in prompts appearing twice. Setting =comint-process-echoes= to =t= helps with that.
|
Note that =comint-process-echoes=, depending on the mode and the circumstances, may result in prompts appearing twice. Setting =comint-process-echoes= to =t= helps with that.
|
||||||
|
|
||||||
|
@ -357,90 +342,10 @@ Note that =comint-process-echoes=, depending on the mode and the circumstances,
|
||||||
"Additional expressions to highlight in `pud-mode'.")
|
"Additional expressions to highlight in `pud-mode'.")
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
* Org Babel
|
|
||||||
Wouldn’t it be nice to be able to write commands in an Org file, and send the command to the connected Mud?
|
|
||||||
|
|
||||||
Since I’m connected to more than one MUD, or at least, I often log in with two different characters as two different characters. Let’s have a function that can return all PUD buffers:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defun pud-get-all-buffers ()
|
|
||||||
"Return a list of all buffers with a live PUD connection."
|
|
||||||
(save-window-excursion
|
|
||||||
(seq-filter (lambda (buf)
|
|
||||||
(switch-to-buffer buf)
|
|
||||||
(and
|
|
||||||
(eq major-mode 'pud-mode)
|
|
||||||
(get-buffer-process (current-buffer))))
|
|
||||||
(buffer-list))))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
And a wrapper around =completing-read= for choosing one of the buffers:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defun pud-current-world ()
|
|
||||||
"Return buffer based on user choice of current PUD connections."
|
|
||||||
(let ((pud-buffers (pud-get-all-buffers)))
|
|
||||||
(cond
|
|
||||||
((null pud-buffers) nil)
|
|
||||||
((length= pud-buffers 1) (car pud-buffers))
|
|
||||||
(t
|
|
||||||
(completing-read "Choose connection: "
|
|
||||||
(seq-map (lambda (buf) (buffer-name buf))
|
|
||||||
pud-buffers))))))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Given a buffer and a string, use the =comint-send-string=:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defun pud-send-string (buf-name text)
|
|
||||||
"Send TEXT to a comint buffer, BUF-NAME."
|
|
||||||
(save-window-excursion
|
|
||||||
(save-excursion
|
|
||||||
(pop-to-buffer buf-name)
|
|
||||||
(goto-char (point-max))
|
|
||||||
(comint-send-string (get-buffer-process (current-buffer))
|
|
||||||
(format "%s\n" text)))))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Let’s send the current line or region.
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :results silent
|
|
||||||
(defun pud-send-line (world)
|
|
||||||
"Send the current line or region to WORLD."
|
|
||||||
(interactive (list (pud-current-world)))
|
|
||||||
(unless world
|
|
||||||
(error "No current MUD connection."))
|
|
||||||
|
|
||||||
(let ((text (buffer-substring-no-properties
|
|
||||||
(if (region-active-p) (region-beginning)
|
|
||||||
(beginning-of-line-text) (point))
|
|
||||||
(if (region-active-p) (region-end)
|
|
||||||
(end-of-line) (point)))))
|
|
||||||
(pud-send-string world text)))
|
|
||||||
|
|
||||||
(global-set-key (kbd "<f6>") 'pud-send-line)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Let’s be able to send the current Org block, where all lines in the block are smooshed together to create a single line:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defun pud-send-block (world)
|
|
||||||
"Send the current Org block to WORLD."
|
|
||||||
(interactive (list (pud-current-world)))
|
|
||||||
(unless world
|
|
||||||
(error "No current MUD connection."))
|
|
||||||
(let ((text (thread-last (org-element-at-point)
|
|
||||||
(org-src--contents-area)
|
|
||||||
(nth 2))))
|
|
||||||
(pud-send-string world
|
|
||||||
(replace-regexp-in-string
|
|
||||||
(rx (one-or-more space)) " " text))))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
* Evennia Mode
|
* Evennia Mode
|
||||||
Make a simple mode for basic highlighting of =ev= code.
|
Make a simple mode for basic highlighting of =ev= code.
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
#+BEGIN_SRC emacs-lisp
|
||||||
(define-derived-mode evennia-mode nil "Evennia"
|
(define-derived-mode evennia-mode nil "Evennia"
|
||||||
"Major mode for editing evennia batch command files.
|
"Major mode for editing evennia batch command files.
|
||||||
\\{evennia-mode-map}
|
\\{evennia-mode-map}
|
||||||
|
@ -456,6 +361,29 @@ Make a simple mode for basic highlighting of =ev= code.
|
||||||
)
|
)
|
||||||
"Additional things to highlight in evennia output.")
|
"Additional things to highlight in evennia output.")
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
* Org Babel
|
||||||
|
Wouldn’t it be nice to be able to write commands in an Org file, and send the command to the connected Mud?
|
||||||
|
|
||||||
|
#+BEGIN_SRC emacs-lisp :results silent
|
||||||
|
(defun pud-send-line (world)
|
||||||
|
"Send the current line or region to WORLD."
|
||||||
|
(interactive (list (pud-get-world)))
|
||||||
|
(save-window-excursion
|
||||||
|
(save-excursion
|
||||||
|
(let ((text (buffer-substring-no-properties
|
||||||
|
(if (region-active-p) (region-beginning)
|
||||||
|
(beginning-of-line-text) (point))
|
||||||
|
(if (region-active-p) (region-end)
|
||||||
|
(end-of-line) (point))))
|
||||||
|
(process (get-buffer-process (current-buffer))))
|
||||||
|
(pop-to-buffer (pud-buffer-name world))
|
||||||
|
(goto-char (point-max))
|
||||||
|
(comint-send-string process (format "%s\n" text))))))
|
||||||
|
|
||||||
|
(global-set-key (kbd "<f6>") 'pud-send-line)
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
* Technical Artifacts :noexport:
|
* Technical Artifacts :noexport:
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue