@flowscripter/dynamic-cli-framework 11.1.0 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cli/BaseCLI.d.ts.map +1 -1
- package/dist/src/cli/BaseCLI.js +5 -2
- package/dist/src/service/printer/PrinterServiceProvider.d.ts +3 -1
- package/dist/src/service/printer/PrinterServiceProvider.d.ts.map +1 -1
- package/dist/src/service/printer/PrinterServiceProvider.js +7 -4
- package/dist/src/service/printer/command/ThemeCommand.d.ts +24 -0
- package/dist/src/service/printer/command/ThemeCommand.d.ts.map +1 -0
- package/dist/src/service/printer/command/ThemeCommand.js +40 -0
- package/dist/src/terminal/Ansi.d.ts +4 -0
- package/dist/src/terminal/Ansi.d.ts.map +1 -1
- package/dist/src/terminal/Ansi.js +7 -3
- package/dist/src/terminal/BackgroundDetector.d.ts +25 -0
- package/dist/src/terminal/BackgroundDetector.d.ts.map +1 -0
- package/dist/src/terminal/BackgroundDetector.js +63 -0
- package/dist/src/terminal/KeyReader.d.ts +7 -0
- package/dist/src/terminal/KeyReader.d.ts.map +1 -1
- package/dist/src/terminal/TtyKeyReader.d.ts +1 -0
- package/dist/src/terminal/TtyKeyReader.d.ts.map +1 -1
- package/dist/src/terminal/TtyKeyReader.js +30 -0
- package/package.json +1 -1
- package/src/cli/BaseCLI.ts +10 -3
- package/src/service/printer/PrinterServiceProvider.ts +11 -4
- package/src/service/printer/command/ThemeCommand.ts +47 -0
- package/src/terminal/Ansi.ts +7 -3
- package/src/terminal/BackgroundDetector.ts +78 -0
- package/src/terminal/KeyReader.ts +7 -0
- package/src/terminal/TtyKeyReader.ts +35 -0
- package/dist/src/service/printer/command/DarkModeCommand.d.ts +0 -17
- package/dist/src/service/printer/command/DarkModeCommand.d.ts.map +0 -1
- package/dist/src/service/printer/command/DarkModeCommand.js +0 -24
- package/src/service/printer/command/DarkModeCommand.ts +0 -32
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseCLI.d.ts","sourceRoot":"","sources":["../../../src/cli/BaseCLI.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,yCAAyC,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAGvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC/E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAsBzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AAU3E,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"BaseCLI.d.ts","sourceRoot":"","sources":["../../../src/cli/BaseCLI.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,yCAAyC,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAGvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC/E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAsBzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AAU3E,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAC;AAEhD,OAAO,KAAK,SAAS,MAAM,0BAA0B,CAAC;AACtD,OAAO,KAAK,qBAAqB,MAAM,4BAA4B,CAAC;AAqCpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,CAAC,OAAO,OAAO,OAAQ,YAAW,GAAG;;IAazC,YACE,SAAS,EAAE,SAAS,EACpB,oBAAoB,EAAE,cAAc,EACpC,oBAAoB,EAAE,cAAc,EACpC,aAAa,EAAE,OAAO,EACtB,aAAa,EAAE,OAAO,EACtB,cAAc,EAAE,QAAQ,EACxB,cAAc,EAAE,QAAQ,EACxB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,OAAO,CAAC,EAAE,qBAAqB,EAuEhC;IAED;;;;;;;OAOG;IACI,kBAAkB,CAAC,eAAe,EAAE,eAAe,QAEzD;IAED;;;;;;;;OAQG;IACH;;;;;OAKG;IACI,cAAc,CAAC,IAAI,EAAE,WAAW,QAEtC;IAEM,UAAU,CAAC,OAAO,EAAE,OAAO,QAQjC;IAEK,GAAG,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CA2QzD;CACF"}
|
package/dist/src/cli/BaseCLI.js
CHANGED
|
@@ -21,6 +21,7 @@ import { awaitBackgroundTasks } from "../runtime/lifecycle/BackgroundTasks.js";
|
|
|
21
21
|
import { printRuntimeError } from "../util/runnerHelper.js";
|
|
22
22
|
import { WritableStream } from "node:stream/web";
|
|
23
23
|
import DefaultPrinterService from "../service/printer/DefaultPrinterService.js";
|
|
24
|
+
import { detectDarkBackground } from "../terminal/BackgroundDetector.js";
|
|
24
25
|
import DefaultPrompterService, { DEFAULT_PROMPTER_CONFIG, } from "../service/prompter/DefaultPrompterService.js";
|
|
25
26
|
import PrompterServiceProvider from "../service/prompter/PrompterServiceProvider.js";
|
|
26
27
|
import DefaultArgumentPrompterService from "../service/argumentPrompter/DefaultArgumentPrompterService.js";
|
|
@@ -189,9 +190,11 @@ export default class BaseCLI {
|
|
|
189
190
|
// create and add core services
|
|
190
191
|
this.addServiceProvider(new ShutdownServiceProvider(SHUTDOWN_SERVICE_PRIORITY));
|
|
191
192
|
this.addServiceProvider(this.#startupServiceProvider);
|
|
192
|
-
this.addServiceProvider(new PrinterServiceProvider(PRINTER_SERVICE_PRIORITY, this.#printerService));
|
|
193
|
-
this.addServiceProvider(new TableGeneratorServiceProvider(TABLE_GENERATOR_SERVICE_PRIORITY));
|
|
194
193
|
const canPrompt = this.#context.isTty.stdin && this.#context.isTty.stderr;
|
|
194
|
+
this.addServiceProvider(new PrinterServiceProvider(PRINTER_SERVICE_PRIORITY, this.#printerService, canPrompt && this.#keyReader !== undefined
|
|
195
|
+
? () => detectDarkBackground(this.#keyReader, this.#stderrTerminal)
|
|
196
|
+
: undefined));
|
|
197
|
+
this.addServiceProvider(new TableGeneratorServiceProvider(TABLE_GENERATOR_SERVICE_PRIORITY));
|
|
195
198
|
if (this.#options.promptingEnabled && !canPrompt) {
|
|
196
199
|
throw new Error("promptingEnabled requires a keyReader and a TTY stderr terminal");
|
|
197
200
|
}
|
|
@@ -9,13 +9,15 @@ export default class PrinterServiceProvider implements ServiceProvider {
|
|
|
9
9
|
readonly serviceId: string;
|
|
10
10
|
readonly servicePriority: number;
|
|
11
11
|
readonly printerService: PrinterService;
|
|
12
|
+
readonly detectDarkBackground: (() => Promise<boolean | undefined>) | undefined;
|
|
12
13
|
/**
|
|
13
14
|
* Create an instance of the service provider with the specified details.
|
|
14
15
|
*
|
|
15
16
|
* @param servicePriority the priority of the service.
|
|
16
17
|
* @param printerService the PrinterService implementation to use.
|
|
18
|
+
* @param detectDarkBackground optional function detecting whether the terminal background is dark.
|
|
17
19
|
*/
|
|
18
|
-
constructor(servicePriority: number, printerService: PrinterService);
|
|
20
|
+
constructor(servicePriority: number, printerService: PrinterService, detectDarkBackground?: () => Promise<boolean | undefined>);
|
|
19
21
|
getServiceInfo(_cliConfig: CLIConfig): Promise<ServiceInfo>;
|
|
20
22
|
initService(context: Context): Promise<void>;
|
|
21
23
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PrinterServiceProvider.d.ts","sourceRoot":"","sources":["../../../../src/service/printer/PrinterServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC5F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAK9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKzE;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,sBAAuB,YAAW,eAAe;IACpE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAsB;IAChD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"PrinterServiceProvider.d.ts","sourceRoot":"","sources":["../../../../src/service/printer/PrinterServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC5F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAK9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKzE;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,sBAAuB,YAAW,eAAe;IACpE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAsB;IAChD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,QAAQ,CAAC,oBAAoB,EAAE,CAAC,MAAM,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,CAAC;IAEhF;;;;;;OAMG;IACH,YACE,eAAe,EAAE,MAAM,EACvB,cAAc,EAAE,cAAc,EAC9B,oBAAoB,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,EAK1D;IAEM,cAAc,CAAC,UAAU,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAUjE;IAED,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAY3C;CACF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import LogLevelCommand from "./command/LogLevelCommand.js";
|
|
2
2
|
import { PRINTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
|
-
import
|
|
3
|
+
import ThemeCommand from "./command/ThemeCommand.js";
|
|
4
4
|
import NoColorCommand from "./command/NoColorCommand.js";
|
|
5
5
|
import NoHyperlinksCommand from "./command/NoHyperlinksCommand.js";
|
|
6
6
|
import { SHUTDOWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
@@ -12,23 +12,26 @@ export default class PrinterServiceProvider {
|
|
|
12
12
|
serviceId = PRINTER_SERVICE_ID;
|
|
13
13
|
servicePriority;
|
|
14
14
|
printerService;
|
|
15
|
+
detectDarkBackground;
|
|
15
16
|
/**
|
|
16
17
|
* Create an instance of the service provider with the specified details.
|
|
17
18
|
*
|
|
18
19
|
* @param servicePriority the priority of the service.
|
|
19
20
|
* @param printerService the PrinterService implementation to use.
|
|
21
|
+
* @param detectDarkBackground optional function detecting whether the terminal background is dark.
|
|
20
22
|
*/
|
|
21
|
-
constructor(servicePriority, printerService) {
|
|
23
|
+
constructor(servicePriority, printerService, detectDarkBackground) {
|
|
22
24
|
this.servicePriority = servicePriority;
|
|
23
25
|
this.printerService = printerService;
|
|
26
|
+
this.detectDarkBackground = detectDarkBackground;
|
|
24
27
|
}
|
|
25
28
|
getServiceInfo(_cliConfig) {
|
|
26
29
|
return Promise.resolve({
|
|
27
30
|
service: this.printerService,
|
|
28
31
|
commands: [
|
|
29
32
|
new NoHyperlinksCommand(this.servicePriority + 3),
|
|
30
|
-
new
|
|
31
|
-
new NoColorCommand(this.servicePriority +
|
|
33
|
+
new ThemeCommand(this.servicePriority + 1, this.detectDarkBackground),
|
|
34
|
+
new NoColorCommand(this.servicePriority + 2),
|
|
32
35
|
new LogLevelCommand(this.servicePriority),
|
|
33
36
|
],
|
|
34
37
|
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { GlobalModifierCommand } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import type { GlobalCommandArgument } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
|
+
import { type SingleValueType } from "@flowscripter/dynamic-cli-framework-api";
|
|
4
|
+
import type { Context } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
|
+
/**
|
|
6
|
+
* Command allowing the specification of the dark/light theme for the {@link PrinterService}.
|
|
7
|
+
*
|
|
8
|
+
* With the `auto` value the terminal background is detected and light is used if it cannot be determined.
|
|
9
|
+
*/
|
|
10
|
+
export default class ThemeCommand implements GlobalModifierCommand {
|
|
11
|
+
#private;
|
|
12
|
+
readonly name = "theme";
|
|
13
|
+
readonly description = "Theme for output";
|
|
14
|
+
readonly enableConfiguration = true;
|
|
15
|
+
readonly argument: GlobalCommandArgument;
|
|
16
|
+
readonly executePriority: number;
|
|
17
|
+
/**
|
|
18
|
+
* @param executePriority the priority of the command.
|
|
19
|
+
* @param detect optional function detecting whether the terminal background is dark.
|
|
20
|
+
*/
|
|
21
|
+
constructor(executePriority: number, detect?: () => Promise<boolean | undefined>);
|
|
22
|
+
execute(context: Context, argumentValue: SingleValueType): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=ThemeCommand.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThemeCommand.d.ts","sourceRoot":"","sources":["../../../../../src/service/printer/command/ThemeCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AACrF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AACrF,OAAO,EAAE,KAAK,eAAe,EAAiB,MAAM,yCAAyC,CAAC;AAC9F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAIvE;;;;GAIG;AACH,MAAM,CAAC,OAAO,OAAO,YAAa,YAAW,qBAAqB;;IAChE,QAAQ,CAAC,IAAI,WAAW;IACxB,QAAQ,CAAC,WAAW,sBAAsB;IAC1C,QAAQ,CAAC,mBAAmB,QAAQ;IACpC,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,CAKtC;IACF,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAGjC;;;OAGG;IACH,YAAmB,eAAe,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,EAGtF;IAEY,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAWpF;CACF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ValueTypeName } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import { PRINTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
|
+
/**
|
|
4
|
+
* Command allowing the specification of the dark/light theme for the {@link PrinterService}.
|
|
5
|
+
*
|
|
6
|
+
* With the `auto` value the terminal background is detected and light is used if it cannot be determined.
|
|
7
|
+
*/
|
|
8
|
+
export default class ThemeCommand {
|
|
9
|
+
name = "theme";
|
|
10
|
+
description = "Theme for output";
|
|
11
|
+
enableConfiguration = true;
|
|
12
|
+
argument = {
|
|
13
|
+
type: ValueTypeName.STRING,
|
|
14
|
+
defaultValue: "auto",
|
|
15
|
+
allowableValues: ["auto", "dark", "light"],
|
|
16
|
+
configurationKey: "THEME",
|
|
17
|
+
};
|
|
18
|
+
executePriority;
|
|
19
|
+
#detect;
|
|
20
|
+
/**
|
|
21
|
+
* @param executePriority the priority of the command.
|
|
22
|
+
* @param detect optional function detecting whether the terminal background is dark.
|
|
23
|
+
*/
|
|
24
|
+
constructor(executePriority, detect) {
|
|
25
|
+
this.executePriority = executePriority;
|
|
26
|
+
this.#detect = detect;
|
|
27
|
+
}
|
|
28
|
+
async execute(context, argumentValue) {
|
|
29
|
+
const printerService = context.getServiceById(PRINTER_SERVICE_ID);
|
|
30
|
+
if (argumentValue === "auto") {
|
|
31
|
+
printerService.darkMode =
|
|
32
|
+
printerService.colorEnabled && this.#detect !== undefined
|
|
33
|
+
? ((await this.#detect()) ?? false)
|
|
34
|
+
: false;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
printerService.darkMode = argumentValue === "dark";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export declare const ESC = "\u001B";
|
|
2
|
+
export declare const BEL = "\u0007";
|
|
1
3
|
export declare const CLEAR_LINE: string;
|
|
2
4
|
export declare const CURSOR_LEFT: string;
|
|
3
5
|
export declare const CURSOR_UP: string;
|
|
@@ -7,6 +9,8 @@ export declare const ITALIC_START: string;
|
|
|
7
9
|
export declare const ITALIC_END: string;
|
|
8
10
|
export declare const FOREGROUND_COLOR_END: string;
|
|
9
11
|
export declare const BACKGROUND_COLOR_END: string;
|
|
12
|
+
export declare const QUERY_BACKGROUND_COLOR = "\u001B]11;?\u001B\\";
|
|
13
|
+
export declare const QUERY_DEVICE_ATTRIBUTES: string;
|
|
10
14
|
export declare const HYPERLINK_END = "\u001B]8;;\u0007";
|
|
11
15
|
export declare function foregroundColorStart(r: number, g: number, b: number): string;
|
|
12
16
|
export declare function backgroundColorStart(r: number, g: number, b: number): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Ansi.d.ts","sourceRoot":"","sources":["../../../src/terminal/Ansi.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Ansi.d.ts","sourceRoot":"","sources":["../../../src/terminal/Ansi.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,GAAG,WAAS,CAAC;AAC1B,eAAO,MAAM,GAAG,WAAS,CAAC;AAM1B,eAAO,MAAM,UAAU,QAAa,CAAC;AACrC,eAAO,MAAM,WAAW,QAAY,CAAC;AACrC,eAAO,MAAM,SAAS,QAAa,CAAC;AACpC,eAAO,MAAM,WAAW,QAAe,CAAC;AACxC,eAAO,MAAM,WAAW,QAAe,CAAC;AACxC,eAAO,MAAM,YAAY,QAAa,CAAC;AACvC,eAAO,MAAM,UAAU,QAAc,CAAC;AACtC,eAAO,MAAM,oBAAoB,QAAc,CAAC;AAChD,eAAO,MAAM,oBAAoB,QAAc,CAAC;AAChD,eAAO,MAAM,sBAAsB,wBAAoB,CAAC;AACxD,eAAO,MAAM,uBAAuB,QAAY,CAAC;AACjD,eAAO,MAAM,aAAa,qBAA8B,CAAC;AAEzD,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5E;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5E;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const
|
|
1
|
+
export const ESC = "\x1b";
|
|
2
|
+
export const BEL = "\x07";
|
|
3
|
+
const CSI = ESC + "[";
|
|
4
|
+
const OSC = ESC + "]";
|
|
5
|
+
const ST = ESC + "\\";
|
|
4
6
|
const SEP = ";";
|
|
5
7
|
export const CLEAR_LINE = CSI + "2K";
|
|
6
8
|
export const CURSOR_LEFT = CSI + "G";
|
|
@@ -11,6 +13,8 @@ export const ITALIC_START = CSI + "3m";
|
|
|
11
13
|
export const ITALIC_END = CSI + "23m";
|
|
12
14
|
export const FOREGROUND_COLOR_END = CSI + "39m";
|
|
13
15
|
export const BACKGROUND_COLOR_END = CSI + "49m";
|
|
16
|
+
export const QUERY_BACKGROUND_COLOR = `${OSC}11;?${ST}`;
|
|
17
|
+
export const QUERY_DEVICE_ATTRIBUTES = CSI + "c";
|
|
14
18
|
export const HYPERLINK_END = `${OSC}8${SEP}${SEP}${BEL}`;
|
|
15
19
|
export function foregroundColorStart(r, g, b) {
|
|
16
20
|
return `\x1b[38;2;${r};${g};${b}m`;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type KeyReader from "./KeyReader.ts";
|
|
2
|
+
import type Terminal from "./Terminal.ts";
|
|
3
|
+
export declare const DEFAULT_DETECTION_TIMEOUT_MS = 150;
|
|
4
|
+
/**
|
|
5
|
+
* Parse the COLORFGBG environment variable value into a dark background flag.
|
|
6
|
+
*
|
|
7
|
+
* @returns `true` for a dark background, `false` for a light background or `undefined` if not determinable.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseColorFgBg(value: string | undefined): boolean | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Parse an OSC 11 background colour reply into a dark background flag.
|
|
12
|
+
*
|
|
13
|
+
* @returns `true` for a dark background, `false` for a light background or `undefined` if no reply is present.
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseOsc11Reply(data: string): boolean | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Detect whether the terminal background is dark.
|
|
18
|
+
*
|
|
19
|
+
* Checks COLORFGBG first and then queries the terminal for its background colour (OSC 11).
|
|
20
|
+
* The query is skipped unless the terminal is a TTY and is bounded by a timeout.
|
|
21
|
+
*
|
|
22
|
+
* @returns `true` for a dark background, `false` for a light background or `undefined` if not determinable.
|
|
23
|
+
*/
|
|
24
|
+
export declare function detectDarkBackground(keyReader: KeyReader, terminal: Terminal, env?: Record<string, string | undefined>, timeoutMs?: number): Promise<boolean | undefined>;
|
|
25
|
+
//# sourceMappingURL=BackgroundDetector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BackgroundDetector.d.ts","sourceRoot":"","sources":["../../../src/terminal/BackgroundDetector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAC5C,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAQ1C,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAEhD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAc7E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CASjE;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,QAAQ,EAClB,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,EACrD,SAAS,GAAE,MAAqC,GAC/C,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAe9B"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { BEL, ESC, QUERY_BACKGROUND_COLOR, QUERY_DEVICE_ATTRIBUTES } from "./Ansi.js";
|
|
3
|
+
const OSC_11_REPLY = new RegExp(`${ESC}\\]11;rgb:([0-9a-fA-F]{1,4})/([0-9a-fA-F]{1,4})/([0-9a-fA-F]{1,4})(?:${BEL}|${ESC}\\\\)`);
|
|
4
|
+
const DA1_REPLY = new RegExp(`${ESC}\\[\\?[0-9;]*c`);
|
|
5
|
+
export const DEFAULT_DETECTION_TIMEOUT_MS = 150;
|
|
6
|
+
/**
|
|
7
|
+
* Parse the COLORFGBG environment variable value into a dark background flag.
|
|
8
|
+
*
|
|
9
|
+
* @returns `true` for a dark background, `false` for a light background or `undefined` if not determinable.
|
|
10
|
+
*/
|
|
11
|
+
export function parseColorFgBg(value) {
|
|
12
|
+
if (value === undefined) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
const parts = value.split(";");
|
|
16
|
+
const background = parts[parts.length - 1];
|
|
17
|
+
if (!/^\d{1,2}$/.test(background)) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const index = Number(background);
|
|
21
|
+
if (index > 15) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
return index <= 6 || index === 8;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Parse an OSC 11 background colour reply into a dark background flag.
|
|
28
|
+
*
|
|
29
|
+
* @returns `true` for a dark background, `false` for a light background or `undefined` if no reply is present.
|
|
30
|
+
*/
|
|
31
|
+
export function parseOsc11Reply(data) {
|
|
32
|
+
const match = OSC_11_REPLY.exec(data);
|
|
33
|
+
if (match === null) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
const [r, g, b] = [match[1], match[2], match[3]].map((channel) => parseInt(channel, 16) / (16 ** channel.length - 1));
|
|
37
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b < 0.5;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Detect whether the terminal background is dark.
|
|
41
|
+
*
|
|
42
|
+
* Checks COLORFGBG first and then queries the terminal for its background colour (OSC 11).
|
|
43
|
+
* The query is skipped unless the terminal is a TTY and is bounded by a timeout.
|
|
44
|
+
*
|
|
45
|
+
* @returns `true` for a dark background, `false` for a light background or `undefined` if not determinable.
|
|
46
|
+
*/
|
|
47
|
+
export async function detectDarkBackground(keyReader, terminal, env = process.env, timeoutMs = DEFAULT_DETECTION_TIMEOUT_MS) {
|
|
48
|
+
if (!terminal.isTty() || env.TERM === "dumb") {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const fromEnv = parseColorFgBg(env.COLORFGBG);
|
|
52
|
+
if (fromEnv !== undefined) {
|
|
53
|
+
return fromEnv;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const reply = keyReader.readRaw((received) => DA1_REPLY.test(received), timeoutMs);
|
|
57
|
+
await terminal.write(QUERY_BACKGROUND_COLOR + QUERY_DEVICE_ATTRIBUTES);
|
|
58
|
+
return parseOsc11Reply(await reply);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -18,5 +18,12 @@ export default interface KeyReader {
|
|
|
18
18
|
enableRawMode(): void;
|
|
19
19
|
disableRawMode(): void;
|
|
20
20
|
readKey(): Promise<KeyEvent>;
|
|
21
|
+
/**
|
|
22
|
+
* Read raw input until `isComplete` returns `true` for the accumulated input or `timeoutMs` elapses.
|
|
23
|
+
* Raw mode and paused state are restored afterwards.
|
|
24
|
+
*
|
|
25
|
+
* @returns the accumulated input.
|
|
26
|
+
*/
|
|
27
|
+
readRaw(isComplete: (received: string) => boolean, timeoutMs: number): Promise<string>;
|
|
21
28
|
}
|
|
22
29
|
//# sourceMappingURL=KeyReader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KeyReader.d.ts","sourceRoot":"","sources":["../../../src/terminal/KeyReader.ts"],"names":[],"mappings":"AAAA,oBAAY,UAAU;IACpB,EAAE,IAAI;IACN,IAAI,IAAI;IACR,IAAI,IAAI;IACR,KAAK,IAAI;IACT,KAAK,IAAI;IACT,KAAK,IAAI;IACT,SAAS,IAAI;IACb,MAAM,IAAI;IACV,GAAG,IAAI;IACP,SAAS,IAAI;CACd;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;CAClC;AAED,MAAM,CAAC,OAAO,WAAW,SAAS;IAChC,aAAa,IAAI,IAAI,CAAC;IACtB,cAAc,IAAI,IAAI,CAAC;IACvB,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"KeyReader.d.ts","sourceRoot":"","sources":["../../../src/terminal/KeyReader.ts"],"names":[],"mappings":"AAAA,oBAAY,UAAU;IACpB,EAAE,IAAI;IACN,IAAI,IAAI;IACR,IAAI,IAAI;IACR,KAAK,IAAI;IACT,KAAK,IAAI;IACT,KAAK,IAAI;IACT,SAAS,IAAI;IACb,MAAM,IAAI;IACV,GAAG,IAAI;IACP,SAAS,IAAI;CACd;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;CAClC;AAED,MAAM,CAAC,OAAO,WAAW,SAAS;IAChC,aAAa,IAAI,IAAI,CAAC;IACtB,cAAc,IAAI,IAAI,CAAC;IACvB,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7B;;;;;OAKG;IACH,OAAO,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACxF"}
|
|
@@ -7,5 +7,6 @@ export default class TtyKeyReader implements KeyReader {
|
|
|
7
7
|
enableRawMode(): void;
|
|
8
8
|
disableRawMode(): void;
|
|
9
9
|
readKey(): Promise<KeyEvent>;
|
|
10
|
+
readRaw(isComplete: (received: string) => boolean, timeoutMs: number): Promise<string>;
|
|
10
11
|
}
|
|
11
12
|
//# sourceMappingURL=TtyKeyReader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TtyKeyReader.d.ts","sourceRoot":"","sources":["../../../src/terminal/TtyKeyReader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,KAAK,QAAQ,EAAc,MAAM,gBAAgB,CAAC;AAE3D,MAAM,CAAC,OAAO,OAAO,YAAa,YAAW,SAAS;;IAGpD,YAAY,aAAa,EAAE,UAAU,EAKpC;IAED,aAAa,IAAI,IAAI,CAGpB;IAED,cAAc,IAAI,IAAI,CAGrB;IAED,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,CA2B3B;CAiDF"}
|
|
1
|
+
{"version":3,"file":"TtyKeyReader.d.ts","sourceRoot":"","sources":["../../../src/terminal/TtyKeyReader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,KAAK,QAAQ,EAAc,MAAM,gBAAgB,CAAC;AAE3D,MAAM,CAAC,OAAO,OAAO,YAAa,YAAW,SAAS;;IAGpD,YAAY,aAAa,EAAE,UAAU,EAKpC;IAED,aAAa,IAAI,IAAI,CAGpB;IAED,cAAc,IAAI,IAAI,CAGrB;IAED,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,CA2B3B;IAED,OAAO,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAiCrF;CAiDF"}
|
|
@@ -41,6 +41,36 @@ export default class TtyKeyReader {
|
|
|
41
41
|
this.#ttyReadStream.on("data", onData);
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
|
+
readRaw(isComplete, timeoutMs) {
|
|
45
|
+
const stream = this.#ttyReadStream;
|
|
46
|
+
const wasRaw = stream.isRaw === true;
|
|
47
|
+
const wasPaused = stream.isPaused();
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
let received = "";
|
|
50
|
+
let timer;
|
|
51
|
+
const finish = () => {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
stream.removeListener("data", onData);
|
|
54
|
+
if (!wasRaw) {
|
|
55
|
+
stream.setRawMode(false);
|
|
56
|
+
}
|
|
57
|
+
if (wasPaused) {
|
|
58
|
+
stream.pause();
|
|
59
|
+
}
|
|
60
|
+
resolve(received);
|
|
61
|
+
};
|
|
62
|
+
const onData = (data) => {
|
|
63
|
+
received += data.toString();
|
|
64
|
+
if (isComplete(received)) {
|
|
65
|
+
finish();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
stream.setRawMode(true);
|
|
69
|
+
stream.on("data", onData);
|
|
70
|
+
stream.resume();
|
|
71
|
+
timer = setTimeout(finish, timeoutMs);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
44
74
|
static #decodeKeyEvent(data) {
|
|
45
75
|
if (data.length === 0) {
|
|
46
76
|
return {};
|
package/package.json
CHANGED
package/src/cli/BaseCLI.ts
CHANGED
|
@@ -44,6 +44,7 @@ import type Terminal from "../terminal/Terminal.ts";
|
|
|
44
44
|
import type { PrinterService } from "@flowscripter/dynamic-cli-framework-api";
|
|
45
45
|
import DefaultPrinterService from "../service/printer/DefaultPrinterService.ts";
|
|
46
46
|
import type Styler from "../terminal/Styler.ts";
|
|
47
|
+
import { detectDarkBackground } from "../terminal/BackgroundDetector.ts";
|
|
47
48
|
import type KeyReader from "../terminal/KeyReader.ts";
|
|
48
49
|
import type BaseCLIFeatureOptions from "./BaseCLIFeatureOptions.ts";
|
|
49
50
|
import DefaultPrompterService, {
|
|
@@ -276,13 +277,19 @@ export default class BaseCLI implements CLI {
|
|
|
276
277
|
// create and add core services
|
|
277
278
|
this.addServiceProvider(new ShutdownServiceProvider(SHUTDOWN_SERVICE_PRIORITY));
|
|
278
279
|
this.addServiceProvider(this.#startupServiceProvider);
|
|
280
|
+
const canPrompt = this.#context.isTty.stdin && this.#context.isTty.stderr;
|
|
281
|
+
|
|
279
282
|
this.addServiceProvider(
|
|
280
|
-
new PrinterServiceProvider(
|
|
283
|
+
new PrinterServiceProvider(
|
|
284
|
+
PRINTER_SERVICE_PRIORITY,
|
|
285
|
+
this.#printerService,
|
|
286
|
+
canPrompt && this.#keyReader !== undefined
|
|
287
|
+
? () => detectDarkBackground(this.#keyReader!, this.#stderrTerminal)
|
|
288
|
+
: undefined,
|
|
289
|
+
),
|
|
281
290
|
);
|
|
282
291
|
this.addServiceProvider(new TableGeneratorServiceProvider(TABLE_GENERATOR_SERVICE_PRIORITY));
|
|
283
292
|
|
|
284
|
-
const canPrompt = this.#context.isTty.stdin && this.#context.isTty.stderr;
|
|
285
|
-
|
|
286
293
|
if (this.#options.promptingEnabled && !canPrompt) {
|
|
287
294
|
throw new Error("promptingEnabled requires a keyReader and a TTY stderr terminal");
|
|
288
295
|
}
|
|
@@ -3,7 +3,7 @@ import LogLevelCommand from "./command/LogLevelCommand.ts";
|
|
|
3
3
|
import type { ServiceInfo, ServiceProvider } from "@flowscripter/dynamic-cli-framework-api";
|
|
4
4
|
import type { PrinterService } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
5
|
import { PRINTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
6
|
-
import
|
|
6
|
+
import ThemeCommand from "./command/ThemeCommand.ts";
|
|
7
7
|
import NoColorCommand from "./command/NoColorCommand.ts";
|
|
8
8
|
import NoHyperlinksCommand from "./command/NoHyperlinksCommand.ts";
|
|
9
9
|
import type { CLIConfig } from "@flowscripter/dynamic-cli-framework-api";
|
|
@@ -18,16 +18,23 @@ export default class PrinterServiceProvider implements ServiceProvider {
|
|
|
18
18
|
readonly serviceId: string = PRINTER_SERVICE_ID;
|
|
19
19
|
readonly servicePriority: number;
|
|
20
20
|
readonly printerService: PrinterService;
|
|
21
|
+
readonly detectDarkBackground: (() => Promise<boolean | undefined>) | undefined;
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Create an instance of the service provider with the specified details.
|
|
24
25
|
*
|
|
25
26
|
* @param servicePriority the priority of the service.
|
|
26
27
|
* @param printerService the PrinterService implementation to use.
|
|
28
|
+
* @param detectDarkBackground optional function detecting whether the terminal background is dark.
|
|
27
29
|
*/
|
|
28
|
-
public constructor(
|
|
30
|
+
public constructor(
|
|
31
|
+
servicePriority: number,
|
|
32
|
+
printerService: PrinterService,
|
|
33
|
+
detectDarkBackground?: () => Promise<boolean | undefined>,
|
|
34
|
+
) {
|
|
29
35
|
this.servicePriority = servicePriority;
|
|
30
36
|
this.printerService = printerService;
|
|
37
|
+
this.detectDarkBackground = detectDarkBackground;
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
public getServiceInfo(_cliConfig: CLIConfig): Promise<ServiceInfo> {
|
|
@@ -35,8 +42,8 @@ export default class PrinterServiceProvider implements ServiceProvider {
|
|
|
35
42
|
service: this.printerService,
|
|
36
43
|
commands: [
|
|
37
44
|
new NoHyperlinksCommand(this.servicePriority + 3),
|
|
38
|
-
new
|
|
39
|
-
new NoColorCommand(this.servicePriority +
|
|
45
|
+
new ThemeCommand(this.servicePriority + 1, this.detectDarkBackground),
|
|
46
|
+
new NoColorCommand(this.servicePriority + 2),
|
|
40
47
|
new LogLevelCommand(this.servicePriority),
|
|
41
48
|
],
|
|
42
49
|
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { GlobalModifierCommand } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import type { GlobalCommandArgument } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
|
+
import { type SingleValueType, ValueTypeName } from "@flowscripter/dynamic-cli-framework-api";
|
|
4
|
+
import type { Context } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
|
+
import type { PrinterService } from "@flowscripter/dynamic-cli-framework-api";
|
|
6
|
+
import { PRINTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Command allowing the specification of the dark/light theme for the {@link PrinterService}.
|
|
10
|
+
*
|
|
11
|
+
* With the `auto` value the terminal background is detected and light is used if it cannot be determined.
|
|
12
|
+
*/
|
|
13
|
+
export default class ThemeCommand implements GlobalModifierCommand {
|
|
14
|
+
readonly name = "theme";
|
|
15
|
+
readonly description = "Theme for output";
|
|
16
|
+
readonly enableConfiguration = true;
|
|
17
|
+
readonly argument: GlobalCommandArgument = {
|
|
18
|
+
type: ValueTypeName.STRING,
|
|
19
|
+
defaultValue: "auto",
|
|
20
|
+
allowableValues: ["auto", "dark", "light"],
|
|
21
|
+
configurationKey: "THEME",
|
|
22
|
+
};
|
|
23
|
+
readonly executePriority: number;
|
|
24
|
+
readonly #detect: (() => Promise<boolean | undefined>) | undefined;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param executePriority the priority of the command.
|
|
28
|
+
* @param detect optional function detecting whether the terminal background is dark.
|
|
29
|
+
*/
|
|
30
|
+
public constructor(executePriority: number, detect?: () => Promise<boolean | undefined>) {
|
|
31
|
+
this.executePriority = executePriority;
|
|
32
|
+
this.#detect = detect;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public async execute(context: Context, argumentValue: SingleValueType): Promise<void> {
|
|
36
|
+
const printerService = context.getServiceById(PRINTER_SERVICE_ID) as PrinterService;
|
|
37
|
+
|
|
38
|
+
if (argumentValue === "auto") {
|
|
39
|
+
printerService.darkMode =
|
|
40
|
+
printerService.colorEnabled && this.#detect !== undefined
|
|
41
|
+
? ((await this.#detect()) ?? false)
|
|
42
|
+
: false;
|
|
43
|
+
} else {
|
|
44
|
+
printerService.darkMode = argumentValue === "dark";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/terminal/Ansi.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const
|
|
1
|
+
export const ESC = "\x1b";
|
|
2
|
+
export const BEL = "\x07";
|
|
3
|
+
const CSI = ESC + "[";
|
|
4
|
+
const OSC = ESC + "]";
|
|
5
|
+
const ST = ESC + "\\";
|
|
4
6
|
const SEP = ";";
|
|
5
7
|
|
|
6
8
|
export const CLEAR_LINE = CSI + "2K";
|
|
@@ -12,6 +14,8 @@ export const ITALIC_START = CSI + "3m";
|
|
|
12
14
|
export const ITALIC_END = CSI + "23m";
|
|
13
15
|
export const FOREGROUND_COLOR_END = CSI + "39m";
|
|
14
16
|
export const BACKGROUND_COLOR_END = CSI + "49m";
|
|
17
|
+
export const QUERY_BACKGROUND_COLOR = `${OSC}11;?${ST}`;
|
|
18
|
+
export const QUERY_DEVICE_ATTRIBUTES = CSI + "c";
|
|
15
19
|
export const HYPERLINK_END = `${OSC}8${SEP}${SEP}${BEL}`;
|
|
16
20
|
|
|
17
21
|
export function foregroundColorStart(r: number, g: number, b: number): string {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import type KeyReader from "./KeyReader.ts";
|
|
3
|
+
import type Terminal from "./Terminal.ts";
|
|
4
|
+
import { BEL, ESC, QUERY_BACKGROUND_COLOR, QUERY_DEVICE_ATTRIBUTES } from "./Ansi.ts";
|
|
5
|
+
|
|
6
|
+
const OSC_11_REPLY = new RegExp(
|
|
7
|
+
`${ESC}\\]11;rgb:([0-9a-fA-F]{1,4})/([0-9a-fA-F]{1,4})/([0-9a-fA-F]{1,4})(?:${BEL}|${ESC}\\\\)`,
|
|
8
|
+
);
|
|
9
|
+
const DA1_REPLY = new RegExp(`${ESC}\\[\\?[0-9;]*c`);
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_DETECTION_TIMEOUT_MS = 150;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parse the COLORFGBG environment variable value into a dark background flag.
|
|
15
|
+
*
|
|
16
|
+
* @returns `true` for a dark background, `false` for a light background or `undefined` if not determinable.
|
|
17
|
+
*/
|
|
18
|
+
export function parseColorFgBg(value: string | undefined): boolean | undefined {
|
|
19
|
+
if (value === undefined) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const parts = value.split(";");
|
|
23
|
+
const background = parts[parts.length - 1]!;
|
|
24
|
+
if (!/^\d{1,2}$/.test(background)) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const index = Number(background);
|
|
28
|
+
if (index > 15) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
return index <= 6 || index === 8;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Parse an OSC 11 background colour reply into a dark background flag.
|
|
36
|
+
*
|
|
37
|
+
* @returns `true` for a dark background, `false` for a light background or `undefined` if no reply is present.
|
|
38
|
+
*/
|
|
39
|
+
export function parseOsc11Reply(data: string): boolean | undefined {
|
|
40
|
+
const match = OSC_11_REPLY.exec(data);
|
|
41
|
+
if (match === null) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const [r, g, b] = [match[1]!, match[2]!, match[3]!].map(
|
|
45
|
+
(channel) => parseInt(channel, 16) / (16 ** channel.length - 1),
|
|
46
|
+
) as [number, number, number];
|
|
47
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b < 0.5;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Detect whether the terminal background is dark.
|
|
52
|
+
*
|
|
53
|
+
* Checks COLORFGBG first and then queries the terminal for its background colour (OSC 11).
|
|
54
|
+
* The query is skipped unless the terminal is a TTY and is bounded by a timeout.
|
|
55
|
+
*
|
|
56
|
+
* @returns `true` for a dark background, `false` for a light background or `undefined` if not determinable.
|
|
57
|
+
*/
|
|
58
|
+
export async function detectDarkBackground(
|
|
59
|
+
keyReader: KeyReader,
|
|
60
|
+
terminal: Terminal,
|
|
61
|
+
env: Record<string, string | undefined> = process.env,
|
|
62
|
+
timeoutMs: number = DEFAULT_DETECTION_TIMEOUT_MS,
|
|
63
|
+
): Promise<boolean | undefined> {
|
|
64
|
+
if (!terminal.isTty() || env.TERM === "dumb") {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const fromEnv = parseColorFgBg(env.COLORFGBG);
|
|
68
|
+
if (fromEnv !== undefined) {
|
|
69
|
+
return fromEnv;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const reply = keyReader.readRaw((received) => DA1_REPLY.test(received), timeoutMs);
|
|
73
|
+
await terminal.write(QUERY_BACKGROUND_COLOR + QUERY_DEVICE_ATTRIBUTES);
|
|
74
|
+
return parseOsc11Reply(await reply);
|
|
75
|
+
} catch {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -20,4 +20,11 @@ export default interface KeyReader {
|
|
|
20
20
|
enableRawMode(): void;
|
|
21
21
|
disableRawMode(): void;
|
|
22
22
|
readKey(): Promise<KeyEvent>;
|
|
23
|
+
/**
|
|
24
|
+
* Read raw input until `isComplete` returns `true` for the accumulated input or `timeoutMs` elapses.
|
|
25
|
+
* Raw mode and paused state are restored afterwards.
|
|
26
|
+
*
|
|
27
|
+
* @returns the accumulated input.
|
|
28
|
+
*/
|
|
29
|
+
readRaw(isComplete: (received: string) => boolean, timeoutMs: number): Promise<string>;
|
|
23
30
|
}
|
|
@@ -52,6 +52,41 @@ export default class TtyKeyReader implements KeyReader {
|
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
readRaw(isComplete: (received: string) => boolean, timeoutMs: number): Promise<string> {
|
|
56
|
+
const stream = this.#ttyReadStream;
|
|
57
|
+
const wasRaw = stream.isRaw === true;
|
|
58
|
+
const wasPaused = stream.isPaused();
|
|
59
|
+
|
|
60
|
+
return new Promise((resolve) => {
|
|
61
|
+
let received = "";
|
|
62
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
63
|
+
|
|
64
|
+
const finish = (): void => {
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
stream.removeListener("data", onData);
|
|
67
|
+
if (!wasRaw) {
|
|
68
|
+
stream.setRawMode(false);
|
|
69
|
+
}
|
|
70
|
+
if (wasPaused) {
|
|
71
|
+
stream.pause();
|
|
72
|
+
}
|
|
73
|
+
resolve(received);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const onData = (data: Buffer | string): void => {
|
|
77
|
+
received += data.toString();
|
|
78
|
+
if (isComplete(received)) {
|
|
79
|
+
finish();
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
stream.setRawMode(true);
|
|
84
|
+
stream.on("data", onData);
|
|
85
|
+
stream.resume();
|
|
86
|
+
timer = setTimeout(finish, timeoutMs);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
55
90
|
static #decodeKeyEvent(data: Buffer): KeyEvent {
|
|
56
91
|
if (data.length === 0) {
|
|
57
92
|
return {};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { GlobalModifierCommand } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
-
import type { GlobalCommandArgument } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
|
-
import { type SingleValueType } from "@flowscripter/dynamic-cli-framework-api";
|
|
4
|
-
import type { Context } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
|
-
/**
|
|
6
|
-
* Command allowing the specification of dark/light mode for the {@link PrinterService}.
|
|
7
|
-
*/
|
|
8
|
-
export default class DarkModeCommand implements GlobalModifierCommand {
|
|
9
|
-
readonly name = "dark-mode";
|
|
10
|
-
readonly description = "Enable dark mode for output";
|
|
11
|
-
readonly enableConfiguration = true;
|
|
12
|
-
readonly argument: GlobalCommandArgument;
|
|
13
|
-
readonly executePriority: number;
|
|
14
|
-
constructor(executePriority: number);
|
|
15
|
-
execute(context: Context, argumentValue: SingleValueType): Promise<void>;
|
|
16
|
-
}
|
|
17
|
-
//# sourceMappingURL=DarkModeCommand.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DarkModeCommand.d.ts","sourceRoot":"","sources":["../../../../../src/service/printer/command/DarkModeCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AACrF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AACrF,OAAO,EAAE,KAAK,eAAe,EAAiB,MAAM,yCAAyC,CAAC;AAC9F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAIvE;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,eAAgB,YAAW,qBAAqB;IACnE,QAAQ,CAAC,IAAI,eAAe;IAC5B,QAAQ,CAAC,WAAW,iCAAiC;IACrD,QAAQ,CAAC,mBAAmB,QAAQ;IACpC,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,CAItC;IACF,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAEjC,YAAmB,eAAe,EAAE,MAAM,EAEzC;IAEM,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAK9E;CACF"}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { ValueTypeName } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
-
import { PRINTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
|
-
/**
|
|
4
|
-
* Command allowing the specification of dark/light mode for the {@link PrinterService}.
|
|
5
|
-
*/
|
|
6
|
-
export default class DarkModeCommand {
|
|
7
|
-
name = "dark-mode";
|
|
8
|
-
description = "Enable dark mode for output";
|
|
9
|
-
enableConfiguration = true;
|
|
10
|
-
argument = {
|
|
11
|
-
type: ValueTypeName.BOOLEAN,
|
|
12
|
-
defaultValue: false,
|
|
13
|
-
configurationKey: "DARK_MODE",
|
|
14
|
-
};
|
|
15
|
-
executePriority;
|
|
16
|
-
constructor(executePriority) {
|
|
17
|
-
this.executePriority = executePriority;
|
|
18
|
-
}
|
|
19
|
-
execute(context, argumentValue) {
|
|
20
|
-
const printerService = context.getServiceById(PRINTER_SERVICE_ID);
|
|
21
|
-
printerService.darkMode = argumentValue;
|
|
22
|
-
return Promise.resolve();
|
|
23
|
-
}
|
|
24
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { GlobalModifierCommand } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
-
import type { GlobalCommandArgument } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
|
-
import { type SingleValueType, ValueTypeName } from "@flowscripter/dynamic-cli-framework-api";
|
|
4
|
-
import type { Context } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
|
-
import type { PrinterService } from "@flowscripter/dynamic-cli-framework-api";
|
|
6
|
-
import { PRINTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Command allowing the specification of dark/light mode for the {@link PrinterService}.
|
|
10
|
-
*/
|
|
11
|
-
export default class DarkModeCommand implements GlobalModifierCommand {
|
|
12
|
-
readonly name = "dark-mode";
|
|
13
|
-
readonly description = "Enable dark mode for output";
|
|
14
|
-
readonly enableConfiguration = true;
|
|
15
|
-
readonly argument: GlobalCommandArgument = {
|
|
16
|
-
type: ValueTypeName.BOOLEAN,
|
|
17
|
-
defaultValue: false,
|
|
18
|
-
configurationKey: "DARK_MODE",
|
|
19
|
-
};
|
|
20
|
-
readonly executePriority: number;
|
|
21
|
-
|
|
22
|
-
public constructor(executePriority: number) {
|
|
23
|
-
this.executePriority = executePriority;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
public execute(context: Context, argumentValue: SingleValueType): Promise<void> {
|
|
27
|
-
const printerService = context.getServiceById(PRINTER_SERVICE_ID) as PrinterService;
|
|
28
|
-
printerService.darkMode = argumentValue as boolean;
|
|
29
|
-
|
|
30
|
-
return Promise.resolve();
|
|
31
|
-
}
|
|
32
|
-
}
|