reslop 0.0.1 → 0.1.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/lib/select.js ADDED
@@ -0,0 +1,218 @@
1
+ 'use strict';
2
+
3
+ const ansi = require('./ansi.js');
4
+
5
+ const {
6
+ ESC,
7
+ RESET,
8
+ THEME,
9
+ fg: fgSeq,
10
+ bg: bgSeq,
11
+ seq,
12
+ stripAnsi,
13
+ graphemes,
14
+ graphemeWidth,
15
+ } = ansi;
16
+
17
+ const CSI_FINAL = /[@-~]/;
18
+ const SGR_RESET = 0;
19
+ const SGR_FG = 38;
20
+ const SGR_BG = 48;
21
+ const SGR_DEFAULT_FG = 39;
22
+ const SGR_DEFAULT_BG = 49;
23
+ const SGR_TRUECOLOR = 2;
24
+
25
+ const isRange = (sel) => {
26
+ if (!sel || !sel.start || !sel.end) return false;
27
+ return sel.start.x !== sel.end.x || sel.start.y !== sel.end.y;
28
+ };
29
+
30
+ const ordered = (sel) => {
31
+ const a = sel.start;
32
+ const b = sel.end;
33
+ if (a.y < b.y) return [a, b];
34
+ if (a.y > b.y) return [b, a];
35
+ if (a.x <= b.x) return [a, b];
36
+ return [b, a];
37
+ };
38
+
39
+ const slicePlain = (plain, fromCol, toCol) => {
40
+ const parts = graphemes(plain);
41
+ let col = 0;
42
+ let out = '';
43
+ for (const ch of parts) {
44
+ const w = graphemeWidth(ch, col);
45
+ const next = col + w;
46
+ if (next > fromCol && col < toCol) out += ch;
47
+ col = next;
48
+ if (col >= toCol) break;
49
+ }
50
+ return out;
51
+ };
52
+
53
+ const lineRange = (rowIndex, sel) => {
54
+ const [a, b] = ordered(sel);
55
+ const y = rowIndex + 1;
56
+ if (y < a.y || y > b.y) return null;
57
+ let from = 0;
58
+ let to = 1e9;
59
+ if (y === a.y) from = a.x - 1;
60
+ if (y === b.y) to = b.x;
61
+ if (to <= from) return null;
62
+ return { from, to };
63
+ };
64
+
65
+ const extractText = (rows, sel) => {
66
+ if (!isRange(sel) || !rows || !rows.length) return '';
67
+ const [a, b] = ordered(sel);
68
+ const lines = [];
69
+ for (let r = a.y - 1; r <= b.y - 1; r++) {
70
+ const range = lineRange(r, sel);
71
+ if (!range) continue;
72
+ const plain = stripAnsi(rows[r] ?? '');
73
+ const chunk = slicePlain(plain, range.from, range.to);
74
+ lines.push(chunk.replace(/\s+$/g, ''));
75
+ }
76
+ return lines.join('\n');
77
+ };
78
+
79
+ const consumeCsi = (line, i) => {
80
+ let j = i + 2;
81
+ while (j < line.length && !CSI_FINAL.test(line[j])) j += 1;
82
+ if (j < line.length) j += 1;
83
+ return j;
84
+ };
85
+
86
+ const parseParams = (body) => {
87
+ if (!body) return [0];
88
+ return body.split(';').map((part) => (part ? parseInt(part, 10) : 0));
89
+ };
90
+
91
+ const applyRgb = (state, field, params, i) => {
92
+ const isTruecolor = params[i + 1] === SGR_TRUECOLOR;
93
+ const hasRgb = isTruecolor && i + 4 < params.length;
94
+ if (!hasRgb) return 1;
95
+ state[field] = [params[i + 2], params[i + 3], params[i + 4]];
96
+ return 5;
97
+ };
98
+
99
+ const SGR = Object.assign(Object.create(null), {
100
+ [SGR_RESET]: (state) => {
101
+ state.fg = null;
102
+ state.bg = null;
103
+ return 1;
104
+ },
105
+ [SGR_FG]: (state, params, i) => applyRgb(state, 'fg', params, i),
106
+ [SGR_BG]: (state, params, i) => applyRgb(state, 'bg', params, i),
107
+ [SGR_DEFAULT_FG]: (state) => {
108
+ state.fg = null;
109
+ return 1;
110
+ },
111
+ [SGR_DEFAULT_BG]: (state) => {
112
+ state.bg = null;
113
+ return 1;
114
+ },
115
+ });
116
+
117
+ const applySgr = (params, state) => {
118
+ let i = 0;
119
+ while (i < params.length) {
120
+ const apply = SGR[params[i]];
121
+ i += apply ? apply(state, params, i) : 1;
122
+ }
123
+ };
124
+
125
+ const nextGrapheme = (line, index) => {
126
+ const parts = graphemes(line.slice(index));
127
+ return parts[0] ?? '';
128
+ };
129
+
130
+ const parseCells = (line) => {
131
+ const cells = [];
132
+ const state = { fg: null, bg: null };
133
+ let col = 0;
134
+ let i = 0;
135
+ while (i < line.length) {
136
+ if (line[i] === ESC && line[i + 1] === '[') {
137
+ const n = consumeCsi(line, i);
138
+ if (line[n - 1] === 'm') {
139
+ const body = line.slice(i + 2, n - 1);
140
+ applySgr(parseParams(body), state);
141
+ }
142
+ i = n;
143
+ continue;
144
+ }
145
+ if (line[i] === ESC) {
146
+ i += 1;
147
+ continue;
148
+ }
149
+ const ch = nextGrapheme(line, i);
150
+ if (!ch) break;
151
+ const width = graphemeWidth(ch, col);
152
+ const fg = state.fg;
153
+ const bg = state.bg;
154
+ cells.push({ ch, fg, bg, col, width });
155
+ col += width;
156
+ i += ch.length;
157
+ }
158
+ return cells;
159
+ };
160
+
161
+ const rgbKey = (rgb) => (rgb ? `${rgb[0]},${rgb[1]},${rgb[2]}` : '');
162
+
163
+ const styleKey = (fg, bg) => `${rgbKey(fg)}|${rgbKey(bg)}`;
164
+
165
+ const styleSeq = (fg, bg) => {
166
+ if (fg && bg) return RESET + seq(fg, bg);
167
+ if (fg) return RESET + fgSeq(fg);
168
+ if (bg) return RESET + bgSeq(bg);
169
+ return RESET;
170
+ };
171
+
172
+ const overlayRange = (line, fromCol, toCol) => {
173
+ if (toCol <= fromCol) return line;
174
+ const cells = parseCells(line);
175
+ if (!cells.length) return line;
176
+ let out = '';
177
+ let last = null;
178
+ for (const cell of cells) {
179
+ const next = cell.col + cell.width;
180
+ const hit = next > fromCol && cell.col < toCol;
181
+ let fg = cell.fg;
182
+ let bg = cell.bg;
183
+ if (hit) {
184
+ fg = cell.bg ?? THEME.ctxBg;
185
+ bg = cell.fg ?? THEME.ctxFg;
186
+ }
187
+ const key = styleKey(fg, bg);
188
+ if (key !== last) {
189
+ out += styleSeq(fg, bg);
190
+ last = key;
191
+ }
192
+ out += cell.ch;
193
+ }
194
+ return out;
195
+ };
196
+
197
+ const overlayRows = (rows, sel) => {
198
+ if (!isRange(sel)) return rows;
199
+ const next = [];
200
+ for (let i = 0; i < rows.length; i++) {
201
+ const range = lineRange(i, sel);
202
+ if (!range) {
203
+ next.push(rows[i]);
204
+ continue;
205
+ }
206
+ next.push(overlayRange(rows[i], range.from, range.to));
207
+ }
208
+ return next;
209
+ };
210
+
211
+ module.exports = {
212
+ isRange,
213
+ ordered,
214
+ extractText,
215
+ overlayRows,
216
+ overlayRange,
217
+ slicePlain,
218
+ };