Sacha Chua: YE16: Sacha and Prot talk Emacs

In this livestream, I showed Prot what I've been doing since our last conversation about Emacs configuration and livestreaming.

  • 04:46 Demonstrating sacha-stream-show-message and qrencode
  • 17:13 My objectives
  • 18:58 keycast-header-mode
  • 16:30 After all the audio issues
  • Livestreaming
    • 19:40 Getting more out of livestreams
      • 19:46 Trade-offs
      • 23:49 Lowering the effort needed to announce a stream: Prot just announces it and the blog post embeds it.
      • 25:23 Timestamps
      • 28:14 Reading other people's configs
      • 32:02 Prot on didactic livestreams
      • 34:03 Breadcrumbs and excursions
    • 37:56 Announcing livestreams
      • 38:30 Embeds: Prot embeds specific YouTube videos instead of the general channel one
      • 39:37 Demo of my new shortcut for converting time zones: Time zones
    • 45:25 Processing the recordings
      • 48:29 Automating more of the process
      • 49:15 Making a blog post; two-speaker subtitles
  • 51:10 Copying non-packaged code
    • 52:26 defcustom
    • 56:46 Prot rewrites functions to fit his style and naming conventions
  • 59:17 A preference for small functions
  • 01:00:23 avy-goto-char-timer
  • 01:02:37 One-shot keyboard modifiers
  • 01:03:26 Toggling
  • 01:07:31 My next steps

2026-04-16-01 Preparing for chat with Prot.jpeg

Questions I'm thinking about / areas I'm working on improving:

  • (Log) Getting more out of livestreams (for yourself or others)
    • You've mentioned that you don't really go back to your videos to listen to them. I was wondering what could make the livestreamed recordings more useful to either the person who made them, people who watched it live, or people who come across it later.
    • Tradeoffs for livestreaming:
      • Plus: debugging help, capturing your thinking out loud, conversation, sharing more practices/tips
      • Minus: Fitting less stuff on screen, distractability
    • A few types of livestreams:
    • (Log) Announcing livestreams
      • You add a post for scheduled/spontaneous livestreams and then you update it with the description; probably fine considering RSS readers - people can visit the page if it's finished
      • Debating whether to embed the channel livestream (picks next public scheduled stream, I think) or embed the specific livestream

      • Now on https://yayemacs.com (also https://sach.ac/live, https://sachachua.com/live)
      • Added timestamp translation to Embark keymap for timestamps, sacha-org-timestamp-in-time-zones
      • TODO: Post template
      • TODO: ical file
      • TODO: Easier workflow for embedding streams
      • TODO: Google API for scheduling a livestream
    • (Log) Processing the recordings
      • I like editing transcripts because that also helps me quickly split up chapters
      • Tracking chapters on the fly
      • Extracting screenshots and clips
      • Turning videos into blog posts (or vice versa)
      • TODO: Automate more of the downloading/transcription, common edits, Internet Archive uploads
  • (Log) Do you sometimes find yourself copying non-packaged code from other people? How do you like to integrate it into your config, keep references to the source, check for updates?
    • convert defvar to defcustom
    • Current approach: autoload if possible; if not, add a note to the docstring

         (use-package prot-comment                ; TODO 2026-04-16:
          :load-path "~/vendor/prot-dotfiles/emacs/.emacs.d/prot-lisp"
                :commands (prot-comment-timestamp-keyword)
                :bind
                (:map prog-mode-map
                                        ("C-x M-;" . prot-comment-timestamp-keyword)))
      
         ;;;###autoload
      (defun sacha-org-capture-region-contents-with-metadata (start end parg)
        "Write selected text between START and END to currently clocked `org-mode' entry.
      
         With PARG, kill the content instead.
         If there is no clocked task, create it as a new note in my inbox instead.
      
         From https://takeonrules.com/2022/10/16/adding-another-function-to-sacha-workflow/, modified slightly so that it creates a new entry if we are not currently clocked in."
        (interactive "r\nP")
        (let ((text (sacha-org-region-contents-get-with-metadata start end)))
          (if (car parg)
              (kill-new text)
            (org-capture-string (concat "-----\n" text)
                                (if (org-clocking-p) "c"
                                  "r")))))
      
    • prot-window: run a command in a new frame
    • Look into using keyd for tap and hold space?
    • header line format with common tips
View Org source for this post

You can e-mail me at sacha@sachachua.com.

-1:-- Sacha Chua: YE16: Sacha and Prot talk Emacs (Post Planet Emacslife)--L0--C0--2026-04-16T16:44:19.000Z

Irreal: LaTeX Preview In Emacs

Over at the Emacs subreddit, _DonK4rma shows an example of his mathematical note taking in Emacs. It’s a nice example of how flexible Org mode is even for writing text with heavy mathematical content but probably not too interesting to most Emacs users.

What should be interesting is this comment, which points to Dan Davison’s Xenops, which he describes as a “LaTeX editing environment for mathematical documents in Emacs.” The idea is that with Xenops when you leave a math mode block it is automatically rendered as the final mathematics, which replaces the original input. If you move the cursor onto the output text and type return, the original text is redisplayed.

