TAONAW - Emacs and Org Mode:

Yesterday morning, I imported an old-blog post of mine, which discusses org-id and UUIDs in org-mode. It’s a bit of a deep dive into how org-mode works. I find that I don’t do those as much anymore - probably because I mostly use Emacs “as is” with a few packages I use day to day, and my workflow has been pretty much the same (capture templates not included) for the last two years or so.

-1:-- TAONAW - Emacs and Org Mode:  (Post Planet Emacslife)--L0--C0--2026-03-26T11:22:10.000Z

Irreal: Moving From Obsidian To Emacs

Curtis McHale runs an online book club where readers share their posts on the current book. He’s been using Longform in Obsidian but it kept corrupting his data organization so he decided to move to Emacs. His site requires Markdown but he decided to go all in on Org mode so he needed a way to convert his old Markdown posts to Org and then to export his Org files to Markdown.

Migrating from Markdown to Org was easily handled by Pandoc. When exporting from Org to Markdown there were a couple of problems. The easiest problem was smart quotes: " and ' are mapped to the HTML entities &ldquote;, &rdquote;, &lsquote;, and &rsquote;, which is not what McHale wanted. He fixed that by simply turning off with-smart-quotes.

The slightly harder problem was footnotes. The Org exporter handles them correctly but presents them as a Top Level heading, which doesn’t work for him because he has each post for a book as a separate subtree in the book’s Org file. He fixed that with a bit of post-processing that mapped # Footnotes to #### Footnotes.

His post has a video that shows him stepping through all this if you prefer a visual presentation. He uses Doom Emacs so that may be a bit disorienting to those who are used to vanilla Emacs.

In any event, it’s a nice post that shows how Emacs can easily handle tasks that you were using more complicated apps like Obsidian to do.

-1:-- Irreal: Moving From Obsidian To Emacs (Post Planet Emacslife)--L0--C0--2026-03-25T15:07:04.000Z

Irreal: OrgFolio

Chris Maiorana takes a lot of notes. He’s a writer after all. Once he’s taken those notes, he wants an easy and convenient way of viewing them. These days that usually means viewing them as a Web page.

Org mode, of course, has all the machinery in place to do this essentially automatically but Maiorana has some problems with it. He has multiple notebooks and finds it a pain to set up the ox-publish scaffolding for each one.

But this is Emacs so, of course, it’s easy to automate the process. Maiorana did that by writing a simple script, he calls OrgFolio, that

  1. Deletes the existing Web content
  2. Builds the scaffolding to export the Org files and exports them
  3. Copies any static content, such as CSS files
  4. Spins up a local Web server to show the content

All of this is kicked off from the command line with a call to Emacs in batch mode. It’s easy and has no overhead. It does require building everything from scratch,. which could be a problem for a large set of notes. That’s one of the reasons that Maiorana keeps a separate notebook for each topic.

I take copious notes about everything but they’re mainly for me so I see no need to export them to HTML. I’m perfectly happy to see them in plain text, just as I wrote them. That said, I do write them—complete with Org markup— as if they were going to be exported.

Still, if you take a lot of notes and would like to consume them as a Web page on your own private Web site, take a look at Maiorana’s post. His Elisp script is easy to read and modify if you want to adjust it for your own needs.

-1:-- Irreal: OrgFolio (Post Planet Emacslife)--L0--C0--2026-03-24T14:06:16.000Z

Curtis McHale: Goodbye Longform Hello Emacs

I want to love the Obsidian Long Form plugin, I even hoped it was a Scrivener replacement but it's failed me again and for the last time. Opening my project to write for the book club I found that it again forgot the order of all my files. I've reorganised them a number of times, but I'm not even bothering this time.

Emacs can be a full writing studio but I'm not abandoning Obsidian, I am going to take my monthly book club writing and move it into emacs.

Migrating .md to .org

First I need to migrate individual posts from Markdown to org files. Yes I know that Emacs has a markdown mode, but after a bit of research I decided to stick with org and let Emacs do it's thing. That means I need to migrate my existing writing into org using pandoc. I can do that with the script below.

pandoc -f markdown -t org input.md -o output.org

This gives me a file called output.org and converts my footnotes to an org compatible syntax. I can then copy the generated content into the new file for each book.

My plan is to keep each book in its own .org file and have each weekly post inside a subtree (heading). I can then export each subtree to markdown as needed for posting on my site.

Fixing Footnotes

My book club posts use footnotes heavily, so I needed them to survive the export back to Markdown intact. The built-in org markdown exporter (ox-md) does include footnotes, but with two problems.

First, the footnotes section gets a top-level # Footnotes heading regardless of where it sits in your document hierarchy. This means we have an h1 in the document which isn't what I want. Second, smart quotes and apostrophes get encoded as HTML entities — ’ instead of ', “ instead of ". Again, not what I want.

Both are fixable in the export function. The smart quotes issue is handled by passing :with-smart-quotes nil to the exporter. The heading level is fixed with a post-processing step that replaces # Footnotes with #### Footnotes before the output lands in the buffer.

