pty-shell
Version:
a virtual PTY shell for javaScript
1,059 lines (1,058 loc) • 44.1 kB
JavaScript
"use strict";
/**
* 有一些子进程 必须要 pty 环境 这里是没有办法的
* 一个标准的 pty 需要具备以下功能:
* 输入输出流:处理标准输入、标准输出和标准错误。
* 环境和工作目录管理:设置子进程的环境变量和工作目录。
* 终端特性:模拟终端的大小、信号和模式。
* 子进程管理:启动和管理子进程,处理进程的退出状态。
* 读取输出与写入输入:捕获输出并发送输入,模拟用户交互。
* 信号和控制字符支持:处理回车、换行等控制字符,并支持信号转发。
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PtyShell = exports.exec_cmd_type = exports.exec_type = void 0;
const word_detection_js_1 = require("./word_detection_js");
const path_util_1 = require("./path_util");
// import {SystemUtil} from "../sys/sys.utl";
/**
* 功能说明:
* 1. 有普通的编辑器功能 对于有子进程执行的时候 会保留子进程的输出 在输出的最后进程编辑
* 2. 支持编辑的各种移动选择删除插入
* 3. 支持 ls pwd 等内置命令 , 除了 cd 所有命令都可以自定义
* 4. 不存在的命令会被用子进程执行
* 5. 对于特殊的 shell 命令 会使用 node-pty 来执行 并让shell托管所有的输入输出数据
* 6. 使用了shell: true 参数 系统的默认shell可以支持管道等操作,还可以支持程序路劲查找的功能 但是这样无法支持 | 这样左边命令的校验了, todo 暂时取消这个功能。以后再添加 不能使用原生的shell因为不知道会有什么特殊语法从而跳过命令校验
*/
const cmd_list = ['ls', 'cd', 'pwd']; // 仅支持这三个内置命令 cd 命令是唯一支持参数的
/**
* \r 是回车 光标移动到最右边 \n 是换行,当前位置下一行 和\x1b[1B 作用一样
* \x1b 是 ESC 后面跟着控制字符
*/
// [ (左中括号) 这个符号是“CSI”(控制序列介绍符)的开始标志。它用于引入一个更复杂的控制序列,说明接下来的字符是一些终端控制命令的组成部分
const ctrl_list = [
'\x1b[A', // 向上箭头 \x1b 是转义字符(ESC) [A 是 ANSI 转义序列中的“上箭头”键的代码
'\x1b[B', // 向下
'\x1b[C', // 向右
'\x1b[D', // 向左
'\x7F', // del 或者 baskspce 删除换行符
'\x01', // ctrl + a 全选
'\x03', // ctrl + c 可以是复制 和 退出
'\x1b[H', // home
'\x1b[F', // end
'\x1b[3~', // delete 删除选中
'\x1b[1;2D', // 光标向左移动了 一个字符
'\x1b[1;2C', // [1;2C 向右移动了 1个字符
'\x1b[1;2H', // shift + home
'\x1b[1;2F', // shift + end
'\x1b[1;5D', // ctrl 向左
'\x1b[1;5C', // ctrl 向右
'\x09', // tab
];
const cancel_ctrl_value = '\x1b[0m'; // 取消控制符号的值
const ctrl_set = new Set(ctrl_list);
// 命令能不能支持的情况
var exec_type;
(function (exec_type) {
exec_type[exec_type["not"] = -1] = "not";
exec_type[exec_type["auto_child_process"] = 0] = "auto_child_process";
exec_type[exec_type["not_pty"] = 1] = "not_pty";
exec_type[exec_type["continue"] = 2] = "continue";
})(exec_type || (exports.exec_type = exec_type = {}));
var exec_cmd_type;
(function (exec_cmd_type) {
exec_cmd_type[exec_cmd_type["copy_text"] = 0] = "copy_text";
})(exec_cmd_type || (exports.exec_cmd_type = exec_cmd_type = {}));
class PtyShell {
constructor(param) {
this.rows = 100; // 这些参数是对某些程序才会有作用的
this.cols = 100;
this.cwd = ""; // 当前的cwd
this.env = {};
this.on_prompt_call = (cwd) => {
const str = `${cwd}:# `;
return { str, char_num: PtyShell.get_full_char_num(str) };
};
this.on_call = (data) => {
};
this.on_control_cmd = (type, data) => {
};
this.cmd_set = new Set(cmd_list);
this.node_require = {};
this.not_use_node_pre_cmd_exec = false;
this.cmd_exec_map = new Map();
this.is_running = true;
this.child_now_line = '';
this.is_pty = false;
this.line = "";
this.line_index = -1; // 当前指针在 某个字符(后面)
this.select_line = "";
this.select_start = -2;
this.select_end = -2;
this.history_line = [];
this.history_line_index = -1;
this.history_line_max = 20;
this.next_not_enter = false;
this.reset_option(param);
if (!this.not_use_node_pre_cmd_exec) {
this.node_require.fs = require("fs");
this.node_require.path = require("path");
this.node_require.child_process = require('child_process');
}
this.on_call(this.raw_prompt);
}
// cmd 命令 参数预测 只要是参数都可能是本目录下的文件名所以可以检测一下
cmd_params_auto_completion(param_str) {
// 这里的默认实现是开启了使用 node
if (this.not_use_node_pre_cmd_exec) {
return;
}
const items = this.node_require.fs.readdirSync(this.cwd); // 读取目录内容
if (!this.word_detection) {
this.word_detection = new word_detection_js_1.word_detection_js();
for (const item of items) {
this.word_detection.add(item);
}
}
let v = this.word_detection.detection_next_one_word(param_str, ".");
if (v === undefined) {
// windows的特殊处理判断
const list = this.word_detection.detection_next_list_word(param_str, ".");
v = (0, path_util_1.get_best_cmd)(list);
}
return v;
}
/**
* public method
*/
reset_option(param) {
for (let key of Object.keys(param)) {
if (key === 'node_pty_shell_list') {
this.shell_set = new Set(param[key]);
}
this[key] = param[key];
}
}
add_cmd_handle(exe_cmd, handle) {
this.cmd_exec_map.set(exe_cmd, handle);
this.cmd_set.add(exe_cmd);
}
close() {
this.is_running = false;
this.close_child(0);
}
kill() {
this.close();
}
/**
* 处理字符串内容工具函数 防止输出的时候 在尾部单词截断
* @param str
*/
cols_handle(str) {
if (!str || str.length <= this.cols)
return str;
const max_index = str.length - 1;
const list = [];
let last_index = 0; // 上一次位置
let index = PtyShell.readFullCharIndex(str, 0, this.cols);
let count = 0;
while (index < max_index) {
if (count > max_index)
break; // 防止错误的一直循环
count++;
if (!this.is_empty(str[index]) && (!this.is_empty(str[index - 1]) || !this.is_empty(str[index + 1]))) {
// 一个单词前后都不是空的
for (let f = index - 1; f >= last_index; f--) {
if (this.is_empty(str[f])) {
list.push(str.substring(last_index, f + 1));
last_index = f + 1;
index = f + 1 + PtyShell.readFullCharIndex(str, f + 1, this.cols);
continue;
}
if (f === last_index) {
// 最后一位 直接把本行全部添加进去
list.push(str.substring(last_index, index));
last_index = index;
index = index + PtyShell.readFullCharIndex(str, index, this.cols);
}
}
}
else {
list.push(str.substring(last_index, index));
last_index = index;
index = index + PtyShell.readFullCharIndex(str, index + 1, this.cols);
}
}
if (index >= max_index) {
list.push(str.substring(last_index));
}
return list.join('\n\r');
}
/**
* 向pty写入数据
* @param data
*/
write(data) {
return __awaiter(this, void 0, void 0, function* () {
if (this.child && this.is_pty) {
// 终端shell 完全 托管给 别的程序
this.spawn_write(data);
return;
}
if (ctrl_set.has(data)) {
// 不改变 编辑器的 指针
this.ctrl_exec(data);
return;
}
else if (data.startsWith('\x1b')) {
// 是控制字符但是没有 对应的处理删除
return;
}
if (this.select_line) {
// 如果有选中就应该先删除
this.ctrl_exec('\x1b[3~'); // 删除
}
// 插入数据
let enter_index = this.get_enter_index(data); // 从换行符开始截取一部分插入
if (this.is_line_end) {
// 在最后插入
if (enter_index !== -1) {
// 有换行
if (data.length > 1) {
// 不是单个的换行符 但是包含换行
yield this.multiple_line(data, enter_index);
}
else {
yield this.parse_exec();
}
}
else {
this.on_call(data);
this.line += data;
this.line_index += data.length;
}
}
else {
// 在某个地方插入
if (enter_index === -1) {
// 还没有换行 只是插入
this.insert_line(data);
}
else {
yield this.multiple_line(data, enter_index);
}
}
});
}
/**
* static method
*/
// 判断一个字符是全角还是半角
static isFullCharWidth(char) {
// 计算字符的 UTF-8 编码字节长度
const byteLength = Buffer.byteLength(char, 'utf8');
// 如果字符的字节长度大于 1,说明是全角字符
return byteLength > 1;
}
// 从start_index往前多少个位置获取指定数量的 半角 字符(宽字符算两个)
static readFullCharIndex(str, start_index, len) {
if (!str)
return 0;
if (start_index >= str.length)
return 0;
let num = 0;
let char_num = 0;
for (let i = start_index; i < str.length; i++) {
if (this.isFullCharWidth(str[i])) {
num += 2;
}
else {
num++;
}
char_num++;
if (num >= len)
return char_num;
}
return char_num;
}
// 获取字符串中有多少个 字符(将宽字符统计成两个)
static get_full_char_num(str) {
if (!str)
return 0;
let char_num = 0;
for (let i = 0; i < str.length; i++) {
if (this.isFullCharWidth(str[i])) {
char_num += 2;
}
else {
char_num++;
}
}
return char_num;
}
/**
* private method
*/
get raw_prompt() {
const { str, char_num } = this.on_prompt_call(this.cwd);
this.prompt_call_len = char_num;
return str;
}
get enter_prompt() {
return `\n\r${this.raw_prompt}`;
}
clear_line() {
this.line_index = -1;
this.line = "";
}
// 光标是不是在尾部
get is_line_end() {
return this.line_index + 1 === this.line.length;
}
insert_line(str) {
// 在某个地方插入
let arr = this.line.split('');
if (this.line_index === -1) {
arr = [str, ...arr];
}
else {
arr.splice(this.line_index + 1, 0, str);
}
this.line = arr.join('');
this.line_index += str.length;
this.update_line({ line_add_num: 1 });
}
close_child(code) {
this.child_now_line = '';
if (this.child) {
// SystemUtil.killProcess(this.child.pid);
const pid = this.child.pid;
if (this.on_child_kill) {
this.exec_end_call(code, pid);
}
else {
this.child.kill(); // 不同平台信号不同 win 默认 SIGHUP
}
this.child = undefined;
}
}
send_and_enter(str, send_prompt = false) {
try {
if (typeof str == "string") {
const list = [];
let i = 0;
const last_i = str.length - 1;
for (let j = 0; j < str.length; j++) {
if (j < last_i) {
const v = `${str[j]}${str[j + 1]}`;
if (v === '\n\r' || v === '\r\n') {
j++; // 多跳一个字符
continue;
}
}
if (str[j] === '\r' || str[j] === '\n') {
list.push(str.substring(i, j));
i = j + 1;
}
}
if (i === 0 || i !== last_i)
list.push(str.substring(i));
if (this.child) {
// 添加子进程的提示换行
this.child_now_line = list[list.length - 1];
}
str = list.join('\n\r'); // 把所有的回车替换一下换行
}
if (str) {
if (this.next_not_enter) {
this.on_call(`${str}`); // 在下一行输出
}
else {
this.on_call(`\n\r${str}`); // 在下一行输出
}
this.next_not_enter = str.endsWith('\n\r') || str.endsWith('\r\n'); // 下一次不用换行了
}
if (!this.child || send_prompt) {
this.on_call(`${this.enter_prompt}`);
}
this.clear_line();
}
catch (e) {
console.log(e);
}
}
// 重新更新显示本行 也许可以更节省的更新 文本 powershell 这样的每次都是全部更新 暂时和他一样
update_line(param) {
var _a, _b;
const prompt = !this.child ? this.raw_prompt : this.child_now_line;
let len = (!this.child ? this.prompt_call_len : PtyShell.get_full_char_num(prompt)) + this.line_char_index; // 字符串前面的字符数量
if (param && param.line_add_num) {
len += param.line_add_num;
}
else if (param && param.line_reduce_num) {
len -= param.line_reduce_num;
}
const line = (_a = param === null || param === void 0 ? void 0 : param.p_line) !== null && _a !== void 0 ? _a : this.line;
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 : "";
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}`;
/**
* \x1b[?25l: 隐藏光标
* \r: 回车,移动光标到行首
* \x1b[0K: 清空当前行光标右侧的内容
* \x1b[21G: 将光标移动到当前行的第 21 列
* \x1b[?25h: 显示光标
* \x1b[0m 是后面的颜色重置 不要影响前面的
*/
this.on_call(updateLineString);
}
cancel_selected() {
this.select_line = '';
this.select_start = -2;
this.select_end = -2;
}
get line_char_index() {
if (this.line_index === -1)
return 0;
return PtyShell.get_full_char_num(this.line.substring(0, this.line_index + 1));
}
ctrl_exec(str) {
let cancel_selected = true; // 取消选中
switch (str) {
case '\x09':
{
// 按下 tab按键
if (this.line_index < 0 ||
this.is_empty(this.line[this.line_index]) || // 字符位置是空的
(!this.is_empty(this.line[this.line_index]) && !this.is_empty(this.line[this.line_index + 1]))) { // 自己和前面都不是空的(数组过节返回的也是空也可以)
// 不符合条件的不做命令自动补充
break;
}
const { word, is_exe } = this.get_last_word_cmd_or_param(this.line, this.line_index);
if (word) {
let word_d;
if (is_exe && this.cmd_exe_auto_completion !== undefined) {
word_d = this.cmd_exe_auto_completion(word);
}
else {
word_d = this.cmd_params_auto_completion(word); // 不可用或者没有实现会空
}
if (word_d !== undefined && word_d !== word) {
this.line = this.line.substring(0, this.line_index - word.length + 1) + word_d + this.line.substring(this.line_index + 1);
this.line_index = this.line_index - word.length + word_d.length;
this.update_line({ line_add_num: 1 });
}
}
}
break;
case "\x1b[A":
{
// 向上
const index = this.history_line_index === -1 ? this.history_line.length - 1 : this.history_line_index - 1;
if (index >= 0) {
this.line = this.history_line[index];
this.history_line_index = index;
}
this.line_index = this.line.length - 1;
this.update_line({ line_add_num: 1 });
}
break;
case "\x1b[B":
{
// 向下
const index = this.history_line_index + 1;
if (index < this.history_line.length) {
this.line = this.history_line[index];
this.history_line_index++;
}
this.line_index = this.line.length - 1;
this.update_line({ line_add_num: 1 });
}
break;
case "\x1b[C":
{
// 向右
if (this.line_index >= this.line.length - 1) {
break;
}
this.line_index++;
const len = PtyShell.get_full_char_num(this.line[this.line_index]);
if (this.select_line) {
this.update_line({ line_add_num: 1 });
break;
}
this.on_call(`\x1b[${len}C`);
}
break;
case "\x1b[D":
{
// 向左
if (this.line_index === -1) {
break;
}
const len = PtyShell.get_full_char_num(this.line[this.line_index]);
this.line_index--;
if (this.select_line) {
this.update_line({ line_add_num: 1 });
break;
}
this.on_call(`\x1b[${len}D`);
}
break;
case '\x7F':
{
// 删除左侧 一个字符 backspace
if (this.select_line) {
this.ctrl_exec('\x1b[3~'); // 删除
break;
}
if (this.line_index === -1) {
break;
}
this.line = this.removeCharacterAt(this.line, this.line_index);
this.line_index--;
this.update_line({ line_add_num: 1 });
}
break;
case '\x1b[3~':
{
// delete 删除 选中
if (!this.select_line) {
break;
}
this.line = this.line.substring(0, this.select_start + 1) + this.line.substring(this.select_end + 1);
this.line_index = this.select_start;
this.update_line({ line_add_num: 1 });
}
break;
case '\x01':
{
// ctrl + a 全选
this.update_line({ all_line_ctrl: `\x1b[48;5;252m${this.line}`, line_add_num: 1 }); // 一行置灰 235 开始就是灰色 值越大灰度越轻
this.select_line = this.line;
this.select_start = -1;
this.select_end = this.line.length - 1;
cancel_selected = false;
}
break;
case '\x03':
{
// ctrl + c 复制 或者 结束 子进程
if (this.select_line) {
this.on_control_cmd(exec_cmd_type.copy_text, this.select_line);
this.cancel_selected();
this.update_line({ line_add_num: 1 });
}
else if (this.child) {
this.close_child(0);
return;
}
}
break;
case '\x1b[F':
{
// end
this.line_index = this.line.length - 1;
this.update_line({ line_add_num: 1 });
}
break;
case '\x1b[H':
{
// home
this.line_index = -1;
this.update_line({ line_add_num: 1 });
}
break;
case '\x1b[1;2D':
{
// 按住 shift 向左移动一个字符 目前只能移动一个
if (this.select_start === -2 || this.select_start === this.line_index) {
const index = this.line_index - 1;
if (index >= -1) {
if (this.select_end === -2) {
this.select_end = this.line_index;
}
// 第一次移动 或者 移动的唯一已经选中 往右移动一次
this.line_index = index;
this.select_start = index;
}
}
else if (this.select_end === this.line_index) {
// 之前是往右选中 现在往左退回
const index = this.line_index - 1;
if (index >= -1) {
// 第一次移动 或者 移动的唯一已经选中 往右移动一次
this.line_index = index;
this.select_end = index;
}
}
this.select_line = this.line.substring(this.select_start + 1, this.select_end + 1);
this.update_line({
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),
line_add_num: 1
}); // 一行置灰 235 开始就是灰色 值越大灰度越轻
cancel_selected = false;
}
break;
case '\x1b[1;2C':
{
// 按住 shift 向右移动一个
if (this.select_end === -2 || this.select_end === this.line_index) {
const index = this.line_index + 1;
if (index <= this.line.length - 1) {
if (this.select_start === -2) {
this.select_start = this.line_index;
}
// 第一次移动 或者 移动的唯一已经选中 往右移动一次
this.line_index = index;
this.select_end = index;
}
}
else if (this.select_start === this.line_index) {
const index = this.line_index + 1;
// 之前是往左选中 现在往右退回
this.line_index = index;
this.select_start = index;
}
this.select_line = this.line.substring(this.select_start + 1, this.select_end + 1);
this.update_line({
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),
line_add_num: 1
}); // 一行置灰 235 开始就是灰色 值越大灰度越轻
cancel_selected = false;
}
break;
case '\x1b[1;2H':
{
// shift home
if (this.select_end === -2) {
this.select_end = this.line_index;
}
this.select_start = -1;
this.line_index = -1;
this.select_line = this.line.substring(this.select_start + 1, this.select_end + 1);
this.update_line({
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),
line_add_num: 1
}); // 一行置灰 235 开始就是灰色 值越大灰度越轻
cancel_selected = false;
}
break;
case '\x1b[1;2F':
{
// shift end
if (this.select_start === -2) {
this.select_start = this.line_index;
}
this.line_index = this.line.length - 1;
this.select_end = this.line.length - 1;
this.select_line = this.line.substring(this.select_start + 1, this.select_end + 1);
this.update_line({
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),
line_add_num: 1
}); // 一行置灰 235 开始就是灰色 值越大灰度越轻
cancel_selected = false;
}
break;
case '\x1b[1;5D':
{
// ctrl 向左
if (this.line_index === -1)
return;
const h = this.is_empty(this.line[this.line_index]);
for (let i = this.line_index; i >= 0; i--) {
if (this.is_empty(this.line[i]) !== h) {
this.line_index = i;
break;
}
else if (i === 0) {
this.line_index = -1;
}
}
this.update_line({ line_add_num: 1 });
}
break;
case '\x1b[1;5C':
{
// ctrl 向右
if (this.line_index === this.line.length - 1)
return;
const h = this.is_empty(this.line[this.line_index + 1]);
const max = this.line.length - 1;
for (let i = this.line_index; i <= max; i++) {
if (this.is_empty(this.line[i + 1]) !== h || i === max) {
this.line_index = i;
break;
}
}
this.update_line({ line_add_num: 1 });
}
break;
// default:{
// if(str.startsWith('\x1b')) {
// // esc控制信号序列
// switch(str[2]) {
// case '1':
// }
// }
// }
}
if (cancel_selected)
this.cancel_selected();
}
push_history_line(line) {
if (this.history_line[this.history_line.length - 1] === line || !line) {
this.history_line_index = -1;
return; // 和最后的一样就不插入了
}
this.history_line.push(line);
if (this.history_line.length > this.history_line_max) {
this.history_line.shift(); // 删除最前面的
}
this.history_line_index = -1;
}
exec_end_call(code, pid) {
if (this.on_child_kill)
this.on_child_kill(code, pid); // 也发送一下;
}
// 解析和执行命令 执行完会自动换行的
parse_exec() {
return __awaiter(this, void 0, void 0, function* () {
// const line = this.delete_all_enter(this.line);
if (!this.line && !this.child) {
this.send_and_enter("");
this.clear_line();
return;
}
this.push_history_line(this.line);
if (this.child && this.is_pty) {
// 终端shell 完全 托管给 别的程序
this.spawn_write(`${this.line}\r`);
return;
}
else if (this.child) {
// 把数据给正在运行的别的程序
this.spawn_write(`${this.line}\n`);
this.clear_line();
return;
}
const { exe, params } = this.get_exec(this.line);
this.history_line_index = -1;
try {
let use_noe_pty = false;
if (this.check_exe_cmd) {
// 检查外部自定义的 是否能执行某个命令
const v = yield this.check_exe_cmd(exe, params);
switch (v) {
case exec_type.not:
this.send_and_enter(`not have permission to execute ${exe}`);
this.clear_line();
this.exec_end_call(-1);
return;
case exec_type.auto_child_process:
break;
case exec_type.not_pty:
use_noe_pty = true;
break;
default:
// 未知的不报错也不执行
this.exec_end_call(0);
return;
}
}
if (this.cmd_set.has(exe)) {
// 检测某个已经有预处理的命令 包括用户自定义的
if (this.exec_cmd(exe, params)) {
// 成功执行了不用再继续了
this.clear_line();
this.exec_end_call(0);
return;
}
}
this.spawn(exe, params, use_noe_pty);
this.clear_line();
}
catch (e) {
// console.log("子线程执行异常", e);
this.send_and_enter(e.message);
this.exec_end_call(-1);
}
});
}
multiple_line(data, enter_index) {
return __awaiter(this, void 0, void 0, function* () {
if (!data)
return;
let num = 0; // 防止解析失败死循环
while (enter_index > -1 && this.is_running && num < 1000) {
// 如果有剩余一直插入
// 不要最后的回车符号
this.insert_line(data.substring(0, enter_index));
// 开始解析执行 执行完应该清理行数据
yield this.parse_exec();
// 下一次
data = data.substring(enter_index + 1);
enter_index = this.get_enter_index(data);
num++;
}
if (data !== "") {
// 剩余的还没有换行符
this.insert_line(data);
}
});
}
spawn_write(str) {
if (this.is_pty) {
this.child.write(str);
}
else {
this.child.stdin.write(str);
}
}
spawn(exe, params, use_noe_pty = true, spawn_option) {
if (this.not_use_node_pre_cmd_exec) {
this.send_and_enter(`not_use_node_pre_cmd_exec is true`);
return;
}
// this.send_and_enter(""); //
// if (!this.child) {
// this.on_call(`\n\r`); // 先换个行
// }
if ((use_noe_pty || this.shell_set.has("*") || this.shell_set.has(exe)) && this.node_pty) {
// if (!exe.includes('exe') && exe !== 'bash' && exe !== 'sh') {
// exe += '.exe';
// }
this.on_call(`\n\r`); // 先换个行
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));
this.child.onData((data) => {
this.on_call(data.toString());
if (this.on_call_child_raw) {
this.on_call_child_raw(data);
}
});
this.child.onExit(({ exitCode, signal }) => {
this.close_child(exitCode);
this.send_and_enter("");
// this.send_and_enter(`pty with ${exitCode}`);
this.next_not_enter = false; // 下一次的换行输出 上一次没有换行
});
this.is_pty = true;
}
else {
this.is_pty = false;
// 其他的没有必要再创建一个 tty 都是资源消耗
this.child = this.node_require.child_process.spawn(exe, params, Object.assign({
// shell:getShell(),
cwd: this.cwd, env: Object.assign(Object.assign(Object.assign({}, process.env), this.env), { LANG: 'en_US.UTF-8' }),
// stdio: 'inherit' // 让子进程的输入输出与父进程共享 pipe ignore inherit
// timeout: 5000, // 设置超时为 5 秒
maxBuffer: 1024 * 1024 * 10 }, spawn_option));
// 设置编码为 'utf8',确保输出按 UTF-8 编码解析
this.child.stdout.setEncoding('utf8');
this.child.stdout.on('data', (data) => {
// const v = data.toString(); // 子程序没有换行等符号不会立即输出 有缓冲区
this.send_and_enter(data.toString());
if (this.on_call_child_raw) {
this.on_call_child_raw(data);
}
});
this.child.stderr.on('data', (data) => {
// const v = data.toString();
this.send_and_enter(data.toString());
this.next_not_enter = false; // 下一次的换行输出 上一次没有换行
if (this.on_call_child_raw) {
this.on_call_child_raw(data);
}
});
this.child.on('exit', (code) => {
this.close_child(code);
// if (code !== 1) {
// this.send_and_enter(`process exited with code ${code}`);
// } else {
this.send_and_enter(``);
// }
this.next_not_enter = false; // 下一次的换行输出 上一次没有换行
});
this.child.on('error', (error) => {
this.next_not_enter = false; // 下一次的换行输出 上一次没有换行
this.close_child(-1);
this.send_and_enter(error.message);
});
}
}
exec_cmd(exe, params) {
try {
const handle = this.cmd_exec_map.get(exe);
if (exe !== 'cd' && handle) {
// 如果用户有了就用用户的 不用系统自己的 但是 cd 命令排除在外
handle(params, (data) => {
this.send_and_enter(data, true);
});
return true;
}
switch (exe) {
case 'pwd':
{
this.send_and_enter(`${this.cwd}`);
}
return true;
case 'cd':
{
let p;
if (!this.not_use_node_pre_cmd_exec) {
// 有node环境可以检测一下
p = this.node_require.path.isAbsolute(params[0]) ? params[0] : this.node_require.path.join(this.cwd, params[0]);
if (!this.node_require.fs.existsSync(p)) {
this.send_and_enter(`not directory ${p}`);
}
}
else {
// 没有node环境只能这样了 todo 对于 .. 路径有问题
p = (0, path_util_1.path_join)(this.cwd, params[0]);
}
this.cwd = p;
this.send_and_enter(``);
this.word_detection = undefined; // 清空检测器
}
return true;
case 'ls':
{
if (this.not_use_node_pre_cmd_exec) {
return false; // 让其它方式处理
}
const items = this.node_require.fs.readdirSync(this.cwd); // 读取目录内容
const v = this.cols_handle(" " + items.join(" "));
this.send_and_enter(v);
}
return true;
default:
return false; // 让其它方式处理
}
}
catch (e) {
this.send_and_enter(JSON.stringify(e));
}
return false; // 让其它方式处理
}
// 解析命令与参数
get_exec(str) {
let exe = "", params = [];
if (!str) {
return { exe, params };
}
let start = -1;
let mate = 0;
for (let i = 0; i < str.length; i++) {
let is_empty = this.is_empty(str[i]);
if (mate % 2 !== 0) {
// 已经有特殊字符串开始
is_empty = false;
}
if (start === -1 && !is_empty) {
// 开始 没有开始 且有非空字符
start = i;
if (str[i] === '\'' || str[i] === '"') {
mate = 1;
}
}
else if (start !== -1 && (is_empty || i === str.length - 1)) {
// 结束 已经开始且遇上空白字符 或者结尾
const i_p = (i === str.length - 1 && !is_empty) ? str.length : i;
if (exe === "") {
exe = str.substring(start, i_p);
mate = 0;
}
else {
if (mate === 0) {
// 没有 " ' 括起来的特殊字符串
params.push(str.substring(start, i_p));
}
else {
mate = 0;
params.push(str.substring(start + 1, i_p - 1));
}
}
start = -1;
}
else if (mate !== 0 && (str[i] === '\'' || str[i] === '"')) {
// 不是结束也不是开始
mate++;
}
}
if (start !== -1) {
params.push(str.substring(start));
}
return { exe, params };
}
get_last_word_cmd_or_param(line, now_index) {
// 获取当前命令 当前位置 往前的一个单词
let word = "", is_exe = true;
if (!!line && !!line[now_index]) {
// 字符串不能是空的 且 当前位置也不能是空的
for (let i = now_index; i >= 0; i--) {
if (!this.is_empty(line[i])) {
word = line[i] + word;
}
else {
// 判断前面是否还有字符
for (let j = i; j >= 0; j--) {
if (line[j]) {
is_exe = false;
break;
}
}
break;
}
}
}
return { word, is_exe };
}
is_empty(str) {
// 判断是否为 null 或 undefined
if (str === null || str === undefined) {
return true;
}
// 判断是否为空字符串或仅包含空白字符(空格、换行符、制表符等) 对于 \b 这样的字符不能判断为空 powershell也是
return str.trim() === '';
}
get_enter_index(str) {
if (!str) {
return -1;
}
for (let i = 0; i < str.length; i++) {
if (str[i] == '\r') {
return i;
}
}
return -1;
}
// 将 \r 替换成 \n\r
get_enter_line(str, index) {
if (!str) {
return str;
}
return str.substring(0, index) + '\n\r';
}
// 删除自定义位置的字符
removeCharacterAt(str, index) {
let arr = str.split('');
arr.splice(index, 1); // 删除指定位置的字符
return arr.join('');
}
// 删除所有换行符号
delete_all_enter(str) {
if (!str) {
return str;
}
const arr = str.split('');
return arr.filter(v => v !== '\r' && v !== '\n').join('');
}
}
exports.PtyShell = PtyShell;