It’s an excellent system that lets you catch any errors you make in entering mathematics as you’re entering them rather than at LaTeX compile time. So far it only works on .tex files but Davison says he will work on getting it to work with Org too.

He has a six minute video that shows the system in action. It gives a good idea of how it works but Xenops can do a lop more; see the repository’s detailed README at the above link for details.

-1:-- Irreal: LaTeX Preview In Emacs (Post Planet Emacslife)--L0--C0--2026-04-16T15:03:07.000Z

Bicycle for Your Mind: Outlining with OmniOutliner Pro 6

OmniOutliner Pro 6OmniOutliner Pro 6

Product: OmniOutliner Pro 6
Price: $99 for new users and $50 for upgrade price. They have a $49.99/year subscription price.

Rationale or the Lack of One

There was no good reason to buy OmniOutliner Pro 6.

I don’t need this program. I have the outlining abilities of Org-mode in Emacs. And dedicated outlining programs in Opal, Zavala and TaskPaper.

They had a good upgrade price and I hadn’t tried out any new software in a while. I know that is not a good reason to spend $50. It was my birthday, and I love outlining programs.

I had used the Pro version in version 3 and had bought the Essentials edition for OmniOutliner 5. A lot of what I see in version 6 is new to me.

Themes

Customizing ThemesCustomizing Themes

OmniOutliner Pro 6 comes with themes. I wanted to make my own or customize the existing ones. It is easy to do. Didn’t do much. Changed the line spacing and the font. The themes it ships with are nice. I am using the blank one and Solarized.

Writing Environment

Writing in OOPWriting in OOP

The best thing about OmniOutliner Pro 6 is the writing environment it provides. There are touches around the program which make it a pleasure to write in. Two of them which stick out to me are:

  1. Typewriter scrolling. I have no idea why more programs don’t give you this feature. I use it all the time. Looking at the bottom of the document is boring and it hurts my neck.
  2. Full screen focus. This is well implemented and another feature which helps me concentrate on the document I am in.

Linking Documents

LinkingLinking

You can link to a document or to a block in the document. Clicking on the space left of the Heading gives you a drop-down menu. Choose the Copy Omni Link and paste it to where you want the link to appear. Useful in linking documents or sections when you have a block of outlines which relate to each other in some way.

Keyboard Commands

keyboard commandskeyboard commands

Keyboard commands are what make an outlining program. OmniOutliner Pro 6 comes with the ability to customize and change every keyboard command that is in the program. It makes the learning curve smoother when you can use the commands you are used to for every task you perform in an outliner. I love this ability to make the outliner my own.

Using OmniOutliner Pro 6

This is the best outliner in the macOS space. OmniOutliner Pro 6 cements that position. It is a pleasure to use. It does everything you need from an outliner and does it with style. It does more than you need. Columns? I have never found the need for columns in an outliner. Other users love this feature. I am not interested. Maybe I am missing something, or I don’t use outlines which need columns. In spite of my lack of enthusiasm for columns, this is the best outlining program available on the macOS.

Comparison with Org-mode

I use Emacs and within it Org-mode. I write in outlines in Emacs all the time.

Org-mode is a strange mix of OmniOutliner and OmniFocus. It does outlines and does task management. All in one application. In plain text. The only problem? You have to deal with the complexity of Emacs. It is a steep learning curve which gives you benefits over the long term but there is pain in the short term. Let’s be honest, there is a ton of pain in the short term. OmniOutliner on the other hand, is easy to pick up and use. You are going to be competent in the program with little effort. The learning curve is minimal. The program is usable and useful. Doesn’t do most of the things Org-mode does, but it is not designed for that. They have a product called OmniFocus to sell you, for that.

Conclusion

If you are looking for an outlining program, you cannot go wrong with OmniOutliner Pro 6. It is fantastic to live in and work with. It gives you a great writing environment. I love writing in it.

There are two things which give me pause when it comes to OmniOutliner Pro 6. The first is the price. I think $99 for an outlining program is steep. That is a function of my retired-person price sensitivity. You might have a different view. The second is the incomplete documentation. They are working on it, slowly. If I am paying for the most expensive outlining program in the marketplace, I want the documentation to be complete and readily available on sale of the product. Not something I have been waiting a few months for. That is negligent.

If you are looking at outlining programs there are competitors in the marketplace. Zavala is a competitive product which is free. Opal is another product which is free and although it doesn’t have all the features of OmniOutliner, is a competent outliner. Or, you can always learn how to use Emacs and adopt Org-mode as the main driver of all your writing.

OmniOutliner Pro 6 is recommended with some reservations.

macosxguru at the gmail thingie.

-1:-- Bicycle for Your Mind: Outlining with OmniOutliner Pro 6 (Post Planet Emacslife)--L0--C0--2026-04-16T07:00:00.000Z

James Endres Howell: Embedding a Mastodon thread as comments to a blog post

