Best Practices topic

Writing a comprehensive best practices guide for using ANSI is hard!

However, here are some general guidelines to follow when using ANSI escape codes, when using package:mansion, and possible directions for future packages that build on top of the library.

Avoid intermediate strings

When writing performance sensitive code, avoid converting ANSI escape codes to strings, and instead either use <Sequence>.writeAnsiString or <Sink>.writeAnsi directly. This will avoid the overhead of creating and manipulating intermediate strings:

import 'dart:io';
import 'package:mansion/mansion.dart';

void main() {
  final sequence = Print('Hello, World!');
  stdout.writeAnsi(sequence);
}

Use finally when shutting down

A try { ... } finally { ... } is equivalent to the defer keyword in Go, or the using keyword in C#. It is a good way to ensure that cleanup code is always run, even if an exception is thrown:

import 'dart:io';
import 'package:mansion/mansion.dart';

void main() {
  try {
    // ... write a bunch of colors and styles ...
  } finally {
    // avoid leaving the terminal in a weird state
    stdout.writeAnsi(SetStyles.reset);
  }
}

More specific advice is listed below.

Prefer alternate screen for full screen applications

When starting an application that will make heavy use of ANSI escape codes, such as an interactive terminal application, game, text editor, or anything that will be effectively "full screen", prefer entering an alternate screen on startup:

import 'dart:io';
import 'package:mansion/mansion.dart';

void main() {
  stdout.writeAnsi(AsciiControl.enterAlternateScreen);
  try {
    // ... your app ...
  } finally {
    stdout.writeAnsi(AsciiControl.leaveAlternateScreen);
  }
}

Disable non-standard capture when shutting down

There are many ways to signal to the terminal that you want to capture events that are not normally emited, such as EnableMouseCapture, CaptureFocus, CapturePaste, and so on.

When shutting down, disable these captures to avoid leaving the terminal in a weird state:

import 'dart:io';
import 'package:mansion/mansion.dart';

void main() {
  try {
    stdout.writeAnsi(CursorVisibility.hide);
    stdout.writeAnsiAll(EnableMouseCapture.all);
    // ... your app ...
  } finally {
    stdout.writeAnsi(CursorVisibility.show);
    stdout.writeAnsiAll(DisableMouseCapture.all);
  }
}

Use SynchronousUpdates for performance

When writing a lot of ANSI escape codes in a row, consider using SynchronousUpdates to avoid flickering:

import 'dart:io';
import 'package:mansion/mansion.dart';

void main() {
  stdout.writeAnsiAll([
    SynchronousUpdates.start,
    // ... intermediate updates ...
    SynchronousUpdates.end,
  ]);
}

Or, even better, use the built-in syncAnsiUpdate extension on StringSink:

import 'dart:io';
import 'package:mansion/mansion.dart';

void main() async {
  stdout.syncAnsiUpdate((sink) {
    // Do anything here, and it will be written atomically.
  });

  // In a real application, you likely want to block on flushing as well.
  await stdout.flush();
}

For example, here is how you might structure a typical "game loop":

import 'dart:async';
import 'dart:io';
import 'package:mansion/mansion.dart';

void main() async {
  late final StreamSubscription<void> sigint;
  var running = true;
  try {
    sigint = ProcessSignal.sigint.watch().listen((_) {
      running = false;
    });
    final events = <List<int>>[];

    stdout.writeAnsi(AsciiControl.enterAlternateScreen);
    stdout.writeAnsi(CursorVisibility.hide);

    while (running) {
      // Process events.
      if (events.isNotEmpty) {
        final captured = events.toList();
        events.clear();
        for (final c in captured) {
          final e = Event.tryParse(c);
          // TODO: Handle the event.
        }
      }

      // Update the game state.
      // TODO: Implement this!

      // Render the game state.
      stdout.syncAnsiUpdate((sink) {
        // Clear the screen.
        sink.writeAnsi(Clear.all);

        // Draw the game state.
        // TODO: Implement this!
      }):

      await stdout.flush();
      await _wait16ms();
    }
  } finally {
    stdout.writeAnsi(CursorVisibility.show);
    stdout.writeAnsi(AsciiControl.leaveAlternateScreen);
    await sigint.cancel();
  }
}

Future<void> _wait16ms() => Future.delayed(const Duration(milliseconds: 16));

◄ Parsing ANSI Text

Extensions

AnsiStringSink on W Introduction Best Practices
Provides extension methods for writing ANSI Sequences to a StringSink.
AnsiStringSink on W Introduction Best Practices
Provides extension methods for writing ANSI Sequences to a StringSink.

Enums

AlternateScreen Screen Manipulation Best Practices
An escape code that enters or leaves the alternate screen buffer.
AlternateScreen Screen Manipulation Best Practices
An escape code that enters or leaves the alternate screen buffer.
SynchronousUpdates Screen Manipulation Best Practices
An escape code that starts or ends synchronous updates.
SynchronousUpdates Screen Manipulation Best Practices
An escape code that starts or ends synchronous updates.