archymedes-cli 2.2.0 → 2.5.0

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.
@@ -1,13 +1,14 @@
1
1
  import {
2
2
  SpringAnimator,
3
3
  paginator
4
- } from "./archymedes-r54qthg5.mjs";
4
+ } from "./archymedes-5vhvv8kr.mjs";
5
5
  import {
6
6
  UNICODE_GLYPHS,
7
+ clipTo,
7
8
  visibleWidth
8
- } from "./archymedes-5zn4zp84.mjs";
9
+ } from "./archymedes-130jt0wr.mjs";
9
10
 
10
- // packages/archymedes-cli/src/chooser.ts
11
+ // packages/archymedes-cli/src/ui/chooser.ts
11
12
  var ALL_CHROME = { title: true, status: true, pagination: true, help: true };
12
13
  function chromeOf(chrome) {
13
14
  return { ...ALL_CHROME, ...chrome };
@@ -49,6 +50,7 @@ function advanceChooser(state, items, input, options = {}) {
49
50
  const last = Math.max(0, visible.length - 1);
50
51
  const page = options.page ?? 8;
51
52
  const clamp = (index) => Math.max(0, Math.min(last, index));
53
+ state = { ...state, selected: clamp(state.selected) };
52
54
  const keep = state.chrome === undefined ? {} : { chrome: state.chrome };
53
55
  if (name === "escape" && options.filter && state.query !== "")
54
56
  return { state: { selected: 0, query: "", ...keep } };
@@ -121,7 +123,7 @@ function renderChooser(state, items, options) {
121
123
  const fadingOut = !active && index === options.transitionFrom;
122
124
  const number = offset < 9 ? `${offset + 1}.` : " ";
123
125
  const furniture = visibleWidth(` ${active ? ">" : " "} ${number} `) + 2;
124
- if (width < furniture) {
126
+ if (width < furniture + 1) {
125
127
  lines.push(active ? paint.green(clip(`${glyphs.prompt}${item.label}`, width)) : clip(item.label, width));
126
128
  continue;
127
129
  }
@@ -141,7 +143,8 @@ function renderChooser(state, items, options) {
141
143
  }
142
144
  const position = visible.length > height && chrome.pagination ? ` ${paginator(state.selected, visible.length)}` : "";
143
145
  if (chrome.help) {
144
- lines.push(paint.dim(clip(` ${options.legend ?? `${glyphs.arrowUp}${glyphs.arrowDown} move ${glyphs.middot} Enter choose ${glyphs.middot} ${options.filter ? `type to filter ${glyphs.middot} ` : ""}${options.filter ? "Esc clear/cancel" : "Esc cancel"}`}${position}`, width)));
146
+ const legend = options.legend !== undefined && options.filter && state.query !== "" ? options.legend.replace(/Esc\b[^·]*$/, "Esc clear filter") : options.legend;
147
+ lines.push(paint.dim(clip(` ${legend ?? `${glyphs.arrowUp}${glyphs.arrowDown} move ${glyphs.middot} Enter choose ${glyphs.middot} ${options.filter ? `type to filter ${glyphs.middot} ` : ""}${options.filter ? "Esc clear/back" : "Esc back"}`}${position}`, width)));
145
148
  } else if (position) {
146
149
  lines.push(paint.dim(clip(position, width)));
147
150
  }
@@ -150,22 +153,6 @@ function renderChooser(state, items, options) {
150
153
  return lines.join(`
151
154
  `);
152
155
  }
153
- function clipTo(text, width, glyphs = UNICODE_GLYPHS) {
154
- if (width <= 0)
155
- return "";
156
- if (visibleWidth(text) <= width)
157
- return text;
158
- const mark = visibleWidth(glyphs.ellipsis) < width ? glyphs.ellipsis : "";
159
- const budget = width - visibleWidth(mark);
160
- let out = "";
161
- const segments = typeof Intl.Segmenter === "function" ? [...new Intl.Segmenter(undefined, { granularity: "grapheme" }).segment(text)].map((entry) => entry.segment) : [...text];
162
- for (const segment of segments) {
163
- if (visibleWidth(out + segment) > budget)
164
- break;
165
- out += segment;
166
- }
167
- return `${out}${mark}`;
168
- }
169
156
  async function runChooser(keys, items, paint, options) {
170
157
  let state = { selected: Math.max(0, Math.min(options.initialIndex ?? 0, Math.max(0, items.length - 1))), query: "" };
171
158
  const liveOptions = () => {
@@ -173,7 +160,9 @@ async function runChooser(keys, items, paint, options) {
173
160
  const height = Math.max(1, Math.min(options.height ?? 10, size?.height ?? Number.POSITIVE_INFINITY));
174
161
  return { ...options, width: size?.width ?? options.width, height };
175
162
  };
176
- paint(renderChooser(state, items, liveOptions()));
163
+ let list = items;
164
+ let pendingDelete;
165
+ paint(renderChooser(state, list, liveOptions()));
177
166
  let glide;
178
167
  const settleGlide = () => {
179
168
  glide?.stop();
@@ -181,9 +170,36 @@ async function runChooser(keys, items, paint, options) {
181
170
  };
182
171
  try {
183
172
  for await (const input of keys) {
173
+ if (pendingDelete && options.onDelete) {
174
+ const target = pendingDelete;
175
+ pendingDelete = undefined;
176
+ if (input.str === "y" || input.str === "Y") {
177
+ const outcome = await options.onDelete(target);
178
+ if (outcome.deleted) {
179
+ list = list.filter((item) => item !== target);
180
+ const remaining = filterItems(list, state.query).length;
181
+ state = { ...state, selected: Math.max(0, Math.min(state.selected, remaining - 1)), status: `deleted "${target.label}"` };
182
+ } else {
183
+ state = { ...state, status: outcome.reason };
184
+ }
185
+ } else {
186
+ state = { ...state, status: "kept" };
187
+ }
188
+ paint(renderChooser(state, list, liveOptions()));
189
+ continue;
190
+ }
191
+ if (options.onDelete && (input.key.name === "delete" || input.key.ctrl && input.key.name === "d")) {
192
+ const target = filterItems(list, state.query)[state.selected];
193
+ if (target && !target.pinned) {
194
+ pendingDelete = target;
195
+ state = { ...state, status: `Delete "${target.label}"? y/n` };
196
+ paint(renderChooser(state, list, liveOptions()));
197
+ }
198
+ continue;
199
+ }
184
200
  const current = liveOptions();
185
201
  const previousSelected = state.selected;
186
- const step = advanceChooser(state, items, input, {
202
+ const step = advanceChooser(state, list, input, {
187
203
  filter: options.filter ?? false,
188
204
  page: options.page ?? current.height ?? 8,
189
205
  ...current.height === undefined ? {} : { height: current.height }
@@ -193,24 +209,24 @@ async function runChooser(keys, items, paint, options) {
193
209
  settleGlide();
194
210
  if (step.done.index === undefined)
195
211
  return;
196
- return filterItems(items, state.query)[step.done.index]?.value;
212
+ return filterItems(list, state.query)[step.done.index]?.value;
197
213
  }
198
214
  settleGlide();
199
215
  const isSingleStep = (input.key.name === "up" || input.key.name === "down") && Math.abs(state.selected - previousSelected) === 1;
200
216
  const motion = options.motion ?? (process.env.ARCHYMEDES_NO_MOTION !== "1" && process.env.NO_COLOR === undefined && process.env.TERM !== "dumb");
201
217
  if (!motion || !isSingleStep || previousSelected === state.selected) {
202
- paint(renderChooser(state, items, liveOptions()));
218
+ paint(renderChooser(state, list, liveOptions()));
203
219
  continue;
204
220
  }
205
- paint(renderChooser(state, items, { ...liveOptions(), transitionFrom: previousSelected, focusProgress: 0 }));
221
+ paint(renderChooser(state, list, { ...liveOptions(), transitionFrom: previousSelected, focusProgress: 0 }));
206
222
  const animator = new SpringAnimator(0, (value) => {
207
223
  if (value < 0.95) {
208
- paint(renderChooser(state, items, { ...liveOptions(), transitionFrom: value < 0.5 ? previousSelected : undefined, focusProgress: value }));
224
+ paint(renderChooser(state, list, { ...liveOptions(), transitionFrom: value < 0.5 ? previousSelected : undefined, focusProgress: value }));
209
225
  return;
210
226
  }
211
227
  animator.stop();
212
228
  glide = undefined;
213
- paint(renderChooser(state, items, liveOptions()));
229
+ paint(renderChooser(state, list, liveOptions()));
214
230
  }, { intervalMs: 30 });
215
231
  glide = animator;
216
232
  animator.retarget(1);
@@ -221,4 +237,4 @@ async function runChooser(keys, items, paint, options) {
221
237
  return;
222
238
  }
223
239
 
224
- export { INITIAL_CHOOSER_STATE, windowStart, terminalColumns, renderChooser, clipTo, runChooser };
240
+ export { INITIAL_CHOOSER_STATE, filterItems, windowStart, terminalColumns, renderChooser, runChooser };
@@ -1,52 +1,25 @@
1
1
  import {
2
+ BOLD,
3
+ DIM,
2
4
  UNICODE_GLYPHS,
3
5
  borderGlyphsFor,
6
+ paint,
7
+ paintAll,
8
+ roleCode,
4
9
  visibleWidth
5
- } from "./archymedes-5zn4zp84.mjs";
10
+ } from "./archymedes-130jt0wr.mjs";
6
11
 
7
- // packages/archymedes-cli/src/ansi.ts
8
- var RESET = "\x1B[0m";
9
- var BOLD = "\x1B[1m";
10
- var DIM = "\x1B[2m";
11
- var REVERSE = "\x1B[7m";
12
- var RED = "\x1B[31m";
13
- var GREEN = "\x1B[32m";
14
- var YELLOW = "\x1B[33m";
15
- var MAGENTA = "\x1B[35m";
16
- var CYAN = "\x1B[36m";
17
- function paint(text, code, depth) {
18
- return depth === "none" || code === "" ? text : `${code}${text}${RESET}`;
19
- }
20
- function paintAll(text, codes, depth) {
21
- return depth === "none" || codes.length === 0 ? text : `${codes.join("")}${text}${RESET}`;
22
- }
23
-
24
- // packages/archymedes-cli/src/sections.ts
25
- var FALLBACK_TONE_CODE = {
26
- neutral: DIM,
27
- good: GREEN,
28
- bad: RED,
29
- warn: YELLOW,
30
- accent: CYAN
12
+ // packages/archymedes-cli/src/render/sections.ts
13
+ var TONE_ROLE = {
14
+ good: "success",
15
+ bad: "error",
16
+ warn: "warning",
17
+ accent: "primary"
31
18
  };
32
19
  function toneCode(tone, style) {
33
20
  if (style.depth === "none")
34
21
  return "";
35
- const palette = style.palette;
36
- if (!palette)
37
- return FALLBACK_TONE_CODE[tone];
38
- switch (tone) {
39
- case "neutral":
40
- return DIM;
41
- case "good":
42
- return palette.success || FALLBACK_TONE_CODE.good;
43
- case "bad":
44
- return palette.error || FALLBACK_TONE_CODE.bad;
45
- case "warn":
46
- return palette.warning || FALLBACK_TONE_CODE.warn;
47
- case "accent":
48
- return palette.primary || FALLBACK_TONE_CODE.accent;
49
- }
22
+ return tone === "neutral" ? DIM : roleCode(TONE_ROLE[tone], style.palette, style.depth);
50
23
  }
51
24
  var GUTTER = " ";
52
25
  function setOf(style) {
@@ -153,13 +126,13 @@ function outcomeMark(outcome, style) {
153
126
  const glyphs = setOf(style);
154
127
  switch (outcome) {
155
128
  case "pass":
156
- return paint(glyphs.check, GREEN, style.depth);
129
+ return paint(glyphs.check, toneCode("good", style), style.depth);
157
130
  case "fail":
158
- return paint(glyphs.cross, RED, style.depth);
131
+ return paint(glyphs.cross, toneCode("bad", style), style.depth);
159
132
  case "skip":
160
133
  return paint(glyphs.circleEmpty, DIM, style.depth);
161
134
  default:
162
- return paint(glyphs.pending, CYAN, style.depth);
135
+ return paint(glyphs.pending, toneCode("accent", style), style.depth);
163
136
  }
164
137
  }
165
138
  function keyValues(rows, style) {
@@ -168,4 +141,4 @@ function keyValues(rows, style) {
168
141
  `);
169
142
  }
170
143
 
171
- export { BOLD, DIM, REVERSE, RED, GREEN, YELLOW, MAGENTA, CYAN, paint, paintAll, GUTTER, rule, heading, note, panel, clip, outcomeMark, keyValues };
144
+ export { toneCode, GUTTER, rule, heading, note, panel, clip, outcomeMark, keyValues };