programming musings

Posts tagged "emacs":

02 Jul 2020

the mode lines i have not seen

i don't dislike emacs' mode line, i just wish (possibly out of a fetish for simplicity and minimalistic user interfaces) sometimes it wasn't there.

There are lots of packages out there "modding" the content and looks of the mode line, but in general it's not looks that bother me and i've seen a tendency among them of not being good at covering all the bases: when using any of these packages, i've always found myself having to write extra code to bring back some bit of information or another.

What i really wanted was the ability to hide the mode line for inactive buffers (and, every now and then, the active one too), and, ideally, to do that on a per frame basis (as frames double as my workspaces, and they're thematic, i want, for instance, mode lines in all my w3m inactive buffers, and usually no mode line for my active PDF document).

Sometimes, one's wishes just come effortlessly true:

(defun jao-toggle--face-height (face &optional all-frames)
  (let* ((h (face-attribute face :height (window-frame)))
         (nh (if (eq 'unspecified h) 1 'unspecified)))
    (set-face-attribute face (when (not all-frames) (window-frame)) :height nh)))

(defun jao-toggle-mode-line (&optional all-frames)
  (interactive)
  (jao-toggle--face-height 'mode-line all-frames))

(defun jao-toggle-inactive-mode-line (&optional all-frames)
  (interactive)
  (jao-toggle--face-height 'mode-line-inactive all-frames))

(defun jao-echo-mode-line ()
  (interactive)
  (message "%s" (format-mode-line mode-line-format)))

That's it: all the requirements above fullfilled in a dozen lines of elisp. As you see, the trick is very simple: one can set the height of the mode line's face to 1, making it display as a line, and faces are frame-local entities.

In my init file, i then call (jao-toggle-inactive-mode-line t), turn it back on some functions initialising my dedicated frames. jao-echo-mode-line comes in handy when i just want to see my active mode line's contents momentarily. i even found a good place in the thinkpad keyboard for shortcuts, with keys i literally never use for anything:

(global-set-key (kbd "<home>") #'jao-toggle-inactive-mode-line)
(global-set-key (kbd "<end>") #'jao-toggle-mode-line)
(global-set-key (kbd "<insert>") #'jao-echo-mode-line)
Tags: emacs
13 May 2020

unlearn

For years, i've been using C-x p, C-x o and C-c <n> to move to other windows, but with ace window i am substituting all of them with M-o. Problem is, muscle memory interferes and i find myself clumsily moving around (and often lost) with the former ones. Or i did, before i followed an advice from Stefan Monnier in emacs-devel: unbind those keys you want to forget, and you'll get an error when you relapse.

In my case, that was just

(global-set-key (kbd "C-x o") nil)

together with commeting out the other one's definitions. For the record, in Stefan's case, he was trying to remember to use the newer C-h o (symbol help) instead of C-h f (function help) or C-h v (variable help), so he unbound the last two.

After a while, when one's muscles have forgotten, one can re-enable the old bindings, for the few cases where they're justified.

Tags: emacs programming
12 May 2020

ace window

As i've mentioned in a previous post, i organise my emacs sessions in workspaces, a.k.a. frames, a.k.a. a thematic tiling of windows. It is therefore important to have quick ways of jumping from a window to another. Until very recently, i used a home-cooked collection of shortcuts (C-c 1, C-c 2C-c n) that would move my point to the nth window in the workspace (i trained myself to count them quick enough, i suppose), and used that together with the stock C-x o and with C-x p bound to (other-window -1), for something similar to "previous window", to move around. But i've discovered a better way.

It's called ace-window, and it's so easy to use that i'll just give you my configuration below:

(use-package ace-window
  :ensure t
  :init (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l)
              aw-char-position 'left
              aw-ignore-current nil
              aw-leading-char-style 'char
              aw-scope 'frame)
  :bind (("M-o" . ace-window)
         ("M-O" . ace-swap-window)))

With exwm, i also bind s-o and s-O, so i can jump when focus is on an X buffer. My only complain is that the character overlay is not visible for X buffers, but i use so few of them that it's nothing to worry about.

Tags: emacs
08 May 2020

a gnus enters a side-bar

gnus-side-bar.png

i typically organize my computing sessions around workspaces, which in emacs are simply glorified default window configurations, sometimes in a one-to-one relationship with emacs frames1. That's specially natural if you use exwm, where there's a concept of workspace essentially equivalent to emacs frame. So here too i'll have a "coding" workspace with programming buffers, a "browsing" one inhabited by emacs-w3m buffers in a two-pane layout, an "X" for the few non-emacs applications i use, and one devoted to "email/calendar/agenda", where Gnus plays a central role.

Until recently, my "Gnus" workspace consisted only of Gnus-related windows (the group, summary, message triumvirate, basically) and i would open my org agenda, or visit the calendar in that workspace as needed. But the thing is, a 1920x1080 frame (or 272x70 columns one, with my current default font) is a lot of space for browsing email alone, and it'd be nice to have there a "side bar" of sorts always displaying (say, on a right column occupying a third of the horizontal space) my "inbox.org", agenda and calendar, while browsing emails and news with Gnus' three usual panes sharing the other two thirds on the left of the screen.

Enter Gnus window configurations, which can fulfill my desires with the following configuration:

(let ((side-bar '(vertical 1.0
                           ("inbox.org" 0.4)
                           ("*Org Agenda*" 1.0)
                           ("*Calendar*" 8))))
  (gnus-add-configuration
   `(article
     (horizontal 1.0
                 (vertical 63 (group 1.0))
                 (vertical 120
                           (summary 0.25 point)
                           (article 1.0))
                 ,side-bar)))

  (gnus-add-configuration
   `(group (horizontal 1.0 (group 183 point) ,side-bar)))

  (gnus-add-configuration
   `(message (horizontal 1.0 (message 183 point) ,side-bar)))

  (gnus-add-configuration
   `(reply-yank (horizontal 1.0 (message 183 point) ,side-bar)))

  (gnus-add-configuration
   `(summary
     (horizontal 1.0
                 (vertical 63 (group 1.0))
                 (vertical 120 (summary 1.0 point))
                 ,side-bar)))

  (gnus-add-configuration
   `(reply
     (horizontal 1.0
                 (message 90 point)
                 (article 100)
                 ,side-bar))))

You can see the "side bar" with the three buffers there, which one can change for whatever others are useful, and how we define layouts for the basic Gnus views: one for the group view, another for seeing the summary and messages (in a 3-pane configuration), and we also keep the side-bar visible when composing and replying to email.

All that i needed besides that in my gnus.org was a function to initialize the workspace calling calendar and org-agenda-list and visiting my inbox.org file and this hook:

  (defun jao-gnus--summary-done ()
    (save-window-excursion
      (org-agenda-list)))

(add-hook 'gnus-summary-prepared-hook #'jao-gnus--summary-done)

which updates my agenda view in the side-bar every time i enter an email or news group (which is often enough to keep it fresh). As a nice side-effect of this arrangement, i've managed to pay more attention to my Org agenda: i kept forgetting looking at it, but i never forget to check my email :)

Footnotes:

1

But not necessarily; all one needs is a way to store the current window configuration in a register and give it a name, and then creating a bunch of shortcuts to recover those configurations by name.

Tags: emacs
02 May 2020

transient

Emacs being, essentially, my operating system and window manager, i've had since i remember a set of keybinding maps grouped by functionality (sleep and related operations, media player control, network access, and so on), whereby every group of commands starts with the same prefix. E.g., all my commands controlling media have a keybinding starting with `C-c m`. Nothing earthshaking, and pretty common. Recently, the transient library has made my life easier in this department.

For instance, this is how i configured my keybindings for the "media keymap":

(global-set-key [(control ?c) ?m ?L] #'jao-player-show-lyrics)
(global-set-key [(control ?c) ?m ?b] #'jao-player-browse)
(global-set-key [(control ?c) ?m ?d] #'jao-player-volume-lower)
(global-set-key [(control ?c) ?m ?k] #'jao-player-stop)
(global-set-key [(control ?c) ?m ?l] #'jao-player-list)
(global-set-key [(control ?c) ?m ?L] #'jao-player-show-lyrics)
(global-set-key [(control ?c) ?m ?m] #'jao-player-toggle)
(global-set-key [(control ?c) ?m ?N] #'jao-player-random-album)
(global-set-key [(control ?c) ?m ?n] #'jao-player-next)
(global-set-key [(control ?c) ?m ?p] #'jao-player-previous)
(global-set-key [(control ?c) ?m ?q] #'jao-player-search)
(global-set-key [(control ?c) ?m ?u] #'jao-player-volume-raise)
(global-set-key [(control ?c) ?m ?w] #'jao-player-osd)
(global-set-key [(control ?c) ?m ?W] #'jao-player-echo)
(global-set-key [(control ?c) ?m ?x] #'jao-player-start)

Over time, most of these bindings become second nature, and i remember easily the ones i use most. But there are those that i use rarely, and on occasion i simply forget they exist at all. To ameliorate that problem, i could always use C-c C-m C-h, which would show me a list of available completions (or, a bit more fancily, which-key), but with transient i can do even better: define a transient menu collecting all the commands, grouped by type, like this:

(define-transient-command jao-media-menu ()
  [["Player"
    ("m" "toogle" jao-player-toggle)
    ("s" "start" jao-player-start)
    ("n" "next" jao-player-next)
    ("p" "previous" jao-player-previous)
    ("k" "stop" jao-player-stop)
    ("w" "now playing" jao-player-osd)
    ("W" "now playing (text)" jao-player-echo)]
   ["Songs"
    ("b" "browse" jao-player-browse)
    ("q" "search" jao-player-search)
    ("l" "show play list" jao-player-list)
    ("L" "show lyrics" jao-player-show-lyrics)
    ("N" "random album" jao-player-random-album)]
   ["Volume"
    ("v" "show volume" jao-player-show-volume)
    ("M" "master toggle" jao-mixer-master-toogle)
    ("d" "master down" jao-mixer-master-down)
    ("u" "master up" jao-mixer-master-up)
    ("D" "capture down" jao-mixer-capture-down)
    ("U" "capture up" jao-mixer-capture-up)
    ("x" "reset exwm" exwm-reset)]])

and bind the menu to my prefix, C-c m:

(global-set-key (kbd "C-c m") #'jao-media-menu)

Now, upon pressing C-c m i see immediately a pop-up with all possible completions, as a nice reminder of what's available, and unobstrusive in that, for those key chords i do remember well, the invocation mechanics is exactly the same.

transient.png

Another really, really nice thing about transient is its excellent documentation. It is for such care and attention to detail that i am a fan of the work of Jonas Bernoulli.

Tags: emacs
26 Feb 2020

literate programming

I got started with literate programming many years ago, out of admiration for almost everything else i knew done by Donal Knuth, and tried my hand at it in some toyish projects in OCaml and Scheme. So it wasn't without lack of enthusiasm that i plunged into the literate world.

For reasons i've forgotten (probably better Emacs integration, perhaps simpler syntax) i chose noweb over funnelweb (perhaps, i would recommend the reverse these days, if it weren't for org's babel) and wrote a set of make functions to help create multi-file noweb projects. If memory serves, i was writing an MMIX interpreter in OCaml and a distributed job scheduler in Scheme, and i tried in both cases to use a literate style.

On paper, literate programming is great. Just take a look at Knuth's MMIXware book, Hanson's C Interfaces and Implementations or Jay MacCarthy's blog: that's definitely how i want to read and understand programs for fun and profit.

As i soon learned, that's however not the way i wanted to write programs.

Programming is for me mostly a dialectic process of understanding problems and their solutions, always evolving and always provisional (and most often buggy!), and i seemed never to be able to frozen the solution at hand in a structure that were not in flux. When changing the structure of your solution means rewriting your essay, there's too much friction.

In addition, i tend to build my programs in a bottom-up fashion. Reorganisation comes later, so it's again a chore to start by trying to write down an essay: it's only when i've reached the top that i can see the landscape and properly describe it.

And of course there's the REPL: interactive programming using a live image is, or seemed to be for a long time, at odds with a literate approach to writing your programs.

So, as many others, i just shelved LP for the day i would write a book, and there it's been, untouched, until recently.

It all began when i found myself, more and more often, writing solutions to customer problems that needed the collaboration of several programming languages, with a sprinkle of shell scripting. That need is even more pressing when moreover one routinely uses DSLs (as we do in BigML with Flatline or WhizzML). Together with all the programming bits, one needs of course to deliver decent documentation explaining it. So it's the perfect storm for an org babel document, specially when one combines it with poly-org. This package allows a truly poly-mode experience in the org buffer itself: when one enters a src block, emacs switches to its language's major mode, and one can do things like sending expressions to the corresponding REPL, or propertly indenting without having to pop-up a new buffer as in babel's default modus operandi. It might seem a little thing, but to me it's made a great difference. Add to that the all but excellent export capabilities of org, and the easiness with which one can tangle out different files from the same org file, together with having at your fingertips the immense gamut of functionality offered by org mode, and it's very difficult not to become a fan of this form of literate programming.

Another setting in which babel is lately winning my heart is as a tool for writing emacs lisp packages. It all began with writing my init.el using org, at first tangling it semi-automatically on emacs startup. But then i discovered literate-elisp, a package that teaches emacs to load org files directly, reading the elisp source blocks within, and, most importantly, retaining the source code positions for them. That means that, when one jumps to a function or variable definition1, one lands directly into the org file: no intermediate tangled elisp is needed. That's, again, incredibly handy, specially when combined with poly-org. I can now write packages like signel in an org file that is directly exportable to a blog post, and i find myself writing at the very same time and place both the emacs lisp program and the blog post explaining how it works.

All that said, this new penchant for LP has its obvious limits: i'm aware that it is not going to be comfortable for projects that aren't as self-contained and small as the ones i mention above. For instance, i don't think we could write our clojure backend as a bunch of org files, much as i wish i were wrong.

Footnotes:

1

i've had since forever (it's been so long that i've forgotten where i stole it) a little utility function that let's me conveniently jump a la geiser using M-. to any emacs lisp definiton:

(defun elisp-find-definition (name)
  "Jump to the definition of the function (or variable) at point."
  (interactive (list (thing-at-point 'symbol)))
  (cond (name
         (let ((symbol (intern-soft name))
               (search (lambda (fun sym)
                         (let* ((r (save-excursion (funcall fun sym)))
                                (buffer (car r))
                                (point (cdr r)))
                           (cond ((not point)
                                  (error "Found no definition for %s in %s"
                                         name buffer))
                                 (t
                                  (switch-to-buffer buffer)
                                  (goto-char point)
                                  (recenter 1)))))))
           (cond ((fboundp symbol)
                  (elisp-push-point-marker)
                  (funcall search 'find-function-noselect symbol))
                 ((boundp symbol)
                  (elisp-push-point-marker)
                  (funcall search 'find-variable-noselect symbol))
                 (t
                  (message "Symbol not bound: %S" symbol)))))
        (t (message "No symbol at point"))))
Tags: programming emacs
23 Feb 2020

signel, a barebones signal chat on top of signal-cli

Unlike most chat systems in common use, Signal lacks a decent emacs client. All i could find was signal-msg, which is able only to send messages and has a readme that explicitly warns that its is not a chat application. Skimming over signal-msg's code i learnt about signal-cli, a java-based daemon that knows how to send and receive signal messages, and how to link to a nearby phone, or register new users. And playing with it i saw that it can output its activities formatted as JSON, and that offers (when run in daemon mode) a DBUS service that can be used to send messages.

Now, emacs knows how to run a process and capture its output handling it to a filter function, and comes equipped with a JSON parser and a set of built-in functions to talk to DBUS buses.

So how about writing a simple Signal chat app for emacs? Let's call it signel, and write it as a blog post in literate org-mode.

Starting a process

We are going to need a variable for our identity (telephone number), and a list of contact names (until i discover how to get them directly from signal-cli):

(require 'cl-lib)

(defvar signel-cli-user "+44744xxxxxx")
(defvar signel-contact-names '(("+447xxxxxxxx" . "john")
                               ("+346xxxxxxxx" . "anna")))

and a simple function to get a contact name given its telephone number:

(defun signel--contact-name (src)
  (or (alist-get src signel-contact-names nil nil #'string-equal) src))

We are also going to need the path for our signal-cli executable

(defvar signel-cli-exec "signal-cli")

Starting the signal-cli process is easy: make-process provides all the necessary bits. What we need is essentially calling

signal-cli -u +44744xxxxxx daemon --json

associating to the process a buffer selected by the function signel--proc-buffer . While we are at it, we'll write also little helpers for users of our API.

(defun signel--proc-buffer ()
  (get-buffer-create "*signal-cli*"))

(defun signel-signal-cli-buffer ()
  (get-buffer "*signal-cli*"))

(defun signel-signal-cli-process ()
  (when-let ((proc (get-buffer-process (signel-signal-cli-buffer))))
    (and (process-live-p proc) proc)))
(defun signel-start ()
  "Start the underlying signal-cli process if needed."
  (interactive)
  (if (signel-signal-cli-process)
      (message "signal-cli is already running!")
    (let ((b (signel--proc-buffer)))
      (make-process :name "signal-cli"
                    :buffer b
                    :command `(,signel-cli-exec
                               "-u"
                               ,signel-cli-user
                               "daemon" "--json")
                    :filter #'signel--filter)
      (message "Listening to signals!"))))

Parsing JSON

We've told emacs to handle any ouput of the process to the function signel--filter, which we're going to write next. This function will receive the process object and its latest output as a string representing a JSON object. Here's an example of the kind of outputs that signal-cli emits:

{
  "envelope": {
    "source": "+4473xxxxxxxx",
    "sourceDevice": 1,
    "relay": null,
    "timestamp": 1582396178696,
    "isReceipt": false,
    "dataMessage": {
      "timestamp": 1582396178696,
      "message": "Hello there!",
      "expiresInSeconds": 0,
      "attachments": [],
      "groupInfo": null
    },
    "syncMessage": null,
    "callMessage": null,
    "receiptMessage": null
  }
}

Everything seems to be always inside envelope, which contains objects for the possible messages received. In the example above, we're receiving a message from a source contact. We can also receive receipt messages, telling us whether our last message has been received or read; e.g.:

{
  "envelope": {
    "source": "+4473xxxxxxxx",
    "sourceDevice": 1,
    "relay": null,
    "timestamp": 1582397117584,
    "isReceipt": false,
    "dataMessage": null,
    "syncMessage": null,
    "callMessage": null,
    "receiptMessage": {
      "when": 1582397117584,
      "isDelivery": true,
      "isRead": false,
      "timestamps": [
        1582397111524
      ]
    }
  }
}

A bit confusingly, that delivery notification has a receiptMessage, but its isReceipt flag is set to false. At other times, we get isReceipt but no receiptMessage:

{
  "envelope": {
    "source": "+346xxxxxxxx",
    "sourceDevice": 1,
    "relay": null,
    "timestamp": 1582476539281,
    "isReceipt": true,
    "dataMessage": null,
    "syncMessage": null,
    "callMessage": null,
    "receiptMessage": null
  }
}

It is very easy to parse JSON in emacs and extract signal-cli's envelopes (and it's become faster in emacs 27, but the interface is a bit different):

(defun signel--parse-json (str)
  (if (> emacs-major-version 26)
      (json-parse-string str
                         :null-object nil
                         :false-object nil
                         :object-type 'alist
                         :array-type 'list)
    (json-read-from-string str)))

(defun signel--msg-contents (str)
  (alist-get 'envelope (ignore-errors (signel--parse-json str))))

Here i am being old-school and opting to receive JSON dicitionaries as alists (rather than hash maps, the default), and arrays as lists rather than vectors just because lisps are lisps for a reason. I'm also going to do some mild nil punning, hence the choice for null and false representations.

Once the contents of the envelope is extracted, it's trivial (and boring) to get into its components:

(defun signel--msg-source (msg) (alist-get 'source msg))

(defun signel--msg-data (msg)
  (alist-get 'message (alist-get 'dataMessage msg)))

(defun signel--msg-timestamp (msg)
  (if-let (msecs (alist-get 'timestamp msg))
      (format-time-string "%H:%M" (/ msecs 1000))
    ""))

;; emacs 26 compat
(defun signel--not-false (x)
  (and (not (eq :json-false x)) x))

(defun signel--msg-receipt (msg)
  (alist-get 'receiptMessage msg))

(defun signel--msg-is-receipt (msg)
  (signel--not-false (alist-get 'isReceipt msg)))

(defun signel--msg-receipt-timestamp (msg)
  (when-let (msecs (alist-get 'when (signel--msg-receipt msg)))
    (format-time-string "%H:%M" (/ msecs 1000))))

(defun signel--msg-is-delivery (msg)
  (when-let ((receipt (signel--msg-receipt msg)))
    (signel--not-false (alist-get 'isDelivery msg))))

(defun signel--msg-is-read (msg)
  (when-let ((receipt (signel--msg-receipt msg)))
    (signel--not-false (alist-get 'isRead msg))))

A process output filter

We're almost ready to write our filter. It will:

  • For debugging purposes, insert the raw JSON string in the process buffer.
  • Parse the received JSON string and extract its envelope contents.
  • Check wether it has a source and either message data or a receipt timestamp.
  • Dispatch to a helper function that will insert the data or notification in a chat buffer.

Or, in elisp:

(defvar signel--line-buffer "")

(defun signel--filter (proc str)
  (signel--ordinary-insertion-filter proc str)
  (let ((str (concat signel--line-buffer str)))
    (if-let ((msg (signel--msg-contents str)))
        (let ((source (signel--msg-source msg))
              (stamp (signel--msg-timestamp msg))
              (data (signel--msg-data msg))
              (rec-stamp (signel--msg-receipt-timestamp msg)))
          (setq signel--line-buffer "")
          (when source
            (signel--update-chat-buffer source data stamp rec-stamp msg)))
      (setq signel--line-buffer
            (if (string-match-p ".*\n$" str) "" str)))))

We've had to take care of the case when the filter receives input that is not a complete JSON expression: in the case of signal-cli, that only happens when we haven't seen yet an end of line.

The function to insert the raw contents in the process buffer is surprisingly hard to get right, but the emacs manual spells out a reasonable implementation, which i just copied:

(defun signel--ordinary-insertion-filter (proc string)
  (when (and proc (buffer-live-p (process-buffer proc)))
    (with-current-buffer (process-buffer proc)
      (let ((moving (= (point) (process-mark proc))))
        (save-excursion
          ;; Insert the text, advancing the process marker.
          (goto-char (process-mark proc))
          (insert string)
          (set-marker (process-mark proc) (point)))
        (if moving (goto-char (process-mark proc)))))))

It's not an emacs app if it doesn't have a new mode

With that out of the way, we just have to insert our data in an appropriate buffer. We are going to associate a separate buffer to each source, using for that its name:

(defvar-local signel-user nil)

(defun signel--contact-buffer (source)
  (let* ((name (format "*%s" (signel--contact-name source)))
         (buffer (get-buffer name)))
    (unless buffer
      (setq buffer (get-buffer-create name))
      (with-current-buffer buffer
        (signel-chat-mode)
        (setq-local signel-user source)
        (insert signel-prompt)))
    buffer))

where, as is often the case in emacs, we are going to have a dedicated major mode for chat buffers, called signel-chat-mode. For now, let's keep it really simple (for the record, this is essentially a copy of what ERC does for its erc-mode):

(defvar signel-prompt ": ")

(define-derived-mode signel-chat-mode fundamental-mode "Signal"
  "Major mode for Signal chats."
  (when (boundp 'next-line-add-newlines)
    (set (make-local-variable 'next-line-add-newlines) nil))
  (setq line-move-ignore-invisible t)
  (set (make-local-variable 'paragraph-separate)
       (concat "\C-l\\|\\(^" (regexp-quote signel-prompt) "\\)"))
  (set (make-local-variable 'paragraph-start)
       (concat "\\(" (regexp-quote signel-prompt) "\\)"))
  (setq-local completion-ignore-case t))

Note how, in signel--contact-buffer, we're storing the user identity associated with the buffer (its source) in a buffer-local variable named signel-user that is set after enabling signel-chat-mode: order here matters because the major mode activation cleans up the values of any local variables previously set (i always forget that!).

And a customization group

We're going to need a couple of new faces for the different parts of inserted messages, so we'll take the chance to be tidy and introduce a customization group:

(defgroup signel nil "Signel")

(defface signel-contact '((t :weight bold))
  "Face for contact names."
  :group 'signel)

(defface signel-timestamp '((t :foreground "grey70"))
  "Face for timestamp names."
  :group 'signel)

(defface signel-notice '((t :inherit signel-timestamp))
  "Face for delivery notices."
  :group 'signel)

(defface signel-prompt '((t :weight bold))
  "Face for the input prompt marker."
  :group 'signel)

(defface signel-user '((t :foreground "orangered"))
  "Face for sent messages."
  :group 'signel)

(defface signel-notification '((t :foreground "burlywood"))
  "Face for notifications shown by tracking, when available."
  :group 'signel)

Displaying incoming messages

We have now almost all the ingredients to write signel--update-chat-buffer, the function that inserts the received message data into the chat buffer. Let's define a few little functions to format those parts:

(defun signel--contact (name)
  (propertize name 'face 'signel-contact))

(defun signel--timestamp (&rest p)
  (propertize (apply #'concat p) 'face 'signel-timestamp))

(defun signel--notice (notice)
  (propertize notice 'face 'signel-notice))

(defun signel--insert-prompt ()
  (let ((inhibit-read-only t)
        (p (point)))
    (insert signel-prompt)
    (set-text-properties p (- (point) 1)
                         '(face signel-prompt
                           read-only t front-sticky t rear-sticky t))))

(defun signel--delete-prompt ()
  (when (looking-at-p (regexp-quote signel-prompt))
    (let ((inhibit-read-only t))
      (delete-char (length signel-prompt)))))

(defun signel--delete-last-prompt ()
  (goto-char (point-max))
  (when (re-search-backward (concat "^" (regexp-quote signel-prompt)))
    (signel--delete-prompt)))

With that, we're finally ready to insert messages in our signel chat buffers:

(defcustom signel-report-deliveries nil
  "Whether to show message delivery notices."
  :group 'signel
  :type 'boolean)

(defcustom signel-report-read t
  "Whether to show message read notices."
  :group 'signel
  :type 'boolean)

(defun signel--prompt-and-notify ()
  (signel--insert-prompt)
  (when (fboundp 'tracking-add-buffer)
    (tracking-add-buffer (current-buffer) '(signel-notification))))

(defun signel--needs-insert-p (data stamp rec-stamp msg)
  (or data
      (and (or rec-stamp stamp)
           (not (string= source signel-cli-user))
           (or signel-report-deliveries
               (and signel-report-read (signel--msg-is-read msg))))))

(defun signel--update-chat-buffer (source data stamp rec-stamp msg)
  (when (signel--needs-insert-p data stamp rec-stamp msg)
    (when-let ((b (signel--contact-buffer source)))
      (with-current-buffer b
        (signel--delete-last-prompt)
        (if data
            (let ((p (point)))
              (insert (signel--timestamp "[" stamp "] ")
                      (signel--contact (signel--contact-name source))
                      signel-prompt
                      data
                      "\n")
              (fill-region p (point)))
          (let ((is-read (signel--msg-is-read msg)))
            (insert (signel--timestamp "*" (or rec-stamp stamp) "* ")
                    (signel--notice (if is-read "(read)" "(delivered)"))
                    "\n")))
        (signel--prompt-and-notify)
        (end-of-line)))))

There are some rough edges in the above implementation that must be polished should signel ever be released in the wild. For one, proper handling of timestamps and their formats. And of course notifications should be much more customizable (here i'm using Circe's tracking.el if available).

Sending messages: the DBUS interface

With that, we're going to receive and display messages and simple receipts, and i'm sure that we will feel the urge to answer some of them. As mentioned above, signal-cli let's us send messages via its DBUS interface. In a nutshell, if you want to send MESSAGETEXT to a RECIPIENT you'd invoke something like:

dbus-send --session --type=method_call \
          --dest="org.asamk.Signal" \
          /org/asamk/Signal \
          org.asamk.Signal.sendMessage \
          string:MESSAGETEXT array:string: string:RECIPIENT

That is, call the method sendMessage of the corresponding service interface with three arguments (the second one empty). Using emacs' dbus libray one can write the above as:

(defun signel--send-message (user msg)
  (dbus-call-method :session "org.asamk.Signal" "/org/asamk/Signal"
                    "org.asamk.Signal" "sendMessage"
                    :string msg
                    '(:array)
                    :string user))

The only complicated bit is being careful with the specification of the types of the method arguments: if one gets them wrong, DBUS will simply complain and say that the method is not defined, which was confusing me at first (but of course makes sense because DBUS allows overloading method names, so the full method spec must include its signature).

We want to read whatever our user writes after the last prompt and send it via the little helper above. Here's our interactive command for that:

(defun signel-send ()
  "Read text inserted in the current buffer after the last prompt and send it.

The recipient of the message is looked up in a local variable set
when the buffer was created."
  (interactive)
  (goto-char (point-max))
  (beginning-of-line)
  (let* ((p (point))
         (plen (length signel-prompt))
         (msg (buffer-substring (+ p plen) (point-max))))
    (signel--delete-prompt)
    (signel--send-message signel-user msg)
    (insert (signel--timestamp (format-time-string "(%H:%M) ")))
    (fill-region p (point-max))
    (goto-char (point-max))
    (set-text-properties p (point) '(face signel-user))
    (insert "\n")
    (signel--insert-prompt)))

and we can bind it to the return key in signal chat buffers:

(define-key signel-chat-mode-map "\C-m" #'signel-send)

And we are going sometimes to want to talk to contacts that don't have yet said anything and have, therefore, no associated chat buffer:

(defun signel-query (contact)
  "Start a conversation with a signal contact."
  (interactive (list (completing-read "Signal to: "
                                      (mapcar #'cdr-safe signel-contact-names))))
  (let ((phone (alist-get contact
                          (cl-pairlis (mapcar #'cdr signel-contact-names)
                                      (mapcar #'car signel-contact-names))
                          nil nil #'string-equal)))
    (when (not phone)
      (error "Unknown contact %s" contact))
    (pop-to-buffer (signel--contact-buffer phone))))

There are of course lots of rough edges and missing functionality in this incipient signel, but it's already usable and a nice demonstration of how easy it is to get the ball rolling in this lisp machine of ours!

Tags: emacs
11 Feb 2020

simplicity

I like simple things. As simple as possible, but not simpler: they should live well in my little emacs universe. Bastian Bechtold's org-static-blog, a static site generator using org-mode, is the latest star in that virtual world.

You are looking at the result. It retrospect, it took me an absurdly long time to do something as natural as incorporating blogging to the platform where i spend most of my computer time, using the most flexible markup language i know. Well, better late than never, i suppose!

Revamping jao.io to this new incarnation took little time. Converting exisiting markdown posts to org was straghtforward with pandoc, and org-static-blog needs very little boilerplate after the conversion Org's exporting capabilities are all but amazing, so it's easy to build on them: one can do it right, like Bastian, or get carried away and create a complex monster.

For the record, this is all the configuration that was required, where it is perhaps worth mentioning how i extract the list of tags from the list of HTML files pointing to them1::

(use-package org-static-blog
  :ensure t
  :init
  (setq org-static-blog-use-preview t
        org-static-blog-preview-convert-titles t
        org-static-blog-preview-ellipsis "..."
        org-static-blog-enable-tags t
        org-static-blog-publish-url "https://jao.io/blog/"
        org-static-blog-publish-title "programming musings"
        org-static-blog-posts-directory "~/doc/jao.io/org/"
        org-static-blog-drafts-directory "~/doc/jao.io/org/drafts/"
        org-static-blog-publish-directory "~/doc/jao.io/blog/")

  (setq jao-org-blog-tags
        (mapcar (lambda (f)
                  (string-match "tag-\\(.+\\)\\.html" f)
                  (format "<a href=\"/blog/%s\">%s</a>"
                          f (match-string 1 f)))
                (directory-files "~/doc/jao.io/blog" nil "tag-.*")))

  (setq org-static-blog-page-header
        (concat
         "<meta name=\"author\" content=\"jao\">"
         "<meta name=\"referrer\" content=\"no-referrer\">"
         "<link href= \"/static/style.css\" rel=\"stylesheet\"
                type=\"text/css\" />"
         "<link rel=\"icon\" href=\"static/favicon.ico\">")

        org-static-blog-page-preamble
        (concat
         "<div class=\"header\">"
         "  <a href=\"https://jao.io\">programming musings</a>"
         "  <div class=\"sitelinks\">"
         "    <a href=\"/blog/about.html\">about</a>"
         "    | <a href=\"/blog/hacking.html\">hacking</a>"
         "    | <a href=\"/blog/archive.html\">archive</a>"
         "    | <div class=\"dropdown\">"
         "       <a href=\"/blog/tags/html\" class=\"dropbtn\">tags</a>"
         "       <div class=\"dropdown-content\">"
         (mapconcat #'identity jao-org-blog-tags "")
         "       </div>"
         "      </div>"
         "    | <a href=\"/blog/rss.xml\">rss</a>"
         "  </div>"
         "</div>")

        org-static-blog-page-postamble
        (with-temp-buffer
          (insert-file-contents "~/.emacs.d/postamble.html")
          (buffer-string))))

with ~/.emacs.d/blog-postamble.html containing mostly license boilerplate2:

<div id="archive"><a href="/blog/archive.html">Other posts</a></div>
<center>
  <a rel="license" href="https://creativecommons.org/licenses/by-sa/3.0/">
  <img alt="Creative Commons License" style="border-width:0"
       src="https://i.creativecommons.org/l/by-sa/3.0/88x31.png" />
  </a>
  <br />
  <span xmlns:dct="https://purl.org/dc/terms/"
        href="https://purl.org/dc/dcmitype/Text" property="dct:title"
        rel="dct:type">jao.io</span> by
  <a xmlns:cc="https://creativecommons.org/ns#" href="https://jao.io"
     property="cc:attributionName" rel="cc:attributionURL">jao</a>
  is licensed under a
  <a rel="license" href="https://creativecommons.org/licenses/by-sa/3.0/">
    Creative Commons Attribution-ShareAlike 3.0 Unported License</a>.
</center>

Then i remembered my old WordPres blogs (programming musings, minor emacs wizardry, and physics musings), collecting dust for more than a decade now, and wondered what would it take to rescue those posts. Turns out one of those other blogging tools, pelican, has an importer, pelican-import, that knows about the XML one can export from WordPress. With that, i had a collection or .rst files that i knew how to move to org files from the comfort of my eshell:

$ for f in *.rst { pandoc -f rst -t org -o $f.org $f }

And then i started writing a bit of elisp to add the blog headers, but didn't go too far: how many of those posts where worth converting? Well, i started skimming over them, and was pleased to discover that my little self fiteen years ago was rather clueless (so i've either learnt something these years or, at least, gained some confidence), and only a handful of them were both salvaging: no elisp needed! If you're curious, you can find them under the tags auld, emacs, and physics, or just looking for the really old entries in the archive.

Footnotes:

1

So this list can be generated only after the set of tags is decided (in my case, by exporting the existing posts once), and has the drawback of having to re-regenerate all the HTML if one ever adds a new tag (remember that these pages are static). But org-static-blog is so much faster doing it than i writing posts that i'm not going to worry.

2

The file is actually generated by tangling a SRC block in my init.org file, and using variable interpolation one wouldn't even need to use an external file, but that's for another post.

Tags: emacs
29 Nov 2014

emacs tip: tweeting from emacs-w3m

To follow twitter, i connect to a local Bitlbee server via emacs, using the Circe IRC client, and i browse the web using emacs-w3m.

I needed a command to automatically tweet about the page i am visiting. This being the emacs environment, i just wrote it in elisp.

The command will check if there's a region selected in the buffer using use-region-p, in which case its contents is used as the tweet body, falling back to the page's title. The only user-specific bit in the code below is the name that Bitlbee gives to the buffer of my twitter account, #twitter_jaotwits.

(defun jao-w3m-tweet (&optional from to)
  (interactive (when (use-region-p) (list (region-beginning) (region-end))))
  (let ((url (or w3m-current-url (error "No URL to tweet!")))
        (txt (if (and from to)
                 (buffer-substring from to)
               (w3m-current-title))))
    (switch-to-buffer "#twitter_jaotwits")
    (goto-char (point-max))
    (insert "'" txt "' -- " url)))

As you see, the command switches to the Bitlbee buffer and writes the contents of the tweet, but falls short of sending it, letting me to review the text before pushing return.

Tags: emacs
20 Jan 2007

record, play, re-play in emacs

Gentle reader Marc Donner has sent me an email sharing one of his favorite emacs hacks:

(global-set-key [f10]  'start-kbd-macro)
(global-set-key [f11]  'end-kbd-macro)
(global-set-key [f12]  'call-last-kbd-macro)

Yes, I could type C-x ( to start the macro and C-x ) to end the macro and C-x e to execute the macro, but that requires that my fingers be more nimble than they really are. Particularly because ( and ) are shifted keys.

This way I can start a macro with a single button push, finish it with another, and then repetitively run it by pressing a third button over and over.

As it happens, Emacs 22 comes with the functionality Marc assigns to his F12 key built-in: press just e right after executing the macro the first time, as many times as you need. There's also the possibility of using a numerical prefix to specify how many times the macro should be executed (as in M-10 C-x e to execute it ten times), and passing zero as the prefix will keep on (re)executing it until the end of the buffer is reached.

One can also achieve macro re-plays with the command repeat, bound by default to C-x z. repeat works quite a bit like Vim's '.': it, well, repeats the most recently executed command, and keeps repeating if you keep pressing z. So, instead of C-x e e e e e ..., one can C-x e C-z z z z.... Not as convenient, but this works in Emacs 21, and, besides, repeat is an interesting command on its own.

Saying that keyboard macro recording is a useful Emacs feature would be an understatement. You can record almost any Emacs operation and, therefore, keyboard macros can automate quite non-trivial tasks. For instance, i've just used them a couple hours ago to generate C function body stubs from their declarations in a header file. Just for the fun of it, here's a screencast:

Note how you're not limited to a single buffer, and how we use generic operations (like searching for next space to kill and yank the function's return type). It's also important to left the cursor in the right position for the next execution of the macro. At first you'll make little mistakes, but fear not: you can actually edit the last recorded macro with kmacro-edit-lossage (Emacs 22 only). Just type C-xC-kl and you'll be teletransported to a Edit macro buffer where you can edit your last keystrokes and record them as needed. Nifty, no?

As you get used to macros, you'll surely discover many other tricks, but you can speed up your learning by means of the excellent pages KeyboardMacros and KeyboardMacrosTricks in the Emacs wiki. (One of my favourite tricks is using Elisp in macros to dynamically change what gets inserted). This Linux Journal article by Jesper Pedersen is also a good way to get you started as a power macro user.

Happy recording!

Tags: emacs
17 Jan 2007

eval and replace anywhere

Being a living Elisp virtual machine, Emacs naturally provides the ability to evaluate any Elisp expression anywhere. Just put the cursor right after the expression to be evaluated and press C-xC-e: the result appears in the mini-buffer. I use this continuously, for instance while reading about a variable to know its value. For instance, imagine i see this line in one of my files:

(setq planner-project "planner")

and want to know if planner-project has been modified. I just need to put my cursor right after the variable name and get its value with C-xC-e.

As i said, C-xC-e works in any (well, almost) buffer. Imagine you're writing a note and need to perform an arithmetic operation: you could write something like

Yesterday I spent (+ 2234.34 3423.4 (* 12.0 12.2) 10) dollars at ...

put the cursor just before 'dollars', press the eval shortcut, and see the result of the arithmetic operation in the mini-buffer. You memorize it, delete the Elisp expression… hmm, wait, this should be easier, shouldn't it? What we want is Emacs to eval and replace the expression for us.

I'm sure that you already know what comes next :-) As it happens, this time i didn't need to write the Elisp snippet myself: i stole it instead from Jorgen "forcer" Schäfer's configuration file:

(defun fc-eval-and-replace ()
  "Replace the preceding sexp with its value."
  (interactive)
  (backward-kill-sexp)
  (prin1 (eval (read (current-kill 0)))
         (current-buffer)))

Complemented with the mandatory shortcut:

(global-set-key (kbd "C-c e") 'fc-eval-and-replace)

this little gem has probably the highest usefulness to lines of code ratio in my Elisp toolbox. Give it a try!

Update: pdq notes in a comment that, if the expression to be evaluated is malformed (like, say, (+1 1)) it gets deleted, and it would be nice if it wouldn't. A quick way to get this functionality is to catch errors in eval and undo the killing in a generic error handler. To wit:

(defun fc-eval-and-replace ()
  "Replace the preceding sexp with its value."
  (interactive)
  (backward-kill-sexp)
  (condition-case nil
      (prin1 (eval (read (current-kill 0)))
             (current-buffer))
    (error (message "Invalid expression")
           (insert (current-kill 0)))))
Tags: emacs
14 Jan 2007

editing your file names

In my experience, dired is one of the most underused modes of Emacs. Just C-xC-f to any directory in your hard disk and you'll be presented with a list of its files and directories. One can browse this list, and execute all kinds of commands and transformations on them. If you've never done that before, just give it a try, and look at the menubar for a list of nifty things you can do inside dired. There're several ways to mark files and operate on them afterwards: for instance, just type A to find inside the selected files any regexp, or Q to search and substitute.

One of my favorites dired functionalities is wdired-change-to-wdired-mode. When you invoke this interactive function (using M-x wdired-change-to-wdired-mode), the dired buffer becomes editable. That is, you can go around and edit directly the filenames as you would edit any other emacs text buffer. And that means you have all the regular editing commands at your disposal. For instance, if i enter wdired-mode in my emacs configuration directory:

emacs-wdired.png

and want to change the name of all those jao-*.el files to, say, jao-config-*.el, all i have to do is to search and replace as i would do in any other text file (i.e., using M-%). Or maybe put the cursor on the first file name, mark (C-SPC), got to the end of the jao-files list, put my cursor after the last dash (as shown in the figure), and use C-x r t to replace the text in the marked rectangle. When you're happy editing the buffer, just press C-c C-c and all your changes will be reflected in the underlying files.

Nifty.

Tags: emacs
14 Jan 2007

the ghost in the lisp machine

A friend of mine uses to say that Emacs fills our yearning for a Lisp Machine. I tend to agree with him: Emacs is not just an editor, but a full integrated environment where you can perform virtually any imaginable task; and, most importantly, the inner workings of the system are open to you to explore and extend. Using, for extra fun, Lisp. No, i don't think that Elisp is the nicest Lisp incarnation around, but is far better than, say, C, and i still prefer it to other scripting languages. Moreover, the awesome range of libraries at your disposal makes up for many of the deficiencies in the language.

Living in Emacs is addictive. Imagine an operating system where you can switch from writing code to browsing the web or chatting without leaving a consistent environment, with the same set of commands and shortcuts. Imagine a set of integrated applications where data is seamlessly shared, where any single functionality can be tweaked, extended and adapted to your particular needs. Where everything is easily scriptable. Imagine, in additon, that the environment provides powerful and complete interactive self-documentation facilities with which the user can find out what is available. I have yet to find an operating system providing such an integrated environment. Not even Mac OS X, where AppleScript support is often lacking and system services are underused.

Of course, the key ingredient here is Emacs' extensibility. Far from being an afterthought or simply one of its features, extensibility is the central aspect of Emacs' architecture. Actually, the whole point of this post is to recommend you reading Richard Stallman's 1981 essay EMACS: The Extensible, Customizable Display Editor, which explains much better than I could the strong points of Emacs design, i.e., those traits that make Emacs more, much more, than just an editor. From the horse's mouth:

Extensibility means that the user can add new editing commands or change old ones to fit his editing needs, while he is editing. EMACS is written in a modular fashion, composed of many separate and independent functions. The user extends EMACS by adding or replacing functions, writing their definitions in the same language that was used to write the original EMACS system. We will explain below why this is the only method of extension which is practical in use: others are theoretically equally good but discourage use, or discourage nontrivial use. […] User customization helps in another, subtler way, by making the whole user community into a breeding and testing ground for new ideas. Users think of small changes, try them, and give them to other users–if an idea becomes popular, it can be incorporated into the core system. When we poll users on suggested changes, they can respond on the basis of actual experience rather than thought experiments.

The article goes on explaining the organization of the Emacs system, how it depends on its interpreter, Elisp's main features and how built-in self-documentation is provided. Also interesting is the list of related systems at the end of the essay: Lisp machines, LOGO, MacLisp and Smalltalk. We're definitely in good company!

Tags: emacs auld
Other posts
Creative Commons License
jao.io by jao is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.