Nathan Shostek
unread,Feb 25, 2020, 1:59:25 PM2/25/20Sign in to reply to author
Sign in to forward
You do not have permission to delete messages in this group
Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message
to stumpw...@nongnu.org
Hello,
When Emacs is accepting input via M-x one can forego writing the whole command
due to the way emacs completes it. While I'm not skilled enough to implement
emacs style completion, I do think it would be nice to be able to complete
incomplete commands when there is only one possible completion. I'm putting this
here instead of in a github pull request because, in all truthfulness, github
forking and pull-requesting confuses me, and someone told me I could also
submit my contributions throught this mailing list.
I've modified the functions eval-command and call-interactively to have an
additional optional argument called auto-complete, which when true retries
completion before throwing an error. I'm not sure if this is the right way
to get the behavior I want, but it works. If theres a better way to do this
I'll gladly try to implement it.
Apologies for the length of this email; I figured it was better to include every
function than just the small bits i changed, but if its not let me know and I'll
do it differently in the future.
Cheers,
Nathan
Heres the code:
(defun call-interactively (command &optional (input "") auto-complete)
"Parse the command's arguments from input given the command's
argument specifications then execute it. Returns a string or nil if
user aborted."
(declare (type (or string symbol) command)
(type (or string argument-line) input))
;; Catch parse errors
(catch 'error
(let* ((arg-line (if (stringp input)
(make-argument-line :string input
:start 0)
input))
(cmd-data (or (get-command-structure command)
(and auto-complete
(let ((comp (input-find-completions command (all-commands))))
(when (and comp (= 1 (length comp)))
(get-command-structure (car comp)))))
(throw 'error (format nil "Command '~a' not found." command))))
(arg-specs (command-args cmd-data))
(args (loop for spec in arg-specs
collect (let* ((type (if (listp spec)
(first spec)
spec))
(prompt (when (listp spec)
(second spec)))
(fn (gethash type *command-type-hash*)))
(unless fn
(throw 'error (format nil "Bad argument type: ~s" type)))
;; If the prompt is NIL then it's
;; considered an optional argument and
;; we shouldn't prompt for it if the
;; arg line is empty.
(if (and (null prompt)
(argument-line-end-p arg-line))
(loop-finish)
(funcall fn arg-line prompt))))))
;; Did the whole string get parsed?
(unless (or (argument-line-end-p arg-line)
(position-if 'alphanumericp (argument-line-string arg-line) :start (argument-line-start arg-line)))
(throw 'error (format nil "Trailing garbage: ~{~A~^ ~}" (subseq (argument-line-string arg-line)
(argument-line-start arg-line)))))
;; Success
(prog1
(apply (command-name cmd-data) args)
(setf *last-command* command)))))
(defun eval-command (cmd &optional interactivep auto-complete)
"exec cmd and echo the result."
(labels ((parse-and-run-command (input)
(let* ((arg-line (make-argument-line :string input
:start 0))
(cmd (argument-pop arg-line)))
(let ((*interactivep* interactivep))
(call-interactively cmd arg-line auto-complete)))))
(multiple-value-bind (result error-p)
;; this fancy footwork lets us grab the backtrace from where the
;; error actually happened.
(restart-case
(handler-bind
((error (lambda (c)
(invoke-restart 'eval-command-error
(format nil "^B^1*Error In Command '^b~a^B': ^n~A~a"
cmd c (if *show-command-backtrace*
(backtrace-string) ""))))))
(parse-and-run-command cmd))
(eval-command-error (err-text)
:interactive (lambda () nil)
(values err-text t)))
;; interactive commands update the modeline
(update-all-mode-lines)
(cond ((stringp result)
(if error-p
(message-no-timeout "~a" result)
(message "~a" result)))
((eq result :abort)
(unless *suppress-abort-messages*
(message "Abort.")))))))
(defcommand colon (&optional initial-input) (:rest)
"Read a command from the user. @var{initial-text} is optional. When
supplied, the text will appear in the prompt.
String arguments with spaces may be passed to the command by
delimiting them with double quotes. A backslash can be used to escape
double quotes or backslashes inside the string. This does not apply to
commands taking :REST or :SHELL type arguments."
(let ((cmd (completing-read (current-screen) ": " (all-commands) :initial-input (or initial-input ""))))
(unless cmd
(throw 'error :abort))
(when (plusp (length cmd))
(eval-command cmd t t))))