pi-ask-tool-extension 0.1.5 → 0.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ask-tool-extension",
3
- "version": "0.1.5",
3
+ "version": "0.2.2",
4
4
  "description": "Ask tool extension for pi with tabbed questioning and inline note editing",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,10 @@
1
+ import { wrapTextWithAnsi } from "@mariozechner/pi-tui";
2
+
1
3
  const INLINE_NOTE_SEPARATOR = " — note: ";
2
4
  const INLINE_EDIT_CURSOR = "▍";
3
5
 
6
+ export const INLINE_NOTE_WRAP_PADDING = 2;
7
+
4
8
  function sanitizeNoteForInlineDisplay(rawNote: string): string {
5
9
  return rawNote.replace(/[\r\n\t]/g, " ").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
6
10
  }
@@ -31,14 +35,31 @@ export function buildOptionLabelWithInlineNote(
31
35
  }
32
36
 
33
37
  const labelPrefix = `${baseOptionLabel}${INLINE_NOTE_SEPARATOR}`;
34
- const rawInlineNote = isEditingNote ? `${sanitizedNote}${INLINE_EDIT_CURSOR}` : sanitizedNote.trim();
35
- const fullInlineLabel = `${labelPrefix}${rawInlineNote}`;
38
+ const inlineNote = isEditingNote ? `${sanitizedNote}${INLINE_EDIT_CURSOR}` : sanitizedNote.trim();
39
+ const inlineLabel = `${labelPrefix}${inlineNote}`;
36
40
 
37
41
  if (maxInlineLabelLength == null) {
38
- return fullInlineLabel;
42
+ return inlineLabel;
39
43
  }
40
44
 
41
45
  return isEditingNote
42
- ? truncateTextKeepingTail(fullInlineLabel, maxInlineLabelLength)
43
- : truncateTextKeepingHead(fullInlineLabel, maxInlineLabelLength);
46
+ ? truncateTextKeepingTail(inlineLabel, maxInlineLabelLength)
47
+ : truncateTextKeepingHead(inlineLabel, maxInlineLabelLength);
48
+ }
49
+
50
+ export function buildWrappedOptionLabelWithInlineNote(
51
+ baseOptionLabel: string,
52
+ rawNote: string,
53
+ isEditingNote: boolean,
54
+ maxInlineLabelLength: number,
55
+ wrapPadding = INLINE_NOTE_WRAP_PADDING,
56
+ ): string[] {
57
+ const inlineLabel = buildOptionLabelWithInlineNote(baseOptionLabel, rawNote, isEditingNote);
58
+ const sanitizedWrapPadding = Number.isFinite(wrapPadding) ? Math.max(0, Math.floor(wrapPadding)) : 0;
59
+ const sanitizedMaxInlineLabelLength = Number.isFinite(maxInlineLabelLength)
60
+ ? Math.max(1, Math.floor(maxInlineLabelLength))
61
+ : 1;
62
+ const wrapWidth = Math.max(1, sanitizedMaxInlineLabelLength - sanitizedWrapPadding);
63
+ const wrappedLines = wrapTextWithAnsi(inlineLabel, wrapWidth);
64
+ return wrappedLines.length > 0 ? wrappedLines : [""];
44
65
  }
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionUIContext } from "@mariozechner/pi-coding-agent";
2
- import { Editor, type EditorTheme, Key, matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
2
+ import { Editor, type EditorTheme, Key, matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
3
3
  import {
4
4
  OTHER_OPTION,
5
5
  appendRecommendedTagToOptionLabels,
@@ -7,7 +7,7 @@ import {
7
7
  type AskOption,
8
8
  type AskSelection,
9
9
  } from "./ask-logic";
10
- import { buildOptionLabelWithInlineNote } from "./ask-inline-note";
10
+ import { INLINE_NOTE_WRAP_PADDING, buildWrappedOptionLabelWithInlineNote } from "./ask-inline-note";
11
11
 
12
12
  interface SingleQuestionInput {
13
13
  question: string;
@@ -115,21 +115,28 @@ export async function askSingleQuestionWithInlineNote(
115
115
  addLine(theme.fg("text", ` ${questionInput.question}`));
116
116
  renderedLines.push("");
117
117
 
118
- const maxInlineLabelLength = Math.max(12, width - 6);
119
118
  for (let optionIndex = 0; optionIndex < selectableOptionLabels.length; optionIndex++) {
120
119
  const optionLabel = selectableOptionLabels[optionIndex];
121
120
  const isCursorOption = optionIndex === cursorOptionIndex;
122
121
  const isEditingThisOption = isNoteEditorOpen && isCursorOption;
123
- const optionLabelWithInlineNote = buildOptionLabelWithInlineNote(
122
+ const cursorPrefixText = isCursorOption ? "→ " : " ";
123
+ const cursorPrefix = isCursorOption ? theme.fg("accent", cursorPrefixText) : cursorPrefixText;
124
+ const bullet = isCursorOption ? "●" : "○";
125
+ const markerText = `${bullet} `;
126
+ const optionColor = isCursorOption ? "accent" : "text";
127
+ const prefixWidth = visibleWidth(cursorPrefixText) + visibleWidth(markerText);
128
+ const wrappedInlineLabelLines = buildWrappedOptionLabelWithInlineNote(
124
129
  optionLabel,
125
130
  getRawNoteForOption(optionIndex),
126
131
  isEditingThisOption,
127
- maxInlineLabelLength,
132
+ Math.max(1, width - prefixWidth),
133
+ INLINE_NOTE_WRAP_PADDING,
128
134
  );
129
- const cursorPrefix = isCursorOption ? theme.fg("accent", "→ ") : " ";
130
- const bullet = isCursorOption ? "" : "○";
131
- const optionColor = isCursorOption ? "accent" : "text";
132
- addLine(`${cursorPrefix}${theme.fg(optionColor, `${bullet} ${optionLabelWithInlineNote}`)}`);
135
+ const continuationPrefix = " ".repeat(prefixWidth);
136
+ addLine(`${cursorPrefix}${theme.fg(optionColor, `${markerText}${wrappedInlineLabelLines[0] ?? ""}`)}`);
137
+ for (const wrappedLine of wrappedInlineLabelLines.slice(1)) {
138
+ addLine(`${continuationPrefix}${theme.fg(optionColor, wrappedLine)}`);
139
+ }
133
140
  }
134
141
 
135
142
  renderedLines.push("");
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionUIContext } from "@mariozechner/pi-coding-agent";
2
- import { Editor, type EditorTheme, Key, matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
2
+ import { Editor, type EditorTheme, Key, matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
3
3
  import {
4
4
  OTHER_OPTION,
5
5
  appendRecommendedTagToOptionLabels,
@@ -8,7 +8,7 @@ import {
8
8
  type AskQuestion,
9
9
  type AskSelection,
10
10
  } from "./ask-logic";
11
- import { buildOptionLabelWithInlineNote } from "./ask-inline-note";
11
+ import { INLINE_NOTE_WRAP_PADDING, buildWrappedOptionLabelWithInlineNote } from "./ask-inline-note";
12
12
 
13
13
  interface PreparedQuestion {
14
14
  id: string;
@@ -317,27 +317,29 @@ export async function askQuestionsWithTabs(
317
317
  addLine(theme.fg("text", ` ${preparedQuestion.question}`));
318
318
  renderedLines.push("");
319
319
 
320
- const maxInlineLabelLength = Math.max(12, width - 8);
321
320
  for (let optionIndex = 0; optionIndex < preparedQuestion.options.length; optionIndex++) {
322
321
  const optionLabel = preparedQuestion.options[optionIndex];
323
322
  const isCursorOption = optionIndex === cursorOptionIndex;
324
323
  const isOptionSelected = selectedOptionIndexes.includes(optionIndex);
325
324
  const isEditingThisOption = isNoteEditorOpen && isCursorOption;
326
- const optionLabelWithInlineNote = buildOptionLabelWithInlineNote(
325
+ const cursorPrefixText = isCursorOption ? "→ " : " ";
326
+ const cursorPrefix = isCursorOption ? theme.fg("accent", cursorPrefixText) : cursorPrefixText;
327
+ const markerText = preparedQuestion.multi
328
+ ? `${isOptionSelected ? "[x]" : "[ ]"} `
329
+ : `${isOptionSelected ? "●" : "○"} `;
330
+ const optionColor = isCursorOption ? "accent" : isOptionSelected ? "success" : "text";
331
+ const prefixWidth = visibleWidth(cursorPrefixText) + visibleWidth(markerText);
332
+ const wrappedInlineLabelLines = buildWrappedOptionLabelWithInlineNote(
327
333
  optionLabel,
328
334
  getQuestionNote(questionIndex, optionIndex),
329
335
  isEditingThisOption,
330
- maxInlineLabelLength,
336
+ Math.max(1, width - prefixWidth),
337
+ INLINE_NOTE_WRAP_PADDING,
331
338
  );
332
- const cursorPrefix = isCursorOption ? theme.fg("accent", "→ ") : " ";
333
- if (preparedQuestion.multi) {
334
- const checkbox = isOptionSelected ? "[x]" : "[ ]";
335
- const optionColor = isCursorOption ? "accent" : isOptionSelected ? "success" : "text";
336
- addLine(`${cursorPrefix}${theme.fg(optionColor, `${checkbox} ${optionLabelWithInlineNote}`)}`);
337
- } else {
338
- const bullet = isOptionSelected ? "●" : "○";
339
- const optionColor = isCursorOption ? "accent" : isOptionSelected ? "success" : "text";
340
- addLine(`${cursorPrefix}${theme.fg(optionColor, `${bullet} ${optionLabelWithInlineNote}`)}`);
339
+ const continuationPrefix = " ".repeat(prefixWidth);
340
+ addLine(`${cursorPrefix}${theme.fg(optionColor, `${markerText}${wrappedInlineLabelLines[0] ?? ""}`)}`);
341
+ for (const wrappedLine of wrappedInlineLabelLines.slice(1)) {
342
+ addLine(`${continuationPrefix}${theme.fg(optionColor, wrappedLine)}`);
341
343
  }
342
344
  }
343
345