Skip to content

Commit 040376b

Browse files
authored
fix: use checkbox style for setup alias selection (#153)
1 parent be2dae6 commit 040376b

File tree

4 files changed

+78
-7
lines changed

4 files changed

+78
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
4646
- `skills/feishu/` 补回 `SKILL.zh.md`,避免技能资产检查在 CI 中因缺少中文入口文件失败
4747
- `chattool gh pr-merge` 新增可选的 `--check` 开关,用于在合并前显式检查 check runs 与 workflow runs,避免再次误把带红 CI 的 PR 当成可安全合并
4848
- `chattool skill install` 不再强制要求 skill frontmatter 包含 `version`,只校验 `name``description`
49+
- `chattool setup alias` 的自定义多选现在统一走 `utils/tui.py` 的 checkbox 封装,交互里显式显示 `☑/☐` 勾选态,不再只靠高亮区分选中项
4950

5051
### Added
5152
- 新增根目录 `Dockerfile.playground`,用于直接构建一个最小的 ChatTool Playground 镜像;镜像在 `/opt/venv` 中安装 ChatTool,容器启动后会线性执行 `chattool setup playground -> chattool env set CHATTOOL_SKILLS_DIR -> chattool setup alias`

src/chattool/setup/alias.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import click
55

66
from chattool.utils.custom_logger import setup_logger
7-
from chattool.utils.tui import ask_select, is_interactive_available
7+
from chattool.utils.tui import ask_checkbox, ask_select, is_interactive_available
88

99
logger = setup_logger("setup_alias")
1010

@@ -86,16 +86,20 @@ def select_aliases_interactively(default_selected):
8686
return list(ALIAS_MAP.keys())
8787
if preset == "Unselect all":
8888
return []
89-
import questionary
89+
from chattool.utils.tui import create_choice
9090

9191
choices = [
92-
questionary.Choice(title=f"{name} => {cmd}", value=name, checked=name in default_selected)
92+
create_choice(
93+
title=f"{name} => {cmd}",
94+
value=name,
95+
checked=name in default_selected,
96+
)
9397
for name, cmd in ALIAS_MAP.items()
9498
]
95-
selected = questionary.checkbox(
99+
selected = ask_checkbox(
96100
"Select aliases (Space to toggle, A select/unselect all)",
97101
choices=choices,
98-
).ask()
102+
)
99103
return selected or []
100104

101105

src/chattool/utils/tui.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
from contextlib import contextmanager
2+
3+
14
BACK_VALUE = "__BACK__"
5+
CHECKBOX_SELECTED_INDICATOR = "☑"
6+
CHECKBOX_UNSELECTED_INDICATOR = "☐"
27

38
def get_style():
49
"""Custom style for questionary."""
@@ -47,10 +52,10 @@ def get_separator():
4752
import questionary
4853
return questionary.Separator()
4954

50-
def create_choice(title, value):
55+
def create_choice(title, value, checked=False):
5156
"""Return a questionary Choice."""
5257
import questionary
53-
return questionary.Choice(title=title, value=value)
58+
return questionary.Choice(title=title, value=value, checked=checked)
5459

5560
def ask_select(message, choices, style=None):
5661
"""Ask a selection question with escape back support."""
@@ -64,6 +69,45 @@ def ask_select(message, choices, style=None):
6469
use_arrow_keys=True
6570
))
6671

72+
73+
@contextmanager
74+
def checkbox_indicator_style():
75+
"""Temporarily render questionary checkboxes as checkbox glyphs."""
76+
import questionary.constants as constants
77+
import questionary.prompts.common as common
78+
79+
old_selected = constants.INDICATOR_SELECTED
80+
old_unselected = constants.INDICATOR_UNSELECTED
81+
old_common_selected = common.INDICATOR_SELECTED
82+
old_common_unselected = common.INDICATOR_UNSELECTED
83+
84+
constants.INDICATOR_SELECTED = CHECKBOX_SELECTED_INDICATOR
85+
constants.INDICATOR_UNSELECTED = CHECKBOX_UNSELECTED_INDICATOR
86+
common.INDICATOR_SELECTED = CHECKBOX_SELECTED_INDICATOR
87+
common.INDICATOR_UNSELECTED = CHECKBOX_UNSELECTED_INDICATOR
88+
try:
89+
yield
90+
finally:
91+
constants.INDICATOR_SELECTED = old_selected
92+
constants.INDICATOR_UNSELECTED = old_unselected
93+
common.INDICATOR_SELECTED = old_common_selected
94+
common.INDICATOR_UNSELECTED = old_common_unselected
95+
96+
97+
def ask_checkbox(message, choices, style=None, instruction=None):
98+
"""Ask a checkbox question with shared style and escape back support."""
99+
import questionary
100+
if style is None:
101+
style = get_style()
102+
with checkbox_indicator_style():
103+
return ask_with_escape_back(questionary.checkbox(
104+
message,
105+
choices=choices,
106+
style=style,
107+
use_arrow_keys=True,
108+
instruction=instruction,
109+
))
110+
67111
def ask_text(message, default="", password=False, style=None):
68112
"""Ask a text question with escape back support."""
69113
import questionary

tests/test_setup_alias.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
render_alias_block,
99
resolve_shell,
1010
resolve_shell_rc,
11+
select_aliases_interactively,
1112
setup_alias,
1213
)
1314

@@ -70,3 +71,24 @@ def test_setup_alias_dry_run_does_not_write(tmp_path, monkeypatch, capsys):
7071
out = capsys.readouterr().out
7172
assert "[dry-run] target shell rc:" in out
7273
assert "alias chatenv='chattool env'" in out
74+
75+
76+
def test_select_aliases_interactively_custom_uses_checkbox(monkeypatch):
77+
monkeypatch.setattr("chattool.setup.alias.ask_select", lambda *args, **kwargs: "Custom")
78+
79+
captured = {}
80+
81+
def fake_ask_checkbox(message, choices, style=None, instruction=None):
82+
captured["message"] = message
83+
captured["choices"] = choices
84+
return ["chatgh"]
85+
86+
monkeypatch.setattr("chattool.setup.alias.ask_checkbox", fake_ask_checkbox)
87+
selected = select_aliases_interactively(["chatenv", "chatgh"])
88+
89+
assert selected == ["chatgh"]
90+
assert captured["message"] == "Select aliases (Space to toggle, A select/unselect all)"
91+
checked = {choice.value: choice.checked for choice in captured["choices"]}
92+
assert checked["chatenv"] is True
93+
assert checked["chatgh"] is True
94+
assert checked["chatdns"] is False

0 commit comments

Comments
 (0)