Use customization buffer to supply arguments to a function - emacs

I need to call a function interactively with a lot of arguments (it is 7 at the moment, but it will grow). Reading all the arguments in succession creates bad experience. For example, I am asking a user to input a class name. The class name can be fully qualified by the package it is in or not. So if a user thinks it must be fully qualified, and then I later ask them to provide the package name, there's no way for the user to go back and fix the error.
There are also many aspects of the input that are difficult to take care of at once. For instance, I need to make sure that certain characters don't appear in a certain pattern in the string being read etc. If the input fails validation on the last item of the input, it will feel frustrating for users to restart the whole procedure, while if I had a customization buffer at my disposal I could simply prevent them from committing the change if it doesn't validate and keep the already submitted good values.
tl;dr
I'm looking for a way to open customization buffer and read the user input into a function that is called interactively. Is there a way to do it?

I would recommend using the Emacs Widget Library instead of the customization buffer itself. Emacs info has an excellent section on the Widget Library. You can access it from emacs using C-h i m Widget RET. Or you can access the HTML version here. Here is the snippet of the widget example from the manual.
(require 'widget)
(eval-when-compile
(require 'wid-edit))
(defvar widget-example-repeat)
(defun widget-example ()
"Create the widgets from the Widget manual."
(interactive)
(switch-to-buffer "*Widget Example*")
(kill-all-local-variables)
(make-local-variable 'widget-example-repeat)
(let ((inhibit-read-only t))
(erase-buffer))
(remove-overlays)
(widget-insert "Here is some documentation.\n\n")
(widget-create 'editable-field
:size 13
:format "Name: %v " ; Text after the field!
"My Name")
(widget-create 'menu-choice
:tag "Choose"
:value "This"
:help-echo "Choose me, please!"
:notify (lambda (widget &rest ignore)
(message "%s is a good choice!"
(widget-value widget)))
'(item :tag "This option" :value "This")
'(choice-item "That option")
'(editable-field :menu-tag "No option" "Thus option"))
(widget-create 'editable-field
:format "Address: %v"
"Some Place\nIn some City\nSome country.")
(widget-insert "\nSee also ")
(widget-create 'link
:notify (lambda (&rest ignore)
(widget-value-set widget-example-repeat
'("En" "To" "Tre"))
(widget-setup))
"other work")
(widget-insert
" for more information.\n\nNumbers: count to three below\n")
(setq widget-example-repeat
(widget-create 'editable-list
:entry-format "%i %d %v"
:notify (lambda (widget &rest ignore)
(let ((old (widget-get widget
':example-length))
(new (length (widget-value widget))))
(unless (eq old new)
(widget-put widget ':example-length new)
(message "You can count to %d." new))))
:value '("One" "Eh, two?" "Five!")
'(editable-field :value "three")))
(widget-insert "\n\nSelect multiple:\n\n")
(widget-create 'checkbox t)
(widget-insert " This\n")
(widget-create 'checkbox nil)
(widget-insert " That\n")
(widget-create 'checkbox
:notify (lambda (&rest ignore) (message "Tickle"))
t)
(widget-insert " Thus\n\nSelect one:\n\n")
(widget-create 'radio-button-choice
:value "One"
:notify (lambda (widget &rest ignore)
(message "You selected %s"
(widget-value widget)))
'(item "One") '(item "Another One.") '(item "A Final One."))
(widget-insert "\n")
(widget-create 'push-button
:notify (lambda (&rest ignore)
(if (= (length (widget-value widget-example-repeat))
3)
(message "Congratulation!")
(error "Three was the count!")))
"Apply Form")
(widget-insert " ")
(widget-create 'push-button
:notify (lambda (&rest ignore)
(widget-example))
"Reset Form")
(widget-insert "\n")
(use-local-map widget-keymap)
(widget-setup))

Related

How to provide completion of interactive function from a request?

I would like to provide completion for an emacs interactive function whose content would be based on a HTTP request's content.
Here, the TODO explains it all.
(defun sensei-record-flow (flow-type)
"Interactive function to record change in flow."
(interactive (list (completing-read
"Flow: "
;; TODO need to get the completion from a variable filled with user's
;; flow types
'(("Foo" Foo) ("Bar" Bar) ("Baz" Baz))
nil t)))
(let ((directory (projectile-project-root)))
(setq sensei-cur-directory directory)
(sensei-send-event-flow directory flow-type))
)
How can I do that? request.el provides a :completion key to block until completion of a request, but it's not clear to me how to do that? I think what I need is to make the interactive function a continuation of the call to senseiilist-flows but I don't know how to do that in elisp.
EDIT: Here is the code of sensei-list-flows based on request.el
(defun sensei-list-flows (on-success)
"List available flow types for the current user.
ON-SUCCESS is a function that's called upon successful completion of the call
and is passed a list of symbols listing user-defined flow names."
(let* ((config (sensei-read-config))
(auth-token (cdr (assoc 'authToken config)))
(username (cdr (assoc 'configUser config)))
(server-uri (cdr (assoc 'serverUri config))))
(request (concat server-uri "api/users/" username)
:headers `(("Content-Type" . "application/json")
("X-API-Version" . "0.38.0")
("Authorization" . ,(concat "Bearer " auth-token)))
:parser 'json-read
:error (cl-function (lambda (&rest args &key error-thrown &allow-other-keys)
(message "Got error: %S" error-thrown)))
:success (cl-function (lambda (&key data &allow-other-keys)
(funcall on-success
(map 'list 'car (cdr (assoc 'userFlowTypes data)))))))))
I don't know anything about request.el and you haven't included any call to a list-flows in your quoted code and so that's impossible to comment on; but OOTB you would use C-hf url-retrieve-synchronously to fetch something via http like that.
In the resulting buffer, the url-http-end-of-headers variable is of particular note (+1 to get to the start of the content); and you may wish to check the url-http-* vars in that buffer in general.
I was able to achieve what I want using url-insert-file-contents which is similar to url-retrieve-synchronously:
(defun sensei-list-flows ()
"List available flow types for the current user."
(let* ((config (sensei-read-config))
(auth-token (cdr (assoc 'authToken config)))
(username (cdr (assoc 'configUser config)))
(server-uri (cdr (assoc 'serverUri config)))
(url-request-extra-headers `(("Content-Type" . "application/json")
("X-API-Version" . "0.38.0")
("Authorization" . ,(concat "Bearer " auth-token)))))
(with-temp-buffer
(url-insert-file-contents (concat server-uri "api/users/" username))
(let ((flows (cdr (assoc 'userFlowTypes (json-parse-buffer :object-type 'alist)))))
(map 'list 'car flows)))))
Then interactive completion is trivial:
(defun sensei-record-flow (flow-type)
"Interactive function to record change in flow."
(interactive (list (completing-read
"Flow: "
(sensei-list-flows)
nil t)))
(let ((directory (projectile-project-root)))
(setq sensei-cur-directory directory)
(sensei-send-event-flow directory flow-type))
)

Org-mode archive entire file if all TODOs DONE

While I've seen a lot of SO questions regarding archiving sub-trees, I use org-journal to create a daily file each day with a template (eg. 2018-09-14.org) which then I then record todos in a pre-templated structure for personal, work or what have you which go through various states till they are either finished DONE or cancelled KILL (I find this approach works for me since it also allows me visually to see in the agenda view how long a task has been hanging around since started).
I am trying to write an interactive function which:
processes a list of all my .org agenda files, and
if it detects all TODOs and DONE or KILL in the file (or there are none present),
prompts me y, n, skip to move the entire file to its whatever.org_archive
(starting to see slowdowns with agenda builds 5 months into using org-mode).
I'm assuming someone else already uses a similar approach ('cause emacs) but was wondering if anyone could point me at a similar function or approach that would be helpful for sussing this out. Googling and thrashing on the elisp has been unproductive so far.
=== One month later ===
Well, teaching myself some lisp has helped but am now at the point where I have the 3 independent functions working, but for some reason am getting an error on calling the final function.
However, I'm getting an error on line 28 with invalid function: on the call to rename-file-buffer-to-org-archive. If someone can see what the problem is, this solves my use case (and probably someone else's which is why I pasted it back here.).
(defun archive-done-org-journal-files ()
"Cycles all org files through checking function."
(interactive)
(save-excursion
(mapc 'check-org-file-finito (directory-files "~/Desktop/test_archives/" t ".org$"))
))
(defun check-org-file-finito (f)
"Checks TODO keyword items are DONE then archives."
(interactive)
(find-file f)
;; Shows open Todo items whether agenda or todo
(let (
(kwd-re
(cond (org-not-done-regexp)
(
(let ((kwd
(completing-read "Keyword (or KWD1|KWD2|...): "
(mapcar #'list org-todo-keywords-1))))
(concat "\\("
(mapconcat 'identity (org-split-string kwd "|") "\\|")
"\\)\\>")))
((<= (prefix-numeric-value) (length org-todo-keywords-1))
(regexp-quote (nth (1- (prefix-numeric-value))
org-todo-keywords-1)))
(t (user-error "Invalid prefix argument: %s")))))
(if (= (org-occur (concat "^" org-outline-regexp " *" kwd-re )) 0)
((rename-file-buffer-to-org-archive)
(kill-buffer (current-buffer)))
(kill-buffer (current-buffer))
)))
(defun rename-file-buffer-to-org-archive ()
"Renames current buffer and file it's visiting."
(interactive)
(let ((name (buffer-name))
(filename (buffer-file-name))
)
(if (not (and filename (file-exists-p filename)))
(error "Buffer '%s' is not visiting a file!" name)
(let ((new-name (concat (file-name-sans-extension filename) ".org_archive")))
(if (get-buffer new-name)
(error "A buffer named '%s' already exists!" new-name)
(rename-file filename new-name 1)
(rename-buffer new-name)
(set-visited-file-name new-name)
(set-buffer-modified-p nil)
(message "File '%s' successfully archived as '%s'."
name (file-name-nondirectory new-name)))))))
So, in the end, this is how I solved it. I'm sure there are optimizations and refactoring to be done here, but this definitely works and is reasonably modular if you need to figure it out. Just change the directory you use (mine is in Dropbox) for your org-files in the archive-done-org-journal-files and this should work for you. I highly recommend testing this on a test archive as per the ~/Desktop/test_archives/ directory as per the actual function just so you can make sure it works as advertised. YMMV. Hope it helps someone!
(defun archive-done-org-journal-files ()
"Cycles all org files through checking function."
(interactive)
(save-excursion
(mapc 'check-org-file-finito (directory-files "~/Desktop/test_archives/" t ".org$"))
))
(defun check-org-file-finito (f)
"Checks TODO keyword items are DONE then archives."
(interactive)
(find-file f)
;; Shows open Todo items whether agenda or todo
(let (
(kwd-re
(cond (org-not-done-regexp)
(
(let ((kwd
(completing-read "Keyword (or KWD1|KWD2|...): "
(mapcar #'list org-todo-keywords-1))))
(concat "\\("
(mapconcat 'identity (org-split-string kwd "|") "\\|")
"\\)\\>")))
((<= (prefix-numeric-value) (length org-todo-keywords-1))
(regexp-quote (nth (1- (prefix-numeric-value))
org-todo-keywords-1)))
(t (user-error "Invalid prefix argument: %s")))))
(if (= (org-occur (concat "^" org-outline-regexp " *" kwd-re )) 0)
(rename-file-buffer-to-org-archive)
(kill-buffer (current-buffer))
)))
(defun rename-file-buffer-to-org-archive ()
"Renames current buffer and file it's visiting."
(interactive)
(let ((name (buffer-name))
(filename (buffer-file-name))
)
(if (not (and filename (file-exists-p filename)))
(error "Buffer '%s' is not visiting a file!" name)
(let ((new-name (concat (file-name-sans-extension filename) ".org_archive")))
(if (get-buffer new-name)
(error "A buffer named '%s' already exists!" new-name)
(rename-file filename new-name 1)
(rename-buffer new-name)
(set-visited-file-name new-name)
(set-buffer-modified-p nil)
(kill-buffer (current-buffer))
(message "File '%s' successfully archived as '%s'."
name (file-name-nondirectory new-name)))))))

Simple vs complex user entry functions in Lisp

At this site: http://www.gigamonkeys.com/book/practical-a-simple-database.html there is user entry function listed as follows:
(defun prompt-read (prompt)
(format *query-io* "~%~a: " prompt)
(force-output *query-io*)
(read-line *query-io*))
Are there any major advantages of above function as compared to following simpler form:
(defun prompt-read2 (prompt)
(format t "~%~a: " prompt)
(setf answer (read-line)))
Is it recommended to always use force-output and *query-io* all the time?
Setting the answer to a global variable like that is bad. You should just return the answer and let the caller do what it wants with it. If you do use special (~global) variables, you should put asterisks around the name (*ANSWER* instead of ANSWER).
FORCE-OUTPUT is needed to ensure that the user actually sees the prompt before having to answer. If I run the second version using SBCL in a terminal, the program just freezes to wait for input without saying anything.
*QUERY-IO* should be used for querying things from the user, because some environment might want to handle that differently from other output. For example, someone might write a GUI wrapper for your program that turns the queries into graphical dialogs. Or maybe they want to run it as a part of a script, providing the input from a string.
(defun prompt-read (prompt)
(format *query-io* "~%~a: " prompt)
(force-output *query-io*)
(read-line *query-io*))
(defun hello ()
(format t "~&Hello ~a!~%" (prompt-read "What's your name")))
(defmacro with-input ((input) &body body)
`(let ((*query-io* (make-two-way-stream (make-string-input-stream ,input)
(make-string-output-stream))))
,#body))
(defun test ()
(with-input ("jkiiski")
(hello))
(with-input ("rnso")
(hello)))
(test)
; Hello jkiiski!
; Hello rnso!
Edit
A more complex example using SBCLs gray streams.
(defclass foo-stream (sb-gray:fundamental-character-input-stream)
((output-input-script :initarg :script :accessor foo-stream-script)
(output-stream :initarg :out :accessor foo-stream-out)
(current-input :initform nil :accessor foo-stream-current-input)))
(defmethod sb-gray:stream-read-char ((stream foo-stream))
(with-accessors ((input foo-stream-current-input)
(out foo-stream-out)
(script foo-stream-script)) stream
(when (or (null input)
(not (listen input)))
(let ((output (string-trim '(#\space #\newline)
(get-output-stream-string out))))
(setf input (make-string-input-stream
(format nil "~a~%"
(cdr (assoc output script :test #'string=)))))))
(read-char input)))
(defun prompt-read (prompt)
(format *query-io* "~%~a: " prompt)
(force-output *query-io*)
(read-line *query-io*))
(defun hello ()
(format t "~&Hello ~a!~%" (prompt-read "What's your name"))
(format t "~&I'm ~a too!" (prompt-read "How are you"))
(format t "~&~a~%" (if (string-equal (prompt-read
"Do you want to delete all your files")
"yes")
"Deleting all files... (not really)"
"Not deleting anything.")))
(defmacro with-input-script ((script) &body body)
(let ((out-sym (gensym "out")))
`(let* ((,out-sym (make-string-output-stream))
(*query-io* (make-two-way-stream
(make-instance 'foo-stream
:out ,out-sym
:script ,script)
,out-sym)))
,#body)))
(defun test ()
(with-input-script ('(("What's your name:" . "jkiiski")
("How are you:" . "great")
("Do you want to delete all your files:" . "No")))
(hello))
(with-input-script ('(("What's your name:" . "Foo Bar")
("How are you:" . "fine")
("Do you want to delete all your files:" . "Yes")))
(hello)))
(test)
; Hello jkiiski!
; I'm great too!
; Not deleting anything.
; Hello Foo Bar!
; I'm fine too!
; Deleting all files... (not really)
Yes you code is easy but the first is more clarifying what are you doing:
*query-io* is a global variable (which you can tell because of the * naming convention for global variables) that contains the input stream
connected to the terminal. The return value of prompt-read will be the
value of the last form, the call to READ-LINE, which returns the
string it read (without the trailing newline.)
This is what they said about *query-io*
And about the streams that you can put there works as follow:
most other I/O functions also accept T and NIL as stream designators
but with a different meaning: as a stream designator, T designates the
bidirectional stream *TERMINAL-IO*, while NIL designates
*STANDARD-OUTPUT* as an output stream and *STANDARD-INPUT* as an input stream
in this case it seems that this is only pointing to *standard-input* and not to the bidirectional stream t

How do I get all paragraphs in Emacs Lisp?

I am defining a major mode that works on paragraphs of the following nature:
: Identifier
1. some text
2. ...
3. some more text
: New Identifier
: Another Identifier
some text
I want to write a defun called get-paragraphs that will return a list that looks like:
( ("Identifier", ("1. some text", "2. ...", "3. some more text")),
("New Identifier", ()),
("Another Identifier", ("some text"))
)
How do I go about cutting up the text like this in Emacs Lisp:
Is there a function to iterate through them (and subsequently chop them up to my liking)? Should I use regular expressions? Is there an easier way?
You should iterate over the buffer and collect your text (untested):
(defun get-paragraphs ()
(save-excursion
(goto-char (point-min))
(let ((ret '()))
(while (search-forward-regexp "^: " nil t)
(let ((header (buffer-substring-no-properties (point) (line-end-position)))
(body '()))
(forward-line)
(while (not (looking-at "^$"))
(push (buffer-substring-no-properties (point) (line-end-position)) body)
(forward-line))
(push (cons header (list (reverse body))) ret)))
(nreverse ret))))
Here, take this Lisp code:
(defun chopchop ()
(mapcar
(lambda (x)
(destructuring-bind (head &rest tail)
(split-string x "\n" t)
(list head tail)))
(split-string (buffer-substring-no-properties
(point-min)
(point-max)) "\n?: *" t)))

How can I revert the buffer at point in emacs' buffer list?

I'm trying to create a function that will revert buffers from emacs' *Buffer List* buffer. As far as I can tell from the documentation, there's no way to do this quickly (in the manner of the save/mark/visit functions built in to buff-menu.el). So I'm writing some elisp. Here's my current attempt:
(defun frobnitz ()
"Call in buffer list to revert buffer at point to file."
(interactive)
(let ((buf (buffer-menu-buffer t)))
(if (y-or-n-p (concat "Revert " (buffer-name (buf)) " ?"))
(with-current-buffer buf
(let (())
(revert-buffer t t t)
(message
(concat "Reverted " (buffer-name (buf)) "to last saved state."))
)))))
Unfortunately, the above defun doesn't seem to work, and I'm having trouble figuring out why. If I eval the above, switch to the *Buffer List* buffer, and invoke M-: (frobnitz), then it errors out with the following.
Debugger entered--Lisp error: (void-function buffer-menu-buffer)
(buffer-menu-buffer t)
(let ((buf (buffer-menu-buffer t))) (if (y-or-n-p (concat "Revert " (buffer-name (buf)) " ?")) (with-current-buffer buf (let (nil) (revert-buffer t t t) (message (concat "Reverted " (buffer-name (buf)) "to last saved state."))))))
frobnitz()
eval((frobnitz) nil)
eval-expression((frobnitz) nil)
call-interactively(eval-expression nil nil)
It seems like that's telling me that there's no function buffer-menu-buffer - but that also seems gratuitously unlikely, since buffer-menu-buffer is a pretty central function in getting the buffer menu to work! For similar reasons, I'm deeply wary of messing with buffer-menu-buffer myself - I don't want to break the buffer menu.
Bearing in mind that the answer might be "invoke this function that you overlooked," how can I get this defun to accomplish its stated purpose of reverting a buffer directly from the buffer menu?
Update: as answerer Sean points out, the correct name of the function I was having a hard time with is Buffer-menu-buffer with a capital initial B. Having fixed that problem, I came across another:
(let (nil) (revert-buffer t t t) (message (concat "Reverted " buf-name "to last saved state.")))
(save-current-buffer (set-buffer buf) (let (nil) (revert-buffer t t t) (message (concat "Reverted " buf-name "to last saved state."))))
(with-current-buffer buf (let (nil) (revert-buffer t t t) (message (concat "Reverted " buf-name "to last saved state."))))
(if (y-or-n-p (concat "Revert " buf-name " ?")) (with-current-buffer buf (let (nil) (revert-buffer t t t) (message (concat "Reverted " buf-name "to last saved state.")))))
(let ((buf (Buffer-menu-buffer t)) (buf-name (concat "" (buffer-name (Buffer-menu-buffer t))))) (if (y-or-n-p (concat "Revert " buf-name " ?")) (with-current-buffer buf (let (nil) (revert-buffer t t t) (message (concat "Reverted " buf-name "to last saved state."))))))
frobnitz()
eval((frobnitz) nil)
eval-expression((frobnitz) nil)
call-interactively(eval-expression nil nil)
My guess is that with-current-buffer tries to save the current buffer and that's a no-no on *Buffer List*. So now I'm looking for an alternative - maybe just switch, revert, and invoke (buffer-list) to switch back.
Update 2:
For future readers: The working function and a single-key binding to invoke it in buffer-menu-mode:
;; Enhance the buffer menu's capabilities.
(defun revert-buffer-from-buffer-list ()
"Call in buffer list to revert buffer at point to file.
Bind this to a key in `buffer-menu-mode' to use it there - not productive in
other modes because it depends on the `Buffer-menu-buffer' function. Undefined
behavior if you invoke it on a buffer not associated with a file: that's why it
has a confirmation gate. Buffers not associated with files get to play by their
own rules when it comes to `revert-buffer' (which see)."
(interactive)
(let (
(buf (Buffer-menu-buffer t))
(buf-name (concat "" (buffer-name(Buffer-menu-buffer t))))
)
(if (y-or-n-p (concat "Revert " buf-name " ?"))
(with-current-buffer buf
(let ()
(revert-buffer t t t)
(message (concat "Reverted " buf-name " to last saved state."))
)))))
(add-hook 'Buffer-menu-mode-hook
(lambda ()
(define-key Buffer-menu-mode-map (kbd "R") revert-buffer-from-buffer-list)
))
Also an exhortation to caution: add-hook is not idempotent, so if you add things to foo-mode-hook that you don't intend to or which don't work, you risk breaking foo-mode until you zorch foo-mode-hook or prune the broken elements out of it. Ask me how I know!
My Emacs has a function Buffer-menu-buffer, but no buffer-menu-buffer. I imagine that's what's tripping you up.
EDIT:
I found two more problems with your code, after which I was able to revert buffers from the buffer menu with it.
I had to change (buf) to buf in two places. buf is a variable, not a function to call.
The (let (()) ...) construct causes an error. Either eliminate it, or change it to (let () ...) (although I don't know why you'd want to).