zk-org / zk

A plain text note-taking assistant
https://zk-org.github.io/zk/
GNU General Public License v3.0
1.68k stars 127 forks source link

Renaming and moving notes #200

Open mickael-menu opened 2 years ago

mickael-menu commented 2 years ago

Discussed in https://github.com/mickael-menu/zk/discussions/199

Originally posted by **srid** April 27, 2022 Would this be in scope for `zk`? Basically: ``` zk rename Foo "Bar Qux" ``` will rename `Foo.md` to `Bar Qux.md` while taking care of references. By which I mean it will replace all occurrences of `[[Foo]]` with `[[Bar Qux]]` in the notebook. As well as `[[Foo|...]]` with `[[Bar Qux|..]]`. This is exactly what the vscode-memo extension does. The replacement can be delegated to [`sd`](https://github.com/chmln/sd). EDIT: It should probably take directory and relative wiki links too into consideration? eg: `[[Parent/Foo]]` -> `[[Parent/Bar Qux]]`. Might have to disambiguate multiple `Foo.md` as well (in different parents).
mickael-menu commented 2 years ago

Here's a spec, feel free to review and comment.

We're talking about two separate features using the same renaming process under the hood:

In both cases, the path of the note, its title and all the inbound or outbound links might need to be updated in a single atomic operation.

The actual FS changes will be handled by POSIX diff and patch.

New commands

We'll need one new zk command per feature. Both commands will fail if the new generated filenames already exist.

zk rename

Usage: zk rename <path> <new-title>

Rename the title of a note, updating all the backlinks and the note filename, if needed.

Arguments:
  <path>         Path to the note to rename.
  <new-title>    New title of the note.

Flags:
  -i, --interactive            Edit the changes before they are applied.
  -n, --dry-run                Don't actually rename the note. Instead, prints the changes on stdout
                               and the updated note path on stderr.

zk move

Usage: zk move <source> ... <target>

Move a note (or several) to a different path, updating all the backlinks.

Arguments:
  <source>    Paths to the notes or directories to move.
  <target>    Target directory or path if a single source note is given.

Flags:
  -i, --interactive            Edit the changes before they are applied.
  -n, --dry-run                Don't actually move the notes. Instead, prints the changes on stdout
                               and the updated note paths on stderr.

Interactive flag (-i)

The interactive flag offers an opportunity to edit the changes before they are applied. The confirmation is done in two steps, by editing temporary files (similar to git commit).

Step 1: Validate new paths

This edits a file containing all the filename/paths changes, with the following format:

source.md > target.md
Foo.md > Bar Qux.md

The user can edit the target paths, or remove a line to cancel a file move. Emptying the file cancels the whole process.

Step2: Validate diff changes

After validating the new paths, zk will generate all the file changes required (renaming titles, updating links) and generate a POSIX diff.

The user can edit any part of the diff, as long as the output is still a valid POSIX diff file. It will be applied as-is. Emptying the diff file cancels the whole process.

Dry run (-n)

The dry run flag prints the content of the changes (as edited in the interactive flag) without applying them.

LCP integration

If the client sends a workspace.didRenameFiles event, the server will trigger the renaming process to update the links.

The user can update the title of a note manually using a RenameRequest when the caret is over the note title. This will trigger the renaming process to update the links and note filepath.

The LCP integration is not compatible with the interactive mode. The user is expected to backup their notes before such operation.

New config options

The user can configure the diff and patch commands used by zk from the config file.

[tool]
# Diff command used to edit the changes applied when renaming notes.
diff = "colordiff -u"

# Editor used to modify a diff when using the interactive mode.
# When missing, falls back on tool.editor.
diff-editor = "nvim"

# Patch command applying the changes when renaming notes.
# Use `patch --backup` to make backup copies of the files before they are modified.
patch = "patch"

Processes

Rename process

Given the note N that we want to retitle "T".

  1. If the note.filename option for the note group of N contains the keyword title:
    1. Regenerate a new filename for N using the provided title T.
      • If the note.filename template contains a generated id, it will be randomized again. This should be fine as the inbound links will be updated anyway, but the user can decide to revert the id in interactive (-i) mode.
    2. If the process is in interactive mode, confirm the new filename with the user using tool.diff-editor.
      • The whole process is interrupted if the user empties the changes file.
    3. Move the file N to its new location.
  2. Create a new compound diff file under .zk/DIFF.
  3. Append to DIFF the result of replacing in the note content:
    • the old title of N by the new one T
    • the old filename by the new generated one, if needed
  4. Iterate over all the notes linking to N, then for each link:
    1. Generate a new link using format.link-format with the new title T and generated filepath.
    2. Append to DIFF the result of replacing the old link by the new generated one.
  5. If the process is in interactive mode, allow the user to edit the DIFF file using tool.diff-editor.
    • The whole process is interrupted if the user empties the changes file.
  6. Apply the diff patch using tool.patch.
  7. Re-index the notebook.

It's not a priority, but the link replacement algorithm could be smarter to try to preserve case changes or custom titles in a link.

Move process (single note as source)

Given the note N that we want to move to the new path P.

  1. If P is a directory, append the filename of N to P.
  2. Move the file N to its new location P.
  3. Create a new compound diff file under .zk/DIFF.
  4. Append to DIFF the result of replacing in the content of N:
    • the old filename by the new one
  5. Iterate over all the notes linking to N, then for each link:
    1. Generate a new link using format.link-format with the new path P.
    2. Append to DIFF the result of replacing the old link by the new generated one.
  6. If the process is in interactive mode, allow the user to edit the DIFF file using tool.diff-editor.
  7. Apply the diff patch using tool.patch.
  8. Re-index the notebook.

Move process (several notes or a folder as source)

Given the sources Sn that we want to move to the new folder P.

  1. We expand each notes under Sn and append the child path portions to P to resolve the rename pairs.
  2. If the process is in interactive mode, confirm the renames with the user using tool.diff-editor.
    • The whole process is interrupted if the user empties the changes file.
    • If only some lines of the file are removed, these files are skipped.
  3. To simplify, we apply the "single note move process" described above to each rename atomically.
  4. If there are empty folders in Sn, they are deleted.

Improvements

It's not a priority, but the link replacement algorithm could be smarter to try to preserve case changes or custom titles in a link.

igxlin commented 1 year ago

@mickael-menu Look like some logic missing in moving process logic. The links inside the moved file should be updated too. For example, file foo.md move to bar/. Then the link ./path/to/file.md inside foo.md may need to be updated as ../path/to/file.md.

mhmood-sf commented 2 months ago

I believe this should be in scope for zk, because the README highlights do mention notebook housekeeping (and renaming is definitely a big part of it, not to mention the "housekeeping" only consists of two features as of now).

The renaming features suggested by @mickael-menu are quite comprehensive; personally I would be very happy with a simple renaming command that lets me do batch renaming using templates, something like the following:

# Change name format from "{{timestamp}}-{{CamelCaseTitle}}" to "{{timestamp}}-{{slug title}}"
zk rename Wiki/ "{{metadata.ctimestamp}}-{{slug title}}"

Since the notes already exist, the rename command can allow metadata and all other relevant fields in the template. This could provide very powerful renaming capabilities.

I am not a heavy user of the linking capabilities so I'm not sure how those would be handled.