File size: 3,725 Bytes
9ada4bc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import * as readline from 'node:readline';
import { CancelablePromise } from '@inquirer/type';
import MuteStream from 'mute-stream';
import { onExit as onSignalExit } from 'signal-exit';
import ScreenManager from './screen-manager.mjs';
import { withHooks, effectScheduler } from './hook-engine.mjs';
import { CancelPromptError, ExitPromptError } from './errors.mjs';
export function createPrompt(view) {
    const prompt = (config, context) => {
        // Default `input` to stdin
        const input = context?.input ?? process.stdin;
        // Add mute capabilities to the output
        const output = new MuteStream();
        output.pipe(context?.output ?? process.stdout);
        const rl = readline.createInterface({
            terminal: true,
            input,
            output,
        });
        const screen = new ScreenManager(rl);
        let cancel = () => { };
        const answer = new CancelablePromise((resolve, reject) => {
            withHooks(rl, (store) => {
                function checkCursorPos() {
                    screen.checkCursorPos();
                }
                const removeExitListener = onSignalExit((code, signal) => {
                    onExit();
                    reject(new ExitPromptError(`User force closed the prompt with ${code} ${signal}`));
                });
                function onExit() {
                    try {
                        store.hooksCleanup.forEach((cleanFn) => {
                            cleanFn?.();
                        });
                    }
                    catch (error) {
                        reject(error);
                    }
                    if (context?.clearPromptOnDone) {
                        screen.clean();
                    }
                    else {
                        screen.clearContent();
                    }
                    screen.done();
                    removeExitListener();
                    store.rl.input.removeListener('keypress', checkCursorPos);
                }
                cancel = () => {
                    onExit();
                    reject(new CancelPromptError());
                };
                function done(value) {
                    // Delay execution to let time to the hookCleanup functions to registers.
                    setImmediate(() => {
                        onExit();
                        // Finally we resolve our promise
                        resolve(value);
                    });
                }
                function workLoop() {
                    store.index = 0;
                    try {
                        const nextView = view(config, done);
                        const [content, bottomContent] = typeof nextView === 'string' ? [nextView] : nextView;
                        screen.render(content, bottomContent);
                        effectScheduler.run();
                    }
                    catch (error) {
                        onExit();
                        reject(error);
                    }
                }
                store.handleChange = () => workLoop();
                workLoop();
                // Re-renders only happen when the state change; but the readline cursor could change position
                // and that also requires a re-render (and a manual one because we mute the streams).
                // We set the listener after the initial workLoop to avoid a double render if render triggered
                // by a state change sets the cursor to the right position.
                store.rl.input.on('keypress', checkCursorPos);
            });
        });
        answer.cancel = cancel;
        return answer;
    };
    return prompt;
}