emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
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


  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).