Showing 565 to 568

Org-roam fixes

Some mods to #org-roam I've felt necessary.

[2024-08-20 Tue]: My replacement package org-node now contains all of these features!

Skip prompting for a filename

It's enough that you gave a title, shouldn't also have to confirm the filename, right?

(setq org-roam-capture-templates
      '(("d" "default" plain "%?" :if-new
         (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                    "#+title: ${title}\n#+filetags: :noexport:stub:"))
        :immediate-finish t
        :jump-to-captured t))

Quick stubs🔗

Situation: You're in the middle of writing and you realize you want to link to a node that doesn't yet exist.

I have the following capture template for that. It makes a blank new file without visiting it, so you can pick it on the org-roam-node-insert screen and be on your merry way.

(add-to-list 'org-roam-capture-templates
             '("i" "instantly create this node" plain "%?" :if-new
               (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                          "#+title: ${title}\n#+filetags: :stub:\n")
               :immediate-finish t)

Performance🔗

Org-roam gets slow as you accumulate thousands of nodes. The following code fixes it. Requires the packages memoize and vulpea.

Vulpea provides faster versions of org-roam commands, but it depends on its own optimized database. The first time you enable vulpea-db-autosync-mode, you need to eval (org-roam-db-sync 'force) so it can populate that database.

;; Make the commands `org-roam-node-find' & `org-roam-node-insert' faster and
;; often instant.

;; Small drawback: after you just created a node, you can't immediately
;; find it as it won't be in the cache.  You must leave Emacs alone for
;; 10 seconds, then it'll enter the cache.

(defun my-vulpea-memo-refresh ()
  (memoize-restore #'vulpea-db-query)
  (memoize         #'vulpea-db-query)
  (vulpea-db-query nil))

(defvar my-vulpea-memo-timer (timer-create))
(defun my-vulpea-memo-schedule-refresh (&rest _)
  "Schedule a re-caching when the user is idle."
  (cancel-timer my-vulpea-memo-timer)
  (setq my-vulpea-memo-timer
        (run-with-idle-timer 10 nil #'my-vulpea-memo-refresh)))

;; Love this lib. Thank you
(use-package vulpea
  :hook ((org-roam-db-autosync-mode . vulpea-db-autosync-enable))
  :bind (([remap org-roam-node-find] . vulpea-find)
         ([remap org-roam-node-insert] . vulpea-insert))
  :config
  (use-package memoize :demand)
  (memoize #'vulpea-db-query)
  (advice-add 'org-roam-db-update-file :after 'my-vulpea-memo-schedule-refresh))

Other speed boosts

;; Don't search for "roam:" links (it slows saving on large files)
(setq org-roam-link-auto-replace nil)

;; Speed up `org-roam-db-sync'
;; NOTE: `setopt' breaks it, use `setq'
;; NOTE: Counterproductive on Windows
(setq org-roam-db-gc-threshold most-positive-fixnum)

If you still find it slow to save large files, maybe you have org-crypt configured. It's that package that encrypts/decrypts subtrees.

Doom Emacs' Org module ships it by default, so here's how to disable it in Doom's packages.el:

(package! org-crypt :disable t)

Custom org-roam-extract-subtree🔗

I encourage you to maintain a personal variant of org-roam-extract-subtree. It's a pleasure to have one that does everything right.

Here's mine. Compare with the original (type M-x find-function org-roam-extract-subtree RET). Differences from the default command:

  1. Actually make a file-level node, i.e. a file with :PROPERTIES: at the top, instead of a plain file with a top-level heading
  2. Skip prompting for a title – just reuse the heading as title
  3. Copy the original file's :CREATED: property if the subtree didn't already have its own (I use this property everywhere because it's not as if you can rely on filesystem ctime)
  4. Copy all the parent tags
    • Only if org-use-tag-inheritance is t
    • If there were no tags anywhere, add a :noexport: tag. That's normally what I want for new nodes (Slipbox workflow).
  5. Add a #+date: for me to fill in later
    • I use this field to indicate the date of "last meaningful update", thus it becomes Last Updated on my blog. Could use file modification time, but it is not meaningful.
  6. Show the resulting file to me, so I can verify it looks right
  7. Leave a link in the original file, where the subtree was
(defun my-org-roam-extract-subtree ()
  "Variant of `org-roam-extract-subtree'.
It skips prompting, and inserts the metadata I want."
  (interactive)
  (save-excursion
    (org-back-to-heading-or-point-min t)
    (when (bobp) (user-error "Already a top-level node"))
    (org-id-get-create)
    (save-buffer)
    (org-roam-db-update-file)
    (let* ((template-info nil)
           (node (org-roam-node-at-point))
           ;; Determine filename based on `org-roam-extract-new-file-path'
           (template (org-roam-format-template
                      (string-trim (org-capture-fill-template
                                    org-roam-extract-new-file-path))
                      (lambda (key default-val)
                        (let ((fn (intern key))
                              (node-fn (intern (concat "org-roam-node-" key))))
                          (cond
                           ((fboundp fn)
                            (funcall fn node))
                           ((fboundp node-fn)
                            (funcall node-fn node))
                           (t (let ((r (read-from-minibuffer (format "%s: " key) default-val)))
                                (plist-put template-info ksym r)
                                r)))))))
           (file-path
            (expand-file-name template org-roam-directory))
           (parent-tags (org-get-tags))
           (parent-creation (save-excursion
                              (goto-char (point-min))
                              (org-entry-get nil "CREATED"))))
      (when (file-exists-p file-path)
        (user-error "%s exists. Aborting" file-path))
      (org-cut-subtree)
      (open-line 1)
      (insert "- " (org-link-make-string
                    (concat "id:" (org-roam-node-id node))
                    (org-roam-node-formatted node)))
      (save-buffer)
      (find-file file-path)
      (org-paste-subtree)
      (while (> (org-current-level) 1)
        (org-promote-subtree))
      (save-buffer)
      (org-roam-promote-entire-buffer)
      (goto-char (point-min))
      (unless (org-entry-get nil "CREATED")
        (org-set-property "CREATED" (or parent-creation
                                        (format-time-string "[%F]"))))
      (org-roam-tag-add (or parent-tags
                            '("noexport")))
      (search-forward "#+title")
      (goto-char (line-beginning-position))
      (if (version<= "29" emacs-version)
          (ensure-empty-lines 0)
        (when (looking-back "\n\n")
          (join-line)))
      (search-forward "#+filetags" nil t)
      (forward-line 1)
      (open-line 2)
      (insert "#+date:")
      (save-buffer))))

Custom org-open-at-point🔗

Before you can start to use roam refs confidently, or even really understand what they are, you need a picture in your head of how they will make your life easier.

Part of my picture was that I should be able to insert a raw URL anywhere and then if I click on it, Emacs will jump to the corresponding roam-node if I happen to have one that refs that URL.

This replacement for org-open-at-point does that.

(define-key global-map [remap org-open-at-point]
            #'my-org-open-at-point-as-maybe-roam-ref)
(defun my-org-open-at-point-as-maybe-roam-ref (&optional arg)
  "Like `org-open-at-point', but prefer to visit any org-roam node
that has the link as a ref.
If already visiting that same node, then follow the link normally."
  (interactive "P")
  (let* ((url (thing-at-point 'url))
         (path (if (derived-mode-p 'org-mode)
                   (org-element-property :path (org-element-context))
                 (replace-regexp-in-string (rx bol (* (not "/"))) "" url)))
         (all-refs (org-roam-db-query
                    [:select [ref id]
                     :from refs
                     :left-join nodes
                     :on (= refs:node-id nodes:id)]))
         (found (when path (assoc path all-refs))))

    (if (and found
             ;; check that the ref does not point to THIS file (if so, better to
             ;; just open the url normally)
             (not (when (derived-mode-p 'org-mode)
                    (equal (cdr found)
                           (or (org-id-get)
                               (progn
                                 (goto-char (point-min))
                                 (org-id-get)))))))
        (org-roam-node-visit (org-roam-node-from-id (cadr found)))
      (if arg
          (org-open-at-point arg)
        (org-open-at-point)))))

Command to auto-rename file based on #+TITLE🔗

In Org-roam, nodes are named by the #+title line – it doesn't care about the name of the files on the filesystem.

However, the filename does influence the exported HTML filenames, which determines your blog post addresses.

With the following command, you can type M-x my-rename-roam-file-by-title whenever you want to update the filename.

(defun my-rename-roam-file-by-title (&optional path)
  "Rename file in current buffer, based on its Org
#+title property.

Can also take a file PATH instead of current buffer."
  (interactive)
  (unless path
    (setq path (buffer-file-name)))
  (unless (equal "org" (file-name-extension path))
    (user-error "File doesn't end in .org: %s" path))
  (let* ((visiting (find-buffer-visiting path))
         (on-window (and visiting (get-buffer-window visiting)))
         (slug
          (with-current-buffer (or visiting (find-file-noselect path))
            (goto-char (point-min))
            (let ((node (org-roam-node-at-point t)))
              (unless (org-roam-node-title node)
                (user-error "Node not yet known to org-roam DB"))
              (org-roam-node-slug node))))
         (new-path (expand-file-name (concat slug ".org")
                                     (file-name-directory path))))
    (if (equal path new-path)
        (message "Filename already correct: %s" path)
      (if (and visiting (buffer-modified-p visiting))
          (message "Unsaved file, letting it be: %s" path)
        (unless (file-writable-p path)
          (error "No permissions to rename file: %s" path))
        (unless (file-writable-p new-path)
          (error "No permissions to write a new file at: %s" new-path))
        ;; Kill buffer before renaming, to be safe
        (when visiting
          (kill-buffer visiting))
        (rename-file path new-path)
        (prog1 (message "File %s renamed to %s"
                        (file-name-nondirectory path)
                        (file-name-nondirectory new-path))
          ;; Visit the file again if you had it open
          (when visiting
            (let ((buf (find-file-noselect new-path)))
              (when on-window
                (set-window-buffer on-window buf)))))))))

Command to rename an image asset and rewrite all links pointing to it

(defun my-rename-roam-asset-and-rewrite-links ()
  (interactive)
  (require 'dash)
  (when (or (equal default-directory org-roam-directory)
            (when (yes-or-no-p "Not in org-roam-directory, go there?")
              (find-file org-roam-directory)
              t))
    (when-let ((bufs (--filter (string-search "*grep*" (buffer-name it))
                               (buffer-list))))
      (when (yes-or-no-p "Some *grep* buffers, kill to be sure this works?")
        (mapc #'kill-buffer bufs)))
    (let* ((filename (file-relative-name
                      (read-file-name "File: ") org-roam-directory))
           (new (read-string "New name: " filename)))
      (mkdir (file-name-directory new) t)
      (unless (file-writable-p new)
        (error "New path wouldn't be writable"))
      (rgrep (regexp-quote filename) "*.org")
      (run-with-timer
       1 nil
       (lambda ()
         (save-window-excursion
           (delete-other-windows)
           (switch-to-buffer (--find (string-search "*grep*" (buffer-name it))
                                     (buffer-list)))
           (wgrep-change-to-wgrep-mode)
           (goto-char (point-min))
           (query-replace filename new)
           (wgrep-finish-edit)
           (when (y-or-n-p "Finished editing links, rename file?")
             (rename-file filename new)
             (message "File renamed from %s to %s" filename new)))))
      (message "Waiting for rgrep to populate buffer..."))))
Created (15 months ago)
Updated (11 months ago)

Do recruiters do anything useful?

Paraphrasing a story someone told on HN: "everyone brags that they hire only the top 1% of developers. Problem is that can't be true, if 100% of the developers here get hired eventually." What's happening is that every time they decide to hire someone, they filter out 99% of what's left on the market and find someone. Then another company filters out 99% of what's left after that, and so on… until all developers have been distributed equally among the companies.

A lot of work for nothing? Why not skip filtering?

Maybe it's that if one company doesn't bother to filter, they will end up with a worse crop over time than the others. (Assuming that filtering is possible, but that's another topic.)

Created (11 months ago)

Marilyn Frye's quote

“To say that straight men are heterosexual is only to say that they engage in sex (fucking exclusively with the other sex, i.e., women). All or almost all of that which pertains to love, most straight men reserve exclusively for other men. The people whom they admire, respect, adore, revere, honor, whom they imitate, idolize, and form profound attachments to, whom they are willing to teach and from whom they are willing to learn, and whose respect, admiration, recognition, honor, reverence and love they desire… those are, overwhelmingly, other men. In their relations with women, what passes for respect is kindness, generosity or paternalism; what passes for honor is removal to the pedestal. From women they want devotion, service and sex. Heterosexual male culture is homoerotic; it is man-loving.”

― Marilyn Frye

What links here

  • Notes on gender and sexuality
Created (12 months ago)

Catalan

  • In Catalan, unstressed "o" is pronounced [u]. Example: Montserrat, oral, al·legoria
  • In Catalan, "x" at the start of a word is pronounced [ʃ] ("sh"). Example: xocolata, Xina, Xoriço
  • In Catalan, pronounce dimarts. Result "dimars" because ending -rts drops the t.
  • In Catalan, pronounce molt. Result: "mol" because ending -lt drops the t.
  • In Catalan, pronounce xocolata. Result: "shokolata"
  • In Catalan, pronounce hola, molt de gust. Result: "ola, mol de gust"
  • In Catalan, pronounce dependent. Result: "dependen" because ending -nt drops the t.
  • In Catalan, "ç" is pronounced [s]. Example: plaça, Barça
  • In Catalan, "h" at the start of a word is pronounced not at all (silent). Example: home, hotel, historia
  • @anki In Catalan, "d" between vowels
  • In Catalan, Tarragona, Palma and Andorra follow the same word-stress rule: stress the next-to-last syllable
  • In Catalan, cerveses, Cases and Rambles folow the same word-stress rule: stress the next-to-last syllable
  • In Catalan, where is main stress in "visiten"? viSIten
  • In Catalan, where is main stress in "restaurant"? restaurANT
  • In Catalan, where is main stress in "vocal"? vocAL
  • In Catalan, where is main stress in "hotel"? hoTEL
  • In Catalan, where is main stress in "música"? MUsica
Created (14 months ago)
Updated (14 months ago)
Showing 565 to 568