deel-local-cli 1.5.0 → 1.5.3
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.en.md +1001 -997
- package/README.md +992 -988
- package/package.json +60 -60
- package/src/commands.js +99 -0
- package/src/i18n/en.js +16 -1
- package/src/i18n/index.js +52 -3
- package/src/i18n/ko.js +18 -3
- package/src/repl.js +59 -19
- package/src/ui/inputbox.js +633 -557
- package/src/ui/motion.js +20 -1
- package/src/ui/office.js +124 -14
- package/src/ui/screen.js +17 -0
- package/src/ui/status.js +13 -3
- package/src/ui/wrap.js +36 -2
package/src/ui/motion.js
CHANGED
|
@@ -469,8 +469,24 @@ const 이름표 = {
|
|
|
469
469
|
기사: '기사', knight: '기사', 동물: '동물', animal: '동물', animals: '동물',
|
|
470
470
|
기본: '기본', default: '기본', plain: '기본',
|
|
471
471
|
};
|
|
472
|
+
/*
|
|
473
|
+
* 설정 파일에서 고른 그림. `/motion` 이 여기에 넣는다.
|
|
474
|
+
*
|
|
475
|
+
* **파일을 여기서 읽으면 안 된다.** 이 함수는 90ms 마다 불린다 — 판마다
|
|
476
|
+
* config.json 을 여는 셈이 된다. 그래서 켤 때 한 번 읽어서 이 값에 넣어 두고,
|
|
477
|
+
* 명령으로 바꿀 때 같이 갈아 끼운다.
|
|
478
|
+
*
|
|
479
|
+
* 환경변수가 이긴다. 한 번 띄우는 동안만 다르게 보고 싶을 때
|
|
480
|
+
* `DEEL_MOTION=기사 deel` 로 덮어쓸 수 있어야 하고, 검사도 그 길로 잰다.
|
|
481
|
+
*/
|
|
482
|
+
let 고른그림 = null;
|
|
483
|
+
export function 그림고르기설정(값) {
|
|
484
|
+
고른그림 = 이름표[String(값 ?? '').trim().toLowerCase()] ?? 이름표[String(값 ?? '').trim()] ?? null;
|
|
485
|
+
}
|
|
486
|
+
|
|
472
487
|
export const 테마이름 = () => 이름표[String(process.env.DEEL_MOTION ?? '').trim().toLowerCase()]
|
|
473
488
|
?? 이름표[String(process.env.DEEL_MOTION ?? '').trim()]
|
|
489
|
+
?? 고른그림
|
|
474
490
|
?? '기본';
|
|
475
491
|
export const 테마 = () => 테마들[테마이름()] ?? 테마들.기본;
|
|
476
492
|
|
|
@@ -481,7 +497,10 @@ export const 테마 = () => 테마들[테마이름()] ?? 테마들.기본;
|
|
|
481
497
|
* 자리가 있다. 그럴 때 DEEL_NO_MOTION=1 이면 예전처럼 한 칸짜리 돌림표만 돈다.
|
|
482
498
|
* 아예 못 끄게 해 두면 그런 사람은 도구를 못 쓴다.
|
|
483
499
|
*/
|
|
484
|
-
|
|
500
|
+
let 설정으로껐나 = false;
|
|
501
|
+
/** `/motion 끔` 이 여기에 넣는다. 환경변수는 따로 살아 있고, 둘 중 하나면 꺼진다. */
|
|
502
|
+
export function 끔설정(값) { 설정으로껐나 = !!값; }
|
|
503
|
+
export const 끔 = () => !!process.env.DEEL_NO_MOTION || 설정으로껐나;
|
|
485
504
|
|
|
486
505
|
// 못 끄는 자리에서 쓸 한 칸짜리. working.js 의 돌림틀과 같은 것이다.
|
|
487
506
|
const 한칸 = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
package/src/ui/office.js
CHANGED
|
@@ -28,7 +28,40 @@
|
|
|
28
28
|
* 그래서 상자가 바뀐 줄만 보낸다(inputbox 의 그리기). 그 덕에 사무실을 켜도
|
|
29
29
|
* 터미널로 나가는 양이 켜기 전과 비슷하다.
|
|
30
30
|
*/
|
|
31
|
-
import {
|
|
31
|
+
import { 느긋해질때 } from './working.js';
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* 방을 그릴 만큼 색이 되는 화면인가.
|
|
35
|
+
*
|
|
36
|
+
* ansi.js 의 색256가능() 은 여기서 못 쓴다. 그것이 보는 것은 TERM 이 dumb
|
|
37
|
+
* 인지 하나뿐인데, 상자 자체가 이미 dumb 를 거부한다(screen.js 상자쓸까).
|
|
38
|
+
* 즉 상자 안에서는 **늘 참**이라 있으나 마나 한 관문이었고, 16색짜리 콘솔에서도
|
|
39
|
+
* 사무실이 켜졌다.
|
|
40
|
+
*
|
|
41
|
+
* 그래서 여기서는 반대로 **확실할 때만** 참이라고 한다. 틀렸을 때 잃는 것이
|
|
42
|
+
* 양쪽으로 다르기 때문이다 — 못 켜면 그냥 사무실이 안 보이지만, 잘못 켜면
|
|
43
|
+
* 색 번호가 글자로 쏟아지거나 열두 줄이 갈색 죽이 된다. 모르면 안 켠다.
|
|
44
|
+
*/
|
|
45
|
+
function 색충분한가() {
|
|
46
|
+
// 색을 끈 사람에게 방을 그리면 안 된다. 방은 색이 전부다.
|
|
47
|
+
if (process.env.NO_COLOR !== undefined) return false;
|
|
48
|
+
if (process.env.FORCE_COLOR === '1') return true;
|
|
49
|
+
|
|
50
|
+
const term = String(process.env.TERM ?? '');
|
|
51
|
+
if (term === 'dumb') return false;
|
|
52
|
+
|
|
53
|
+
const 컬러텀 = String(process.env.COLORTERM ?? '').toLowerCase();
|
|
54
|
+
if (컬러텀 === 'truecolor' || 컬러텀 === '24bit') return true;
|
|
55
|
+
if (/256/.test(term)) return true;
|
|
56
|
+
|
|
57
|
+
// 윈도우는 Node 20 이 Win10 이상을 요구하고, 거기 콘솔은 256색을 한다.
|
|
58
|
+
if (process.platform === 'win32') return true;
|
|
59
|
+
if (process.env.WT_SESSION) return true; // 윈도우 터미널
|
|
60
|
+
if (process.env.TERM_PROGRAM) return true; // iTerm · VS Code · Apple Terminal
|
|
61
|
+
if (process.env.ConEmuANSI === 'ON') return true;
|
|
62
|
+
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
32
65
|
|
|
33
66
|
const ESC = '\x1b';
|
|
34
67
|
const 투명 = -1;
|
|
@@ -45,7 +78,7 @@ const C = {
|
|
|
45
78
|
화면: 45, 화면꺼짐: 239, 화면글: 123, 살: 223,
|
|
46
79
|
머리: [52, 240, 94, 130], 옷: [67, 131, 71, 97],
|
|
47
80
|
밤: 17, 별: 226, 낮: 111, 구름: 255,
|
|
48
|
-
종이: 255, 종이어둠: 250, 쪽지: 227, 쪽지끝: 120, 초록: 46,
|
|
81
|
+
종이: 255, 종이어둠: 250, 쪽지: 227, 쪽지끝: 120, 초록: 46, 넘침: 208,
|
|
49
82
|
};
|
|
50
83
|
|
|
51
84
|
// ── 픽셀 판 ─────────────────────────────────────────────────────────────
|
|
@@ -170,13 +203,22 @@ export function 사무실줄들(상태, 폭) {
|
|
|
170
203
|
const 그림 = ['FFFFFFFFFFFFFFFFFF'];
|
|
171
204
|
for (let r = 0; r < 3; r++) 그림.push(`F${'W'.repeat(16)}F`);
|
|
172
205
|
그림.push('FFFFFFFFFFFFFFFFFF');
|
|
206
|
+
/*
|
|
207
|
+
* 쪽지는 다섯 장뿐이라 열 개짜리 목록은 눌러 담아야 한다. 그런데 끝난
|
|
208
|
+
* 것부터 채우면 「열 개 다 끝남」과 「열 개 끝나고 하나 남음」이 똑같이
|
|
209
|
+
* 초록 다섯 장으로 나온다 — 남은 일이 화면에서 사라진다.
|
|
210
|
+
*
|
|
211
|
+
* 그래서 남은 일이 하나라도 있으면 **노란 쪽지 한 장은 반드시 남긴다.**
|
|
212
|
+
* 눌러 담더라도 '아직 안 끝났다' 는 사실만은 안 지운다.
|
|
213
|
+
*/
|
|
173
214
|
const 총 = Math.min(5, 할일 + 끝난일);
|
|
215
|
+
const 초록수 = 할일 > 0 ? Math.min(끝난일, 총 - 1) : Math.min(끝난일, 총);
|
|
174
216
|
for (let n = 0; n < 총; n++) {
|
|
175
217
|
const x = 2 + n * 3;
|
|
176
218
|
for (const r of [1, 2, 3]) {
|
|
177
219
|
const 줄 = 그림[r].split('');
|
|
178
|
-
줄[x] = n <
|
|
179
|
-
줄[x + 1] = n <
|
|
220
|
+
줄[x] = n < 초록수 ? 'D' : 'N';
|
|
221
|
+
줄[x + 1] = n < 초록수 ? 'D' : 'N';
|
|
180
222
|
그림[r] = 줄.join('');
|
|
181
223
|
}
|
|
182
224
|
}
|
|
@@ -185,14 +227,23 @@ export function 사무실줄들(상태, 폭) {
|
|
|
185
227
|
|
|
186
228
|
// 서버 불빛 14×5 — 모델 호출. 오른쪽 벽 끝.
|
|
187
229
|
{
|
|
230
|
+
/*
|
|
231
|
+
* 불은 열두 개뿐이다. 여섯 번 부른 것과 백 번 부른 것이 똑같이 다 켜진
|
|
232
|
+
* 채로 나오면, 이 불은 어느 순간부터 아무것도 안 말하게 된다.
|
|
233
|
+
*
|
|
234
|
+
* 다 차면 맨 끝 불을 다른 색으로 바꾼다 — '여기서부터는 더 있다' 는 뜻이다.
|
|
235
|
+
* 없는 눈금을 지어내지 않으면서 넘쳤다는 것만 알린다.
|
|
236
|
+
*/
|
|
237
|
+
const 켠수 = Math.min(12, 부른횟수 * 2);
|
|
238
|
+
const 넘쳤나 = 부른횟수 * 2 > 12;
|
|
188
239
|
const 그림 = ['RRRRRRRRRRRRRR'];
|
|
189
240
|
for (let r = 0; r < 3; r++) {
|
|
190
241
|
let 줄 = 'R';
|
|
191
|
-
for (let k = 0; k < 12; k++) 줄 += k
|
|
242
|
+
for (let k = 0; k < 12; k++) 줄 += (넘쳤나 && k === 11) ? 'O' : (k < 켠수 ? 'L' : 'l');
|
|
192
243
|
그림.push(줄 + 'R');
|
|
193
244
|
}
|
|
194
245
|
그림.push('RRRRRRRRRRRRRR');
|
|
195
|
-
p.올리기(가로 - 15, 0, 그림, { R: C.검정, L: C.초록, l: C
|
|
246
|
+
p.올리기(가로 - 15, 0, 그림, { R: C.검정, L: C.초록, l: C.쇠진, O: C.넘침 });
|
|
196
247
|
}
|
|
197
248
|
|
|
198
249
|
// 창문 — 벽에 남는 자리만큼. 컨텍스트가 차면 밤이 된다.
|
|
@@ -236,6 +287,15 @@ export function 사무실줄들(상태, 폭) {
|
|
|
236
287
|
*/
|
|
237
288
|
const 왼쪽 = 15;
|
|
238
289
|
const 자리수 = Math.max(1, Math.floor((가로 - 왼쪽 - 2) / (자리폭 + 1)));
|
|
290
|
+
/*
|
|
291
|
+
* 자리보다 사람이 많으면 그냥 잘려 나갔다 — 좁은 화면에서 둘이 도는 것과
|
|
292
|
+
* 셋이 도는 것이 **똑같은 그림**이 됐다. 이 화면의 규칙이 '전부 진짜 숫자'
|
|
293
|
+
* 인데 그러면 자리 수만 거짓이 된다.
|
|
294
|
+
*
|
|
295
|
+
* 못 그리는 것을 억지로 그릴 수는 없으니, 대신 마지막 자리의 책상 불을
|
|
296
|
+
* 넘침 색으로 바꿔 '여기 뒤로 더 있다' 를 남긴다.
|
|
297
|
+
*/
|
|
298
|
+
const 넘친자리 = 일꾼들.length > 자리수;
|
|
239
299
|
const 짝 = Math.floor(틱 / 5) % 2 === 0;
|
|
240
300
|
for (let i = 0; i < 자리수; i++) {
|
|
241
301
|
const x = 왼쪽 + i * (자리폭 + 1);
|
|
@@ -246,6 +306,8 @@ export function 사무실줄들(상태, 폭) {
|
|
|
246
306
|
: 그사람.갈래 === '읽기' ? (짝 ? '읽기A' : '읽기B')
|
|
247
307
|
: 그사람.갈래 === '졸기' ? '졸기' : '대기';
|
|
248
308
|
p.올리기(x, y, 자세들[자세], 자리색(자세, C.머리[i % 4], C.옷[i % 4]));
|
|
309
|
+
// 마지막 자리인데 뒤에 더 있으면, 책상 모서리에 넘침 표시를 남긴다.
|
|
310
|
+
if (넘친자리 && i === 자리수 - 1) p.칠하기(x + 자리폭 - 2, y + 자리높이 - 3, 2, 2, C.넘침);
|
|
249
311
|
// 조는 사람 머리 위 z
|
|
250
312
|
if (자세 === '졸기' && 짝) p.칠하기(x + 13, y + 2, 2, 1, C.종이);
|
|
251
313
|
// 고친 파일은 책상 위 종이로
|
|
@@ -265,9 +327,22 @@ export function 사무실줄들(상태, 폭) {
|
|
|
265
327
|
* · 그림을 끈 사람 (DEEL_NO_MOTION) — 화면 읽기 프로그램을 쓰는 자리다
|
|
266
328
|
* · 화면이 낮은 터미널 — 사무실 12줄 + 상자 5줄이면 대화가 들어갈 자리가
|
|
267
329
|
* 없다. 사무실을 보려고 도구를 못 쓰게 되는 것은 앞뒤가 바뀐 것이다.
|
|
330
|
+
* · 화면이 **좁은** 터미널 — 아래 최소폭 참고. 이건 안 지키면 화면이 깨진다.
|
|
268
331
|
*/
|
|
269
332
|
export const 최소높이 = 28;
|
|
270
333
|
|
|
334
|
+
/*
|
|
335
|
+
* 방은 늘 60칸을 쓴다(가로 = Math.max(60, 폭)). 더 좁게 그리면 책상과 사람이
|
|
336
|
+
* 뭉개져서 방으로 안 보인다 — 줄여야 할 것은 사람이 아니라 여백인데, 60칸이
|
|
337
|
+
* 여백을 다 뺀 자리다.
|
|
338
|
+
*
|
|
339
|
+
* 그래서 60칸보다 좁은 터미널에서는 **안 켠다**. 켜면 열두 줄이 스물네 줄로
|
|
340
|
+
* 접히는데 상자는 열두 줄로 세고 그만큼만 커서를 올린다. 어긋난 자리에 다음
|
|
341
|
+
* 판을 덮어써서 대화가 통째로 지워진다 — 50칸에서 200판을 돌리면 테두리도
|
|
342
|
+
* 대화도 남지 않는다. 좁으면 그냥 안 보여 주는 편이 낫다.
|
|
343
|
+
*/
|
|
344
|
+
export const 최소폭 = 60;
|
|
345
|
+
|
|
271
346
|
/**
|
|
272
347
|
* 사람이 켜 달라고 했나 (화면이 되는지는 안 본다).
|
|
273
348
|
*
|
|
@@ -275,15 +350,29 @@ export const 최소높이 = 28;
|
|
|
275
350
|
* 정해야지 화면 높이로 정하면 안 된다. 창을 줄였다 늘였다 할 때마다 상자 안
|
|
276
351
|
* 그림이 기사였다 돌림표였다 하면 무슨 규칙인지 아무도 모른다.
|
|
277
352
|
*/
|
|
353
|
+
/*
|
|
354
|
+
* 설정 파일에서 켜 뒀나. `/motion 사무실` 이 여기에 넣는다.
|
|
355
|
+
*
|
|
356
|
+
* motion.js 와 같은 이유로 파일을 여기서 안 읽는다 — 판마다 불리는 자리다.
|
|
357
|
+
* 환경변수가 이긴다(한 번만 다르게 보고 싶을 때, 그리고 검사가 쓰는 길).
|
|
358
|
+
*/
|
|
359
|
+
let 설정으로켰나 = false;
|
|
360
|
+
export function 사무실설정(켬) { 설정으로켰나 = !!켬; }
|
|
361
|
+
|
|
278
362
|
export function 켜달라했나() {
|
|
279
|
-
const
|
|
280
|
-
|
|
363
|
+
const raw = process.env.DEEL_OFFICE;
|
|
364
|
+
if (raw !== undefined) {
|
|
365
|
+
const v = String(raw).trim().toLowerCase();
|
|
366
|
+
return v === '1' || v === 'on' || v === '켬' || v === '사무실';
|
|
367
|
+
}
|
|
368
|
+
return 설정으로켰나;
|
|
281
369
|
}
|
|
282
370
|
|
|
283
|
-
export function 켜나(rows = process.stdout.rows) {
|
|
371
|
+
export function 켜나(rows = process.stdout.rows, cols = process.stdout.columns) {
|
|
284
372
|
if (!켜달라했나()) return false;
|
|
285
373
|
if (process.env.DEEL_NO_MOTION) return false;
|
|
286
|
-
if (
|
|
374
|
+
if (!색충분한가()) return false;
|
|
375
|
+
if ((cols ?? 0) < 최소폭) return false;
|
|
287
376
|
return (rows ?? 0) >= 최소높이;
|
|
288
377
|
}
|
|
289
378
|
|
|
@@ -311,13 +400,34 @@ export function 상태만들기(session, 일감, 틱 = 0, 할일들 = [], 도는
|
|
|
311
400
|
*/
|
|
312
401
|
const 일꾼들 = [];
|
|
313
402
|
if (일감) {
|
|
314
|
-
|
|
403
|
+
/*
|
|
404
|
+
* 오래 끌면 존다.
|
|
405
|
+
*
|
|
406
|
+
* 여기서 직접 재야 한다. 부르는 쪽(inputbox)이 넘겨 주는 일감.갈래 는
|
|
407
|
+
* '생각'·'답'·'하위'·'접기'·도구이름 중 하나일 뿐, **'느긋' 이 되는
|
|
408
|
+
* 길이 없다.** 상자 안 돌림표는 자기 자리에서 따로 재고 있었으므로,
|
|
409
|
+
* 문서에 적어 둔 '45초 넘으면 존다' 가 방에서는 한 번도 일어나지
|
|
410
|
+
* 않았다 — 졸기 자세 코드가 통째로 죽어 있었다.
|
|
411
|
+
*/
|
|
412
|
+
const 지난 = Number.isFinite(일감.시작) ? Date.now() - 일감.시작 : 0;
|
|
413
|
+
const 오래됐나 = 일감.갈래 === '느긋' || 지난 >= 느긋해질때;
|
|
315
414
|
const 갈래 = 오래됐나 ? '졸기'
|
|
316
415
|
: ['읽기', '웹'].includes(일감.갈래) ? '읽기'
|
|
317
416
|
: ['쓰기', '명령', '답'].includes(일감.갈래) ? '쓰기' : '대기';
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
417
|
+
/*
|
|
418
|
+
* 하위 작업이 돌면 그만큼 자리가 더 찬다. 부모 몫 하나는 늘 있다.
|
|
419
|
+
*
|
|
420
|
+
* 자세는 **부모 것만** 진짜다. 하위가 지금 읽는 중인지 쓰는 중인지는
|
|
421
|
+
* 여기까지 안 올라온다 — 세는 것은 개수뿐이다. 그런데 예전에는 그 자리에도
|
|
422
|
+
* 부모의 자세를 그대로 복사해 놨다. 부모가 자판을 두드리면 읽고 있던
|
|
423
|
+
* 하위까지 자판을 두드렸다. 자리 수는 진짜인데 자세는 지어낸 것이라,
|
|
424
|
+
* 이 화면의 규칙(전부 진짜 숫자)에 어긋나는 유일한 자리였다.
|
|
425
|
+
*
|
|
426
|
+
* 그래서 하위 자리는 '대기' 로 앉힌다. 모르는 것을 그럴듯하게 채우느니
|
|
427
|
+
* 모른다고 두는 편이 낫다 — 자리가 찼다는 사실은 그대로 보인다.
|
|
428
|
+
*/
|
|
429
|
+
일꾼들.push({ 갈래 });
|
|
430
|
+
for (let i = 0; i < Math.max(0, 도는하위); i++) 일꾼들.push({ 갈래: '대기' });
|
|
321
431
|
}
|
|
322
432
|
|
|
323
433
|
return {
|
package/src/ui/screen.js
CHANGED
|
@@ -243,6 +243,23 @@ export class BoxScreen extends LineScreen {
|
|
|
243
243
|
// 그 다음 지우기가 답까지 같이 지운다.
|
|
244
244
|
this.상자.줄중간표시();
|
|
245
245
|
super.붙임(s);
|
|
246
|
+
|
|
247
|
+
/*
|
|
248
|
+
* 줄이 끝나는 토막이면 그 자리에서 다시 세운다.
|
|
249
|
+
*
|
|
250
|
+
* 예전에는 답이 흐르는 내내 상자를 안 세웠다. 상자만 있을 때는 그래도
|
|
251
|
+
* 됐다 — 없어도 답이 흐르는 것 자체가 살아 있다는 신호였으니까.
|
|
252
|
+
* 그런데 **사무실을 켜면 얘기가 다르다.** 붙박아 두라고 켠 것이 정작
|
|
253
|
+
* 일할 때만 사라지니, 켠 사람 눈에는 고장으로 보인다.
|
|
254
|
+
*
|
|
255
|
+
* 그렇다고 토막마다 세우면 글자 몇 개마다 깜빡인다. 그래서 **줄이
|
|
256
|
+
* 끝날 때만** 세운다 — 줄() 이 이미 하고 있는 것과 같은 빈도이고,
|
|
257
|
+
* 그 자리에서는 답을 덮을 일이 없다.
|
|
258
|
+
*/
|
|
259
|
+
if (typeof s === 'string' && s.endsWith('\n')) {
|
|
260
|
+
this.상자.줄끝남();
|
|
261
|
+
this.상자.일그리기();
|
|
262
|
+
}
|
|
246
263
|
}
|
|
247
264
|
|
|
248
265
|
/** 일하기 시작·바뀜·끝. 상자 안엣것만 바뀐다 (ui/working.js 의 문구). */
|
package/src/ui/status.js
CHANGED
|
@@ -408,7 +408,15 @@ function 급머리말(session) {
|
|
|
408
408
|
}));
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
-
|
|
411
|
+
/**
|
|
412
|
+
* 켤 때 한 번 보여 주는 머리말.
|
|
413
|
+
*
|
|
414
|
+
* @param {boolean} 상자쓰나 입력 상자를 쓰는 화면인가. Shift+Tab · Alt+Enter ·
|
|
415
|
+
* 줄 끝 백틱은 전부 상자 안에서만 도는 것이라(repl.js 의 `if (상자쓰나)`),
|
|
416
|
+
* 줄 화면(`--no-tui`·파이프·CI)에서 이 줄들을 그대로 찍으면 **되지도 않는
|
|
417
|
+
* 조작법을 알려 주는 셈**이 된다. 안 되는 것을 알려 주는 안내는 없느니만 못하다.
|
|
418
|
+
*/
|
|
419
|
+
export function headerLines(session, found, 상자쓰나 = true) {
|
|
412
420
|
const conn = session.conn;
|
|
413
421
|
const 능력 = [
|
|
414
422
|
conn.streaming ? c.green(말('head.streaming')) : c.gray(말('head.noStreaming')),
|
|
@@ -463,9 +471,11 @@ export function headerLines(session, found) {
|
|
|
463
471
|
// 켤 때 한 번은 사람 말로 알려 준다. 상태줄의 `⏵⏵ 자동 승인` 이 무슨
|
|
464
472
|
// 뜻인지 여기서 한 번 읽고 나면 그 다음부터는 글자만 봐도 안다.
|
|
465
473
|
`${c.gray(자리(말('head.approve')))}${승인표시(session.mode)} ${c.gray('— ' + 승인고르기(session.mode).한줄)}`,
|
|
466
|
-
`${빈자리}${c.gray(말('head.shiftTab'))}${c.gray(말('head.shiftTabHint'))}${c.gray(말('head.tabHint'))}`,
|
|
467
|
-
`${빈자리}${c.gray(말('head.newlineHint'))}`,
|
|
468
474
|
];
|
|
475
|
+
if (상자쓰나) {
|
|
476
|
+
lines.push(`${빈자리}${c.gray(말('head.shiftTab'))}${c.gray(말('head.shiftTabHint'))}${c.gray(말('head.tabHint'))}`);
|
|
477
|
+
lines.push(`${빈자리}${c.gray(말('head.newlineHint'))}`);
|
|
478
|
+
}
|
|
469
479
|
if (found.skills.length || found.commands.length) {
|
|
470
480
|
lines.push(`${c.gray(자리(말('head.thisPC')))}${c.white(말('head.skills', { n: found.skills.length }))}${c.gray(' · ')}${c.white(말('head.commands', { n: found.commands.length }))}${c.gray(' · ')}${c.white(말('head.plugins', { n: found.plugins.length }))}`);
|
|
471
481
|
}
|
package/src/ui/wrap.js
CHANGED
|
@@ -4,6 +4,39 @@
|
|
|
4
4
|
// 여기로 옮긴다 — 입력 상자가 긴 입력을 접을 때 같은 것이 필요하다.
|
|
5
5
|
import { width } from './ansi.js';
|
|
6
6
|
|
|
7
|
+
/*
|
|
8
|
+
* i 자리에서 **눈에 보이는 한 덩이**를 통째로 떼어 온다.
|
|
9
|
+
*
|
|
10
|
+
* 글자를 s[i] 로 한 칸씩 읽으면 UTF-16 코드 단위로 잘린다. 😀 같은 글자는
|
|
11
|
+
* 두 칸을 차지하므로 하필 그 사이에서 줄이 접히면 반쪽짜리 서로게이트가
|
|
12
|
+
* 양쪽 줄에 하나씩 남는다 — 접어쓰기("a😀", 2) 가 ["a\ud83d","\ude00"] 를
|
|
13
|
+
* 내놨다. 화면에는 물음표 두 개로 찍히고, 폭 계산도 그때부터 어긋난다.
|
|
14
|
+
*
|
|
15
|
+
* 그래서 코드 포인트로 읽고, 뒤에 붙는 것들(살색 조절·변형 선택자·결합
|
|
16
|
+
* 부호·ZWJ 로 이어붙인 다음 글자)까지 한 덩이로 본다. 완전한 문자소 분할은
|
|
17
|
+
* 아니지만 — 그건 표가 있어야 한다 — 실제로 깨지던 자리는 이걸로 다 막힌다.
|
|
18
|
+
*/
|
|
19
|
+
function 한덩이(s, i) {
|
|
20
|
+
let 끝 = i + String.fromCodePoint(s.codePointAt(i)).length;
|
|
21
|
+
for (;;) {
|
|
22
|
+
if (끝 >= s.length) return s.slice(i, 끝);
|
|
23
|
+
const cp = s.codePointAt(끝);
|
|
24
|
+
// ZWJ 는 다음 글자까지 끌고 온다 (👨👩👧 같은 이어붙인 그림글자)
|
|
25
|
+
if (cp === 0x200d) {
|
|
26
|
+
const 다음 = 끝 + 1;
|
|
27
|
+
if (다음 >= s.length) return s.slice(i, 끝);
|
|
28
|
+
끝 = 다음 + String.fromCodePoint(s.codePointAt(다음)).length;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const 딸린것 = (cp >= 0xfe00 && cp <= 0xfe0f) // 변형 선택자
|
|
32
|
+
|| (cp >= 0x1f3fb && cp <= 0x1f3ff) // 살색 조절
|
|
33
|
+
|| (cp >= 0x0300 && cp <= 0x036f) // 결합 부호
|
|
34
|
+
|| cp === 0x20e3; // 키캡
|
|
35
|
+
if (!딸린것) return s.slice(i, 끝);
|
|
36
|
+
끝 += String.fromCodePoint(cp).length;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
7
40
|
/**
|
|
8
41
|
* 색을 유지하면서 폭에 맞춰 접는다.
|
|
9
42
|
*
|
|
@@ -43,6 +76,7 @@ export function 접어쓰기(글, 폭) {
|
|
|
43
76
|
let 지금 = '';
|
|
44
77
|
let w = 0;
|
|
45
78
|
let 색 = ''; // 지금 켜져 있는 SGR
|
|
79
|
+
|
|
46
80
|
for (let i = 0; i < s.length;) {
|
|
47
81
|
// 색 코드는 통째로 옮긴다. 폭에는 안 센다.
|
|
48
82
|
if (s[i] === '\x1b') {
|
|
@@ -54,7 +88,7 @@ export function 접어쓰기(글, 폭) {
|
|
|
54
88
|
continue;
|
|
55
89
|
}
|
|
56
90
|
}
|
|
57
|
-
const ch = s
|
|
91
|
+
const ch = 한덩이(s, i);
|
|
58
92
|
const cw = width(ch);
|
|
59
93
|
// 첫 줄은 원래 폭, 접힌 줄부터는 들여쓴 만큼 좁아진다.
|
|
60
94
|
const 한도 = 줄들.length === 0 ? 폭 : 이어폭 + 들여.length;
|
|
@@ -65,7 +99,7 @@ export function 접어쓰기(글, 폭) {
|
|
|
65
99
|
}
|
|
66
100
|
지금 += ch;
|
|
67
101
|
w += cw;
|
|
68
|
-
i +=
|
|
102
|
+
i += ch.length;
|
|
69
103
|
}
|
|
70
104
|
if (지금.trim() !== '' || 줄들.length === 0) 줄들.push(지금 + (색 ? '\x1b[0m' : ''));
|
|
71
105
|
return 줄들;
|