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

feat(cli): command line spinner #3968

Merged
merged 12 commits into from
Dec 20, 2023
38 changes: 27 additions & 11 deletions cli/spinner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,26 @@ const encoder = new TextEncoder();

const LINE_CLEAR = encoder.encode("\r\u001b[K"); // From cli/prompt_secret.ts
const COLOR_RESET = "\u001b[0m";
const DEFAULT_SPEED = 75;
const DEFAULT_INTERVAL = 75;
const DEFAULT_SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];

// This is a hack to allow us to use the same type for both the color name and an ANSI escape code.
// deno-lint-ignore ban-types
type Ansi = string & { };
type Color = 'black' | 'red' | 'green' | 'yellow' | 'blue' | 'magenta' | 'cyan' | 'white' | 'gray' | Ansi;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iuioiua I used this typescript hack to allow for type suggestions on colors and allowing for a ANSI string, is this ok?

See microsoft/TypeScript#29729 (comment)


const COLORS: Record<Color, string> = {
black: "\u001b[30m",
red: "\u001b[31m",
green: "\u001b[32m",
yellow: "\u001b[33m",
blue: "\u001b[34m",
magenta: "\u001b[35m",
cyan: "\u001b[36m",
white: "\u001b[37m",
gray: "\u001b[90m",
};

/** Options for {@linkcode Spinner}. */
export interface SpinnerOptions {
/**
Expand All @@ -17,13 +34,13 @@ export interface SpinnerOptions {
spinner?: string[];
/** The message to display next to the spinner. */
message?: string;
/** The speed of the spinner.
/** The time between each frame of the spinner.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful to include the unit of time here (milliseconds) — otherwise, the user must review the implementation to understand. For example:

- The time between each frame of the spinner.
+ The time (milliseconds) between each frame of the spinner.

*
* @default {75}
*/
speed?: number;
interval?: number;
/** The color of the spinner. Defaults to the default terminal color. */
color?: string;
color?: Color;
}

/**
Expand All @@ -32,8 +49,8 @@ export interface SpinnerOptions {
export class Spinner {
#spinner: string[];
#message: string;
#speed: number;
#color?: string;
#interval: number;
#color?: Color;
#intervalId: number | undefined;
#active = false;

Expand All @@ -50,8 +67,8 @@ export class Spinner {
constructor(options?: SpinnerOptions) {
this.#spinner = options?.spinner ?? DEFAULT_SPINNER;
this.#message = options?.message ?? "";
this.#speed = options?.speed ?? DEFAULT_SPEED;
this.#color = options?.color;
this.#interval = options?.interval ?? DEFAULT_INTERVAL;
this.#color = options?.color ? COLORS[options.color] : undefined;
}

/**
Expand Down Expand Up @@ -80,7 +97,7 @@ export class Spinner {
Deno.stdout.writeSync(frame);
i = (i + 1) % this.#spinner.length;
};
this.#intervalId = setInterval(updateFrame, this.#speed);
this.#intervalId = setInterval(updateFrame, this.#interval);
}
/**
* Stops the spinner.
Expand All @@ -102,8 +119,7 @@ export class Spinner {
if (this.#intervalId && this.#active) {
clearInterval(this.#intervalId);
Deno.stdout.writeSync(LINE_CLEAR); // Clear the current line
Deno.removeSignalListener("SIGINT", this.stop.bind(this));
this.#active = false;
}
}
}
}
Loading