Uploaded Test files
This commit is contained in:
parent
f584ad9d97
commit
2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions
60
venv/Lib/site-packages/prompt_toolkit/widgets/__init__.py
Normal file
60
venv/Lib/site-packages/prompt_toolkit/widgets/__init__.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
"""
|
||||
Collection of reusable components for building full screen applications.
|
||||
These are higher level abstractions on top of the `prompt_toolkit.layout`
|
||||
module.
|
||||
|
||||
Most of these widgets implement the ``__pt_container__`` method, which makes it
|
||||
possible to embed these in the layout like any other container.
|
||||
"""
|
||||
from .base import (
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
CheckboxList,
|
||||
Frame,
|
||||
HorizontalLine,
|
||||
Label,
|
||||
ProgressBar,
|
||||
RadioList,
|
||||
Shadow,
|
||||
TextArea,
|
||||
VerticalLine,
|
||||
)
|
||||
from .dialogs import Dialog
|
||||
from .menus import MenuContainer, MenuItem
|
||||
from .toolbars import (
|
||||
ArgToolbar,
|
||||
CompletionsToolbar,
|
||||
FormattedTextToolbar,
|
||||
SearchToolbar,
|
||||
SystemToolbar,
|
||||
ValidationToolbar,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Base.
|
||||
"TextArea",
|
||||
"Label",
|
||||
"Button",
|
||||
"Frame",
|
||||
"Shadow",
|
||||
"Box",
|
||||
"VerticalLine",
|
||||
"HorizontalLine",
|
||||
"CheckboxList",
|
||||
"RadioList",
|
||||
"Checkbox",
|
||||
"ProgressBar",
|
||||
# Toolbars.
|
||||
"ArgToolbar",
|
||||
"CompletionsToolbar",
|
||||
"FormattedTextToolbar",
|
||||
"SearchToolbar",
|
||||
"SystemToolbar",
|
||||
"ValidationToolbar",
|
||||
# Dialogs.
|
||||
"Dialog",
|
||||
# Menus.
|
||||
"MenuContainer",
|
||||
"MenuItem",
|
||||
]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
916
venv/Lib/site-packages/prompt_toolkit/widgets/base.py
Normal file
916
venv/Lib/site-packages/prompt_toolkit/widgets/base.py
Normal file
|
@ -0,0 +1,916 @@
|
|||
"""
|
||||
Collection of reusable components for building full screen applications.
|
||||
|
||||
All of these widgets implement the ``__pt_container__`` method, which makes
|
||||
them usable in any situation where we are expecting a `prompt_toolkit`
|
||||
container object.
|
||||
|
||||
.. warning::
|
||||
|
||||
At this point, the API for these widgets is considered unstable, and can
|
||||
potentially change between minor releases (we try not too, but no
|
||||
guarantees are made yet). The public API in
|
||||
`prompt_toolkit.shortcuts.dialogs` on the other hand is considered stable.
|
||||
"""
|
||||
from functools import partial
|
||||
from typing import Callable, Generic, List, Optional, Sequence, Tuple, TypeVar, Union
|
||||
|
||||
from prompt_toolkit.application.current import get_app
|
||||
from prompt_toolkit.auto_suggest import AutoSuggest, DynamicAutoSuggest
|
||||
from prompt_toolkit.buffer import Buffer, BufferAcceptHandler
|
||||
from prompt_toolkit.completion import Completer, DynamicCompleter
|
||||
from prompt_toolkit.document import Document
|
||||
from prompt_toolkit.filters import (
|
||||
Condition,
|
||||
FilterOrBool,
|
||||
has_focus,
|
||||
is_done,
|
||||
is_true,
|
||||
to_filter,
|
||||
)
|
||||
from prompt_toolkit.formatted_text import (
|
||||
AnyFormattedText,
|
||||
StyleAndTextTuples,
|
||||
Template,
|
||||
to_formatted_text,
|
||||
)
|
||||
from prompt_toolkit.formatted_text.utils import fragment_list_to_text
|
||||
from prompt_toolkit.history import History
|
||||
from prompt_toolkit.key_binding.key_bindings import KeyBindings
|
||||
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
|
||||
from prompt_toolkit.keys import Keys
|
||||
from prompt_toolkit.layout.containers import (
|
||||
AnyContainer,
|
||||
ConditionalContainer,
|
||||
Container,
|
||||
DynamicContainer,
|
||||
Float,
|
||||
FloatContainer,
|
||||
HSplit,
|
||||
VSplit,
|
||||
Window,
|
||||
WindowAlign,
|
||||
)
|
||||
from prompt_toolkit.layout.controls import (
|
||||
BufferControl,
|
||||
FormattedTextControl,
|
||||
GetLinePrefixCallable,
|
||||
)
|
||||
from prompt_toolkit.layout.dimension import AnyDimension
|
||||
from prompt_toolkit.layout.dimension import Dimension as D
|
||||
from prompt_toolkit.layout.dimension import to_dimension
|
||||
from prompt_toolkit.layout.margins import (
|
||||
ConditionalMargin,
|
||||
NumberedMargin,
|
||||
ScrollbarMargin,
|
||||
)
|
||||
from prompt_toolkit.layout.processors import (
|
||||
AppendAutoSuggestion,
|
||||
BeforeInput,
|
||||
ConditionalProcessor,
|
||||
PasswordProcessor,
|
||||
Processor,
|
||||
)
|
||||
from prompt_toolkit.lexers import DynamicLexer, Lexer
|
||||
from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
|
||||
from prompt_toolkit.utils import get_cwidth
|
||||
from prompt_toolkit.validation import DynamicValidator, Validator
|
||||
|
||||
from .toolbars import SearchToolbar
|
||||
|
||||
__all__ = [
|
||||
"TextArea",
|
||||
"Label",
|
||||
"Button",
|
||||
"Frame",
|
||||
"Shadow",
|
||||
"Box",
|
||||
"VerticalLine",
|
||||
"HorizontalLine",
|
||||
"RadioList",
|
||||
"CheckboxList",
|
||||
"Checkbox", # backward compatibility
|
||||
"ProgressBar",
|
||||
]
|
||||
|
||||
E = KeyPressEvent
|
||||
|
||||
|
||||
class Border:
|
||||
" Box drawing characters. (Thin) "
|
||||
HORIZONTAL = "\u2500"
|
||||
VERTICAL = "\u2502"
|
||||
TOP_LEFT = "\u250c"
|
||||
TOP_RIGHT = "\u2510"
|
||||
BOTTOM_LEFT = "\u2514"
|
||||
BOTTOM_RIGHT = "\u2518"
|
||||
|
||||
|
||||
class TextArea:
|
||||
"""
|
||||
A simple input field.
|
||||
|
||||
This is a higher level abstraction on top of several other classes with
|
||||
sane defaults.
|
||||
|
||||
This widget does have the most common options, but it does not intend to
|
||||
cover every single use case. For more configurations options, you can
|
||||
always build a text area manually, using a
|
||||
:class:`~prompt_toolkit.buffer.Buffer`,
|
||||
:class:`~prompt_toolkit.layout.BufferControl` and
|
||||
:class:`~prompt_toolkit.layout.Window`.
|
||||
|
||||
Buffer attributes:
|
||||
|
||||
:param text: The initial text.
|
||||
:param multiline: If True, allow multiline input.
|
||||
:param completer: :class:`~prompt_toolkit.completion.Completer` instance
|
||||
for auto completion.
|
||||
:param complete_while_typing: Boolean.
|
||||
:param accept_handler: Called when `Enter` is pressed (This should be a
|
||||
callable that takes a buffer as input).
|
||||
:param history: :class:`~prompt_toolkit.history.History` instance.
|
||||
:param auto_suggest: :class:`~prompt_toolkit.auto_suggest.AutoSuggest`
|
||||
instance for input suggestions.
|
||||
|
||||
BufferControl attributes:
|
||||
|
||||
:param password: When `True`, display using asterisks.
|
||||
:param focusable: When `True`, allow this widget to receive the focus.
|
||||
:param focus_on_click: When `True`, focus after mouse click.
|
||||
:param input_processors: `None` or a list of
|
||||
:class:`~prompt_toolkit.layout.Processor` objects.
|
||||
:param validator: `None` or a :class:`~prompt_toolkit.validation.Validator`
|
||||
object.
|
||||
|
||||
Window attributes:
|
||||
|
||||
:param lexer: :class:`~prompt_toolkit.lexers.Lexer` instance for syntax
|
||||
highlighting.
|
||||
:param wrap_lines: When `True`, don't scroll horizontally, but wrap lines.
|
||||
:param width: Window width. (:class:`~prompt_toolkit.layout.Dimension` object.)
|
||||
:param height: Window height. (:class:`~prompt_toolkit.layout.Dimension` object.)
|
||||
:param scrollbar: When `True`, display a scroll bar.
|
||||
:param style: A style string.
|
||||
:param dont_extend_width: When `True`, don't take up more width then the
|
||||
preferred width reported by the control.
|
||||
:param dont_extend_height: When `True`, don't take up more width then the
|
||||
preferred height reported by the control.
|
||||
:param get_line_prefix: None or a callable that returns formatted text to
|
||||
be inserted before a line. It takes a line number (int) and a
|
||||
wrap_count and returns formatted text. This can be used for
|
||||
implementation of line continuations, things like Vim "breakindent" and
|
||||
so on.
|
||||
|
||||
Other attributes:
|
||||
|
||||
:param search_field: An optional `SearchToolbar` object.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text: str = "",
|
||||
multiline: FilterOrBool = True,
|
||||
password: FilterOrBool = False,
|
||||
lexer: Optional[Lexer] = None,
|
||||
auto_suggest: Optional[AutoSuggest] = None,
|
||||
completer: Optional[Completer] = None,
|
||||
complete_while_typing: FilterOrBool = True,
|
||||
validator: Optional[Validator] = None,
|
||||
accept_handler: Optional[BufferAcceptHandler] = None,
|
||||
history: Optional[History] = None,
|
||||
focusable: FilterOrBool = True,
|
||||
focus_on_click: FilterOrBool = False,
|
||||
wrap_lines: FilterOrBool = True,
|
||||
read_only: FilterOrBool = False,
|
||||
width: AnyDimension = None,
|
||||
height: AnyDimension = None,
|
||||
dont_extend_height: FilterOrBool = False,
|
||||
dont_extend_width: FilterOrBool = False,
|
||||
line_numbers: bool = False,
|
||||
get_line_prefix: Optional[GetLinePrefixCallable] = None,
|
||||
scrollbar: bool = False,
|
||||
style: str = "",
|
||||
search_field: Optional[SearchToolbar] = None,
|
||||
preview_search: FilterOrBool = True,
|
||||
prompt: AnyFormattedText = "",
|
||||
input_processors: Optional[List[Processor]] = None,
|
||||
) -> None:
|
||||
|
||||
if search_field is None:
|
||||
search_control = None
|
||||
elif isinstance(search_field, SearchToolbar):
|
||||
search_control = search_field.control
|
||||
|
||||
if input_processors is None:
|
||||
input_processors = []
|
||||
|
||||
# Writeable attributes.
|
||||
self.completer = completer
|
||||
self.complete_while_typing = complete_while_typing
|
||||
self.lexer = lexer
|
||||
self.auto_suggest = auto_suggest
|
||||
self.read_only = read_only
|
||||
self.wrap_lines = wrap_lines
|
||||
self.validator = validator
|
||||
|
||||
self.buffer = Buffer(
|
||||
document=Document(text, 0),
|
||||
multiline=multiline,
|
||||
read_only=Condition(lambda: is_true(self.read_only)),
|
||||
completer=DynamicCompleter(lambda: self.completer),
|
||||
complete_while_typing=Condition(
|
||||
lambda: is_true(self.complete_while_typing)
|
||||
),
|
||||
validator=DynamicValidator(lambda: self.validator),
|
||||
auto_suggest=DynamicAutoSuggest(lambda: self.auto_suggest),
|
||||
accept_handler=accept_handler,
|
||||
history=history,
|
||||
)
|
||||
|
||||
self.control = BufferControl(
|
||||
buffer=self.buffer,
|
||||
lexer=DynamicLexer(lambda: self.lexer),
|
||||
input_processors=[
|
||||
ConditionalProcessor(
|
||||
AppendAutoSuggestion(), has_focus(self.buffer) & ~is_done
|
||||
),
|
||||
ConditionalProcessor(
|
||||
processor=PasswordProcessor(), filter=to_filter(password)
|
||||
),
|
||||
BeforeInput(prompt, style="class:text-area.prompt"),
|
||||
]
|
||||
+ input_processors,
|
||||
search_buffer_control=search_control,
|
||||
preview_search=preview_search,
|
||||
focusable=focusable,
|
||||
focus_on_click=focus_on_click,
|
||||
)
|
||||
|
||||
if multiline:
|
||||
if scrollbar:
|
||||
right_margins = [ScrollbarMargin(display_arrows=True)]
|
||||
else:
|
||||
right_margins = []
|
||||
if line_numbers:
|
||||
left_margins = [NumberedMargin()]
|
||||
else:
|
||||
left_margins = []
|
||||
else:
|
||||
height = D.exact(1)
|
||||
left_margins = []
|
||||
right_margins = []
|
||||
|
||||
style = "class:text-area " + style
|
||||
|
||||
self.window = Window(
|
||||
height=height,
|
||||
width=width,
|
||||
dont_extend_height=dont_extend_height,
|
||||
dont_extend_width=dont_extend_width,
|
||||
content=self.control,
|
||||
style=style,
|
||||
wrap_lines=Condition(lambda: is_true(self.wrap_lines)),
|
||||
left_margins=left_margins,
|
||||
right_margins=right_margins,
|
||||
get_line_prefix=get_line_prefix,
|
||||
)
|
||||
|
||||
@property
|
||||
def text(self) -> str:
|
||||
"""
|
||||
The `Buffer` text.
|
||||
"""
|
||||
return self.buffer.text
|
||||
|
||||
@text.setter
|
||||
def text(self, value: str) -> None:
|
||||
self.document = Document(value, 0)
|
||||
|
||||
@property
|
||||
def document(self) -> Document:
|
||||
"""
|
||||
The `Buffer` document (text + cursor position).
|
||||
"""
|
||||
return self.buffer.document
|
||||
|
||||
@document.setter
|
||||
def document(self, value: Document) -> None:
|
||||
self.buffer.set_document(value, bypass_readonly=True)
|
||||
|
||||
@property
|
||||
def accept_handler(self) -> Optional[BufferAcceptHandler]:
|
||||
"""
|
||||
The accept handler. Called when the user accepts the input.
|
||||
"""
|
||||
return self.buffer.accept_handler
|
||||
|
||||
@accept_handler.setter
|
||||
def accept_handler(self, value: BufferAcceptHandler) -> None:
|
||||
self.buffer.accept_handler = value
|
||||
|
||||
def __pt_container__(self) -> Container:
|
||||
return self.window
|
||||
|
||||
|
||||
class Label:
|
||||
"""
|
||||
Widget that displays the given text. It is not editable or focusable.
|
||||
|
||||
:param text: The text to be displayed. (This can be multiline. This can be
|
||||
formatted text as well.)
|
||||
:param style: A style string.
|
||||
:param width: When given, use this width, rather than calculating it from
|
||||
the text size.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text: AnyFormattedText,
|
||||
style: str = "",
|
||||
width: AnyDimension = None,
|
||||
dont_extend_height: bool = True,
|
||||
dont_extend_width: bool = False,
|
||||
) -> None:
|
||||
|
||||
self.text = text
|
||||
|
||||
def get_width() -> AnyDimension:
|
||||
if width is None:
|
||||
text_fragments = to_formatted_text(self.text)
|
||||
text = fragment_list_to_text(text_fragments)
|
||||
if text:
|
||||
longest_line = max(get_cwidth(line) for line in text.splitlines())
|
||||
else:
|
||||
return D(preferred=0)
|
||||
return D(preferred=longest_line)
|
||||
else:
|
||||
return width
|
||||
|
||||
self.formatted_text_control = FormattedTextControl(text=lambda: self.text)
|
||||
|
||||
self.window = Window(
|
||||
content=self.formatted_text_control,
|
||||
width=get_width,
|
||||
style="class:label " + style,
|
||||
dont_extend_height=dont_extend_height,
|
||||
dont_extend_width=dont_extend_width,
|
||||
)
|
||||
|
||||
def __pt_container__(self) -> Container:
|
||||
return self.window
|
||||
|
||||
|
||||
class Button:
|
||||
"""
|
||||
Clickable button.
|
||||
|
||||
:param text: The caption for the button.
|
||||
:param handler: `None` or callable. Called when the button is clicked. No
|
||||
parameters are passed to this callable. Use for instance Python's
|
||||
`functools.partial` to pass parameters to this callable if needed.
|
||||
:param width: Width of the button.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, text: str, handler: Optional[Callable[[], None]] = None, width: int = 12
|
||||
) -> None:
|
||||
|
||||
self.text = text
|
||||
self.handler = handler
|
||||
self.width = width
|
||||
self.control = FormattedTextControl(
|
||||
self._get_text_fragments,
|
||||
key_bindings=self._get_key_bindings(),
|
||||
focusable=True,
|
||||
)
|
||||
|
||||
def get_style() -> str:
|
||||
if get_app().layout.has_focus(self):
|
||||
return "class:button.focused"
|
||||
else:
|
||||
return "class:button"
|
||||
|
||||
self.window = Window(
|
||||
self.control,
|
||||
align=WindowAlign.CENTER,
|
||||
height=1,
|
||||
width=width,
|
||||
style=get_style,
|
||||
dont_extend_width=True,
|
||||
dont_extend_height=True,
|
||||
)
|
||||
|
||||
def _get_text_fragments(self) -> StyleAndTextTuples:
|
||||
text = ("{:^%s}" % (self.width - 2)).format(self.text)
|
||||
|
||||
def handler(mouse_event: MouseEvent) -> None:
|
||||
if (
|
||||
self.handler is not None
|
||||
and mouse_event.event_type == MouseEventType.MOUSE_UP
|
||||
):
|
||||
self.handler()
|
||||
|
||||
return [
|
||||
("class:button.arrow", "<", handler),
|
||||
("[SetCursorPosition]", ""),
|
||||
("class:button.text", text, handler),
|
||||
("class:button.arrow", ">", handler),
|
||||
]
|
||||
|
||||
def _get_key_bindings(self) -> KeyBindings:
|
||||
" Key bindings for the Button. "
|
||||
kb = KeyBindings()
|
||||
|
||||
@kb.add(" ")
|
||||
@kb.add("enter")
|
||||
def _(event: E) -> None:
|
||||
if self.handler is not None:
|
||||
self.handler()
|
||||
|
||||
return kb
|
||||
|
||||
def __pt_container__(self) -> Container:
|
||||
return self.window
|
||||
|
||||
|
||||
class Frame:
|
||||
"""
|
||||
Draw a border around any container, optionally with a title text.
|
||||
|
||||
Changing the title and body of the frame is possible at runtime by
|
||||
assigning to the `body` and `title` attributes of this class.
|
||||
|
||||
:param body: Another container object.
|
||||
:param title: Text to be displayed in the top of the frame (can be formatted text).
|
||||
:param style: Style string to be applied to this widget.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
body: AnyContainer,
|
||||
title: AnyFormattedText = "",
|
||||
style: str = "",
|
||||
width: AnyDimension = None,
|
||||
height: AnyDimension = None,
|
||||
key_bindings: Optional[KeyBindings] = None,
|
||||
modal: bool = False,
|
||||
) -> None:
|
||||
|
||||
self.title = title
|
||||
self.body = body
|
||||
|
||||
fill = partial(Window, style="class:frame.border")
|
||||
style = "class:frame " + style
|
||||
|
||||
top_row_with_title = VSplit(
|
||||
[
|
||||
fill(width=1, height=1, char=Border.TOP_LEFT),
|
||||
fill(char=Border.HORIZONTAL),
|
||||
fill(width=1, height=1, char="|"),
|
||||
# Notice: we use `Template` here, because `self.title` can be an
|
||||
# `HTML` object for instance.
|
||||
Label(
|
||||
lambda: Template(" {} ").format(self.title),
|
||||
style="class:frame.label",
|
||||
dont_extend_width=True,
|
||||
),
|
||||
fill(width=1, height=1, char="|"),
|
||||
fill(char=Border.HORIZONTAL),
|
||||
fill(width=1, height=1, char=Border.TOP_RIGHT),
|
||||
],
|
||||
height=1,
|
||||
)
|
||||
|
||||
top_row_without_title = VSplit(
|
||||
[
|
||||
fill(width=1, height=1, char=Border.TOP_LEFT),
|
||||
fill(char=Border.HORIZONTAL),
|
||||
fill(width=1, height=1, char=Border.TOP_RIGHT),
|
||||
],
|
||||
height=1,
|
||||
)
|
||||
|
||||
@Condition
|
||||
def has_title() -> bool:
|
||||
return bool(self.title)
|
||||
|
||||
self.container = HSplit(
|
||||
[
|
||||
ConditionalContainer(content=top_row_with_title, filter=has_title),
|
||||
ConditionalContainer(content=top_row_without_title, filter=~has_title),
|
||||
VSplit(
|
||||
[
|
||||
fill(width=1, char=Border.VERTICAL),
|
||||
DynamicContainer(lambda: self.body),
|
||||
fill(width=1, char=Border.VERTICAL),
|
||||
# Padding is required to make sure that if the content is
|
||||
# too small, the right frame border is still aligned.
|
||||
],
|
||||
padding=0,
|
||||
),
|
||||
VSplit(
|
||||
[
|
||||
fill(width=1, height=1, char=Border.BOTTOM_LEFT),
|
||||
fill(char=Border.HORIZONTAL),
|
||||
fill(width=1, height=1, char=Border.BOTTOM_RIGHT),
|
||||
]
|
||||
),
|
||||
],
|
||||
width=width,
|
||||
height=height,
|
||||
style=style,
|
||||
key_bindings=key_bindings,
|
||||
modal=modal,
|
||||
)
|
||||
|
||||
def __pt_container__(self) -> Container:
|
||||
return self.container
|
||||
|
||||
|
||||
class Shadow:
|
||||
"""
|
||||
Draw a shadow underneath/behind this container.
|
||||
(This applies `class:shadow` the the cells under the shadow. The Style
|
||||
should define the colors for the shadow.)
|
||||
|
||||
:param body: Another container object.
|
||||
"""
|
||||
|
||||
def __init__(self, body: AnyContainer) -> None:
|
||||
self.container = FloatContainer(
|
||||
content=body,
|
||||
floats=[
|
||||
Float(
|
||||
bottom=-1,
|
||||
height=1,
|
||||
left=1,
|
||||
right=-1,
|
||||
transparent=True,
|
||||
content=Window(style="class:shadow"),
|
||||
),
|
||||
Float(
|
||||
bottom=-1,
|
||||
top=1,
|
||||
width=1,
|
||||
right=-1,
|
||||
transparent=True,
|
||||
content=Window(style="class:shadow"),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
def __pt_container__(self) -> Container:
|
||||
return self.container
|
||||
|
||||
|
||||
class Box:
|
||||
"""
|
||||
Add padding around a container.
|
||||
|
||||
This also makes sure that the parent can provide more space than required by
|
||||
the child. This is very useful when wrapping a small element with a fixed
|
||||
size into a ``VSplit`` or ``HSplit`` object. The ``HSplit`` and ``VSplit``
|
||||
try to make sure to adapt respectively the width and height, possibly
|
||||
shrinking other elements. Wrapping something in a ``Box`` makes it flexible.
|
||||
|
||||
:param body: Another container object.
|
||||
:param padding: The margin to be used around the body. This can be
|
||||
overridden by `padding_left`, padding_right`, `padding_top` and
|
||||
`padding_bottom`.
|
||||
:param style: A style string.
|
||||
:param char: Character to be used for filling the space around the body.
|
||||
(This is supposed to be a character with a terminal width of 1.)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
body: AnyContainer,
|
||||
padding: AnyDimension = None,
|
||||
padding_left: AnyDimension = None,
|
||||
padding_right: AnyDimension = None,
|
||||
padding_top: AnyDimension = None,
|
||||
padding_bottom: AnyDimension = None,
|
||||
width: AnyDimension = None,
|
||||
height: AnyDimension = None,
|
||||
style: str = "",
|
||||
char: Union[None, str, Callable[[], str]] = None,
|
||||
modal: bool = False,
|
||||
key_bindings: Optional[KeyBindings] = None,
|
||||
) -> None:
|
||||
|
||||
if padding is None:
|
||||
padding = D(preferred=0)
|
||||
|
||||
def get(value: AnyDimension) -> D:
|
||||
if value is None:
|
||||
value = padding
|
||||
return to_dimension(value)
|
||||
|
||||
self.padding_left = get(padding_left)
|
||||
self.padding_right = get(padding_right)
|
||||
self.padding_top = get(padding_top)
|
||||
self.padding_bottom = get(padding_bottom)
|
||||
self.body = body
|
||||
|
||||
self.container = HSplit(
|
||||
[
|
||||
Window(height=self.padding_top, char=char),
|
||||
VSplit(
|
||||
[
|
||||
Window(width=self.padding_left, char=char),
|
||||
body,
|
||||
Window(width=self.padding_right, char=char),
|
||||
]
|
||||
),
|
||||
Window(height=self.padding_bottom, char=char),
|
||||
],
|
||||
width=width,
|
||||
height=height,
|
||||
style=style,
|
||||
modal=modal,
|
||||
key_bindings=None,
|
||||
)
|
||||
|
||||
def __pt_container__(self) -> Container:
|
||||
return self.container
|
||||
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
class _DialogList(Generic[_T]):
|
||||
"""
|
||||
Common code for `RadioList` and `CheckboxList`.
|
||||
"""
|
||||
|
||||
open_character: str = ""
|
||||
close_character: str = ""
|
||||
container_style: str = ""
|
||||
default_style: str = ""
|
||||
selected_style: str = ""
|
||||
checked_style: str = ""
|
||||
multiple_selection: bool = False
|
||||
show_scrollbar: bool = True
|
||||
|
||||
def __init__(self, values: Sequence[Tuple[_T, AnyFormattedText]]) -> None:
|
||||
assert len(values) > 0
|
||||
|
||||
self.values = values
|
||||
# current_values will be used in multiple_selection,
|
||||
# current_value will be used otherwise.
|
||||
self.current_values: List[_T] = []
|
||||
self.current_value: _T = values[0][0]
|
||||
self._selected_index = 0
|
||||
|
||||
# Key bindings.
|
||||
kb = KeyBindings()
|
||||
|
||||
@kb.add("up")
|
||||
def _up(event: E) -> None:
|
||||
self._selected_index = max(0, self._selected_index - 1)
|
||||
|
||||
@kb.add("down")
|
||||
def _down(event: E) -> None:
|
||||
self._selected_index = min(len(self.values) - 1, self._selected_index + 1)
|
||||
|
||||
@kb.add("pageup")
|
||||
def _pageup(event: E) -> None:
|
||||
w = event.app.layout.current_window
|
||||
if w.render_info:
|
||||
self._selected_index = max(
|
||||
0, self._selected_index - len(w.render_info.displayed_lines)
|
||||
)
|
||||
|
||||
@kb.add("pagedown")
|
||||
def _pagedown(event: E) -> None:
|
||||
w = event.app.layout.current_window
|
||||
if w.render_info:
|
||||
self._selected_index = min(
|
||||
len(self.values) - 1,
|
||||
self._selected_index + len(w.render_info.displayed_lines),
|
||||
)
|
||||
|
||||
@kb.add("enter")
|
||||
@kb.add(" ")
|
||||
def _click(event: E) -> None:
|
||||
self._handle_enter()
|
||||
|
||||
@kb.add(Keys.Any)
|
||||
def _find(event: E) -> None:
|
||||
# We first check values after the selected value, then all values.
|
||||
values = list(self.values)
|
||||
for value in values[self._selected_index + 1 :] + values:
|
||||
text = fragment_list_to_text(to_formatted_text(value[1])).lower()
|
||||
|
||||
if text.startswith(event.data.lower()):
|
||||
self._selected_index = self.values.index(value)
|
||||
return
|
||||
|
||||
# Control and window.
|
||||
self.control = FormattedTextControl(
|
||||
self._get_text_fragments, key_bindings=kb, focusable=True
|
||||
)
|
||||
|
||||
self.window = Window(
|
||||
content=self.control,
|
||||
style=self.container_style,
|
||||
right_margins=[
|
||||
ConditionalMargin(
|
||||
margin=ScrollbarMargin(display_arrows=True),
|
||||
filter=Condition(lambda: self.show_scrollbar),
|
||||
),
|
||||
],
|
||||
dont_extend_height=True,
|
||||
)
|
||||
|
||||
def _handle_enter(self) -> None:
|
||||
if self.multiple_selection:
|
||||
val = self.values[self._selected_index][0]
|
||||
if val in self.current_values:
|
||||
self.current_values.remove(val)
|
||||
else:
|
||||
self.current_values.append(val)
|
||||
else:
|
||||
self.current_value = self.values[self._selected_index][0]
|
||||
|
||||
def _get_text_fragments(self) -> StyleAndTextTuples:
|
||||
def mouse_handler(mouse_event: MouseEvent) -> None:
|
||||
"""
|
||||
Set `_selected_index` and `current_value` according to the y
|
||||
position of the mouse click event.
|
||||
"""
|
||||
if mouse_event.event_type == MouseEventType.MOUSE_UP:
|
||||
self._selected_index = mouse_event.position.y
|
||||
self._handle_enter()
|
||||
|
||||
result: StyleAndTextTuples = []
|
||||
for i, value in enumerate(self.values):
|
||||
if self.multiple_selection:
|
||||
checked = value[0] in self.current_values
|
||||
else:
|
||||
checked = value[0] == self.current_value
|
||||
selected = i == self._selected_index
|
||||
|
||||
style = ""
|
||||
if checked:
|
||||
style += " " + self.checked_style
|
||||
if selected:
|
||||
style += " " + self.selected_style
|
||||
|
||||
result.append((style, self.open_character))
|
||||
|
||||
if selected:
|
||||
result.append(("[SetCursorPosition]", ""))
|
||||
|
||||
if checked:
|
||||
result.append((style, "*"))
|
||||
else:
|
||||
result.append((style, " "))
|
||||
|
||||
result.append((style, self.close_character))
|
||||
result.append((self.default_style, " "))
|
||||
result.extend(to_formatted_text(value[1], style=self.default_style))
|
||||
result.append(("", "\n"))
|
||||
|
||||
# Add mouse handler to all fragments.
|
||||
for i in range(len(result)):
|
||||
result[i] = (result[i][0], result[i][1], mouse_handler)
|
||||
|
||||
result.pop() # Remove last newline.
|
||||
return result
|
||||
|
||||
def __pt_container__(self) -> Container:
|
||||
return self.window
|
||||
|
||||
|
||||
class RadioList(_DialogList[_T]):
|
||||
"""
|
||||
List of radio buttons. Only one can be checked at the same time.
|
||||
|
||||
:param values: List of (value, label) tuples.
|
||||
"""
|
||||
|
||||
open_character = "("
|
||||
close_character = ")"
|
||||
container_style = "class:radio-list"
|
||||
default_style = "class:radio"
|
||||
selected_style = "class:radio-selected"
|
||||
checked_style = "class:radio-checked"
|
||||
multiple_selection = False
|
||||
|
||||
|
||||
class CheckboxList(_DialogList[_T]):
|
||||
"""
|
||||
List of checkbox buttons. Several can be checked at the same time.
|
||||
|
||||
:param values: List of (value, label) tuples.
|
||||
"""
|
||||
|
||||
open_character = "["
|
||||
close_character = "]"
|
||||
container_style = "class:checkbox-list"
|
||||
default_style = "class:checkbox"
|
||||
selected_style = "class:checkbox-selected"
|
||||
checked_style = "class:checkbox-checked"
|
||||
multiple_selection = True
|
||||
|
||||
|
||||
class Checkbox(CheckboxList[str]):
|
||||
"""Backward compatibility util: creates a 1-sized CheckboxList
|
||||
|
||||
:param text: the text
|
||||
"""
|
||||
|
||||
show_scrollbar = False
|
||||
|
||||
def __init__(self, text: AnyFormattedText = "", checked: bool = False) -> None:
|
||||
values = [("value", text)]
|
||||
CheckboxList.__init__(self, values)
|
||||
self.checked = checked
|
||||
|
||||
@property
|
||||
def checked(self) -> bool:
|
||||
return "value" in self.current_values
|
||||
|
||||
@checked.setter
|
||||
def checked(self, value: bool) -> None:
|
||||
if value:
|
||||
self.current_values = ["value"]
|
||||
else:
|
||||
self.current_values = []
|
||||
|
||||
|
||||
class VerticalLine(object):
|
||||
"""
|
||||
A simple vertical line with a width of 1.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.window = Window(
|
||||
char=Border.VERTICAL, style="class:line,vertical-line", width=1
|
||||
)
|
||||
|
||||
def __pt_container__(self) -> Container:
|
||||
return self.window
|
||||
|
||||
|
||||
class HorizontalLine:
|
||||
"""
|
||||
A simple horizontal line with a height of 1.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.window = Window(
|
||||
char=Border.HORIZONTAL, style="class:line,horizontal-line", height=1
|
||||
)
|
||||
|
||||
def __pt_container__(self) -> Container:
|
||||
return self.window
|
||||
|
||||
|
||||
class ProgressBar:
|
||||
def __init__(self) -> None:
|
||||
self._percentage = 60
|
||||
|
||||
self.label = Label("60%")
|
||||
self.container = FloatContainer(
|
||||
content=Window(height=1),
|
||||
floats=[
|
||||
# We first draw the label, then the actual progress bar. Right
|
||||
# now, this is the only way to have the colors of the progress
|
||||
# bar appear on top of the label. The problem is that our label
|
||||
# can't be part of any `Window` below.
|
||||
Float(content=self.label, top=0, bottom=0),
|
||||
Float(
|
||||
left=0,
|
||||
top=0,
|
||||
right=0,
|
||||
bottom=0,
|
||||
content=VSplit(
|
||||
[
|
||||
Window(
|
||||
style="class:progress-bar.used",
|
||||
width=lambda: D(weight=int(self._percentage)),
|
||||
),
|
||||
Window(
|
||||
style="class:progress-bar",
|
||||
width=lambda: D(weight=int(100 - self._percentage)),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@property
|
||||
def percentage(self) -> int:
|
||||
return self._percentage
|
||||
|
||||
@percentage.setter
|
||||
def percentage(self, value: int) -> None:
|
||||
self._percentage = value
|
||||
self.label.text = "{0}%".format(value)
|
||||
|
||||
def __pt_container__(self) -> Container:
|
||||
return self.container
|
106
venv/Lib/site-packages/prompt_toolkit/widgets/dialogs.py
Normal file
106
venv/Lib/site-packages/prompt_toolkit/widgets/dialogs.py
Normal file
|
@ -0,0 +1,106 @@
|
|||
"""
|
||||
Collection of reusable components for building full screen applications.
|
||||
"""
|
||||
from typing import Optional, Sequence, Union
|
||||
|
||||
from prompt_toolkit.filters import has_completions, has_focus
|
||||
from prompt_toolkit.formatted_text import AnyFormattedText
|
||||
from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous
|
||||
from prompt_toolkit.key_binding.key_bindings import KeyBindings
|
||||
from prompt_toolkit.layout.containers import (
|
||||
AnyContainer,
|
||||
DynamicContainer,
|
||||
HSplit,
|
||||
VSplit,
|
||||
)
|
||||
from prompt_toolkit.layout.dimension import AnyDimension
|
||||
from prompt_toolkit.layout.dimension import Dimension as D
|
||||
|
||||
from .base import Box, Button, Frame, Shadow
|
||||
|
||||
__all__ = [
|
||||
"Dialog",
|
||||
]
|
||||
|
||||
|
||||
class Dialog:
|
||||
"""
|
||||
Simple dialog window. This is the base for input dialogs, message dialogs
|
||||
and confirmation dialogs.
|
||||
|
||||
Changing the title and body of the dialog is possible at runtime by
|
||||
assigning to the `body` and `title` attributes of this class.
|
||||
|
||||
:param body: Child container object.
|
||||
:param title: Text to be displayed in the heading of the dialog.
|
||||
:param buttons: A list of `Button` widgets, displayed at the bottom.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
body: AnyContainer,
|
||||
title: AnyFormattedText = "",
|
||||
buttons: Optional[Sequence[Button]] = None,
|
||||
modal: bool = True,
|
||||
width: AnyDimension = None,
|
||||
with_background: bool = False,
|
||||
) -> None:
|
||||
|
||||
self.body = body
|
||||
self.title = title
|
||||
|
||||
buttons = buttons or []
|
||||
|
||||
# When a button is selected, handle left/right key bindings.
|
||||
buttons_kb = KeyBindings()
|
||||
if len(buttons) > 1:
|
||||
first_selected = has_focus(buttons[0])
|
||||
last_selected = has_focus(buttons[-1])
|
||||
|
||||
buttons_kb.add("left", filter=~first_selected)(focus_previous)
|
||||
buttons_kb.add("right", filter=~last_selected)(focus_next)
|
||||
|
||||
frame_body: AnyContainer
|
||||
if buttons:
|
||||
frame_body = HSplit(
|
||||
[
|
||||
# Add optional padding around the body.
|
||||
Box(
|
||||
body=DynamicContainer(lambda: self.body),
|
||||
padding=D(preferred=1, max=1),
|
||||
padding_bottom=0,
|
||||
),
|
||||
# The buttons.
|
||||
Box(
|
||||
body=VSplit(buttons, padding=1, key_bindings=buttons_kb),
|
||||
height=D(min=1, max=3, preferred=3),
|
||||
),
|
||||
]
|
||||
)
|
||||
else:
|
||||
frame_body = body
|
||||
|
||||
# Key bindings for whole dialog.
|
||||
kb = KeyBindings()
|
||||
kb.add("tab", filter=~has_completions)(focus_next)
|
||||
kb.add("s-tab", filter=~has_completions)(focus_previous)
|
||||
|
||||
frame = Shadow(
|
||||
body=Frame(
|
||||
title=lambda: self.title,
|
||||
body=frame_body,
|
||||
style="class:dialog.body",
|
||||
width=(None if with_background is None else width),
|
||||
key_bindings=kb,
|
||||
modal=modal,
|
||||
)
|
||||
)
|
||||
|
||||
self.container: Union[Box, Shadow]
|
||||
if with_background:
|
||||
self.container = Box(body=frame, style="class:dialog", width=width)
|
||||
else:
|
||||
self.container = frame
|
||||
|
||||
def __pt_container__(self) -> AnyContainer:
|
||||
return self.container
|
360
venv/Lib/site-packages/prompt_toolkit/widgets/menus.py
Normal file
360
venv/Lib/site-packages/prompt_toolkit/widgets/menus.py
Normal file
|
@ -0,0 +1,360 @@
|
|||
from typing import Callable, Iterable, List, Optional, Sequence, Union
|
||||
|
||||
from prompt_toolkit.application.current import get_app
|
||||
from prompt_toolkit.filters import Condition
|
||||
from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple, StyleAndTextTuples
|
||||
from prompt_toolkit.key_binding.key_bindings import KeyBindings, KeyBindingsBase
|
||||
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
|
||||
from prompt_toolkit.keys import Keys
|
||||
from prompt_toolkit.layout.containers import (
|
||||
AnyContainer,
|
||||
ConditionalContainer,
|
||||
Container,
|
||||
Float,
|
||||
FloatContainer,
|
||||
HSplit,
|
||||
Window,
|
||||
)
|
||||
from prompt_toolkit.layout.controls import FormattedTextControl
|
||||
from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
|
||||
from prompt_toolkit.utils import get_cwidth
|
||||
from prompt_toolkit.widgets import Shadow
|
||||
|
||||
from .base import Border
|
||||
|
||||
__all__ = [
|
||||
"MenuContainer",
|
||||
"MenuItem",
|
||||
]
|
||||
|
||||
E = KeyPressEvent
|
||||
|
||||
|
||||
class MenuContainer:
|
||||
"""
|
||||
:param floats: List of extra Float objects to display.
|
||||
:param menu_items: List of `MenuItem` objects.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
body: AnyContainer,
|
||||
menu_items: List["MenuItem"],
|
||||
floats: Optional[List[Float]] = None,
|
||||
key_bindings: Optional[KeyBindingsBase] = None,
|
||||
) -> None:
|
||||
|
||||
self.body = body
|
||||
self.menu_items = menu_items
|
||||
self.selected_menu = [0]
|
||||
|
||||
# Key bindings.
|
||||
kb = KeyBindings()
|
||||
|
||||
@Condition
|
||||
def in_main_menu() -> bool:
|
||||
return len(self.selected_menu) == 1
|
||||
|
||||
@Condition
|
||||
def in_sub_menu() -> bool:
|
||||
return len(self.selected_menu) > 1
|
||||
|
||||
# Navigation through the main menu.
|
||||
|
||||
@kb.add("left", filter=in_main_menu)
|
||||
def _left(event: E) -> None:
|
||||
self.selected_menu[0] = max(0, self.selected_menu[0] - 1)
|
||||
|
||||
@kb.add("right", filter=in_main_menu)
|
||||
def _right(event: E) -> None:
|
||||
self.selected_menu[0] = min(
|
||||
len(self.menu_items) - 1, self.selected_menu[0] + 1
|
||||
)
|
||||
|
||||
@kb.add("down", filter=in_main_menu)
|
||||
def _down(event: E) -> None:
|
||||
self.selected_menu.append(0)
|
||||
|
||||
@kb.add("c-c", filter=in_main_menu)
|
||||
@kb.add("c-g", filter=in_main_menu)
|
||||
def _cancel(event: E) -> None:
|
||||
" Leave menu. "
|
||||
event.app.layout.focus_last()
|
||||
|
||||
# Sub menu navigation.
|
||||
|
||||
@kb.add("left", filter=in_sub_menu)
|
||||
@kb.add("c-g", filter=in_sub_menu)
|
||||
@kb.add("c-c", filter=in_sub_menu)
|
||||
def _back(event: E) -> None:
|
||||
" Go back to parent menu. "
|
||||
if len(self.selected_menu) > 1:
|
||||
self.selected_menu.pop()
|
||||
|
||||
@kb.add("right", filter=in_sub_menu)
|
||||
def _submenu(event: E) -> None:
|
||||
" go into sub menu. "
|
||||
if self._get_menu(len(self.selected_menu) - 1).children:
|
||||
self.selected_menu.append(0)
|
||||
|
||||
# If This item does not have a sub menu. Go up in the parent menu.
|
||||
elif (
|
||||
len(self.selected_menu) == 2
|
||||
and self.selected_menu[0] < len(self.menu_items) - 1
|
||||
):
|
||||
self.selected_menu = [
|
||||
min(len(self.menu_items) - 1, self.selected_menu[0] + 1)
|
||||
]
|
||||
if self.menu_items[self.selected_menu[0]].children:
|
||||
self.selected_menu.append(0)
|
||||
|
||||
@kb.add("up", filter=in_sub_menu)
|
||||
def _up_in_submenu(event: E) -> None:
|
||||
" Select previous (enabled) menu item or return to main menu. "
|
||||
# Look for previous enabled items in this sub menu.
|
||||
menu = self._get_menu(len(self.selected_menu) - 2)
|
||||
index = self.selected_menu[-1]
|
||||
|
||||
previous_indexes = [
|
||||
i
|
||||
for i, item in enumerate(menu.children)
|
||||
if i < index and not item.disabled
|
||||
]
|
||||
|
||||
if previous_indexes:
|
||||
self.selected_menu[-1] = previous_indexes[-1]
|
||||
elif len(self.selected_menu) == 2:
|
||||
# Return to main menu.
|
||||
self.selected_menu.pop()
|
||||
|
||||
@kb.add("down", filter=in_sub_menu)
|
||||
def _down_in_submenu(event: E) -> None:
|
||||
" Select next (enabled) menu item. "
|
||||
menu = self._get_menu(len(self.selected_menu) - 2)
|
||||
index = self.selected_menu[-1]
|
||||
|
||||
next_indexes = [
|
||||
i
|
||||
for i, item in enumerate(menu.children)
|
||||
if i > index and not item.disabled
|
||||
]
|
||||
|
||||
if next_indexes:
|
||||
self.selected_menu[-1] = next_indexes[0]
|
||||
|
||||
@kb.add("enter")
|
||||
def _click(event: E) -> None:
|
||||
" Click the selected menu item. "
|
||||
item = self._get_menu(len(self.selected_menu) - 1)
|
||||
if item.handler:
|
||||
event.app.layout.focus_last()
|
||||
item.handler()
|
||||
|
||||
# Controls.
|
||||
self.control = FormattedTextControl(
|
||||
self._get_menu_fragments, key_bindings=kb, focusable=True, show_cursor=False
|
||||
)
|
||||
|
||||
self.window = Window(height=1, content=self.control, style="class:menu-bar")
|
||||
|
||||
submenu = self._submenu(0)
|
||||
submenu2 = self._submenu(1)
|
||||
submenu3 = self._submenu(2)
|
||||
|
||||
@Condition
|
||||
def has_focus() -> bool:
|
||||
return get_app().layout.current_window == self.window
|
||||
|
||||
self.container = FloatContainer(
|
||||
content=HSplit(
|
||||
[
|
||||
# The titlebar.
|
||||
self.window,
|
||||
# The 'body', like defined above.
|
||||
body,
|
||||
]
|
||||
),
|
||||
floats=[
|
||||
Float(
|
||||
xcursor=True,
|
||||
ycursor=True,
|
||||
content=ConditionalContainer(
|
||||
content=Shadow(body=submenu), filter=has_focus
|
||||
),
|
||||
),
|
||||
Float(
|
||||
attach_to_window=submenu,
|
||||
xcursor=True,
|
||||
ycursor=True,
|
||||
allow_cover_cursor=True,
|
||||
content=ConditionalContainer(
|
||||
content=Shadow(body=submenu2),
|
||||
filter=has_focus
|
||||
& Condition(lambda: len(self.selected_menu) >= 1),
|
||||
),
|
||||
),
|
||||
Float(
|
||||
attach_to_window=submenu2,
|
||||
xcursor=True,
|
||||
ycursor=True,
|
||||
allow_cover_cursor=True,
|
||||
content=ConditionalContainer(
|
||||
content=Shadow(body=submenu3),
|
||||
filter=has_focus
|
||||
& Condition(lambda: len(self.selected_menu) >= 2),
|
||||
),
|
||||
),
|
||||
# --
|
||||
]
|
||||
+ (floats or []),
|
||||
key_bindings=key_bindings,
|
||||
)
|
||||
|
||||
def _get_menu(self, level: int) -> "MenuItem":
|
||||
menu = self.menu_items[self.selected_menu[0]]
|
||||
|
||||
for i, index in enumerate(self.selected_menu[1:]):
|
||||
if i < level:
|
||||
try:
|
||||
menu = menu.children[index]
|
||||
except IndexError:
|
||||
return MenuItem("debug")
|
||||
|
||||
return menu
|
||||
|
||||
def _get_menu_fragments(self) -> StyleAndTextTuples:
|
||||
focused = get_app().layout.has_focus(self.window)
|
||||
|
||||
# This is called during the rendering. When we discover that this
|
||||
# widget doesn't have the focus anymore. Reset menu state.
|
||||
if not focused:
|
||||
self.selected_menu = [0]
|
||||
|
||||
# Generate text fragments for the main menu.
|
||||
def one_item(i: int, item: MenuItem) -> Iterable[OneStyleAndTextTuple]:
|
||||
def mouse_handler(mouse_event: MouseEvent) -> None:
|
||||
if mouse_event.event_type == MouseEventType.MOUSE_UP:
|
||||
# Toggle focus.
|
||||
app = get_app()
|
||||
if app.layout.has_focus(self.window):
|
||||
if self.selected_menu == [i]:
|
||||
app.layout.focus_last()
|
||||
else:
|
||||
app.layout.focus(self.window)
|
||||
self.selected_menu = [i]
|
||||
|
||||
yield ("class:menu-bar", " ", mouse_handler)
|
||||
if i == self.selected_menu[0] and focused:
|
||||
yield ("[SetMenuPosition]", "", mouse_handler)
|
||||
style = "class:menu-bar.selected-item"
|
||||
else:
|
||||
style = "class:menu-bar"
|
||||
yield style, item.text, mouse_handler
|
||||
|
||||
result: StyleAndTextTuples = []
|
||||
for i, item in enumerate(self.menu_items):
|
||||
result.extend(one_item(i, item))
|
||||
|
||||
return result
|
||||
|
||||
def _submenu(self, level: int = 0) -> Window:
|
||||
def get_text_fragments() -> StyleAndTextTuples:
|
||||
result: StyleAndTextTuples = []
|
||||
if level < len(self.selected_menu):
|
||||
menu = self._get_menu(level)
|
||||
if menu.children:
|
||||
result.append(("class:menu", Border.TOP_LEFT))
|
||||
result.append(("class:menu", Border.HORIZONTAL * (menu.width + 4)))
|
||||
result.append(("class:menu", Border.TOP_RIGHT))
|
||||
result.append(("", "\n"))
|
||||
try:
|
||||
selected_item = self.selected_menu[level + 1]
|
||||
except IndexError:
|
||||
selected_item = -1
|
||||
|
||||
def one_item(
|
||||
i: int, item: MenuItem
|
||||
) -> Iterable[OneStyleAndTextTuple]:
|
||||
def mouse_handler(mouse_event: MouseEvent) -> None:
|
||||
if mouse_event.event_type == MouseEventType.MOUSE_UP:
|
||||
app = get_app()
|
||||
if item.handler:
|
||||
app.layout.focus_last()
|
||||
item.handler()
|
||||
else:
|
||||
self.selected_menu = self.selected_menu[
|
||||
: level + 1
|
||||
] + [i]
|
||||
|
||||
if i == selected_item:
|
||||
yield ("[SetCursorPosition]", "")
|
||||
style = "class:menu-bar.selected-item"
|
||||
else:
|
||||
style = ""
|
||||
|
||||
yield ("class:menu", Border.VERTICAL)
|
||||
if item.text == "-":
|
||||
yield (
|
||||
style + "class:menu-border",
|
||||
"{}".format(Border.HORIZONTAL * (menu.width + 3)),
|
||||
mouse_handler,
|
||||
)
|
||||
else:
|
||||
yield (
|
||||
style,
|
||||
" {}".format(item.text).ljust(menu.width + 3),
|
||||
mouse_handler,
|
||||
)
|
||||
|
||||
if item.children:
|
||||
yield (style, ">", mouse_handler)
|
||||
else:
|
||||
yield (style, " ", mouse_handler)
|
||||
|
||||
if i == selected_item:
|
||||
yield ("[SetMenuPosition]", "")
|
||||
yield ("class:menu", Border.VERTICAL)
|
||||
|
||||
yield ("", "\n")
|
||||
|
||||
for i, item in enumerate(menu.children):
|
||||
result.extend(one_item(i, item))
|
||||
|
||||
result.append(("class:menu", Border.BOTTOM_LEFT))
|
||||
result.append(("class:menu", Border.HORIZONTAL * (menu.width + 4)))
|
||||
result.append(("class:menu", Border.BOTTOM_RIGHT))
|
||||
return result
|
||||
|
||||
return Window(FormattedTextControl(get_text_fragments), style="class:menu")
|
||||
|
||||
@property
|
||||
def floats(self) -> Optional[List[Float]]:
|
||||
return self.container.floats
|
||||
|
||||
def __pt_container__(self) -> Container:
|
||||
return self.container
|
||||
|
||||
|
||||
class MenuItem:
|
||||
def __init__(
|
||||
self,
|
||||
text: str = "",
|
||||
handler: Optional[Callable[[], None]] = None,
|
||||
children: Optional[List["MenuItem"]] = None,
|
||||
shortcut: Optional[Sequence[Union[Keys, str]]] = None,
|
||||
disabled: bool = False,
|
||||
) -> None:
|
||||
|
||||
self.text = text
|
||||
self.handler = handler
|
||||
self.children = children or []
|
||||
self.shortcut = shortcut
|
||||
self.disabled = disabled
|
||||
self.selected_item = 0
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
if self.children:
|
||||
return max(get_cwidth(c.text) for c in self.children)
|
||||
else:
|
||||
return 0
|
374
venv/Lib/site-packages/prompt_toolkit/widgets/toolbars.py
Normal file
374
venv/Lib/site-packages/prompt_toolkit/widgets/toolbars.py
Normal file
|
@ -0,0 +1,374 @@
|
|||
from typing import Optional
|
||||
|
||||
from prompt_toolkit.application.current import get_app
|
||||
from prompt_toolkit.buffer import Buffer
|
||||
from prompt_toolkit.enums import SYSTEM_BUFFER
|
||||
from prompt_toolkit.filters import (
|
||||
Condition,
|
||||
FilterOrBool,
|
||||
emacs_mode,
|
||||
has_arg,
|
||||
has_completions,
|
||||
has_focus,
|
||||
has_validation_error,
|
||||
to_filter,
|
||||
vi_mode,
|
||||
vi_navigation_mode,
|
||||
)
|
||||
from prompt_toolkit.formatted_text import (
|
||||
AnyFormattedText,
|
||||
StyleAndTextTuples,
|
||||
fragment_list_len,
|
||||
to_formatted_text,
|
||||
)
|
||||
from prompt_toolkit.key_binding.key_bindings import (
|
||||
ConditionalKeyBindings,
|
||||
KeyBindings,
|
||||
KeyBindingsBase,
|
||||
merge_key_bindings,
|
||||
)
|
||||
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
|
||||
from prompt_toolkit.key_binding.vi_state import InputMode
|
||||
from prompt_toolkit.keys import Keys
|
||||
from prompt_toolkit.layout.containers import ConditionalContainer, Container, Window
|
||||
from prompt_toolkit.layout.controls import (
|
||||
BufferControl,
|
||||
FormattedTextControl,
|
||||
SearchBufferControl,
|
||||
UIContent,
|
||||
UIControl,
|
||||
)
|
||||
from prompt_toolkit.layout.dimension import Dimension
|
||||
from prompt_toolkit.layout.processors import BeforeInput
|
||||
from prompt_toolkit.lexers import SimpleLexer
|
||||
from prompt_toolkit.search import SearchDirection
|
||||
|
||||
__all__ = [
|
||||
"ArgToolbar",
|
||||
"CompletionsToolbar",
|
||||
"FormattedTextToolbar",
|
||||
"SearchToolbar",
|
||||
"SystemToolbar",
|
||||
"ValidationToolbar",
|
||||
]
|
||||
|
||||
E = KeyPressEvent
|
||||
|
||||
|
||||
class FormattedTextToolbar(Window):
|
||||
def __init__(self, text: AnyFormattedText, style: str = "", **kw) -> None:
|
||||
# Note: The style needs to be applied to the toolbar as a whole, not
|
||||
# just the `FormattedTextControl`.
|
||||
super().__init__(
|
||||
FormattedTextControl(text, **kw),
|
||||
style=style,
|
||||
dont_extend_height=True,
|
||||
height=Dimension(min=1),
|
||||
)
|
||||
|
||||
|
||||
class SystemToolbar:
|
||||
"""
|
||||
Toolbar for a system prompt.
|
||||
|
||||
:param prompt: Prompt to be displayed to the user.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
prompt: AnyFormattedText = "Shell command: ",
|
||||
enable_global_bindings: FilterOrBool = True,
|
||||
) -> None:
|
||||
|
||||
self.prompt = prompt
|
||||
self.enable_global_bindings = to_filter(enable_global_bindings)
|
||||
|
||||
self.system_buffer = Buffer(name=SYSTEM_BUFFER)
|
||||
|
||||
self._bindings = self._build_key_bindings()
|
||||
|
||||
self.buffer_control = BufferControl(
|
||||
buffer=self.system_buffer,
|
||||
lexer=SimpleLexer(style="class:system-toolbar.text"),
|
||||
input_processors=[
|
||||
BeforeInput(lambda: self.prompt, style="class:system-toolbar")
|
||||
],
|
||||
key_bindings=self._bindings,
|
||||
)
|
||||
|
||||
self.window = Window(
|
||||
self.buffer_control, height=1, style="class:system-toolbar"
|
||||
)
|
||||
|
||||
self.container = ConditionalContainer(
|
||||
content=self.window, filter=has_focus(self.system_buffer)
|
||||
)
|
||||
|
||||
def _get_display_before_text(self) -> StyleAndTextTuples:
|
||||
return [
|
||||
("class:system-toolbar", "Shell command: "),
|
||||
("class:system-toolbar.text", self.system_buffer.text),
|
||||
("", "\n"),
|
||||
]
|
||||
|
||||
def _build_key_bindings(self) -> KeyBindingsBase:
|
||||
focused = has_focus(self.system_buffer)
|
||||
|
||||
# Emacs
|
||||
emacs_bindings = KeyBindings()
|
||||
handle = emacs_bindings.add
|
||||
|
||||
@handle("escape", filter=focused)
|
||||
@handle("c-g", filter=focused)
|
||||
@handle("c-c", filter=focused)
|
||||
def _cancel(event: E) -> None:
|
||||
" Hide system prompt. "
|
||||
self.system_buffer.reset()
|
||||
event.app.layout.focus_last()
|
||||
|
||||
@handle("enter", filter=focused)
|
||||
def _accept(event: E) -> None:
|
||||
" Run system command. "
|
||||
event.app.run_system_command(
|
||||
self.system_buffer.text,
|
||||
display_before_text=self._get_display_before_text(),
|
||||
)
|
||||
self.system_buffer.reset(append_to_history=True)
|
||||
event.app.layout.focus_last()
|
||||
|
||||
# Vi.
|
||||
vi_bindings = KeyBindings()
|
||||
handle = vi_bindings.add
|
||||
|
||||
@handle("escape", filter=focused)
|
||||
@handle("c-c", filter=focused)
|
||||
def _cancel_vi(event: E) -> None:
|
||||
" Hide system prompt. "
|
||||
event.app.vi_state.input_mode = InputMode.NAVIGATION
|
||||
self.system_buffer.reset()
|
||||
event.app.layout.focus_last()
|
||||
|
||||
@handle("enter", filter=focused)
|
||||
def _accept_vi(event: E) -> None:
|
||||
" Run system command. "
|
||||
event.app.vi_state.input_mode = InputMode.NAVIGATION
|
||||
event.app.run_system_command(
|
||||
self.system_buffer.text,
|
||||
display_before_text=self._get_display_before_text(),
|
||||
)
|
||||
self.system_buffer.reset(append_to_history=True)
|
||||
event.app.layout.focus_last()
|
||||
|
||||
# Global bindings. (Listen to these bindings, even when this widget is
|
||||
# not focussed.)
|
||||
global_bindings = KeyBindings()
|
||||
handle = global_bindings.add
|
||||
|
||||
@handle(Keys.Escape, "!", filter=~focused & emacs_mode, is_global=True)
|
||||
def _focus_me(event: E) -> None:
|
||||
" M-'!' will focus this user control. "
|
||||
event.app.layout.focus(self.window)
|
||||
|
||||
@handle("!", filter=~focused & vi_mode & vi_navigation_mode, is_global=True)
|
||||
def _focus_me_vi(event: E) -> None:
|
||||
" Focus. "
|
||||
event.app.vi_state.input_mode = InputMode.INSERT
|
||||
event.app.layout.focus(self.window)
|
||||
|
||||
return merge_key_bindings(
|
||||
[
|
||||
ConditionalKeyBindings(emacs_bindings, emacs_mode),
|
||||
ConditionalKeyBindings(vi_bindings, vi_mode),
|
||||
ConditionalKeyBindings(global_bindings, self.enable_global_bindings),
|
||||
]
|
||||
)
|
||||
|
||||
def __pt_container__(self) -> Container:
|
||||
return self.container
|
||||
|
||||
|
||||
class ArgToolbar:
|
||||
def __init__(self) -> None:
|
||||
def get_formatted_text() -> StyleAndTextTuples:
|
||||
arg = get_app().key_processor.arg or ""
|
||||
if arg == "-":
|
||||
arg = "-1"
|
||||
|
||||
return [
|
||||
("class:arg-toolbar", "Repeat: "),
|
||||
("class:arg-toolbar.text", arg),
|
||||
]
|
||||
|
||||
self.window = Window(FormattedTextControl(get_formatted_text), height=1)
|
||||
|
||||
self.container = ConditionalContainer(content=self.window, filter=has_arg)
|
||||
|
||||
def __pt_container__(self) -> Container:
|
||||
return self.container
|
||||
|
||||
|
||||
class SearchToolbar:
|
||||
"""
|
||||
:param vi_mode: Display '/' and '?' instead of I-search.
|
||||
:param ignore_case: Search case insensitive.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
search_buffer: Optional[Buffer] = None,
|
||||
vi_mode: bool = False,
|
||||
text_if_not_searching: AnyFormattedText = "",
|
||||
forward_search_prompt: AnyFormattedText = "I-search: ",
|
||||
backward_search_prompt: AnyFormattedText = "I-search backward: ",
|
||||
ignore_case: FilterOrBool = False,
|
||||
) -> None:
|
||||
|
||||
if search_buffer is None:
|
||||
search_buffer = Buffer()
|
||||
|
||||
@Condition
|
||||
def is_searching() -> bool:
|
||||
return self.control in get_app().layout.search_links
|
||||
|
||||
def get_before_input() -> AnyFormattedText:
|
||||
if not is_searching():
|
||||
return text_if_not_searching
|
||||
elif (
|
||||
self.control.searcher_search_state.direction == SearchDirection.BACKWARD
|
||||
):
|
||||
return "?" if vi_mode else backward_search_prompt
|
||||
else:
|
||||
return "/" if vi_mode else forward_search_prompt
|
||||
|
||||
self.search_buffer = search_buffer
|
||||
|
||||
self.control = SearchBufferControl(
|
||||
buffer=search_buffer,
|
||||
input_processors=[
|
||||
BeforeInput(get_before_input, style="class:search-toolbar.prompt")
|
||||
],
|
||||
lexer=SimpleLexer(style="class:search-toolbar.text"),
|
||||
ignore_case=ignore_case,
|
||||
)
|
||||
|
||||
self.container = ConditionalContainer(
|
||||
content=Window(self.control, height=1, style="class:search-toolbar"),
|
||||
filter=is_searching,
|
||||
)
|
||||
|
||||
def __pt_container__(self) -> Container:
|
||||
return self.container
|
||||
|
||||
|
||||
class _CompletionsToolbarControl(UIControl):
|
||||
def create_content(self, width: int, height: int) -> UIContent:
|
||||
all_fragments: StyleAndTextTuples = []
|
||||
|
||||
complete_state = get_app().current_buffer.complete_state
|
||||
if complete_state:
|
||||
completions = complete_state.completions
|
||||
index = complete_state.complete_index # Can be None!
|
||||
|
||||
# Width of the completions without the left/right arrows in the margins.
|
||||
content_width = width - 6
|
||||
|
||||
# Booleans indicating whether we stripped from the left/right
|
||||
cut_left = False
|
||||
cut_right = False
|
||||
|
||||
# Create Menu content.
|
||||
fragments: StyleAndTextTuples = []
|
||||
|
||||
for i, c in enumerate(completions):
|
||||
# When there is no more place for the next completion
|
||||
if fragment_list_len(fragments) + len(c.display_text) >= content_width:
|
||||
# If the current one was not yet displayed, page to the next sequence.
|
||||
if i <= (index or 0):
|
||||
fragments = []
|
||||
cut_left = True
|
||||
# If the current one is visible, stop here.
|
||||
else:
|
||||
cut_right = True
|
||||
break
|
||||
|
||||
fragments.extend(
|
||||
to_formatted_text(
|
||||
c.display_text,
|
||||
style=(
|
||||
"class:completion-toolbar.completion.current"
|
||||
if i == index
|
||||
else "class:completion-toolbar.completion"
|
||||
),
|
||||
)
|
||||
)
|
||||
fragments.append(("", " "))
|
||||
|
||||
# Extend/strip until the content width.
|
||||
fragments.append(("", " " * (content_width - fragment_list_len(fragments))))
|
||||
fragments = fragments[:content_width]
|
||||
|
||||
# Return fragments
|
||||
all_fragments.append(("", " "))
|
||||
all_fragments.append(
|
||||
("class:completion-toolbar.arrow", "<" if cut_left else " ")
|
||||
)
|
||||
all_fragments.append(("", " "))
|
||||
|
||||
all_fragments.extend(fragments)
|
||||
|
||||
all_fragments.append(("", " "))
|
||||
all_fragments.append(
|
||||
("class:completion-toolbar.arrow", ">" if cut_right else " ")
|
||||
)
|
||||
all_fragments.append(("", " "))
|
||||
|
||||
def get_line(i: int) -> StyleAndTextTuples:
|
||||
return all_fragments
|
||||
|
||||
return UIContent(get_line=get_line, line_count=1)
|
||||
|
||||
|
||||
class CompletionsToolbar:
|
||||
def __init__(self) -> None:
|
||||
self.container = ConditionalContainer(
|
||||
content=Window(
|
||||
_CompletionsToolbarControl(), height=1, style="class:completion-toolbar"
|
||||
),
|
||||
filter=has_completions,
|
||||
)
|
||||
|
||||
def __pt_container__(self) -> Container:
|
||||
return self.container
|
||||
|
||||
|
||||
class ValidationToolbar:
|
||||
def __init__(self, show_position: bool = False) -> None:
|
||||
def get_formatted_text() -> StyleAndTextTuples:
|
||||
buff = get_app().current_buffer
|
||||
|
||||
if buff.validation_error:
|
||||
row, column = buff.document.translate_index_to_position(
|
||||
buff.validation_error.cursor_position
|
||||
)
|
||||
|
||||
if show_position:
|
||||
text = "%s (line=%s column=%s)" % (
|
||||
buff.validation_error.message,
|
||||
row + 1,
|
||||
column + 1,
|
||||
)
|
||||
else:
|
||||
text = buff.validation_error.message
|
||||
|
||||
return [("class:validation-toolbar", text)]
|
||||
else:
|
||||
return []
|
||||
|
||||
self.control = FormattedTextControl(get_formatted_text)
|
||||
|
||||
self.container = ConditionalContainer(
|
||||
content=Window(self.control, height=1), filter=has_validation_error
|
||||
)
|
||||
|
||||
def __pt_container__(self) -> Container:
|
||||
return self.container
|
Loading…
Add table
Add a link
Reference in a new issue