Skip to content

Unclear semantics for Prompt completions #610

Open
@solomon-b

Description

@solomon-b

Problem Description

I'm pretty motivated to put in some work on XMonad.Prompt. There is a lot of potential improvements around navigation and scrolling through commands. However, the semantics of the completion system are very unclear and possibly buggy. Is there anyone here who can give me clear explanation of the expected behavior of the completions system?

I'm going to give some examples of weird behavior of completions. I'm hoping we can determine which of these behaviors are intended, which are bugs, and finally define the semantics for completions. If we can do that then I would like to refactor this module a bit in anticipation of doing some feature work to extend the Prompt interface.

Simple Completions (alwaysHighlight == False)

I am using an XMonad prompt because they are very simple and use the default definitions of XPrompt.

Given the prompt:

systemCtlPrompt :: X ()
systemCtlPrompt =
  let commands =
        [ ("foo", pure ())
        , ("bar", pure ())
        , ("baz", pure ())
        , ("qux", pure ())
        ]
  in xmonadPromptCT "TEST!!" commands promptConfig

Tab completion works as I would expect. If I have entered nothing into the command line then pressing tab cycles between all the completion list options. If I enter b and press tab, it cycles between bar and baz.

Now if I change the prompt to use two words per completion:

systemCtlPrompt :: X ()
systemCtlPrompt =
  let commands =
        [ ("1 foo", pure ())
        , ("2 bar", pure ())
        , ("3 baz", pure ())
        , ("4 qux", pure ())
        ]
  in xmonadPromptCT "TEST!!" commands promptConfig

Now I get what I would consider a bug. The first tab sets the prompt to 1 foo and subsequent tabs prepend the command line with more 1 s.

This behavior is actually no surprise considering the default implementation of nextCompletion:

-- | Given the prompt type, the command line and the completion list,
-- return the next completion in the list for the last word of the
-- command line. This is the default 'nextCompletion' implementation.
getNextOfLastWord :: XPrompt t => t -> String -> [String] -> String
getNextOfLastWord t c l = skipLastWord c ++ completionToCommand t (l !! ni)
    where ni = case commandToComplete t c `elemIndex` map (completionToCommand t) l of
                 Just i -> if i >= length l - 1 then 0 else i + 1
                 Nothing -> 0

Given the default XPrompt definitions, what happens here is that we lookup the last word of the command line in the completion list--that word would be foo in our case--which will never be found thus setting nl = 0. Then the command line minus the last word is prepended to the entire completion string at index 0 and returned.

Based on the haddock description of this function, I believe what it should be doing is filtering the completion list for entries which contain the last word of the command line. I have no idea why this is a useful behavior for tab completion but that is what the haddock sort of describes.

highlight completions (alwaysHighlight == True)

The hlComplete code is a lot more confusing. Not so much to follow the subroutines, but to understand the desired behavior.
The user experience of of high completions feels pretty similar to simple completions modulo edge cases.

Using the same prompt from earlier:

systemCtlPrompt :: X ()
systemCtlPrompt =
  let commands =
        [ ("1 foo", pure ())
        , ("2 bar", pure ())
        , ("3 baz", pure ())
        , ("4 qux", pure ())
        ]
  in xmonadPromptCT "TEST!!" commands promptConfig

The tab key with no command input cycles through all the available completions. If I type b then tab cycles between 2 bar and 3 baz.

It gets weird if you enter a multiword command. For example, if you type "foo " (that is with a space after foo) then you get the entire completion list. I would expect this to either yield no completions (there is no completion with "foo " as a substring) or it would yield the 1 foo completion (by stripping the trailng space from the "search").

If after typing "foo "you then press tab, the highlight stays on 1 foo, the command gets replaced with foo 1 foo, and the completion list is still unfiltered. Subsequent pressed of tab cycle the highlighted completion and change the command to that completion prepended with "foo ", until you cycle back to the first completion and the prepended foo goes away.

If we use another prompt example I can show another strange behavior:

scrotPrompt :: X ()
scrotPrompt = xmonadPromptCT "Screenshot Options" commands promptConfig
  where
    commands = [ ("1: Capture Screen", spawn "scrot")
               , ("2: Capture Selection", spawn "scrot -s")
               , ("3: Capture All Screens", spawn "scrot -m")
               , ("4: Capture with 3 second countdown", spawn "scrot -d 3 -c")
               ]

If I enter 1 as my command then the search options are filtered down to 1: Capture Screen. Now if I press tab then the command is replaced with 1: Capture Screen as expected, however the completion options are also now extended to include 3: Capture All Screens. i have not been able to replicate this behavior with any other prompts or command inputs.

Proposal

If someone can outline the intended behavior for both completion types then I would be happy to write a PR eliminating whatever edge cases are not intended as part of that behavior.

Alternatively I would propose we eliminate the distinction between both completion types and replace them with a simple fuzzy filter of the completion options based on the currently entered command and the tab key can cycle through the filtered list of completions. alwaysHighlight would be either removed or not do anything (depending on how we handle deprecation of type parameters).

LIke I said, I am motivated to work on and expand the functionality of XMonad.Prompts. I want to do some cooler stuff like pagination and multiselect but before I can get there I think we need to have sensible behavior for completions.

Checklist

  • I've read CONTRIBUTING.md

  • I tested my configuration

    • With xmonad commit 33a86c0cdb9aa481e23cc5527a997adef5e32d42
    • With xmonad-contrib commit 20fdcba

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions