Skip to main content

pwsh 1.0.2 copy "pwsh: ^1.0.2" to clipboard
pwsh: ^1.0.2 copied to clipboard

A wrapper for XTerm with PTY and Powershell styling

example/lib/main.dart

/// Example app for the `pwsh` plugin.
///
/// The window has a row of demo buttons, a command box and a [PwshTerminal].
/// Each button shows one part of the API:
///
/// * **Log levels**: every `write*` log method on [PwshController].
/// * **Line rules**: how log messages handle missing and trailing newlines,
///   and text that was left mid-line.
/// * **Progress**: in-place updates with `\r` log messages and
///   [PwshController.replaceLine].
/// * **Styled lines**: [PwshController.writeLine] with each [ConsoleTone].
/// * **Run**: runs the command in the box with PowerShell through a
///   [PwshSession], with a Kill button in the terminal toolbar while it runs.
///
/// Everything except Run works without the native PTY library. If the
/// library or PowerShell is missing, Run prints a `FAIL` message explaining
/// what went wrong instead of crashing.
library;

import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:pwsh/pwsh.dart';

void main() => runApp(const ExampleApp());

/// The root widget: a dark Material app showing [ConsolePage].
class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'pwsh example',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        brightness: Brightness.dark,
        colorSchemeSeed: const Color(0xFF61AFEF),
      ),
      home: const ConsolePage(),
    );
  }
}

/// A page with demo buttons, a command box and the terminal.
class ConsolePage extends StatefulWidget {
  const ConsolePage({super.key});

  @override
  State<ConsolePage> createState() => _ConsolePageState();
}

class _ConsolePageState extends State<ConsolePage> {
  /// The console model. It lives in the state, not the widget tree, so its
  /// contents survive rebuilds. 5,000 lines of scrollback is plenty here.
  final _console = PwshController(maxLines: 5000);

  final _command = TextEditingController(
    text: r'$PSVersionTable | Format-Table; Get-ChildItem | Select-Object -First 10',
  );

  /// The running PowerShell session, or `null` when nothing is running.
  /// Used to enable the Kill button and to stop two commands overlapping.
  PwshSession? _session;

  /// Set while a demo is printing, so the demo buttons can't be pressed
  /// again halfway through.
  bool _busy = false;

  /// PowerShell 7 is `pwsh.exe` on Windows and `pwsh` elsewhere. To use the
  /// built-in Windows PowerShell 5.1 instead, change this to
  /// `'powershell.exe'`.
  static final _shell = Platform.isWindows ? 'pwsh.exe' : 'pwsh';

  @override
  void initState() {
    super.initState();
    _console.writeLog('Welcome to the pwsh example.');
    _console.writeDebug('Pick a demo above, or type a command and press Run.');
  }

  @override
  void dispose() {
    _session?.kill();
    _command.dispose();
    super.dispose();
  }

  /// Runs [demo] with the buttons disabled, so demos don't interleave.
  Future<void> _runDemo(Future<void> Function() demo) async {
    setState(() => _busy = true);
    try {
      await demo();
    } finally {
      if (mounted) setState(() => _busy = false);
    }
  }

  /// Prints one message at every log level.
  Future<void> _logLevels() async {
    _console.writeTrace('logTrace: step-by-step detail');
    _console.writeDebug('writeDebug: useful while developing');
    _console.writeLog('writeLog: normal information');
    _console.writePass('writePass: a step succeeded');
    _console.writeWarn('writeWarn: something looks off, carrying on');
    _console.writeFail('writeFail: a step failed');
    _console.writeDev('writeDev: temporary output while working on a feature');
  }

  /// Shows that log messages always get exactly one line each.
  Future<void> _lineRules() async {
    _console.writeLog('No trailing newline: one is added for you');
    _console.writeLog('Trailing newline: no extra one is added\n');
    _console.writeLog('Multi-line messages\nkeep their inner\nline breaks');

    // Leave the cursor mid-line, the way a prompt or progress dots would.
    _console.write('Some text without a newline...');
    _console.writeWarn('...and this message still starts on its own line');
  }

  /// Animates two kinds of in-place progress.
  Future<void> _progress() async {
    const step = Duration(milliseconds: 60);

    // 1. Log messages starting with \r redraw the current line, with the
    //    level label and timestamp redrawn too.
    for (var percent = 0; percent <= 100; percent += 5) {
      _console.writeLog('\rDownloading packages $percent%');
      await Future<void>.delayed(step);
    }
    // Ending the last update with \n keeps it on screen.
    _console.writePass('\rDownloaded packages\n');

    // 2. replaceLine redraws a plain line in a tone of your choice, here as a
    //    text progress bar.
    const width = 30;
    for (var done = 0; done <= width; done++) {
      final bar = '#' * done + '.' * (width - done);
      final percent = (done * 100 / width).round();
      _console.replaceLine('[$bar] $percent%', tone: ConsoleTone.info);
      await Future<void>.delayed(step);
    }
    // replaceLine leaves the cursor at the end of the line; move on.
    _console.ensureLineStart();
    _console.writeLine('Build finished', tone: ConsoleTone.success);
  }

  /// Prints a line in every [ConsoleTone].
  Future<void> _styledLines() async {
    for (final tone in ConsoleTone.values) {
      _console.writeLine('ConsoleTone.${tone.name}', tone: tone);
    }
  }

  /// Runs the command in the text box with PowerShell.
  Future<void> _run() async {
    final command = _command.text.trim();
    if (command.isEmpty || _session != null) return;

    _console.writeLine('> $command', tone: ConsoleTone.command);

    final PwshSession session;
    try {
      session = PwshSession.start(
        _shell,
        // -NoProfile skips the user's profile script, so the example starts
        // quickly and behaves the same on every machine. -Command runs the
        // text and then exits.
        ['-NoLogo', '-NoProfile', '-Command', command],
        console: _console,
      );
    } on Object catch (error) {
      // Thrown when the native PTY library isn't bundled with the app or
      // the pseudo console can't be created. See the README.
      _console.writeFail('Could not start $_shell: $error');
      return;
    }

    setState(() => _session = session);
    final code = await session.exitCode;
    if (!mounted) return;
    setState(() => _session = null);

    // exitCode completes after the last output has been written, so this
    // always appears below the command's output.
    if (code == 0) {
      _console.writePass('$_shell exited with code 0');
    } else {
      _console.writeFail('$_shell exited with code $code');
    }
  }

  @override
  Widget build(BuildContext context) {
    final running = _session != null;
    final demosEnabled = !_busy && !running;

    return Scaffold(
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Padding(
            padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
            child: Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                _DemoButton(
                  'Log levels',
                  demosEnabled,
                  () => _runDemo(_logLevels),
                ),
                _DemoButton(
                  'Line rules',
                  demosEnabled,
                  () => _runDemo(_lineRules),
                ),
                _DemoButton(
                  'Progress',
                  demosEnabled,
                  () => _runDemo(_progress),
                ),
                _DemoButton(
                  'Styled lines',
                  demosEnabled,
                  () => _runDemo(_styledLines),
                ),
              ],
            ),
          ),
          Padding(
            padding: const EdgeInsets.fromLTRB(12, 0, 12, 12),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _command,
                    enabled: !running,
                    style: const TextStyle(fontFamily: 'Cascadia Mono'),
                    decoration: InputDecoration(
                      isDense: true,
                      border: const OutlineInputBorder(),
                      labelText: 'PowerShell command ($_shell)',
                    ),
                    onSubmitted: (_) => _run(),
                  ),
                ),
                const SizedBox(width: 8),
                FilledButton.icon(
                  onPressed: demosEnabled ? _run : null,
                  icon: const Icon(Icons.play_arrow),
                  label: const Text('Run'),
                ),
              ],
            ),
          ),
          Expanded(
            child: PwshTerminal(
              _console,
              title: running
                  ? 'Running $_shell (pid ${_session!.pid})'
                  : 'Console',
              // Only set while a command runs, so the Kill button only shows
              // up when there's something to kill.
              onKill: running ? () => _session?.kill() : null,
            ),
          ),
        ],
      ),
    );
  }
}

/// A tonal button for one demo.
class _DemoButton extends StatelessWidget {
  const _DemoButton(this.label, this.enabled, this.onPressed);

  final String label;
  final bool enabled;
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return FilledButton.tonal(
      onPressed: enabled ? onPressed : null,
      child: Text(label),
    );
  }
}
0
likes
140
points
133
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A wrapper for XTerm with PTY and Powershell styling

Homepage

License

unknown (license)

Dependencies

ffi, flutter, flutter_web_plugins, msend, plugin_platform_interface, recollect, web, xterm

More

Packages that depend on pwsh