I wrote org-static-blog-emfed, a little Emacs package that extends org-static-blog with the ability to embed a Mastodon thread in a blog post to serve as comments. The root of the Mastodon thread also serves as an announcement of the blog post to your followers. It’s based on Adrian Sampson’s Emfed, and of course Bastian Bechtold’s org-static-blog.

I had shared it before, but alas, after changing Mastodon instances the comments from old posts were lost, so I disabled them on this blog. Just over the past few days I’ve found time to get it all working again.

It also seems, at least in #Emacs on Mastodon, that org-static-blog has gained in popularity recently.

Prompted as I was to make a few improvements, I thought I would update the README and share it again. Hope it’s useful for someone!

-1:-- James Endres Howell: Embedding a Mastodon thread as comments to a blog post (Post Planet Emacslife)--L0--C0--2026-04-15T22:17:00.000Z

Sacha Chua: Org Mode: JS for translating times to people's local timezones

I want to get back into the swing of doing Emacs Chats again, which means scheduling, which means timezones. Let's see first if anyone happens to match up with the Thursday timeslots (10:30 or 12:45) that I'd like to use for Emacs-y video things, but I might be able to shuffle things around if needed.

I want something that can translate times into people's local timezones. I use Org Mode timestamps a lot because they're so easy to insert with C-u C-c ! (org-timestamp-inactive), which inserts a timestamp like this:

By default, the Org HTML export for it does not include the timezone offset. That's easily fixed by adding %z to the time specifier, like this:

(setq org-html-datetime-formats '("%F" . "%FT%T%z"))

Now a little bit of Javascript code makes it clickable and lets us toggle a translated time. I put the time afterwards so that people can verify it visually. I never quite trust myself when it comes to timezone translations.

function translateTime(event) {
  if (event.target.getAttribute('datetime')?.match(/[0-9][0-9][0-9][0-9]$/)) {
    if (event.target.querySelector('.translated')) {
      event.target.querySelectorAll('.translated').forEach((o) => o.remove());
    } else {
      const span = document.createElement('span');
      span.classList.add('translated');
      span.textContent = ' → ' + (new Date(event.target.getAttribute('datetime'))).toLocaleString(undefined, {
        month: 'short',  
        day: 'numeric',  
        hour: 'numeric', 
        minute: '2-digit',
        timeZoneName: 'short'
      });
      event.target.appendChild(span);
    }
  }
}
function clickForLocalTime() {
  document.querySelectorAll('time').forEach((o) => {
    if (o.getAttribute('datetime')?.match(/[0-9][0-9][0-9][0-9]$/)) {
      o.addEventListener('click', translateTime);
      o.classList.add('clickable');
    }
  });
}

And some CSS to make it more obvious that it's now clickable:

.clickable {
    cursor: pointer;
    text-decoration: underline dotted;
}

Let's see if this is useful.

Someday, it would probably be handy to have a button that translates all the timestamps in a table, but this is a good starting point.

View Org source for this post

You can e-mail me at sacha@sachachua.com.

-1:-- Sacha Chua: Org Mode: JS for translating times to people's local timezones (Post Planet Emacslife)--L0--C0--2026-04-14T18:44:16.000Z

Sacha Chua: 2026-04-13 Emacs news

Lots of little improvements in this one! I'm looking forward to borrowing the config tweaks that bbatsov highlighted and also trying out popterm for quick-access shells. Also, the Emacs Carnival for April has a temporary home at Newbies/starter kits - feel free to write and share your thoughts!

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View Org source for this post

You can comment on Mastodon or e-mail me at sacha@sachachua.com.

-1:-- Sacha Chua: 2026-04-13 Emacs news (Post Planet Emacslife)--L0--C0--2026-04-13T13:43:00.000Z

Irreal: Days Until

Charles Choi recently saw a Mastodon post showing the days until the next election and started wondering how one would compute that with Emacs. He looked into it and, of course, the answer turned out to be simple. Org mode has a function, org-time-stamp-to-now that does exactly that. It takes a date string and calculates the number of days until that date.

Choi wrote an internal function that takes a date string and outputs a string specifying the number of days until that date. The default is x days until <date string> but you can specify a different output string if you like. That function, cc/--days-until, serves as a base for other functions.

Choi shows two such functions. One that allows you to specify a date from a date picker and computes the number of days until that date. The other—following the original question—computers the number of days until the next midterm and general elections in the U.S. for 2006. It’s a simple matter to change it for other election years. Nobody but the terminally politically obsessed would care about that but it’s a nice example of how easy it is to use cc/--days-until to find the number of days until some event.

Finally, in the comments to Choi’s reddit announcement ggxx-sdf notes that you can also use calc-eval for these sorts of calculations.

As Choi says, it’s a human characteristic to want to know how long something is going to take. If you have some event that you want a countdown clock for, take a look at Choi’s post.

-1:-- Irreal: Days Until (Post Planet Emacslife)--L0--C0--2026-04-12T14:50:16.000Z

Irreal: Magit Support

Just about everyone agrees that the two Emacs packages considered “killer apps” by those considering adopting the editor are Org mode and Magit. I’ve seen several people say they use Emacs mainly for one or the other.

Their development models are completely different. Org has a development team with a lead developer in much the same way that Emacs does. Magit is basically a one man show, although there are plenty of contributors offering pull requests and even fixing bugs. That one man is Jonas Bernoulli (tarsius) who develops Magit full time and earns his living from doing so.

Like most nerds, he hates marketing and would rather be writing code than seeking funding. Still, that thing about earning a living from Magit means that he must occasionally worry about raising money. Now is one such time. Some of his funding pledges have expired and the weakening U.S. dollar is also contributing to his dwindling income.

Virtually every Emacs user is also a Magit user and many of us depend on it so now would be a propitious moment to chip in some money to keep the good times rolling. The best thing, of course, is to get your employer to make a more robust contribution than would be feasible for an individual developer but even if every developer chips in a few dollars (or whatever) we can support tarsius and allow him to continue working on Magit and its associated packages.

His support page is here. Please consider contributing a few dollars. Tarsius certainly deserves it and we’ll be getting our money’s worth.

-1:-- Irreal: Magit Support (Post Planet Emacslife)--L0--C0--2026-04-11T14:24:14.000Z

Sacha Chua: Org Mode: Tangle Emacs config snippets to different files and add boilerplate

I want to organize the functions in my Emacs configuration so that they are easier for me to test and so that other people can load them from my repository. Instead of copying multiple code blogs from my blog posts or my exported Emacs configuration, it would be great if people could just include a file from the repository. I don't think people copy that much from my config, but it might still be worth making it easier for people to borrow interesting functions. It would be great to have libraries of functions that people can evaluate without worrying about side effects, and then they can copy or write a shorter piece of code to use those functions.

In Prot's configuration (The custom libraries of my configuration), he includes each library as in full, in a single code block, with the boilerplate description, keywords, and (provide '...) that make them more like other libraries in Emacs.

