run method

String? run()

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;
}