From: Arthur Miller <arthur.miller@live.com>
To: Bruno Barbier <brubar.cs@gmail.com>
Cc: emacs-orgmode@gnu.org
Subject: Re: Problem with let/cl-letf binding stuff with org-capture
Date: Sat, 11 Feb 2023 17:14:32 +0100 [thread overview]
Message-ID: <AM9PR09MB4977306B77F9A5338764324596DF9@AM9PR09MB4977.eurprd09.prod.outlook.com> (raw)
In-Reply-To: <63e74a8b.df0a0220.7f45d.5b05@mx.google.com> (Bruno Barbier's message of "Sat, 11 Feb 2023 08:58:01 +0100")
Bruno Barbier <brubar.cs@gmail.com> writes:
> Arthur Miller <arthur.miller@live.com> writes:
>
>>> Bruno Barbier <brubar.cs@gmail.com> writes:
>>> If you really want to just get the piece of text, you might be able to
>>> use the hook `org-capture-mode-hook' to replace the key binding to
>>> 'C-c C-c' in the capture buffer, so that it calls your own function that
>>> will take the string and call `org-capture-kill'.
>>
>> In this case you wouldn't like to replace the key binding, it would affect all
>> org-capture buffers; the point is just to replace it when called in certain
>> context (my-read-line). Let-binding the function in this context achieves
>> exactly the same effect of C-c C-c beng bound to my function but without
>> affecting all org-capture-buffers.
>
> The hook `org-capture-mode-hook' will be run in your special
> capture buffer. You can override the "C-c C-c" binding only there.
Yes and in every other capture buffer, so I would either have to test on some
variable, make another mode, or mode-map or something similar. Just to customize
on the key, instead of the value bound to that key.
>>
>> Yes, I am aware of both hooks and advising; but again, with those I would affect
>> all uses of the function, and that would lead to checking some global state or
>> variable to switch on, which is not optimal either. With let-binding we can have
>> different behaviour only in a certain context.
>
> Even if I could let bind the function at the right time, I would avoid
> that solution, as I can't garantuee that this global hack will not break
> other parts of Emacs (other captures, output filters, threads, timers,
> etc.).
Why do you think it will break other parts? This is not a global hack, on contrary it
exactly tries to prevent to be "global" in entire Emacs, by let-binding a name
to a local lambda, which becomes "global" only in that buffer. If that
explains.
Here is another version on the same theme, where I don't think you could modify the local
environment without let-binding at all:
#+begin_src emacs-lisp
(defun my-read-string (prompt)
(let ((delta 20 )
(minibuffer-mode-map org-mode-map))
(window-resize (minibuffer-window) delta)
(cl-letf (((symbol-function 'org-ctrl-c-ctrl-c)
(lambda ()
(interactive)
(let ((s (buffer-string)))
(exit-minibuffer) s)))
((symbol-function 'minibuffer-mode) #'org-mode)
((symbol-function 'minibuffer-complete-and-exit) #'org-return)
((symbol-function 'org-kill-note-or-show-branches) #'keyboard-escape-quit))
(read-string (concat "# Press C-c C-c to continue, C-c C-k to cancel\n# " prompt "\n\n")))))
#+end_src
read-string is written in C and creates its own minibuffer, which is deleted by
the time read-string exits. I don't know of any other way to cutomize exactly
*that* minibuffer, without installing a hook or advising some functions, which I
think is way less clean and much more "global" than just running the function in
a local environment. As I understand, let binding for this purpose is a normal
technique in lisps, but I am not an expert as said; I am actually experimenting
with this for the purpose of learning and seeing what is possible.
Of course, we can always write our own read-string function and re-implement the
function from scratch, which author of that blog actually did. The experiment
was to test if I can modify the existing functionality to re-use, rather than to
re-invent something. Since read-string functions does not let us specify buffer,
mode, etc, let binding is one way of doing it locally.
Considering how org-capture works, the same technique of just modifying the
local environment is not really applicable; now when you reminded me that
capture buffer lives longer then let-binding, I understand what happens. I can
access the buffer after org-capture exits, in my-read-string:
(with-current-buffer
(try-completion "CAPTURE" (mapcar #'buffer-name (buffer-list)))
( .... do the thng .... )
but I am not sure if I can do anything here without introducing at-least an
extra keymap, to not install into the org-capture-mode-map, so I can as well
create a minor mode, but at this point it is not much different than
re-invinting the read-string, so I'll terminate my experiment here :).
But I wouldn't speak in some generic terms like "use hooks" or "advise" instead
of let-binding. Let binding is a powerful and legitimate technique to modify
local environment of functions. I am not really sure where it stands in
abstraction, if it is sort-of template, or interface programming, since I am not
that familiar with lisp (yet), but I do understand it has very good and powerful
uses. Consider this (you can actually eval an run from gnus, or in scartch):
#+begin_src emacs-lisp
;;;; Project folder - org-capture related code
(defvar org-project-root-dir nil)
(defvar org-project-templates nil)
(defvar org-project-finalize-hooks nil)
(setq org-project-templates
`(("W" "Web Projects")
("Wb" "Bootstrap Project" plain
(function org-project-new)
"A Bootstrap Project")
("Wh" "Web project" plain
(function org-project-new)
"Simple HTML Project")
("C" "C/C++ Projects")
("Cc" "C Console Project" plain
(function org-project-new)
"A Command Line Project with C")
("CC" "C++ Console Project" plain
(function org-project-new)
"A Command Line Project with C++")
("CG" "C++ GLUT Project" plain
(function org-project-new)
"Simple GLUT Project")
("CQ" "C++ Qt Project" plain
(function org-project-new)
"Qt Project")
("CK" "C++ Qt Quick Project" plain
(function org-project-new)
"Qt Quick Project")))
(defun org-project-new-project ()
(interactive)
(let ((org-capture-templates org-project-templates))
(org-capture)))
(define-key global-map (kbd "C-S-n") #'org-project-new-project)
(defun org-project-new ()
"Interactively create new directory for a project.
Directory should not exist prior to call to this function."
(let ((project-path (read-file-name "Project name: "
org-project-root-dir
nil nil nil)))
(cond ((not (file-directory-p project-path))
(make-directory project-path)
(let ((file-name (concat (file-name-nondirectory project-path) ".org")))
(find-file (expand-file-name file-name project-path))
(goto-char (point-min))))
(t (message "Directory %s already exists." project-path))))
(if org-project-finalize-hooks (run-hooks org-project-finalize-hooks)))
#+end_src
The only extra work I did, was to actually create an interactive function to
specify a path, and I can re-use both org-capture template language and
interactive functionality already built into the org-capture. I get an entire
framework for free :). The real action is happening in hooks, where I init git,
copy some templates, license and some other boiler-plate stuff.
I don't know what would be the alternative, but let-binding on
org-capture-templates, let me clearly re-use the functionality without polluting
the global org-capture-templates variable and without re-implementing pretty
much anything.
As I understand, let binding is useful in lisp, and as legitimate technique as
hooks, or advices. As said, I am not an expert, but I think, it is probably
preferrable to advising, where possible, since advising is global to all
function instances, while let-binding affects only local function instances, if
I can borrow some of OOP terms here.
I am very interested to hear more on the topic, since I would definitely like to
learn more about different techniques.
Hope that explains a bit more on the background of the experiment :).
best regards
/a
next prev parent reply other threads:[~2023-02-11 16:20 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-02-10 15:05 Problem with let/cl-letf binding stuff with org-capture Arthur Miller
2023-02-10 15:38 ` Ruijie Yu via General discussions about Org-mode.
2023-02-10 16:29 ` Arthur Miller
2023-02-10 19:00 ` Bruno Barbier
2023-02-11 6:33 ` Arthur Miller
2023-02-11 7:58 ` Bruno Barbier
2023-02-11 16:14 ` Arthur Miller [this message]
2023-02-11 19:23 ` Bruno Barbier
2023-02-12 7:21 ` Arthur Miller
2023-02-12 9:22 ` Bruno Barbier
2023-02-12 16:12 ` Arthur Miller
2023-02-12 16:22 ` Ihor Radchenko
2023-02-13 18:40 ` Bruno Barbier
2023-02-15 11:45 ` Arthur Miller
2023-02-15 13:18 ` Bruno Barbier
2023-02-15 17:36 ` arthur miller
2023-02-13 18:37 ` Bruno Barbier
2023-02-11 16:49 ` Ihor Radchenko
2023-02-15 13:06 ` Arthur Miller
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.orgmode.org/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=AM9PR09MB4977306B77F9A5338764324596DF9@AM9PR09MB4977.eurprd09.prod.outlook.com \
--to=arthur.miller@live.com \
--cc=brubar.cs@gmail.com \
--cc=emacs-orgmode@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).