xshell-like 0.2.1__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {xshell_like-0.2.1/xshell_like.egg-info → xshell_like-0.2.2}/PKG-INFO +1 -1
  2. {xshell_like-0.2.1 → xshell_like-0.2.2}/pyproject.toml +1 -1
  3. {xshell_like-0.2.1 → xshell_like-0.2.2}/tests/test_terminal.py +46 -0
  4. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/__main__.py +5 -0
  5. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/core/terminal.py +30 -4
  6. {xshell_like-0.2.1 → xshell_like-0.2.2/xshell_like.egg-info}/PKG-INFO +1 -1
  7. {xshell_like-0.2.1 → xshell_like-0.2.2}/LICENSE +0 -0
  8. {xshell_like-0.2.1 → xshell_like-0.2.2}/README.md +0 -0
  9. {xshell_like-0.2.1 → xshell_like-0.2.2}/setup.cfg +0 -0
  10. {xshell_like-0.2.1 → xshell_like-0.2.2}/tests/test_batch.py +0 -0
  11. {xshell_like-0.2.1 → xshell_like-0.2.2}/tests/test_gui_smoke.py +0 -0
  12. {xshell_like-0.2.1 → xshell_like-0.2.2}/tests/test_keydeploy.py +0 -0
  13. {xshell_like-0.2.1 → xshell_like-0.2.2}/tests/test_serial_ports.py +0 -0
  14. {xshell_like-0.2.1 → xshell_like-0.2.2}/tests/test_serial_transport.py +0 -0
  15. {xshell_like-0.2.1 → xshell_like-0.2.2}/tests/test_session_storage.py +0 -0
  16. {xshell_like-0.2.1 → xshell_like-0.2.2}/tests/test_ssh_transport.py +0 -0
  17. {xshell_like-0.2.1 → xshell_like-0.2.2}/tests/test_terminal_widget.py +0 -0
  18. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/__init__.py +0 -0
  19. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/core/__init__.py +0 -0
  20. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/core/batch.py +0 -0
  21. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/core/keydeploy.py +0 -0
  22. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/core/serial_ports.py +0 -0
  23. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/core/serial_transport.py +0 -0
  24. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/core/session.py +0 -0
  25. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/core/sftp.py +0 -0
  26. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/core/ssh_transport.py +0 -0
  27. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/core/storage.py +0 -0
  28. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/core/transport.py +0 -0
  29. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/py.typed +0 -0
  30. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/ui/__init__.py +0 -0
  31. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/ui/dialogs.py +0 -0
  32. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/ui/main_window.py +0 -0
  33. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/ui/session_tree.py +0 -0
  34. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/ui/sftp_dialog.py +0 -0
  35. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/ui/terminal_widget.py +0 -0
  36. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell/ui/theme.py +0 -0
  37. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell_like.egg-info/SOURCES.txt +0 -0
  38. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell_like.egg-info/dependency_links.txt +0 -0
  39. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell_like.egg-info/entry_points.txt +0 -0
  40. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell_like.egg-info/requires.txt +0 -0
  41. {xshell_like-0.2.1 → xshell_like-0.2.2}/xshell_like.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xshell-like
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: 类 Xshell 终端工具:SSH/串口、批量连接、多页签、会话管理、SFTP、免密部署
5
5
  Author: weixin_53700778
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "xshell-like"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "类 Xshell 终端工具:SSH/串口、批量连接、多页签、会话管理、SFTP、免密部署"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -139,3 +139,49 @@ def test_cursor_follows_ansi_cursor_move():
139
139
  assert tb.cursor == (0, 1)
140
140
  tb.feed(b"\x1b[1A") # CUU: up one row, column unchanged → row 0, col 0
141
141
  assert tb.cursor == (0, 0)
