protesilaos / denote

Simple notes for Emacs with an efficient file-naming scheme

Home Page:https://protesilaos.com/emacs/denote

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Denote file names... without ID (cue ominous music)

mentalisttraceur opened this issue · comments

I often use file names which have Denote-style title and keywords, but no datetime. I also wrap and monkey-patch denote-rename-file so that I can still use it on such files.

I was originally making this issue to just describe this and so I can link to it in other issues when relevant, but now it seems like there's receptivity to actually adding it.


Motivation

Denote's file naming convention is really, really great, but for many files and use-cases a timestamp in the name is just visual noise. But many of those can still get huge benefits from the rest of Denote's file naming scheme.

I mentioned in other issues using Denote's naming convention for music files, ebook files, and so on. That probably raised eyebrows - who wants to see YYYYMMDDTHHMMSS-- at the start of every file name in their media player GUI, for example? I certainly don't.

So in my setup, if I have files that look like,

  • title__keyword_keyword2.txt,
  • title--other-title__keyword.txt,
  • __keyword.txt,
  • ==1--whatever.txt
  • and so on.

I've hacked Denote with advice and use some wrapper functions so that keybinds to add/remove keywords or change the title (or the datetime) through denote-rename-file "just work", but it's somewhat narrowly limited to how I use Denote currently, and just involved enough that I'll hold off writing it up until someone tells me it would help them.

(I remove the leading "--" if the title is the first thing because that's the most common case, it's more readable that way, and because "title" is the natural interpretation of an unadorned/unprefixed file name. I keep the other two so that it's always immediately obvious and unambiguous when the start of the file name isn't a title.)

Glad to hear you're open to it too @protesilaos!

In the meantime, I've decided to go ahead and write up what I currently do to make this work in my config.

Big picture: I have four separate commands - one to add keywords one, to remove keywords, one to change the title, and one to change the datetime, Each one prompts for the thing they're changing, then retrieves all the other information that it's not changing, and finally calls denote-rename-wrapper through my wrapper.

Details:

First, I have these two helper functions to add a fake ID to a path, and to strip the ID off:

(defun denoted--remove-id (path)
     (concat
         (file-name-directory path)
         (string-remove-prefix "--"
             (substring (file-name-nondirectory path) 15))))

(defun denoted--add-nil-id (path)
    (let ((name (file-name-nondirectory path)))
        (concat
            (file-name-directory path)
            "00000000T000000"
            (if (or (string-prefix-p "==" name)
                    (string-prefix-p "__" name))
                ""
                "--")
            name)))

For retrieving the title, I use this:

