diff --git a/TermTk/TTkWidgets/TTkTerminal/terminal.py b/TermTk/TTkWidgets/TTkTerminal/terminal.py index 35248939..32b2cf32 100644 --- a/TermTk/TTkWidgets/TTkTerminal/terminal.py +++ b/TermTk/TTkWidgets/TTkTerminal/terminal.py @@ -64,7 +64,17 @@ class TTkTerminal(TTkWidget): super().__init__(*args, **kwargs) + w,h = self.size() + self._screen_alt.resize(w,h) + self._screen_normal.resize(w,h) + self.setFocusPolicy(TTkK.ClickFocus + TTkK.TabFocus) + self.enableWidgetCursor() + + def resizeEvent(self, w: int, h: int): + self._screen_alt.resize(w,h) + self._screen_normal.resize(w,h) + return super().resizeEvent(w, h) def runShell(self, program=None): self._shell = program if program else self._shell @@ -85,7 +95,7 @@ class TTkTerminal(TTkWidget): # xterm escape sequences from: # https://invisible-island.net/xterm/ctlseqs/ctlseqs.html # https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_ - re_CURSOR = re.compile('^(\d*)(;?)(\d*)([@ABCDEFGIJKLMPSTXZ^`abcdeginqx])') + re_CURSOR = re.compile('^(\d*)(;(\d*))?([@ABCDEFGIJKLMPSTXZ^`abcdeginqxHf])') # https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_ # Basic Re for CSI Ps matches: # CSI : Control Sequence Introducer "[" = '\033[' @@ -145,6 +155,8 @@ class TTkTerminal(TTkWidget): slice = slice.replace('\033','').replace('\n','\\n') TTkLog.debug(f"{slice=}") self.update() + self.setWidgetCursor(pos=self._screen_current.getCursor()) + TTkLog.debug(f"wc:{self._screen_current.getCursor()}") def mousePressEvent(self, evt): return True @@ -152,7 +164,6 @@ class TTkTerminal(TTkWidget): def keyEvent(self, evt): # TTkLog.debug(f"Key: {evt.code=}") TTkLog.debug(f"Key: {str(evt)=}") - self._inout.write(evt.code.encode()) if evt.type == TTkK.SpecialKey: if evt.key == TTkK.Key_Enter: TTkLog.debug(f"Key: Enter") @@ -161,6 +172,7 @@ class TTkTerminal(TTkWidget): else: # Input char TTkLog.debug(f"Key: {evt.key=}") # self._inout.write(evt.key.encode()) + self._inout.write(evt.code.encode()) return True def paintEvent(self, canvas: TTkCanvas): diff --git a/TermTk/TTkWidgets/TTkTerminal/terminal_alt.py b/TermTk/TTkWidgets/TTkTerminal/terminal_alt.py index 7cb701d4..1e4e3a2a 100644 --- a/TermTk/TTkWidgets/TTkTerminal/terminal_alt.py +++ b/TermTk/TTkWidgets/TTkTerminal/terminal_alt.py @@ -39,9 +39,19 @@ from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView, TTkAbst from TermTk.TTkWidgets.widget import TTkWidget class _TTkTerminalAltScreen(): - __slots__ = ('_lines') - def __init__(self) -> None: + __slots__ = ('_lines', '_terminalCursor', '_w', '_h') + def __init__(self, w=80, h=24) -> None: self._lines = [TTkString()] + self._terminalCursor = (0,0) + self._w = w + self._h = h + + def getCursor(self): + return self._terminalCursor + + def resize(self, w, h): + self._w = w + self._h = h def pushLine(self, line:str): lines = line.replace('\r','').split('\n') @@ -112,7 +122,7 @@ class _TTkTerminalAltScreen(): # Ps = 0 ⇒ Erase to Right (default). # Ps = 1 ⇒ Erase to Left. # Ps = 2 ⇒ Erase All. - def _CSI_K_el(self, ps, _): pass + def _CSI_K_EL(self, ps, _): pass # CSI ? Ps K # Erase in Line (DECSEL), VT220. @@ -1372,7 +1382,7 @@ class _TTkTerminalAltScreen(): 'H': _CSI_H_CUP, 'I': _CSI_I_CHT, 'J': _CSI_J_ED, - 'K': _CSI_K_el, + 'K': _CSI_K_EL, 'L': _CSI_L_IL, 'M': _CSI_M_DL, 'P': _CSI_P_DCH, diff --git a/TermTk/TTkWidgets/TTkTerminal/terminal_normal.py b/TermTk/TTkWidgets/TTkTerminal/terminal_normal.py index 6fe8c323..911bd574 100644 --- a/TermTk/TTkWidgets/TTkTerminal/terminal_normal.py +++ b/TermTk/TTkWidgets/TTkTerminal/terminal_normal.py @@ -39,30 +39,52 @@ from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView, TTkAbst from TermTk.TTkWidgets.widget import TTkWidget class _TTkTerminalNormalScreen(): - __slots__ = ('_lines', '_terminalCursor') - def __init__(self) -> None: + __slots__ = ('_lines', '_terminalCursor', '_w', '_h', '_bufferSize') + def __init__(self, w=80, h=24, bufferSize=200) -> None: self._lines = [TTkString()] self._terminalCursor = (0,0) + self._w = w + self._h = h + self._bufferSize = bufferSize + + def getCursor(self): + x,y = self._terminalCursor + h = self._h + l = len(self._lines) + y = y if l= len(self._lines): + self._lines.append(TTkString()) + self._lines = self._lines[-self._bufferSize:] + self._terminalCursor = (x,y) = (0,min(self._bufferSize-1, y)) ls = l.split('\r') for ii,ll in enumerate(ls): if ii: - self._terminalCursor=(0,len(self._lines)-1) + self._terminalCursor=(0,y) lls = ll.split('\b') # 0x08 = Backspace for iii,lll in enumerate(lls): if iii: @@ -70,8 +92,6 @@ class _TTkTerminalNormalScreen(): self._terminalCursor = (max(0,x-1),y) self._pushTxt(lll) - - def paintEvent(self, canvas:TTkCanvas, w:int, h:int, ox:int=0, oy:int=0) -> None: canvas.drawText(text="NORMAL Screen", pos=(0,0), width=w) for y,l in enumerate(self._lines[-h:]): @@ -93,7 +113,9 @@ class _TTkTerminalNormalScreen(): # CSI Ps A Cursor Up Ps Times (default = 1) (CUU). def _CSI_A_CUU(self, ps, _): x,y = self._terminalCursor - self._terminalCursor=(x,max(0,y-ps)) + l = len(self._lines) + y = max(0, l-self._h, y-ps) + self._terminalCursor=(x,y) # CSI Ps SP A (SP = Space) # Shift right Ps columns(s) (default = 1) (SR), ECMA-48. @@ -104,50 +126,57 @@ class _TTkTerminalNormalScreen(): # CSI Ps B Cursor Down Ps Times (default = 1) (CUD). def _CSI_B_CUD(self, ps, _): x,y = self._terminalCursor - # w,h = self.size() - y = min(len(self._lines)-1,y+ps) + l = len(self._lines) + y = min(l-1, y+ps) self._terminalCursor=(x,y) # CSI Ps C Cursor Forward Ps Times (default = 1) (CUF). def _CSI_C_CUF(self, ps, _): x,y = self._terminalCursor - w,h = self.size() - x = min(w-1,x+ps) + x = min(self._w-1, x+ps) self._terminalCursor=(x,y) # CSI Ps D Cursor Backward Ps Times (default = 1) (CUB). def _CSI_D_CUB(self, ps, _): x,y = self._terminalCursor - # w,h = self.size() x = max(0,x-ps) self._terminalCursor=(x,y) # CSI Ps E Cursor Next Line Ps Times (default = 1) (CNL). def _CSI_E_CNL(self, ps, _): x,y = self._terminalCursor - # w,h = self.size() - y = min(len(self._lines)-1,y+ps) + l = len(self._lines) + y = min(l-1, y+ps) self._terminalCursor=(0,y) # CSI Ps F Cursor Preceding Line Ps Times (default = 1) (CPL). def _CSI_F_CPL(self, ps, _): x,y = self._terminalCursor - w,h = self.size() - y = max(0,len(self._lines)-h,y-ps) + l = len(self._lines) + y = max(0, l-self._h, y-ps) self._terminalCursor=(0,y) # CSI Ps G Cursor Character Absolute [column] (default = [row,1]) (CHA). def _CSI_G_CHA(self, row, _): x,y = self._terminalCursor - w,h = self.size() - self._terminalCursor=(max(0,min(w-1,row)),y) + w = self._w + x = max( 0, min(w-1, row-1)) + self._terminalCursor=(x,y) # CSI Ps ; Ps H # Cursor Position [row;column] (default = [1,1]) (CUP). def _CSI_H_CUP(self, row, col): x,y = self._terminalCursor - w,h = self.size() - self._terminalCursor=(max(0,min(w-1,col)),max(0,min(len(self._lines)-1,col))) + w,h = self._w, self._h + row, col = row-1, col-1 + l = len(self._lines) + oy = max(0,l-h) # y offset + ny = row + oy # y row + x = max(0, min(w-1, col)) + y = max(oy, min(ny,oy+h)) + self._terminalCursor=(x,y) + if y>=l: + self._lines += [TTkString()]*(y-l+1) # CSI Ps I Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). @@ -158,7 +187,19 @@ class _TTkTerminalNormalScreen(): # Ps = 1 ⇒ Erase Above. # Ps = 2 ⇒ Erase All. # Ps = 3 ⇒ Erase Saved Lines, xterm. - def _CSI_J_ED(self, ps, _): pass + def _CSI_J_ED(self, ps, _): + x,y = self._terminalCursor + h = self._h + l = len(self._lines) + # y = max(0,y-l-h) + if ps == 0: + self._lines[y+1:] = [TTkString()]*(l-y-1) + self._lines[y] = self._lines[y].substring(to=x) + elif ps == 1: + self._lines[-h:y-1] = [TTkString()]*(y-1-l+h) + self._lines[y] = TTkString(' '*x) + self._lines[y].substring(fr=x) + elif ps == 2: + self._lines[-h:] = [TTkString()]*(h) # CSI ? Ps J # Erase in Display (DECSED), VT220. @@ -171,16 +212,27 @@ class _TTkTerminalNormalScreen(): # Ps = 0 ⇒ Erase to Right (default). # Ps = 1 ⇒ Erase to Left. # Ps = 2 ⇒ Erase All. - def _CSI_K_el(self, ps, _): pass + def _CSI_K_EL(self, ps, _): + x,y = self._terminalCursor + line = self._lines[y] + if ps == 0: + self._lines[y] = line.substring(to=x) + elif ps == 1: + self._lines[y] = TTkString(' '*x) + self._lines[y].substring(fr=x) + elif ps == 2: + self._lines[y] = TTkString() # CSI ? Ps K # Erase in Line (DECSEL), VT220. # Ps = 0 ⇒ Selective Erase to Right (default). # Ps = 1 ⇒ Selective Erase to Left. # Ps = 2 ⇒ Selective Erase All. + # def _CSI_K_DeCSEL(self, ps, _): pass # CSI Ps L Insert Ps Line(s) (default = 1) (IL). - def _CSI_L_IL(self, ps, _): pass + def _CSI_L_IL(self, ps, _): + x,y = self._terminalCursor + self._lines[y:y] = [TTkString()]*ps # CSI Ps M Delete Ps Line(s) (default = 1) (DL). def _CSI_M_DL(self, ps, _): pass @@ -1431,7 +1483,7 @@ class _TTkTerminalNormalScreen(): 'H': _CSI_H_CUP, 'I': _CSI_I_CHT, 'J': _CSI_J_ED, - 'K': _CSI_K_el, + 'K': _CSI_K_EL, 'L': _CSI_L_IL, 'M': _CSI_M_DL, 'P': _CSI_P_DCH, diff --git a/tests/test.pty.006.terminal.02.py b/tests/test.pty.006.terminal.02.py index 5a39eaf0..671197e1 100755 --- a/tests/test.pty.006.terminal.02.py +++ b/tests/test.pty.006.terminal.02.py @@ -56,15 +56,16 @@ split.addItem(top := ttk.TTkLayout()) split.addWidget(ttk.TTkLogViewer(follow=False ), title='Log') -win1 = ttk.TTkWindow(pos=(1,1), size=(70,15), title="Terminallo n.1", border=True, layout=ttk.TTkVBoxLayout(), flags = ttk.TTkK.WindowFlag.WindowMinMaxButtonsHint) +win1 = ttk.TTkWindow(pos=(90,5), size=(70,15), title="Terminallo n.1", border=True, layout=ttk.TTkVBoxLayout(), flags = ttk.TTkK.WindowFlag.WindowMinMaxButtonsHint) term1 = ttk.TTkTerminal(parent=win1) term1.runShell() -win2 = ttk.TTkWindow(pos=(10,5), size=(70,15), title="Terminallo n.2", border=True, layout=ttk.TTkVBoxLayout(), flags = ttk.TTkK.WindowFlag.WindowMinMaxButtonsHint) +win2 = ttk.TTkWindow(pos=(0,0), size=(150,30), title="Terminallo n.2", border=True, layout=ttk.TTkVBoxLayout(), flags = ttk.TTkK.WindowFlag.WindowMinMaxButtonsHint) term2 = ttk.TTkTerminal(parent=win2) term2.runShell() top.addWidgets([win1,win2]) +term2.setFocus() root.mainloop() \ No newline at end of file