142
+
143
+
144
+ # ---------- resize anchoring (xterm semantics) ----------
145
+
146
+ def test_grow_keeps_content_at_top_blanks_below():
147
+ tb = TerminalBuffer(cols=20, rows=6)
148
+ tb.feed(b"line1\r\nline2\r\nline3\r\nline4")
149
+ tb.resize(20, 12)
150
+ lines = [ln.rstrip() for ln in tb.render().splitlines()]
151
+ assert lines[0] == "line1" and lines[3] == "line4"
152
+ assert all(not ln for ln in lines[4:])
153
+ assert tb.cursor == (5, 3)
154
+
155
+
156
+ def test_shrink_keeps_cursor_line_visible():
157
+ """Regression: pyte's own shrink truncates the bottom and drops the prompt
158
+ line — after SIGWINCH the remote repainted it mid-screen with blank space
159
+ above. xterm semantics: the cursor line must stay visible."""
160
+ tb = TerminalBuffer(cols=20, rows=8)
161
+ tb.feed(b"l1\r\nl2\r\nl3\r\nprompt$ ")
162
+ tb.resize(20, 3)
163
+ lines = [ln.rstrip() for ln in tb.render().splitlines()]
164
+ assert lines[-1].startswith("prompt$") # cursor line kept at the bottom
165
+ assert len(lines) == 3
166
+ assert tb.cursor[1] <= 2 # cursor clamped into the shrunken screen
167
+
168
+
169
+ def test_shrink_then_grow_no_blank_gap_above_content():
170
+ tb = TerminalBuffer(cols=20, rows=8)
171
+ tb.feed(b"l1\r\nl2\r\nl3\r\nprompt$ ")
172
+ tb.resize(20, 3) # user drags the window smaller
173
+ tb.resize(20, 8) # ...then bigger again
174
+ lines = [ln.rstrip() for ln in tb.render().splitlines()]
175
+ assert lines[0] == "l2" # l1 went to scrollback, content anchored to top
176
+ assert lines[2].startswith("prompt$")
177
+ assert all(not ln for ln in lines[3:]) # blanks BELOW content, never above
178
+
179
+
180
+ def test_shrink_drops_top_lines_into_scrollback():
181
+ tb = TerminalBuffer(cols=20, rows=6)
182
+ tb.feed(b"a\r\nb\r\nc\r\nd\r\ne\r\nf\r\nprompt$ ") # cursor on row 6
183
+ tb.resize(20, 4) # cursor line must survive -> 3 top lines to scrollback
184
+ sb = tb.scrollback
185
+ assert "a" in sb and "b" in sb and "c" in sb
186
+ lines = [ln.rstrip() for ln in tb.render().splitlines()]
187
+ assert lines[-1].startswith("prompt$")
@@ -3,12 +3,17 @@ import sys
3
3
 
4
4
 
5
5
  def main() -> int:
6
+ from PySide6.QtCore import Qt
6
7
  from PySide6.QtWidgets import QApplication
7
8
 
8
9
  from xshell.core.storage import SessionStore
9
10
  from xshell.ui.main_window import MainWindow
10
11
  from xshell.ui.theme import apply_theme
11
12
 
13
+ # honor the display's fractional scaling exactly (HiDPI / 4K screens)
14
+ QApplication.setHighDpiScaleFactorRoundingPolicy(
15
+ Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
16
+ )
12
17
  app = QApplication(sys.argv)
13
18
  app.setApplicationName("xshell-like")
14
19
  app.setOrganizationName("xshell-like")
@@ -69,8 +69,34 @@ class TerminalBuffer:
69
69
  rows = max(2, int(rows))
70
70
  if cols == self.cols and rows == self.rows:
71
71
  return
72
- # pyte signature is resize(lines, columns) — rows first!
73
- self.screen.resize(rows, cols)
72
+ old_rows = self.rows
73
+ buffer = self.screen.buffer
74
+ cursor_y = min(self.screen.cursor.y, old_rows - 1)
75
+
76
+ # Line dimension is handled HERE, with xterm semantics: content is
77
+ # top-anchored and a shrink drops lines from the TOP into scrollback
78
+ # so the cursor's line (the prompt!) stays visible. pyte's own resize
79
+ # keeps cursor.y absolute instead — after a shrink the prompt line is
80
+ # dropped and the remote's SIGWINCH repaint lands mid-screen with
81
+ # blank space above it ("content doesn't fill the window").
82
+ drop = max(0, cursor_y - (rows - 1)) if rows < old_rows else 0
83
+ for y in range(drop):
84
+ row = buffer.get(y)
85
+ if row is not None:
86
+ self.screen.history.top.append(row)
87
+ old_rows_list = [buffer.get(y) for y in range(old_rows)]
88
+ buffer.clear()
89
+ for new_y in range(rows):
90
+ src = old_rows_list[drop + new_y] if drop + new_y < old_rows else None
91
+ if src is not None:
92
+ buffer[new_y] = src
93
+ self.screen.cursor.y = max(0, min(cursor_y - drop, rows - 1))
94
+ self.screen.lines = rows
95
+
96
+ # Column dimension: pyte truncates/pads the right side fine. With
97
+ # lines already equal, its resize only runs the column logic.
98
+ pyte.Screen.resize(self.screen, rows, cols)
99
+ self.screen.dirty = set(range(rows))
74
100
 
75
101
  # ---------- output ----------
76
102
 
@@ -109,7 +135,7 @@ class TerminalBuffer:
109
135
  current_style = style
110
136
  run.append(char.data if char.data else " ")
111
137
  parts.append(self._wrap("".join(run), current_style))
112
- return "".join(parts).rstrip(" ") or " "
138
+ return "".join(parts).rstrip(" ") or " "
113
139
 
114
140
  @staticmethod
115
141
  def _char_style(char: Char) -> Optional[str]:
@@ -144,7 +170,7 @@ class TerminalBuffer:
144
170
  text.replace("&", "&amp;")
145
171
  .replace("<", "&lt;")
146
172
  .replace(">", "&gt;")
147
- .replace(" ", " ")
173
+ .replace(" ", " ")
148
174
  )
149
175
  if not style:
150
176
  return text
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xshell-like
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: 类 Xshell 终端工具:SSH/串口、批量连接、多页签、会话管理、SFTP、免密部署
5
5
  Author: weixin_53700778
6
6
  License: MIT
File without changes
File without changes
File without changes
File without changes