deel-local-cli 1.5.3 → 1.5.5
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 +5 -3
- package/README.md +5 -3
- package/package.json +1 -1
- package/src/repl.js +9 -0
- package/src/ui/inputbox.js +16 -1
- package/src/ui/office.js +246 -26
- package/src/ui/screen.js +340 -336
package/README.en.md
CHANGED
|
@@ -100,7 +100,7 @@ This page is the **summary**. Each section links to the detail behind it.
|
|
|
100
100
|
| [Speed and spend](docs/en/tuning.md) | Per-stage effort · the prefix cache · context length |
|
|
101
101
|
| [Safety and corporate review](docs/en/safety.md) | Undo · working scope · audit log · the review package |
|
|
102
102
|
| [Configuration](docs/en/config.md) · [Development](docs/en/develop.md) | Env vars · run flags · running the tests · folder layout |
|
|
103
|
-
| [Release notes](docs/en/releases.md) | [1.5.3](docs/en/releases.md#153) · [1.5.2](docs/en/releases.md#152) · [1.5.1](docs/en/releases.md#151) · [1.5.0](docs/en/releases.md#150) · [1.4.3](docs/en/releases.md#143) · [1.4.2](docs/en/releases.md#142) · [1.4.1](docs/en/releases.md#141) · [1.4.0](docs/en/releases.md#140) · [1.3.0](docs/en/releases.md#130) · [1.2.0](docs/en/releases.md#120) |
|
|
103
|
+
| [Release notes](docs/en/releases.md) | [1.5.5](docs/en/releases.md#155) · [1.5.4](docs/en/releases.md#154) · [1.5.3](docs/en/releases.md#153) · [1.5.2](docs/en/releases.md#152) · [1.5.1](docs/en/releases.md#151) · [1.5.0](docs/en/releases.md#150) · [1.4.3](docs/en/releases.md#143) · [1.4.2](docs/en/releases.md#142) · [1.4.1](docs/en/releases.md#141) · [1.4.0](docs/en/releases.md#140) · [1.3.0](docs/en/releases.md#130) · [1.2.0](docs/en/releases.md#120) |
|
|
104
104
|
|
|
105
105
|
---
|
|
106
106
|
|
|
@@ -929,7 +929,7 @@ Stored in `~/.deel/config.json`. A `.deel/config.json` in the project folder tak
|
|
|
929
929
|
## Development
|
|
930
930
|
|
|
931
931
|
```bash
|
|
932
|
-
npm test Full suite (3,
|
|
932
|
+
npm test Full suite (3,693 checks)
|
|
933
933
|
npm run coverage Which lines the tests actually execute
|
|
934
934
|
npm run verify Import + network checks only
|
|
935
935
|
npm run bench Edit success rate
|
|
@@ -981,7 +981,9 @@ so one run tells you everything.
|
|
|
981
981
|
|
|
982
982
|
| Version | What changed |
|
|
983
983
|
|---|---|
|
|
984
|
-
| **[1.5.
|
|
984
|
+
| **[1.5.5](docs/en/releases.md#155)** | A plan fills the room — "only one person" was a bug, not a design |
|
|
985
|
+
| [1.5.4](docs/en/releases.md#154) | A day passes in the room — morning to night, a wall clock, a cold coffee |
|
|
986
|
+
| [1.5.3](docs/en/releases.md#153) | Three things a Mac user tripped over — newline hint, office stays put, one `/motion` |
|
|
985
987
|
| [1.5.2](docs/en/releases.md#152) | Everything else the adversarial reviews turned up — where the screen was lying |
|
|
986
988
|
| [1.5.1](docs/en/releases.md#151) | Two of the things 1.5.0 added shipped broken. This fixes them |
|
|
987
989
|
| [1.5.0](docs/en/releases.md#150) | Line breaks exist now, the screen got fun, and it came out lighter than before |
|
package/README.md
CHANGED
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
| [속도와 씀씀이](docs/ko/tuning.md) | 단계별 추론 강도 · 프리픽스 캐시 · 컨텍스트 길이 |
|
|
102
102
|
| [안전망과 사내 반입](docs/ko/safety.md) | 되돌리기 · 작업 범위 · 감사기록 · 심사 서류 |
|
|
103
103
|
| [설정](docs/ko/config.md) · [개발](docs/ko/develop.md) | 환경변수 · 실행 옵션 · 검사 돌리기 · 폴더 구조 |
|
|
104
|
-
| [릴리스 노트](docs/ko/releases.md) | [1.5.3](docs/ko/releases.md#153) · [1.5.2](docs/ko/releases.md#152) · [1.5.1](docs/ko/releases.md#151) · [1.5.0](docs/ko/releases.md#150) · [1.4.3](docs/ko/releases.md#143) · [1.4.2](docs/ko/releases.md#142) · [1.4.1](docs/ko/releases.md#141) · [1.4.0](docs/ko/releases.md#140) · [1.3.0](docs/ko/releases.md#130) · [1.2.0](docs/ko/releases.md#120) |
|
|
104
|
+
| [릴리스 노트](docs/ko/releases.md) | [1.5.5](docs/ko/releases.md#155) · [1.5.4](docs/ko/releases.md#154) · [1.5.3](docs/ko/releases.md#153) · [1.5.2](docs/ko/releases.md#152) · [1.5.1](docs/ko/releases.md#151) · [1.5.0](docs/ko/releases.md#150) · [1.4.3](docs/ko/releases.md#143) · [1.4.2](docs/ko/releases.md#142) · [1.4.1](docs/ko/releases.md#141) · [1.4.0](docs/ko/releases.md#140) · [1.3.0](docs/ko/releases.md#130) · [1.2.0](docs/ko/releases.md#120) |
|
|
105
105
|
|
|
106
106
|
---
|
|
107
107
|
|
|
@@ -907,7 +907,7 @@ deel sbom --only sbom # SBOM 한 장만
|
|
|
907
907
|
## 개발
|
|
908
908
|
|
|
909
909
|
```bash
|
|
910
|
-
npm test 전체 검증 (3,
|
|
910
|
+
npm test 전체 검증 (3,693항목)
|
|
911
911
|
npm run coverage 검사가 소스의 어디를 밟았는지
|
|
912
912
|
npm run verify 반입·통신 검증만
|
|
913
913
|
npm run bench 편집 성공률 측정
|
|
@@ -972,7 +972,9 @@ zip 은 진짜 `unzip` 으로, tar 는 진짜 `tar` 가 만든 것을 읽혀 교
|
|
|
972
972
|
|
|
973
973
|
| 판 | 무엇이 바뀌었나 |
|
|
974
974
|
|---|---|
|
|
975
|
-
| **[1.5.
|
|
975
|
+
| **[1.5.5](docs/ko/releases.md#155)** | 계획을 세우면 방이 찬다 — 「한 명뿐」은 고정이 아니라 버그였다 |
|
|
976
|
+
| [1.5.4](docs/ko/releases.md#154) | 방에 하루가 흐른다 — 아침·낮·노을·밤 · 벽시계 · 식은 커피 |
|
|
977
|
+
| [1.5.3](docs/ko/releases.md#153) | 맥에서 걸린 세 가지 — 줄바꿈 안내 · 사무실이 안 사라짐 · `/motion` 하나로 |
|
|
976
978
|
| [1.5.2](docs/ko/releases.md#152) | 적대적 리뷰가 남긴 나머지를 전부 정리했다 — 화면이 거짓말하던 자리들 |
|
|
977
979
|
| [1.5.1](docs/ko/releases.md#151) | 1.5.0 이 넣은 것 두 개가 망가진 채 나갔다. 그것을 고친다 |
|
|
978
980
|
| [1.5.0](docs/ko/releases.md#150) | 줄바꿈이 생겼고, 화면이 재미있어졌고, 그러면서 오히려 가벼워졌다 |
|
package/package.json
CHANGED
package/src/repl.js
CHANGED
|
@@ -1118,12 +1118,17 @@ export async function chatLoop(opts = {}) {
|
|
|
1118
1118
|
clearThinking();
|
|
1119
1119
|
if (streamed) { 답비우기(); say(''); streamed = false; }
|
|
1120
1120
|
화면.일바꿈(갈래고르기(ev.names?.[0]));
|
|
1121
|
+
// 사무실은 이만큼 자리를 채운다 — 이것이 진짜로 동시에 도는 수다.
|
|
1122
|
+
화면.함께갱신(ev.count);
|
|
1121
1123
|
say('');
|
|
1122
1124
|
say(` ${c.gray(`${ev.count}개를 함께 돌립니다`)} ${c.gray('·')} ${c.gray(ev.names.join(' '))}`);
|
|
1123
1125
|
break;
|
|
1124
1126
|
|
|
1125
1127
|
case 'tool':
|
|
1126
1128
|
tools++;
|
|
1129
|
+
// 결과가 오면 그 묶음은 끝난 것이다. 안 지우면 다음 한 개짜리
|
|
1130
|
+
// 도구까지 여럿이 도는 것처럼 보인다 — 자리 수가 거짓이 된다.
|
|
1131
|
+
화면.함께갱신(0);
|
|
1127
1132
|
// 걸러져 나온 것(인자가 잘렸거나, 모르는 도구거나, 거부된 것)은
|
|
1128
1133
|
// '시작' 을 안 거쳤다. 그래서 '생각 중…' 줄이 안 지워진 채로 결과가
|
|
1129
1134
|
// 그 줄 뒤에 가서 붙고, 이름도 없이 "└ 인자가 잘렸습니다" 만 남는다.
|
|
@@ -1426,6 +1431,10 @@ export async function chatLoop(opts = {}) {
|
|
|
1426
1431
|
// 어떻게 끝났든 일하는 표시는 반드시 걷는다. 오류로 빠져나온 길에서
|
|
1427
1432
|
// 안 걷으면 돌아가는 표시가 화면에 붙박이로 남고, 시계도 계속 돈다.
|
|
1428
1433
|
화면.일끝();
|
|
1434
|
+
// 중간에 끊겨도 자리는 비운다. 안 비우면 다음 턴이 시작될 때까지
|
|
1435
|
+
// 아무도 안 도는 방에 사람이 앉아 있다.
|
|
1436
|
+
화면.함께갱신(0);
|
|
1437
|
+
화면.하위갱신(0);
|
|
1429
1438
|
clearInterval(제목시계);
|
|
1430
1439
|
/*
|
|
1431
1440
|
* 오래 걸린 턴만 알린다. "안녕" 에 1초 만에 답할 때마다 딩 소리가 나면
|
package/src/ui/inputbox.js
CHANGED
|
@@ -355,6 +355,16 @@ export class InputBox {
|
|
|
355
355
|
this.할일들 = [];
|
|
356
356
|
// 지금 도는 하위 작업 수 (사무실 자리 수). 모르면 0 이다.
|
|
357
357
|
this.도는하위 = 0;
|
|
358
|
+
/*
|
|
359
|
+
* 지금 **한꺼번에** 도는 도구 수.
|
|
360
|
+
*
|
|
361
|
+
* 하위 작업은 순서대로 돈다(loop.js 가 하위를 `for await` 로 기다린다).
|
|
362
|
+
* 그래서 하위 수만 보면 자리는 거의 늘 하나다 — 방이 늘 한 명짜리로
|
|
363
|
+
* 보이던 진짜 이유가 이것이다. 실제로 동시에 도는 것은 읽기 도구 묶음
|
|
364
|
+
* 쪽이고(loop.js 의 Promise.all), 그 개수는 이미 알고 있었는데 방만
|
|
365
|
+
* 안 보고 있었다.
|
|
366
|
+
*/
|
|
367
|
+
this.함께도는 = 0;
|
|
358
368
|
}
|
|
359
369
|
|
|
360
370
|
/** 할 일이 바뀌었다. 사무실 화이트보드가 이걸 본다. */
|
|
@@ -367,6 +377,11 @@ export class InputBox {
|
|
|
367
377
|
this.도는하위 = Number.isFinite(n) && n > 0 ? Math.trunc(n) : 0;
|
|
368
378
|
}
|
|
369
379
|
|
|
380
|
+
/** 한꺼번에 도는 도구 수. 묶음이 끝나면 0 으로 돌아온다. */
|
|
381
|
+
함께갱신(n) {
|
|
382
|
+
this.함께도는 = Number.isFinite(n) && n > 1 ? Math.trunc(n) : 0;
|
|
383
|
+
}
|
|
384
|
+
|
|
370
385
|
/** 대화 글이 줄 중간까지 쓰였다. 줄이 끝날 때까지 상자를 안 세운다. */
|
|
371
386
|
줄중간표시() {
|
|
372
387
|
this.지우기();
|
|
@@ -526,7 +541,7 @@ export class InputBox {
|
|
|
526
541
|
this.그린높이 = 높이;
|
|
527
542
|
}
|
|
528
543
|
const 방 = 사무실켜나(process.stdout.rows, 칸수)
|
|
529
|
-
? 사무실줄들(사무실상태(this.session, this.일감, this.틱, this.할일들, this
|
|
544
|
+
? 사무실줄들(사무실상태(this.session, this.일감, this.틱, this.할일들, this.도는하위, this.함께도는), 폭)
|
|
530
545
|
: null;
|
|
531
546
|
|
|
532
547
|
const 짜기 = (방줄들) => 프레임({
|
package/src/ui/office.js
CHANGED
|
@@ -76,11 +76,21 @@ const C = {
|
|
|
76
76
|
나무: 137, 나무어둠: 94, 나무진: 58,
|
|
77
77
|
쇠: 145, 쇠어둠: 102, 쇠진: 59, 검정: 235,
|
|
78
78
|
화면: 45, 화면꺼짐: 239, 화면글: 123, 살: 223,
|
|
79
|
+
화면쉼: 24, 화면글쉼: 66,
|
|
79
80
|
머리: [52, 240, 94, 130], 옷: [67, 131, 71, 97],
|
|
80
81
|
밤: 17, 별: 226, 낮: 111, 구름: 255,
|
|
82
|
+
새벽: 153, 노을: 173, 노을구름: 217, 별흐림: 250,
|
|
83
|
+
해: 226, 아침해: 229, 노을해: 208, 달: 230,
|
|
84
|
+
시계판: 253, 머그: 254, 커피: 94,
|
|
81
85
|
종이: 255, 종이어둠: 250, 쪽지: 227, 쪽지끝: 120, 초록: 46, 넘침: 208,
|
|
82
86
|
};
|
|
83
87
|
|
|
88
|
+
// 하루가 가면 방도 저문다. [벽, 바닥]
|
|
89
|
+
const 방색 = {
|
|
90
|
+
아침: [C.벽, C.바닥], 낮: [C.벽, C.바닥],
|
|
91
|
+
노을: [237, 137], 밤: [236, C.바닥줄],
|
|
92
|
+
};
|
|
93
|
+
|
|
84
94
|
// ── 픽셀 판 ─────────────────────────────────────────────────────────────
|
|
85
95
|
class 판 {
|
|
86
96
|
constructor(가로, 세로칸) {
|
|
@@ -164,17 +174,34 @@ const 자세들 = {
|
|
|
164
174
|
졸기: 자리짜기([빈, 빈, 빈, ' hhhhhh ', ' hhffffffhh ', ' ffffff '],
|
|
165
175
|
['SSSSSSSSSS', 'SSSSSSSSSS']),
|
|
166
176
|
대기: 자리짜기(사람, ['SSSSSSSSSS', 'SSSSSSSSSS']),
|
|
177
|
+
/*
|
|
178
|
+
* 아직 차례가 안 온 일.
|
|
179
|
+
*
|
|
180
|
+
* 처음에는 화면을 아예 꺼 뒀다. "다섯이 동시에 도는 것처럼 보이면 거짓말"
|
|
181
|
+
* 이라는 이유였는데, 결과는 **꺼진 모니터 앞에 넷이 굳어 앉아 있는 방**
|
|
182
|
+
* 이었다. 정직하려다 이 화면을 켠 이유를 죽인 셈이다.
|
|
183
|
+
*
|
|
184
|
+
* 그래서 이 사람들도 움직인다 — 다만 화면이 **흐리다.** 밝은 화면이
|
|
185
|
+
* 지금 도는 것, 흐린 화면이 맡았지만 아직 차례가 안 온 것이다. 구분은
|
|
186
|
+
* 남기고 방은 살린다.
|
|
187
|
+
*/
|
|
188
|
+
쉬기A: 자리짜기([빈, ...사람.slice(0, 5)], ['GGGGGGGGGG', 'SSSSSSSSSS']),
|
|
189
|
+
쉬기B: 자리짜기([빈, 빈, ...사람.slice(0, 4)], ['SSSSSSSSSS', 'GGGGGGGGGG']),
|
|
167
190
|
빈자리: 자리짜기([빈, 빈, 빈, 빈, 빈, 빈], ['SSSSSSSSSS', 'SSSSSSSSSS']),
|
|
168
191
|
};
|
|
169
192
|
const 자리폭 = 18;
|
|
170
193
|
const 자리높이 = 14;
|
|
171
194
|
|
|
172
195
|
function 자리색(자세, 머리, 옷) {
|
|
173
|
-
const
|
|
196
|
+
const 빈것 = 자세 === '빈자리';
|
|
197
|
+
// 차례를 기다리는 자리는 사람도 책상도 멀쩡하다. 흐린 것은 화면뿐이다.
|
|
198
|
+
const 쉼 = 자세 === '쉬기A' || 자세 === '쉬기B';
|
|
174
199
|
return {
|
|
175
200
|
h: 머리, f: C.살, b: 옷,
|
|
176
|
-
M: C.검정,
|
|
177
|
-
|
|
201
|
+
M: C.검정,
|
|
202
|
+
S: 빈것 ? C.화면꺼짐 : 쉼 ? C.화면쉼 : C.화면,
|
|
203
|
+
G: 쉼 ? C.화면글쉼 : C.화면글,
|
|
204
|
+
D: 빈것 ? C.나무어둠 : C.나무, d: 빈것 ? C.나무진 : C.나무어둠,
|
|
178
205
|
};
|
|
179
206
|
}
|
|
180
207
|
|
|
@@ -187,16 +214,27 @@ function 자리색(자세, 머리, 옷) {
|
|
|
187
214
|
export function 사무실줄들(상태, 폭) {
|
|
188
215
|
const {
|
|
189
216
|
일꾼들 = [], 읽은파일 = 0, 고친파일 = 0, 할일 = 0, 끝난일 = 0,
|
|
190
|
-
부른횟수 = 0, 창 = 0, 틱 = 0,
|
|
217
|
+
부른횟수 = 0, 창 = 0, 틱 = 0, 걸린초 = 0,
|
|
191
218
|
} = 상태;
|
|
192
219
|
|
|
193
220
|
const 가로 = Math.max(60, 폭);
|
|
194
221
|
const p = new 판(가로, 세로);
|
|
195
|
-
p.칠하기(0, 0, 가로, 벽, C.벽);
|
|
196
|
-
p.칠하기(0, 벽, 가로, 1, C.굽도리);
|
|
197
|
-
p.칠하기(0, 벽 + 1, 가로, 세로 - 벽 - 1, C.바닥);
|
|
198
222
|
|
|
199
|
-
|
|
223
|
+
// 컨텍스트가 차는 만큼 하루가 간다. 넷으로 나눈다 — 둘이면 5% 와 50% 가
|
|
224
|
+
// 똑같은 대낮이라 창이 아무 말도 안 하게 된다.
|
|
225
|
+
const 하늘 = 창 >= 0.8 ? '밤' : 창 >= 0.55 ? '노을' : 창 >= 0.25 ? '낮' : '아침';
|
|
226
|
+
|
|
227
|
+
/*
|
|
228
|
+
* 방 안도 같이 저문다. 창만 어두워지고 벽은 대낮이면 창이 그림 액자처럼
|
|
229
|
+
* 보인다 — 저 창이 이 방의 창이라는 것이 안 읽힌다.
|
|
230
|
+
*
|
|
231
|
+
* 사람과 책상은 안 어둡게 한다. 이 방을 켠 이유가 자리를 읽는 것인데,
|
|
232
|
+
* 밤이라고 그게 안 보이면 분위기를 위해 본문을 버린 셈이 된다.
|
|
233
|
+
*/
|
|
234
|
+
const [벽색, 바닥색] = 방색[하늘];
|
|
235
|
+
p.칠하기(0, 0, 가로, 벽, 벽색);
|
|
236
|
+
p.칠하기(0, 벽, 가로, 1, C.굽도리);
|
|
237
|
+
p.칠하기(0, 벽 + 1, 가로, 세로 - 벽 - 1, 바닥색);
|
|
200
238
|
|
|
201
239
|
// 화이트보드 18×5 — 할 일. 끝난 것은 초록, 남은 것은 노랑.
|
|
202
240
|
{
|
|
@@ -246,25 +284,100 @@ export function 사무실줄들(상태, 폭) {
|
|
|
246
284
|
p.올리기(가로 - 15, 0, 그림, { R: C.검정, L: C.초록, l: C.쇠진, O: C.넘침 });
|
|
247
285
|
}
|
|
248
286
|
|
|
249
|
-
|
|
287
|
+
/*
|
|
288
|
+
* ── 창밖 — 컨텍스트가 차는 만큼 하루가 간다 ──────────────────────────
|
|
289
|
+
*
|
|
290
|
+
* 아침 → 낮 → 노을 → 밤. 넷은 꾸밈이 아니라 **컨텍스트가 얼마나 찼는가**
|
|
291
|
+
* 하나를 네 칸으로 나눈 것이다. 노을이 들면 슬슬 접을 때고, 밤이면 이미
|
|
292
|
+
* 늦은 것이다 — 숫자를 안 읽고 곁눈으로 봐도 그게 보인다.
|
|
293
|
+
*
|
|
294
|
+
* 예전에는 낮/밤 둘뿐이라 5% 와 50% 가 똑같이 환한 대낮이었다. 컨텍스트가
|
|
295
|
+
* 반이나 찬 것과 거의 안 찬 것이 같은 그림이면, 이 창은 아무 말도 안 한다.
|
|
296
|
+
*/
|
|
250
297
|
{
|
|
251
298
|
const 시작 = 21;
|
|
252
299
|
const 남 = 가로 - 15 - 시작;
|
|
253
300
|
const 창수 = Math.max(0, Math.floor(남 / 18));
|
|
301
|
+
|
|
302
|
+
/*
|
|
303
|
+
* 해는 동쪽에서 낮게 떠 한낮에 높이 오고, 서쪽으로 기울었다가 달로 바뀐다.
|
|
304
|
+
* 자리가 곧 시각이라, 세 칸짜리 하늘에서도 하루가 도는 것이 보인다.
|
|
305
|
+
* 3×2 로 그린다 — 2×1 짜리 점은 구름과 구별이 안 됐다.
|
|
306
|
+
*/
|
|
307
|
+
const 하늘것 = {
|
|
308
|
+
아침: { k: 2, r: 1 }, 낮: { k: 4, r: 0 }, 노을: { k: 9, r: 1 }, 밤: { k: 10, r: 0 },
|
|
309
|
+
}[하늘];
|
|
310
|
+
const 칠 = {
|
|
311
|
+
아침: { F: C.쇠어둠, S: C.새벽, c: C.구름, o: C.아침해 },
|
|
312
|
+
낮: { F: C.쇠어둠, S: C.낮, c: C.구름, o: C.해 },
|
|
313
|
+
노을: { F: C.쇠어둠, S: C.노을, c: C.노을구름, o: C.노을해 },
|
|
314
|
+
밤: { F: C.쇠어둠, S: C.밤, c: C.별, o: C.달 },
|
|
315
|
+
}[하늘];
|
|
316
|
+
|
|
317
|
+
/*
|
|
318
|
+
* 구름은 흐르고 별은 깜빡인다. 이 둘만은 숫자가 아니다 — 방이 멈춰 있지
|
|
319
|
+
* 않다는 것만 말한다. 그래서 아주 느리게 둔다(구름 14판, 별 9판에 한 번).
|
|
320
|
+
* 빨리 움직이면 눈이 그쪽으로 끌려가서, 정작 읽어야 할 자리·쪽지·불빛을
|
|
321
|
+
* 안 보게 된다.
|
|
322
|
+
*
|
|
323
|
+
* 구름은 **덩어리**여야 한다. 칸마다 따로 주사위를 굴렸더니 흰 점이
|
|
324
|
+
* 흩뿌려져서, 구름이 아니라 화면 잡티로 보였다. 가운데 줄을 넓게 잡아
|
|
325
|
+
* 뭉게구름 모양을 만든다.
|
|
326
|
+
*/
|
|
327
|
+
const 흐름 = Math.floor(틱 / 14);
|
|
328
|
+
const 깜빡 = Math.floor(틱 / 9);
|
|
329
|
+
const 구름폭 = [1, 3, 2];
|
|
330
|
+
const 구름인가 = (i, r, k) => {
|
|
331
|
+
for (const b of [0, 1]) {
|
|
332
|
+
const 중심 = ((i * 9 + b * 11 + 흐름) % 22) - 4;
|
|
333
|
+
if (Math.abs(k - 중심) <= 구름폭[r]) return true;
|
|
334
|
+
}
|
|
335
|
+
return false;
|
|
336
|
+
};
|
|
337
|
+
|
|
254
338
|
for (let i = 0; i < 창수; i++) {
|
|
255
339
|
const 그림 = ['FFFFFFFFFFFFFFFF'];
|
|
256
340
|
for (let r = 0; r < 3; r++) {
|
|
257
341
|
let 줄 = 'F';
|
|
258
342
|
for (let k = 0; k < 14; k++) {
|
|
259
|
-
|
|
260
|
-
|
|
343
|
+
if (k === 7) { 줄 += 'F'; continue; } // 창틀 세로대
|
|
344
|
+
const 해자리 = (r === 하늘것.r || r === 하늘것.r + 1)
|
|
345
|
+
&& k >= 하늘것.k && k < 하늘것.k + 3;
|
|
346
|
+
if (해자리) { 줄 += 'o'; continue; }
|
|
347
|
+
if (하늘 === '밤') {
|
|
348
|
+
const h = (i * 37 + r * 19 + k * 11) % 31;
|
|
349
|
+
줄 += (h < 4 && (h + 깜빡) % 4 !== 0) ? 'c' : 'S';
|
|
350
|
+
} else {
|
|
351
|
+
줄 += 구름인가(i, r, k) ? 'c' : 'S';
|
|
352
|
+
}
|
|
261
353
|
}
|
|
262
354
|
그림.push(줄 + 'F');
|
|
263
355
|
}
|
|
264
356
|
그림.push('FFFFFFFFFFFFFFFF');
|
|
265
|
-
p.올리기(시작 + i * 18, 0, 그림,
|
|
266
|
-
|
|
267
|
-
|
|
357
|
+
p.올리기(시작 + i * 18, 0, 그림, 칠);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/*
|
|
361
|
+
* 벽시계 — 이번 턴이 얼마나 걸렸나.
|
|
362
|
+
*
|
|
363
|
+
* 상자 오른쪽 끝에 걸린 시간이 이미 초로 찍히지만, 그건 읽어야 보인다.
|
|
364
|
+
* 바늘은 안 읽어도 보인다 — 한 바퀴가 1분이라, 두 바퀴째면 "오래 걸리는
|
|
365
|
+
* 중이구나" 가 숫자를 세기 전에 온다.
|
|
366
|
+
*
|
|
367
|
+
* 창을 놓고 남는 자리에만 건다. 좁은 터미널에서 시계 때문에 창이 하나
|
|
368
|
+
* 줄면 그건 손해다 — 창이 이 방의 본문이다.
|
|
369
|
+
*/
|
|
370
|
+
const 남은칸 = 남 - 창수 * 18;
|
|
371
|
+
if (남은칸 >= 8) {
|
|
372
|
+
const cx = 시작 + 창수 * 18 + Math.floor((남은칸 - 7) / 2);
|
|
373
|
+
p.올리기(cx, 0, [' TTTTT ', 'TWWWWWT', 'TWWWWWT', 'TWWWWWT', ' TTTTT '],
|
|
374
|
+
{ T: C.검정, W: C.시계판 });
|
|
375
|
+
// 한 바퀴에 1분. 7.5초마다 한 칸씩 — 도는 것이 보이면서도 매 판 다시
|
|
376
|
+
// 그릴 만큼 부산하지는 않다.
|
|
377
|
+
const 칸 = Math.floor((걸린초 % 60) / 7.5) % 8;
|
|
378
|
+
const [dx, dy] = [[0, -2], [1, -1], [2, 0], [1, 1], [0, 2], [-1, 1], [-2, 0], [-1, -1]][칸];
|
|
379
|
+
p.찍기(cx + 3, 2, C.검정);
|
|
380
|
+
p.찍기(cx + 3 + dx, 2 + dy, C.검정);
|
|
268
381
|
}
|
|
269
382
|
}
|
|
270
383
|
|
|
@@ -288,32 +401,106 @@ export function 사무실줄들(상태, 폭) {
|
|
|
288
401
|
const 왼쪽 = 15;
|
|
289
402
|
const 자리수 = Math.max(1, Math.floor((가로 - 왼쪽 - 2) / (자리폭 + 1)));
|
|
290
403
|
/*
|
|
291
|
-
* 자리보다 사람이 많으면 그냥 잘려
|
|
292
|
-
* 셋이 도는 것이 **똑같은 그림**이 됐다.
|
|
293
|
-
* 인데 그러면 자리 수만 거짓이 된다.
|
|
404
|
+
* 자리보다 사람이 많으면 앉히지 못한다. 예전에는 그냥 잘려 나가서, 좁은
|
|
405
|
+
* 화면에서 둘이 도는 것과 셋이 도는 것이 **똑같은 그림**이 됐다.
|
|
294
406
|
*
|
|
295
|
-
* 못
|
|
296
|
-
*
|
|
407
|
+
* 이제 못 앉은 사람은 **방을 돌아다닌다.** 자리가 모자라 서 있는 것이
|
|
408
|
+
* 실제로 일어나는 일이고, 화면에서도 그렇게 보이는 편이 낫다. 마지막 자리
|
|
409
|
+
* 책상의 넘침 표시는 그대로 둔다 — 걷는 사람도 다 못 그릴 만큼 많을 때가
|
|
410
|
+
* 있어서다.
|
|
297
411
|
*/
|
|
298
|
-
|
|
299
|
-
|
|
412
|
+
/*
|
|
413
|
+
* 몇은 앉고 몇은 서성인다.
|
|
414
|
+
*
|
|
415
|
+
* 자리가 모자라면 당연히 못 앉는다. 그런데 자리가 남아도 셋 이상이면 한
|
|
416
|
+
* 명은 일어나 있게 둔다 — 다 앉아만 있으면 사무실이 아니라 독서실이다.
|
|
417
|
+
* 사람 수는 그대로고, 그중 하나가 자리에 없을 뿐이다.
|
|
418
|
+
*
|
|
419
|
+
* 앞에서부터 앉힌다. 맨 앞이 **지금 도는 일**이라 그 사람은 늘 자리에
|
|
420
|
+
* 있어야 한다 — 일하는 중인 사람이 방을 돌아다니면 그게 거짓말이다.
|
|
421
|
+
*/
|
|
422
|
+
const 걷는수 = Math.min(4, Math.max(
|
|
423
|
+
일꾼들.length - 자리수,
|
|
424
|
+
일꾼들.length >= 3 ? 1 : 0));
|
|
425
|
+
const 앉는것 = 일꾼들.slice(0, Math.max(1, 일꾼들.length - 걷는수));
|
|
426
|
+
const 넘친자리 = 앉는것.length > 자리수;
|
|
300
427
|
for (let i = 0; i < 자리수; i++) {
|
|
301
428
|
const x = 왼쪽 + i * (자리폭 + 1);
|
|
302
429
|
const y = 세로 - 자리높이;
|
|
303
|
-
const 그사람 =
|
|
430
|
+
const 그사람 = 앉는것[i];
|
|
431
|
+
/*
|
|
432
|
+
* 자리마다 박자를 어긋나게 둔다.
|
|
433
|
+
*
|
|
434
|
+
* 예전에는 `틱/5` 하나를 온 방이 같이 봤다. 그래서 세 사람이 **한 몸처럼**
|
|
435
|
+
* 동시에 자판을 두드렸다 — 사람 셋이 아니라 톱니 셋으로 보인다. 자리
|
|
436
|
+
* 번호만큼 박자를 밀어 주면 그 자리에서 사무실이 된다.
|
|
437
|
+
*/
|
|
438
|
+
const 짝 = Math.floor((틱 + i * 4) / 5) % 2 === 0;
|
|
439
|
+
/*
|
|
440
|
+
* 차례를 기다리는 사람은 **더 느리게** 움직인다.
|
|
441
|
+
*
|
|
442
|
+
* 보기에도 그편이 맞고(바쁜 사람과 한가한 사람이 같은 박자로 움직이면
|
|
443
|
+
* 누가 바쁜지 안 보인다), 값도 싸다. 사람 하나가 1픽셀 움직이면 그 자리
|
|
444
|
+
* 세 줄이 통째로 다시 나가므로, 박자를 늦추는 것이 곧 나가는 양이다.
|
|
445
|
+
*/
|
|
446
|
+
const 쉬는짝 = Math.floor((틱 + i * 7) / 9) % 2 === 0;
|
|
304
447
|
const 자세 = !그사람 ? '빈자리'
|
|
305
448
|
: 그사람.갈래 === '쓰기' ? (짝 ? '치기A' : '치기B')
|
|
306
449
|
: 그사람.갈래 === '읽기' ? (짝 ? '읽기A' : '읽기B')
|
|
307
|
-
: 그사람.갈래 === '졸기' ? '졸기'
|
|
450
|
+
: 그사람.갈래 === '졸기' ? '졸기'
|
|
451
|
+
: 그사람.갈래 === '기다림' ? (쉬는짝 ? '쉬기A' : '쉬기B') : '대기';
|
|
308
452
|
p.올리기(x, y, 자세들[자세], 자리색(자세, C.머리[i % 4], C.옷[i % 4]));
|
|
309
453
|
// 마지막 자리인데 뒤에 더 있으면, 책상 모서리에 넘침 표시를 남긴다.
|
|
310
454
|
if (넘친자리 && i === 자리수 - 1) p.칠하기(x + 자리폭 - 2, y + 자리높이 - 3, 2, 2, C.넘침);
|
|
311
455
|
// 조는 사람 머리 위 z
|
|
312
456
|
if (자세 === '졸기' && 짝) p.칠하기(x + 13, y + 2, 2, 1, C.종이);
|
|
313
|
-
|
|
314
|
-
|
|
457
|
+
/*
|
|
458
|
+
* 조는 자리에는 식은 커피가 있다.
|
|
459
|
+
*
|
|
460
|
+
* 이것도 지어낸 것이 아니라 조는 것과 **같은 수**(45초 넘김)를 한 번 더
|
|
461
|
+
* 다르게 보인 것이다. 자는 사람이 화면 뒤로 반쯤 숨는 자세라, 좁은
|
|
462
|
+
* 터미널에서는 조는지 자리를 비운 것인지 헷갈렸다. 머그는 안 숨는다.
|
|
463
|
+
*/
|
|
464
|
+
if (자세 === '졸기') {
|
|
465
|
+
p.칠하기(x + 2, y + 10, 2, 2, C.머그);
|
|
466
|
+
p.칠하기(x + 2, y + 10, 2, 1, C.커피);
|
|
467
|
+
}
|
|
468
|
+
/*
|
|
469
|
+
* 고친 파일은 책상 위 종이로. 한 자리에 두 장까지.
|
|
470
|
+
*
|
|
471
|
+
* 전에는 자리마다 한 장이라 다섯 자리짜리 방에서 다섯 장이 상한이었고,
|
|
472
|
+
* 여섯 개를 고치나 스무 개를 고치나 같은 그림이었다. 자리당 두 장으로
|
|
473
|
+
* 두면 눈금이 두 배가 된다 — 없는 자리를 만들지 않으면서.
|
|
474
|
+
*/
|
|
475
|
+
const 종이몫 = Math.min(2, Math.max(0, 고친파일 - i * 2));
|
|
476
|
+
for (let n = 0; n < 종이몫; n++) p.칠하기(x + 13 - n * 4, y + 9, 3, 1, C.종이);
|
|
315
477
|
}
|
|
316
478
|
|
|
479
|
+
/*
|
|
480
|
+
* ── 자리를 못 잡은 사람은 방을 돌아다닌다 ───────────────────────────────
|
|
481
|
+
*
|
|
482
|
+
* 예전에는 자리보다 사람이 많으면 그냥 잘려 나갔다 — 넷이 도는 것과 여덟이
|
|
483
|
+
* 도는 것이 똑같은 그림이었다. 앉을 자리가 없으면 서 있는 것이 실제로
|
|
484
|
+
* 일어나는 일이니, 화면에서도 그렇게 보이는 편이 낫다.
|
|
485
|
+
*
|
|
486
|
+
* 사람마다 걸음이 다르다. 다 같은 속도로 가면 줄지어 행진하는 것처럼
|
|
487
|
+
* 보여서, 사람이 아니라 눈금자로 읽힌다.
|
|
488
|
+
*/
|
|
489
|
+
{
|
|
490
|
+
for (let j = 0; j < 걷는수; j++) {
|
|
491
|
+
const 속도 = 2 + (j % 3);
|
|
492
|
+
const 걸음 = Math.floor((틱 + j * 17) / 속도);
|
|
493
|
+
// 벽 밖에서 들어왔다 반대쪽으로 나간다. 화면 안에서 뿅 사라지면
|
|
494
|
+
// 걷는 것이 아니라 깜빡이는 것으로 보인다.
|
|
495
|
+
const 나아간것 = (걸음 + j * 23) % (가로 + 6) - 3;
|
|
496
|
+
const x = j % 2 === 0 ? 나아간것 : 가로 - 3 - 나아간것;
|
|
497
|
+
const 발 = 걸음 % 2 === 0 ? 'l l' : ' l ';
|
|
498
|
+
p.올리기(x, 벽 + 1, [' h ', 'bbb', 발],
|
|
499
|
+
{ h: C.살, b: C.옷[j % 4], l: C.머리[j % 4] });
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
|
|
317
504
|
return p.줄들();
|
|
318
505
|
}
|
|
319
506
|
|
|
@@ -386,7 +573,7 @@ export function 켜나(rows = process.stdout.rows, cols = process.stdout.columns
|
|
|
386
573
|
* @param {object} 일감 지금 도는 일 (없으면 null)
|
|
387
574
|
* @param {number} 틱
|
|
388
575
|
*/
|
|
389
|
-
export function 상태만들기(session, 일감, 틱 = 0, 할일들 = [], 도는하위 = 0) {
|
|
576
|
+
export function 상태만들기(session, 일감, 틱 = 0, 할일들 = [], 도는하위 = 0, 함께도는 = 0) {
|
|
390
577
|
const s = session ?? {};
|
|
391
578
|
const 쓴것 = s.usage ?? {};
|
|
392
579
|
const 총토큰 = (쓴것.in ?? 0) + (쓴것.out ?? 0);
|
|
@@ -399,6 +586,8 @@ export function 상태만들기(session, 일감, 틱 = 0, 할일들 = [], 도는
|
|
|
399
586
|
* 것을 그대로 쓴다 — motion.js 가 쓰는 그 갈래다.
|
|
400
587
|
*/
|
|
401
588
|
const 일꾼들 = [];
|
|
589
|
+
// 벽시계가 볼 값. 일이 없으면 0 — 바늘이 12 시에 선다.
|
|
590
|
+
let 걸린초 = 0;
|
|
402
591
|
if (일감) {
|
|
403
592
|
/*
|
|
404
593
|
* 오래 끌면 존다.
|
|
@@ -411,6 +600,7 @@ export function 상태만들기(session, 일감, 틱 = 0, 할일들 = [], 도는
|
|
|
411
600
|
*/
|
|
412
601
|
const 지난 = Number.isFinite(일감.시작) ? Date.now() - 일감.시작 : 0;
|
|
413
602
|
const 오래됐나 = 일감.갈래 === '느긋' || 지난 >= 느긋해질때;
|
|
603
|
+
걸린초 = Math.floor(지난 / 1000);
|
|
414
604
|
const 갈래 = 오래됐나 ? '졸기'
|
|
415
605
|
: ['읽기', '웹'].includes(일감.갈래) ? '읽기'
|
|
416
606
|
: ['쓰기', '명령', '답'].includes(일감.갈래) ? '쓰기' : '대기';
|
|
@@ -427,11 +617,41 @@ export function 상태만들기(session, 일감, 틱 = 0, 할일들 = [], 도는
|
|
|
427
617
|
* 모른다고 두는 편이 낫다 — 자리가 찼다는 사실은 그대로 보인다.
|
|
428
618
|
*/
|
|
429
619
|
일꾼들.push({ 갈래 });
|
|
620
|
+
/*
|
|
621
|
+
* 읽기 도구를 한꺼번에 돌리면 그만큼 진짜로 동시에 돈다.
|
|
622
|
+
*
|
|
623
|
+
* 방이 늘 한 명짜리로 보이던 진짜 이유가 여기 있었다. 하위 작업은
|
|
624
|
+
* loop.js 가 `for await` 로 기다리므로 **순서대로** 돈다 — 도는하위 는
|
|
625
|
+
* 사실상 0 아니면 1 이고, 겹치는 것은 하위가 또 하위를 떼어 낼 때뿐이다.
|
|
626
|
+
* 정작 동시에 도는 것은 읽기 묶음(loop.js 의 Promise.all)인데, 그 개수는
|
|
627
|
+
* 화면에 「3개를 함께 돌립니다」로 이미 찍히고 있었는데도 방만 안 봤다.
|
|
628
|
+
*
|
|
629
|
+
* 자세는 지어내는 것이 아니다. 묶어서 돌리는 것은 읽기 전용 도구뿐이라
|
|
630
|
+
* (Read·Glob·Grep·Skill·WebFetch) 저 자리는 전부 읽는 중인 게 맞다.
|
|
631
|
+
* 첫 자리는 이미 위에서 넣었으므로 하나를 뺀다.
|
|
632
|
+
*/
|
|
633
|
+
for (let i = 1; i < Math.max(0, 함께도는); i++) 일꾼들.push({ 갈래: '읽기' });
|
|
430
634
|
for (let i = 0; i < Math.max(0, 도는하위); i++) 일꾼들.push({ 갈래: '대기' });
|
|
431
635
|
}
|
|
432
636
|
|
|
637
|
+
/*
|
|
638
|
+
* 남은 할 일도 이 방의 사람이다.
|
|
639
|
+
*
|
|
640
|
+
* 「계획을 세웠는데도 한 명뿐이냐」는 말을 들었다. 맞는 말이었다 — 자리는
|
|
641
|
+
* '지금 도는 일' 만 셌는데 deel 은 한 번에 하나씩 하므로 늘 하나였고,
|
|
642
|
+
* 계획 다섯 개는 화이트보드 쪽지로만 있고 방은 텅 비어 있었다.
|
|
643
|
+
*
|
|
644
|
+
* 남은 할 일은 이 방이 **떠맡은 일감**이다. 사람으로 앉힌다. 대신 화면은
|
|
645
|
+
* 꺼 둔다 — 다섯이 동시에 돌고 있다고 말하면 그건 거짓말이라서다.
|
|
646
|
+
* 켜진 화면이 지금 도는 것, 꺼진 화면이 아직 차례가 안 온 것이다.
|
|
647
|
+
*/
|
|
648
|
+
for (let i = 0; i < Math.max(0, 할일들.filter((t) => t.state !== 'done').length); i++) {
|
|
649
|
+
일꾼들.push({ 갈래: '기다림' });
|
|
650
|
+
}
|
|
651
|
+
|
|
433
652
|
return {
|
|
434
653
|
일꾼들,
|
|
654
|
+
걸린초,
|
|
435
655
|
읽은파일: s.filesRead?.size ?? 0,
|
|
436
656
|
고친파일: s.changes?.size ?? 0,
|
|
437
657
|
끝난일: 할일들.filter((t) => t.state === 'done').length,
|
package/src/ui/screen.js
CHANGED
|
@@ -1,336 +1,340 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 화면 한 장.
|
|
3
|
-
*
|
|
4
|
-
* 왜 이 파일이 생겼나:
|
|
5
|
-
* repl.js 안에 say()·process.stdout.write 가 79군데 흩어져 있었다. 그 상태로는
|
|
6
|
-
* 화면을 하나 더 얹을 수가 없다 — 어디로 무엇이 나가는지 한 군데서 못 잡으니
|
|
7
|
-
* 두 화면이 같은 코드를 나눠 쓰려다 결국 둘 다 어중간해진다.
|
|
8
|
-
*
|
|
9
|
-
* 그래서 '무엇을 그린다' 와 '어떻게 그린다' 를 가른다. repl.js 는 이제
|
|
10
|
-
* `화면.줄(...)`·`화면.답조각(...)` 처럼 **뜻**만 말하고, 그것을 줄로 흘릴지
|
|
11
|
-
* 칸에 담아 다시 그릴지는 여기서 정한다.
|
|
12
|
-
*
|
|
13
|
-
* 두 가지 구현이 있다:
|
|
14
|
-
* 줄화면(LineScreen) 지금까지의 그 화면. 위에서 아래로 흘러간다.
|
|
15
|
-
* 파이프·기록·CI·`deel run`·검사가 전부 이것을 읽는다.
|
|
16
|
-
* 상자화면(BoxScreen) ui/inputbox.js. 사람이 터미널 앞에 앉아 있을 때만.
|
|
17
|
-
* **대화는 똑같이 흘려보내고** 맨 아래 입력 상자만
|
|
18
|
-
* 우리가 지우고 다시 그린다.
|
|
19
|
-
*
|
|
20
|
-
* ── 한 번 틀렸던 길 ─────────────────────────────────────────────────────
|
|
21
|
-
*
|
|
22
|
-
* 처음에는 터미널을 통째로 빌려(대체 화면) 대화 칸·파일 칸·할 일 칸을 나눠
|
|
23
|
-
* 그렸다. 보기에는 그럴듯했는데 **슬래시 명령이 전부 먹통**이 됐다.
|
|
24
|
-
* commands.js 를 비롯한 여섯 모듈이 화면 객체를 안 거치고 stdout 에 바로 쓰는데,
|
|
25
|
-
* 전체화면은 매번 자기 버퍼에서 화면을 다시 그리므로 그 글이 찍히자마자
|
|
26
|
-
* 덮여 사라졌다. 명령이 안 도는 게 아니라 결과가 안 보이는 것이라 더 나빴다.
|
|
27
|
-
*
|
|
28
|
-
* 고치려면 stdout 에 쓰는 자리를 전부 화면 객체로 꿰야 하는데, 지금 여섯 곳이고
|
|
29
|
-
* 앞으로 늘 것이며, 하나라도 빠뜨리면 같은 증상이 조용히 돌아온다. 그래서
|
|
30
|
-
* 대화는 그냥 흘려보내기로 했다. 그러면 stdout 에 쓰는 모든 코드가 손 안 대고
|
|
31
|
-
* 그대로 맞다. 스크롤·복사·`| tee` 도 그대로다.
|
|
32
|
-
*/
|
|
33
|
-
import { c, say, cursor, box } from './ansi.js';
|
|
34
|
-
import { statusLine, contextWarning } from './status.js';
|
|
35
|
-
import { spin } from './spinner.js';
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 지금 이 자리에서 입력 상자를 써도 되는가.
|
|
39
|
-
*
|
|
40
|
-
* 하나라도 아니면 줄화면이다 — 애매하면 줄화면이 맞다. 잘못 켜면 사용자
|
|
41
|
-
* 화면이 깨지지만, 잘못 안 켜면 그냥 지금까지의 화면일 뿐이다.
|
|
42
|
-
*
|
|
43
|
-
* 상자는 커서를 위로 올려 가며 자기가 그린 줄을 지운다. 그 앞자리가
|
|
44
|
-
* 터미널이 아니면(파이프·기록) 제어문자가 그대로 글에 섞인다.
|
|
45
|
-
*/
|
|
46
|
-
export function 상자쓸까({ tui = null } = {}) {
|
|
47
|
-
if (tui === false) return false; // deel --no-tui
|
|
48
|
-
if (!process.stdout.isTTY) return false; // 파이프·기록·CI
|
|
49
|
-
if (!process.stdin.isTTY) return false; // 입력이 파이프로 들어옴 (검사·데모)
|
|
50
|
-
if (process.env.TERM === 'dumb') return false;
|
|
51
|
-
if (process.env.CI) return false;
|
|
52
|
-
// 너무 좁으면 상자 안에 남는 자리가 없다. 테두리만 남고 글이 안 보인다.
|
|
53
|
-
if ((process.stdout.columns ?? 0) < 40) return false;
|
|
54
|
-
if (tui === true) return true;
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* 줄로 흘려보내는 화면 — 지금까지의 그 화면.
|
|
60
|
-
*
|
|
61
|
-
* 여기 있는 메서드 하나하나가 전에 repl.js 에 흩어져 있던 출력 한 줄과
|
|
62
|
-
* **글자 하나까지 같아야 한다.** 검사가 전부 이 화면을 글로 읽고 있어서,
|
|
63
|
-
* 다르면 그 자리에서 잡힌다. 그게 이 갈라내기가 맞는지 재는 자다.
|
|
64
|
-
*/
|
|
65
|
-
export class LineScreen {
|
|
66
|
-
constructor() {
|
|
67
|
-
this.kind = 'line';
|
|
68
|
-
// 곧 지워질 줄이 화면에 있나. \r 로 커서만 앞으로 보내 놓고 안 지우면
|
|
69
|
-
// 다음에 오는 짧은 글이 그 줄 위에 겹쳐 찍힌다.
|
|
70
|
-
this.임시중 = false;
|
|
71
|
-
this.돌림 = null;
|
|
72
|
-
/*
|
|
73
|
-
* 하위 작업 안쪽인가 — 그렇다면 몇 겹인가.
|
|
74
|
-
*
|
|
75
|
-
* 하위 작업(tools/task.js)은 제 대화에서 따로 돌지만 화면은 하나다.
|
|
76
|
-
* 표시가 없으면 하위가 부른 도구 줄이 부모 것과 똑같이 찍혀서, 사람은
|
|
77
|
-
* 부모가 파일 열두 개를 읽은 줄로 본다. 한 단 들여 그으면 어디부터
|
|
78
|
-
* 어디까지가 떼어 낸 일인지가 그냥 보인다.
|
|
79
|
-
*
|
|
80
|
-
* 여기 한 군데서만 붙인다. repl.js 의 say() 자리 예순 곳을 고치는 대신
|
|
81
|
-
* 이벤트가 들고 온 깊이를 화면에 알려 주는 쪽이 안 빠뜨린다.
|
|
82
|
-
*/
|
|
83
|
-
this.깊이 = 0;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// ── 흘러가는 글 ────────────────────────────────────────────────────────
|
|
87
|
-
|
|
88
|
-
/** 지금부터 찍는 줄이 몇 겹 안쪽인지. 0 이면 평소대로. */
|
|
89
|
-
들여쓰기(n) { this.깊이 = Math.max(0, Number(n) || 0); }
|
|
90
|
-
|
|
91
|
-
/*
|
|
92
|
-
* 하위 작업 구간에 세로줄을 세운다.
|
|
93
|
-
*
|
|
94
|
-
* 빈 칸으로만 밀면 어디서 시작해 어디서 끝났는지가 안 보인다 — 특히 하위가
|
|
95
|
-
* 파일을 열 개 만지고 나면, 스크롤을 올려 여는 줄을 찾아야 그게 하위였는지
|
|
96
|
-
* 안다. 세로줄 한 칸이면 훑는 것만으로 구간이 잡힌다.
|
|
97
|
-
*/
|
|
98
|
-
#안내() { return this.깊이 ? c.gray(' │').repeat(this.깊이) : ''; }
|
|
99
|
-
|
|
100
|
-
/** 한 줄. 이미 색이 입혀진 글을 받는다. */
|
|
101
|
-
줄(s = '') { this.임시지움(); say(this.깊이 && s ? this.#안내() + s : s); }
|
|
102
|
-
|
|
103
|
-
/** 줄바꿈 없이 이어 붙인다 — 스트리밍으로 오는 답. */
|
|
104
|
-
붙임(s) { this.임시지움(); process.stdout.write(s); }
|
|
105
|
-
|
|
106
|
-
// ── 곧 지워질 표시 ─────────────────────────────────────────────────────
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* 기다리는 중. \r 로 커서만 앞으로 보낸다.
|
|
110
|
-
*
|
|
111
|
-
* 파이프일 때도 찍는다 — 데모·기록에서 '무엇을 기다리는 중이었나' 가
|
|
112
|
-
* 남아야 한다. 다만 지워야 할 줄로 세는 것은 터미널일 때만이다.
|
|
113
|
-
*/
|
|
114
|
-
기다림(s) {
|
|
115
|
-
process.stdout.write(` ${s}\r`);
|
|
116
|
-
if (process.stdout.isTTY) this.임시중 = true;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** 생각하는 중. 글자 수가 계속 바뀌므로 터미널일 때만 그린다. */
|
|
120
|
-
생각(s) {
|
|
121
|
-
if (!process.stdout.isTTY) return;
|
|
122
|
-
cursor.clearLine();
|
|
123
|
-
process.stdout.write(` ${s}`);
|
|
124
|
-
this.임시중 = true;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
임시지움() {
|
|
128
|
-
if (this.임시중) { cursor.clearLine(); this.임시중 = false; }
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* 사람이 치고 있던 입력 줄을 지운다.
|
|
133
|
-
*
|
|
134
|
-
* Shift+Tab 으로 승인 방식을 돌릴 때처럼, 입력을 기다리는 도중에 화면에
|
|
135
|
-
* 한 줄을 끼워 넣어야 하는 자리가 있다. 안 지우면 `❯ ` 뒤에 새 글이 붙는다.
|
|
136
|
-
* 상자화면에서는 상자를 통째로 걷어낸다(BoxScreen 이 갈아 끼운다).
|
|
137
|
-
*/
|
|
138
|
-
입력지움() { cursor.clearLine(); }
|
|
139
|
-
|
|
140
|
-
/** 오래 걸리는 일에 돌아가는 표시를 세운다. */
|
|
141
|
-
돌리기(label) { this.돌림 = spin(label); return this.돌림; }
|
|
142
|
-
|
|
143
|
-
// spinner 가 멈추면서 이미 줄을 지우고 커서를 되살린다. 여기서 또 지우지 않는다 —
|
|
144
|
-
// 화면에 나가는 제어문자가 한 벌이라도 달라지면 그게 갈라내기의 흔적이 된다.
|
|
145
|
-
돌림멈춤(finalLine) {
|
|
146
|
-
if (!this.돌림) return;
|
|
147
|
-
this.돌림.stop(finalLine);
|
|
148
|
-
this.돌림 = null;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// ── 자리를 차지하는 것 ─────────────────────────────────────────────────
|
|
152
|
-
|
|
153
|
-
/** 켤 때 한 번 그리는 머리말 상자. */
|
|
154
|
-
머리말(lines) {
|
|
155
|
-
say('');
|
|
156
|
-
for (const l of box(lines, { tone: c.gray })) say(' ' + l);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* 입력 자리. 위에 상태줄을 한 줄 깔고 그 아래에 커서를 둔다.
|
|
161
|
-
*
|
|
162
|
-
* 줄화면에서는 이게 매번 새로 찍힌다 — 대화가 길어지면 상태줄이 화면에
|
|
163
|
-
* 여러 번 남는다. 그게 흘러가는 화면의 성질이고, 기록으로 읽을 때는
|
|
164
|
-
* 오히려 그때그때 상태를 알 수 있어 낫다.
|
|
165
|
-
*/
|
|
166
|
-
입력자리(session) {
|
|
167
|
-
say('');
|
|
168
|
-
say(statusLine(session));
|
|
169
|
-
const w = contextWarning(session);
|
|
170
|
-
if (w) say(` ${w}`);
|
|
171
|
-
process.stdout.write(` ${c.hcyan('❯')} `);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/** 사람이 치는 중인 글이 바뀌었다. 줄화면은 readline 이 알아서 되비춘다. */
|
|
175
|
-
입력갱신() { }
|
|
176
|
-
|
|
177
|
-
/** 일하는 중 미리 치는 글. 줄화면은 상자가 없어 보여 줄 자리가 없다. */
|
|
178
|
-
대기갱신() { }
|
|
179
|
-
|
|
180
|
-
/** 줄화면은 붙여 쓰기 전에 임시글만 지우면 되므로 따로 셀 것이 없다. */
|
|
181
|
-
줄중간인가() { return false; }
|
|
182
|
-
|
|
183
|
-
/*
|
|
184
|
-
* 일하는 중 표시. 줄화면은 안 그린다.
|
|
185
|
-
*
|
|
186
|
-
* 줄화면은 흘러가는 화면이라 '한 자리에서 도는 표시' 를 둘 데가 없다.
|
|
187
|
-
* 대신 기다림()·생각() 이 그 자리를 이미 맡고 있다 — 여기서 또 그리면
|
|
188
|
-
* 같은 말이 두 번 나간다. 파이프·기록에서는 돌아가는 표시 자체가 잡음이다.
|
|
189
|
-
*/
|
|
190
|
-
일시작() { }
|
|
191
|
-
일바꿈() { }
|
|
192
|
-
일끝() { }
|
|
193
|
-
// 줄 화면에는 사무실이 없다 — 받아만 두고 아무것도 안 한다.
|
|
194
|
-
할일갱신() { }
|
|
195
|
-
하위갱신() { }
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
this
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
this
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
this
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
this
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
*
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 화면 한 장.
|
|
3
|
+
*
|
|
4
|
+
* 왜 이 파일이 생겼나:
|
|
5
|
+
* repl.js 안에 say()·process.stdout.write 가 79군데 흩어져 있었다. 그 상태로는
|
|
6
|
+
* 화면을 하나 더 얹을 수가 없다 — 어디로 무엇이 나가는지 한 군데서 못 잡으니
|
|
7
|
+
* 두 화면이 같은 코드를 나눠 쓰려다 결국 둘 다 어중간해진다.
|
|
8
|
+
*
|
|
9
|
+
* 그래서 '무엇을 그린다' 와 '어떻게 그린다' 를 가른다. repl.js 는 이제
|
|
10
|
+
* `화면.줄(...)`·`화면.답조각(...)` 처럼 **뜻**만 말하고, 그것을 줄로 흘릴지
|
|
11
|
+
* 칸에 담아 다시 그릴지는 여기서 정한다.
|
|
12
|
+
*
|
|
13
|
+
* 두 가지 구현이 있다:
|
|
14
|
+
* 줄화면(LineScreen) 지금까지의 그 화면. 위에서 아래로 흘러간다.
|
|
15
|
+
* 파이프·기록·CI·`deel run`·검사가 전부 이것을 읽는다.
|
|
16
|
+
* 상자화면(BoxScreen) ui/inputbox.js. 사람이 터미널 앞에 앉아 있을 때만.
|
|
17
|
+
* **대화는 똑같이 흘려보내고** 맨 아래 입력 상자만
|
|
18
|
+
* 우리가 지우고 다시 그린다.
|
|
19
|
+
*
|
|
20
|
+
* ── 한 번 틀렸던 길 ─────────────────────────────────────────────────────
|
|
21
|
+
*
|
|
22
|
+
* 처음에는 터미널을 통째로 빌려(대체 화면) 대화 칸·파일 칸·할 일 칸을 나눠
|
|
23
|
+
* 그렸다. 보기에는 그럴듯했는데 **슬래시 명령이 전부 먹통**이 됐다.
|
|
24
|
+
* commands.js 를 비롯한 여섯 모듈이 화면 객체를 안 거치고 stdout 에 바로 쓰는데,
|
|
25
|
+
* 전체화면은 매번 자기 버퍼에서 화면을 다시 그리므로 그 글이 찍히자마자
|
|
26
|
+
* 덮여 사라졌다. 명령이 안 도는 게 아니라 결과가 안 보이는 것이라 더 나빴다.
|
|
27
|
+
*
|
|
28
|
+
* 고치려면 stdout 에 쓰는 자리를 전부 화면 객체로 꿰야 하는데, 지금 여섯 곳이고
|
|
29
|
+
* 앞으로 늘 것이며, 하나라도 빠뜨리면 같은 증상이 조용히 돌아온다. 그래서
|
|
30
|
+
* 대화는 그냥 흘려보내기로 했다. 그러면 stdout 에 쓰는 모든 코드가 손 안 대고
|
|
31
|
+
* 그대로 맞다. 스크롤·복사·`| tee` 도 그대로다.
|
|
32
|
+
*/
|
|
33
|
+
import { c, say, cursor, box } from './ansi.js';
|
|
34
|
+
import { statusLine, contextWarning } from './status.js';
|
|
35
|
+
import { spin } from './spinner.js';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 지금 이 자리에서 입력 상자를 써도 되는가.
|
|
39
|
+
*
|
|
40
|
+
* 하나라도 아니면 줄화면이다 — 애매하면 줄화면이 맞다. 잘못 켜면 사용자
|
|
41
|
+
* 화면이 깨지지만, 잘못 안 켜면 그냥 지금까지의 화면일 뿐이다.
|
|
42
|
+
*
|
|
43
|
+
* 상자는 커서를 위로 올려 가며 자기가 그린 줄을 지운다. 그 앞자리가
|
|
44
|
+
* 터미널이 아니면(파이프·기록) 제어문자가 그대로 글에 섞인다.
|
|
45
|
+
*/
|
|
46
|
+
export function 상자쓸까({ tui = null } = {}) {
|
|
47
|
+
if (tui === false) return false; // deel --no-tui
|
|
48
|
+
if (!process.stdout.isTTY) return false; // 파이프·기록·CI
|
|
49
|
+
if (!process.stdin.isTTY) return false; // 입력이 파이프로 들어옴 (검사·데모)
|
|
50
|
+
if (process.env.TERM === 'dumb') return false;
|
|
51
|
+
if (process.env.CI) return false;
|
|
52
|
+
// 너무 좁으면 상자 안에 남는 자리가 없다. 테두리만 남고 글이 안 보인다.
|
|
53
|
+
if ((process.stdout.columns ?? 0) < 40) return false;
|
|
54
|
+
if (tui === true) return true;
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 줄로 흘려보내는 화면 — 지금까지의 그 화면.
|
|
60
|
+
*
|
|
61
|
+
* 여기 있는 메서드 하나하나가 전에 repl.js 에 흩어져 있던 출력 한 줄과
|
|
62
|
+
* **글자 하나까지 같아야 한다.** 검사가 전부 이 화면을 글로 읽고 있어서,
|
|
63
|
+
* 다르면 그 자리에서 잡힌다. 그게 이 갈라내기가 맞는지 재는 자다.
|
|
64
|
+
*/
|
|
65
|
+
export class LineScreen {
|
|
66
|
+
constructor() {
|
|
67
|
+
this.kind = 'line';
|
|
68
|
+
// 곧 지워질 줄이 화면에 있나. \r 로 커서만 앞으로 보내 놓고 안 지우면
|
|
69
|
+
// 다음에 오는 짧은 글이 그 줄 위에 겹쳐 찍힌다.
|
|
70
|
+
this.임시중 = false;
|
|
71
|
+
this.돌림 = null;
|
|
72
|
+
/*
|
|
73
|
+
* 하위 작업 안쪽인가 — 그렇다면 몇 겹인가.
|
|
74
|
+
*
|
|
75
|
+
* 하위 작업(tools/task.js)은 제 대화에서 따로 돌지만 화면은 하나다.
|
|
76
|
+
* 표시가 없으면 하위가 부른 도구 줄이 부모 것과 똑같이 찍혀서, 사람은
|
|
77
|
+
* 부모가 파일 열두 개를 읽은 줄로 본다. 한 단 들여 그으면 어디부터
|
|
78
|
+
* 어디까지가 떼어 낸 일인지가 그냥 보인다.
|
|
79
|
+
*
|
|
80
|
+
* 여기 한 군데서만 붙인다. repl.js 의 say() 자리 예순 곳을 고치는 대신
|
|
81
|
+
* 이벤트가 들고 온 깊이를 화면에 알려 주는 쪽이 안 빠뜨린다.
|
|
82
|
+
*/
|
|
83
|
+
this.깊이 = 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── 흘러가는 글 ────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
/** 지금부터 찍는 줄이 몇 겹 안쪽인지. 0 이면 평소대로. */
|
|
89
|
+
들여쓰기(n) { this.깊이 = Math.max(0, Number(n) || 0); }
|
|
90
|
+
|
|
91
|
+
/*
|
|
92
|
+
* 하위 작업 구간에 세로줄을 세운다.
|
|
93
|
+
*
|
|
94
|
+
* 빈 칸으로만 밀면 어디서 시작해 어디서 끝났는지가 안 보인다 — 특히 하위가
|
|
95
|
+
* 파일을 열 개 만지고 나면, 스크롤을 올려 여는 줄을 찾아야 그게 하위였는지
|
|
96
|
+
* 안다. 세로줄 한 칸이면 훑는 것만으로 구간이 잡힌다.
|
|
97
|
+
*/
|
|
98
|
+
#안내() { return this.깊이 ? c.gray(' │').repeat(this.깊이) : ''; }
|
|
99
|
+
|
|
100
|
+
/** 한 줄. 이미 색이 입혀진 글을 받는다. */
|
|
101
|
+
줄(s = '') { this.임시지움(); say(this.깊이 && s ? this.#안내() + s : s); }
|
|
102
|
+
|
|
103
|
+
/** 줄바꿈 없이 이어 붙인다 — 스트리밍으로 오는 답. */
|
|
104
|
+
붙임(s) { this.임시지움(); process.stdout.write(s); }
|
|
105
|
+
|
|
106
|
+
// ── 곧 지워질 표시 ─────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 기다리는 중. \r 로 커서만 앞으로 보낸다.
|
|
110
|
+
*
|
|
111
|
+
* 파이프일 때도 찍는다 — 데모·기록에서 '무엇을 기다리는 중이었나' 가
|
|
112
|
+
* 남아야 한다. 다만 지워야 할 줄로 세는 것은 터미널일 때만이다.
|
|
113
|
+
*/
|
|
114
|
+
기다림(s) {
|
|
115
|
+
process.stdout.write(` ${s}\r`);
|
|
116
|
+
if (process.stdout.isTTY) this.임시중 = true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** 생각하는 중. 글자 수가 계속 바뀌므로 터미널일 때만 그린다. */
|
|
120
|
+
생각(s) {
|
|
121
|
+
if (!process.stdout.isTTY) return;
|
|
122
|
+
cursor.clearLine();
|
|
123
|
+
process.stdout.write(` ${s}`);
|
|
124
|
+
this.임시중 = true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
임시지움() {
|
|
128
|
+
if (this.임시중) { cursor.clearLine(); this.임시중 = false; }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 사람이 치고 있던 입력 줄을 지운다.
|
|
133
|
+
*
|
|
134
|
+
* Shift+Tab 으로 승인 방식을 돌릴 때처럼, 입력을 기다리는 도중에 화면에
|
|
135
|
+
* 한 줄을 끼워 넣어야 하는 자리가 있다. 안 지우면 `❯ ` 뒤에 새 글이 붙는다.
|
|
136
|
+
* 상자화면에서는 상자를 통째로 걷어낸다(BoxScreen 이 갈아 끼운다).
|
|
137
|
+
*/
|
|
138
|
+
입력지움() { cursor.clearLine(); }
|
|
139
|
+
|
|
140
|
+
/** 오래 걸리는 일에 돌아가는 표시를 세운다. */
|
|
141
|
+
돌리기(label) { this.돌림 = spin(label); return this.돌림; }
|
|
142
|
+
|
|
143
|
+
// spinner 가 멈추면서 이미 줄을 지우고 커서를 되살린다. 여기서 또 지우지 않는다 —
|
|
144
|
+
// 화면에 나가는 제어문자가 한 벌이라도 달라지면 그게 갈라내기의 흔적이 된다.
|
|
145
|
+
돌림멈춤(finalLine) {
|
|
146
|
+
if (!this.돌림) return;
|
|
147
|
+
this.돌림.stop(finalLine);
|
|
148
|
+
this.돌림 = null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── 자리를 차지하는 것 ─────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
/** 켤 때 한 번 그리는 머리말 상자. */
|
|
154
|
+
머리말(lines) {
|
|
155
|
+
say('');
|
|
156
|
+
for (const l of box(lines, { tone: c.gray })) say(' ' + l);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 입력 자리. 위에 상태줄을 한 줄 깔고 그 아래에 커서를 둔다.
|
|
161
|
+
*
|
|
162
|
+
* 줄화면에서는 이게 매번 새로 찍힌다 — 대화가 길어지면 상태줄이 화면에
|
|
163
|
+
* 여러 번 남는다. 그게 흘러가는 화면의 성질이고, 기록으로 읽을 때는
|
|
164
|
+
* 오히려 그때그때 상태를 알 수 있어 낫다.
|
|
165
|
+
*/
|
|
166
|
+
입력자리(session) {
|
|
167
|
+
say('');
|
|
168
|
+
say(statusLine(session));
|
|
169
|
+
const w = contextWarning(session);
|
|
170
|
+
if (w) say(` ${w}`);
|
|
171
|
+
process.stdout.write(` ${c.hcyan('❯')} `);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** 사람이 치는 중인 글이 바뀌었다. 줄화면은 readline 이 알아서 되비춘다. */
|
|
175
|
+
입력갱신() { }
|
|
176
|
+
|
|
177
|
+
/** 일하는 중 미리 치는 글. 줄화면은 상자가 없어 보여 줄 자리가 없다. */
|
|
178
|
+
대기갱신() { }
|
|
179
|
+
|
|
180
|
+
/** 줄화면은 붙여 쓰기 전에 임시글만 지우면 되므로 따로 셀 것이 없다. */
|
|
181
|
+
줄중간인가() { return false; }
|
|
182
|
+
|
|
183
|
+
/*
|
|
184
|
+
* 일하는 중 표시. 줄화면은 안 그린다.
|
|
185
|
+
*
|
|
186
|
+
* 줄화면은 흘러가는 화면이라 '한 자리에서 도는 표시' 를 둘 데가 없다.
|
|
187
|
+
* 대신 기다림()·생각() 이 그 자리를 이미 맡고 있다 — 여기서 또 그리면
|
|
188
|
+
* 같은 말이 두 번 나간다. 파이프·기록에서는 돌아가는 표시 자체가 잡음이다.
|
|
189
|
+
*/
|
|
190
|
+
일시작() { }
|
|
191
|
+
일바꿈() { }
|
|
192
|
+
일끝() { }
|
|
193
|
+
// 줄 화면에는 사무실이 없다 — 받아만 두고 아무것도 안 한다.
|
|
194
|
+
할일갱신() { }
|
|
195
|
+
하위갱신() { }
|
|
196
|
+
함께갱신() { }
|
|
197
|
+
|
|
198
|
+
close() {
|
|
199
|
+
this.돌림멈춤();
|
|
200
|
+
this.임시지움();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 대화는 그대로 흘려보내고, 맨 아래 입력 상자만 우리가 관리하는 화면.
|
|
206
|
+
*
|
|
207
|
+
* LineScreen 을 그대로 물려받는다 — 대화가 나가는 길이 **한 글자도 다르면
|
|
208
|
+
* 안 되기** 때문이다. 다르면 검사가 읽는 화면과 사람이 보는 화면이 갈라진다.
|
|
209
|
+
* 여기서 갈아 끼우는 것은 입력 자리 세 가지뿐이다.
|
|
210
|
+
*/
|
|
211
|
+
export class BoxScreen extends LineScreen {
|
|
212
|
+
constructor(상자) {
|
|
213
|
+
super();
|
|
214
|
+
this.kind = 'box';
|
|
215
|
+
this.상자 = 상자;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/*
|
|
219
|
+
* 대화 한 줄이 나가기 전에 상자를 걷어낸다. 안 걷으면 새 글이 상자 위에
|
|
220
|
+
* 겹쳐 찍히고, 지울 때 대화까지 같이 지워진다.
|
|
221
|
+
*
|
|
222
|
+
* 일하는 중이면 찍은 **뒤에 다시 세운다.** 이게 없으면 첫 도구 결과가
|
|
223
|
+
* 찍히는 순간 상자가 사라지고, 그 뒤로는 일이 끝날 때까지 화면 아래가
|
|
224
|
+
* 텅 빈 채로 남는다 — 정확히 그게 "밋밋해 보인다" 는 상태다.
|
|
225
|
+
*/
|
|
226
|
+
줄(s = '') {
|
|
227
|
+
this.상자.지우기();
|
|
228
|
+
super.줄(s);
|
|
229
|
+
// 줄이 끝났다 — 이제 아래에 상자를 세워도 대화를 안 덮는다.
|
|
230
|
+
this.상자.줄끝남();
|
|
231
|
+
this.상자.일그리기();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/*
|
|
235
|
+
* 스트리밍으로 이어 붙이는 글은 다시 안 세운다.
|
|
236
|
+
*
|
|
237
|
+
* 토막마다 상자를 세웠다 걷으면, 글자 몇 개마다 상자가 깜빡이고 커서가
|
|
238
|
+
* 위아래로 튄다. 답이 흘러나오는 동안에는 그 자체가 살아 있다는 신호라
|
|
239
|
+
* 상자가 없어도 밋밋하지 않다. 답이 끝나면 repl.js 가 다시 세운다.
|
|
240
|
+
*/
|
|
241
|
+
붙임(s) {
|
|
242
|
+
// 상자를 걷고, **줄 중간에 멈춰 있다고 표시한다.** 이 표시가 없으면
|
|
243
|
+
// 돌아가는 표시가 90ms 뒤에 제 시계로 상자를 답 줄 위에 그려 버리고,
|
|
244
|
+
// 그 다음 지우기가 답까지 같이 지운다.
|
|
245
|
+
this.상자.줄중간표시();
|
|
246
|
+
super.붙임(s);
|
|
247
|
+
|
|
248
|
+
/*
|
|
249
|
+
* 줄이 끝나는 토막이면 그 자리에서 다시 세운다.
|
|
250
|
+
*
|
|
251
|
+
* 예전에는 답이 흐르는 내내 상자를 안 세웠다. 상자만 있을 때는 그래도
|
|
252
|
+
* 됐다 — 없어도 답이 흐르는 것 자체가 살아 있다는 신호였으니까.
|
|
253
|
+
* 그런데 **사무실을 켜면 얘기가 다르다.** 붙박아 두라고 켠 것이 정작
|
|
254
|
+
* 일할 때만 사라지니, 켠 사람 눈에는 고장으로 보인다.
|
|
255
|
+
*
|
|
256
|
+
* 그렇다고 토막마다 세우면 글자 몇 개마다 깜빡인다. 그래서 **줄이
|
|
257
|
+
* 끝날 때만** 세운다 — 줄() 이 이미 하고 있는 것과 같은 빈도이고,
|
|
258
|
+
* 그 자리에서는 답을 덮을 일이 없다.
|
|
259
|
+
*/
|
|
260
|
+
if (typeof s === 'string' && s.endsWith('\n')) {
|
|
261
|
+
this.상자.줄끝남();
|
|
262
|
+
this.상자.일그리기();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** 일하기 시작·바뀜·끝. 상자 안엣것만 바뀐다 (ui/working.js 의 문구). */
|
|
267
|
+
일시작(session, 갈래) { this.임시지움(); this.상자.일시작(session, 갈래); }
|
|
268
|
+
일바꿈(갈래, 곁정보) { this.상자.일바꿈(갈래, 곁정보); }
|
|
269
|
+
일끝() { this.상자.일끝(); }
|
|
270
|
+
/** 할 일이 바뀌었다 — 사무실 화이트보드가 이걸 본다. */
|
|
271
|
+
할일갱신(할일들) { this.상자.할일갱신(할일들); }
|
|
272
|
+
/** 도는 하위 작업 수가 바뀌었다 — 사무실 자리가 이만큼 찬다. */
|
|
273
|
+
하위갱신(n) { this.상자.하위갱신(n); }
|
|
274
|
+
|
|
275
|
+
/** 한꺼번에 도는 도구 수가 바뀌었다 — 사무실 자리가 그만큼 찬다. */
|
|
276
|
+
함께갱신(n) { this.상자.함께갱신(n); }
|
|
277
|
+
|
|
278
|
+
/** 일하는 도중에 미리 치고 있는 글 · 예약해 둔 개수. */
|
|
279
|
+
대기갱신(글, 예약수) { this.상자.대기갱신(글, 예약수); }
|
|
280
|
+
|
|
281
|
+
/** 대화가 줄 중간에 멈춰 있나 (답이 흘러나오는 중). */
|
|
282
|
+
줄중간인가() { return this.상자.줄중간; }
|
|
283
|
+
|
|
284
|
+
/*
|
|
285
|
+
* 줄화면에서 '생각 중…' 을 한 줄로 흘리던 자리.
|
|
286
|
+
*
|
|
287
|
+
* 상자를 쓸 때는 그 말이 상자 안으로 들어가야 한다. 안 그러면 같은 말이
|
|
288
|
+
* 두 군데(흘러가는 줄 + 상자)에 동시에 뜬다.
|
|
289
|
+
*/
|
|
290
|
+
기다림() { }
|
|
291
|
+
생각(s) {
|
|
292
|
+
// '생각 중… 1,234자' 에서 숫자만 떼어 곁정보로 붙인다. 진짜로 늘어나는
|
|
293
|
+
// 숫자가 하나 있어야 '살아 있다' 가 표시가 아니라 사실이 된다.
|
|
294
|
+
const m = /([\d,]+)자/.exec(String(s ?? ''));
|
|
295
|
+
this.상자.일바꿈('생각', m ? `생각 ${m[1]}자` : null);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* 입력 자리 = 상태줄 + 상자. 커서는 상자 안에 놓인다.
|
|
300
|
+
*
|
|
301
|
+
* 치던 글을 받는다. 입력 도중에 한 줄을 끼워 넣고 다시 그리는 자리
|
|
302
|
+
* (Shift+Tab·Ctrl+O·Ctrl+C)에서, 빈 칸을 그렸다가 곧바로 채우면 한 번
|
|
303
|
+
* 깜빡인다. 처음부터 채워서 그린다 — 한 번만 그리면 안 깜빡인다.
|
|
304
|
+
*/
|
|
305
|
+
입력자리(session, 글 = '', 커서 = 0, 추천 = []) {
|
|
306
|
+
this.임시지움();
|
|
307
|
+
say('');
|
|
308
|
+
this.상자.그리기(session, 글, 커서, null, 추천);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** 사람이 치는 중인 글이 바뀌었다 (repl.js 의 keypress 가 부른다). */
|
|
312
|
+
입력갱신(session, 글, 커서, 추천 = []) { this.상자.그리기(session, 글, 커서, null, 추천); }
|
|
313
|
+
|
|
314
|
+
입력지움() { this.상자.지우기(); }
|
|
315
|
+
|
|
316
|
+
close() {
|
|
317
|
+
this.상자.지우기();
|
|
318
|
+
super.close();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* 상황에 맞는 화면을 하나 고른다.
|
|
324
|
+
*
|
|
325
|
+
* 상자 쪽은 필요할 때만 읽어 들인다. 줄화면으로 돌 때 inputbox.js 를
|
|
326
|
+
* 파싱조차 안 하게 하려는 것이다 — `deel run` 이 조금이라도 빨리 시작해야 한다.
|
|
327
|
+
*/
|
|
328
|
+
export async function 화면고르기(opts = {}) {
|
|
329
|
+
if (!상자쓸까(opts)) return new LineScreen();
|
|
330
|
+
try {
|
|
331
|
+
const { InputBox } = await import('./inputbox.js');
|
|
332
|
+
return new BoxScreen(new InputBox());
|
|
333
|
+
} catch (e) {
|
|
334
|
+
// 상자를 못 세우면 조용히 줄화면으로 간다. 화면 하나 때문에
|
|
335
|
+
// 프로그램이 안 뜨는 일은 없어야 한다.
|
|
336
|
+
const s = new LineScreen();
|
|
337
|
+
s.줄(` ${c.gray(`입력 상자를 못 켰습니다 — 줄 화면으로 갑니다. (${e.message})`)}`);
|
|
338
|
+
return s;
|
|
339
|
+
}
|
|
340
|
+
}
|