From 997d0f6d7278ba6e09777bbfdd45ba8e359d33ef Mon Sep 17 00:00:00 2001 From: Pier CeccoPierangioliEugenio Date: Tue, 28 Oct 2025 11:30:49 +0000 Subject: [PATCH] feat(TTkTerminal): add getBuffer (#496) --- libs/pyTermTk/TermTk/TTkCore/canvas.py | 4 +- .../TermTk/TTkWidgets/TTkTerminal/terminal.py | 19 ++- .../TTkWidgets/TTkTerminal/terminal_screen.py | 21 +++- .../TTkWidgets/TTkTerminal/terminalview.py | 15 +++ .../test.pty.006.terminal.08.getBuffer.py | 110 ++++++++++++++++++ 5 files changed, 166 insertions(+), 3 deletions(-) create mode 100755 tests/t.pty/test.pty.006.terminal.08.getBuffer.py diff --git a/libs/pyTermTk/TermTk/TTkCore/canvas.py b/libs/pyTermTk/TermTk/TTkCore/canvas.py index e72e7682..ffbf5e4c 100644 --- a/libs/pyTermTk/TermTk/TTkCore/canvas.py +++ b/libs/pyTermTk/TermTk/TTkCore/canvas.py @@ -22,6 +22,8 @@ __all__ = ['TTkCanvas'] +from typing import Tuple + from TermTk.TTkCore.TTkTerm.term import TTkTerm from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog @@ -90,7 +92,7 @@ class TTkCanvas(): self._height = h self._width = w - def size(self): + def size(self) -> Tuple[int,int]: return (self._width, self._height) def resize(self, w, h): diff --git a/libs/pyTermTk/TermTk/TTkWidgets/TTkTerminal/terminal.py b/libs/pyTermTk/TermTk/TTkWidgets/TTkTerminal/terminal.py index 7f27587b..beef8faa 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/TTkTerminal/terminal.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/TTkTerminal/terminal.py @@ -22,7 +22,10 @@ __all__ = ['TTkTerminal'] +from typing import List + from TermTk.TTkCore.constant import TTkK +from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot from TermTk.TTkAbstract.abstractscrollarea import TTkAbstractScrollArea, _ForwardData from TermTk.TTkWidgets.TTkTerminal.terminalview import TTkTerminalView @@ -40,7 +43,7 @@ class TTkTerminal(TTkAbstractScrollArea): 'bell', 'titleChanged', 'terminalClosed', 'textSelected', 'termData', 'termResized'], methods=[# Forwarded Methods From TTkTreeWidget - 'termWrite', 'termSize'] + 'termWrite', 'termSize', 'getBuffer'] ) __slots__ = ('_terminalView') @@ -145,4 +148,18 @@ class TTkTerminal(TTkAbstractScrollArea): :rtype: tuple ''' return self._terminalView.termSize() + def getBuffer(self) -> List[TTkString]: + ''' + .. seealso:: this method is forwarded to :py:meth:`TTkTerminalView.getBuffer` + + Get the terminal buffer contents. + + This method returns the complete terminal buffer, including both the + scrollback buffer (buffered lines) and the currently visible screen content. + + :return: A list of TTkString objects representing all lines in the terminal buffer. + The list includes the scrollback history followed by the visible screen lines. + :rtype: List[TTkString] + ''' + return self._terminalView.getBuffer() #--FORWARD-AUTOGEN-END--# \ No newline at end of file diff --git a/libs/pyTermTk/TermTk/TTkWidgets/TTkTerminal/terminal_screen.py b/libs/pyTermTk/TermTk/TTkWidgets/TTkTerminal/terminal_screen.py index 064840f4..567869c9 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/TTkTerminal/terminal_screen.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/TTkTerminal/terminal_screen.py @@ -1,4 +1,4 @@ - # MIT License +1 # MIT License # # Copyright (c) 2023 Eugenio Parodi # @@ -26,6 +26,8 @@ import collections import unicodedata from dataclasses import dataclass +from typing import List + from TermTk.TTkCore.canvas import TTkCanvas from TermTk.TTkCore.color import TTkColor @@ -253,6 +255,23 @@ class _TTkTerminalScreen(_TTkTerminalScreen_CSI, _TTkTerminalScreen_C1): # Convert x/y in line/pos self._selectCursor.select(x,y,moveAnchor) + def getBuffer(self) -> List[TTkString]: + w,h = self._canvas.size() + ret:List[TTkString] = list(self._bufferedLines) + for y in range(h): + nl = self._canvasNewLine[y] + ls = self._canvasLineSize[y] + data = self._canvas._data[y][:ls] + colors = self._canvas._colors[y][:ls] + line = TTkString._importString1("".join(data),colors) + if nl and ret: + ret[-1] += line + else: + ret.append(line) + while ret and not len(ret[-1]): + ret.pop() + return ret + def getSelected(self): if not self._selectCursor.hasSelection(): return "" diff --git a/libs/pyTermTk/TermTk/TTkWidgets/TTkTerminal/terminalview.py b/libs/pyTermTk/TermTk/TTkWidgets/TTkTerminal/terminalview.py index 88433b45..04dc2561 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/TTkTerminal/terminalview.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/TTkTerminal/terminalview.py @@ -24,6 +24,8 @@ __all__ = ['TTkTerminalView'] import re +from typing import List + from dataclasses import dataclass from TermTk.TTkCore.canvas import TTkCanvas @@ -309,6 +311,19 @@ class TTkTerminalView(TTkAbstractScrollView, _TTkTerminal_CSI_DEC): if data: self._termLoop.send(data) + def getBuffer(self) -> List[TTkString]: + ''' + Get the terminal buffer contents. + + This method returns the complete terminal buffer, including both the + scrollback buffer (buffered lines) and the currently visible screen content. + + :return: A list of TTkString objects representing all lines in the terminal buffer. + The list includes the scrollback history followed by the visible screen lines. + :rtype: List[TTkString] + ''' + return self._screen_current.getBuffer() + def _loopGenerator(self): def _checkSize(): if self._newSize: diff --git a/tests/t.pty/test.pty.006.terminal.08.getBuffer.py b/tests/t.pty/test.pty.006.terminal.08.getBuffer.py new file mode 100755 index 00000000..efde2545 --- /dev/null +++ b/tests/t.pty/test.pty.006.terminal.08.getBuffer.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the"Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED"AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import os, sys, argparse +from select import select + +sys.path.append(os.path.join(sys.path[0],'../..')) +import TermTk as ttk + +parser = argparse.ArgumentParser() +parser.add_argument('-d', help='Debug (Add LogViewer Panel)', action='store_true') +args = parser.parse_args() + +# ttk.TTkLog.use_default_file_logging() +root = ttk.TTk(layout=ttk.TTkGridLayout(), mouseTrack=True) + +split = ttk.TTkSplitter(parent=root, orientation=ttk.TTkK.VERTICAL) + +split.addItem(top := ttk.TTkLayout()) + +if args.d: + split.addWidget(ttk.TTkLogViewer(follow=False ), title='Log', size=20) + +quitBtn = ttk.TTkButton(text="QUIT", border=True) +quitBtn.clicked.connect(ttk.TTkHelper.quit) + +cb_c = ttk.TTkCheckbox(pos=(0,3),size=(20,1), text="CTRL-C (VINTR) ", checked=ttk.TTkK.Checked) +cb_s = ttk.TTkCheckbox(pos=(0,4),size=(20,1), text="CTRL-S (VSTOP) ", checked=ttk.TTkK.Checked) +cb_z = ttk.TTkCheckbox(pos=(0,5),size=(20,1), text="CTRL-Z (VSUSP) ", checked=ttk.TTkK.Checked) +cb_q = ttk.TTkCheckbox(pos=(0,6),size=(20,1), text="CTRL-Q (VSTART)", checked=ttk.TTkK.Checked) + + +cb_c.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_C,x==ttk.TTkK.Checked)) +cb_s.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_S,x==ttk.TTkK.Checked)) +cb_z.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_Z,x==ttk.TTkK.Checked)) +cb_q.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_Q,x==ttk.TTkK.Checked)) + +winT = ttk.TTkWindow(pos=(20,10), size=(100,30), title="TextEdit", border=True, layout=ttk.TTkVBoxLayout(), flags = ttk.TTkK.WindowFlag.WindowMinMaxButtonsHint|ttk.TTkK.WindowFlag.WindowCloseButtonHint) +te = ttk.TTkTextEdit(parent=winT, readOnly=False, lineNumber=True) + +clipboard = ttk.TTkClipboard() + +@ttk.pyTTkSlot(ttk.TTkString) +def _textSelected(text, te=te): + te.setText("Selected Text in the terminal:") + te.append( "------------------------------\n") + te.append(text) + +tnum = 0 +@ttk.pyTTkSlot() +def _addTerminal() -> ttk.TTkTerminal: + global tnum + tnum+=1 + win = ttk.TTkWindow(pos=(12,0), size=(100,30), title=f"Terminallo n.{tnum}", border=True, layout=ttk.TTkVBoxLayout(), flags = ttk.TTkK.WindowFlag.WindowMinMaxButtonsHint|ttk.TTkK.WindowFlag.WindowCloseButtonHint) + term = ttk.TTkTerminal(parent=win) + + th = ttk.TTkTerminalHelper(term=term) + th.terminalClosed.connect(win.close) + + term.bell.connect(lambda : ttk.TTkLog.debug("BELL!!! 🔔🔔🔔")) + term.titleChanged.connect(win.setTitle) + + term.textSelected.connect(clipboard.setText) + term.textSelected.connect(_textSelected) + win.closed.connect(term.close) + top.addWidget(win) + term.setFocus() + term.raiseWidget() + + th.runShell() + + return term + +btn_gb = ttk.TTkButton(pos=(0,8), text="Get Buffer", border=True) + +top.addWidgets([quitBtn, btn_gb, cb_c, cb_s, cb_z, cb_q, winT]) + +term = _addTerminal() + +@ttk.pyTTkSlot() +def _getBuffer(): + buffer = term.getBuffer() + text = ttk.TTkString('\n').join(buffer) + te.setText("Selected Text in the terminal:") + te.append( "------------------------------\n") + te.append(text) +btn_gb.clicked.connect(_getBuffer) + +root.mainloop() \ No newline at end of file