I'm not quite sure my little functions are at that point yet. For now, I like the way that the functions are embedded in the blog posts and notes that explain them, and the org-babel :comments argument can insert links back to the sections of my configuration that I can open with org-open-at-point-global or org-babel-tangle-jump-to-org.

Thinking through the options...

Org tangles blocks in order, so if I want boilerplate or if I want to add require statements, I need to have a section near the beginning of my config that sets those up for each file. Noweb references might help me with common text like the license. Likewise, if I want a (provide ...) line at the end of each file, I need a section near the end of the file.

If I want to specify things out of sequence, I could use Noweb. By setting :noweb-ref some-id :tangle no on the blocks I want to collect later, I can then tangle them in the middle of the boilerplate. Here's a brief demo:

#+begin_src emacs-lisp :noweb yes :tangle lisp/sacha-eshell.el :comments no
;; -*- lexical-binding: t; -*-
<<sacha-eshell>>
(provide 'sacha-eshell)
#+end_src

However, I'll lose the comment links that let me jump back to the part of the Org file with the original source block. This means that if I use find-function to jump to the definition of a function and then I want to find the outline section related to it, I have to use a function that checks if this might be my custom code and then looks in my config for "defun …". It's a little less generic.

I wonder if I can combine multiple targets with some code that knows what it's being tangled to, so it can write slightly different text. org-babel-tangle-single-block currently calculates the result once and then adds it to the list for each filename, so that doesn't seem likely.

Alternatively, maybe I can use noweb or my own tangling function and add the link comments from org-babel-tangle-comments.

Aha, I can fiddle with org-babel-post-tangle-hook to insert the boilerplate after the blocks have been written. Then I can add the lexical-binding: t cookie and the structure that makes it look more like the other libraries people define and use. It's always nice when I can get away with a small change that uses an existing hook. For good measure, let's even include a list of links to the sections of my config that affect that file.

(defvar sacha-dotemacs-url "https://sachachua.com/dotemacs/")

;;;###autoload
(defun sacha-dotemacs-link-for-section-at-point (&optional combined)
  "Return the link for the current section."
  (let* ((custom-id (org-entry-get-with-inheritance "CUSTOM_ID"))
         (title (org-entry-get (point) "ITEM"))
         (url (if custom-id
                  (concat "dotemacs:" custom-id)
                (concat sacha-dotemacs-url ":-:text=" (url-hexify-string title)))))
    (if combined
        (org-link-make-string
         url
         title)
      (cons url title))))

(eval-and-compile
  (require 'org-core nil t)
  (require 'org-macs nil t)
  (require 'org-src nil t))
(declare-function 'org-babel-tangle--compute-targets "ob-tangle")
(defun sacha-org-collect-links-for-tangled-files ()
  "Return a list of ((filename (link link link link)) ...)."
  (let* ((file (buffer-file-name))
         results)
    (org-babel-map-src-blocks (buffer-file-name)
      (let* ((info (org-babel-get-src-block-info))
             (link (sacha-dotemacs-link-for-section-at-point)))
        (mapc
         (lambda (target)
           (let ((list (assoc target results #'string=)))
             (if list
                 (cl-pushnew link (cdr list) :test 'equal)
               (push (list target link) results))))
         (org-babel-tangle--compute-targets file info))))
    ;; Put it back in source order
    (nreverse
     (mapcar (lambda (o)
               (cons (car o)
                     (nreverse (cdr o))))
             results))))
(defvar sacha-emacs-config-module-links nil "Cache for links from tangled files.")

;;;###autoload
(defun sacha-emacs-config-update-module-info ()
  "Update the list of links."
  (interactive)
  (setq sacha-emacs-config-module-links
        (seq-filter
         (lambda (o)
           (string-match "sacha-" (car o)))
         (sacha-org-collect-links-for-tangled-files)))
  (setq sacha-emacs-config-modules-info
        (mapcar (lambda (group)
                  `(,(file-name-base (car group))
                    (commentary
                     .
                     ,(replace-regexp-in-string
                       "^"
                       ";; "
                       (concat
                        "Related Emacs config sections:\n\n"
                        (org-export-string-as
                         (mapconcat
                          (lambda (link)
                            (concat "- " (cdr link) "\\\\\n  " (org-link-make-string (car link)) "\n"))
                          (cdr group)
                          "\n")
                         'ascii
                         t))))))
                sacha-emacs-config-module-links)))

;;;###autoload
(defun sacha-emacs-config-prepare-to-tangle ()
  "Update module info if tangling my config."
  (when (string-match "Sacha.org" (buffer-file-name))
    (sacha-emacs-config-update-module-info)))

Let's set up the functions for tangling the boilerplate.

(defvar sacha-emacs-config-modules-dir "~/sync/emacs/lisp/")
(defvar sacha-emacs-config-modules-info nil "Alist of module info.")
(defvar sacha-emacs-config-url "https://sachachua.com/dotemacs")

;;;###autoload
(defun sacha-org-babel-post-tangle-insert-boilerplate-for-sacha-lisp ()
  (when (file-in-directory-p (buffer-file-name) sacha-emacs-config-modules-dir)
    (goto-char (point-min))
    (let ((base (file-name-base (buffer-file-name))))
      (insert (format ";;; %s.el --- %s -*- lexical-binding: t -*-

;; Author: %s <%s>
;; URL: %s

;;; License:
;;
;; This file is not part of GNU Emacs.
;;
;; This is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Commentary:
;;
%s
;;; Code:

\n\n"
                      base
                      (or
                       (assoc-default 'description
                                      (assoc-default base sacha-emacs-config-modules-info #'string=))
                       "")
                      user-full-name
                      user-mail-address
                      sacha-emacs-config-url
                      (or
                       (assoc-default 'commentary
                                      (assoc-default base sacha-emacs-config-modules-info #'string=))
                       "")))
      (goto-char (point-max))
      (insert (format "\n(provide '%s)\n;;; %s.el ends here\n"
                      base
                      base))
      (save-buffer))))
(setq sacha-emacs-config-url "https://sachachua.com/dotemacs")
(with-eval-after-load 'org
  (add-hook 'org-babel-pre-tangle-hook #'sacha-emacs-config-prepare-to-tangle)
  (add-hook 'org-babel-post-tangle-hook #'sacha-org-babel-post-tangle-insert-boilerplate-for-sacha-lisp))

You can see the results at .emacs.d/lisp. For example, the function definitions in this post are at lisp/sacha-emacs.el.

This is part of my Emacs configuration.
View Org source for this post

You can view 2 comments or e-mail me at sacha@sachachua.com.

-1:-- Sacha Chua: Org Mode: Tangle Emacs config snippets to different files and add boilerplate (Post Planet Emacslife)--L0--C0--2026-04-11T14:13:19.000Z

Erik L. Arneson: Emacs as the Freelancer's Command Center

Freelancing for small businesses and organizations leads to a position where you are juggling a number of projects for multiple clients. You need to keep track of a number of tasks ranging from software development to sending emails to project management. This is a lot easier when you have a system that can do a bunch of the work for you, which is why I use Emacs as my freelancer command center.

I would like to share some of the tools and workflows I use in Emacs to help me keep on top of multiple clients’ needs and expectations.

Organization with org-mode

It should be no surprise that at the center of my Emacs command center is org-mode. I have already written about it a lot. Every org-mode user seems to have their own way of keeping track of things, so please don’t take my organizational scheme as some kind of gospel. A couple of years ago, I wrote about how I handle to-do lists in org-mode, and I am still using that method for to-do keywords. However, file structure is also important. I have a number of core files.

Freelance.org

This top-level file contains all of my ongoing business tasks, such as tracking potential new clients, recurring tasks like website maintenance and checking my MainWP dashboard. I also have recurring tasks for invoicing, tracking expenses, and other important business things.

This file is also where I have my primary time tracking and reporting. Org-mode already supports this pretty nicely, I just use the built-in clocktable feature.

Clients/*.org

Clients that have large projects or ongoing business get their own file. This makes organization a lot easier. All tasks associated with a client and their various projects end up in these individual files. The important part is making sure that these files are included in the time-tracking clock table and your org-mode agenda, so you can see what is going on every week.

References and Linking

I have C-c l bound to org-store-link and use it all the time to link to various files, directories, URLs, and even emails. I can then use those links in my client notes, various tasks in my to-do list, and so on. This helps me keep my agenda organized even when my filesystem and browser bookmarks are a bit of a mess.

Email with mu4e

I have been reading and managing my email in Emacs for over 25 years. There have been a few breaks here and there where I have tried out other software or even web mail clients, but it has always been a headache. I return to Emacs! Long ago, I used VM (which seems to have taken on new life!), but currently I use mu4e.

This gives me a ton of power and flexibility when dealing with email. I have custom functions to help me compose and organize my email, and I can use org-store-link to keep track of individual emails from clients as they relate to agenda items. I even have a function to convert emails that I have written in Markdown into HTML email, and one that searches for questions in a client email to make sure I haven’t missed anything.

The ability to write custom code to both process and create email is extremely powerful and a great time saver.

Writing Code

I don’t know what else to say about this, I use Emacs for doing all of my software development. I make sure to use Eglot whenever there is a language server available, and I try to leverage all the fancy features offered by Emacs whenever possible. The vast majority of projects for clients are PHP (thanks WordPress), Go, JavaScript, and TypeScript.

Writing Words

Previously, I have shared quite a bit about writing in Emacs. I like to start everything in org-mode, but I also write quite a bit in Markdown. Emacs has become a powerful tool for writing. I use the Harper language server along with Eglot to check grammar and spelling.

Track All Changes with Magit

Version control is essential, a lesson I have learned over 30+ years of software development. While Git is not part of Emacs, the software I use to interface with Git is. Magit is a Git user interface that runs entirely in Emacs. I use it to track my writing, my source code, and even all of my org-mode files. Using version control is so essential that I have a weekly repeating agenda task reminding me to check all of my everyday files to make sure I have checked-in my changes for the week.

Thinking Music with EMMS

I like to have some soothing background music when I am programming, writing, or otherwise working on my computer. However, if that background music has lyrics, it can be really distracting. It is easy to make a playlist for various suitable SomaFM channels to load into EMMS (the Emacs Multimedia System) using the command M-x emms-play-playlist.

Try saving the following into playlist.el somewhere, and using it the next time you are writing:

 ;;; This is an EMMS playlist file Play it with M-x emms-play-playlist
 ((*track* (type . url) (name . "https://somafm.com/synphaera.pls"))
  (*track* (type . url) (name . "https://somafm.com/gsclassic.pls"))
  (*track* (type . url) (name . "https://somafm.com/sonicuniverse.pls"))
  (*track* (type . url) (name . "https://somafm.com/groovesalad.pls")))

And make sure to check out SomaFM’s selection to find some good background music that suits your tastes!

And the tools I have missed

There are undoubtedly Emacs tools that I have missed in this brief overview. I have been wracking my brain as I write, trying to see what I have forgotten or overlooked. Frankly, Emacs has become such a central part of the organization for my freelancing that there are probably many tools, packages, and processes that I use every day without thinking about it too much.

Emacs makes it possible for me to freelance for multiple clients and small businesses without losing my mind with organization and task management. The tools it provides allow me to stay on top of multiple projects, handle client relationships, and keep track of years worth of tasks, communications, and projects. Without it, I’d be sunk!

What Emacs tools are you using to manage your freelance business? I am always looking for ways to improve or streamline my process.

The featured image for this post comes from Agostino Ramelli’s Le diverse et artificiose machine (1588). Read more about it on the Public Domain Review.

-1:-- Erik L. Arneson: Emacs as the Freelancer's Command Center (Post Planet Emacslife)--L0--C0--2026-04-10T00:00:00.000Z

Charles Choi: Computing Days Until with Emacs

A recent Mastodon post showing the days until the next U.S. election got me to wonder, “how can I compute that in Emacs?” Turns out, this is trivial with the Org mode function org-time-stamp-to-now doing the timestamp computation for you.

We can wrap org-time-stamp-to-now in an internal function cc/--days-until that generates a formatted string of the days until a target date.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(defun cc/--days-until (target &optional template)
  "Formatted string of days until TARGET.

- TARGET: date string that conforms to `parse-time-string'.
- TEMPLATE : format string that includes ‘%d’ specifier.

If TEMPLATE is nil, then a predefined format string will be
used."
  (let* ((template (if template
                       template
                     (concat "%d days until " target)))
         (days (org-time-stamp-to-now target))
         (msg (format template days)))
    msg))

From there we can then start defining commands that use cc/--days-until. The command cc/days-until shown below will prompt you with a date picker to enter a date. Note that you can enter a date value (e.g. “Dec 25, 2026”) in the mini-buffer prompt for org-read-date.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(defun cc/days-until (arg)
  "Prompt user for date and show days until in the mini-buffer.

Use `org-read-date' to compute days until to display in the mini-buffer.

If prefix ARG is non-nil, then the computed result is stored in the
 `kill-ring'."
  (interactive "P")
  (let* ((target (org-read-date))
         (msg (cc/--days-until target)))
    (if arg
        (kill-new msg))
    (message msg)))

Going back to the original motivator for this post, here’s an implementation of days until the next two major U.S. election dates with the command cc/days-until-voting.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(defun cc/days-until-voting (arg)
  "Days until U.S. elections in 2026 and 2028.

If prefix ARG is non-nil, then the computed result is stored in the
 `kill-ring'."
  (interactive "P")
  (let* ((midterms (cc/--days-until "2026-11-03" "%d days until 2026 midterms"))
         (election (cc/--days-until "2028-11-07" "%d days until 2028 presidential election"))
         (msg (format "%s, %s" midterms election)))
    (if arg
        (kill-new msg))
    (message msg)))

The result of M-x cc/days-until-voting as of 8 April 2026 is:

209 days until 2026 midterms, 944 days until 2028 presidential election

It’s so human to want to know how long it’s going to take. Feel free to build your own countdown clocks using the code above. May your journey to whatever you plan be a happy one!

-1:-- Charles Choi: Computing Days Until with Emacs (Post Planet Emacslife)--L0--C0--2026-04-08T23:00:00.000Z

Sacha Chua: 2026-04-06 Emacs news

There's a lot of buzz around the remote code execution thing that involves Git, but it seems to be more of a Git issue than an Emacs one. This might be a workaround if you want, and in the meantime, don't check out git repositories you don't trust. There's no page for the Emacs Carnival for April yet, but you can start thinking about the theme of "newbies/starter kits" already, and I'm sure Cena or someone will round things up afterwards. Enjoy!

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View Org source for this post

You can e-mail me at sacha@sachachua.com.

-1:-- Sacha Chua: 2026-04-06 Emacs news (Post Planet Emacslife)--L0--C0--2026-04-06T12:00:00.000Z

Sacha Chua: YE11: Fix find-function for Emacs Lisp from org-babel or scratch

Watch on Internet Archive, watch/comment on YouTube, download captions, or email me

Where can you define an Emacs Lisp function so that you can use find-function to jump to it again later?

  • A: In an indirect buffer from Org Mode source block with your favorite eval function like eval-defun (hint)nope
    • C-c ' (org-edit-special) inside the block; execute the defun with C-M-x (eval-defun), C-x C-e (eval-last-sexp), or eval-buffer.

          (defun my-test-1 () (message "Hello"))
      
  • B: In an Org Mode file by executing the block with C-c C-c (hint)nope

      (defun my-test-2 () (message "Hello"))
    
  • C: In a .el file (hint)yup

    file:///tmp/test-search-function.el : execute the defun with C-M-x (eval-defun), C-x C-e (eval-last-sexp), or eval-buffer

  • D: In a scratch buffer, other temporary buffer, or really any buffer thanks to eval-last-sexp (hint)nope

    (defun my-test-4 () (message "Hello"))

Only option C works - it's gotta be in an .el file for find-function to find it. But I love jumping to function definitions using find-function or lispy-goto-symbol (which is bound to M-. if you use lispy and set up lispy-mode) so that I can look at or change how something works. It can be a little frustrating when I try to jump to a definition and it says, "Don't know where blahblahblah is defined." I just defined it five minutes ago! It's there in one of my other buffers, don't make me look for it myself. Probably this will get fixed in Emacs core someday, but no worries, we can work around it today with a little bit of advice.

I did some digging around in the source code. Turns out that symbol-file can't find the function definition in the load-history variable if you're not in a .el file, so find-function-search-for-symbol gets called with nil for the library, which causes the error. (emacs:subr.el)

I wrote some advice that searches in any open emacs-lisp-mode buffers or in a list of other files, like my Emacs configuration. This is how I activate it:

(setq sacha-elisp-find-function-search-extra '("~/sync/emacs/Sacha.org"))
(advice-add 'find-function-search-for-symbol :around #'sacha-elisp-find-function-search-for-symbol)

Now I should be able to jump to all those functions wherever they're defined.

(my-test-1)
(my-test-2)
(my-test-3)
(my-test-4)

Note that by default, M-. in emacs-lisp-mode uses xref-find-definitions, which seems to really want files. I haven't figured out a good workaround for that yet, but lispy-mode makes M-. work and gives me a bunch of other great shortcuts, so I'd recommend checking that out.

Here's the source code for the find function thing:

(defvar sacha-elisp-find-function-search-extra
  nil
  "List of filenames to search for functions.")

;;;###autoload
(defun sacha-elisp-find-function-search-for-symbol (fn symbol type library &rest _)
  "Find SYMBOL with TYPE in Emacs Lisp buffers or `sacha-find-function-search-extra'.
Prioritize buffers that do not have associated files, such as Org Src
buffers or *scratch*. Note that the fallback search uses \"^([^ )]+\" so that
it isn't confused by preceding forms.

If LIBRARY is specified, fall back to FN.

Activate this with:

(advice-add 'find-function-search-for-symbol
 :around #'sacha-org-babel-find-function-search-for-symbol-in-dotemacs)"
  (if (null library)
      ;; Could not find library; search my-dotemacs-file just in case
      (progn
        (while (and (symbolp symbol) (get symbol 'definition-name))
          (setq symbol (get symbol 'definition-name)))
        (catch 'found
          (mapc
           (lambda (buffer-or-file)
             (with-current-buffer (if (bufferp buffer-or-file)
                                      buffer-or-file
                                    (find-file-noselect buffer-or-file))
               (let* ((regexp-symbol
                       (or (and (symbolp symbol)
                                (alist-get type (get symbol 'find-function-type-alist)))
                           (alist-get type find-function-regexp-alist)))
                      (form-matcher-factory
                       (and (functionp (cdr-safe regexp-symbol))
                            (cdr regexp-symbol)))
                      (regexp-symbol (if form-matcher-factory
                                         (car regexp-symbol)
                                       regexp-symbol))

                      (case-fold-search)
                      (regexp (if (functionp regexp-symbol) regexp-symbol
                                (format (symbol-value regexp-symbol)
                                        ;; Entry for ` (backquote) macro in loaddefs.el,
                                        ;; (defalias (quote \`)..., has a \ but
                                        ;; (symbol-name symbol) doesn't.  Add an
                                        ;; optional \ to catch this.
                                        (concat "\\\\?"
                                                (regexp-quote (symbol-name symbol)))))))
                 (save-restriction
                   (widen)
                   (with-syntax-table emacs-lisp-mode-syntax-table
                     (goto-char (point-min))
                     (if (if (functionp regexp)
                             (funcall regexp symbol)
                           (or (re-search-forward regexp nil t)
                               ;; `regexp' matches definitions using known forms like
                               ;; `defun', or `defvar'.  But some functions/variables
                               ;; are defined using special macros (or functions), so
                               ;; if `regexp' can't find the definition, we look for
                               ;; something of the form "(SOMETHING <symbol> ...)".
                               ;; This fails to distinguish function definitions from
                               ;; variable declarations (or even uses thereof), but is
                               ;; a good pragmatic fallback.
                               (re-search-forward
                                (concat "^([^ )]+" find-function-space-re "['(]?"
                                        (regexp-quote (symbol-name symbol))
                                        "\\_>")
                                nil t)))
                         (progn
                           (beginning-of-line)
                           (throw 'found
                                   (cons (current-buffer) (point))))
                       (when-let* ((find-expanded
                                    (when (trusted-content-p)
                                      (find-function--search-by-expanding-macros
                                       (current-buffer) symbol type
                                       form-matcher-factory))))
                         (throw 'found
                                 (cons (current-buffer)
                                       find-expanded)))))))))
           (delq nil
                 (append
                  (sort
                   (match-buffers '(derived-mode . emacs-lisp-mode))
                   :key (lambda (o) (or (buffer-file-name o) "")))
                  sacha-elisp-find-function-search-extra)))))
    (funcall fn symbol type library)))

I even figured out how to write tests for it:

(ert-deftest sacha-elisp--find-function-search-for-symbol--in-buffer ()
  (let ((sym (make-temp-name "--test-fn"))
        buffer)
    (unwind-protect
        (with-temp-buffer
          (emacs-lisp-mode)
          (insert (format ";; Comment\n(defun %s () (message \"Hello\"))" sym))
          (eval-last-sexp nil)
          (setq buffer (current-buffer))
          (with-temp-buffer
            (let ((pos (sacha-elisp-find-function-search-for-symbol nil (intern sym) nil nil)))
              (should (equal (car pos) buffer))
              (should (equal (cdr pos) 12)))))
      (fmakunbound (intern sym)))))

(ert-deftest sacha-elisp--find-function-search-for-symbol--in-file ()
  (let* ((sym (make-temp-name "--test-fn"))
         (temp-file (make-temp-file
                     "test-" nil ".org"
                     (format
                      "#+begin_src emacs-lisp\n;; Comment\n(defun %s () (message \"Hello\"))\n#+end_src"
                      sym)))
         (sacha-elisp-find-function-search-extra (list temp-file))
         buffer)
    (unwind-protect
        (with-temp-buffer
          (let ((pos (sacha-elisp-find-function-search-for-symbol nil (intern sym) nil nil)))
            (should (equal (buffer-file-name (car pos)) temp-file))
            (should (equal (cdr pos) 35))))
      (delete-file temp-file))))
This is part of my Emacs configuration.
View Org source for this post

You can comment on Mastodon or e-mail me at sacha@sachachua.com.

-1:-- Sacha Chua: YE11: Fix find-function for Emacs Lisp from org-babel or scratch (Post Planet Emacslife)--L0--C0--2026-04-05T21:03:48.000Z

Please note that planet.emacslife.com aggregates blogs, and blog authors might mention or link to nonfree things. To add a feed to this page, please e-mail the RSS or ATOM feed URL to sacha@sachachua.com . Thank you!