sermo 0.3.0__tar.gz → 0.3.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.
- {sermo-0.3.0 → sermo-0.3.2}/PKG-INFO +2 -2
- {sermo-0.3.0 → sermo-0.3.2}/README.md +1 -1
- {sermo-0.3.0 → sermo-0.3.2}/pyproject.toml +1 -1
- {sermo-0.3.0 → sermo-0.3.2}/sermo.egg-info/PKG-INFO +2 -2
- {sermo-0.3.0 → sermo-0.3.2}/sermo_cli/tui.py +64 -9
- {sermo-0.3.0 → sermo-0.3.2}/sermo.egg-info/SOURCES.txt +0 -0
- {sermo-0.3.0 → sermo-0.3.2}/sermo.egg-info/dependency_links.txt +0 -0
- {sermo-0.3.0 → sermo-0.3.2}/sermo.egg-info/entry_points.txt +0 -0
- {sermo-0.3.0 → sermo-0.3.2}/sermo.egg-info/top_level.txt +0 -0
- {sermo-0.3.0 → sermo-0.3.2}/sermo_cli/__init__.py +0 -0
- {sermo-0.3.0 → sermo-0.3.2}/sermo_cli/__main__.py +0 -0
- {sermo-0.3.0 → sermo-0.3.2}/sermo_cli/api.py +0 -0
- {sermo-0.3.0 → sermo-0.3.2}/sermo_cli/cli.py +0 -0
- {sermo-0.3.0 → sermo-0.3.2}/sermo_cli/formatting.py +0 -0
- {sermo-0.3.0 → sermo-0.3.2}/sermo_cli/store.py +0 -0
- {sermo-0.3.0 → sermo-0.3.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sermo
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Command-line Sermo client
|
|
5
5
|
Author: Sermo
|
|
6
6
|
Project-URL: Homepage, https://sermo.jyonn.space
|
|
@@ -104,7 +104,7 @@ sermo tui
|
|
|
104
104
|
- 直接输入文字,按 `Enter` 发送。
|
|
105
105
|
- `F5` 手动刷新。
|
|
106
106
|
- `Ctrl+U` 清空输入。
|
|
107
|
-
- `
|
|
107
|
+
- `Esc` 或 `F10` 退出;输入框为空时也可以按 `q` 退出。
|
|
108
108
|
|
|
109
109
|
打开聊天:
|
|
110
110
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sermo
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Command-line Sermo client
|
|
5
5
|
Author: Sermo
|
|
6
6
|
Project-URL: Homepage, https://sermo.jyonn.space
|
|
@@ -104,7 +104,7 @@ sermo tui
|
|
|
104
104
|
- 直接输入文字,按 `Enter` 发送。
|
|
105
105
|
- `F5` 手动刷新。
|
|
106
106
|
- `Ctrl+U` 清空输入。
|
|
107
|
-
- `
|
|
107
|
+
- `Esc` 或 `F10` 退出;输入框为空时也可以按 `q` 退出。
|
|
108
108
|
|
|
109
109
|
打开聊天:
|
|
110
110
|
|
|
@@ -1,14 +1,65 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import curses
|
|
4
|
-
import textwrap
|
|
5
4
|
import time
|
|
5
|
+
import unicodedata
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
8
|
from .api import ApiClient, SermoApiError
|
|
9
9
|
from .formatting import chat_title, format_time, message_text, user_name
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
def cell_width(char: str) -> int:
|
|
13
|
+
if not char:
|
|
14
|
+
return 0
|
|
15
|
+
if unicodedata.combining(char):
|
|
16
|
+
return 0
|
|
17
|
+
if unicodedata.east_asian_width(char) in {"F", "W"}:
|
|
18
|
+
return 2
|
|
19
|
+
return 1
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def display_width(text: str) -> int:
|
|
23
|
+
return sum(cell_width(char) for char in str(text))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def clip_display(text: str, max_width: int) -> str:
|
|
27
|
+
if max_width <= 0:
|
|
28
|
+
return ""
|
|
29
|
+
used = 0
|
|
30
|
+
result: list[str] = []
|
|
31
|
+
for char in str(text).replace("\n", " "):
|
|
32
|
+
width = cell_width(char)
|
|
33
|
+
if used + width > max_width:
|
|
34
|
+
break
|
|
35
|
+
result.append(char)
|
|
36
|
+
used += width
|
|
37
|
+
return "".join(result)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def pad_display(text: str, width: int) -> str:
|
|
41
|
+
clipped = clip_display(text, width)
|
|
42
|
+
return clipped + " " * max(0, width - display_width(clipped))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def wrap_display(text: str, width: int) -> list[str]:
|
|
46
|
+
if width <= 0:
|
|
47
|
+
return [""]
|
|
48
|
+
lines: list[str] = []
|
|
49
|
+
current: list[str] = []
|
|
50
|
+
used = 0
|
|
51
|
+
for char in str(text).replace("\n", " "):
|
|
52
|
+
char_width = cell_width(char)
|
|
53
|
+
if used + char_width > width and current:
|
|
54
|
+
lines.append("".join(current))
|
|
55
|
+
current = []
|
|
56
|
+
used = 0
|
|
57
|
+
current.append(char)
|
|
58
|
+
used += char_width
|
|
59
|
+
lines.append("".join(current))
|
|
60
|
+
return lines or [""]
|
|
61
|
+
|
|
62
|
+
|
|
12
63
|
class SermoTui:
|
|
13
64
|
def __init__(self, client: ApiClient, *, refresh_interval: float = 5.0, limit: int = 40):
|
|
14
65
|
self.client = client
|
|
@@ -170,7 +221,10 @@ class SermoTui:
|
|
|
170
221
|
except curses.error:
|
|
171
222
|
return
|
|
172
223
|
|
|
173
|
-
if key in ("\
|
|
224
|
+
if key in ("\x03", "\x1b", curses.KEY_F10):
|
|
225
|
+
self.running = False
|
|
226
|
+
return
|
|
227
|
+
if key in ("q", "Q") and not self.input_line:
|
|
174
228
|
self.running = False
|
|
175
229
|
return
|
|
176
230
|
if key == curses.KEY_F5:
|
|
@@ -210,15 +264,15 @@ class SermoTui:
|
|
|
210
264
|
|
|
211
265
|
title = f"Sermo TUI · {self.space.get('name') or 'Sermo'} @{self.space.get('slug') or '-'}"
|
|
212
266
|
self._add(stdscr, 0, 0, title, curses.A_BOLD | curses.color_pair(1), width)
|
|
213
|
-
self._add(stdscr, 0, max(0, width -
|
|
267
|
+
self._add(stdscr, 0, max(0, width - display_width(self.status) - 1), self.status, curses.color_pair(5), width)
|
|
214
268
|
self._draw_separator(stdscr, 1, width)
|
|
215
269
|
self._draw_chats(stdscr, 2, left_width, content_height)
|
|
216
270
|
self._draw_vertical_separator(stdscr, 2, left_width, height - 3)
|
|
217
271
|
self._draw_messages(stdscr, 2, main_x, width - main_x, content_height)
|
|
218
272
|
self._draw_input(stdscr, input_y, main_x, width - main_x)
|
|
219
|
-
help_text = "↑/↓ 切换会话 · Enter 发送 · F5 刷新 · Ctrl+U
|
|
273
|
+
help_text = "↑/↓ 切换会话 · Enter 发送 · F5 刷新 · Ctrl+U 清空 · Esc/F10 退出 · 空输入 q 退出"
|
|
220
274
|
self._add(stdscr, help_y, 0, help_text, curses.A_DIM, width)
|
|
221
|
-
stdscr.move(input_y, min(width - 1, main_x + 2 +
|
|
275
|
+
stdscr.move(input_y, min(width - 1, main_x + 2 + display_width(self.input_line)))
|
|
222
276
|
stdscr.refresh()
|
|
223
277
|
|
|
224
278
|
def _draw_chats(self, stdscr: Any, top: int, width: int, height: int) -> None:
|
|
@@ -271,10 +325,11 @@ class SermoTui:
|
|
|
271
325
|
text = message_text(message)
|
|
272
326
|
prefix = f"{format_time(message.get('created_at'))} {author}: "
|
|
273
327
|
attrs = curses.color_pair(2) if is_self else curses.A_NORMAL
|
|
274
|
-
|
|
328
|
+
prefix_width = display_width(prefix)
|
|
329
|
+
wrapped = wrap_display(text, width=max(8, wrap_width - prefix_width))
|
|
275
330
|
lines.append((prefix + wrapped[0], attrs))
|
|
276
331
|
for extra in wrapped[1:]:
|
|
277
|
-
lines.append((" " * min(
|
|
332
|
+
lines.append((" " * min(prefix_width, wrap_width - 1) + extra, attrs))
|
|
278
333
|
return lines
|
|
279
334
|
|
|
280
335
|
def _draw_input(self, stdscr: Any, y: int, x: int, width: int) -> None:
|
|
@@ -299,9 +354,9 @@ class SermoTui:
|
|
|
299
354
|
max_width = max(0, min(width or max_x - x, max_x - x))
|
|
300
355
|
if max_width <= 0:
|
|
301
356
|
return
|
|
302
|
-
safe = str(text)
|
|
357
|
+
safe = pad_display(str(text), max_width)
|
|
303
358
|
try:
|
|
304
|
-
stdscr.addstr(y, x, safe
|
|
359
|
+
stdscr.addstr(y, x, safe, attrs)
|
|
305
360
|
except curses.error:
|
|
306
361
|
pass
|
|
307
362
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|