(defun denoted-title-get (path)
    (let ((denote-directory default-directory)
          (note-type (denote-file-note-type path)))
        (if note-type
            (denote-retrieve-title-value path note-type)
            (if (denote-file-has-identifier-p path)
                (denote-retrieve-filename-title path)
                (denote-retrieve-filename-title
                    (denoted--add-nil-id path))))))
  • denote-file-note-type (see #284) already works whether or not the file name has a Denote ID in it.

  • If denote-file-note-type did not recognize the file as a note, get the title from the file name. (I am using the not-yet-release definition of denote-retrieve-filename-path, per #272.)

    • If the file name does not have an ID, I just slap a fake one on it so that I can reuse Denote's own logic.

denote-extract-id-from-string, denote-extract-keywords-from-path, and denote-retrieve-filename-signature Just Work with the naming scheme described above.

Then I wrap denote-rename-file calls to apply some advice during the call, kinda like this:

(defun my-denote-rename-file-wrapper (... id)
    (with-advice ('denote-get-file-extension
                      :filter-args 'hack-denote-get-file-extension)
        (if (equal id "")
            (with-advice ('denote-format-file-name
                              :filter-return 'denoted--remove-id)
                (denote-rename-file ...))
            (with-advice ('denote-retrieve-filename-identifier
                              :override (lambda (&rest _) id))
                (denote-rename-file ...)))))

where hack-denote-get-file-extension is defined as

(defun hack-denote-get-file-extension (arguments)
    (let ((path (car arguments)))
        (unless (denote-file-has-identifier-p path)
            (setcar arguments (denoted--add-nil-id path))))
    arguments)

and if I the ID is blank, I abuse a lucky aspect of the denote-rename-file implementation by advising denote-format-file-name to remove the ID from its return value - otherwise, I do the trick described in #278 .

We have a basic version of this in the manual: https://protesilaos.com/emacs/denote#h:e666ced6-da75-4bdb-9be3-82c2f4455ee9

Okay, I took the time to understand this. Thank you for the pointer @protesilaos !

I think I could achieve what I currently do without monkey-patching (or even calling) denote-rename-file by combining

  • denote-format-file-name,
  • denote-rename-file-prompt,
  • denote-rename-file-and-buffer (with #279),
  • denote-update-dired-buffers,
  • denote-rewrite-front-matter,
  • denote--edit-front-matter-p, and
  • denote-file-note-type (from #284).

(I disable denote--add-front-matter with :override 'ignore during my wrapped Denote rename calls, so I don't need that.)

I'm gonna try that out. I'd certainly much rather move away from monkey-patching with advice dependent on internal implementation details.

Okay, assuming

  1. denote-file-note-type from #284 ,
  2. denote-rename-buffer-and-file is patched to ignore missing-file from rename-file (the simplest subset of #279 ), and
  3. denote-format-rename-file is patched to accept an empty string ID to produce names with with no ID as described in this issue,

then I think I can achieve everything I am currently doing+wanting with denote-rename-file without resorting to any monkey-patching. Here's a sketch, untested:

    (defun denoted-rename-file (path datetime prefix title tags)
        (setq-if-nil datetime "")
        (setq-if-nil prefix "")
        (setq-if-nil title "")
        (let* ((directory (file-name-directory (expand-file-name path)))
               (extension (...))
               (new-path  (denote-format-file-name
                              directory
                              datetime
                              tags
                              (denote-sluggify title 'title)
                              (concat "." extension)
                              (denote-sluggify-signature prefix))))
            (when (denote-rename-file-prompt path new-path)
                (denote-rename-file-and-buffer path new-path)
                (let ((denote-directory directory))
                    (denote-update-dired-buffers))
                (when-let (type (denote-file-note-type new-path))
                    (denote-rewrite-front-matter new-path title tags type t)))))

That ends up being much clearer than all the advising monkey patching I was doing, and significantly less code in total.

In my fork of the code (I will release it this weekend), all file name components are optional (including ID) and can be arranged in another order. Note that some features will not work without identifiers (linking and others).

Only the identifier and the title will drop their delimiter if it is the first component of the file. Fontification (probably) will work.

I will start making pull requests in May for their inclusion in Denote.

Just a comment about the code,
| We have a basic version of this in the manual:
| https://protesilaos.com/emacs/denote#h:e666ced6-da75-4bdb-9be3-82c2f4455ee9

This helps me quite a bit with my need to mark PDFs and other kinds of files without the date ID. Fantastic, as far as it goes. I don't understand the edge cases. This is something I really need; I agree that the ID is noise for these files.

I also have two other items on my feature request list, to mention now:

  • could a modified theme dim down the ID part of the name and emphasize the title and keywords differentially.?
  • I have not been about to find out how to "respect CamelCase" so far.

As one who understands denote in only a superficial way, I have a long ways to go to make best use of the "keywords-only" renaming. It would be really cool to be able to invoke a dynamic block with those pdfs. I may be asking too much.

Thank you for that code, anyway; now I need to read the rest of this post.

Alan

The code in the

@lngndvs

could a modified theme dim down the ID part of the name

Yeah, the theme just needs to set the faces

  • denote-faces-time
  • denote-faces-date, and
  • denote-faces-time-delimiter

to the dimmed colors. (If the theme is already changing one or more of denote-faces-year, denote-faces-month, denote-faces-day, denote-faces-hour, denote-faces-minute, and denote-faces-second, you might need to change those as well.)

@lngndvs As for the second part of your post, here is the solution to allow CamelCase for keywords:

(setq denote-file-name-slug-functions
    '((title . denote-sluggify-title)
      (signature . identity)
      (keyword . identity)))

I may make a pull request in the future to make this the default. It seems to me that sluggification really is only necessary by default for the title.

@jeanphilippegg It was possible to dim the date/id, by editing the file for modus-themes. I have noticed something previously about modus-vivendi that bit me once before, but I cannot explain that previous issue. What it appears to stem from is that the full gamut of faces is not present in the file modus-vivendi.el, as they were in modus-operandi.el. So changes to modus-vivendi do not happen. I am guessing that some shortcuts are taken with modus-vivendi? Interestingly, when I had the other issue (which involved a hack in Sacha Chua's Emacs News posts, some months ago) it did not work with modus-vivendi, but it DID work with the deuteranope version. Interesting.

I am well on the way to better readability.

By the way, if sluggification of titles respected titles for PDFs that would be swell for my use case. Two different emacs packages can deal with CamelCase, I think, but I didn't try either of them. Keyword CamelCase may not be as useful, at least to me. Perhaps the searching regular expressions in denote would be hamstrung by uc/lc distinctions?

Thank you for the help.

@lngndvs, mentalisttracer is the one that gave you the solution to dim the identifier. Try something like this:

(set-face-attribute 'denote-faces-date nil :inherrit 'denote-faces-time-delimiter)

I gave you the solution to disable the sluggification for titles. Here is a function that converts "This is a note" to "ThisIsANote".

(defun my-camel-case-function (str)
  (string-replace " " "" (capitalize str)))

You can use it directly has as the sluggification function for titles if you don't want anything else that the default sluggification does.

Having the sluggification for everything ensures that searches like _word or =word will always match. Whereas not touching the input will result in file names that may have varying patterns. Long-term, this makes it a bit harder to find what you are looking for (granted, a regexp will do it, but still).

Thank you, I now understand the design choice. We can keep the current defaults. I will not make a pull request for that.

I am fine with only denote-file-name-slug-functions for now. It can be let-bound. Maybe some other sluggification functions can be provided (eg camelcase) if there is demand for it.