@aliou/pi-guardrails 0.5.3 → 0.6.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.
package/pattern-editor.ts CHANGED
@@ -8,16 +8,17 @@ import {
8
8
  } from "@mariozechner/pi-tui";
9
9
 
10
10
  /**
11
- * A submenu component for editing an array of {pattern, description} objects.
11
+ * A submenu component for editing an array of {pattern, description, regex?} objects.
12
12
  *
13
13
  * List mode: navigate, delete with 'd', add with 'a', edit with 'e'/Enter.
14
- * Form mode: two-field form (pattern + description), Tab to switch fields,
15
- * Enter to submit, Escape to cancel.
14
+ * Form mode: three-field form (pattern + description + regex toggle),
15
+ * Tab to switch fields, Enter to submit, Escape to cancel.
16
16
  */
17
17
 
18
18
  export interface PatternItem {
19
19
  pattern: string;
20
20
  description: string;
21
+ regex?: boolean;
21
22
  }
22
23
 
23
24
  export interface PatternEditorOptions {
@@ -26,10 +27,12 @@ export interface PatternEditorOptions {
26
27
  theme: SettingsListTheme;
27
28
  onSave: (items: PatternItem[]) => void;
28
29
  onDone: () => void;
30
+ /** Context hint for the pattern field label. */
31
+ context?: "file" | "command";
29
32
  maxVisible?: number;
30
33
  }
31
34
 
32
- type Field = "pattern" | "description";
35
+ type Field = "pattern" | "description" | "regex";
33
36
 
34
37
  export class PatternEditor implements Component {
35
38
  private items: PatternItem[];
@@ -37,6 +40,7 @@ export class PatternEditor implements Component {
37
40
  private theme: SettingsListTheme;
38
41
  private onSave: (items: PatternItem[]) => void;
39
42
  private onDone: () => void;
43
+ private context: "file" | "command";
40
44
  private selectedIndex = 0;
41
45
  private maxVisible: number;
42
46
  private mode: "list" | "add" | "edit" = "list";
@@ -46,6 +50,7 @@ export class PatternEditor implements Component {
46
50
  private patternInput: Input;
47
51
  private descriptionInput: Input;
48
52
  private activeField: Field = "pattern";
53
+ private regexEnabled = false;
49
54
 
50
55
  constructor(options: PatternEditorOptions) {
51
56
  this.items = [...options.items];
@@ -53,6 +58,7 @@ export class PatternEditor implements Component {
53
58
  this.theme = options.theme;
54
59
  this.onSave = options.onSave;
55
60
  this.onDone = options.onDone;
61
+ this.context = options.context ?? "command";
56
62
  this.maxVisible = options.maxVisible ?? 10;
57
63
 
58
64
  this.patternInput = new Input();
@@ -72,7 +78,13 @@ export class PatternEditor implements Component {
72
78
  return;
73
79
  }
74
80
 
75
- // If on description field (or pattern is empty), submit
81
+ // If on description field, move to regex toggle
82
+ if (this.activeField === "description") {
83
+ this.activeField = "regex";
84
+ return;
85
+ }
86
+
87
+ // If on regex field, submit
76
88
  this.submitForm();
77
89
  }
78
90
 
@@ -89,6 +101,9 @@ export class PatternEditor implements Component {
89
101
  pattern,
90
102
  description: description || pattern,
91
103
  };
104
+ if (this.regexEnabled) {
105
+ item.regex = true;
106
+ }
92
107
 
93
108
  if (this.mode === "edit") {
94
109
  this.items[this.editIndex] = item;
@@ -105,6 +120,7 @@ export class PatternEditor implements Component {
105
120
  this.mode = "list";
106
121
  this.editIndex = -1;
107
122
  this.activeField = "pattern";
123
+ this.regexEnabled = false;
108
124
  this.patternInput.setValue("");
109
125
  this.descriptionInput.setValue("");
110
126
  }
@@ -118,6 +134,7 @@ export class PatternEditor implements Component {
118
134
  this.activeField = "pattern";
119
135
  this.patternInput.setValue(item.pattern);
120
136
  this.descriptionInput.setValue(item.description);
137
+ this.regexEnabled = item.regex ?? false;
121
138
  }
122
139
 
123
140
  private deleteSelected() {
@@ -167,7 +184,8 @@ export class PatternEditor implements Component {
167
184
  const prefix = isSelected ? this.theme.cursor : " ";
168
185
  const prefixWidth = visibleWidth(prefix);
169
186
  const maxItemWidth = width - prefixWidth - 2;
170
- const display = `${item.description} (${item.pattern})`;
187
+ const regexTag = item.regex ? " [regex]" : "";
188
+ const display = `${item.description} (${item.pattern})${regexTag}`;
171
189
  const text = this.theme.value(
172
190
  truncateToWidth(display, maxItemWidth, ""),
173
191
  isSelected,
@@ -197,13 +215,16 @@ export class PatternEditor implements Component {
197
215
 
198
216
  const patternActive = this.activeField === "pattern";
199
217
  const descActive = this.activeField === "description";
218
+ const regexActive = this.activeField === "regex";
200
219
 
201
220
  // Title
202
221
  lines.push(this.theme.hint(isEdit ? " Edit pattern:" : " New pattern:"));
203
222
  lines.push("");
204
223
 
205
224
  // Pattern field
206
- const patternLabel = " Pattern (regex):";
225
+ const patternHint =
226
+ this.context === "file" ? "(glob or regex)" : "(substring or regex)";
227
+ const patternLabel = ` Pattern ${patternHint}:`;
207
228
  lines.push(
208
229
  patternActive
209
230
  ? this.theme.label(patternLabel, true)
@@ -222,8 +243,21 @@ export class PatternEditor implements Component {
222
243
  lines.push(` ${this.descriptionInput.render(inputWidth).join("")}`);
223
244
  lines.push("");
224
245
 
246
+ // Regex toggle
247
+ const regexLabel = " Regex:";
248
+ const regexValue = this.regexEnabled ? "on" : "off";
249
+ const regexDisplay = `${regexLabel} ${regexValue}`;
225
250
  lines.push(
226
- this.theme.hint(" Tab: switch field · Enter: next/submit · Esc: cancel"),
251
+ regexActive
252
+ ? this.theme.label(regexDisplay, true)
253
+ : this.theme.hint(regexDisplay),
254
+ );
255
+ lines.push("");
256
+
257
+ lines.push(
258
+ this.theme.hint(
259
+ " Tab: switch field · Space: toggle regex · Enter: next/submit · Esc: cancel",
260
+ ),
227
261
  );
228
262
 
229
263
  return lines;
@@ -251,6 +285,7 @@ export class PatternEditor implements Component {
251
285
  } else if (data === "a" || data === "A") {
252
286
  this.mode = "add";
253
287
  this.activeField = "pattern";
288
+ this.regexEnabled = false;
254
289
  this.patternInput.setValue("");
255
290
  this.descriptionInput.setValue("");
256
291
  } else if (data === "e" || data === "E" || matchesKey(data, Key.enter)) {
@@ -264,8 +299,12 @@ export class PatternEditor implements Component {
264
299
 
265
300
  private handleFormInput(data: string) {
266
301
  if (matchesKey(data, Key.tab) || matchesKey(data, Key.shift("tab"))) {
267
- this.activeField =
268
- this.activeField === "pattern" ? "description" : "pattern";
302
+ const fields: Field[] = ["pattern", "description", "regex"];
303
+ const idx = fields.indexOf(this.activeField);
304
+ const dir = matchesKey(data, Key.shift("tab")) ? -1 : 1;
305
+ this.activeField = fields[
306
+ (idx + dir + fields.length) % fields.length
307
+ ] as Field;
269
308
  return;
270
309
  }
271
310
 
@@ -274,6 +313,18 @@ export class PatternEditor implements Component {
274
313
  return;
275
314
  }
276
315
 
316
+ // Regex toggle: space toggles when on regex field
317
+ if (this.activeField === "regex") {
318
+ if (data === " " || matchesKey(data, Key.enter)) {
319
+ this.regexEnabled = !this.regexEnabled;
320
+ }
321
+ // Enter on regex field submits if we already have a pattern
322
+ if (matchesKey(data, Key.enter) && this.patternInput.getValue().trim()) {
323
+ this.submitForm();
324
+ }
325
+ return;
326
+ }
327
+
277
328
  // Delegate to active input
278
329
  const activeInput =
279
330
  this.activeField === "pattern"