pty-shell 1.0.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/dist/index.js ADDED
@@ -0,0 +1,1044 @@
1
+ "use strict";
2
+ /**
3
+ * 有一些子进程 必须要 pty 环境 这里是没有办法的
4
+ * 一个标准的 pty 需要具备以下功能:
5
+ * 输入输出流:处理标准输入、标准输出和标准错误。
6
+ * 环境和工作目录管理:设置子进程的环境变量和工作目录。
7
+ * 终端特性:模拟终端的大小、信号和模式。
8
+ * 子进程管理:启动和管理子进程,处理进程的退出状态。
9
+ * 读取输出与写入输入:捕获输出并发送输入,模拟用户交互。
10
+ * 信号和控制字符支持:处理回车、换行等控制字符,并支持信号转发。
11
+ */
12
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
13
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
14
+ return new (P || (P = Promise))(function (resolve, reject) {
15
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
16
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
17
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
18
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
19
+ });
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.PtyShell = exports.exec_cmd_type = exports.exec_type = void 0;
23
+ const word_detection_js_1 = require("./word_detection_js");
24
+ const path_util_1 = require("./path_util");
25
+ // import {SystemUtil} from "../sys/sys.utl";
26
+ /**
27
+ * 功能说明:
28
+ * 1. 有普通的编辑器功能 对于有子进程执行的时候 会保留子进程的输出 在输出的最后进程编辑
29
+ * 2. 支持编辑的各种移动选择删除插入
30
+ * 3. 支持 ls pwd 等内置命令 , 除了 cd 所有命令都可以自定义
31
+ * 4. 不存在的命令会被用子进程执行
32
+ * 5. 对于特殊的 shell 命令 会使用 node-pty 来执行 并让shell托管所有的输入输出数据
33
+ * 6. 使用了shell: true 参数 系统的默认shell可以支持管道等操作,还可以支持程序路劲查找的功能 但是这样无法支持 | 这样左边命令的校验了, todo 暂时取消这个功能。以后再添加 不能使用原生的shell因为不知道会有什么特殊语法从而跳过命令校验
34
+ */
35
+ const cmd_list = ['ls', 'cd', 'pwd']; // 仅支持这三个内置命令 cd 命令是唯一支持参数的
36
+ /**
37
+ * \r 是回车 光标移动到最右边 \n 是换行,当前位置下一行 和\x1b[1B 作用一样
38
+ * \x1b 是 ESC 后面跟着控制字符
39
+ */
40
+ // [ (左中括号) 这个符号是“CSI”(控制序列介绍符)的开始标志。它用于引入一个更复杂的控制序列,说明接下来的字符是一些终端控制命令的组成部分
41
+ const ctrl_list = [
42
+ '\x1b[A', // 向上箭头 \x1b 是转义字符(ESC) [A 是 ANSI 转义序列中的“上箭头”键的代码
43
+ '\x1b[B', // 向下
44
+ '\x1b[C', // 向右
45
+ '\x1b[D', // 向左
46
+ '\x7F', // del 或者 baskspce 删除换行符
47
+ '\x01', // ctrl + a 全选
48
+ '\x03', // ctrl + c 可以是复制 和 退出
49
+ '\x1b[H', // home
50
+ '\x1b[F', // end
51
+ '\x1b[3~', // delete 删除选中
52
+ '\x1b[1;2D', // 光标向左移动了 一个字符
53
+ '\x1b[1;2C', //  向右移动了 1个字符
54
+ '\x1b[1;2H', // shift + home
55
+ '\x1b[1;2F', // shift + end
56
+ '\x1b[1;5D', // ctrl 向左
57
+ '\x1b[1;5C', // ctrl 向右
58
+ '\x09', // tab
59
+ ];
60
+ const cancel_ctrl_value = '\x1b[0m'; // 取消控制符号的值
61
+ const ctrl_set = new Set(ctrl_list);
62
+ // 命令能不能支持的情况
63
+ var exec_type;
64
+ (function (exec_type) {
65
+ exec_type[exec_type["not"] = -1] = "not";
66
+ exec_type[exec_type["auto_child_process"] = 0] = "auto_child_process";
67
+ exec_type[exec_type["not_pty"] = 1] = "not_pty";
68
+ exec_type[exec_type["continue"] = 2] = "continue";
69
+ })(exec_type || (exports.exec_type = exec_type = {}));
70
+ var exec_cmd_type;
71
+ (function (exec_cmd_type) {
72
+ exec_cmd_type[exec_cmd_type["copy_text"] = 0] = "copy_text";
73
+ })(exec_cmd_type || (exports.exec_cmd_type = exec_cmd_type = {}));
74
+ class PtyShell {
75
+ constructor(param) {
76
+ this.rows = 100; // 这些参数是对某些程序才会有作用的
77
+ this.cols = 100;
78
+ this.cwd = ""; // 当前的cwd
79
+ this.env = {};
80
+ this.on_prompt_call = (cwd) => {
81
+ const str = `${cwd}:# `;
82
+ return { str, char_num: PtyShell.get_full_char_num(str) };
83
+ };
84
+ this.on_call = (data) => {
85
+ };
86
+ this.on_control_cmd = (type, data) => {
87
+ };
88
+ this.cmd_set = new Set(cmd_list);
89
+ this.node_require = {};
90
+ this.not_use_node_pre_cmd_exec = false;
91
+ this.cmd_exec_map = new Map();
92
+ this.is_running = true;
93
+ this.child_now_line = '';
94
+ this.is_pty = false;
95
+ this.line = "";
96
+ this.line_index = -1; // 当前指针在 某个字符(后面)
97
+ this.select_line = "";
98
+ this.select_start = -2;
99
+ this.select_end = -2;
100
+ this.history_line = [];
101
+ this.history_line_index = -1;
102
+ this.next_not_enter = false;
103
+ this.reset_option(param);
104
+ if (!this.not_use_node_pre_cmd_exec) {
105
+ this.node_require.fs = require("fs");
106
+ this.node_require.path = require("path");
107
+ this.node_require.child_process = require('child_process');
108
+ }
109
+ this.on_call(this.raw_prompt);
110
+ }
111
+ // cmd 命令 参数预测 只要是参数都可能是本目录下的文件名所以可以检测一下
112
+ cmd_params_auto_completion(param_str) {
113
+ // 这里的默认实现是开启了使用 node
114
+ if (this.not_use_node_pre_cmd_exec) {
115
+ return;
116
+ }
117
+ const items = this.node_require.fs.readdirSync(this.cwd); // 读取目录内容
118
+ if (!this.word_detection) {
119
+ this.word_detection = new word_detection_js_1.word_detection_js();
120
+ for (const item of items) {
121
+ this.word_detection.add(item);
122
+ }
123
+ }
124
+ let v = this.word_detection.detection_next_one_word(param_str, ".");
125
+ if (v === undefined) {
126
+ // windows的特殊处理判断
127
+ const list = this.word_detection.detection_next_list_word(param_str, ".");
128
+ v = (0, path_util_1.get_best_cmd)(list);
129
+ }
130
+ return v;
131
+ }
132
+ /**
133
+ * public method
134
+ */
135
+ reset_option(param) {
136
+ for (let key of Object.keys(param)) {
137
+ if (key === 'node_pty_shell_list') {
138
+ this.shell_set = new Set(param[key]);
139
+ }
140
+ this[key] = param[key];
141
+ }
142
+ }
143
+ add_cmd_handle(exe_cmd, handle) {
144
+ this.cmd_exec_map.set(exe_cmd, handle);
145
+ this.cmd_set.add(exe_cmd);
146
+ }
147
+ close() {
148
+ this.is_running = false;
149
+ this.close_child(0);
150
+ }
151
+ kill() {
152
+ this.close();
153
+ }
154
+ /**
155
+ * 处理字符串内容工具函数 防止输出的时候 在尾部单词截断
156
+ * @param str
157
+ */
158
+ cols_handle(str) {
159
+ if (!str || str.length <= this.cols)
160
+ return str;
161
+ const max_index = str.length - 1;
162
+ const list = [];
163
+ let last_index = 0; // 上一次位置
164
+ let index = PtyShell.readFullCharIndex(str, 0, this.cols);
165
+ let count = 0;
166
+ while (index < max_index) {
167
+ if (count > max_index)
168
+ break; // 防止错误的一直循环
169
+ count++;
170
+ if (!this.is_empty(str[index]) && (!this.is_empty(str[index - 1]) || !this.is_empty(str[index + 1]))) {
171
+ // 一个单词前后都不是空的
172
+ for (let f = index - 1; f >= last_index; f--) {
173
+ if (this.is_empty(str[f])) {
174
+ list.push(str.substring(last_index, f + 1));
175
+ last_index = f + 1;
176
+ index = f + 1 + PtyShell.readFullCharIndex(str, f + 1, this.cols);
177
+ continue;
178
+ }
179
+ if (f === last_index) {
180
+ // 最后一位 直接把本行全部添加进去
181
+ list.push(str.substring(last_index, index));
182
+ last_index = index;
183
+ index = index + PtyShell.readFullCharIndex(str, index, this.cols);
184
+ }
185
+ }
186
+ }
187
+ else {
188
+ list.push(str.substring(last_index, index));
189
+ last_index = index;
190
+ index = index + PtyShell.readFullCharIndex(str, index + 1, this.cols);
191
+ }
192
+ }
193
+ if (index >= max_index) {
194
+ list.push(str.substring(last_index));
195
+ }
196
+ return list.join('\n\r');
197
+ }
198
+ /**
199
+ * 向pty写入数据
200
+ * @param data
201
+ */
202
+ write(data) {
203
+ return __awaiter(this, void 0, void 0, function* () {
204
+ if (this.child && this.is_pty) {
205
+ // 终端shell 完全 托管给 别的程序
206
+ this.spawn_write(data);
207
+ return;
208
+ }
209
+ if (ctrl_set.has(data)) {
210
+ // 不改变 编辑器的 指针
211
+ this.ctrl_exec(data);
212
+ return;
213
+ }
214
+ else if (data.startsWith('\x1b')) {
215
+ // 是控制字符但是没有 对应的处理删除
216
+ return;
217
+ }
218
+ if (this.select_line) {
219
+ // 如果有选中就应该先删除
220
+ this.ctrl_exec('\x1b[3~'); // 删除
221
+ }
222
+ // 插入数据
223
+ let enter_index = this.get_enter_index(data); // 从换行符开始截取一部分插入
224
+ if (this.is_line_end) {
225
+ // 在最后插入
226
+ if (enter_index !== -1) {
227
+ // 有换行
228
+ if (data.length > 1) {
229
+ // 不是单个的换行符 但是包含换行
230
+ yield this.multiple_line(data, enter_index);
231
+ }
232
+ else {
233
+ yield this.parse_exec();
234
+ }
235
+ }
236
+ else {
237
+ this.on_call(data);
238
+ this.line += data;
239
+ this.line_index += data.length;
240
+ }
241
+ }
242
+ else {
243
+ // 在某个地方插入
244
+ if (enter_index === -1) {
245
+ // 还没有换行 只是插入
246
+ this.insert_line(data);
247
+ }
248
+ else {
249
+ yield this.multiple_line(data, enter_index);
250
+ }
251
+ }
252
+ });
253
+ }
254
+ /**
255
+ * static method
256
+ */
257
+ // 判断一个字符是全角还是半角
258
+ static isFullCharWidth(char) {
259
+ // 计算字符的 UTF-8 编码字节长度
260
+ const byteLength = Buffer.byteLength(char, 'utf8');
261
+ // 如果字符的字节长度大于 1,说明是全角字符
262
+ return byteLength > 1;
263
+ }
264
+ // 从start_index往前多少个位置获取指定数量的 半角 字符(宽字符算两个)
265
+ static readFullCharIndex(str, start_index, len) {
266
+ if (!str)
267
+ return 0;
268
+ if (start_index >= str.length)
269
+ return 0;
270
+ let num = 0;
271
+ let char_num = 0;
272
+ for (let i = start_index; i < str.length; i++) {
273
+ if (this.isFullCharWidth(str[i])) {
274
+ num += 2;
275
+ }
276
+ else {
277
+ num++;
278
+ }
279
+ char_num++;
280
+ if (num >= len)
281
+ return char_num;
282
+ }
283
+ return char_num;
284
+ }
285
+ // 获取字符串中有多少个 字符(将宽字符统计成两个)
286
+ static get_full_char_num(str) {
287
+ if (!str)
288
+ return 0;
289
+ let char_num = 0;
290
+ for (let i = 0; i < str.length; i++) {
291
+ if (this.isFullCharWidth(str[i])) {
292
+ char_num += 2;
293
+ }
294
+ else {
295
+ char_num++;
296
+ }
297
+ }
298
+ return char_num;
299
+ }
300
+ /**
301
+ * private method
302
+ */
303
+ get raw_prompt() {
304
+ const { str, char_num } = this.on_prompt_call(this.cwd);
305
+ this.prompt_call_len = char_num;
306
+ return str;
307
+ }
308
+ get enter_prompt() {
309
+ return `\n\r${this.raw_prompt}`;
310
+ }
311
+ clear_line() {
312
+ this.line_index = -1;
313
+ this.line = "";
314
+ }
315
+ // 光标是不是在尾部
316
+ get is_line_end() {
317
+ return this.line_index + 1 === this.line.length;
318
+ }
319
+ insert_line(str) {
320
+ // 在某个地方插入
321
+ let arr = this.line.split('');
322
+ if (this.line_index === -1) {
323
+ arr = [str, ...arr];
324
+ }
325
+ else {
326
+ arr.splice(this.line_index + 1, 0, str);
327
+ }
328
+ this.line = arr.join('');
329
+ this.line_index += str.length;
330
+ this.update_line({ line_add_num: 1 });
331
+ }
332
+ close_child(code) {
333
+ this.child_now_line = '';
334
+ if (this.child) {
335
+ // SystemUtil.killProcess(this.child.pid);
336
+ const pid = this.child.pid;
337
+ this.child.kill(); // 不同平台信号不同 win 默认 SIGHUP
338
+ this.exec_end_call(code, pid);
339
+ this.child = undefined;
340
+ }
341
+ }
342
+ send_and_enter(str, send_prompt = false) {
343
+ try {
344
+ if (typeof str == "string") {
345
+ const list = [];
346
+ let i = 0;
347
+ const last_i = str.length - 1;
348
+ for (let j = 0; j < str.length; j++) {
349
+ if (j < last_i) {
350
+ const v = `${str[j]}${str[j + 1]}`;
351
+ if (v === '\n\r' || v === '\r\n') {
352
+ j++; // 多跳一个字符
353
+ continue;
354
+ }
355
+ }
356
+ if (str[j] === '\r' || str[j] === '\n') {
357
+ list.push(str.substring(i, j));
358
+ i = j + 1;
359
+ }
360
+ }
361
+ if (i === 0 || i !== last_i)
362
+ list.push(str.substring(i));
363
+ if (this.child) {
364
+ // 添加子进程的提示换行
365
+ this.child_now_line = list[list.length - 1];
366
+ }
367
+ str = list.join('\n\r'); // 把所有的回车替换一下换行
368
+ }
369
+ if (str) {
370
+ if (this.next_not_enter) {
371
+ this.on_call(`${str}`); // 在下一行输出
372
+ }
373
+ else {
374
+ this.on_call(`\n\r${str}`); // 在下一行输出
375
+ }
376
+ this.next_not_enter = str.endsWith('\n\r') || str.endsWith('\r\n'); // 下一次不用换行了
377
+ }
378
+ if (!this.child || send_prompt) {
379
+ this.on_call(`${this.enter_prompt}`);
380
+ }
381
+ this.clear_line();
382
+ }
383
+ catch (e) {
384
+ console.log(e);
385
+ }
386
+ }
387
+ // 重新更新显示本行 也许可以更节省的更新 文本 powershell 这样的每次都是全部更新 暂时和他一样
388
+ update_line(param) {
389
+ var _a, _b;
390
+ const prompt = !this.child ? this.raw_prompt : this.child_now_line;
391
+ let len = (!this.child ? this.prompt_call_len : PtyShell.get_full_char_num(prompt)) + this.line_char_index; // 字符串前面的字符数量
392
+ if (param && param.line_add_num) {
393
+ len += param.line_add_num;
394
+ }
395
+ else if (param && param.line_reduce_num) {
396
+ len -= param.line_reduce_num;
397
+ }
398
+ const line = (_a = param === null || param === void 0 ? void 0 : param.p_line) !== null && _a !== void 0 ? _a : this.line;
399
+ const cancel_ctrl = (param === null || param === void 0 ? void 0 : param.all_line_ctrl) !== undefined || (param === null || param === void 0 ? void 0 : param.p_line) !== undefined ? cancel_ctrl_value : "";
400
+ const updateLineString = `\x1b[?25l\r\x1b[0K${prompt}${(_b = param === null || param === void 0 ? void 0 : param.all_line_ctrl) !== null && _b !== void 0 ? _b : line}\x1b[${len}G\x1b[?25h${cancel_ctrl}`;
401
+ /**
402
+ * \x1b[?25l: 隐藏光标
403
+ * \r: 回车,移动光标到行首
404
+ * \x1b[0K: 清空当前行光标右侧的内容
405
+ * \x1b[21G: 将光标移动到当前行的第 21 列
406
+ * \x1b[?25h: 显示光标
407
+ * \x1b[0m 是后面的颜色重置 不要影响前面的
408
+ */
409
+ this.on_call(updateLineString);
410
+ }
411
+ cancel_selected() {
412
+ this.select_line = '';
413
+ this.select_start = -2;
414
+ this.select_end = -2;
415
+ }
416
+ get line_char_index() {
417
+ if (this.line_index === -1)
418
+ return 0;
419
+ return PtyShell.get_full_char_num(this.line.substring(0, this.line_index + 1));
420
+ }
421
+ ctrl_exec(str) {
422
+ let cancel_selected = true; // 取消选中
423
+ switch (str) {
424
+ case '\x09':
425
+ {
426
+ // 按下 tab按键
427
+ if (this.line_index < 0 ||
428
+ this.is_empty(this.line[this.line_index]) || // 字符位置是空的
429
+ (!this.is_empty(this.line[this.line_index]) && !this.is_empty(this.line[this.line_index + 1]))) { // 自己和前面都不是空的(数组过节返回的也是空也可以)
430
+ // 不符合条件的不做命令自动补充
431
+ break;
432
+ }
433
+ const { word, is_exe } = this.get_last_word_cmd_or_param(this.line, this.line_index);
434
+ if (word) {
435
+ let word_d;
436
+ if (is_exe && this.cmd_exe_auto_completion !== undefined) {
437
+ word_d = this.cmd_exe_auto_completion(word);
438
+ }
439
+ else {
440
+ word_d = this.cmd_params_auto_completion(word); // 不可用或者没有实现会空
441
+ }
442
+ if (word_d !== undefined && word_d !== word) {
443
+ this.line = this.line.substring(0, this.line_index - word.length + 1) + word_d + this.line.substring(this.line_index + 1);
444
+ this.line_index = this.line_index - word.length + word_d.length;
445
+ this.update_line({ line_add_num: 1 });
446
+ }
447
+ }
448
+ }
449
+ break;
450
+ case "\x1b[A":
451
+ {
452
+ // 向上
453
+ const index = this.history_line_index === -1 ? this.history_line.length - 1 : this.history_line_index - 1;
454
+ if (index >= 0) {
455
+ this.line = this.history_line[index];
456
+ this.history_line_index = index;
457
+ }
458
+ this.line_index = this.line.length - 1;
459
+ this.update_line({ line_add_num: 1 });
460
+ }
461
+ break;
462
+ case "\x1b[B":
463
+ {
464
+ // 向下
465
+ const index = this.history_line_index + 1;
466
+ if (index < this.history_line.length) {
467
+ this.line = this.history_line[index];
468
+ this.history_line_index++;
469
+ }
470
+ this.line_index = this.line.length - 1;
471
+ this.update_line({ line_add_num: 1 });
472
+ }
473
+ break;
474
+ case "\x1b[C":
475
+ {
476
+ // 向右
477
+ if (this.line_index >= this.line.length - 1) {
478
+ break;
479
+ }
480
+ this.line_index++;
481
+ const len = PtyShell.get_full_char_num(this.line[this.line_index]);
482
+ if (this.select_line) {
483
+ this.update_line({ line_add_num: 1 });
484
+ break;
485
+ }
486
+ this.on_call(`\x1b[${len}C`);
487
+ }
488
+ break;
489
+ case "\x1b[D":
490
+ {
491
+ // 向左
492
+ if (this.line_index === -1) {
493
+ break;
494
+ }
495
+ const len = PtyShell.get_full_char_num(this.line[this.line_index]);
496
+ this.line_index--;
497
+ if (this.select_line) {
498
+ this.update_line({ line_add_num: 1 });
499
+ break;
500
+ }
501
+ this.on_call(`\x1b[${len}D`);
502
+ }
503
+ break;
504
+ case '\x7F':
505
+ {
506
+ // 删除左侧 一个字符 backspace
507
+ if (this.select_line) {
508
+ this.ctrl_exec('\x1b[3~'); // 删除
509
+ break;
510
+ }
511
+ if (this.line_index === -1) {
512
+ break;
513
+ }
514
+ this.line = this.removeCharacterAt(this.line, this.line_index);
515
+ this.line_index--;
516
+ this.update_line({ line_add_num: 1 });
517
+ }
518
+ break;
519
+ case '\x1b[3~':
520
+ {
521
+ // delete 删除 选中
522
+ if (!this.select_line) {
523
+ break;
524
+ }
525
+ this.line = this.line.substring(0, this.select_start + 1) + this.line.substring(this.select_end + 1);
526
+ this.line_index = this.select_start;
527
+ this.update_line({ line_add_num: 1 });
528
+ }
529
+ break;
530
+ case '\x01':
531
+ {
532
+ // ctrl + a 全选
533
+ this.update_line({ all_line_ctrl: `\x1b[48;5;252m${this.line}`, line_add_num: 1 }); // 一行置灰 235 开始就是灰色 值越大灰度越轻
534
+ this.select_line = this.line;
535
+ this.select_start = -1;
536
+ this.select_end = this.line.length - 1;
537
+ cancel_selected = false;
538
+ }
539
+ break;
540
+ case '\x03':
541
+ {
542
+ // ctrl + c 复制 或者 结束 子进程
543
+ if (this.select_line) {
544
+ this.on_control_cmd(exec_cmd_type.copy_text, this.select_line);
545
+ this.cancel_selected();
546
+ this.update_line({ line_add_num: 1 });
547
+ }
548
+ else if (this.child) {
549
+ this.close_child(0);
550
+ return;
551
+ }
552
+ }
553
+ break;
554
+ case '\x1b[F':
555
+ {
556
+ // end
557
+ this.line_index = this.line.length - 1;
558
+ this.update_line({ line_add_num: 1 });
559
+ }
560
+ break;
561
+ case '\x1b[H':
562
+ {
563
+ // home
564
+ this.line_index = -1;
565
+ this.update_line({ line_add_num: 1 });
566
+ }
567
+ break;
568
+ case '\x1b[1;2D':
569
+ {
570
+ // 按住 shift 向左移动一个字符 目前只能移动一个
571
+ if (this.select_start === -2 || this.select_start === this.line_index) {
572
+ const index = this.line_index - 1;
573
+ if (index >= -1) {
574
+ if (this.select_end === -2) {
575
+ this.select_end = this.line_index;
576
+ }
577
+ // 第一次移动 或者 移动的唯一已经选中 往右移动一次
578
+ this.line_index = index;
579
+ this.select_start = index;
580
+ }
581
+ }
582
+ else if (this.select_end === this.line_index) {
583
+ // 之前是往右选中 现在往左退回
584
+ const index = this.line_index - 1;
585
+ if (index >= -1) {
586
+ // 第一次移动 或者 移动的唯一已经选中 往右移动一次
587
+ this.line_index = index;
588
+ this.select_end = index;
589
+ }
590
+ }
591
+ this.select_line = this.line.substring(this.select_start + 1, this.select_end + 1);
592
+ this.update_line({
593
+ p_line: this.line.substring(0, this.select_start + 1) + `\x1b[48;5;252m${this.select_line}` + cancel_ctrl_value + this.line.substring(this.select_end + 1),
594
+ line_add_num: 1
595
+ }); // 一行置灰 235 开始就是灰色 值越大灰度越轻
596
+ cancel_selected = false;
597
+ }
598
+ break;
599
+ case '\x1b[1;2C':
600
+ {
601
+ // 按住 shift 向右移动一个
602
+ if (this.select_end === -2 || this.select_end === this.line_index) {
603
+ const index = this.line_index + 1;
604
+ if (index <= this.line.length - 1) {
605
+ if (this.select_start === -2) {
606
+ this.select_start = this.line_index;
607
+ }
608
+ // 第一次移动 或者 移动的唯一已经选中 往右移动一次
609
+ this.line_index = index;
610
+ this.select_end = index;
611
+ }
612
+ }
613
+ else if (this.select_start === this.line_index) {
614
+ const index = this.line_index + 1;
615
+ // 之前是往左选中 现在往右退回
616
+ this.line_index = index;
617
+ this.select_start = index;
618
+ }
619
+ this.select_line = this.line.substring(this.select_start + 1, this.select_end + 1);
620
+ this.update_line({
621
+ p_line: this.line.substring(0, this.select_start + 1) + `\x1b[48;5;252m${this.select_line}` + cancel_ctrl_value + this.line.substring(this.select_end + 1),
622
+ line_add_num: 1
623
+ }); // 一行置灰 235 开始就是灰色 值越大灰度越轻
624
+ cancel_selected = false;
625
+ }
626
+ break;
627
+ case '\x1b[1;2H':
628
+ {
629
+ // shift home
630
+ if (this.select_end === -2) {
631
+ this.select_end = this.line_index;
632
+ }
633
+ this.select_start = -1;
634
+ this.line_index = -1;
635
+ this.select_line = this.line.substring(this.select_start + 1, this.select_end + 1);
636
+ this.update_line({
637
+ p_line: this.line.substring(0, this.select_start + 1) + `\x1b[48;5;252m${this.select_line}` + cancel_ctrl_value + this.line.substring(this.select_end + 1),
638
+ line_add_num: 1
639
+ }); // 一行置灰 235 开始就是灰色 值越大灰度越轻
640
+ cancel_selected = false;
641
+ }
642
+ break;
643
+ case '\x1b[1;2F':
644
+ {
645
+ // shift end
646
+ if (this.select_start === -2) {
647
+ this.select_start = this.line_index;
648
+ }
649
+ this.line_index = this.line.length - 1;
650
+ this.select_end = this.line.length - 1;
651
+ this.select_line = this.line.substring(this.select_start + 1, this.select_end + 1);
652
+ this.update_line({
653
+ p_line: this.line.substring(0, this.select_start + 1) + `\x1b[48;5;252m${this.select_line}` + cancel_ctrl_value + this.line.substring(this.select_end + 1),
654
+ line_add_num: 1
655
+ }); // 一行置灰 235 开始就是灰色 值越大灰度越轻
656
+ cancel_selected = false;
657
+ }
658
+ break;
659
+ case '\x1b[1;5D':
660
+ {
661
+ // ctrl 向左
662
+ if (this.line_index === -1)
663
+ return;
664
+ const h = this.is_empty(this.line[this.line_index]);
665
+ for (let i = this.line_index; i >= 0; i--) {
666
+ if (this.is_empty(this.line[i]) !== h) {
667
+ this.line_index = i;
668
+ break;
669
+ }
670
+ else if (i === 0) {
671
+ this.line_index = -1;
672
+ }
673
+ }
674
+ this.update_line({ line_add_num: 1 });
675
+ }
676
+ break;
677
+ case '\x1b[1;5C':
678
+ {
679
+ // ctrl 向右
680
+ if (this.line_index === this.line.length - 1)
681
+ return;
682
+ const h = this.is_empty(this.line[this.line_index + 1]);
683
+ const max = this.line.length - 1;
684
+ for (let i = this.line_index; i <= max; i++) {
685
+ if (this.is_empty(this.line[i + 1]) !== h || i === max) {
686
+ this.line_index = i;
687
+ break;
688
+ }
689
+ }
690
+ this.update_line({ line_add_num: 1 });
691
+ }
692
+ break;
693
+ // default:{
694
+ // if(str.startsWith('\x1b')) {
695
+ // // esc控制信号序列
696
+ // switch(str[2]) {
697
+ // case '1':
698
+ // }
699
+ // }
700
+ // }
701
+ }
702
+ if (cancel_selected)
703
+ this.cancel_selected();
704
+ }
705
+ push_history_line(line) {
706
+ if (this.history_line[this.history_line.length - 1] === line || !line) {
707
+ this.history_line_index = -1;
708
+ return; // 和最后的一样就不插入了
709
+ }
710
+ this.history_line.push(line);
711
+ if (this.history_line.length > 20) {
712
+ this.history_line.shift(); // 删除最前面的
713
+ }
714
+ this.history_line_index = -1;
715
+ }
716
+ exec_end_call(code, pid) {
717
+ if (this.on_child_kill)
718
+ this.on_child_kill(code, pid); // 也发送一下;
719
+ }
720
+ // 解析和执行命令 执行完会自动换行的
721
+ parse_exec() {
722
+ return __awaiter(this, void 0, void 0, function* () {
723
+ // const line = this.delete_all_enter(this.line);
724
+ if (!this.line && !this.child) {
725
+ this.send_and_enter("");
726
+ this.clear_line();
727
+ return;
728
+ }
729
+ this.push_history_line(this.line);
730
+ if (this.child && this.is_pty) {
731
+ // 终端shell 完全 托管给 别的程序
732
+ this.spawn_write(`${this.line}\r`);
733
+ return;
734
+ }
735
+ else if (this.child) {
736
+ // 把数据给正在运行的别的程序
737
+ this.spawn_write(`${this.line}\n`);
738
+ this.clear_line();
739
+ return;
740
+ }
741
+ const { exe, params } = this.get_exec(this.line);
742
+ this.history_line_index = -1;
743
+ try {
744
+ let use_noe_pty = false;
745
+ if (this.check_exe_cmd) {
746
+ // 检查外部自定义的 是否能执行某个命令
747
+ const v = yield this.check_exe_cmd(exe, params);
748
+ switch (v) {
749
+ case exec_type.not:
750
+ this.send_and_enter(`not have permission to execute ${exe}`);
751
+ this.clear_line();
752
+ this.exec_end_call(-1);
753
+ return;
754
+ case exec_type.auto_child_process:
755
+ break;
756
+ case exec_type.not_pty:
757
+ use_noe_pty = true;
758
+ break;
759
+ default:
760
+ // 未知的不报错也不执行
761
+ this.exec_end_call(0);
762
+ return;
763
+ }
764
+ }
765
+ if (this.cmd_set.has(exe)) {
766
+ // 检测某个已经有预处理的命令 包括用户自定义的
767
+ if (this.exec_cmd(exe, params)) {
768
+ // 成功执行了不用再继续了
769
+ this.clear_line();
770
+ this.exec_end_call(0);
771
+ return;
772
+ }
773
+ }
774
+ this.spawn(exe, params, use_noe_pty);
775
+ this.clear_line();
776
+ }
777
+ catch (e) {
778
+ // console.log("子线程执行异常", e);
779
+ this.send_and_enter(e.message);
780
+ this.exec_end_call(-1);
781
+ }
782
+ });
783
+ }
784
+ multiple_line(data, enter_index) {
785
+ return __awaiter(this, void 0, void 0, function* () {
786
+ if (!data)
787
+ return;
788
+ let num = 0; // 防止解析失败死循环
789
+ while (enter_index > -1 && this.is_running && num < 1000) {
790
+ // 如果有剩余一直插入
791
+ // 不要最后的回车符号
792
+ this.insert_line(data.substring(0, enter_index));
793
+ // 开始解析执行 执行完应该清理行数据
794
+ yield this.parse_exec();
795
+ // 下一次
796
+ data = data.substring(enter_index + 1);
797
+ enter_index = this.get_enter_index(data);
798
+ num++;
799
+ }
800
+ if (data !== "") {
801
+ // 剩余的还没有换行符
802
+ this.insert_line(data);
803
+ }
804
+ });
805
+ }
806
+ spawn_write(str) {
807
+ if (this.is_pty) {
808
+ this.child.write(str);
809
+ }
810
+ else {
811
+ this.child.stdin.write(str);
812
+ }
813
+ }
814
+ spawn(exe, params, use_noe_pty = true, spawn_option) {
815
+ if (this.not_use_node_pre_cmd_exec) {
816
+ this.send_and_enter(`not_use_node_pre_cmd_exec is true`);
817
+ return;
818
+ }
819
+ // this.send_and_enter(""); //
820
+ // if (!this.child) {
821
+ // this.on_call(`\n\r`); // 先换个行
822
+ // }
823
+ if ((use_noe_pty || this.shell_set.has("*") || this.shell_set.has(exe)) && this.node_pty) {
824
+ // if (!exe.includes('exe') && exe !== 'bash' && exe !== 'sh') {
825
+ // exe += '.exe';
826
+ // }
827
+ this.on_call(`\n\r`); // 先换个行
828
+ this.child = this.node_pty.spawn(exe, params, Object.assign({ name: 'xterm-color', cols: this.cols, rows: this.rows, cwd: this.cwd, useConptyDll: false, useConpty: process.env.NODE_ENV !== "production" ? false : undefined, env: Object.assign(Object.assign({}, process.env), this.env) }, spawn_option));
829
+ this.child.onData((data) => {
830
+ this.on_call(data.toString());
831
+ });
832
+ this.child.onExit(({ exitCode, signal }) => {
833
+ this.close_child(exitCode);
834
+ this.send_and_enter("");
835
+ // this.send_and_enter(`pty with ${exitCode}`);
836
+ this.next_not_enter = false; // 下一次的换行输出 上一次没有换行
837
+ });
838
+ this.is_pty = true;
839
+ }
840
+ else {
841
+ this.is_pty = false;
842
+ // 其他的没有必要再创建一个 tty 都是资源消耗
843
+ this.child = this.node_require.child_process.spawn(exe, params, Object.assign({
844
+ // shell:getShell(),
845
+ cwd: this.cwd, env: Object.assign(Object.assign(Object.assign({}, process.env), this.env), { LANG: 'en_US.UTF-8' }),
846
+ // stdio: 'inherit' // 让子进程的输入输出与父进程共享 pipe ignore inherit
847
+ // timeout: 5000, // 设置超时为 5 秒
848
+ maxBuffer: 1024 * 1024 * 10 }, spawn_option));
849
+ // 设置编码为 'utf8',确保输出按 UTF-8 编码解析
850
+ this.child.stdout.setEncoding('utf8');
851
+ this.child.stdout.on('data', (data) => {
852
+ // const v = data.toString(); // 子程序没有换行等符号不会立即输出 有缓冲区
853
+ this.send_and_enter(data.toString());
854
+ });
855
+ this.child.stderr.on('data', (data) => {
856
+ // const v = data.toString();
857
+ this.send_and_enter(data.toString());
858
+ this.next_not_enter = false; // 下一次的换行输出 上一次没有换行
859
+ });
860
+ this.child.on('exit', (code) => {
861
+ this.close_child(code);
862
+ // if (code !== 1) {
863
+ // this.send_and_enter(`process exited with code ${code}`);
864
+ // } else {
865
+ this.send_and_enter(``);
866
+ // }
867
+ this.next_not_enter = false; // 下一次的换行输出 上一次没有换行
868
+ });
869
+ this.child.on('error', (error) => {
870
+ this.next_not_enter = false; // 下一次的换行输出 上一次没有换行
871
+ this.close_child(-1);
872
+ this.send_and_enter(error.message);
873
+ });
874
+ }
875
+ }
876
+ exec_cmd(exe, params) {
877
+ try {
878
+ const handle = this.cmd_exec_map.get(exe);
879
+ if (exe !== 'cd' && handle) {
880
+ // 如果用户有了就用用户的 不用系统自己的 但是 cd 命令排除在外
881
+ handle(params, (data) => {
882
+ this.send_and_enter(data, true);
883
+ });
884
+ return true;
885
+ }
886
+ switch (exe) {
887
+ case 'pwd':
888
+ {
889
+ this.send_and_enter(`${this.cwd}`);
890
+ }
891
+ return true;
892
+ case 'cd':
893
+ {
894
+ let p;
895
+ if (!this.not_use_node_pre_cmd_exec) {
896
+ // 有node环境可以检测一下
897
+ p = this.node_require.path.isAbsolute(params[0]) ? params[0] : this.node_require.path.join(this.cwd, params[0]);
898
+ if (!this.node_require.fs.existsSync(p)) {
899
+ this.send_and_enter(`not directory ${p}`);
900
+ }
901
+ }
902
+ else {
903
+ // 没有node环境只能这样了 todo 对于 .. 路径有问题
904
+ p = (0, path_util_1.path_join)(this.cwd, params[0]);
905
+ }
906
+ this.cwd = p;
907
+ this.send_and_enter(``);
908
+ this.word_detection = undefined; // 清空检测器
909
+ }
910
+ return true;
911
+ case 'ls':
912
+ {
913
+ if (this.not_use_node_pre_cmd_exec) {
914
+ return false; // 让其它方式处理
915
+ }
916
+ const items = this.node_require.fs.readdirSync(this.cwd); // 读取目录内容
917
+ const v = this.cols_handle(" " + items.join(" "));
918
+ this.send_and_enter(v);
919
+ }
920
+ return true;
921
+ default:
922
+ return false; // 让其它方式处理
923
+ }
924
+ }
925
+ catch (e) {
926
+ this.send_and_enter(JSON.stringify(e));
927
+ }
928
+ return false; // 让其它方式处理
929
+ }
930
+ // 解析命令与参数
931
+ get_exec(str) {
932
+ let exe = "", params = [];
933
+ if (!str) {
934
+ return { exe, params };
935
+ }
936
+ let start = -1;
937
+ let mate = 0;
938
+ for (let i = 0; i < str.length; i++) {
939
+ let is_empty = this.is_empty(str[i]);
940
+ if (mate % 2 !== 0) {
941
+ // 已经有特殊字符串开始
942
+ is_empty = false;
943
+ }
944
+ if (start === -1 && !is_empty) {
945
+ // 开始 没有开始 且有非空字符
946
+ start = i;
947
+ if (str[i] === '\'' || str[i] === '"') {
948
+ mate = 1;
949
+ }
950
+ }
951
+ else if (start !== -1 && (is_empty || i === str.length - 1)) {
952
+ // 结束 已经开始且遇上空白字符 或者结尾
953
+ const i_p = (i === str.length - 1 && !is_empty) ? str.length : i;
954
+ if (exe === "") {
955
+ exe = str.substring(start, i_p);
956
+ mate = 0;
957
+ }
958
+ else {
959
+ if (mate === 0) {
960
+ // 没有 " ' 括起来的特殊字符串
961
+ params.push(str.substring(start, i_p));
962
+ }
963
+ else {
964
+ mate = 0;
965
+ params.push(str.substring(start + 1, i_p - 1));
966
+ }
967
+ }
968
+ start = -1;
969
+ }
970
+ else if (mate !== 0 && (str[i] === '\'' || str[i] === '"')) {
971
+ // 不是结束也不是开始
972
+ mate++;
973
+ }
974
+ }
975
+ if (start !== -1) {
976
+ params.push(str.substring(start));
977
+ }
978
+ return { exe, params };
979
+ }
980
+ get_last_word_cmd_or_param(line, now_index) {
981
+ // 获取当前命令 当前位置 往前的一个单词
982
+ let word = "", is_exe = true;
983
+ if (!!line && !!line[now_index]) {
984
+ // 字符串不能是空的 且 当前位置也不能是空的
985
+ for (let i = now_index; i >= 0; i--) {
986
+ if (!this.is_empty(line[i])) {
987
+ word = line[i] + word;
988
+ }
989
+ else {
990
+ // 判断前面是否还有字符
991
+ for (let j = i; j >= 0; j--) {
992
+ if (line[j]) {
993
+ is_exe = false;
994
+ break;
995
+ }
996
+ }
997
+ break;
998
+ }
999
+ }
1000
+ }
1001
+ return { word, is_exe };
1002
+ }
1003
+ is_empty(str) {
1004
+ // 判断是否为 null 或 undefined
1005
+ if (str === null || str === undefined) {
1006
+ return true;
1007
+ }
1008
+ // 判断是否为空字符串或仅包含空白字符(空格、换行符、制表符等) 对于 \b 这样的字符不能判断为空 powershell也是
1009
+ return str.trim() === '';
1010
+ }
1011
+ get_enter_index(str) {
1012
+ if (!str) {
1013
+ return -1;
1014
+ }
1015
+ for (let i = 0; i < str.length; i++) {
1016
+ if (str[i] == '\r') {
1017
+ return i;
1018
+ }
1019
+ }
1020
+ return -1;
1021
+ }
1022
+ // 将 \r 替换成 \n\r
1023
+ get_enter_line(str, index) {
1024
+ if (!str) {
1025
+ return str;
1026
+ }
1027
+ return str.substring(0, index) + '\n\r';
1028
+ }
1029
+ // 删除自定义位置的字符
1030
+ removeCharacterAt(str, index) {
1031
+ let arr = str.split('');
1032
+ arr.splice(index, 1); // 删除指定位置的字符
1033
+ return arr.join('');
1034
+ }
1035
+ // 删除所有换行符号
1036
+ delete_all_enter(str) {
1037
+ if (!str) {
1038
+ return str;
1039
+ }
1040
+ const arr = str.split('');
1041
+ return arr.filter(v => v !== '\r' && v !== '\n').join('');
1042
+ }
1043
+ }
1044
+ exports.PtyShell = PtyShell;