@flowscripter/dynamic-cli-framework 1.4.0 → 1.4.1
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/README/development.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowscripter/dynamic-cli-framework",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "A framework for developing CLI applications which supports dynamic discovery and installation of new commands",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -39,5 +39,11 @@
|
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"typescript": "^6.0.3"
|
|
42
|
+
},
|
|
43
|
+
"typedocOptions": {
|
|
44
|
+
"readme": "none",
|
|
45
|
+
"exclude": [
|
|
46
|
+
"tests/**"
|
|
47
|
+
]
|
|
42
48
|
}
|
|
43
49
|
}
|
package/src/cli/BaseCLI.ts
CHANGED
|
@@ -233,7 +233,7 @@ export default class BaseCLI implements CLI {
|
|
|
233
233
|
let completionService: DefaultCompletionService | undefined;
|
|
234
234
|
if (this.#options.completionServiceEnabled) {
|
|
235
235
|
completionService = new DefaultCompletionService();
|
|
236
|
-
this.addServiceProvider(new CompletionServiceProvider(
|
|
236
|
+
this.addServiceProvider(new CompletionServiceProvider(5, completionService));
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
const configurationServiceProvider = new ConfigurationServiceProvider(
|
|
@@ -14,8 +14,10 @@ export interface PromptContext {
|
|
|
14
14
|
export function physicalLineCount(terminal: Terminal, text: string): number {
|
|
15
15
|
const cols = terminal.columns();
|
|
16
16
|
if (cols <= 0) return 1;
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
return text.split("\n").reduce((sum, segment) => {
|
|
18
|
+
const width = Bun.stringWidth(segment);
|
|
19
|
+
return sum + Math.max(1, Math.ceil(width / cols));
|
|
20
|
+
}, 0);
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export function headerLineCount(terminal: Terminal, promptDef: Prompt): number {
|
|
@@ -27,6 +27,25 @@ export default class TtyKeyReader implements KeyReader {
|
|
|
27
27
|
return new Promise((resolve) => {
|
|
28
28
|
const onData = (data: Buffer): void => {
|
|
29
29
|
this.#ttyReadStream.removeListener("data", onData);
|
|
30
|
+
|
|
31
|
+
// A lone 0x1b may be the start of a multi-byte escape sequence (e.g. arrow keys).
|
|
32
|
+
// Wait briefly for the rest of the sequence before decoding.
|
|
33
|
+
if (data.length === 1 && data[0] === 0x1b) {
|
|
34
|
+
const timeout = setTimeout(() => {
|
|
35
|
+
this.#ttyReadStream.removeListener("data", onMore);
|
|
36
|
+
resolve(TtyKeyReader.#decodeKeyEvent(data));
|
|
37
|
+
}, 20);
|
|
38
|
+
|
|
39
|
+
const onMore = (more: Buffer): void => {
|
|
40
|
+
clearTimeout(timeout);
|
|
41
|
+
this.#ttyReadStream.removeListener("data", onMore);
|
|
42
|
+
resolve(TtyKeyReader.#decodeKeyEvent(Buffer.concat([data, more])));
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
this.#ttyReadStream.once("data", onMore);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
30
49
|
resolve(TtyKeyReader.#decodeKeyEvent(data));
|
|
31
50
|
};
|
|
32
51
|
this.#ttyReadStream.on("data", onData);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { physicalLineCount } from "../../../../src/service/prompter/prompt/PromptContext.ts";
|
|
3
|
+
import type Terminal from "../../../../src/terminal/Terminal.ts";
|
|
4
|
+
|
|
5
|
+
class MockTerminal implements Terminal {
|
|
6
|
+
readonly #cols: number;
|
|
7
|
+
constructor(cols: number) {
|
|
8
|
+
this.#cols = cols;
|
|
9
|
+
}
|
|
10
|
+
columns(): number {
|
|
11
|
+
return this.#cols;
|
|
12
|
+
}
|
|
13
|
+
rows(): number {
|
|
14
|
+
return 24;
|
|
15
|
+
}
|
|
16
|
+
clearLine(): Promise<void> {
|
|
17
|
+
return Promise.resolve();
|
|
18
|
+
}
|
|
19
|
+
clearUpLines(): Promise<void> {
|
|
20
|
+
return Promise.resolve();
|
|
21
|
+
}
|
|
22
|
+
hideCursor(): Promise<void> {
|
|
23
|
+
return Promise.resolve();
|
|
24
|
+
}
|
|
25
|
+
showCursor(): Promise<void> {
|
|
26
|
+
return Promise.resolve();
|
|
27
|
+
}
|
|
28
|
+
write(): Promise<void> {
|
|
29
|
+
return Promise.resolve();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("physicalLineCount", () => {
|
|
34
|
+
test("single short text fits on one line", () => {
|
|
35
|
+
expect(physicalLineCount(new MockTerminal(80), "hello")).toBe(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("text wider than terminal wraps", () => {
|
|
39
|
+
// 11 chars on a 10-col terminal => 2 lines
|
|
40
|
+
expect(physicalLineCount(new MockTerminal(10), "hello world")).toBe(2);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("embedded newline on wide terminal counts as two lines", () => {
|
|
44
|
+
// Both parts fit on one line (terminal 300 cols > 127 and > 81),
|
|
45
|
+
// but the embedded \n always forces 2 rendered lines
|
|
46
|
+
const text =
|
|
47
|
+
"This will set up your terminal so that pressing TAB while typing commands will show possible options and autocomplete arguments.\n(Enabling autocompletion will modify configuration files in your home directory.)";
|
|
48
|
+
expect(physicalLineCount(new MockTerminal(300), text)).toBe(2);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("embedded newline combined with wrapping", () => {
|
|
52
|
+
// "hello world" (11 chars) => ceil(11/10) = 2 lines
|
|
53
|
+
// "foo" (3 chars) => ceil(3/10) = 1 line
|
|
54
|
+
// total = 3
|
|
55
|
+
expect(physicalLineCount(new MockTerminal(10), "hello world\nfoo")).toBe(3);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("zero columns returns 1", () => {
|
|
59
|
+
expect(physicalLineCount(new MockTerminal(0), "hello")).toBe(1);
|
|
60
|
+
});
|
|
61
|
+
});
|