prompt-toolkit / prompt-toolkit/python-prompt-toolkit

HSplit: second window jumps to lower end of terminal

Open
#716 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
10.6k
Forks
815
PR merge metrics
No merged PRs in 30d

Description

Hey there,
I'm using whaaaat, an inquirer.js clone based on prompt_toolkit to implement an internal CLI for our company. After building a minimal PoC, I found that when using prompt_toolkits HSplit component, the second window passed into the layout initially gets rendered correctly and then jumps to the bottom of the terminal window (see GIF). Also, this only happens when the Inquiry is started within a cleared window. I have already filed an issue at whaaaaat, but thought that you folks may be more suited to give me hint, since this seems to be caused by HSplit.

The library uses the following Layout:

layout = HSplit([
  Window(content=TokenListControl(get_prompt_tokens, align_center=False)),
  ConditionalContainer(
    Window(
      ic,
      width=D.exact(43),
      height=D(min=3),
      scroll_offsets=ScrollOffsets(top=1, bottom=1)
    ),
    filter=~IsDone()
  )
])

where ic is an instance of InquirerControl implemented as follows:

class InquirerControl(TokenListControl):
    def __init__(self, choices, **kwargs):
        self.selected_option_index = 0
        self.answered = False
        self.choices = choices
        self._init_choices(choices)
        super(InquirerControl, self).__init__(self._get_choice_tokens,
                                              **kwargs)

    def _init_choices(self, choices, default=None):
        # helper to convert from question format to internal format
        self.choices = []  # list (name, value, disabled)
        searching_first_choice = True
        for i, c in enumerate(choices):
            if isinstance(c, Separator):
                self.choices.append((c, None, None))
            else:
                if isinstance(c, basestring):
                    self.choices.append((c, c, None))
                else:
                    name = c.get('name')
                    value = c.get('value', name)
                    disabled = c.get('disabled', None)
                    self.choices.append((name, value, disabled))
                if searching_first_choice:
                    self.selected_option_index = i  # found the first choice
                    searching_first_choice = False

    @property
    def choice_count(self):
        return len(self.choices)

    def _get_choice_tokens(self, cli):
        tokens = []
        T = Token

        def append(index, choice):
            selected = (index == self.selected_option_index)

            @if_mousedown
            def select_item(cli, mouse_event):
                # bind option with this index to mouse event
                self.selected_option_index = index
                self.answered = True
                cli.set_return_value(None)

            tokens.append((T.Pointer if selected else T, ' \u276f ' if selected
            else '   '))
            if selected:
                tokens.append((Token.SetCursorPosition, ''))
            if choice[2]:  # disabled
                tokens.append((T.Selected if selected else T,
                               '- %s (%s)' % (choice[0], choice[2])))
            else:
                tokens.append((T.Selected if selected else T, str(choice[0]),
                               select_item))
            tokens.append((T, '\n'))

        # prepare the select choices
        for i, choice in enumerate(self.choices):
            append(i, choice)
        tokens.pop()  # Remove last newline.
        return tokens

    def get_selection(self):
        return self.choices[self.selected_option_index]

Gif with cleared window:

GIF with cleared

Gif without cleared window:

uncleared

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the minimal PoC with the shown prompt_toolkit HSplit layout in both cleared and uncleared terminal windows. Inspect HSplit rendering and terminal cursor or screen-position handling, then verify that the second window remains in its intended position in both cases.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
cli
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.