-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Expand file tree
/
Copy pathcursor-helpers.ts
More file actions
109 lines (97 loc) · 2.97 KB
/
Copy pathcursor-helpers.ts
File metadata and controls
109 lines (97 loc) · 2.97 KB
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import ansiEscapes from 'ansi-escapes';
export type CursorPosition = {
x: number;
y: number;
};
const showCursorEscape = '\u001B[?25h';
const hideCursorEscape = '\u001B[?25l';
export {showCursorEscape, hideCursorEscape};
/**
Compare two cursor positions. Returns true if they differ.
*/
export const cursorPositionChanged = (
a: CursorPosition | undefined,
b: CursorPosition | undefined,
): boolean => a?.x !== b?.x || a?.y !== b?.y;
/**
Build escape sequence to move cursor from bottom of output to the target position and show it.
Assumes cursor is at (col 0, line visibleLineCount) — i.e. just after the last output line.
*/
export const buildCursorSuffix = (
visibleLineCount: number,
cursorPosition: CursorPosition | undefined,
viewportHeight = Infinity,
): string => {
if (!cursorPosition) {
return '';
}
const moveUp = Math.min(
visibleLineCount - cursorPosition.y,
viewportHeight,
);
return (
(moveUp > 0 ? ansiEscapes.cursorUp(moveUp) : '') +
ansiEscapes.cursorTo(cursorPosition.x) +
showCursorEscape
);
};
/**
Build escape sequence to move cursor from previousCursorPosition back to the bottom of output.
This must be done before eraseLines or any operation that assumes cursor is at the bottom.
*/
export const buildReturnToBottom = (
previousLineCount: number,
previousCursorPosition: CursorPosition | undefined,
): string => {
if (!previousCursorPosition) {
return '';
}
// PreviousLineCount includes trailing newline, so visible lines = previousLineCount - 1
// cursor is at previousCursorPosition.y, need to go to line (previousLineCount - 1)
const down = previousLineCount - 1 - previousCursorPosition.y;
return (
(down > 0 ? ansiEscapes.cursorDown(down) : '') + ansiEscapes.cursorTo(0)
);
};
export type CursorOnlyInput = {
cursorWasShown: boolean;
previousLineCount: number;
previousCursorPosition: CursorPosition | undefined;
visibleLineCount: number;
cursorPosition: CursorPosition | undefined;
viewportHeight?: number;
};
/**
Build the escape sequence for cursor-only updates (output unchanged, cursor moved).
Hides cursor if it was previously shown, returns to bottom, then repositions.
*/
export const buildCursorOnlySequence = (input: CursorOnlyInput): string => {
const hidePrefix = input.cursorWasShown ? hideCursorEscape : '';
const returnToBottom = buildReturnToBottom(
input.previousLineCount,
input.previousCursorPosition,
);
const cursorSuffix = buildCursorSuffix(
input.visibleLineCount,
input.cursorPosition,
input.viewportHeight,
);
return hidePrefix + returnToBottom + cursorSuffix;
};
/**
Build the prefix that hides cursor and returns to bottom before erasing or rewriting.
Returns empty string if cursor was not shown.
*/
export const buildReturnToBottomPrefix = (
cursorWasShown: boolean,
previousLineCount: number,
previousCursorPosition: CursorPosition | undefined,
): string => {
if (!cursorWasShown) {
return '';
}
return (
hideCursorEscape +
buildReturnToBottom(previousLineCount, previousCursorPosition)
);
};