(after! org
  (defun my/org-export-subtree-to-md ()
    (interactive)
    (let* ((md (org-export-as 'md t nil nil '(:with-toc nil :section-numbers nil :with-smart-quotes nil)))
           (md (replace-regexp-in-string "^# Footnotes$" "#### Footnotes" md)))
      (with-current-buffer (get-buffer-create "*Org MD Export*")
        (erase-buffer)
        (insert md)
        (goto-char (point-min))
        (pop-to-buffer (current-buffer)))))

  (map! :map org-mode-map
        :localleader
        "M" #'my/org-export-subtree-to-md))

The footnote links themselves render as HTML anchor tags (<sup><a href...>) rather than Markdown footnote syntax ([^1]). That's a limitation of ox-md, but it works fine in practice since most static site generators accept inline HTML.

Exporting to Markdown

Once the footnotes issues are sorted, the export workflow is straightforward. With my cursor inside the subtree I want to export, I press \ M and a *Org MD Export* buffer opens with the Markdown output ready to copy. The \ M binding assumes your Doom localleader is set to \ — if you haven't changed it, the default is , so it would be , M instead.

The t argument in org-export-as is what limits the export to the current subtree rather than the whole file. That matters because each book lives in one .org file with multiple subtrees — one per post. Without it, you'd get the entire book's worth of content every time.

I also suppress the table of contents and section numbers since those don't belong in a blog post:

'(:with-toc nil :section-numbers nil :with-smart-quotes nil)

Creating a project

Now to make it easier to access my 2026 book posts I need to create a project. First I needed to create a folder for them, I put it inside my Obsidian vault so that it syncs to all my devices.

To keep Obsidian syncing .org files you need to go to your sync settings and choose to sync "All Other Types" so that they get caught by Obsidian's sync process

While I got the project working, every time I've tried to get a new project in Emacs it takes far more tries than I expect.

First to be viewed as a project you need a .projectile file in the folder in question1. So I typed SPC f f then went to ~/Documents/main/emacs-writing/2026-book-posts/.projectile and pressed Return. This tells me the file doesn't exist and we create it.

Next press SPC p a to add a project. Go to the same folder and press Return. Now you should be able to press SPC p p when you start Emacs and you'll see a list of your projects which you can select.

Sounds easy, but I think I restarted Emacs 3 times testing this before it worked as it should. Likely a user error.

Future Stuff?

This workflow is pretty new, as in this week, so there are a few things I'd like to explore. There is an Obsidian plugin for .org files that I'd like to try out. There is also obsidian.el which brings Obsidian features into Emacs.

I realise I'm giving up on backlinks in my writing doing things the way I am currently, but Long Form isn't working and I don't want to spend time moving files around when I have time to write.

I just want to write.


  1. Yes .git and other file types work as well 

-1:-- Curtis McHale: Goodbye Longform Hello Emacs (Post Planet Emacslife)--L0--C0--2026-03-24T13:00:00.000Z

Sacha Chua: Categorizing Emacs News items by voice in Org Mode

I'm having fun exploring which things might actually be easier to do by voice than by typing. For example, after I wrote some code to expand yasnippets by voice, I realized that it was easier to:

  1. press my shortcut,
  2. say "okay, define interactive function",
  3. and then press my shortcut again,

than to:

  1. mentally say it,
  2. get the first initials,
  3. type in "dfi",
  4. and press Tab to expand.

Another area where I do this kind of mental translation for keyboard shortcuts is when I categorize dozens of Emacs-related links each week for Emacs News. I used to do this by hand. Then I wrote a function to try to guess the category based on regular expressions (my-emacs-news-guess-category in emacs-news/index.org, which is large). Then I set up a menu that lets me press numbers corresponding to the most frequent categories and use tab completion for the rest. 1 is Emacs Lisp, 2 is Emacs development, 3 is Emacs configuration, 4 is appearance, 5 is navigation, and so on. It's not very efficient, but some of it has at least gotten into muscle memory, which is also part of why it's hard to change the mapping. I don't come across that many links for Emacs development or Spacemacs, and I could probably change them to something else, but… Anyway.

2026-03-23_20-38-33.png
Figure 1: Screenshot of my menu for categorizing links

I wanted to see if I could categorize links by voice instead. I might not always be able to count on being able to type a lot, and it's always fun to experiment with other modes of input. Here's a demonstration showing how Emacs can automatically open the URLs, wait for voice input, and categorize the links using a reasonably close match. The *Messages* buffer displays the recognized output to help with debugging.

Screencast with audio: categorizing links by voice

This is how it works:

  1. It starts an ffmpeg recording process.
  2. It starts Silero voice activity detection.
  3. When it detects that speech has ended, it use curl to send the WAV to an OpenAI-compatible server (in my case, Speaches with the Systran/faster-whisper-base.en model) for transcription, along with a prompt to try to influence the recognition.
  4. It compares the result with the candidates using string-distance for an approximate match. It calls the code to move the current item to the right category, creating the category if needed.

Since this doesn't always result in the right match, I added an Undo command. I also have a Delete command for removing the current item, Scroll Up and Scroll Down, and a way to quit.

Initial thoughts

I used it to categorize lots of links in this week's Emacs News, and I think it's promising. I loved the way my hands didn't have to hover over the number keys or move between those and the characters. Using voice activity detection meant that I could just keep dictating categories instead of pressing keyboard shortcuts or using the foot pedal I recently dusted off. There's a slight delay, of course, but I think it's worth it. If this settles down and becomes a solid part of my workflow, I might even be able to knit or hand-sew while doing this step, or simply do some stretching exercises.

What about using streaming speech recognition? I've written some code to use streaming speech recognition, but the performance wasn't good enough when I tried it on my laptop (Lenovo P52 released in 2018, no configured GPU under Linux). The streaming server dropped audio segments in order to try to catch up. I'd rather have everything transcribed at the level of the model I want, even if I have to wait a little while. I also tried using the Web Speech API in Google Chrome for real-time speech transcription, but it's a little finicky. I'm happy with the performance I get from either manually queueing speech segments or using VAD and then using batch speech recognition with a model that's kept in memory (which is why I use a local server instead of a command-line tool). Come to think of it, I should try this with a higher-quality model like medium or large, just in case the latency turns out to be not that much more for this use case.

What about external voice control systems like Talon Voice or Cursorless? They seem like neat ideas and lots of people use them. I think hacking something into Emacs with full access to its internals could be lots of fun too.

A lot of people have experimented with voice input for Emacs over the years. It could be fun to pick up ideas for commands and grammars. Some examples:

What about automating myself out of this loop? I've considered training a classifier or sending the list to a large language model to categorize links in order to set more reasonable defaults, but I think I'd still want manual control, since the fun is in getting a sense of all the cool things that people are tinkering around with in the Emacs community. I found that with voice control, it was easier for me to say the category than to look for the category it suggested and then say "Okay" to accept the default. If I display the suggested category in a buffer with very large text (and possibly category-specific background colours), then I can quickly glance at it or use my peripheral vision. But yeah, it's probably easier to look at a page and say "Org Mode" than to look at the page, look at the default text, see if it matches Org Mode, and then say okay if it is.

Ideas for next steps

I wonder how to line up several categories. I could probably rattle off a few without waiting for the next one to load, and just pause when I'm not sure. Maybe while there's a reasonably good match within the first 1-3 words, I'll take candidates from the front of the queue. Or I could delimit it with another easily-recognized word, like "next".

I want to make a more synchronous version of this idea so that I can have a speech-enabled drop-in replacement that I can use as my y-or-n-p while still being able to type y or n. This probably involves using sit-for and polling to see if it's done. And then I can use that to play Twenty Questions, but also to do more serious stuff. It would also be nice to have replacements for read-string and completing-read, since those block Emacs until the user enters something.

I might take a side-trip into a conversational interface for M-x doctor and M-x dunnet, because why not. Naturally, it also makes sense to voice-enable agent-shell and gptel interactions.

I'd like to figure out a number- or word-based completion mechanism so that I can control Reddit link replacement as well, since I want to select from a list of links from the page. Maybe something similar to the way voicemacs adds numbers to helm and company or how flexi-choose.el works.

I'm also thinking about how I can shift seamlessly between typing and speaking, like when I want to edit a link title. Maybe I can check if I'm in the minibuffer and what kind of minibuffer I'm in, perhaps like the way Embark does.

It would be really cool to define speech commands by reusing the keymap structure that menus also use. This is how to define a menu in Emacs Lisp:

(easy-menu-define words-menu global-map
  "Menu for word navigation commands."
  '("Words"
     ["Forward word" forward-word]
     ["Backward word" backward-word]))

and this is how to set just one binding:

(keymap-set-after my-menu "<drink>"
  '("Drink" . drink-command) 'eat)

That makes sense to reuse for speech commands. I'd also like to be able to specify aliases while hiding them or collapsing them for a "What can I say" help view… Also, if keymaps work, then maybe minor modes or transient maps could work? This sort of feels like it should be the voice equivalent of a transient map.

The code so far

(defun my-emacs-news-categorize-with-voice (&optional skip-browse)
  (interactive (list current-prefix-arg))
  (unless skip-browse
    (my-spookfox-browse))
  (speech-input-cancel-recording)
  (let ((default (if (fboundp 'my-emacs-news-guess-category) (my-emacs-news-guess-category))))
    (speech-input-from-list
     (if default
         (format "Category (%s): " default)
       "Category: ")
     '(("Org Mode" "Org" "Org Mode")
       "Other"
       "Emacs Lisp"
       "Coding"
       ("Emacs configuration" "Config" "Configuration")
       ("Appearance" "Appearance")
       ("Default" "Okay" "Default")
       "Community"
       "AI"
       "Writing"
       ("Reddit" "Read it" "Reddit")
       "Shells"
       "Navigation"
       "Fun"
       ("Dired" "Directory" "Dir ed")
       ("Mail, news, and chat" "News" "Mail" "Chat")
       "Multimedia"
       "Scroll down"
       "Scroll up"
       "Web"
       "Delete"
       "Skip"
       "Undo"
       ("Quit" "Quit" "Cancel" "All done"))
     (lambda (result text)
       (message "Recognized %s original %s" result text)
       (pcase result
         ("Undo"
          (undo)
          (my-emacs-news-categorize-with-voice t))
         ("Skip"
          (forward-line)
          (my-emacs-news-categorize-with-voice))
         ("Quit"
          (message "All done.")
          (speech-input-cancel-recording))
         ("Reddit"
          (my-emacs-news-replace-reddit-link)
          (my-emacs-news-categorize-with-voice t))
         ("Scroll down"
          (my-spookfox-scroll-down)
          (my-emacs-news-categorize-with-voice t))
         ("Scroll up"
          (my-spookfox-scroll-up)
          (my-emacs-news-categorize-with-voice t))
         ("Delete"
          (delete-line)
          (undo-boundary)
          (my-emacs-news-categorize-with-voice))
         ("Default"
          (my-org-move-current-item-to-category
           (concat default ":"))
          (undo-boundary)
          (my-emacs-news-categorize-with-voice))
         (_
          (my-org-move-current-item-to-category
           (concat result ":"))
          (undo-boundary)
          (my-emacs-news-categorize-with-voice))))
     t)))

It uses Spookfox to control Firefox from Emacs:

(defun my-spookfox-scroll-down ()
  (interactive)
  (spookfox-js-injection-eval-in-active-tab "window.scrollBy(0, document.documentElement.clientHeight);" t))

(defun my-spookfox-scroll-up ()
  (interactive)
  (spookfox-js-injection-eval-in-active-tab "window.scrollBy(0, -document.documentElement.clientHeight);"))

(defun my-spookfox-background-tab (url &rest args)
  "Open URL as a background tab."
  (if spookfox--connected-clients
      (spookfox-tabs--request (cl-first spookfox--connected-clients) "OPEN_TAB" `(:url ,url))
    (browse-url url)))

It also uses these functions for categorizing Org Mode items:

(defun my-org-move-current-item-to-category (category)
    "Move current list item under CATEGORY earlier in the list.
  CATEGORY can be a string or a list of the form (text indent regexp).
  Point should be on the next line to process, even if a new category
  has been inserted."
    (interactive (list (completing-read "Category: " (my-org-get-list-categories))))
    (when category
      (let* ((col (current-column))
             (item (point-at-bol))
             (struct (org-list-struct))
             (category-text (if (stringp category) category (elt category 0)))
             (category-indent (if (stringp category) 2 (+ 2 (elt category 1))))
             (category-regexp (if (stringp category) category (elt category 2)))
             (end (elt (car (last struct)) 6))
             (pos (point))
             s)
        (setq s (org-remove-indentation (buffer-substring-no-properties item (org-list-get-item-end item struct))))
        (save-excursion
          (if (string= category-text "x")
              (org-list-send-item item 'delete struct)
            (goto-char (caar struct))
            (if (re-search-forward (concat "^ *- +" category-regexp) end t)
                (progn
                  ;; needs a patch to ol.el to check if stringp
                  (org-list-send-item item (point-at-bol) struct)
                  (org-move-item-down)
                  (org-indent-item))
              (goto-char end)
              (org-list-insert-item
               (point-at-bol)
               struct (org-list-prevs-alist struct))
              (let ((old-struct (copy-tree struct)))
                (org-list-set-ind (point-at-bol) struct 0)
                (org-list-struct-fix-bul struct (org-list-prevs-alist struct))
                (org-list-struct-apply-struct struct old-struct))
              (goto-char (point-at-eol))
              (insert category-text)
              (org-list-send-item item 'end struct)
              (org-indent-item)
              (org-indent-item))
            (recenter))))))

(defun my-org-guess-list-category (&optional categories)
  (interactive)
  (require 'cl-lib)
  (unless categories
    (setq categories
          (my-helm-org-list-categories-init-candidates)))
  (let* ((beg (line-beginning-position))
         (end (line-end-position))
         (string (buffer-substring-no-properties beg end))
         (found
          (cl-member string
                     categories
                     :test
                     (lambda (string cat-entry)
                       (unless (string= (car cat-entry) "x")
                         (string-match (regexp-quote (downcase (car cat-entry)))
                                       string))))))
    (when (car found)
      (my-org-move-current-item-to-category
       (cdr (car found)))
      t)))

For the speech-input functions, experimental code is at https://codeberg.org/sachac/speech-input .

View Org source for this post

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

-1:-- Sacha Chua: Categorizing Emacs News items by voice in Org Mode (Post Planet Emacslife)--L0--C0--2026-03-24T01:19:29.000Z

Chris Maiorana: OrgFolio | Turn your scattered interests into cultivated obsessions

A few weeks ago, I wrote a post about “distributed notebooks” generated in Org Mode and published to bare bones Web 1.0 HTML pages. Now here’s the sequel: OrgFolio. For the diligent note-takers, lifelong learners, deep readers, seekers, philosophers, Junior Orwells, and obsessive Emacs users in all of us.

What is OrgFolio?

OrgFolio is, at heart, just an Emacs lisp build script and file structure for doing the above. It takes your linked .org files and exports them to a separate HTML directory as a complete website.

It’s basically a static site generator for Emacs, but you don’t need to install or configure anything. You don’t need to do anything, just write!

(Except, you will need the simple-httpd package, if you want to serve your site on local http server, which I recommend.)

It’s also hosted on GitHub, here: OrgFolio GitHub.

A simplified approach

I have been experimenting with using Org Mode as a static site generator for a few years, and it has some downsides. (Note: it’s possible there are solutions for all of these downsides I have simply overlooked. If so, by all means, leave a comment and share your experiences.)

First, the ox-publish manifest configuration is long and complicated, and I didn’t want to have to rewrite all of that for each notebook, website, or project. In my implementation, the build.el script uses the directory name as a variable, so no additional configuration is necessary from project to project, directory to directory.

Second, once you have your publishing list configured, it can be difficult to keep your /org and /html directories in sync. Org Mode has a built-in solution for this: ox-publish uses a timestamp cache controlled by org-publish-use-timestamps-flag. When enabled (it’s on by default), the org-publish-needed-p function checks each file’s modification time against the last export and skips anything that hasn’t changed. This can speed up the build process, but I’ve often had trouble with it and opted to simply delete the HTML contents and start fresh.

Also, it has no way of cleaning up orphaned files. Let’s say you delete or rename an .org file, the corresponding .html file will sit in the build directory, basically outdated and no longer being used but still there.

I’ve solved that problem here by wiping/deleting the html directory contents on each build via the build.el script. Though, this can increase your build time if you have a large amount of .org files. That’s why I’d recommend keeping separate notebooks organized by topic.

How it works

I would recommend first writing notes by hand using something like the Cornell method, then transcribing notes into Org Mode. (But that’s up to you.)

Then, you can build those org notes into a simple wiki style website using OrgFolio. No fuss, no complicated ox-publish configuration.

The core of this setup is the file structure. You have an /org directory for storing your Org Mode files and a /static directory for holding your optional CSS stylesheet, images, and any other static document formats you might require. Then the build.el script drives Emacs in batch mode, so you can run it right from your command line.

emacs --batch --load build.el

That single command runs four steps in sequence:

Cleaning
wipes the output folder so you always get a fresh build.
Exporting
converts every .org file to HTML5 via ox-publish with sensible defaults (table of contents, no section numbers), all of which can be rewritten as needed.
Copying static items
copies any CSS, JavaScript, images, and fonts from /static into the output directory.
Service
spins up a local HTTP server via simple-httpd at localhost:8080 for preview and reading pleasure.

Alternatively, you can load the build file and run it from withing Emacs. Your choice!

Directory structure explained

You can get everything by cloning the repo from GitHub, but for your reference and perusal, this is the basic directory structure for OrgFolio:

project/
├── build.el       ← the build script
├── org/           ← your .org source files go here
└── static/        ← CSS, JS, images, fonts

The script will build the /html directory on demand when you run it.

Quick start

You will need:

  • Emacs (any reasonably current version)
  • simple-httpd package: M-x package-install RET simple-httpd

Installation

  • Go to the GitHub page
  • Clone the repository or download a zip file

Run a basic build

emacs --batch --load build.el

Then open your favorite browser and visit localhost:8080.

That’s it!


If you have any comments or questions let me know below and be sure to my check out my DRM-free eBooks (including Git For Writers and Emacs For Writers).

The post OrgFolio | Turn your scattered interests into cultivated obsessions appeared first on Chris Maiorana.

-1:-- Chris Maiorana: OrgFolio | Turn your scattered interests into cultivated obsessions (Post Planet Emacslife)--L0--C0--2026-03-23T18:34:22.000Z

Sacha Chua: 2026-03-23 Emacs news

: Removed elecxzy comment-dwim, whoops.

Might be a good opportunity to set up better auto-saves, with buffer-guardian.el inspiring an update to super-save 0.5. Also, there were a couple of interesting experiments embedding Chromium (Reddit) or native macOS views in Emacs (Reddit), and one about embedding Emacs in a webpage (Reddit).

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-03-23 Emacs news (Post Planet Emacslife)--L0--C0--2026-03-23T14:07:14.000Z

Irreal: Chris Maiorana On Hl-line-mode

A couple of years ago, I wrote about Chris Maiorana’a post suggesting that writers adopt a one-line-per-sentence workflow. You can read his post on the matter to see his arguments on why. At the time, I wrote that I used that method for my two books but because it worked better with the [GT]roff typesetter rather than for any writer workflow reasons. Since moving to Org mode for my writing I’ve given it up not least because it doesn’t play well with WordPress where a lot of my writing ends up.

Maiorana is back with another post on the subject that those who embrace the method may find useful. His idea is to use Emacs’ hl-line-mode to highlight the current line. That brings some other benefits to the one-line-per-sentence method that Maiorana describes in his latest post.

I used to use hl-line-mode, although not in conjunction with one-line-per-sentence, but stopped because it interacted poorly with some other mode—probably visual-line-mode where it highlights whole paragraphs. Even when I was using it, I sometimes found it annoying despite it being useful for locating the current line. I’m sure I’m in the minority on that so you should definitely give it a try if you’re not familiar with it. It’s builtin so you can simply toggle it on for the current buffer with Meta+x hl-line-mode to try it out.

-1:-- Irreal: Chris Maiorana On Hl-line-mode (Post Planet Emacslife)--L0--C0--2026-03-22T14:20:35.000Z

Bicycle for Your Mind: Much Ado About Emacs 012

EmacsEmacs

Haven’t written an update on my usage of Emacs for a while. Decided to stop tweaking the configuration to concentrate on getting familiar with what I have setup. That lasted for two seconds. Emacs developers are coming up with new things and I have severe FOMO. I have additions.

Kirigami

Kirigami “The kirigami Emacs package provides a unified method to fold and unfold text in Emacs across a diverse set of Emacs modes.” One set of keybindings to open/close folds across a wide range of modes, including markdown-mode, outline-mode and org-mode.

This is my general.el setup for kirigami

 ;; kirigami
  "k" '(nil :which-key "kirigami")
  "kc" '(kirigami-close-folds :which-key "close all")
  "ko" '(kirigami-open-folds :which-key "open all")
  "kp" '(kirigami-open-fold :which-key "open at point")
  "kr" '(kirigami-open-fold-rec :which-key "open rec")
  "kt" '(kirigami-toggle-fold :which-key "toggle folds")

Visible Marks

visible-mark - MELPA and ideasman42/emacs-visible-mark - Codeberg.org Minor mode to highlight mark(s). Allows setting the number of marks to display, and the faces to display them.

These are my settings for visual-mark

(defface visible-mark-active
  '((((type tty) (class mono)))
    (t (:background "magenta"))) "")
(setq visible-mark-max 2)
(setq visible-mark-faces `(visible-mark-face1 visible-mark-face2))
(use-package visible-mark
  :ensure t)
(global-visible-mark-mode 1)

Javelin

DamianB-BitFlipper/javelin.el at 9516e3729b8cd85d6b02fd1940ec10638c6706cb

Described by the author as “Quick file bookmarks for Emacs, inspired by ThePrimeagen’s Harpoon. Pin files to numbered positions (1-9) for instant access. Positions are automatically separated by project and git branch.”

I like it. The only problem is that the list doesn’t last across restarts of Emacs. Have to figure out how to achieve that.

OPML to Org-mode

I bought OmniOutliner 6 Pro. That produces OPML files. I wanted to be able to convert them to Org-mode. Gemini helped.

;; Converting OPML to Org-mode

(defun my/opml-to-org-with-notes (opml-file)
  "Convert an OPML file's 'outline' elements and their notes into Org-mode."
  (interactive "fSelect OPML file: ")
  (let* ((xml (xml-parse-file opml-file))
         ;; Standard OPML structure is <opml><body>...</body></opml>
         (body (car (xml-get-children (car xml) 'body)))
         (outlines (xml-get-children body 'outline))
         (new-buf (generate-new-buffer "*OPML Import with Notes*")))
    (with-current-buffer new-buf
      (org-mode)
      (cl-labels ((parse-outline (nodes level)
                    (dolist (node nodes)
                      (let* ((attrs (xml-node-attributes node))
                             (text (cdr (assoc 'text attrs)))
                             (note (or (cdr (assoc '_note attrs)) 
                                       (cdr (assoc 'description attrs))))
                             (children (xml-get-children node 'outline)))
                        ;; Create Heading
                        (insert (make-string level ?*) " " (or text "Untitled") "\n")
                        ;; Insert Note (if it exists)
                        (when note
                          (insert note "\n"))
                        ;; Recurse for nested nodes
                        (when children
                          (parse-outline children (1+ level)))))))
        (parse-outline outlines 1))
      (goto-char (point-min)))
    (switch-to-buffer new-buf)))

Appine

chaoswork/appine: Appine

“Appine means”App in Emacs”, which is an Emacs plugin using a Dynamic Module that allows you to embed native macOS views (WebKit, PDFKit, Quick look PreviewView, etc.) directly inside Emacs windows.”

This is my Appine setup:

;; appine
(use-package appine
  :straight (appine :type git :host github :repo "chaoswork/appine")
  :custom
  ;; enables opening URLs and files with Appine, default is nil
  (appine-enable-open-in-org-mode nil) 
  :config
  ;; Optional: Set default keybindings
  (global-set-key (kbd "C-x a w") 'appine-open-web-split)
  (global-set-key (kbd "C-x a o") 'appine-open-file-split))

Turned off (appine-enable-open-in-org-mode nil). I don’t want links to be opened in org-mode without me specifically wanting it. When the link opens in Emacs, I don’t have the ability to zoom in/out. I am old, I need that for most sites.

My general.el setup for Appine.

  ;; Windows
  "w" '(nil :which-key "window")
  "ww" '(appine-open-web-split :which-key "appine-web")
  "wf" '(appine-open-file-split :which-key "appine-file")

buffer-guardian

buffer-guardian.el – Automatically Save Emacs Buffers Without Manual Intervention (When Buffers Lose Focus, Regularly, or After Emacs is Idle) | James Cherti

Saves you from having to continuously save your document. It automatically saves when the buffer loses focus.

There is an alternative to this package, super-save 0.5: Modernized and Better Than Ever | Emacs Redux.

My setup for buffer-guardian is:

(use-package buffer-guardian
  :custom
  ;; When non-nil, include remote files in the auto-save process
  (buffer-guardian-inhibit-saving-remote-files nil)

  ;; When non-nil, buffers visiting nonexistent files are not saved
  (buffer-guardian-inhibit-saving-nonexistent-files t)

  ;; Save the buffer even if the window change results in the same buffer
  (buffer-guardian-save-on-same-buffer-window-change t)

  ;; Non-nil to enable verbose mode to log when a buffer is automatically saved
  (buffer-guardian-verbose nil)

  ;; Save all buffers after N seconds of user idle time. (Disabled by default)
  ;; (buffer-guardian-save-all-buffers-idle 30)

  :hook
  (after-init . buffer-guardian-mode))

isearch-lazy-count

isearch-lazy-count: Built-in Search Match Counting | Emacs Redux

This is useful. My setup:

#+begin_src emacs-lisp
;;customizing isearch
(setopt isearch-lazy-count t)
(setopt lazy-count-prefix-format nil)
(setopt lazy-count-suffix-format " [%s/%s]")
#+end_src

markdown-table-wrap

dnouri/markdown-table-wrap at 71a1cec53bf9d7875126b4cd557b2c00ae52b576

Markdown tables wrapped to a defined character width.

My setup:

#+begin_src emacs-lisp
;; markdown-table-wrap
(use-package markdown-table-wrap
  :ensure t
  :config
  (markdown-table-wrap table-text 60))
#+end_src

surround.el

surround.el: Vim-Style Pair Editing Comes to Emacs | Emacs Redux

Useful package to delete paired delimiters.

My setup:

#+begin_src emacs-lisp
(use-package surround
  :ensure t
  :bind-keymap ("M-'" . surround-keymap))
#+end_src

Conclusion

Using Emacs every day. My comfort with the program is increasing. I am learning and I am having a blast. That is all I have for now.

macosxguru

-1:-- Bicycle for Your Mind: Much Ado About Emacs 012 (Post Planet Emacslife)--L0--C0--2026-03-20T07:00:00.000Z

Irreal: Delete Org Markup Pairs

The other day I wrote about Bozhidar Batsov’s post on deleting paired delimiters in Emacs and mentioned that he included a bit of Elisp that—sort of—duplicated the behavior of Vim’s surround.vim. The idea is that if you are in a delimited expression and invoke the function, it will ask what delimiter you want to delete and will delete the closest enclosing pair. That obviously has some shortcomings such as not recognizing if one of the delimiters is in, say, a string. Still it’s handy code that does the right thing in most cases.

The code was interesting but I thought I didn’t need it because delete-pair already handled all the cases I was concerned with. As I was writing about Batsov’s post, I had something like =some text= and realized that I didn’t want it displayed in monospace after all. Since delete-pair was on my mind, I tried it but of course it didn’t work because = isn’t a delimiter that delete-pair recognizes so I deleted them by hand.

Later, I realized that I could simply add the Org markup pairs I was interested in to Batsov’s code so I did that and added it to my init.el. It worked perfectly and now I have an easy way of deleting Org markup .

It’s worth noting that Org mode gives you a way to do the opposite too: if you highlight some text and call org-empasize (Ctrl+c Ctrl+x Ctrl+f) it will add the Org markup you choose to the highlighted text.

Note

After I wrote this post, I found out about surround.el. More about that tomorrow.

-1:-- Irreal: Delete Org Markup Pairs (Post Planet Emacslife)--L0--C0--2026-03-18T15:17:09.000Z

Srijan Choudhary: 2026-03-18-001

A small #Emacs #OrgMode quality-of-life tweak. I often need to replace an org heading's title while preserving the original text in the body. The problem is that pressing enter on a heading inserts a line above the properties drawer, which breaks things.

Here's a function that moves the heading title into the body (below the properties drawer and metadata), and binds it to S-RET:

(defun my-org-demote-title-to-body ()
  "Move the current heading's title into the body, below the metadata.
  Point returns to the heading for editing."
  (interactive)
  (org-back-to-heading t)
  (let* ((element (org-element-at-point))
         (title (org-element-property :raw-value element)))
    (org-edit-headline "")
    (save-excursion
      (org-end-of-meta-data t)
      (insert title "\n"))
    (org-beginning-of-line)))

(defun my-org-shift-return ()
  "On a heading, demote title to body. In a table, copy down."
  (interactive)
  (cond
   ((org-at-heading-p) (my-org-demote-title-to-body))
   ((org-at-table-p) (org-table-copy-down 1))
   (t (org-return))))
  (define-key org-mode-map (kbd "S-<return>") #'my-org-shift-return)
-1:-- Srijan Choudhary: 2026-03-18-001 (Post Planet Emacslife)--L0--C0--2026-03-18T15:10:00.000Z

James Cherti: buffer-guardian.el – Automatically Save Emacs Buffers Without Manual Intervention (When Buffers Lose Focus, Regularly, or After Emacs is Idle)

The buffer-guardian Emacs package provides buffer-guardian-mode, a global mode that automatically saves buffers without requiring manual intervention.

By default, buffer-guardian-mode saves file-visiting buffers when:

  • Switching to another buffer.
  • Switching to another window or frame.
  • The window configuration changes (e.g., window splits).
  • The minibuffer is opened.
  • Emacs loses focus.

In addition to regular file-visiting buffers, buffer-guardian-mode also handles specialized editing buffers used for inline code blocks, such as org-src (for Org mode) and edit-indirect (commonly used for Markdown source code blocks). These temporary buffers are linked to an underlying parent buffer. Automatically saving them ensures that modifications made within these isolated code environments are correctly propagated back to the original Org or Markdown file.

If this package enhances your workflow, please show your support by ⭐ starring buffer-guardian on GitHub to help more users discover its benefits.

Other features that are disabled by default:

  • Save the buffer even if a window change results in the same buffer being selected. (Variable: buffer-guardian-save-on-same-buffer-window-change)
  • Save all file-visiting buffers periodically at a specific interval. (Variable: buffer-guardian-save-all-buffers-interval)
  • Save all file-visiting buffers after a period of user inactivity. (Variable: buffer-guardian-save-all-buffers-idle)
  • Prevent auto-saving remote files. (Variable: buffer-guardian-inhibit-saving-remote-files)
  • Prevent saving files that do not exist on disk. (Variable: buffer-guardian-inhibit-saving-nonexistent-files)
  • Set a maximum buffer size limit for auto-saving. (Variable: buffer-guardian-max-buffer-size)
  • Ignore buffers whose names match specific regular expressions. (Variable: buffer-guardian-exclude-regexps)
  • Use custom predicate functions to determine if a buffer should be saved. (Variable: buffer-guardian-predicate-functions)

(Buffer Guardian runs in the background without interrupting the workflow. For example, the package safely aborts the auto-save process if the file is read-only, if the file’s parent directory does not exist, or if the file was modified externally. Additionally, it gracefully catches and logs errors if a third-party hook attempts to request user input, ensuring that the editor never freezes during an automatic background save.)

Installation

Installation from MELPA

To install buffer-guardian from MELPA:

  1. If you haven’t already done so, add MELPA repository to your Emacs configuration.
  2. Add the following code to your Emacs init file to install buffer-guardian from MELPA:
(use-package buffer-guardian
  :custom
  ;; When non-nil, include remote files in the auto-save process
  (buffer-guardian-inhibit-saving-remote-files t)

  ;; When non-nil, buffers visiting nonexistent files are not saved
  (buffer-guardian-inhibit-saving-nonexistent-files nil)

  ;; Save the buffer even if the window change results in the same buffer
  (buffer-guardian-save-on-same-buffer-window-change t)

  ;; Non-nil to enable verbose mode to log when a buffer is automatically saved
  (buffer-guardian-verbose nil)

  ;; Save all buffers after N seconds of user idle time. (Disabled by default)
  ;; (buffer-guardian-save-all-buffers-idle 30)

  :hook
  (after-init . buffer-guardian-mode))

Alternative installation: Doom Emacs

Here is how to install buffer-guardian on Doom Emacs:

  1. Add to the ~/.doom.d/packages.el file:
(package! buffer-guardian
  :recipe
  (:host github :repo "jamescherti/buffer-guardian.el"))
  1. Add to ~/.doom.d/config.el:
(after! buffer-guardian
  ;; When non-nil, include remote files in the auto-save process
  (setq buffer-guardian-inhibit-saving-remote-files t)

  ;; When non-nil, buffers visiting nonexistent files are not saved
  (setq buffer-guardian-inhibit-saving-nonexistent-files nil)

  ;; Save the buffer even if the window change results in the same buffer
  (setq buffer-guardian-save-on-same-buffer-window-change t)

  ;; Non-nil to enable verbose mode to log when a buffer is automatically saved
  (setq buffer-guardian-verbose nil)

  ;; Save all buffers after N seconds of user idle time. (Disabled by default)
  ;; (setq buffer-guardian-save-all-buffers-idle 30)

  (buffer-guardian-mode 1))
  1. Run the doom sync command:
doom sync

Configuration

You can customize buffer-guardian to fit your workflow. Below are the main customization variables:

Triggers

  • buffer-guardian-save-on-focus-loss (Default: t): Save when the Emacs frame loses focus.
  • buffer-guardian-save-on-minibuffer-setup (Default: t): Save when the minibuffer opens.
  • buffer-guardian-save-on-buffer-switch (Default: t): Save when window-buffer-change-functions runs.
  • buffer-guardian-save-on-window-selection-change (Default: t): Save when window-selection-change-functions runs.
  • buffer-guardian-save-on-window-configuration-change (Default: t): Save when window-configuration-change-hook runs.
  • buffer-guardian-save-on-same-buffer-window-change (Default: nil): Save the buffer even if the window change results in the same buffer.

Timers

  • buffer-guardian-save-all-buffers-interval (Default: nil): Save all buffers periodically every N seconds.
  • buffer-guardian-save-all-buffers-idle (Default: nil): Save all buffers after N seconds of user idle time.

Exclusions and Filters

  • buffer-guardian-inhibit-saving-remote-files (Default: t): Prevent auto-saving remote files.
  • buffer-guardian-inhibit-saving-nonexistent-files (Default: t): Prevent saving files that do not exist on disk.
  • buffer-guardian-exclude-regexps (Default: nil): A list of regular expressions for file names to ignore.
  • buffer-guardian-max-buffer-size (Default: nil): Maximum buffer size (in characters) to save. Set to 0 or nil to disable.
  • buffer-guardian-predicate-functions (Default: nil): List of predicate functions to determine if a buffer should be saved.

Specialized Buffers (Inline Code Blocks)

  • buffer-guardian-handle-org-src (Default: t): Enable automatic saving for org-src buffers.
  • buffer-guardian-handle-edit-indirect (Default: t): Enable automatic saving for edit-indirect buffers.

Advanced

  • buffer-guardian-save-all-buffers-trigger-hooks: A list of hooks that trigger saving all modified buffers. Defaults to nil.
  • buffer-guardian-save-trigger-functions: A list of functions to advise. A :before advice will save the current buffer before these functions execute.
  • buffer-guardian-verbose (Default: nil): Enable logging messages when a buffer is saved.

Author and License

The buffer-guardian Emacs package has been written by James Cherti and is distributed under terms of the GNU General Public License version 3, or, at your choice, any later version.

Copyright (C) 2026 James Cherti

This program 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 of the License, or (at your option) any later version. This program 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 this program.

Links

Other Emacs packages by the same author:

  • minimal-emacs.d: This repository hosts a minimal Emacs configuration designed to serve as a foundation for your vanilla Emacs setup and provide a solid base for an enhanced Emacs experience.
  • compile-angel.el: Speed up Emacs! This package guarantees that all .el files are both byte-compiled and native-compiled, which significantly speeds up Emacs.
  • outline-indent.el: An Emacs package that provides a minor mode that enables code folding and outlining based on indentation levels for various indentation-based text files, such as YAML, Python, and other indented text files.
  • easysession.el: Easysession is lightweight Emacs session manager that can persist and restore file editing buffers, indirect buffers/clones, Dired buffers, the tab-bar, and the Emacs frames (with or without the Emacs frames size, width, and height).
  • vim-tab-bar.el: Make the Emacs tab-bar Look Like Vim’s Tab Bar.
  • elispcomp: A command line tool that allows compiling Elisp code directly from the terminal or from a shell script. It facilitates the generation of optimized .elc (byte-compiled) and .eln (native-compiled) files.
  • tomorrow-night-deepblue-theme.el: The Tomorrow Night Deepblue Emacs theme is a beautiful deep blue variant of the Tomorrow Night theme, which is renowned for its elegant color palette that is pleasing to the eyes. It features a deep blue background color that creates a calming atmosphere. The theme is also a great choice for those who miss the blue themes that were trendy a few years ago.
  • Ultyas: A command-line tool designed to simplify the process of converting code snippets from UltiSnips to YASnippet format.
  • dir-config.el: Automatically find and evaluate .dir-config.el Elisp files to configure directory-specific settings.
  • flymake-bashate.el: A package that provides a Flymake backend for the bashate Bash script style checker.
  • flymake-ansible-lint.el: An Emacs package that offers a Flymake backend for ansible-lint.
  • inhibit-mouse.el: A package that disables mouse input in Emacs, offering a simpler and faster alternative to the disable-mouse package.
  • quick-sdcv.el: This package enables Emacs to function as an offline dictionary by using the sdcv command-line tool directly within Emacs.
  • enhanced-evil-paredit.el: An Emacs package that prevents parenthesis imbalance when using evil-mode with paredit. It intercepts evil-mode commands such as delete, change, and paste, blocking their execution if they would break the parenthetical structure.
  • stripspace.el: Ensure Emacs Automatically removes trailing whitespace before saving a buffer, with an option to preserve the cursor column.
  • persist-text-scale.el: Ensure that all adjustments made with text-scale-increase and text-scale-decrease are persisted and restored across sessions.
  • pathaction.el: Execute the pathaction command-line tool from Emacs. The pathaction command-line tool enables the execution of specific commands on targeted files or directories. Its key advantage lies in its flexibility, allowing users to handle various types of files simply by passing the file or directory as an argument to the pathaction tool. The tool uses a .pathaction.yaml rule-set file to determine which command to execute. Additionally, Jinja2 templating can be employed in the rule-set file to further customize the commands.
  • kirigami.el: The kirigami Emacs package offers a unified interface for opening and closing folds across a diverse set of major and minor modes in Emacs, including outline-mode, outline-minor-mode, outline-indent-minor-mode, org-mode, markdown-mode, vdiff-mode, vdiff-3way-mode, hs-minor-mode, hide-ifdef-mode, origami-mode, yafolding-mode, folding-mode, and treesit-fold-mode. With Kirigami, folding key bindings only need to be configured once. After that, the same keys work consistently across all supported major and minor modes, providing a unified and predictable folding experience.
-1:-- James Cherti: buffer-guardian.el – Automatically Save Emacs Buffers Without Manual Intervention (When Buffers Lose Focus, Regularly, or After Emacs is Idle) (Post Planet Emacslife)--L0--C0--2026-03-16T21:57:02.000Z

Sacha Chua: 2026-03-16 Emacs news

Security reminder: If you use kubernetes-el, don't update for now, and you might want to check your installation if you updated it recently. The repo was compromised. (Analysis, Reddit discussion, lobste.rs) If you use Emacs 31, please consider enabling package-review-policy.

There were a number of lively conversations around Emacs Solo (142 comments on HN), Emacs and Vim in the age of AI (52 comments on Reddit, 138 on HN), and agent-shell 0.47 (62 on Reddit). Also, Prot has posted the video and text of his talk Computing in freedom with GNU Emacs (YouTube 42:40, Video with Q&A, more links in the community section).

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-03-16 Emacs news (Post Planet Emacslife)--L0--C0--2026-03-16T16:40:12.000Z

Bicycle for Your Mind: general.el to Conquer Emacs Keybindings

KeybaordKeybaord

noctuid/general.el: More convenient key definitions in emacs, is a critical package for my usage of Emacs.

One of the features of Emacs is the keybindings that are usable in the program. Keybindings is another way of saying keyboard commands. The base program comes with its own keybindings. Every time you add a package to the program, it comes with it’s own keybindings or it asks you to set keybindings for its important functions. You write a function in elisp and it is more useful if you assign a keybinding to it. Soon you are swimming in keybindings and you can’t remember them. There are several ways to solve this problem. Extensive transient menus is one of them. kickingvegas/casual-suite: Casual Suite - An umbrella package to support a single install point for all Casual user interfaces. is an attempt to do that. I am going to write about that in a future article. Another solution is general.el.

I am going to tackle my solution to the problem by using general.el.

Keybindings and the Problems With Them

I love keybindings. It is easier to use a program if you know the keybindings which let you perform functions. You don’t have to mouse over to a menu or search for a command when you know the keybindings. Makes your hands not move from the keyboard. Jeff Raskin said in defense of keeping his hands on the keyboard, “You shouldn’t have to live, a hand to mouse existence.” The problem is that there are too many of them and since they are set by someone else, it is difficult to remember them.

I wanted a system which lets me set my own keybindings for my often used commands. The reasoning is that if I am setting my own keybindings, then it is more likely that I will remember them. General.el lets me do that.

Setting Up the Usual Markdown Commands

#+begin_src emacs-lisp
(general-define-key
 :keymaps 'markdown-mode-map
 :prefix "s-o"
 "b" '(markdown-insert-bold :which-key "md bold")
 "q" '(markdown-insert-blockquote :which-key "md blockquote")
 "c" '(markdown-insert-code :which-key "md code")
 "i" '(markdown-insert-italic :which-key "md italic")
 "j" '(markdown-move-list-item-down :which-key "md list dn")
 "k" '(markdown-move-list-item-up :which-key "md list up")
 "l" '(markdown-insert-link :which-key "md link")
 "f" '(markdown-insert-footnote :which-key "md footnote")
 "p" '(md-capture :which-key "md capture")
 "t" '(markdown-insert-table :which-key "md table")
 "u" '(markdown-move-subtree-up :which-key "md item up")
 "d" '(markdown-move-subtree-down :which-key "md item down"))
#+end_src

Wanted to standardize on a leader key. In my case, it made sense to use s-o. In the Emacs world on macOS s is the ⌘key. I type the leader key and then another key to achieve the function assigned to it. Thus if I want to bold something, I can type s-o b and Emacs puts in four asterisks with the cursor between the four asterisks. Or, I can select something which I want to be bold, press the keyboard command s-o b and it gets the syntax of two asterisks surrounding the selected text. I have a whole list of commands tied to Markdown mode assigned to the s-o leader key. Since they follow a pattern and they are chosen by me. It is easier to remember them.

Setting Up the Usual Org-mode Commands

Org-mode is where I spend all my time, and it does a whole lot. The Org-mode commands are broken up into a few sections. These are the usual syntax commands. You have to specify them first, like,

#+begin_src emacs-lisp
;; general.el
(defun my/org-bold () (interactive) (org-emphasize ?*))
(defun my/org-code () (interactive) (org-emphasize ?~))
(defun my/org-italic () (interactive) (org-emphasize ?/))
(defun my/org-clear () (interactive) (org-emphasize ? ))
(defun my/org-strikethrough () (interactive) (org-emphasize ?+))
(defun my/org-underline () (interactive) (org-emphasize ?_))
(defun my/org-verbatim () (interactive) (org-emphasize ?=))
#+end_src

and,

#+begin_src emacs-lisp
(general-define-key
 :keymaps 'org-mode-map
 :prefix "s-o"
 "a" '(org-agenda :which-key "org agenda")
 "b" '(my/org-bold :which-key "org bold")
 "C" '(org-capture :which-key "org capture")
 "c" '(my/org-code :which-key "org code")
 "i" '(my/org-italic :which-key "org italic")
 "e" '(org-emphasize :which-key "org emph")
 "l" '(my/org-insert-link-dwim :which-key "org ins link")
 "s" '(org-schedule :which-key "org sch")
 "t" '(org-todo :which-key "org todo"))
#+end_src

Because I use Org-mode extensively this list includes a few commands that are not syntax related (agenda, capture, schedule and todo). They are commands I use all the time and the s-o leader key is convenient. This is a list of syntax commands and four function commands which get extensive use in Org-mode.

Navigation Commands

These are commands which let me move between headers in Org-mode:

#+begin_src emacs-lisp
(general-define-key
 :keymaps 'org-mode-map
 "M-n" 'org-next-visible-heading
 "M-p" 'org-previous-visible-heading)
#+end_src

There are additional navigation commands which get used for every mode:

#+begin_src emacs-lisp
(general-define-key
 ;; Move to beginning/end of buffer
 "s-<up>" 'beginning-of-buffer
 "s-<down>" 'end-of-buffer

 ;; Move to beginning/end of line
 "s-<left>" 'beginning-of-line
 "s-<right>" 'end-of-line)
#+end_src

Commands Which Deal With Emacs

Now we come to the bulk of the commands that I have set in general.el. These have to be assigned to a leader key. I chose M-o since the right option key and the o are readily accessible. There are many keys assigned to this leader-key. Firstly, a set of keys which are accessed regularly.

#+begin_src emacs-lisp
(general-create-definer my-leader-def
  :prefix "M-o"
  :non-normal-prefix "M-o")
(my-leader-def
  ;; Top level functions
  "." '(find-file :which-key "find file")
  ";" '(consult-recent-file :which-key "recent files")
  "/" '(bufferfile-rename :which-key "rename file")
  "TAB" '(switch-to-prev-buffer :which-key "previous buffer")
  "r" '(repeat :which-key "repeat")
  "g" '(avy-goto-char-timer :which-key "avy char timer")
  "p" '(org-capture :which-key "org-capture")
  "x" '(execute-extended-command :which-key "execute command")
  "+" '(tab-bar-new-tab :which-key "tab new")
  "-" '(tab-close :which-key "tab close")
  "1" '(tab-previous :which-key "tab previous")
  "2" '(tab-next :which-key "tab next")
  "9" '(my/scroll-up-half-page :which-key "scroll up")
  "0" '(my/scroll-down-half-page :which-key "scroll down")
  "<up>" '(push-mark-command :which-key "push mark")
  "<left>" '(backward-forward-previous-location :which-key "previous location")
  "<right>" '(backward-forward-next-location :which-key "next location")
#+end_src

This group went through changes initially. I was trying to see how often they got used. I keep a close watch on the frequency of use to get an idea of its utility. This group changes often.

#+begin_src emacs-lisp
  ;; Applications
  "a" '(nil :which-key "applications")
  "ao" '(org-agenda :which-key "org-agenda")
  "ac" '(org-capture :which-key "org-capture")
  "ab" '(nil :which-key "browse url")
  "abc" '(browse-url-chrome :which-key "chrome")
  "av" '(nil :which-key "avy")
  "avc" '(avy-goto-char-2 :which-key "avy goto char")
  "avp" '(avy-goto-parens :which-key "avy goto parens")
  "ad" '(dired :which-key "dired")
#+end_src

A set of keys assigned to “applications.” Thus M-o a and then a key to call the function.

#+begin_src emacs-lisp
  ;; Buffers
  "b" '(nil :which-key "buffer")
  "bb" '(switch-to-buffer :which-key "switch-to-buffer")
  "bc" '(consult-buffer :which-key "consult-buffer")
  "bd" '(kill-buffer :which-key "kill-buffer")
  "be" '(my/kill-other-buffers :which-key "kill other buffers")
  "bf" '(my/copy-buffer-file-path-to-clipboard :which-key "copy file path")
  "bi" '(ibuffer :which-key "ibuffer")
  "bk" '(my/save-and-close-this-buffer :which-key "save-kill-buffer")
  "bm" '(nil :which-key "bookmark")
  "bmd" '(bookmark-delete :which-key "bookmark delete")
  "bmj" '(bookmark-jump :which-key "bookmark jump")
  "bms" '(bookmark-set :which-key "bookmark set")
  "bn" '(next-buffer :which-key "next-buffer")
  "bo" '(org-switchb :which-key "org-switchb")
  "bp" '(previous-buffer :which-key "previous-buffer")
  "bR" '(revert-buffer :which-key "revert-buffer")
  "br" '(bufferfile-rename :which-key "rename buffer")
  "bs" '(scratch :which-key "scratch")
#+end_src

A set of keys assigned to M-o b and then a key. Buffer related. Except the ones for bookmarks. It was mnemonically consistent to have them here.

#+begin_src emacs-lisp
  ;;Consult
  "c" '(nil :which-key "consult")
  "cd" '(consult-dir :which-key "con dir")
  "cg" '(consult-grep :which-key "con grep")
  "ch" 'consult-org-heading :which-key "con org heading"
  "cl" '(consult-line :which-key "con line")
  "cn" '(consult-notes :which-key "con notes")
  "co" '(consult-outline :which-key "con outline")
  "cy" '(consult-yasnippet :which-key "con yasnippet")
  "cb" '(nil :which-key "bookmark/buffer")
  "cbo" '(consult-bookmark :which-key "con bookmark")
  "cbu" '(consult-buffer :which-key "con buffer")
  ;;Consult-register
  "cr" '(nil :which-key "consult-register")
  "crr" '(consult-register :which-key "con register")
  "crs" '(consult-register-store :which-key "con reg store")
#+end_src

These are the keys set for minad/consult: :mag: consult.el - Search and navigate via completing-read. M-o c and then the relevant key. This group gets used a lot every day. I love consult. Thank you Howard Melman for pointing me to it.

#+begin_src emacs-lisp
  ;; Denote
  "d" '(nil :which-key "denote")
  "dd" '(denote-sort-dired :which-key "denote-sort-dired")
  "dl" '(denote-link :which-key "denote-link")
  "dL" '(denote-add-links :which-key "denote-add-links")
  "dn" '(nil :which-key "denote")
  "dno" '(denote :which-key "denote")
  "dnc" '(my/denote-create-note :which-key "denote choose")
  "db" '(denote-backlinks :which-key "denote-backlinks")
  "dr" '(denote-rename-file :which-key "denote-rename-file")
  "dR" '(denote-rename-file-using-front-matter :which-key "denote-rename-file-using-front-matter")
  "ds" '(denote-subdirectory :which-key "denote-subdir")
  "dt" '(denote-type :which-key "denote-type")
#+end_src

Assigned to M-o d this is my Denote (denote.el) | Protesilaos Stavrou group. I rely on this for all my note-taking. Thank you Prot. You are special.

#+begin_src emacs-lisp
  ;;Elfeed
  "e" '(nil :which-key "elfeed")
  "ee" '(elfeed :which-key "elfeed")
  "eu" '(elfeed-update :which-key "elfeed-update")
#+end_src

This is the skeeto/elfeed: An Emacs web feeds client group. I love reading my feeds in Emacs.

#+begin_src emacs-lisp
;; Files
  "f" '(nil :which-key "files")
  "fb" '(my-list-blog :which-key "blogwriting")
  "fn" '(my-list-notes :which-key "notes")
  "fo" '(my/open-buffer-file-mac :which-key "open buffer in macOS")
  "fm" '(my-list-markdown :which-key "markdown files")
  "ff" '(find-file :which-key "find file")
  "fs" '(save-buffer :which-key "save buffer")
  "fS" '(write-file :which-key "write file")
  "fd" '(dired :which-key "dired")
  "fo" '(reveal-in-osx-finder :which-key "reveal in finder")
  "fr" '(consult-recent-file :which-key "recent files")
  "fR" '(bufferfile-rename :which-key "rename buffer")
#+end_src

This section deals with file operations. Goes to directories, finds files and other assorted tasks.

#+begin_src emacs-lisp
;; Help/emacs
  "h" '(nil :which-key "help/emacs")
  "hv" '(describe-variable :which-key "des. variable")
  "hb" '(describe-bindings :which-key "des. bindings")
  "hM" '(describe-mode :which-key "des. mode")
  "hf" '(describe-function :which-key "des. func")
  "hF" '(describe-face :which-key "des. face")
  "hk" '(describe-key :which-key "des. key")
#+end_src

This is the help section. Gets used multiple times a day.

#+begin_src emacs-lisp
;;set-mode
  "hm" '(nil :which-key "which-mode")
  "hme" '(emacs-lisp-mode :which-key "elisp mode")
  "hmo" '(org-mode :which-key "org mode")
  "hmm" '(markdown-mode :which-key "markdown mode")
  "hmt" '(text-mode :which-key "text mode")
#+end_src

Usually automatic, I use this group rarely. Not sure that I need it but it is here for the time being.

#+begin_src emacs-lisp
  ;; Jumps
  "j" '(nil :which-key "jumps")
  "jr" '(jump-to-register :which-key "registers")
  "jb" '(bookmark-jump :which-key "bookmark jump")
#+end_src

Jumps to the registers and the bookmarks. I do these several times a day and they are repeated commands. Usually achieve this through consult. These are the in built Emacs commands. Might get rid of these.

#+begin_src emacs-lisp
  ;; kirigami
  "k" '(nil :which-key "kirigami")
  "kc" '(kirigami-close-folds :which-key "close all")
  "ko" '(kirigami-open-folds :which-key "open all")
  "kt" '(kirigami-toggle-fold :which-key "toggle folds")
#+end_src

These are the commands for jamescherti/kirigami.el: kirigami.el, a unified method to fold and unfold text in Emacs: outline, outline-indent, org-mode, markdown-mode, gfm-mode, vdiff, hideshow, fold-this, ts-fold, treesit-fold, vimish-fold…. Useful for long documents, and I have a few of those. Love this developer and this package.

#+begin_src emacs-lisp
  ;; Lisp
  "l" '(nil :which-key "lisp")
  "lb" '(eval-buffer :which-key "eval buffer")
  "ld" '(eval-defun :which-key "eval defun")
  "le" '(eval-expression :which-key "eval expression")
  "lr" '(eval-region :which-key "eval region")
  "ls" '(eval-sexp :which-key "eval sexp")
#+end_src

Learning elisp, this is important.

#+begin_src emacs-lisp
  ;;package
  "hp" '(nil :which-key "package")
  "hpr" '(package-refresh-contents)
  "hpi" '(package-install)
  "hpd" '(package-delete)
  "hps" '(straight-pull-all)
#+end_src

Package management in Emacs. Useful sometimes.

#+begin_src emacs-lisp
  ;; Search
  "s" '(nil :which-key "search")
  "sa" '(avy-goto-char-2 :which-key "avy-goto-char-2")
  "ss" '(isearch-forward :which-key "isearch-forward")
  "sr" '(isearch-backward :which-key "isearch-backward")
  "sp" '(project-search :which-key "project search")
  "si" '(consult-imenu :which-key "consult-imenu")
  "sm" '(consult-mark :which-key "jump to marker")
  "sl" '(consult-goto-line :which-key "Go to line")
#+end_src

Search. Gets used multiple times every day. Useful bunch of commands. Fond of consult-goto-line.

#+begin_src emacs-lisp
  ;;System
  "m" '(nil :which-key "system")
  "m1" '(restart-emacs :which-key "restart emacs")
  "m+" '(tab-bar-new-tab :which-key "tab new")
  "m-" '(tab-close :which-key "tab close")
  "ma" '(read-abbrev-file :which-key "read abbrev file")
  "mc" '(hrm-load-current-lisp-file :which-key "load lisp file")
  "mf" '(my/resources-visit :which-key "visit resources")
  "mr" '(nil :which-key "region")
  "mre" '(er/expand-region :which-key "expand region")
  "mrc" '(er/contract-region :which-key "contract region")
  "mp" '(nil :which-key "palimpsest")
  "mpb" '(palimpsest-move-region-to-bottom :which-key "pal move bottom")
  "mpt" '(palimpsest-move-region-to-top :which-key "pal move top")
  "mm" '(nil :which-key "mark")
  "mml" '(my/mark-line :which-key "mark line")
  "mmb" '(my/mark-block :which-key "mark block")
  "mw" '(writegood-mode :which-key "writegood toggle")
#+end_src

The classification is iffy. This is not all system related. The mnemonic is not useful. However these are a set of commands which get used a lot. I have meshed them into my fingers.

#+begin_src emacs-lisp
  ;; Org-mode
  "o" '(nil :which-key "org-mode")
  "oa" '(org-agenda :which-key "org-agenda")
  "oc" '(org-capture :which-key "org-capture")
  "on" '(recursive-narrow-or-widen-dwim :which-key "narrow")
  "or" '(org-refile :which-key "refile")
  "ot" '(org-todo :which-key "DONE")
  "ou" '(my/org-jump-to-heading-beginning :which-key "jump to heading")
  ;; More Org
  "ol" '(nil :which-key "line/link")
  "olb" '(org-beginning-of-line :which-key "begin of line")
  "ole" '(org-end-of-line :which-key "end of line")
  "oli" '(org-insert-link :which-key "org insert link")
  "olh" '(denote-org-link-to-heading :which-key "org link to heading")
  "ols" '(org-store-link :which-key "org store link")
  ;; Yet More org
  "," '(nil :which-key "more-org")
  ",c" '(org-cycle :which-key "org-cycle")
  ",m" '(my/yank-markdown-as-org :which-key "yank md>org")
  ",n" '(outline-next-visible-heading :which-key "next-heading")
  ",p" '(outline-previous-visible-heading :which-key "previous-heading")
  ",t" '(org-set-tags-command :which-key "set-tags")
  ",u" '(outline-up-heading :which-key "up-heading")
#+end_src

I write primarily in Org-mode and there are a host of things Org can do for you. Not all of these get used all the time. They are Org features which make it better than any other markup language for me.

#+begin_src emacs-lisp
  ;; text
  "t" '(nil :which-key "text")
  "ti" '(indent-whole-buffer :which-key "indent whole buffer")
  "tr" '(isearch-query-replace :which-key "find and replace")
  "ts" '(yas-insert-snippet :which-key "insert yasnippet")
  "tR" '(replace-regexp :which-key "replace-regexp")
  "tt" '(titlecase-dwim :which-key "titlecase")
  "t/" '(my/comment-or-uncomment :which-key "comment")
  ;; date/time
  "td" '(nil :which-key "stamp date/time")
  "tdt" '(my/time-stamp-short :which-key "time-stamp-short")
  "tdd" '(my/date-stamp :which-key "date-stamp")
  ;; word
  "tw" '(nil :which-key "word")
  "twc" '(my/copy-whole-word :which-key "copy whole word")
  "twk" '(my/kill-inner-word :which-key "kill inner word")
  "two" '(copy-as-format-org-mode :which-key "copy as org mode")
  "twm" '(copy-as-format-markdown :which-key "copy as markdown mode")
  "twi" '(org-web-tools-insert-web-page-as-entry :which-key "insert web page as entry")
  "tww" '(org-web-tools-insert-link-for-url :which-key "insert link for url")
  "twl" '(dictionary-lookup-definition :which-key "dictionary lookup")
  "twp" '(my/lookup-word-at-point :which-key "lookup word at point")
#+end_src

This is a collection of things which are text related. Functions which make working with text easier in Emacs. I am always using these. They are critical.

#+begin_src emacs-lisp
  ;; Windows
  "w" '(nil :which-key "window")
  "wl" '(windmove-right :which-key "windmove-right")
  "wh" '(windmove-left :which-key "windmove-left")
  "wk" '(windmove-up :which-key "windmove-up")
  "wj" '(windmove-down :which-key "windmove-down")
  "wd" '(delete-window :which-key "delete-window")
  "wr" '(hsplit-last-buffer :which-key "split-window-right")
  "wb" '(vsplit-last-buffer :which-key "split-window-below")
  "ww" '(appine-open-web-split :which-key "appine-web")
  "wf" '(appine-open-file-split :which-key "appine-file")
#+end_src

Windows management related keyboard commands. I use these several times every day.

#+begin_src emacs-lisp
  ;; Visual
  "v" '(nil :which-key "visual")
  "vm" '(view-mode :which-key "view-mode")
  "vt" '(consult-theme :which-key "load theme")
  "vR" '(read-only-mode :which-key "read only mode"))
#+end_src

This is a strange bunch of commands which don’t get used all that often. I use read-only-mode when the document is critical and needs deliberate editing.

Some of the functions are repeated several times in the setup. Those are important to my use and I am trying to figure out which is the easiest way to get to them. This is going to get cleaned up over time.

I love general.el. It has made it possible to define, remember, and use my own keyboard commands in Emacs. Thank you to the developer.

macosxguru at the gmail thingie.

Thanks: Photo by cottonbro studio: https://www.pexels.com/photo/white-apple-keyboard-on-white-table-5082571/

-1:-- Bicycle for Your Mind: general.el to Conquer Emacs Keybindings (Post Planet Emacslife)--L0--C0--2026-03-16T07:00:00.000Z

Sacha Chua: Org Mode: Export HTML, copy files, and serve the results via simple-httpd so that media files work

: Update Oh, ignore all of this! For some reason, when I export the regular Org Mode way, my media files work. Maybe it was just a weird hiccup!

In Org Mode, when you use "Export to HTML - As HTML file and open", the resulting HTML file is loaded using a file:// URL. This means you can't load any media files. In my post about pronunciation practice, I wanted to test the playback without waiting for my 11ty-based static site generator to churn through the files.

simple-httpd lets you run a web server from Emacs. By default, the httpd-root is ~/public_html and httpd-port is 8085, but you can configure it to be somewhere else. Here I set it up to create a new temporary directory, and to delete that directory afterwards.

(use-package simple-httpd
  :config
  (setq httpd-root (make-temp-file "httpd" t))
  :hook
  (httpd-stop . my-simple-httpd-remove-temporary-root)
  (kill-emacs . httpd-stop))

(defun my-simple-httpd-remove-temporary-root ()
  "Remove `httpd-root' only if it's a temporary directory."
  (when (file-in-directory-p httpd-root temporary-file-directory)
    (delete-directory httpd-root t)))

The following code exports your Org buffer or subtree to a file in that directory, copies all the referenced local files (if they're newer) and updates the links in the HTML, and then serves it via simple-httpd. Note that it just overwrites everything without confirmation, so if you refer to files with the same name, only the last one will be kept.

(with-eval-after-load 'ox
  (org-export-define-derived-backend 'my-html-served 'html
    :menu-entry
    '(?s "Export to HTML and Serve"
         ((?b "Buffer"  my-org-serve--buffer)
          (?s "Subtree" my-org-serve--subtree)))))

(defun my-org-serve--buffer (&optional async _subtreep visible-only body-only ext-plist)
  (my-org-export-and-serve nil))

(defun my-org-serve--subtree (&optional async _subtreep visible-only body-only ext-plist)
  (my-org-export-and-serve t))

;; Based on org-11ty--copy-files-and-replace-links
;; Might be a good idea to use something DOM-based instead
(defun my-html-copy-files-and-replace-links (info &optional destination-dir)
  (let ((file-regexp "\\(?:src\\|href\\|poster\\)=\"\\(\\(file:\\)?.*?\\)\"")
        (destination-dir (or destination-dir (file-name-directory (plist-get info :file-path))))
        file-all-urls file-name beg
        new-file file-re
        unescaped)
    (unless (file-directory-p destination-dir)
      (make-directory destination-dir t))
    (unless (file-directory-p destination-dir)
      (error "%s is not a directory." destination-dir))
    (save-excursion
      (goto-char (point-min))
      (while (re-search-forward file-regexp nil t)
        (setq file-name (or (match-string 1) (match-string 2)))
        (unless (or (string-match "^#" file-name)
                    (get-text-property 0 'changed file-name))
          (setq file-name
                (replace-regexp-in-string
                 "\\?.+" ""
                 (save-match-data (if (string-match "^file:" file-name)
                                      (substring file-name 7)
                                    file-name))))
          (setq unescaped
                (replace-regexp-in-string
                 "%23" "#"
                 file-name))
          (setq new-file (concat
                          (if info (plist-get info :permalink) "")
                          (file-name-nondirectory unescaped)))
          (unless (org-url-p file-name)
            (let ((new-file-name (expand-file-name (file-name-nondirectory unescaped)
                                                   destination-dir)))
              (condition-case err
                  (when (or (not (file-exists-p new-file-name))
                            (file-newer-than-file-p unescaped new-file-name))
                    (copy-file unescaped new-file-name t))
                (error nil))
              (when (file-exists-p new-file-name)
                (save-excursion
                  (goto-char (point-min))
                  (setq file-re (concat "\\(?: src=\"\\| href=\"\\| poster=\"\\)\\(\\(?:file://\\)?" (regexp-quote file-name) "\\)"))
                  (while (re-search-forward file-re nil t)
                    (replace-match
                     (propertize
                      (save-match-data (replace-regexp-in-string "#" "%23" new-file))
                      'changed t)
                     t t nil 1)))))))))))

(defun my-org-export-and-serve (&optional subtreep)
  "Export current org buffer (or subtree if SUBTREEP) to HTML and serve via simple-httpd."
  (interactive "P")
  (require 'simple-httpd)
  (httpd-stop)
  (unless httpd-root (error "Set `httpd-root'."))
  (unless (file-directory-p httpd-root)
    (make-directory httpd-root t))
  (unless (file-directory-p httpd-root)
    (error "%s is not a directory." httpd-root))
  (let* ((out-file (expand-file-name (concat (file-name-base (buffer-file-name)) ".html")
                                     httpd-root))
         (html-file (org-export-to-file 'my-html-served out-file nil subtreep)))
    ;; Copy all the files and rewrite all the links
    (with-temp-file out-file
      (insert-file-contents out-file)
      (my-html-copy-files-and-replace-links
       `(:permalink "/") httpd-root))
    (httpd-start)
    (browse-url (format "http://localhost:%d/%s"
                        httpd-port
                        (file-name-nondirectory html-file)))))

Now I can use C-c C-e (org-export-dispatch), select the subtree with C-s, and use s s to export a subtree to a webserver and have all the media files work. This took 0.46 seconds for my post on pronunciation practice and automatically opens the page in a browser window. In comparison, my 11ty static site generator took 5.18 seconds for a subset of my site (1630 files copied, 214 files generated), and I haven't yet hooked up monitoring it to Emacs, so I have to take an extra step to open the page in the browser when I think it's finished. I think exporting to HTML and serving it with simple-httpd will be much easier for simple cases like this, and then I can export to 11ty once I'm done with the basic checks.

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

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

-1:-- Sacha Chua: Org Mode: Export HTML, copy files, and serve the results via simple-httpd so that media files work (Post Planet Emacslife)--L0--C0--2026-03-14T20:43:37.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!