5
5
6
6
from __future__ import annotations
7
7
8
+ import re
9
+ import sys
8
10
from abc import ABCMeta , abstractmethod
9
11
from enum import Enum
10
12
from functools import partial
11
- from typing import TYPE_CHECKING , Callable , Sequence , Union , cast
13
+ from typing import TYPE_CHECKING , Callable , Sequence , Tuple , Union , cast
12
14
13
15
from prompt_toolkit .application .current import get_app
14
16
from prompt_toolkit .cache import SimpleCache
23
25
AnyFormattedText ,
24
26
StyleAndTextTuples ,
25
27
to_formatted_text ,
28
+ to_plain_text ,
26
29
)
27
30
from prompt_toolkit .formatted_text .utils import (
31
+ fragment_list_len ,
28
32
fragment_list_to_text ,
29
33
fragment_list_width ,
30
34
)
38
42
GetLinePrefixCallable ,
39
43
UIContent ,
40
44
UIControl ,
45
+ WrapFinderCallable ,
41
46
)
42
47
from .dimension import (
43
48
AnyDimension ,
@@ -1310,7 +1315,10 @@ def get_height_for_line(self, lineno: int) -> int:
1310
1315
"""
1311
1316
if self .wrap_lines :
1312
1317
return self .ui_content .get_height_for_line (
1313
- lineno , self .window_width , self .window .get_line_prefix
1318
+ lineno ,
1319
+ self .window_width ,
1320
+ self .window .get_line_prefix ,
1321
+ self .window .wrap_finder ,
1314
1322
)
1315
1323
else :
1316
1324
return 1
@@ -1442,6 +1450,10 @@ class Window(Container):
1442
1450
wrap_count and returns formatted text. This can be used for
1443
1451
implementation of line continuations, things like Vim "breakindent" and
1444
1452
so on.
1453
+ :param wrap_finder: None or a callable that returns how to wrap a line.
1454
+ It takes a line number, a start and an end position (ints) and returns
1455
+ the the wrap position, a number of characters to be skipped (if any),
1456
+ and formatted text for the continuation marker.
1445
1457
"""
1446
1458
1447
1459
def __init__ (
@@ -1459,6 +1471,7 @@ def __init__(
1459
1471
scroll_offsets : ScrollOffsets | None = None ,
1460
1472
allow_scroll_beyond_bottom : FilterOrBool = False ,
1461
1473
wrap_lines : FilterOrBool = False ,
1474
+ word_wrap : FilterOrBool = False ,
1462
1475
get_vertical_scroll : Callable [[Window ], int ] | None = None ,
1463
1476
get_horizontal_scroll : Callable [[Window ], int ] | None = None ,
1464
1477
always_hide_cursor : FilterOrBool = False ,
@@ -1471,10 +1484,12 @@ def __init__(
1471
1484
style : str | Callable [[], str ] = "" ,
1472
1485
char : None | str | Callable [[], str ] = None ,
1473
1486
get_line_prefix : GetLinePrefixCallable | None = None ,
1487
+ wrap_finder : WrapFinderCallable | None = None ,
1474
1488
) -> None :
1475
1489
self .allow_scroll_beyond_bottom = to_filter (allow_scroll_beyond_bottom )
1476
1490
self .always_hide_cursor = to_filter (always_hide_cursor )
1477
1491
self .wrap_lines = to_filter (wrap_lines )
1492
+ self .word_wrap = to_filter (word_wrap )
1478
1493
self .cursorline = to_filter (cursorline )
1479
1494
self .cursorcolumn = to_filter (cursorcolumn )
1480
1495
@@ -1493,6 +1508,7 @@ def __init__(
1493
1508
self .style = style
1494
1509
self .char = char
1495
1510
self .get_line_prefix = get_line_prefix
1511
+ self .wrap_finder = wrap_finder
1496
1512
1497
1513
self .width = width
1498
1514
self .height = height
@@ -1601,6 +1617,7 @@ def preferred_content_height() -> int | None:
1601
1617
max_available_height ,
1602
1618
wrap_lines ,
1603
1619
self .get_line_prefix ,
1620
+ self .wrap_finder ,
1604
1621
)
1605
1622
1606
1623
return self ._merge_dimensions (
@@ -1766,6 +1783,9 @@ def _write_to_screen_at_index(
1766
1783
self ._scroll (
1767
1784
ui_content , write_position .width - total_margin_width , write_position .height
1768
1785
)
1786
+ wrap_finder = self .wrap_finder or (
1787
+ self ._whitespace_wrap_finder (ui_content ) if self .word_wrap () else None
1788
+ )
1769
1789
1770
1790
# Erase background and fill with `char`.
1771
1791
self ._fill_bg (screen , write_position , erase_bg )
@@ -1789,7 +1809,7 @@ def _write_to_screen_at_index(
1789
1809
has_focus = get_app ().layout .current_control == self .content ,
1790
1810
align = align ,
1791
1811
get_line_prefix = self .get_line_prefix ,
1792
- wrap_finder = self . _whitespace_wrap_finder ( ui_content ) ,
1812
+ wrap_finder = wrap_finder ,
1793
1813
)
1794
1814
1795
1815
# Remember render info. (Set before generating the margins. They need this.)
@@ -1924,26 +1944,30 @@ def render_margin(m: Margin, width: int) -> UIContent:
1924
1944
def _whitespace_wrap_finder (
1925
1945
self ,
1926
1946
ui_content : UIContent ,
1927
- sep : str | re .Pattern = r'\s' ,
1928
- split : str = ' remove' ,
1947
+ sep : str | re .Pattern = r"\s" ,
1948
+ split : str = " remove" ,
1929
1949
continuation : StyleAndTextTuples = [],
1930
- ) -> Callable [[ int , int , int ], tuple [ int , int , StyleAndTextTuples ]] :
1931
- """ Returns a function that defines where to break """
1950
+ ) -> WrapFinderCallable :
1951
+ """Returns a function that defines where to break"""
1932
1952
sep_re = sep if isinstance (sep , re .Pattern ) else re .compile (sep )
1933
1953
if sep_re .groups :
1934
- raise ValueError (f'Pattern { sep_re .pattern !r} has capture group – use non-capturing groups instead' )
1935
- elif split == 'after' :
1936
- sep_re = re .compile ('(?<={sep_re.pattern})()' )
1937
- elif split == 'before' :
1938
- sep_re = re .compile ('(?={sep_re.pattern})()' )
1939
- elif split == 'remove' :
1940
- sep_re = re .compile (f'({ sep_re .pattern } )' )
1954
+ raise ValueError (
1955
+ f"Pattern { sep_re .pattern !r} has capture group – use non-capturing groups instead"
1956
+ )
1957
+ elif split == "after" :
1958
+ sep_re = re .compile ("(?<={sep_re.pattern})()" )
1959
+ elif split == "before" :
1960
+ sep_re = re .compile ("(?={sep_re.pattern})()" )
1961
+ elif split == "remove" :
1962
+ sep_re = re .compile (f"({ sep_re .pattern } )" )
1941
1963
else :
1942
- raise ValueError (f' Unrecognized value of split paramter: { split !r} ' )
1964
+ raise ValueError (f" Unrecognized value of split paramter: { split !r} " )
1943
1965
1944
- cont_width = fragment_list_width (text )
1966
+ cont_width = fragment_list_width (continuation )
1945
1967
1946
- def wrap_finder (lineno : int , start : int , end : int ) -> tuple [int , int , StyleAndTextTuples ]:
1968
+ def wrap_finder (
1969
+ lineno : int , start : int , end : int
1970
+ ) -> Tuple [int , int , AnyFormattedText ]:
1947
1971
line = explode_text_fragments (ui_content .get_line (lineno ))
1948
1972
cont_reserved = 0
1949
1973
while cont_reserved < cont_width :
@@ -1961,7 +1985,6 @@ def wrap_finder(lineno: int, start: int, end: int) -> tuple[int, int, StyleAndTe
1961
1985
1962
1986
return wrap_finder
1963
1987
1964
-
1965
1988
def _copy_body (
1966
1989
self ,
1967
1990
ui_content : UIContent ,
@@ -1978,7 +2001,8 @@ def _copy_body(
1978
2001
has_focus : bool = False ,
1979
2002
align : WindowAlign = WindowAlign .LEFT ,
1980
2003
get_line_prefix : Callable [[int , int ], AnyFormattedText ] | None = None ,
1981
- wrap_finder : Callable [[int , int , int ], tuple [int , int , AnyFormattedText ] | None ] | None = None ,
2004
+ wrap_finder : Callable [[int , int , int ], Tuple [int , int , AnyFormattedText ] | None ]
2005
+ | None = None ,
1982
2006
) -> tuple [dict [int , tuple [int , int ]], dict [tuple [int , int ], tuple [int , int ]]]:
1983
2007
"""
1984
2008
Copy the UIContent into the output screen.
@@ -2007,10 +2031,9 @@ def find_next_wrap(remaining_width, is_input, lineno, fragment=0, char_pos=0):
2007
2031
line = ui_content .get_line (lineno )
2008
2032
style0 , text0 , * more = line [fragment ]
2009
2033
fragment_pos = char_pos - fragment_list_len (line [:fragment ])
2010
- line_part = [(style0 , text0 [char_pos :], * more ), * line [fragment + 1 :]]
2034
+ line_part = [(style0 , text0 [char_pos :], * more ), * line [fragment + 1 :]]
2011
2035
line_width = [fragment_list_width ([fragment ]) for fragment in line_part ]
2012
2036
2013
- orig_remaining_width = remaining_width
2014
2037
if sum (line_width ) <= remaining_width :
2015
2038
return sys .maxsize , 0 , []
2016
2039
@@ -2083,7 +2106,10 @@ def copy_line(
2083
2106
x += width - line_width
2084
2107
2085
2108
new_buffer_row = new_buffer [y + ypos ]
2086
- wrap_start , wrap_replaced , continuation = find_next_wrap (width - x , is_input , lineno )
2109
+ wrap_start , wrap_replaced , continuation = find_next_wrap (
2110
+ width - x , is_input , lineno
2111
+ )
2112
+ continuation = to_formatted_text (continuation )
2087
2113
2088
2114
col = 0
2089
2115
wrap_count = 0
@@ -2106,7 +2132,7 @@ def copy_line(
2106
2132
if wrap_lines and char_count == wrap_start :
2107
2133
skipped_width = sum (
2108
2134
_CHAR_CACHE [char , style ].width
2109
- for char in text [wrap_start - text_start :][:wrap_replaced ]
2135
+ for char in text [wrap_start - text_start :][:wrap_replaced ]
2110
2136
)
2111
2137
col += wrap_replaced
2112
2138
visible_line_to_row_col [y + 1 ] = (
@@ -2135,8 +2161,13 @@ def copy_line(
2135
2161
2136
2162
new_buffer_row = new_buffer [y + ypos ]
2137
2163
wrap_start , wrap_replaced , continuation = find_next_wrap (
2138
- width - x , is_input , lineno , fragment_count , wrap_start + wrap_replaced
2164
+ width - x ,
2165
+ is_input ,
2166
+ lineno ,
2167
+ fragment_count ,
2168
+ wrap_start + wrap_replaced ,
2139
2169
)
2170
+ continuation = to_formatted_text (continuation )
2140
2171
2141
2172
if y >= write_position .height :
2142
2173
return x , y # Break out of all for loops.
@@ -2435,7 +2466,9 @@ def _scroll_when_linewrapping(
2435
2466
self .horizontal_scroll = 0
2436
2467
2437
2468
def get_line_height (lineno : int ) -> int :
2438
- return ui_content .get_height_for_line (lineno , width , self .get_line_prefix )
2469
+ return ui_content .get_height_for_line (
2470
+ lineno , width , self .get_line_prefix , self .wrap_finder
2471
+ )
2439
2472
2440
2473
# When there is no space, reset `vertical_scroll_2` to zero and abort.
2441
2474
# This can happen if the margin is bigger than the window width.
@@ -2460,6 +2493,7 @@ def get_line_height(lineno: int) -> int:
2460
2493
ui_content .cursor_position .y ,
2461
2494
width ,
2462
2495
self .get_line_prefix ,
2496
+ self .wrap_finder ,
2463
2497
slice_stop = ui_content .cursor_position .x ,
2464
2498
)
2465
2499
0 commit comments