Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Add indicator of current silo in the prompt when running denote commands #432

Open
IceAsteroid opened this issue Sep 12, 2024 · 17 comments

Comments

@IceAsteroid
Copy link

IceAsteroid commented Sep 12, 2024

Hi, greetings, guys!


I found it's inefficient to identify the current silo after switching to one of them.

In my use case, a silo will contain a large amount of org files, each silo is for a specific field of research.

After I switched to a silo, it's rare to switch to another silo during the day or some few days, but sometimes after the switch, I must know which silo I'm current in, otherwise, I'd mess up and a newly created note by me will reside in an undesired silo instead.

Here's some suggestions:

  • Add a command in denote-silo-extras.el to only switch to a silo and do nothing, compared to other commands that must run actions after execution.
  • [Fix typo] Add an indicator string, say if I have two silos "IT" and "Art", and currently in the "IT" silo, when I execute the denote command and the minibuffer's prompt shall be like:
1/23   In IT   New file TITLE: 

(I'm using the vertico package, something might be displayed a bit different here)

  • Either IT or Art is the last directory name of the path, as specified in the denote-silo-extras-directories variable. For example: ~/OrgWiki/IT and ~/OrgWiki/Art.

It's more nice to also add the silo indicator for consult-notes, but that's another discussion, I'll post it on the consult-notes' repo issue page.

@protesilaos
Copy link
Owner

protesilaos commented Sep 12, 2024 via email

protesilaos added a commit that referenced this issue Sep 12, 2024
Thanks to IceAsteroid for suggesting this in issue 432:
<#432>.
@IceAsteroid
Copy link
Author

IceAsteroid commented Sep 13, 2024

Note that you need to update 'denote' as I just added the 'denote-silo-extras-with-silo'.

Sorry for the delay, I'm trying to figure out how to upgrade the package to a lastest commit from source, since it seems not being shipped with the latest version. I gotta try the code first.

@protesilaos
Copy link
Owner

protesilaos commented Sep 13, 2024

No worries! Here is the code that you can use without upgrading:

(defun denote-silo-extras-path-is-silo-p (path)
  "Return non-nil if PATH is among `denote-silo-extras-directories'."
  (member path denote-silo-extras-directories))

(defun my-denote-silo-extras-dired-to-silo (silo)
  "Switch to SILO directory using `dired'.
SILO is a file path from `denote-silo-extras-directories'.

When called from Lisp, SILO is a file system path to a directory that
conforms with `denote-silo-extras-path-is-silo-p'."
  (interactive (list (denote-silo-extras-directory-prompt)))
  (if (denote-silo-extras-path-is-silo-p silo)
      (dired silo)
    (user-error "`%s' is not among the `denote-silo-extras-directories'" silo)))

(defun my-denote-silo-extras-cd-to-silo (silo)
  "Switch to SILO directory using `cd'.
SILO is a file path from `denote-silo-extras-directories'.

When called from Lisp, SILO is a file system path to a directory that
conforms with `denote-silo-extras-path-is-silo-p'."
  (interactive (list (denote-silo-extras-directory-prompt)))
  (if (denote-silo-extras-path-is-silo-p silo)
      (cd silo)
    (user-error "`%s' is not among the `denote-silo-extras-directories'" silo)))

EDIT: added a missing function.

@IceAsteroid
Copy link
Author

IceAsteroid commented Sep 14, 2024

I might understand it wrong.(I got busy, sorry to be late)

After I switched to a silo with the provided commands above, invoking the command denote-create-note won't create the note in the selected silo, but instead it's created in the directory set in the denote-directory variable.

I've also set up the .dir-locals.el file in each silo's directory and made it executable.

It seems that the switched silo is remembered as the default selected candidate when I once again invoke one of the commands like denote-silo-extras-create-note, in the minibuffer with vertico enabled. Is this what's supposed to work?

The documentation says:

When inside the directory that contains this .dir-locals.el file, all Denote commands/functions for note creation, linking, the inference of available keywords, et cetera will use the silo as their point of reference (How to switch a silo). They will not read the global value of denote-directory. The global value of denote-directory is read everywhere else except the silos.

@IceAsteroid
Copy link
Author

IceAsteroid commented Sep 14, 2024

It must visit a file in a silo directory, or with my-denote-silo-extras-dired-to-silo to open a dired buffer in the silo's directory and it must also be opened in the "current buffer", in order to make the dir-locals.el file set the directory-variables. And the my-denote-silo-extras-cd-to-silo command would not work.

That being said, it's not about the switch on which silo to use, but it depends on the visiting of files in a silo. Even though it was switched to another silo with the commands provided above, say silo A, but the visiting file in the "current buffer" is in silo B, the note creation will still pick silo B instead, because of dir-locals.el in each silo, that sets the denote-directory variable to the path of silo.

Which makes the switch on silo senseless, the user will still mess up that which silo is in use, or he has to visit a file in a desired silo, or to open the silo directory with dired every time when he creates a note.


In short, the commands denote-silo-extras-create-note, denote-silo-extras-open-or-create, and denote-silo-extras-select-silo-then-command work just fine.

But my-denote-silo-extras-cd-to-silo doesn't work, and my-denote-silo-extras-dired-to-silo does work unexpectedly with the description of this feature request.


I think this working mechanism on silos by itself is a bit chaotic, I've been trying lots of tests just to figure out, I didn't mean to be confusing, sorry.


In the current setup, It's leaving a few options for me:

  1. Switch to a silo with my-denote-silo-extras-dired-to-silo. And do not manually visit files in any silo via other commands like find-file, to prevent confusion on which silo is in use, by first switching to a silo before visiting a desired note.

  2. Use existing commands denote-silo-extras-create-note, denote-silo-extras-open-or-create, and denote-silo-extras-select-silo-then-command. And consult-notes must be used only after any of the listed commands, in order to open in the right silo.

Plus each time when done visiting notes in a silo and before switching to another silo, I have to close the buffers of the visited notes all to prevent they mess up denote-directory again when switching buffers!

  1. Do not use dir-locals.el to set directory variables, but instead have some way to set the denote-directory after the switch to a silo.

  2. Don't use silos at all..


That's basically it, it's complicated then it seems.

@protesilaos
Copy link
Owner

Plus each time when done visiting notes in a silo and before switching to another silo, I have to close the buffers of the visited notes all to prevent they mess up denote-directory again when switching buffers!

I do not know exactly how your workflow is, but you do not need to close any buffers for the active silo to be picked up each time. The directory-local variables apply to the files in those directories as well, so when you switch to another buffer that belongs to a different silo Emacs will read a new directory-local variable.

The code I shared with you earlier does what it intends, namely to prompt for a silo and then use cd or dired there. The cd, for example, is useful if your next command is something that searches the current directory. So these are technically not wrong, but I understand they are not what you expect.

Let's then take a step back. Can you describe me what your preferred workflow is? I will then see what I can do.

@protesilaos
Copy link
Owner

Also, please tell me what version of Emacs you are on.

@IceAsteroid
Copy link
Author

IceAsteroid commented Sep 20, 2024

The directory-local variables apply to the files in those directories as well, so when you switch to another buffer that belongs to a different silo Emacs will read a new directory-local variable.

That's the problem here. I wish to switch to a silo by any of the above provided command. That the selected silo is picked up each time when I create notes, by any of the denote commands.

Not being interfered by any of the opened notes that belong to other silos when my current-buffer is at any of them.

Plus when the current buffer is not a note of any silo, creating a note with the denote-create-note command just creates a note that its path is the denote-directory global variable's value. Which in reverse, I want it to create a note in the selected silo.


No matter the current buffer is at a note of any silo, at any random files, or buffers, after the a "switch command" has set it to a silo, I wish all commands not being interfered by any of the factors mentioned, just create notes in the selected silo.


Which just treats silo paths like in global variable denote-directory, this is doable even by myself. Just define a list variable, say my-denote-separated-directories, containing paths of said silo A, silo B, and silo C.

And also define the switch command, say my-denote-switch-to-a-directory, to change the global variable denote-directory's value to the selected path that is one of the values of `my-denote-separated-directories'.

Also, delete the created dir-locals.el files in all those silos with this usage to cease interfering the value of denote-directory. At this rate, they're not used as silos in denote, but just like a standalone "denote directory", switching to any of them shadows any of others like non-existent to denote's commands. Invoking denote commands only works on the selected directory's notes.

Except that there's still a need that tells which directory the denote is working on as an indicator.


Maybe you should check the comments here in Github, I edited a lot of my comments, I'm not sure that email could show the latest edit.

@IceAsteroid
Copy link
Author

Also, please tell me what version of Emacs you are on.

GNU Emacs 29.4 (build 2, x86_64-unknown-linux-gnu, GTK+ Version 3.24.42, cairo version 1.18.0) of 2024-06-25

@protesilaos
Copy link
Owner

protesilaos commented Sep 20, 2024 via email

@IceAsteroid
Copy link
Author

IceAsteroid commented Sep 21, 2024

Maybe you should check the comments here in Github, I edited a lot of my comments, I'm not sure that email could show the latest edit.


I decided to use the method of "changing the denote-directory global variable for the 'directory switch'" instead of using silos. Like I said, after the switch, denote should create notes in that selected directory, regardless of any other factors. This perfectly fits my needs.

And there also needs wrapper commands for the directory switch and then doing actions just like denote-silo-extras-create-note to create notes for silos. A wrapper command's directory is selective regardless of the current directory in use.


All the described features above in this comment are simple configurations of the current functionality of denote, they also don't interfere or change the way silos behave. So they can work along with silos.

I'll try to implement the above features in my own init file.


If you are always going to select the directory you will operate on,
then the indicator will not make much of a difference.

However, I still think it's necessary, otherwise, in the workflow, when creating notes, I still need to check the value of denote-directory, in order to make sure which directory will be used to create the note in, since my memorization is not that good these days :(

[Fix typo] Directory is still sometimes getting switched when use. If the user doesn't need to switch, with the method I described above, nor using silos, he doesn't have to concern about this.

Maybe, if the path is one of the silo paths, display indicator like Silo:_lastDirName_, if it's not a silo path, display Dir:_lastDirName_.


Still this is a feature I need to consider further. I think it can be
useful, though I have to decide how best it can be done.

That's good, Protesilaos. It's on your decision. I'll then just do something convenient in my own init config first.

@protesilaos
Copy link
Owner

protesilaos commented Sep 21, 2024 via email

@IceAsteroid
Copy link
Author

I see, but then it is hard for me to keep track of everything. You will
notice that this project is very active---and I have many more!
Sometimes I am on the website (e.g. to merge a pull request), but it is
not as convenient as using email inside of Emacs to deal with
everything. If I am missing something essential, just let me know.

That's understandable.

@IceAsteroid
Copy link
Author

IceAsteroid commented Sep 24, 2024

Here's the code that I implemented for denote to work with "switching directories". Not yet to implement the indicator.

(defun my-deploy/denote-switch-directory ()
  (with-eval-after-load 'denote
    (defgroup my/denote-switch-directory nil
      "Switch between directories in the `denote-directory’ global variable.")
    
    (defcustom my/denote-switch-directory-list nil
      "List of directories to switch in `denote-directory’ global variable."
      :group 'my/denote-switch-directory
      :type '(repeat string))

    (setq my/denote-switch-directory-list '("~/OrgWiki_Test/KnowledgeBaseTech"
					    "~/OrgWiki_Test/KnowledgeBaseZH"))

    (defvar my/denote-switch-directory--history nil
      "Minibuffer history generated from invoking `my/denote-switch-directory-prompt’.")

    (defun my/denote-switch-directory-prompt ()
      "Prompt the user to switch to one of the directories in the `my/denote-switch-directory-list’ variable."
      (if-let* ((req-var 'my/denote-switch-directory-list)
		(directory (or (symbol-value req-var))))
	  (let ((default (car my/denote-switch-directory--history)))
	    (completing-read
	     (format-prompt "Select a directory" default)
	     (denote--completion-table 'file directory)
	     nil :require-match nil 'my/denote-switch-directory--history))
	(user-error "The required variable ’%s’ is undefined or nil." req-var)))

    (defun my/denote-switch-directory (dir)
      "Switch the denote directory globally to any of values of the `my/denote-switch-directory-list’ variable."
      (interactive (list (my/denote-switch-directory-prompt)))
      (setq denote-directory dir))

    ;; Set the `denote-directory’ variable to nil at the startup of Emacs
    ;;,the following code will run `my/denote-switch-directory’ when it’s nil.
    ;;,when every time Emacs restarts and reload this init config file.
    (setq denote-directory nil)

    (defcustom my/denote-commands-with-switch-directory nil
      "List of denote-related commands that need to use the switched directory by the `my/denote-switch-directory’ command, if `denote-directory’ is nil as set at the startup of Emacs, prompt the user with `my/denote-switch-directory’."
      :type '(repeat symbol)
      :group 'my/denote-switch-directory)
    
    (setq my/denote-commands-with-switch-directory
	  '(consult-notes
	    denote
	    denote-create-note
	    denote-date
	    denote-subdirectory
	    denote-template))

    ;; (defun my/set-dir-before-denote-commands-advice (&rest _)
    ;;   (unless denote-directory
    ;; 	(call-interactively #'my/denote-switch-directory)))

    (defun my/set-dir-before-denote-commands-advice (orig-fun &rest _)
      (interactive)
      (unless denote-directory
	(call-interactively 'my/denote-switch-directory))
      (call-interactively orig-fun))
    
    (when my/denote-commands-with-switch-directory
      (dolist (command my/denote-commands-with-switch-directory)
	(advice-add command :around 'my/set-dir-before-denote-commands-advice))))
  )
(my-deploy/denote-switch-directory)

But I got a problem, it must use the first part of the following code instead of the second, in order to make it prompt the minibuffer, but I don't wanna make the advicing function to be a command by being interactive.

When I use the second part, it works for all other commands. Except the "denote" command, and denote-create-note, which is an alias of it, these two don't prompt the minibuffer for the user.

I've tried lots of ways, for example, using a lambda inside the advicing function, making the lambda interactive, and such, they all don't work except making the advicing function itself to be interactive.

Is it something wrong with the denote command, or just an issue of my code?

The first part:

    (defun my/set-dir-before-denote-commands-advice (orig-fun &rest _)
      (interactive)
      (unless denote-directory
	(call-interactively 'my/denote-switch-directory))
      (call-interactively orig-fun))

The second part:

    (defun my/set-dir-before-denote-commands-advice (&rest _)
      (unless denote-directory
	(call-interactively #'my/denote-switch-directory)))

@IceAsteroid
Copy link
Author

After all my required features are implemented and sharped, I'll post the code again here or in a discussion page with a more accurate title, to just share it, it may be helpful for someone.

@IceAsteroid
Copy link
Author

Almost done here. The code isn't that sharped, and I feel the urge to post, I got another different method(probably better).


Note, when the current buffer is on a note belonging to a denote path, where it's opened via find-file or just newly created by one of the denote commands.

The denote-directory variable will locally be the directory of the current buffer's file.

However, the implemented indicator just displays it correctly in the prompt when a denote command that's used to create a note, is invoked.

When this case happens, just use the implemented my/denote-switch-directory command to switch to the desired directory. The path of denote-directory will be changed while the current buffer is still as the same.


Here's another method and it's more hassle-free to implement.

Which is to:

1.1. Prompt the user for the confirmation of the directory before denote commands that create notes actually are executed.
1.2. The prompt contains indicator of current denote directory and a list of directory candidates to select
1.3. The prompt exits if the user is in idle after a limited time. If anything's typed, the prompt lasts and waits a candidate to be selected.

2.1. After the prompt, execute the actual desired command.

The reason is because it needs not only to redefine the denote--with-conditional-completion macro, but also the denote-template command, since it doesn't use the macro, there may be more exceptions.

And it's also less-consistent, for example, the command denote-signature will display the indicator both in the prompts of asking signature and title. But another command denote-create-note will show indicator only in the title prompt, but in the keyword prompt.

The denote-subdirectory will not show indicator for the first prompt asking the directory, till the title prompt, etc.

It's also gonna alter the code of consult-notes of which that I use with denote, I got intimidated at this point.


This is the old code that consists two parts:

The first part's for switching denote directories

(defun my-deploy/denote-switch-directory ()
  (with-eval-after-load 'denote
    (defgroup my/denote-switch-directory nil
      "Switch between directories in the `denote-directory’ global variable.")
    
    (defcustom my/denote-switch-directory-list nil
      "List of directories to switch in `denote-directory’ global variable."
      :group 'my/denote-switch-directory
      :type '(repeat string))

    (setq my/denote-switch-directory-list '("~/OrgWiki_Test/KnowledgeBaseTech"
					    "~/OrgWiki_Test/KnowledgeBaseZH"))

    (defvar my/denote-switch-directory--history nil
      "Minibuffer history generated from invoking `my/denote-switch-directory-prompt’.")

    (defun my/denote-switch-directory-prompt ()
      "Prompt the user to switch to one of the directories in the `my/denote-switch-directory-list’ variable."
      (if-let* ((req-var 'my/denote-switch-directory-list)
		(directory (or (symbol-value req-var))))
	  (let ((default (car my/denote-switch-directory--history)))
	    (completing-read
	     (format-prompt "Set denote directory" default)
	     (denote--completion-table 'file directory)
	     nil :require-match nil 'my/denote-switch-directory--history))
	(user-error "The required variable ’%s’ is undefined or nil." req-var)))

    (defun my/denote-switch-directory (dir)
      "Switch the denote directory globally to any of values of the `my/denote-switch-directory-list’ variable."
      (interactive (list (my/denote-switch-directory-prompt)))
      (setq denote-directory dir))

    ;; Set the `denote-directory’ variable to nil at the startup of Emacs
    ;;,the following code will run `my/denote-switch-directory’ when it’s nil.
    ;;,when every time Emacs restarts and reload this init config file.
    (setq denote-directory nil)

    (defcustom my/denote-commands-with-switch-directory nil
      "List of denote-related commands that need to use the switched directory by the `my/denote-switch-directory’ command, if `denote-directory’ is nil as set at the startup of Emacs, prompt the user with `my/denote-switch-directory’."
      :type '(repeat symbol)
      :group 'my/denote-switch-directory)
    
    (setq my/denote-commands-with-switch-directory
	  '(consult-notes
	    denote
	    denote-create-note
	    denote-date
	    denote-subdirectory
	    denote-template
	    denote-silo-extras-create-note
	    denote-silo-extras-open-or-create
	    denote-silo-extras-select-silo-then-command))

    ;; (defun my/set-dir-before-denote-commands-advice (&rest _)
    ;;   (unless denote-directory
    ;; 	(call-interactively #'my/denote-switch-directory)))

    (defun my/set-dir-before-denote-commands-advice (orig-fun &rest _)
      (interactive)
      (unless denote-directory
	(call-interactively 'my/denote-switch-directory))
      (call-interactively orig-fun))
    
    (when my/denote-commands-with-switch-directory
      (dolist (command my/denote-commands-with-switch-directory)
	(advice-add command :around 'my/set-dir-before-denote-commands-advice))))
  (require 'denote)
  (require 'denote-silo-extras)
  )
(my-deploy/denote-switch-directory)

The second part's for the current denote directory indicator in prompt

(defun my-deploy/denote-cur-dir-indicator-in-prompt ()
  (with-eval-after-load 'denote
    (with-eval-after-load 'denote-silo-extras
      (defun my/dir-path-last-name (path &optional separators)
	"Return the give path’s last name.

SEPARATORS is a regular expression to match separators to divide path into substrings. It’s default to \"/\".

See the used `string-split’ function."
	;; directory-file-name removes possbile trailing slashes.
	(let ((path (directory-file-name path))
	      (separators (or separators "/")))
	  (car (last (string-split path separators)))))
      
      (defun my-deploy/denote-prompt-indicator ()
	"Return the last name of the path in the `denote-directory’ variable.
If the path is a silo path, return ’Silo:_LastName_’, else return ’Dir:_LastName_’."
	(if-let ((req-var 'denote-directory)
		 (cur-dir (or (symbol-value req-var))))
	    (let* ((silo-dirs (or denote-silo-extras-directories))
		   (last-name (my/dir-path-last-name cur-dir))
		   (indicator (if (member cur-dir silo-dirs)
				  (concat "Silo: " last-name)
				(concat "Dir: " last-name))))
	      indicator
	      )
	  (user-error "The required variable ’%s’ is non-existent or nil." req-var))
	)
      ))
  (require 'denote)
  (require 'denote-silo-extras)
  )
(my-deploy/denote-cur-dir-indicator-in-prompt)

Except the code implemented in the init file, the denote--with-conditional-completion macro is also redefined in the denote.el file.

;; Modify the macro to display directory indicator in denote command prompts
;; It must be defined here instead of the init file, since it’s expanded at
;;,compile time, which doens’t call the macro anymore, so redefining it in
;;,the init file doens’t work.
(defmacro denote--with-conditional-completion (fn prompt history &optional initial-value default-value)
  "Produce body of FN that may perform completion.
Use PROMPT, HISTORY, INITIAL-VALUE, and DEFAULT-VALUE as arguments for
the given minibuffer prompt."
  `(if (denote--prompt-with-completion-p ,fn)
       (let* ((separator " | ")
              (prompt (string-join
                       (list (my-deploy/denote-prompt-indicator)
                             ,prompt)
                       separator)))
	 ;; NOTE 2023-10-27: By default SPC performs completion in the
	 ;; minibuffer.  We do not want that, as the user should be able to
	 ;; input an arbitrary string, while still performing completion
	 ;; against their input history.
	 (minibuffer-with-setup-hook
	     (lambda ()
	       (use-local-map
		(let ((map (make-composed-keymap nil (current-local-map))))
		  (define-key map (kbd "SPC") nil)
		  map)))
	   (completing-read prompt ,history nil nil ,initial-value ',history ,default-value)))
     (read-string ,prompt ,initial-value ',history ,default-value)))

This is the demo code & video of the new idea that prompts a minibuffer with a give limited of time before the actual denote command runs. Somehow a pain at least for me to figure out how to run another expression while an interative expression is still in the middle of the run. But I achieved it, it may look ugly and be unefficient, if you know better, please tell me.

(setq completing-read-idle-timeout 2)
(defmacro completing-read-with-timer (comp timer &optional break break-cancel)
  `(when-let ((timeout completing-read-idle-timeout))
    (unless (equal timeout 0)
      (progn
	 ,timer
	 (if (and ,break ,break-cancel)
	     (progn
	       ;; (message "in the break %s" ,break)
	       (run-at-time completing-read-idle-timeout
			    nil
			    (lambda () ,break-cancel))
	       ,break
	       ,comp
	       ;; ,break-cancel
	       ))
	   (progn
	     ;; (message "Not in the break")
	     ,comp))
	 )))
;; completing-read-with-timer-p
(completing-read-with-timer
 (progn
   (completing-read "ask: " '("xxxx" "ffff")))
 
 (setq completing-read-timer
       (run-with-idle-timer
	completing-read-idle-timeout
	nil
	(lambda () (abort-recursive-edit))))

 (progn
   (defun cancel-timer-to-post-hook ()
     (cancel-timer completing-read-timer))
   (defun func-to-minibuffer-hook ()
     (run-at-time
      0.1 nil
      (lambda ()
	(add-hook 'post-command-hook 'cancel-timer-to-post-hook))))
   (add-hook 'minibuffer-setup-hook 'func-to-minibuffer-hook))
 
 (progn
   (remove-hook 'minibuffer-setup-hook 'func-to-minibuffer-hook)
   (remove-hook 'post-command-hook 'cancel-timer-to-post-hook))
 )

denote-indicator-new-method.mp4

@IceAsteroid
Copy link
Author

I'm sorry for the non-sense. The code in my last previous comment that was to implement the prompt the directory switch within a given limited time when idle, lasting if anything is typed before denote commands, has been done in my test, but it's uglier than necessary. And I found out it's harder than the old code.

Since the implemented my/denote-switch-directory function launches a prompt with history, it's easy and convenient to commit the current denote directory by just hitting the return key once.

It's saner just to use the old one, with some minor changes.

Here's the code, not that good, but enough for personal use.

It has two parts, the "switch directory one" and the "indicator" one.

(defun my-deploy/denote-switch-directory ()
  (with-eval-after-load 'denote
    (defgroup my/denote-switch-directory nil
      "Switch between directories in the `denote-directory’ global variable.")
    
    (defcustom my/denote-switch-directory-list nil
      "List of directories to switch in `denote-directory’ global variable."
      :group 'my/denote-switch-directory
      :type '(repeat string))

    (setq my/denote-switch-directory-list '("~/OrgWiki_Test/Tech"
					    "~/OrgWiki_Test/Art"))

    (defvar my/denote-switch-directory--history nil
      "Minibuffer history generated from invoking `my/denote-switch-directory-prompt’.")

    (defun my/denote-switch-directory-prompt (prompt-string)
      "Prompt the user to switch to one of the directories in the `my/denote-switch-directory-list’ variable."
      (if-let* ((req-var 'my/denote-switch-directory-list)
		(directory (or (symbol-value req-var))))
	  (let ((default (car my/denote-switch-directory--history)))
	    (completing-read
	     (format-prompt prompt-string default)
	     (denote--completion-table 'file directory)
	     nil :require-match nil 'my/denote-switch-directory--history))
	(user-error "The required variable ’%s’ is undefined or nil." req-var)))

    (defun my/denote-switch-directory (dir)
      "Switch the denote directory globally to any of values of the `my/denote-switch-directory-list’ variable."
      (interactive (list (my/denote-switch-directory-prompt "Set denote directory")))
      (setq denote-directory dir))

    (defcustom my/denote-commands-with-switch-directory nil
      "List of denote-related commands that need to use the switched directory by the `my/denote-switch-directory’ command, if `denote-directory’ is nil as set at the startup of Emacs, prompt the user with `my/denote-switch-directory’."
      :type '(repeat symbol)
      :group 'my/denote-switch-directory)
    
    (setq my/denote-commands-with-switch-directory
	  '(consult-notes
	    denote
	    ;;denote-create-note
	    denote-date
	    denote-subdirectory
	    denote-template))
    
    (defun my/set-dir-before-denote-commands-advice (orig-fun &rest _)
      (interactive)
      (call-interactively 'my/denote-switch-directory)
      (call-interactively orig-fun))
    
    (when my/denote-commands-with-switch-directory
      (dolist (command my/denote-commands-with-switch-directory)
	(advice-add command :around 'my/set-dir-before-denote-commands-advice))))
  (require 'denote)
  (require 'denote-silo-extras)
  )
(my-deploy/denote-switch-directory)

(defun my-deploy/denote-cur-dir-indicator-in-prompt ()
  (with-eval-after-load 'denote
    (with-eval-after-load 'denote-silo-extras
      (defun my/dir-path-last-name (path &optional separators)
	"Return the give path’s last name.

SEPARATORS is a regular expression to match separators to divide path into substrings. It’s default to \"/\".

See the used `string-split’ function."
	;; directory-file-name removes possbile trailing slashes.
	(let ((path (directory-file-name path))
	      (separators (or separators "/")))
	  (car (last (string-split path separators)))))
      
      (defun my/denote-prompt-indicator ()
	"Return the last name of the path in the `denote-directory’ variable.
If the path is a silo path, return ’Silo:_LastName_’, else return ’Dir:_LastName_’."
	(if-let ((req-var 'denote-directory)
		 (cur-dir (or (symbol-value req-var))))
	    (let* ((silo-dirs (or denote-silo-extras-directories))
		   (last-name (my/dir-path-last-name cur-dir))
		   (indicator (if (member cur-dir silo-dirs)
				  (concat "Silo: " last-name)
				(concat "Dir: " last-name))))
	      ;; Return the value of `indicator’.
	      indicator
	      )
	  (user-error "The required variable ’%s’ is non-existent or nil." req-var))
	)

    (with-eval-after-load 'consult-notes
      ;; Redefine `consult-notes’ to display the indicator by the `my/denote-prompt-indicator’ function.
      (defun consult-notes (&optional sources)
	"Find a file in a notes directory with consult-multi, or from SOURCES."
	(interactive)
	(consult-notes--make-file-dir-sources)
	(let* ((separator " | ")
	       (prompt-text (string-join
			     (list
			      (my/denote-prompt-indicator)
			      "Notes: ")
			     separator)))
	  (consult--multi (or sources consult-notes-all-sources)
			  :require-match
			  (confirm-nonexistent-file-or-buffer)
			  ;; :prompt "Notes: "
			  :prompt prompt-text
			  :history 'consult-notes-history
			  :add-history (seq-some #'thing-at-point '(region symbol))))))
    ))
  (require 'denote)
  (require 'denote-silo-extras)
  (require 'consult-notes)
  )
(my-deploy/denote-cur-dir-indicator-in-prompt)

And the re-definition of the denote--with-conditional-completion macro in denote.el, I should've changed only the denote-title-prompt function, but changing the macro takes effect for more prompts.

;; Modify the macro to display directory indicator in denote command prompts
;; It must be defined here instead of the init file, since it’s expanded at
;;,compile time, which doens’t call the macro anymore, so redefining it in
;;,the init file doens’t work.
(defmacro denote--with-conditional-completion (fn prompt history &optional initial-value default-value)
  "Produce body of FN that may perform completion.
Use PROMPT, HISTORY, INITIAL-VALUE, and DEFAULT-VALUE as arguments for
the given minibuffer prompt."
  `(if (denote--prompt-with-completion-p ,fn)
       (let* ((separator " | ")
              (prompt (string-join
                       (list (my/denote-prompt-indicator)
                             ,prompt)
                       separator)))
	 ;; NOTE 2023-10-27: By default SPC performs completion in the
	 ;; minibuffer.  We do not want that, as the user should be able to
	 ;; input an arbitrary string, while still performing completion
	 ;; against their input history.
	 (minibuffer-with-setup-hook
	     (lambda ()
	       (use-local-map
		(let ((map (make-composed-keymap nil (current-local-map))))
		  (define-key map (kbd "SPC") nil)
		  map)))
	   (completing-read prompt ,history nil nil ,initial-value ',history ,default-value)))
     (read-string ,prompt ,initial-value ',history ,default-value)))

I'm already very happy with what I've done for my needs.

And I'm sorry, Protesilaos. For the confusion I made so far. I hope it didn't bother you much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants