{"version":3,"file":"workflowPrompt.mjs","names":[],"sources":["../../../../src/tui/workflow/workflowPrompt.ts"],"sourcesContent":["/**\n * The short prompts: pick one of a few options, or type one value.\n *\n * These were `ui.select` and `ui.input`, which meant the run manager, the runner\n * picker and every `workflow_dispatch` input rendered as native Pi widgets in the\n * middle of a flow whose other steps are framed doom overlays. Same surface for\n * all of them now, so a launch reads as one sequence rather than three styles.\n *\n * Rendering is pure, so both modes can be asserted as text without a terminal.\n */\n\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey } from '@earendil-works/pi-tui';\n\nimport {\n  DOOM_FULLSCREEN_UI_OPTIONS,\n  DOOM_OVERLAY_ACCENT,\n  DoomOverlay,\n  type DoomOverlayChrome,\n  type DoomOverlayTui,\n} from './doomOverlay';\nimport { CURSOR_BLOCK, fit, isControlInput, rightAligned, SELECTION_MARKER } from './overlayText';\n\nexport const WORKFLOW_PROMPT_OVERLAY_OPTIONS = DOOM_FULLSCREEN_UI_OPTIONS.overlayOptions;\n\nconst CHOICE_TITLE = 'WORKFLOW PROMPT';\nconst CHOOSE_LABEL = 'CHOOSE';\nconst VALUE_LABEL = 'VALUE';\nconst DEFAULT_MARKER = 'default';\nconst EMPTY_CHOICES = 'No options are available.';\nconst CHOICE_HINTS: readonly (readonly [string, string])[] = [\n  ['↑↓', 'select'],\n  ['enter', 'confirm'],\n  ['esc', 'cancel'],\n];\nconst INPUT_HINTS: readonly (readonly [string, string])[] = [\n  ['type', 'edit'],\n  ['enter', 'confirm'],\n  ['ctrl+u', 'clear'],\n  ['esc', 'cancel'],\n];\nconst KEY_CLEAR = '\\x15';\n/** Rows the heading and its spacer take before the first option. */\nconst HEADING_ROWS = 2;\n\n/** Neither palette name is exported, so take both from the theme's methods. */\ntype ThemeBg = Parameters<Theme['bg']>[0];\ntype ThemeColor = Parameters<Theme['fg']>[0];\n\nconst DIM: ThemeColor = 'dim';\nconst ACCENT: ThemeColor = 'accent';\nconst SELECTED_BG: ThemeBg = 'selectedBg';\nconst ROW_BG: ThemeBg = 'userMessageBg';\n\nexport interface WorkflowChoiceOptions {\n  title: string;\n  breadcrumb: string;\n  choices: readonly string[];\n  /** Marked `default` in the list and pre-selected. */\n  preselect?: string;\n}\n\nexport interface WorkflowInputOptions {\n  title: string;\n  breadcrumb: string;\n  /** Committed when the field is left empty, and shown beside the label. */\n  fallback?: string;\n}\n\nexport class WorkflowChoiceComponent extends DoomOverlay {\n  private selected: number;\n\n  constructor(\n    tui: DoomOverlayTui,\n    theme: Theme,\n    private readonly options: WorkflowChoiceOptions,\n    private readonly done: (choice: string | undefined) => void,\n  ) {\n    super(tui, theme);\n    this.selected = Math.max(0, options.preselect ? options.choices.indexOf(options.preselect) : 0);\n  }\n\n  handleInput(data: string): void {\n    if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n      this.done(undefined);\n      return;\n    }\n    if (matchesKey(data, 'enter')) {\n      const choice = this.options.choices[this.selected];\n      if (choice !== undefined) this.done(choice);\n      return;\n    }\n    const count = this.options.choices.length;\n    if (count === 0) return;\n    if (matchesKey(data, 'up') || matchesKey(data, 'ctrl+p')) this.selected = Math.max(0, this.selected - 1);\n    else if (matchesKey(data, 'down') || matchesKey(data, 'ctrl+n'))\n      this.selected = Math.min(count - 1, this.selected + 1);\n    else return;\n    this.tui.requestRender();\n  }\n\n  protected getChrome(): DoomOverlayChrome {\n    return {\n      title: CHOICE_TITLE,\n      accent: DOOM_OVERLAY_ACCENT,\n      breadcrumb: this.options.breadcrumb,\n      headerRight: `${this.options.choices.length} options`,\n      footer: '↑↓ select · enter confirm · esc cancel',\n      footerHints: CHOICE_HINTS,\n      footerRight:\n        this.options.choices.length > 0 ? `${this.selected + 1}/${this.options.choices.length}` : 'no options',\n    };\n  }\n\n  protected renderBody(width: number, height: number): string[] {\n    const lines = [this.theme.bold(fit(this.options.title, width)), ''];\n    if (this.options.choices.length === 0) {\n      lines.push(this.theme.fg(DIM, EMPTY_CHOICES));\n      return lines.slice(0, height);\n    }\n\n    lines.push(this.theme.fg(DIM, CHOOSE_LABEL));\n    // The window follows the cursor, so a list longer than the body still scrolls.\n    const capacity = Math.max(1, height - HEADING_ROWS - 1);\n    const start = Math.max(\n      0,\n      Math.min(this.selected - capacity + 1, Math.max(0, this.options.choices.length - capacity)),\n    );\n    for (const [offset, choice] of this.options.choices.slice(start, start + capacity).entries()) {\n      const current = start + offset === this.selected;\n      const marker = current ? this.theme.fg(ACCENT, SELECTION_MARKER) : ' ';\n      const label = current ? this.theme.bold(choice) : choice;\n      const note = choice === this.options.preselect ? this.theme.fg(DIM, DEFAULT_MARKER) : '';\n      const background: ThemeBg = current ? SELECTED_BG : ROW_BG;\n      lines.push(this.theme.bg(background, rightAligned(`${marker} ${label}`, note, width)));\n    }\n    return lines.slice(0, height);\n  }\n}\n\nexport class WorkflowInputComponent extends DoomOverlay {\n  private draft = '';\n\n  constructor(\n    tui: DoomOverlayTui,\n    theme: Theme,\n    private readonly options: WorkflowInputOptions,\n    private readonly done: (value: string | undefined) => void,\n  ) {\n    super(tui, theme);\n  }\n\n  handleInput(data: string): void {\n    if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n      this.done(undefined);\n      return;\n    }\n    if (matchesKey(data, 'enter')) {\n      // An empty field commits the fallback rather than an empty string: the\n      // caller offered a default, and pressing enter is how you accept it.\n      this.done(this.draft.trim() || this.options.fallback || '');\n      return;\n    }\n    if (matchesKey(data, 'backspace')) this.draft = this.draft.slice(0, -1);\n    else if (data === KEY_CLEAR) this.draft = '';\n    else if (!isControlInput(data)) this.draft += data;\n    else return;\n    this.tui.requestRender();\n  }\n\n  protected getChrome(): DoomOverlayChrome {\n    return {\n      title: CHOICE_TITLE,\n      accent: DOOM_OVERLAY_ACCENT,\n      breadcrumb: this.options.breadcrumb,\n      headerRight: this.options.fallback ? `default ${this.options.fallback}` : 'no default',\n      footer: 'type to edit · enter confirm · ctrl+u clear · esc cancel',\n      footerHints: INPUT_HINTS,\n    };\n  }\n\n  protected renderBody(width: number, height: number): string[] {\n    const label = this.options.fallback\n      ? rightAligned(\n          this.theme.fg(DIM, VALUE_LABEL),\n          this.theme.fg(DIM, `${DEFAULT_MARKER} ${this.options.fallback}`),\n          width,\n        )\n      : this.theme.fg(DIM, VALUE_LABEL);\n    return [\n      this.theme.bold(fit(this.options.title, width)),\n      '',\n      label,\n      this.theme.bg(SELECTED_BG, fit(` ${this.draft}${CURSOR_BLOCK}`, width)),\n    ].slice(0, height);\n  }\n}\n\nexport async function openWorkflowChoice(\n  ctx: ExtensionContext,\n  options: WorkflowChoiceOptions,\n): Promise<string | undefined> {\n  return ctx.ui.custom<string | undefined>(\n    (tui, theme, _keybindings, done) => new WorkflowChoiceComponent(tui, theme, options, done),\n    DOOM_FULLSCREEN_UI_OPTIONS,\n  );\n}\n\nexport async function openWorkflowInput(\n  ctx: ExtensionContext,\n  options: WorkflowInputOptions,\n): Promise<string | undefined> {\n  return ctx.ui.custom<string | undefined>(\n    (tui, theme, _keybindings, done) => new WorkflowInputComponent(tui, theme, options, done),\n    DOOM_FULLSCREEN_UI_OPTIONS,\n  );\n}\n"],"mappings":"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"}