Categories: geek

RSS - Atom - Subscribe via email

Make chapter markers and video time hyperlinks easier to note while I livestream

| org, emacs

I want to make it easier to add chapter markers to my YouTube video descriptions and hyperlinks to specific times in videos in my blog posts.

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

YE16: Sacha and Prot talk Emacs

| emacs, yay-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

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

| org, emacs, js

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

2026-04-13 Emacs news

| emacs, 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

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

| emacs, org

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

YE12: Categorizing Emacs News, epwgraph, languages

| emacs, stream, yay-emacs

View in the Internet Archive, watch or comment on YouTube, or email me.

Chapters:

  • 00:41:21 epwgraph
  • 00:54:56 learning languages

Thanks for your patience with the audio issues! At some point, I need to work out the contention between all the different processes that I want to be listening to the audio from my mic. =)

In this livestream, I categorize Emacs News for 2026-04-06, show epwgraph for managing Pipewire connections from Emacs, and share some of my language learning workflows.

View Org source for this post

2026-04-06 Emacs news

| emacs, 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