Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Renders the default for all Prompt types that accepts TextOptions. #3508

Merged
merged 11 commits into from
Aug 29, 2024
9 changes: 9 additions & 0 deletions .changeset/late-apricots-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@effect/cli": patch
---

Renders the default for all `Prompt` types that accepts `TextOptions`.

- The default value will be rendered as ghost text for `Prompt.text` and `Prompt.list`.
- The default value will be rendered as redacted ghost text for `Prompt.password`.
- The default value will remain hidden for `Prompt.hidden`.
40 changes: 36 additions & 4 deletions packages/cli/src/internal/prompt/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as Doc from "@effect/printer-ansi/AnsiDoc"
import * as Optimize from "@effect/printer/Optimize"
import * as Arr from "effect/Array"
import * as Effect from "effect/Effect"
import * as Match from "effect/Match"
import * as Option from "effect/Option"
import * as Redacted from "effect/Redacted"
import type * as Prompt from "../../Prompt.js"
Expand All @@ -25,6 +26,10 @@ interface State {
readonly error: Option.Option<string>
}

function getValue(state: State, options: Options): string {
return state.value.length > 0 ? state.value : options.default
}

const renderBeep = Doc.render(Doc.beep, { style: "pretty" })

function renderClearScreen(state: State, options: Options) {
Expand Down Expand Up @@ -58,19 +63,32 @@ function renderClearScreen(state: State, options: Options) {
}

function renderInput(nextState: State, options: Options, submitted: boolean) {
const text = getValue(nextState, options)

const annotation = Option.match(nextState.error, {
onNone: () => submitted ? Ansi.white : Ansi.combine(Ansi.underlined, Ansi.cyanBright),
onNone: () =>
Match.value(submitted)
cdierkens marked this conversation as resolved.
Show resolved Hide resolved
.pipe(
Match.when(true, () => Ansi.green),
cdierkens marked this conversation as resolved.
Show resolved Hide resolved
Match.orElse(() =>
Match.value(nextState.value).pipe(
Match.when("", () => Ansi.blackBright),
Match.orElse(() => Ansi.combine(Ansi.underlined, Ansi.cyanBright))
)
)
),
onSome: () => Ansi.red
})

switch (options.type) {
case "hidden": {
return Doc.empty
}
case "password": {
return Doc.annotate(Doc.text("*".repeat(nextState.value.length)), annotation)
return Doc.annotate(Doc.text("*".repeat(text.length)), annotation)
}
case "text": {
return Doc.annotate(Doc.text(nextState.value), annotation)
return Doc.annotate(Doc.text(text), annotation)
}
}
}
Expand Down Expand Up @@ -191,6 +209,17 @@ function processCursorRight(state: State) {
}))
}

function processTab(state: State, options: Options) {
if (state.value === options.default) {
return Effect.succeed(Action.Beep())
}
const value = getValue(state, options)
const cursor = value.length
return Effect.succeed(Action.NextFrame({
state: { ...state, value, cursor, error: Option.none() }
}))
}

function defaultProcessor(input: string, state: State) {
const beforeCursor = state.value.slice(0, state.cursor)
const afterCursor = state.value.slice(state.cursor)
Expand Down Expand Up @@ -232,7 +261,7 @@ function handleProcess(options: Options) {
}
case "enter":
case "return": {
const value = state.value.length > 0 ? state.value : options.default
const value = getValue(state, options)
return Effect.match(options.validate(value), {
onFailure: (error) =>
Action.NextFrame({
Expand All @@ -241,6 +270,9 @@ function handleProcess(options: Options) {
onSuccess: (value) => Action.Submit({ value })
})
}
case "tab": {
return processTab(state, options)
}
default: {
const value = Option.getOrElse(input.input, () => "")
return defaultProcessor(value, state)
Expand Down
Loading