@flowscripter/dynamic-cli-framework 5.2.1 → 5.3.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/service/plugin/command/PluginAddSubCommand.js +2 -2
- package/dist/src/service/plugin/command/parsePluginSpecifier.d.ts +9 -6
- package/dist/src/service/plugin/command/parsePluginSpecifier.d.ts.map +1 -1
- package/dist/src/service/plugin/command/parsePluginSpecifier.js +16 -10
- package/dist/src/service/printer/DefaultPrinterService.d.ts.map +1 -1
- package/dist/src/service/printer/DefaultPrinterService.js +9 -1
- package/dist/src/service/printer/terminal/Quote.d.ts.map +1 -1
- package/dist/src/service/printer/terminal/Quote.js +11 -2
- package/package.json +1 -1
- package/src/service/plugin/command/PluginAddSubCommand.ts +2 -2
- package/src/service/plugin/command/parsePluginSpecifier.ts +16 -10
- package/src/service/printer/DefaultPrinterService.ts +9 -1
- package/src/service/printer/terminal/Quote.ts +11 -2
|
@@ -11,7 +11,7 @@ export class PluginAddSubCommand {
|
|
|
11
11
|
positionals = [
|
|
12
12
|
{
|
|
13
13
|
name: "pluginId",
|
|
14
|
-
description: "Plugin ID to install (e.g. @scope/name or @scope/name
|
|
14
|
+
description: "Plugin ID to install (e.g. @scope/name or @scope/name@version)",
|
|
15
15
|
type: ValueTypeName.STRING,
|
|
16
16
|
},
|
|
17
17
|
];
|
|
@@ -19,7 +19,7 @@ export class PluginAddSubCommand {
|
|
|
19
19
|
const printerService = context.getServiceById(PRINTER_SERVICE_ID);
|
|
20
20
|
const pluginService = context.getServiceById(PLUGIN_SERVICE_ID);
|
|
21
21
|
const { pluginId, version } = parsePluginSpecifier(argumentValues["pluginId"]);
|
|
22
|
-
const searchLabel = version ? `${pluginId}
|
|
22
|
+
const searchLabel = version ? `${pluginId}@${version}` : pluginId;
|
|
23
23
|
await printerService.info(`Searching for plugin: ${searchLabel}\n`, Icon.INFORMATION);
|
|
24
24
|
let descriptor;
|
|
25
25
|
for await (const d of pluginService.search({ text: pluginId })) {
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Splits a plugin specifier into a bare plugin ID and an optional version
|
|
2
|
+
* Splits a plugin specifier into a bare plugin ID and an optional version, following npm's own
|
|
3
|
+
* `[@scope/]name[@version]` convention (`:` is not used as a version separator anywhere in the
|
|
4
|
+
* npm/JS ecosystem, so it is not supported here).
|
|
3
5
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
6
|
+
* A leading `@scope/` portion (if present) is stripped first, then the *last* `@` in the
|
|
7
|
+
* remainder - if any - is the version separator. This correctly distinguishes the scope marker
|
|
8
|
+
* (always the first character) from a version-separating `@` occurring later.
|
|
7
9
|
*
|
|
8
|
-
* Everything after the
|
|
10
|
+
* Everything after the separator is always treated as the requested version, including
|
|
9
11
|
* non-semver tags such as `latest` or `next` - no validation is performed on the tail.
|
|
10
12
|
*
|
|
11
|
-
* @param specifier raw plugin specifier, e.g. `@scope/name`, `@scope/name
|
|
13
|
+
* @param specifier raw plugin specifier, e.g. `@scope/name`, `@scope/name@3.0.0` or
|
|
14
|
+
* `name@latest`.
|
|
12
15
|
*/
|
|
13
16
|
export declare function parsePluginSpecifier(specifier: string): {
|
|
14
17
|
pluginId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parsePluginSpecifier.d.ts","sourceRoot":"","sources":["../../../../../src/service/plugin/command/parsePluginSpecifier.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"parsePluginSpecifier.d.ts","sourceRoot":"","sources":["../../../../../src/service/plugin/command/parsePluginSpecifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAY9F"}
|
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Splits a plugin specifier into a bare plugin ID and an optional version
|
|
2
|
+
* Splits a plugin specifier into a bare plugin ID and an optional version, following npm's own
|
|
3
|
+
* `[@scope/]name[@version]` convention (`:` is not used as a version separator anywhere in the
|
|
4
|
+
* npm/JS ecosystem, so it is not supported here).
|
|
3
5
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
6
|
+
* A leading `@scope/` portion (if present) is stripped first, then the *last* `@` in the
|
|
7
|
+
* remainder - if any - is the version separator. This correctly distinguishes the scope marker
|
|
8
|
+
* (always the first character) from a version-separating `@` occurring later.
|
|
7
9
|
*
|
|
8
|
-
* Everything after the
|
|
10
|
+
* Everything after the separator is always treated as the requested version, including
|
|
9
11
|
* non-semver tags such as `latest` or `next` - no validation is performed on the tail.
|
|
10
12
|
*
|
|
11
|
-
* @param specifier raw plugin specifier, e.g. `@scope/name`, `@scope/name
|
|
13
|
+
* @param specifier raw plugin specifier, e.g. `@scope/name`, `@scope/name@3.0.0` or
|
|
14
|
+
* `name@latest`.
|
|
12
15
|
*/
|
|
13
16
|
export function parsePluginSpecifier(specifier) {
|
|
14
|
-
const
|
|
15
|
-
|
|
17
|
+
const isScoped = specifier.startsWith("@");
|
|
18
|
+
const scopeEnd = isScoped ? specifier.indexOf("/") : -1;
|
|
19
|
+
const searchFrom = isScoped && scopeEnd !== -1 ? scopeEnd + 1 : 0;
|
|
20
|
+
const atIndex = specifier.lastIndexOf("@");
|
|
21
|
+
if (atIndex === -1 || atIndex < searchFrom) {
|
|
16
22
|
return { pluginId: specifier };
|
|
17
23
|
}
|
|
18
24
|
return {
|
|
19
|
-
pluginId: specifier.slice(0,
|
|
20
|
-
version: specifier.slice(
|
|
25
|
+
pluginId: specifier.slice(0, atIndex),
|
|
26
|
+
version: specifier.slice(atIndex + 1),
|
|
21
27
|
};
|
|
22
28
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultPrinterService.d.ts","sourceRoot":"","sources":["../../../../src/service/printer/DefaultPrinterService.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAC9E,OAAO,EACL,KAAK,IAAI,EACT,KAAK,EACL,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,QAAQ,MAAM,4BAA4B,CAAC;AAGvD,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,MAAM,MAAM,0BAA0B,CAAC;AAEnD,MAAM,CAAC,OAAO,OAAO,qBAAsB,YAAW,cAAc;;IAClE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"DefaultPrinterService.d.ts","sourceRoot":"","sources":["../../../../src/service/printer/DefaultPrinterService.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAC9E,OAAO,EACL,KAAK,IAAI,EACT,KAAK,EACL,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,QAAQ,MAAM,4BAA4B,CAAC;AAGvD,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,MAAM,MAAM,0BAA0B,CAAC;AAEnD,MAAM,CAAC,OAAO,OAAO,qBAAsB,YAAW,cAAc;;IAClE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;gBAiDtC,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;IAgBhB,IAAI,YAAY,CAAC,OAAO,EAAE,OAAO,EAQhC;IAED,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED,IAAI,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAErC;IAED,IAAI,iBAAiB,IAAI,OAAO,CAE/B;IAEM,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM;IAI5C,aAAa,IAAI,MAAM;IAIvB,aAAa,IAAI,MAAM;IAI9B,IAAI,QAAQ,CAAC,UAAU,EAAE,OAAO,EAa/B;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAEM,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIhC,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIlC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAInC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIjC,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAI/B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAI/B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAI/B,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAI5B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIhC,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAI/B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAI7B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAI7B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAe9B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,MAAM;IAIzD,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAI1C,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAI5C,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAI7C,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAI3C,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIzC,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIzC,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAItC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAI1C,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIzC,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIvC,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIvC,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIxC,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,MAAM;IAI7D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBxD,UAAU,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI;IAO5C,QAAQ,IAAI,IAAI;IAIhB,SAAS,IAAI,IAAI;IAQjB,OAAO,IAAI,IAAI;IAOtB;;;;OAIG;IACI,WAAW,IAAI,IAAI;IAQb,WAAW,CAAC,oBAAoB,SAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBpD,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ3C,QAAQ,IAAI,KAAK;IAIX,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAclE,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,eAAe,CAC1B,KAAK,EAAE,MAAM,EACb,OAAO,SAAK,EACZ,KAAK,SAAM,EACX,OAAO,SAAI,EACX,KAAK,CAAC,EAAE,aAAa,GACpB,OAAO,CAAC,MAAM,CAAC;IAeL,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI1C,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;CAMlF"}
|
|
@@ -23,7 +23,15 @@ export default class DefaultPrinterService {
|
|
|
23
23
|
#markedLineCount;
|
|
24
24
|
#markEndedAt;
|
|
25
25
|
#countLines(message) {
|
|
26
|
-
|
|
26
|
+
// Strip ANSI escape sequences first: colorText()/italicText() etc. wrap the *entire*
|
|
27
|
+
// passed-in text (including a trailing "\n", when the caller's message already ends with
|
|
28
|
+
// one) with a reset sequence appended *after* that newline, e.g. "foo\n" -> "<color>foo\n
|
|
29
|
+
// <reset>". Checking endsWith("\n") on the styled text is then always false, so the
|
|
30
|
+
// subtraction below never fires and every such line is over-counted by one - which, summed
|
|
31
|
+
// across every write in a marked region, causes clearMarked() to erase far more physical
|
|
32
|
+
// rows than were actually written (see #150).
|
|
33
|
+
const stripped = Bun.stripANSI(message);
|
|
34
|
+
return stripped.split("\n").length - (stripped.endsWith("\n") ? 1 : 0);
|
|
27
35
|
}
|
|
28
36
|
#recordMarkedWrite(message) {
|
|
29
37
|
if (this.#markedLineCount === undefined) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Quote.d.ts","sourceRoot":"","sources":["../../../../../src/service/printer/terminal/Quote.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,6BAA6B,CAAC;AAWtD,MAAM,CAAC,OAAO,OAAO,KAAK;;gBAIL,MAAM,EAAE,MAAM;IAIjC,IAAW,QAAQ,IAAI,OAAO,CAE7B;IAEM,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIzB,GAAG,IAAI,IAAI;IA6BX,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;
|
|
1
|
+
{"version":3,"file":"Quote.d.ts","sourceRoot":"","sources":["../../../../../src/service/printer/terminal/Quote.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,6BAA6B,CAAC;AAWtD,MAAM,CAAC,OAAO,OAAO,KAAK;;gBAIL,MAAM,EAAE,MAAM;IAIjC,IAAW,QAAQ,IAAI,OAAO,CAE7B;IAEM,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIzB,GAAG,IAAI,IAAI;IA6BX,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;CAqB5C"}
|
|
@@ -42,8 +42,17 @@ export default class Quote {
|
|
|
42
42
|
if (!this.isActive) {
|
|
43
43
|
return message;
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
// colorText()/italicText() etc. wrap an entire passed-in string - including a trailing
|
|
46
|
+
// "\n", when the caller's message already ends with one - with a reset sequence appended
|
|
47
|
+
// *after* that newline (e.g. "foo\n" -> "<color>foo\n<reset>"). Naively checking
|
|
48
|
+
// endsWith("\n") on such styled text is always false, and splitting on "\n" then treats the
|
|
49
|
+
// trailing reset sequence as a spurious extra line, which gets its own quote prefix -
|
|
50
|
+
// rendering as if two quote levels were active for a single actual line (see #150). Every
|
|
51
|
+
// line gets re-colored below regardless, so strip all existing ANSI upfront rather than
|
|
52
|
+
// trying to preserve and restore it.
|
|
53
|
+
const body = Bun.stripANSI(message);
|
|
54
|
+
const endsWithNewline = body.endsWith("\n");
|
|
55
|
+
const raw = endsWithNewline ? body.slice(0, -1) : body;
|
|
47
56
|
const color = this.#levels[this.#levels.length - 1].color;
|
|
48
57
|
const lines = raw
|
|
49
58
|
.split("\n")
|
package/package.json
CHANGED
|
@@ -18,7 +18,7 @@ export class PluginAddSubCommand implements SubCommand {
|
|
|
18
18
|
readonly positionals = [
|
|
19
19
|
{
|
|
20
20
|
name: "pluginId",
|
|
21
|
-
description: "Plugin ID to install (e.g. @scope/name or @scope/name
|
|
21
|
+
description: "Plugin ID to install (e.g. @scope/name or @scope/name@version)",
|
|
22
22
|
type: ValueTypeName.STRING,
|
|
23
23
|
},
|
|
24
24
|
];
|
|
@@ -28,7 +28,7 @@ export class PluginAddSubCommand implements SubCommand {
|
|
|
28
28
|
const pluginService = context.getServiceById(PLUGIN_SERVICE_ID) as PluginService;
|
|
29
29
|
|
|
30
30
|
const { pluginId, version } = parsePluginSpecifier(argumentValues["pluginId"] as string);
|
|
31
|
-
const searchLabel = version ? `${pluginId}
|
|
31
|
+
const searchLabel = version ? `${pluginId}@${version}` : pluginId;
|
|
32
32
|
await printerService.info(`Searching for plugin: ${searchLabel}\n`, Icon.INFORMATION);
|
|
33
33
|
|
|
34
34
|
let descriptor: VersionedPluginDescriptor | undefined;
|
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Splits a plugin specifier into a bare plugin ID and an optional version
|
|
2
|
+
* Splits a plugin specifier into a bare plugin ID and an optional version, following npm's own
|
|
3
|
+
* `[@scope/]name[@version]` convention (`:` is not used as a version separator anywhere in the
|
|
4
|
+
* npm/JS ecosystem, so it is not supported here).
|
|
3
5
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
6
|
+
* A leading `@scope/` portion (if present) is stripped first, then the *last* `@` in the
|
|
7
|
+
* remainder - if any - is the version separator. This correctly distinguishes the scope marker
|
|
8
|
+
* (always the first character) from a version-separating `@` occurring later.
|
|
7
9
|
*
|
|
8
|
-
* Everything after the
|
|
10
|
+
* Everything after the separator is always treated as the requested version, including
|
|
9
11
|
* non-semver tags such as `latest` or `next` - no validation is performed on the tail.
|
|
10
12
|
*
|
|
11
|
-
* @param specifier raw plugin specifier, e.g. `@scope/name`, `@scope/name
|
|
13
|
+
* @param specifier raw plugin specifier, e.g. `@scope/name`, `@scope/name@3.0.0` or
|
|
14
|
+
* `name@latest`.
|
|
12
15
|
*/
|
|
13
16
|
export function parsePluginSpecifier(specifier: string): { pluginId: string; version?: string } {
|
|
14
|
-
const
|
|
15
|
-
|
|
17
|
+
const isScoped = specifier.startsWith("@");
|
|
18
|
+
const scopeEnd = isScoped ? specifier.indexOf("/") : -1;
|
|
19
|
+
const searchFrom = isScoped && scopeEnd !== -1 ? scopeEnd + 1 : 0;
|
|
20
|
+
const atIndex = specifier.lastIndexOf("@");
|
|
21
|
+
if (atIndex === -1 || atIndex < searchFrom) {
|
|
16
22
|
return { pluginId: specifier };
|
|
17
23
|
}
|
|
18
24
|
return {
|
|
19
|
-
pluginId: specifier.slice(0,
|
|
20
|
-
version: specifier.slice(
|
|
25
|
+
pluginId: specifier.slice(0, atIndex),
|
|
26
|
+
version: specifier.slice(atIndex + 1),
|
|
21
27
|
};
|
|
22
28
|
}
|
|
@@ -33,7 +33,15 @@ export default class DefaultPrinterService implements PrinterService {
|
|
|
33
33
|
#markEndedAt: number | undefined;
|
|
34
34
|
|
|
35
35
|
#countLines(message: string): number {
|
|
36
|
-
|
|
36
|
+
// Strip ANSI escape sequences first: colorText()/italicText() etc. wrap the *entire*
|
|
37
|
+
// passed-in text (including a trailing "\n", when the caller's message already ends with
|
|
38
|
+
// one) with a reset sequence appended *after* that newline, e.g. "foo\n" -> "<color>foo\n
|
|
39
|
+
// <reset>". Checking endsWith("\n") on the styled text is then always false, so the
|
|
40
|
+
// subtraction below never fires and every such line is over-counted by one - which, summed
|
|
41
|
+
// across every write in a marked region, causes clearMarked() to erase far more physical
|
|
42
|
+
// rows than were actually written (see #150).
|
|
43
|
+
const stripped = Bun.stripANSI(message);
|
|
44
|
+
return stripped.split("\n").length - (stripped.endsWith("\n") ? 1 : 0);
|
|
37
45
|
}
|
|
38
46
|
|
|
39
47
|
#recordMarkedWrite(message: string): void {
|
|
@@ -58,8 +58,17 @@ export default class Quote {
|
|
|
58
58
|
if (!this.isActive) {
|
|
59
59
|
return message;
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
// colorText()/italicText() etc. wrap an entire passed-in string - including a trailing
|
|
62
|
+
// "\n", when the caller's message already ends with one - with a reset sequence appended
|
|
63
|
+
// *after* that newline (e.g. "foo\n" -> "<color>foo\n<reset>"). Naively checking
|
|
64
|
+
// endsWith("\n") on such styled text is always false, and splitting on "\n" then treats the
|
|
65
|
+
// trailing reset sequence as a spurious extra line, which gets its own quote prefix -
|
|
66
|
+
// rendering as if two quote levels were active for a single actual line (see #150). Every
|
|
67
|
+
// line gets re-colored below regardless, so strip all existing ANSI upfront rather than
|
|
68
|
+
// trying to preserve and restore it.
|
|
69
|
+
const body = Bun.stripANSI(message);
|
|
70
|
+
const endsWithNewline = body.endsWith("\n");
|
|
71
|
+
const raw = endsWithNewline ? body.slice(0, -1) : body;
|
|
63
72
|
const color = this.#levels[this.#levels.length - 1]!.color;
|
|
64
73
|
const lines = raw
|
|
65
74
|
.split("\n")
|