- completion-gestures
-
A list of gesture to complete the user input “as fully as
possible”. In McCLIM
:complete
gesture bound toTAB
. - help-gestures
-
A list of gestures to show help or to show possibilities. In
McCLIM
:help
gesture bound toC-?
. - possibilities-gestures
-
A list of gestures to show possible completions. In McCLIM
:possibilities
gesture bound toC-/
. - (complete-input stream function &key partial-completers ,@other)
- Function works on the input-editing-stream (i.e Drei). It accepts a function which is responsible for producing completions.
- (complete-from-generator str gen delim &key action predcate)
-
Returns set of possibilities starting with
str
, delimited withdelim
and generated by a functiongen
.
Presentation method accept
specialized on command-name
is defined
in Core/clim-core/commands.lisp:1213
. It calls complete-input
with
a trampoline to complete-from-generator
called with a local function
going over all command names which are not disabled. There is some
nasty drei-induced hack in there but is is irrelevant to the issue, we
may assume that function generator
“just works” and ignore it.
(multiple-value-bind (object success string)
(complete-input stream
#'(lambda (so-far mode)
(complete-from-generator so-far
#'generator
'(#\space)
:action mode))
:partial-completers '(#\space))
(if success
(values object type)
(simple-parse-error "No command named ~S" string)))
So parsing is done as: first command name is parsed then
arguments. When we decide that something is a command name there is no
way to go back based on what has been put as the first argument. I.e
if we have commands Print
and Print Foo
if we succesfully parse
Print
as a command typing later Foo
as an argument has no means to
affect the command name. In other words we can’t make a decision based
on input typed after the command.
Accepts prefix (so-far string) and action being one of:
- :complete
- complete either an exact match or “as much as possible”
- :complete-maximal
- complete “as much as possible”
- :complete-limited
- complete to the next partial delimiter
- :possibilities
- return alist of possible completions as last value
Returns five values:
- completed input string,
- boolean value indicating success or failure,
- object matching the completion,
- the number of matches,
- the list of possible completions
Function reads input from the user on the input editing stream until
it reads one of the possibilities. partial-completers
argument
allows completing on some strategic points (i.e spaces or dashes). It
returns three values: object
, success
and string
.
Whem command is parsed, then its name is delimited from its argument
by one of characters in *command-name-delimiters*
. It is specified,
that space must be one of them. In principle we could add another
delimiter like a double colon, so in ambigous case foo bar qux:
could be parsed without fail.
Proposing to “prohibit” space in command name would directly violate
how define-command is specified. Most notably
command-name-from-symbol
defines, that hyphens are replaced by
spaces in the command name.
By “current” I mean that this is how it behaves, not that it is specified to do so.
- Return object if it is already typed in the stream.
- Read gesture and decide its
action
.
- when gesture is
possibilities
return them - when gesture is one of
partial-completers
return:complete-limited
- when gesture is one of
*completion-gestures*
return:complete-maximal
- when gesture is either delimiter or activation gesture return
:complete
- otherwise return nil
- If there is no action append the character to the stream. Goto 1.
- Call the completer function with current buffer and the action.
- Do spaghetti logic which boils down to:
- when mode is
:complete-limited
and there are not matches fallback to:complete
if the gesture is a delimiter or activation gesture. - then if match is a success and mode is
:complete
unread the gesture - then if there are matches and mode is
:possibilities
read match from a box - then if we are succesful or mode is not
:complete
put completion in stream - then
– if we are succesful and mode is complete return result – else if gesture is activation-gesture either return or signal failure – else Goto 1
Interesting paths and issues:
- in point 1 in practice we may return
:complete
only by activation
gesture, because delimiter is the same as partial-delimiter (space).
Since we unread the gesture on succesful complete activation gesture is read later (return or newline) so not only it completes the input but also opens accepting-values dialog (undesired)
- completion-* action yields 2 results and none is exact match
- mode is not
:complete
so we insert the common prefix in buffer - we loop over and completion yields once again 2 results
- mode is not
:complete
so we insert “” in buffer
That means that we can’t complete further with neither completer and we need to type the missing character to reach the conclusion. If the missing character is space, which is partial completer, we are out of luck - it is impossible to enter the command.
When we press C-/
multiple times possibilities are shown but output
should not be scrolled. It looks awkward and is hardly justifiable. In
some cases it also finishes the parsing.
Commands in the table: “T”, “To”, “ToLisp”, “To Lisp”, “ToL isp” and “Toggle Foo”. They expose various problems with completion. Also, to not occlude the :possibilities issue , there is a command “Print”. There are also commands “A”, “AB” and “ABC” to illustrate :complete-maximal. Each command accepts one argument.
buffer state | gesture | action | buffer after | result |
---|---|---|---|---|
“T” | [space] | :complete-limited | “To” | |
“To” | [space] | :complete-limited | “To” | :complete fall no-op |
“To” | [return] | :complete | “To” | command name read, dialog |
“To” | [tab] | :complete-maximal | “To” | conflicts (no fallback) |
“To” | “g” | nil | “Tog” | |
“To” | [tab] | :complete-maximal | “Toggle Foo” | no conflicts procesing OK |
“Toggle Foo” | [space] | :complete-limited | success | :complete fall, return |
“ToL” | [space] | :complete-limited | “ToL ” | We skip direct match ToL |
“ToL ” | [space] | :complete-limited | “ToL isp” | Next space will rad line |
“A” | [tab] | :complete-maximal | “AB” | |
“AB” | [tab] | :complete-maximal | “ABC” | |
“ABC” | [tab] | :complete-maximal | “ABC” | no op after maximal match |
“Print” | C-/ | :possibilities | “Print” | possibilities are displayed |
“Print” | [space] | :complete-limited | “Print” | match, command returned |
“Pr” | C-/ | :possibilities | “Pr” | possibilities are displayed |
“Pr” | [click] | [continuation] | “Print” | buffer has the command |
“Print” | [space] | :complete-limited | “Print” | match, command returned |
“Pr” | C-/ | :possibilities | “Pr” | possibilities are displayed |
“Pr” | C-/ | :possibilities | ”” | command parse aborted (??) |
- possibilities gesture invoked invoked during possibilities are shown
is a no-op (doesn’t scroll, doesn’t abort command - nothing)
I’d like to test how it behaves more though (especially finding and redefining ramifications when and where it is shown, when (if at all) it disappears, when we can select commands from the list, how it scrolls the buffer etc. I’ve found some other bugs but I didn’t care to narrow them down yet.
- when the :complete-limited action is triggered and we have a
conflict (nmatches>=2) and input buffer doesn’t change after the operation “just” append gesture to the buffer
- when the :complete-maximal action is triggered and we have a
conflict (nmatches>=2) and input buffer doesn’t change after the operation invoke the possibilities menu (both as a visual hint that there is a conflict and to show what the options are)
- when the :complete action is triggered and we have a direct match
do not unread the gesture.
Here is a pseudocode draft of how it should (imo) behave. I also think that structure should not be a progn with multiple IFs but rather ecase like in this example, code in there is very hard to follow. TAGBODY + ECASE would be justified here. Mind it is not something I’ve run or tested, I’ve just tried to put above suggestion in code.
(loop
(multiple-value-bind (gesture mode)
(read-completion-gesture)
(tagbody
:again
(multiple-value-bind (input success object nmatches possibilities)
(and mode (funcall func (subseq so-far 0) mode))
(ecase mode
((nil)
(vector-push-extend gesture so-far))
(:possibilities
(multiple-value-bind (input object) (read-possibility)
(when input
(return-from complete-input
(values object success input)))))
(:complete
(when success
(return-from complete-input
(values object success input))))
(:complete-limited
(cond ((zerop nmatches)
;; we have no further completion, try what we have
(setf mode :complete)
(go :again))
((emptyp input)
;; we have no progress despite matches
(setf mode nil)
(go :again))
(t
;; if we have a common prefix, complete-limited!
(insert-input input))))
(:complete-maximal
(cond ((zerop nmatches)
(beep))
((emtyp input)
;; we have no progress despite matches
(setf mode :possibilities)
(go :again))
(t
;; we have completion which adds input, complete-maximal!
(insert-input input))))))
(when (activation-gesture-p gesture)
(if allow-any-input
(return-from complete-input
(values nil t (subseq so-far 0)))
(error 'simple-completion-error
:format-control "Input ~S does not match"
:format-arguments (list so-far)
:input-so-far so-far))))))
(in-package :clim-user)
(clim:define-application-frame foo-frame ()
()
(:pane :interactor)
(:geometry :width 600 :height 600))
(defun open-foo-frame ()
(let ((frame (clim:make-application-frame 'foo-frame)))
(clim:run-frame-top-level frame)))
(define-foo-frame-command (cmd-0 :name "Print") ((arg string)) (format t arg))
(define-foo-frame-command (cmd-1 :name "T") ((arg string)) (format t arg))
(define-foo-frame-command (cmd-2 :name "To") ((arg string)) (format t arg))
(define-foo-frame-command (cmd-3 :name "ToLisp") ((arg string)) (format t arg))
(define-foo-frame-command (cmd-4 :name "To Lisp") ((arg string)) (format t arg))
(define-foo-frame-command (cmd-5 :name "ToL isp") ((arg string)) (format t arg))
(define-foo-frame-command (cmd-6 :name "Toggle Foo") ((arg string)) (format t arg))
(define-foo-frame-command (cmd-7 :name "A") ((arg string)) (format t arg))
(define-foo-frame-command (cmd-8 :name "AB") ((arg string)) (format t arg))
(define-foo-frame-command (cmd-9 :name "ABC") ((arg string)) (format t arg))
(open-foo-frame)