Skip to content

Commit 925c9cd

Browse files
ceccopierangiolieugenioCopilot
andauthored
chore(textedit): add fastwrap (#615)
Co-authored-by: Copilot <copilot@github.com>
1 parent f8f1b58 commit 925c9cd

19 files changed

Lines changed: 1289 additions & 141 deletions

demo/showcase/_showcasehelper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def getUtfColoredWords(n):
7171
www = [random.choice(utfwords) for _ in range(n)]
7272
return ttk.TTkString(" ".join(www), randColor())
7373
def getUtfColoredSentence(a,b):
74-
return ttk.TTkString(" ").join([getUtfColoredWords(random.randint(1,4)) for _ in range(0,random.randint(a,b))])
74+
return ttk.TTkString(" ").join([getUtfColoredWords(random.randint(1,8)) for _ in range(0,random.randint(a,b))])
7575

7676
def main():
7777
root = ttk.TTk()

docs/MDNotes/internals/textedit.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Elements
2+
3+
```
4+
TTkTextEdit <- TTkTextEditView <- TTkTextWrap <- TTkTextDocument
5+
\ /
6+
\---- TTkTextCursor <-------/
7+
```

libs/pyTermTk/TermTk/TTkCore/constant.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,8 +371,8 @@ class WrapEngine(Enum):
371371
'''No Wrapping'''
372372
FullWrap = auto()
373373
'''Full document Wrap at any change, Ideal for small documents (~300 Lines) [Precise] (Slow)'''
374-
# FastWrap = auto()
375-
# '''Chunk based document Wrap, Default wrap [Fast] (Scrolling position is estimated)'''
374+
FastWrap = auto()
375+
'''Chunk based document Wrap, Default wrap [Fast] (Scrolling position is estimated)'''
376376
VimWrap = auto()
377377
'''Wrap applied only in the displayed area, [Fastest] (the scrolling is snap to the beginning of the lines)'''
378378

libs/pyTermTk/TermTk/TTkGui/TTkTextWrap/text_wrap.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@
2929
from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot
3030
from TermTk.TTkGui.textdocument import TTkTextDocument
3131

32-
from .text_wrap_data import _RetScreenRows, _WrapState, _ReWrapData
32+
from .text_wrap_data import _RetScreenPositions, _RetScreenRows, _WrapState, _ReWrapData
3333
from .text_wrap_engine import _WrapEngine_Interface
3434
from .text_wrap_engine_no_wrap import _WrapEngine_NoWrap
3535
from .text_wrap_engine_vim_wrap import _WrapEngine_VimWrap
3636
from .text_wrap_engine_fast_wrap import _WrapEngine_FastWrap
3737
from .text_wrap_engine_full_wrap import _WrapEngine_FullWrap
3838

3939
_wrapEngines = {
40-
# TTkK.WrapEngine.FastWrap : _WrapEngine_FastWrap,
40+
TTkK.WrapEngine.FastWrap : _WrapEngine_FastWrap,
4141
TTkK.WrapEngine.FullWrap : _WrapEngine_FullWrap,
4242
TTkK.WrapEngine.VimWrap : _WrapEngine_VimWrap,
4343
TTkK.WrapEngine.NoWrap : _WrapEngine_NoWrap,
@@ -101,7 +101,8 @@ def setEngine(self, engine:TTkK.WrapEngine, width:Optional[int]=None) -> None:
101101
102102
:param engine: engine selector from :py:class:`TTkK.WrapEngine`.
103103
:type engine: :py:class:`TTkK.WrapEngine`
104-
:param width: optional wrap width applied before switching engines.
104+
:param width: optional wrap width applied before switching engines
105+
when the engine type actually changes.
105106
:type width: Optional[int]
106107
'''
107108
engine_class = _wrapEngines.get(engine, _WrapEngine_NoWrap)
@@ -171,10 +172,10 @@ def screenRows(self, y:int, h:int) -> _RetScreenRows:
171172
:type h: int
172173
173174
:return: wrapped row slices.
174-
:rtype: List[:py:class:`_WrapLine`]
175+
:rtype: :py:class:`_RetScreenRows`
175176
'''
176177
if h <= 0:
177-
return _RetScreenRows(y=y, rows=[])
178+
return _RetScreenRows(rows=[])
178179
return self._wrapEngine.screenRows(y=y,h=h)
179180

180181
def rewrap(self) -> None:
@@ -186,15 +187,15 @@ def rewrap(self) -> None:
186187
self._wrapEngine.rewrap()
187188
self.wrapChanged.emit()
188189

189-
def dataToScreenPosition(self, line:int, pos:int) -> Tuple[int, int]:
190+
def dataToScreenPosition(self, line:int, pos:int) -> _RetScreenPositions:
190191
'''Map a document position to wrapped screen coordinates.
191192
192193
:param line: logical line index.
193194
:type line: int
194195
:param pos: character position in the logical line.
195196
:type pos: int
196-
:return: ``(x, y)`` wrapped screen coordinates.
197-
:rtype: Tuple[int, int]
197+
:return: wrapped screen coordinates.
198+
:rtype: :py:class:`_RetScreenPositions`
198199
'''
199200
return self._wrapEngine.dataToScreenPosition(line=line, pos=pos)
200201

libs/pyTermTk/TermTk/TTkGui/TTkTextWrap/text_wrap_data.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
__all__:list = []
2424

2525
from dataclasses import dataclass
26-
from typing import List
26+
from typing import List, Optional, Tuple
2727

2828
from TermTk.TTkCore.constant import TTkK
2929

@@ -54,11 +54,15 @@ class _WrapLine():
5454
:type start: int
5555
:param stop: exclusive stop character offset in the source line.
5656
:type stop: int
57+
:param last_slice: ``True`` when this fragment is the final wrapped slice
58+
for :py:attr:`line`, ``False`` otherwise.
59+
:type last_slice: bool
5760
'''
58-
__slots__ = ('line', 'start', 'stop')
61+
__slots__ = ('line', 'start', 'stop', 'last_slice')
5962
line:int
6063
start: int
6164
stop:int
65+
last_slice:bool
6266

6367

6468
@dataclass
@@ -82,5 +86,17 @@ class _WrapState():
8286

8387
@dataclass
8488
class _RetScreenRows():
85-
y:int
8689
rows: List[_WrapLine]
90+
91+
@dataclass
92+
class _RetScreenPosition():
93+
x: int
94+
y: int
95+
def to_xy(self) -> Tuple[int,int]:
96+
return self.x, self.y
97+
@dataclass
98+
class _RetScreenPositions():
99+
main: _RetScreenPosition
100+
extra: Optional[_RetScreenPosition] = None
101+
def to_xy(self) -> Tuple[int,int]:
102+
return self.main.to_xy()

libs/pyTermTk/TermTk/TTkGui/TTkTextWrap/text_wrap_engine.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from TermTk.TTkCore.constant import TTkK
2929
from TermTk.TTkCore.string import TTkString
3030

31-
from .text_wrap_data import _RetScreenRows, _WrapLine, _WrapState, _ReWrapData
31+
from .text_wrap_data import _RetScreenPosition, _RetScreenPositions, _RetScreenRows, _WrapLine, _WrapState, _ReWrapData
3232

3333
class _WrapEngine_Interface():
3434
'''Base interface for text wrap engines.
@@ -41,8 +41,8 @@ class _WrapEngine_Interface():
4141
:py:attr:`TTkTextDocument.contentsChange` signal.
4242
* :py:class:`_WrapEngine_VimWrap` -- lazily wraps only the visible window,
4343
caching the last viewport for coordinate conversions.
44-
* :py:class:`_WrapEngine_FastWrap` -- placeholder for a future incremental
45-
wrapping strategy.
44+
* :py:class:`_WrapEngine_FastWrap` -- chunk-based lazy wrapping strategy
45+
for large documents.
4646
'''
4747
__slots__ = ('_wrapState')
4848

@@ -77,10 +77,13 @@ def rewrap(self, data: Optional[_ReWrapData]=None) -> None:
7777
:py:class:`_WrapEngine_FullWrap`) rebuild it here; engines
7878
that wrap lazily (e.g. :py:class:`_WrapEngine_NoWrap`,
7979
:py:class:`_WrapEngine_VimWrap`) may be a no-op.
80+
81+
:param data: optional incremental change descriptor.
82+
:type data: Optional[:py:class:`_ReWrapData`]
8083
'''
8184
raise NotImplementedError()
8285

83-
def dataToScreenPosition(self, line:int, pos:int) -> Tuple[int, int]:
86+
def dataToScreenPosition(self, line:int, pos:int) -> _RetScreenPositions:
8487
'''Map a document position to wrapped screen coordinates.
8588
8689
Converts a logical ``(line, pos)`` pair in the source document
@@ -92,9 +95,9 @@ def dataToScreenPosition(self, line:int, pos:int) -> Tuple[int, int]:
9295
:param pos: character offset within the document line.
9396
:type pos: int
9497
95-
:return: ``(x, y)`` screen coordinates where *x* is the
96-
terminal-cell column and *y* is the wrapped row index.
97-
:rtype: Tuple[int, int]
98+
:return: wrapped screen coordinates where *main.x* is the
99+
terminal-cell column and *main.y* is the wrapped row index.
100+
:rtype: :py:class:`_RetScreenPositions`
98101
'''
99102
raise NotImplementedError()
100103

@@ -144,8 +147,8 @@ def screenRows(self, y:int, h:int) -> _RetScreenRows:
144147
:param h: number of rows to retrieve.
145148
:type h: int
146149
147-
:return: list of wrapped row descriptors.
148-
:rtype: List[:py:class:`_WrapLine`]
150+
:return: wrapped row descriptors.
151+
:rtype: :py:class:`_RetScreenRows`
149152
'''
150153
raise NotImplementedError()
151154

@@ -188,12 +191,12 @@ def _wrapLine(self, _w:int, _i:int, _l:TTkString) -> List[_WrapLine]:
188191
wordWrapMode = self._wrapState.wordWrapMode
189192
fr = 0
190193
if not len(_l): # if the line is empty append it
191-
ret.append(_WrapLine(_i,0,0))
194+
ret.append(_WrapLine(_i,0,0,True))
192195
return ret
193196
while len(_l):
194197
to = _l.tabCharPos(_w, tabSpaces)
195198
if to >= len(_l):
196-
ret.append(_WrapLine(_i,fr,fr+len(_l)+1))
199+
ret.append(_WrapLine(_i,fr,fr+len(_l)+1,True))
197200
return ret
198201
to = max(1,to)
199202
if wordWrapMode == TTkK.WordWrap: # Find the index of the first white space
@@ -202,7 +205,9 @@ def _wrapLine(self, _w:int, _i:int, _l:TTkString) -> List[_WrapLine]:
202205
while newTo and ( s[newTo] != ' ' and s[newTo] != '\t' ): newTo-=1
203206
if newTo: to = newTo
204207

205-
ret.append(_WrapLine(_i,fr,fr+to))
208+
ret.append(_WrapLine(_i,fr,fr+to,False))
206209
_l = _l.substring(to)
207210
fr += to
211+
if ret:
212+
ret[-1].last_slice=True
208213
return ret

0 commit comments

Comments
 (0)