run method
Runs the prompt and returns the result.
Returns null if cancelled, otherwise returns the trimmed text.
Implementation
String? run() {
final state = TextInputState();
// Build bindings
var bindings = state.buffer.toTextInputBindings(onInput: () {
state.clearError();
});
bindings = bindings +
KeyBindings.confirm(onConfirm: () {
final text = state.text.trim();
// Validate required
if (required && text.isEmpty) {
state.setError('Input cannot be empty.');
return KeyActionResult.handled;
}
// Custom validation
if (validator != null) {
final error = normalizeValidationError(validator!(text));
if (error != null) {
state.setError(error);
return KeyActionResult.handled;
}
}
state.confirm();
return KeyActionResult.confirmed;
});
// Add reveal toggle for password mode
if (masked && allowReveal) {
bindings = bindings +
KeyBindings.ctrlR(
onPress: state.toggleVisibility,
hintDescription: 'reveal',
);
}
bindings = bindings + KeyBindings.cancel(onCancel: state.cancel);
// Render function
void renderFrame(RenderOutput out) {
final frame = FrameView(
title: title,
theme: theme,
bindings: bindings,
);
frame.render(out, (ctx) {
// Build display text
String displayText;
if (state.isEmpty) {
displayText = placeholder != null
? '${theme.dim}$placeholder${theme.reset}'
: '';
} else if (masked && !state.showPlain) {
displayText = maskChar * state.length;
} else {
displayText = state.text;
}
// Static cursor (always visible)
final cursor = '${theme.accent}▌${theme.reset}';
// Color based on validation
final color = state.valid ? theme.accent : theme.error;
ctx.gutterLine('$color$displayText$cursor${theme.reset}');
// Error message (hints are handled by FrameView)
if (state.error != null) {
ctx.gutterLine('${theme.error}${state.error}${theme.reset}');
}
});
}
final runner = PromptRunner(hideCursor: true);
runner.runWithBindings(
render: renderFrame,
bindings: bindings,
);
if (state.isCancelled) return null;
return state.isConfirmed ? state.text.trim() : null;
}