oh-pi 0.1.82 → 0.1.84
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.
- package/dist/index.js +4 -118
- package/dist/locales.js +3 -0
- package/dist/tui/agents-select.d.ts +1 -1
- package/dist/tui/agents-select.js +2 -1
- package/dist/tui/config-wizard.d.ts +6 -0
- package/dist/tui/config-wizard.js +126 -0
- package/dist/tui/config-wizard.test.d.ts +1 -0
- package/dist/tui/config-wizard.test.js +30 -0
- package/dist/tui/extension-select.d.ts +1 -1
- package/dist/tui/extension-select.js +6 -2
- package/dist/tui/horizontal-tabs.d.ts +8 -0
- package/dist/tui/horizontal-tabs.js +21 -15
- package/dist/tui/horizontal-tabs.test.d.ts +1 -0
- package/dist/tui/horizontal-tabs.test.js +17 -0
- package/dist/tui/keybinding-select.d.ts +1 -1
- package/dist/tui/keybinding-select.js +2 -1
- package/dist/tui/theme-select.d.ts +1 -1
- package/dist/tui/theme-select.js +2 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
1
|
import { selectLanguage, getLocale } from "./i18n.js";
|
|
3
|
-
import { t } from "./i18n.js";
|
|
4
2
|
import { welcome } from "./tui/welcome.js";
|
|
5
3
|
import { selectMode } from "./tui/mode-select.js";
|
|
6
4
|
import { setupProviders } from "./tui/provider-setup.js";
|
|
7
5
|
import { selectPreset } from "./tui/preset-select.js";
|
|
8
|
-
import {
|
|
9
|
-
import { selectKeybindings } from "./tui/keybinding-select.js";
|
|
10
|
-
import { selectExtensions } from "./tui/extension-select.js";
|
|
11
|
-
import { selectAgents } from "./tui/agents-select.js";
|
|
12
|
-
import { runHorizontalTabs } from "./tui/horizontal-tabs.js";
|
|
6
|
+
import { runConfigWizard } from "./tui/config-wizard.js";
|
|
13
7
|
import { confirmApply } from "./tui/confirm-apply.js";
|
|
14
8
|
import { detectEnv } from "./utils/detect.js";
|
|
15
9
|
import { EXTENSIONS } from "./registry.js";
|
|
@@ -58,7 +52,7 @@ async function quickFlow(env) {
|
|
|
58
52
|
*/
|
|
59
53
|
async function presetFlow(env) {
|
|
60
54
|
const preset = await selectPreset();
|
|
61
|
-
return
|
|
55
|
+
return runConfigWizard(env, preset);
|
|
62
56
|
}
|
|
63
57
|
/**
|
|
64
58
|
* 自定义配置流程。用户逐项选择主题、快捷键、扩展、代理等,并可配置高级选项(如自动压缩阈值)。
|
|
@@ -67,121 +61,13 @@ async function presetFlow(env) {
|
|
|
67
61
|
*/
|
|
68
62
|
async function customFlow(env) {
|
|
69
63
|
const defaultExtensions = EXTENSIONS.filter(e => e.default).map(e => e.name);
|
|
70
|
-
|
|
64
|
+
const initial = {
|
|
71
65
|
theme: "dark",
|
|
72
66
|
keybindings: "default",
|
|
73
67
|
extensions: defaultExtensions,
|
|
74
68
|
prompts: ["review", "fix", "explain", "commit", "test", "refactor", "optimize", "security", "document", "pr"],
|
|
75
69
|
agents: "general-developer",
|
|
76
70
|
thinking: "medium",
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
async function runTabbedFlow(env, initial) {
|
|
80
|
-
const defaultExtensions = EXTENSIONS.filter(e => e.default).map(e => e.name);
|
|
81
|
-
let providerSetup = null;
|
|
82
|
-
let theme = initial.theme;
|
|
83
|
-
let keybindings = initial.keybindings;
|
|
84
|
-
let extensions = initial.extensions.length > 0 ? [...initial.extensions] : defaultExtensions;
|
|
85
|
-
let agents = initial.agents;
|
|
86
|
-
await runHorizontalTabs({
|
|
87
|
-
title: t("custom.tabHeader"),
|
|
88
|
-
canFinish: () => !!providerSetup,
|
|
89
|
-
finishBlockedMessage: () => t("custom.needProviders"),
|
|
90
|
-
tabs: [
|
|
91
|
-
{
|
|
92
|
-
label: t("custom.tabProviders"),
|
|
93
|
-
summary: () => summarizeProviders(providerSetup),
|
|
94
|
-
details: () => providerDetails(providerSetup),
|
|
95
|
-
edit: async () => {
|
|
96
|
-
providerSetup = await setupProviders(env);
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
label: t("custom.tabAppearance"),
|
|
101
|
-
summary: () => `${t("confirm.theme")} ${theme} · ${t("confirm.keybindings")} ${keybindings}`,
|
|
102
|
-
details: () => [
|
|
103
|
-
`${chalk.dim(t("confirm.theme"))} ${chalk.cyan(theme)}`,
|
|
104
|
-
`${chalk.dim(t("confirm.keybindings"))} ${chalk.cyan(keybindings)}`,
|
|
105
|
-
],
|
|
106
|
-
edit: async () => {
|
|
107
|
-
theme = await selectTheme();
|
|
108
|
-
keybindings = await selectKeybindings();
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
label: t("custom.tabFeatures"),
|
|
113
|
-
summary: () => t("custom.tabFeaturesHint", { count: extensions.length }),
|
|
114
|
-
details: () => [
|
|
115
|
-
chalk.dim(t("confirm.extensions")),
|
|
116
|
-
extensions.length > 0 ? ` ${extensions.join(", ")}` : ` ${t("confirm.none")}`,
|
|
117
|
-
],
|
|
118
|
-
edit: async () => {
|
|
119
|
-
extensions = await selectExtensions();
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
label: t("custom.tabAgents"),
|
|
124
|
-
summary: () => `${t("confirm.agents")} ${agents}`,
|
|
125
|
-
details: () => [
|
|
126
|
-
`${chalk.dim(t("confirm.agents"))} ${chalk.cyan(agents)}`,
|
|
127
|
-
`${chalk.dim(t("confirm.thinking"))} ${chalk.cyan(initial.thinking)}`,
|
|
128
|
-
],
|
|
129
|
-
edit: async () => {
|
|
130
|
-
agents = await selectAgents();
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
label: t("custom.tabFinish"),
|
|
135
|
-
summary: () => t("custom.tabFinishHint"),
|
|
136
|
-
details: () => [
|
|
137
|
-
chalk.dim(t("custom.tabFinishHelp")),
|
|
138
|
-
],
|
|
139
|
-
edit: async () => {
|
|
140
|
-
// Finish tab is read-only; use key F to complete.
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
],
|
|
144
|
-
});
|
|
145
|
-
if (!providerSetup) {
|
|
146
|
-
throw new Error("Provider setup is required before finishing tabbed flow");
|
|
147
|
-
}
|
|
148
|
-
const finalProviderSetup = providerSetup;
|
|
149
|
-
return {
|
|
150
|
-
providers: finalProviderSetup.providers,
|
|
151
|
-
providerStrategy: finalProviderSetup.providerStrategy,
|
|
152
|
-
theme,
|
|
153
|
-
keybindings,
|
|
154
|
-
extensions,
|
|
155
|
-
prompts: initial.prompts,
|
|
156
|
-
agents,
|
|
157
|
-
thinking: initial.thinking,
|
|
158
71
|
};
|
|
159
|
-
|
|
160
|
-
function summarizeProviders(setup) {
|
|
161
|
-
if (!setup)
|
|
162
|
-
return t("custom.providersUnset");
|
|
163
|
-
if (setup.providerStrategy === "keep")
|
|
164
|
-
return t("confirm.providerStrategyKeep");
|
|
165
|
-
if (setup.providerStrategy === "add") {
|
|
166
|
-
return setup.providers.length > 0
|
|
167
|
-
? t("custom.providersAdd", { list: setup.providers.map(p => p.name).join(", ") })
|
|
168
|
-
: t("confirm.providerStrategyAdd");
|
|
169
|
-
}
|
|
170
|
-
if (setup.providers.length === 0)
|
|
171
|
-
return t("confirm.providerStrategyReplace");
|
|
172
|
-
return t("custom.providersReplace", { list: setup.providers.map(p => p.name).join(", ") });
|
|
173
|
-
}
|
|
174
|
-
function providerDetails(setup) {
|
|
175
|
-
if (!setup)
|
|
176
|
-
return [chalk.dim(t("custom.needProviders"))];
|
|
177
|
-
if (setup.providerStrategy === "keep")
|
|
178
|
-
return [chalk.dim(t("confirm.providerStrategyKeep"))];
|
|
179
|
-
if (setup.providers.length === 0)
|
|
180
|
-
return [chalk.dim(t("confirm.none"))];
|
|
181
|
-
const primary = setup.providers[0];
|
|
182
|
-
return [
|
|
183
|
-
`${chalk.dim(t("confirm.providerStrategy"))} ${chalk.cyan(setup.providerStrategy)}`,
|
|
184
|
-
`${chalk.dim(t("confirm.providers"))} ${chalk.cyan(setup.providers.map(p => p.name).join(", "))}`,
|
|
185
|
-
`${chalk.dim(t("confirm.model"))} ${chalk.cyan(primary?.defaultModel ?? t("confirm.none"))}`,
|
|
186
|
-
];
|
|
72
|
+
return runConfigWizard(env, initial);
|
|
187
73
|
}
|
package/dist/locales.js
CHANGED
|
@@ -28,6 +28,7 @@ export const messages = {
|
|
|
28
28
|
"custom.tabFinish": "Finish",
|
|
29
29
|
"custom.tabFinishHint": "Review in next step and apply",
|
|
30
30
|
"custom.tabFinishHelp": "Press F to finish. Press Enter on other tabs to edit.",
|
|
31
|
+
"custom.finishReady": "Required sections complete. Proceed to review and apply.",
|
|
31
32
|
"custom.tabControls": "Controls: ←/→ switch tabs · Enter edit tab · 1-5 jump · F finish · Ctrl+C cancel",
|
|
32
33
|
"custom.needProviders": "Please configure Providers first.",
|
|
33
34
|
"custom.providersUnset": "Providers: not configured",
|
|
@@ -195,6 +196,7 @@ export const messages = {
|
|
|
195
196
|
"custom.tabFinish": "完成",
|
|
196
197
|
"custom.tabFinishHint": "下一步可预览并应用",
|
|
197
198
|
"custom.tabFinishHelp": "按 F 完成;在其他页面按 Enter 进入编辑。",
|
|
199
|
+
"custom.finishReady": "必填项已完成,可进入预览并应用。",
|
|
198
200
|
"custom.tabControls": "操作:←/→ 切换标签 · Enter 编辑当前标签 · 1-5 快速跳转 · F 完成 · Ctrl+C 取消",
|
|
199
201
|
"custom.needProviders": "请先配置 Providers。",
|
|
200
202
|
"custom.providersUnset": "Providers:未配置",
|
|
@@ -353,6 +355,7 @@ export const messages = {
|
|
|
353
355
|
"custom.tabFinish": "Terminer",
|
|
354
356
|
"custom.tabFinishHint": "Prévisualiser puis appliquer à l'étape suivante",
|
|
355
357
|
"custom.tabFinishHelp": "Appuyez sur F pour terminer ; appuyez sur Entrée sur les autres onglets pour éditer.",
|
|
358
|
+
"custom.finishReady": "Sections obligatoires terminées. Passez à la revue puis appliquez.",
|
|
356
359
|
"custom.tabControls": "Contrôles : ←/→ changer d'onglet · Entrée éditer · 1-5 accès rapide · F terminer · Ctrl+C annuler",
|
|
357
360
|
"custom.needProviders": "Veuillez d'abord configurer les fournisseurs.",
|
|
358
361
|
"custom.providersUnset": "Fournisseurs : non configurés",
|
|
@@ -8,7 +8,7 @@ import { t } from "../i18n.js";
|
|
|
8
8
|
*
|
|
9
9
|
* @returns The selected agent template identifier string.
|
|
10
10
|
*/
|
|
11
|
-
export async function selectAgents() {
|
|
11
|
+
export async function selectAgents(initialValue) {
|
|
12
12
|
const agent = await p.select({
|
|
13
13
|
message: t("agent.select"),
|
|
14
14
|
options: [
|
|
@@ -18,6 +18,7 @@ export async function selectAgents() {
|
|
|
18
18
|
{ value: "data-ai-engineer", label: t("agent.dataai"), hint: t("agent.dataaiHint") },
|
|
19
19
|
{ value: "colony-operator", label: t("agent.colony"), hint: t("agent.colonyHint") },
|
|
20
20
|
],
|
|
21
|
+
initialValue,
|
|
21
22
|
});
|
|
22
23
|
if (p.isCancel(agent)) {
|
|
23
24
|
p.cancel(t("cancelled"));
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { OhPConfig } from "../types.js";
|
|
2
|
+
import type { EnvInfo } from "../utils/detect.js";
|
|
3
|
+
import { type ProviderSetupResult } from "./provider-setup.js";
|
|
4
|
+
export type WizardBaseConfig = Pick<OhPConfig, "theme" | "keybindings" | "extensions" | "prompts" | "agents" | "thinking">;
|
|
5
|
+
export declare function runConfigWizard(env: EnvInfo, initial: WizardBaseConfig): Promise<OhPConfig>;
|
|
6
|
+
export declare function summarizeProviders(setup: ProviderSetupResult | null): string;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { t } from "../i18n.js";
|
|
4
|
+
import { EXTENSIONS } from "../registry.js";
|
|
5
|
+
import { selectAgents } from "./agents-select.js";
|
|
6
|
+
import { selectExtensions } from "./extension-select.js";
|
|
7
|
+
import { selectKeybindings } from "./keybinding-select.js";
|
|
8
|
+
import { setupProviders } from "./provider-setup.js";
|
|
9
|
+
import { selectTheme } from "./theme-select.js";
|
|
10
|
+
function sectionLabel(label, done) {
|
|
11
|
+
return done
|
|
12
|
+
? `${label} ${chalk.green("✓")}`
|
|
13
|
+
: `${label} ${chalk.yellow("•")}`;
|
|
14
|
+
}
|
|
15
|
+
function summarizeAppearance(theme, keybindings) {
|
|
16
|
+
return `${t("confirm.theme")} ${theme} · ${t("confirm.keybindings")} ${keybindings}`;
|
|
17
|
+
}
|
|
18
|
+
function summarizeFeatures(extensions) {
|
|
19
|
+
return t("custom.tabFeaturesHint", { count: extensions.length });
|
|
20
|
+
}
|
|
21
|
+
function summarizeAgents(agents) {
|
|
22
|
+
return `${t("confirm.agents")} ${agents}`;
|
|
23
|
+
}
|
|
24
|
+
function buildWizardOptions(state) {
|
|
25
|
+
return [
|
|
26
|
+
{
|
|
27
|
+
value: "providers",
|
|
28
|
+
label: sectionLabel(t("custom.tabProviders"), !!state.providerSetup),
|
|
29
|
+
hint: summarizeProviders(state.providerSetup),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
value: "appearance",
|
|
33
|
+
label: sectionLabel(t("custom.tabAppearance"), true),
|
|
34
|
+
hint: summarizeAppearance(state.theme, state.keybindings),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
value: "features",
|
|
38
|
+
label: sectionLabel(t("custom.tabFeatures"), true),
|
|
39
|
+
hint: summarizeFeatures(state.extensions),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
value: "agents",
|
|
43
|
+
label: sectionLabel(t("custom.tabAgents"), true),
|
|
44
|
+
hint: summarizeAgents(state.agents),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
value: "finish",
|
|
48
|
+
label: sectionLabel(t("custom.tabFinish"), !!state.providerSetup),
|
|
49
|
+
hint: state.providerSetup ? t("custom.finishReady") : t("custom.needProviders"),
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
export async function runConfigWizard(env, initial) {
|
|
54
|
+
const defaultExtensions = EXTENSIONS.filter(e => e.default).map(e => e.name);
|
|
55
|
+
const state = {
|
|
56
|
+
providerSetup: null,
|
|
57
|
+
theme: initial.theme,
|
|
58
|
+
keybindings: initial.keybindings,
|
|
59
|
+
extensions: initial.extensions.length > 0 ? [...initial.extensions] : defaultExtensions,
|
|
60
|
+
prompts: initial.prompts,
|
|
61
|
+
agents: initial.agents,
|
|
62
|
+
thinking: initial.thinking,
|
|
63
|
+
};
|
|
64
|
+
let nextStep = "providers";
|
|
65
|
+
while (true) {
|
|
66
|
+
const step = await p.select({
|
|
67
|
+
message: t("custom.tabPrompt"),
|
|
68
|
+
options: buildWizardOptions(state),
|
|
69
|
+
initialValue: nextStep,
|
|
70
|
+
});
|
|
71
|
+
if (p.isCancel(step)) {
|
|
72
|
+
p.cancel(t("cancelled"));
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
if (step === "providers") {
|
|
76
|
+
state.providerSetup = await setupProviders(env);
|
|
77
|
+
nextStep = "appearance";
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (step === "appearance") {
|
|
81
|
+
state.theme = await selectTheme(state.theme);
|
|
82
|
+
state.keybindings = await selectKeybindings(state.keybindings);
|
|
83
|
+
nextStep = "features";
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (step === "features") {
|
|
87
|
+
state.extensions = await selectExtensions(state.extensions);
|
|
88
|
+
nextStep = "agents";
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (step === "agents") {
|
|
92
|
+
state.agents = await selectAgents(state.agents);
|
|
93
|
+
nextStep = "finish";
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (!state.providerSetup) {
|
|
97
|
+
p.log.warn(t("custom.needProviders"));
|
|
98
|
+
nextStep = "providers";
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
providers: state.providerSetup.providers,
|
|
103
|
+
providerStrategy: state.providerSetup.providerStrategy,
|
|
104
|
+
theme: state.theme,
|
|
105
|
+
keybindings: state.keybindings,
|
|
106
|
+
extensions: state.extensions,
|
|
107
|
+
prompts: state.prompts,
|
|
108
|
+
agents: state.agents,
|
|
109
|
+
thinking: state.thinking,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export function summarizeProviders(setup) {
|
|
114
|
+
if (!setup)
|
|
115
|
+
return t("custom.providersUnset");
|
|
116
|
+
if (setup.providerStrategy === "keep")
|
|
117
|
+
return t("confirm.providerStrategyKeep");
|
|
118
|
+
if (setup.providerStrategy === "add") {
|
|
119
|
+
return setup.providers.length > 0
|
|
120
|
+
? t("custom.providersAdd", { list: setup.providers.map(p => p.name).join(", ") })
|
|
121
|
+
: t("confirm.providerStrategyAdd");
|
|
122
|
+
}
|
|
123
|
+
if (setup.providers.length === 0)
|
|
124
|
+
return t("confirm.providerStrategyReplace");
|
|
125
|
+
return t("custom.providersReplace", { list: setup.providers.map(p => p.name).join(", ") });
|
|
126
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { summarizeProviders } from "./config-wizard.js";
|
|
3
|
+
const setup = (overrides) => ({
|
|
4
|
+
providerStrategy: "replace",
|
|
5
|
+
providers: [],
|
|
6
|
+
...overrides,
|
|
7
|
+
});
|
|
8
|
+
describe("summarizeProviders", () => {
|
|
9
|
+
it("returns unset copy when setup is missing", () => {
|
|
10
|
+
expect(summarizeProviders(null)).toBe("Providers: not configured");
|
|
11
|
+
});
|
|
12
|
+
it("returns keep strategy copy", () => {
|
|
13
|
+
expect(summarizeProviders(setup({ providerStrategy: "keep" }))).toBe("keep existing");
|
|
14
|
+
});
|
|
15
|
+
it("returns add copy with provider list", () => {
|
|
16
|
+
expect(summarizeProviders(setup({
|
|
17
|
+
providerStrategy: "add",
|
|
18
|
+
providers: [{ name: "openai", apiKey: "OPENAI_API_KEY", defaultModel: "gpt-4o" }],
|
|
19
|
+
}))).toBe("Add fallback providers: openai");
|
|
20
|
+
});
|
|
21
|
+
it("returns add fallback copy without providers", () => {
|
|
22
|
+
expect(summarizeProviders(setup({ providerStrategy: "add", providers: [] }))).toBe("keep existing and add fallback providers");
|
|
23
|
+
});
|
|
24
|
+
it("returns replace summary with provider list", () => {
|
|
25
|
+
expect(summarizeProviders(setup({
|
|
26
|
+
providerStrategy: "replace",
|
|
27
|
+
providers: [{ name: "anthropic", apiKey: "ANTHROPIC_API_KEY", defaultModel: "claude-sonnet-4-20250514" }],
|
|
28
|
+
}))).toBe("Replace providers with: anthropic");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* Exits the process if the user cancels the selection.
|
|
4
4
|
* @returns A promise that resolves to an array of selected extension names.
|
|
5
5
|
*/
|
|
6
|
-
export declare function selectExtensions(): Promise<string[]>;
|
|
6
|
+
export declare function selectExtensions(initialValues?: string[]): Promise<string[]>;
|
|
@@ -6,14 +6,18 @@ import { EXTENSIONS } from "../registry.js";
|
|
|
6
6
|
* Exits the process if the user cancels the selection.
|
|
7
7
|
* @returns A promise that resolves to an array of selected extension names.
|
|
8
8
|
*/
|
|
9
|
-
export async function selectExtensions() {
|
|
9
|
+
export async function selectExtensions(initialValues) {
|
|
10
|
+
const fallbackDefaults = EXTENSIONS.filter(e => e.default).map(e => e.name);
|
|
11
|
+
const validValues = new Set(EXTENSIONS.map(e => e.name));
|
|
12
|
+
const seeded = (initialValues && initialValues.length > 0 ? initialValues : fallbackDefaults)
|
|
13
|
+
.filter(name => validValues.has(name));
|
|
10
14
|
const exts = await p.multiselect({
|
|
11
15
|
message: t("ext.select"),
|
|
12
16
|
options: EXTENSIONS.map(e => ({
|
|
13
17
|
value: e.name,
|
|
14
18
|
label: e.label,
|
|
15
19
|
})),
|
|
16
|
-
initialValues:
|
|
20
|
+
initialValues: seeded,
|
|
17
21
|
});
|
|
18
22
|
if (p.isCancel(exts)) {
|
|
19
23
|
p.cancel(t("cancelled"));
|
|
@@ -10,5 +10,13 @@ interface HorizontalTabsOptions {
|
|
|
10
10
|
canFinish: () => boolean;
|
|
11
11
|
finishBlockedMessage?: () => string;
|
|
12
12
|
}
|
|
13
|
+
type TabAction = "left" | "right" | "edit" | "finish" | "cancel" | {
|
|
14
|
+
jump: number;
|
|
15
|
+
};
|
|
16
|
+
interface KeypressLike {
|
|
17
|
+
name?: string;
|
|
18
|
+
ctrl?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function mapTabAction(str: string, key: KeypressLike, tabCount: number): TabAction | null;
|
|
13
21
|
export declare function runHorizontalTabs(opts: HorizontalTabsOptions): Promise<void>;
|
|
14
22
|
export {};
|
|
@@ -5,6 +5,24 @@ import { t } from "../i18n.js";
|
|
|
5
5
|
function clearScreen() {
|
|
6
6
|
process.stdout.write("\x1b[2J\x1b[H");
|
|
7
7
|
}
|
|
8
|
+
export function mapTabAction(str, key, tabCount) {
|
|
9
|
+
if (key.ctrl && key.name === "c")
|
|
10
|
+
return "cancel";
|
|
11
|
+
if (key.name === "left")
|
|
12
|
+
return "left";
|
|
13
|
+
if (key.name === "right")
|
|
14
|
+
return "right";
|
|
15
|
+
if (key.name === "return" || key.name === "enter" || key.name === "space" || key.name === "e")
|
|
16
|
+
return "edit";
|
|
17
|
+
if (key.name === "f")
|
|
18
|
+
return "finish";
|
|
19
|
+
if (/^[1-9]$/.test(str)) {
|
|
20
|
+
const idx = Number(str) - 1;
|
|
21
|
+
if (idx >= 0 && idx < tabCount)
|
|
22
|
+
return { jump: idx };
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
8
26
|
function waitForAction(tabCount) {
|
|
9
27
|
return new Promise((resolve) => {
|
|
10
28
|
const stdin = process.stdin;
|
|
@@ -19,21 +37,9 @@ function waitForAction(tabCount) {
|
|
|
19
37
|
resolve(action);
|
|
20
38
|
};
|
|
21
39
|
const onKeypress = (str, key) => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return done("left");
|
|
26
|
-
if (key.name === "right")
|
|
27
|
-
return done("right");
|
|
28
|
-
if (key.name === "return" || key.name === "space" || key.name === "e")
|
|
29
|
-
return done("edit");
|
|
30
|
-
if (key.name === "f")
|
|
31
|
-
return done("finish");
|
|
32
|
-
if (/^[1-9]$/.test(str)) {
|
|
33
|
-
const idx = Number(str) - 1;
|
|
34
|
-
if (idx >= 0 && idx < tabCount)
|
|
35
|
-
return done({ jump: idx });
|
|
36
|
-
}
|
|
40
|
+
const action = mapTabAction(str, key, tabCount);
|
|
41
|
+
if (action)
|
|
42
|
+
done(action);
|
|
37
43
|
};
|
|
38
44
|
stdin.on("keypress", onKeypress);
|
|
39
45
|
if (!stdin.isTTY) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { mapTabAction } from "./horizontal-tabs.js";
|
|
3
|
+
describe("mapTabAction", () => {
|
|
4
|
+
it("maps enter and return to edit", () => {
|
|
5
|
+
expect(mapTabAction("", { name: "enter" }, 5)).toBe("edit");
|
|
6
|
+
expect(mapTabAction("", { name: "return" }, 5)).toBe("edit");
|
|
7
|
+
});
|
|
8
|
+
it("maps arrows and finish", () => {
|
|
9
|
+
expect(mapTabAction("", { name: "left" }, 5)).toBe("left");
|
|
10
|
+
expect(mapTabAction("", { name: "right" }, 5)).toBe("right");
|
|
11
|
+
expect(mapTabAction("", { name: "f" }, 5)).toBe("finish");
|
|
12
|
+
});
|
|
13
|
+
it("maps jump index within range", () => {
|
|
14
|
+
expect(mapTabAction("3", { name: "3" }, 5)).toEqual({ jump: 2 });
|
|
15
|
+
expect(mapTabAction("9", { name: "9" }, 5)).toBeNull();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -7,7 +7,7 @@ import { t } from "../i18n.js";
|
|
|
7
7
|
*
|
|
8
8
|
* @returns The name of the selected keybinding scheme.
|
|
9
9
|
*/
|
|
10
|
-
export async function selectKeybindings() {
|
|
10
|
+
export async function selectKeybindings(initialValue) {
|
|
11
11
|
const kb = await p.select({
|
|
12
12
|
message: t("kb.select"),
|
|
13
13
|
options: [
|
|
@@ -15,6 +15,7 @@ export async function selectKeybindings() {
|
|
|
15
15
|
{ value: "vim", label: t("kb.vim"), hint: t("kb.vimHint") },
|
|
16
16
|
{ value: "emacs", label: t("kb.emacs"), hint: t("kb.emacsHint") },
|
|
17
17
|
],
|
|
18
|
+
initialValue,
|
|
18
19
|
});
|
|
19
20
|
if (p.isCancel(kb)) {
|
|
20
21
|
p.cancel(t("cancelled"));
|
package/dist/tui/theme-select.js
CHANGED
|
@@ -6,13 +6,14 @@ import { THEMES } from "../registry.js";
|
|
|
6
6
|
* Exits the process if the user cancels the selection.
|
|
7
7
|
* @returns The name of the selected theme.
|
|
8
8
|
*/
|
|
9
|
-
export async function selectTheme() {
|
|
9
|
+
export async function selectTheme(initialValue) {
|
|
10
10
|
const theme = await p.select({
|
|
11
11
|
message: t("theme.select"),
|
|
12
12
|
options: THEMES.map(th => ({
|
|
13
13
|
value: th.name,
|
|
14
14
|
label: `${th.style === "dark" ? "🌙" : "☀️"} ${th.label}`,
|
|
15
15
|
})),
|
|
16
|
+
initialValue,
|
|
16
17
|
});
|
|
17
18
|
if (p.isCancel(theme)) {
|
|
18
19
|
p.cancel(t("cancelled"));
|