execute incomplete commands when theres only one completion

1 view
Skip to first unread message

Nathan Shostek

unread,
Feb 25, 2020, 1:59:25 PM2/25/20
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))))

Nathan Shostek

unread,
Feb 26, 2020, 8:48:31 AM2/26/20
to stumpw...@nongnu.org
After digging further, I think the best place to make changes isnt inthese
functions, but rather in get-completion-preview-list. Does this sound correct?

Thanks,
Nathan s

Responding to:

Nathan Shostek

unread,
Feb 27, 2020, 4:52:57 PM2/27/20
to stumpw...@nongnu.org
Sorry for spamming this mailing list, but I've worked out how to achieve
the emacs-esq completions I would like to have when inputing commands
via colon. Knowing myself, I'm probably doing something wrong, but it
seems to work fine.

Heres the code: https://gist.github.com/szos/d7dbd8f1c9f4b7d71666bafe8621629c

Cheers,
Nathan

David Bjergaard

unread,
Mar 3, 2020, 7:01:55 PM3/3/20
to Nathan Shostek, stumpw...@nongnu.org
If the community would benefit feel free to open a pull request and we’ll get the code reviewed and integrated in stumpwm proper.

David

> On Feb 27, 2020, at 4:53 PM, Nathan Shostek <sz...@posteo.net> wrote:
>
> Sorry for spamming this mailing list, but I've worked out how to achieve

Nathan Shostek

unread,
Mar 5, 2020, 10:43:58 AM3/5/20
to David Bjergaard, stumpw...@nongnu.org
I'm unsure how useful this would be to the community. It seems (to me)
to be pretty hacky, and there are a couple edge cases where it doesnt
behave properly with regards to inserting a hyphen or a space
Reply all
Reply to author
Forward
0 new messages