Spaces:
Sleeping
Sleeping
File size: 10,375 Bytes
2d876d1 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
"""
Margin implementations for a :class:`~prompt_toolkit.layout.containers.Window`.
"""
from __future__ import annotations
from abc import ABCMeta, abstractmethod
from typing import TYPE_CHECKING, Callable
from prompt_toolkit.filters import FilterOrBool, to_filter
from prompt_toolkit.formatted_text import (
StyleAndTextTuples,
fragment_list_to_text,
to_formatted_text,
)
from prompt_toolkit.utils import get_cwidth
from .controls import UIContent
if TYPE_CHECKING:
from .containers import WindowRenderInfo
__all__ = [
"Margin",
"NumberedMargin",
"ScrollbarMargin",
"ConditionalMargin",
"PromptMargin",
]
class Margin(metaclass=ABCMeta):
"""
Base interface for a margin.
"""
@abstractmethod
def get_width(self, get_ui_content: Callable[[], UIContent]) -> int:
"""
Return the width that this margin is going to consume.
:param get_ui_content: Callable that asks the user control to create
a :class:`.UIContent` instance. This can be used for instance to
obtain the number of lines.
"""
return 0
@abstractmethod
def create_margin(
self, window_render_info: WindowRenderInfo, width: int, height: int
) -> StyleAndTextTuples:
"""
Creates a margin.
This should return a list of (style_str, text) tuples.
:param window_render_info:
:class:`~prompt_toolkit.layout.containers.WindowRenderInfo`
instance, generated after rendering and copying the visible part of
the :class:`~prompt_toolkit.layout.controls.UIControl` into the
:class:`~prompt_toolkit.layout.containers.Window`.
:param width: The width that's available for this margin. (As reported
by :meth:`.get_width`.)
:param height: The height that's available for this margin. (The height
of the :class:`~prompt_toolkit.layout.containers.Window`.)
"""
return []
class NumberedMargin(Margin):
"""
Margin that displays the line numbers.
:param relative: Number relative to the cursor position. Similar to the Vi
'relativenumber' option.
:param display_tildes: Display tildes after the end of the document, just
like Vi does.
"""
def __init__(
self, relative: FilterOrBool = False, display_tildes: FilterOrBool = False
) -> None:
self.relative = to_filter(relative)
self.display_tildes = to_filter(display_tildes)
def get_width(self, get_ui_content: Callable[[], UIContent]) -> int:
line_count = get_ui_content().line_count
return max(3, len(f"{line_count}") + 1)
def create_margin(
self, window_render_info: WindowRenderInfo, width: int, height: int
) -> StyleAndTextTuples:
relative = self.relative()
style = "class:line-number"
style_current = "class:line-number.current"
# Get current line number.
current_lineno = window_render_info.ui_content.cursor_position.y
# Construct margin.
result: StyleAndTextTuples = []
last_lineno = None
for y, lineno in enumerate(window_render_info.displayed_lines):
# Only display line number if this line is not a continuation of the previous line.
if lineno != last_lineno:
if lineno is None:
pass
elif lineno == current_lineno:
# Current line.
if relative:
# Left align current number in relative mode.
result.append((style_current, "%i" % (lineno + 1)))
else:
result.append(
(style_current, ("%i " % (lineno + 1)).rjust(width))
)
else:
# Other lines.
if relative:
lineno = abs(lineno - current_lineno) - 1
result.append((style, ("%i " % (lineno + 1)).rjust(width)))
last_lineno = lineno
result.append(("", "\n"))
# Fill with tildes.
if self.display_tildes():
while y < window_render_info.window_height:
result.append(("class:tilde", "~\n"))
y += 1
return result
class ConditionalMargin(Margin):
"""
Wrapper around other :class:`.Margin` classes to show/hide them.
"""
def __init__(self, margin: Margin, filter: FilterOrBool) -> None:
self.margin = margin
self.filter = to_filter(filter)
def get_width(self, get_ui_content: Callable[[], UIContent]) -> int:
if self.filter():
return self.margin.get_width(get_ui_content)
else:
return 0
def create_margin(
self, window_render_info: WindowRenderInfo, width: int, height: int
) -> StyleAndTextTuples:
if width and self.filter():
return self.margin.create_margin(window_render_info, width, height)
else:
return []
class ScrollbarMargin(Margin):
"""
Margin displaying a scrollbar.
:param display_arrows: Display scroll up/down arrows.
"""
def __init__(
self,
display_arrows: FilterOrBool = False,
up_arrow_symbol: str = "^",
down_arrow_symbol: str = "v",
) -> None:
self.display_arrows = to_filter(display_arrows)
self.up_arrow_symbol = up_arrow_symbol
self.down_arrow_symbol = down_arrow_symbol
def get_width(self, get_ui_content: Callable[[], UIContent]) -> int:
return 1
def create_margin(
self, window_render_info: WindowRenderInfo, width: int, height: int
) -> StyleAndTextTuples:
content_height = window_render_info.content_height
window_height = window_render_info.window_height
display_arrows = self.display_arrows()
if display_arrows:
window_height -= 2
try:
fraction_visible = len(window_render_info.displayed_lines) / float(
content_height
)
fraction_above = window_render_info.vertical_scroll / float(content_height)
scrollbar_height = int(
min(window_height, max(1, window_height * fraction_visible))
)
scrollbar_top = int(window_height * fraction_above)
except ZeroDivisionError:
return []
else:
def is_scroll_button(row: int) -> bool:
"True if we should display a button on this row."
return scrollbar_top <= row <= scrollbar_top + scrollbar_height
# Up arrow.
result: StyleAndTextTuples = []
if display_arrows:
result.extend(
[
("class:scrollbar.arrow", self.up_arrow_symbol),
("class:scrollbar", "\n"),
]
)
# Scrollbar body.
scrollbar_background = "class:scrollbar.background"
scrollbar_background_start = "class:scrollbar.background,scrollbar.start"
scrollbar_button = "class:scrollbar.button"
scrollbar_button_end = "class:scrollbar.button,scrollbar.end"
for i in range(window_height):
if is_scroll_button(i):
if not is_scroll_button(i + 1):
# Give the last cell a different style, because we
# want to underline this.
result.append((scrollbar_button_end, " "))
else:
result.append((scrollbar_button, " "))
else:
if is_scroll_button(i + 1):
result.append((scrollbar_background_start, " "))
else:
result.append((scrollbar_background, " "))
result.append(("", "\n"))
# Down arrow
if display_arrows:
result.append(("class:scrollbar.arrow", self.down_arrow_symbol))
return result
class PromptMargin(Margin):
"""
[Deprecated]
Create margin that displays a prompt.
This can display one prompt at the first line, and a continuation prompt
(e.g, just dots) on all the following lines.
This `PromptMargin` implementation has been largely superseded in favor of
the `get_line_prefix` attribute of `Window`. The reason is that a margin is
always a fixed width, while `get_line_prefix` can return a variable width
prefix in front of every line, making it more powerful, especially for line
continuations.
:param get_prompt: Callable returns formatted text or a list of
`(style_str, type)` tuples to be shown as the prompt at the first line.
:param get_continuation: Callable that takes three inputs. The width (int),
line_number (int), and is_soft_wrap (bool). It should return formatted
text or a list of `(style_str, type)` tuples for the next lines of the
input.
"""
def __init__(
self,
get_prompt: Callable[[], StyleAndTextTuples],
get_continuation: None
| (Callable[[int, int, bool], StyleAndTextTuples]) = None,
) -> None:
self.get_prompt = get_prompt
self.get_continuation = get_continuation
def get_width(self, get_ui_content: Callable[[], UIContent]) -> int:
"Width to report to the `Window`."
# Take the width from the first line.
text = fragment_list_to_text(self.get_prompt())
return get_cwidth(text)
def create_margin(
self, window_render_info: WindowRenderInfo, width: int, height: int
) -> StyleAndTextTuples:
get_continuation = self.get_continuation
result: StyleAndTextTuples = []
# First line.
result.extend(to_formatted_text(self.get_prompt()))
# Next lines.
if get_continuation:
last_y = None
for y in window_render_info.displayed_lines[1:]:
result.append(("", "\n"))
result.extend(
to_formatted_text(get_continuation(width, y, y == last_y))
)
last_y = y
return result
|