xterm.js模拟终端输入

参考出处:https://www.cnblogs.com/wzs5800/p/13221344.html (作者:Ashin5800)
模块包源码地址:https://github.com/AshinWu/webterminal

项目需要模拟terminal连接服务器调用命令操作,以下代码是xterm.js的使用,包含键盘keyCode操作的判断,websocket功能未添加,使用也就是调用命令即可
注:keyCode的源码文件做了一点点修改,下载源码之后的路径为:\node_modules\xterm\lib\core\input\Keyboard.js,在302行做了修改,修改代码如下:

 if(ev.keyCode == 67){
         result.key = EscapeSequences_1.C0.ESC + '[67;';
 }else if(ev.keyCode == 86){
         result.key = EscapeSequences_1.C0.ESC + '[86;';
}else{
         result.key = String.fromCharCode(ev.keyCode - 64);
}

注:兼容中英文输入与其他操作


<template>
    <div class="hello" style="width:555px;">
        <div id="terminal-container"></div>
    </div>
</template>

<script>
import "xterm/dist/xterm.css";
// import 'xterm/dist/xterm'
import * as fit from "xterm/dist/addons/fit/fit";
import * as attach from "xterm/dist/addons/attach/attach";
import { Terminal } from "xterm";

export default {
  name: "HelloWorld",
  data() {
    return {
        terminal: Object,
        termOptions: {
            rows: 16,
            scrollback: 800,
        },
        input: "",
        prefix: "ashin$ ",    // 每行开头,可自行修改
        // 当前历史指令
        histIndex: 0,
        histCommandList: [],         // 记录历史命令
        currentOffset: Number,
        beginIndex: 0,
        rowsNum: [],
        inputData: {},     // 存放输入的字符串,单行/多行
        beginY: null,      // 开始的行数
    };
  },
  mounted() {
    this.terminal = this.initTerm();
  },
  methods: {
    initTerm() {
    	// 初始化
        let term = new Terminal({
            rendererType: "canvas",
            cursorBlink: true,
            convertEol: true,
            scrollback: this.termOptions.scrollback,
            rows: this.termOptions.rows,
            cursorStyle: "underline", //光标样式
            theme: {
                foreground: "yellow",
                background: "#060101",
                cursor: "help", //设置光标 
            },
            fontSize: 16
        });
        let terminalContainer = document.querySelector("#terminal-container");
        term.open(terminalContainer);
        Terminal.applyAddon(fit);
        Terminal.applyAddon(attach);
        term.fit();
        term.focus();
        term.writeln(`Hello from web terminal`);

        term.prompt = () => {
            term.write(this.prefix);
        };

        // 实际需要使用socket来交互
        if ("WebSocket" in window) {
            term.writeln("\x1b[1;1;32mThe Browser supports websocket!\x1b[0m");
            // term.prompt();
            term.write("\n");
            // socket监听事件

        } else {
            term.writeln(
            "\x1b[1;1;31mThe Browser does not support websocket!\x1b[0m"
            );
        }
        term.write(this.prefix);
        setTimeout(function(){
            this.beginY = term._core.buffer.y;
        },0);

        let specialKeycode1 = ['\x1b[86;',];
        let specialKeycode2 = [
            '\x1b[15~','\x1b[17~','\x1b[18~','\x1b[19~',
            '\x1b[20~','\x1b[21~','\x1b[23~','\x1b[24~'
        ];
        // 若需要中文输入, 使用on data监听
        term.onData(
            function (e) {
                let cols = term.cols;
                let y = term._core.buffer.y;
                let x = term._core.buffer.x;
                let subObj;
                switch (e) {
                    case "\r": // Enter
                        if(this.input.length > 0){
                            let temp = [];
                            for(let key in this.inputData){
                                temp.push(key);
                            }
                            term._core.buffer.y = temp[temp.length-1];
                        }
                        
                        this.handleInput();
                        this.input = "";
                        for(let key in this.inputData){
                            delete this.inputData[key];
                        }
                        this.beginY = null;
                        y = term._core.buffer.y;
                        break;
                    case "\t": // tab
                        break;
                    case "\x1b[A": // up
                        if(this.histCommandList[this.histIndex - 1]){
                            this.backspaceRow(term, cols);
                            this.input = this.histCommandList[this.histIndex - 1];
                            term.write(this.input);
                            this.histIndex--;
                            this.updateInputData(this.input,cols,term._core.buffer.y);
                        }
                        break;
                    case "\x1b[B": // down
                        if (this.histCommandList.length) {
                            if (this.histCommandList[this.histIndex + 1]) {
                                this.backspaceRow(term, cols);
                                this.input = this.histCommandList[this.histIndex + 1];
                                term.write(this.input);
                                this.histIndex++;
                                this.updateInputData(this.input,cols,term._core.buffer.y);
                            }else{
                                if(this.histIndex == this.histCommandList.length-1){
                                    this.backspaceRow(term, cols);
                                    this.input = '';
                                    term.write(this.input);
                                    this.updateInputData(this.input,cols,term._core.buffer.y);
                                    this.histIndex = this.histCommandList.length;
                                }
                            }
                        }
                        break;
                    case "\x1b[D": // left
                        subObj = this.posStr(this.inputData[y], x, 'l');
                        
                        if((this.inputData[y]!=undefined && this.inputData[y].indexOf(this.prefix) > -1)
                            || (subObj.index == null && subObj.x == this.prefix.length)
                        ){
                            if(x <= this.prefix.length){
                                return;
                            }
                        }
                        if(this.stringType(subObj.str)){
                            term.write(e);
                            term.write(e);
                        }else{
                            term.write(e);
                        }
                        
                        if(x == 0){
                            term._core.buffer.y--;
                            let lastStr = this.inputData[term._core.buffer.y][this.inputData[term._core.buffer.y].length-1];
                            
                            if(this.stringType(lastStr)){
                                if( this.strLen(this.inputData[term._core.buffer.y]) != cols){
                                    term._core.buffer.x = cols-2;
                                }else{
                                    term._core.buffer.x = cols-1;
                                }
                                
                            }else{
                                term._core.buffer.x = cols+1;
                            }
                        }
                        break;
                    case "\x1b[C": // right
                        subObj = null;
                        subObj = this.posStr(this.inputData[y], x+2, 'r');
                        
                        if(subObj.x != null && this.inputData[y]!=undefined 
                            && subObj.x <= this.strLen(this.inputData[y])){
                            if(this.inputData[y+1]!=undefined && subObj.index == this.strLen(this.inputData[y])){
                                term._core.buffer.x = 0;
                                term._core.buffer.y++;
                            }else{
                                if(this.stringType(subObj.str)){ 
                                    term.write(e);
                                    term.write(e);
                                }else{ 
                                    term.write(e);
                                }
                            }
                        }else{
                            if(this.inputData[y]!=undefined && subObj.x > this.strLen(this.inputData[y])
                                && subObj.x-1 == this.strLen(this.inputData[y])
                            ){
                                if(this.inputData[y+1]!=undefined && subObj.index == this.strLen(this.inputData[y])){
                                    term._core.buffer.x = 0;
                                    term._core.buffer.y++;
                                }else{
                                    term.write(e);
                                }
                            }
                        }
                        break;
                    case "\u007F": // Backspace (DEL)  区分中英文字符
                        if(x==0){
                            y--;
                            term._core.buffer.y--;
                        }
                        if(this.inputData[y] != undefined){
                            if(this.inputData[y]!=undefined&&this.inputData[y].length == 0){
                                delete this.inputData[y];
                                term._core.buffer.y--;
                                y--;
                                term._core.buffer.x = cols;
                                x = term._core.buffer.x;
                            }else{
                                if(x == 0){
                                    term._core.buffer.x = cols;
                                    x = term._core.buffer.x;
                                }
                            }
                            let subObj = this.posStr(this.inputData[y], term._core.buffer.x,'l');
                            let isStr = this.stringType(subObj.str);
                            
                            if(this.inputData[y].indexOf(this.prefix) > -1){
                                if(x <= this.prefix.length){
                                    return;
                                }
                            }
                            
                            if (isStr) {
                                term._core.buffer.x = subObj.x - 2;
                            } else {
                                term._core.buffer.x = subObj.x - 1;
                            }
                            
                            // 当前行光标位置插入空格
                            term.write("\x1b[?K" + this.inputData[y].slice(subObj.i + 1));
                            const cursor = this.bulidData(this.strLen(this.inputData[y].slice(subObj.i + 1)),"\x1b[D");
                            term.write(cursor);
                            this.inputData[y] = `${this.inputData[y].slice(0,subObj.i)}${this.inputData[y].slice(subObj.i + 1)}`;
                    
                            let _this = this;
                            _this.input = '';
                            for(var key in _this.inputData){
                                if(key == term._core.buffer.y){
                                    _this.delFun(key, cols, term);
                                }

                                _this.input += _this.inputData[key];
                            }
                            _this.input = _this.input.slice(_this.prefix.length);
                        }
                        break;
                    case "\x1b[3~": // 46  Del
                        break;
                    case "\x1b[2~": // 45  Ins
                        break;
                    case "\x1b[5~": // 33  PgUp
                        break;
                    case "\x1b[6~": // 34  PgDn
                        break;
                    case "\x1b[H": // 36  Home
                        break;
                    case "\x1b[67;": // ctrl+c
                        term.write("^C");
                        this.handleInput();
                        this.input = "";
                        for(let key in this.inputData){
                            delete this.inputData[key];
                        }
                        this.beginY = null;
                        break;
                    case "\x1bOP": // F1
                        break;
                    case "\x1bOQ": //F2
                        break;
                    case "\x1bOR": // F3
                        break;
                    case "\x1bOS": // F4
                        break;
                    default:
                        if(specialKeycode1.indexOf(e) > -1){
                            e = '^V';
                        }
                        if(specialKeycode2.indexOf(e) > -1){
                            e = '~';
                        }
                        
                        this.histIndex = this.histCommandList.length;

                        if(!this.beginY){
                            this.beginY = term._core.buffer.y;
                        }
                        
                        setTimeout(function(){
                            let y = term._core.buffer.y;
                            let x = term._core.buffer.x;
                            if(this.inputData[y]){
                                let _this = this;
                                _this.input = '';
                                for(var key in _this.inputData){
                                    if(key == term._core.buffer.y){
                                        _this.addFun(key , cols, term, e, x);
                                        break;
                                    }
                                }
                                for(var key1 in _this.inputData){
                                    _this.input += _this.inputData[key1];
                                }
                                _this.input = _this.input.slice(_this.prefix.length);
                            }else{
                                term.write(e);
                                this.input += e;
                                this.updateInputData(this.input, cols, this.beginY);
                            }
                        }.bind(this),0);
                }
            }.bind(this)
        );
        
        this.term = term;
        return term;
    },
    // 在这里处理自定义输入...
    handleInput() {
        // 判断空值
        this.terminal.write("\r\n");
        
        if (this.input.trim() && this.input.replace("^C", "") != "") {
            // 记录历史命令  相同命令也需要记录
            this.histCommandList.push(this.input);
            this.histIndex = this.histCommandList.length;
            this.input = this.input.replace("^C", "");
            const command = this.input.trim().split(" ");
            // 可限制可用命令
            // 这里进行socket交互
            switch (command[0]) {
                case "help":
                    this.terminal.writeln(
                    "\x1b[40;33;1m\nthis is a web terminal demo based on xterm!\x1b[0m\n此demo模拟shell上下左右和退格键效果\n"
                    );
                    break;
                default:
                    // 发送数据
                    break;
            }
        }
        this.terminal.prompt();
    },
    bulidData(length, subString) {
        let cursor = "";
        for (let i = 0; i < length; i++) {
            cursor += subString;
        }
        return cursor;
    },
    // 计算中英文混合字符串长度
    strLen(str){
        if(!str) return 0;
        let strLen = 0;
        for(let i=0;i<str.length;i++){
            if(/^[\u4e00-\u9fa5]/.test(str[i])){
                strLen = strLen + 2;
            }else{
                strLen ++;
            }
        }
      
        return strLen;
    },
    // 查找中英文混合字符串指定位置字符
    posStr(str, x, flag) {
        if(!str) return { str: "", index: null, i: null, x: x, isLast:isLast };
        let index = 0;
        let len = str.length;
        let isLast = false;
        if(this.strLen(str) == this.term.cols-1){
            if(x > this.term.cols-1){
                x = this.strLen(str);
            }
        }
        
        for (let i = 0; i < len; i++) {
            if (/^[\u4e00-\u9fa5]/.test(str[i])) {
                index = index + 2;
            } else {
                index = index + 1;
            }
            if(i == len-1){
                isLast = true;
            }
            if(flag == 'r'){
                if (/^[\u4e00-\u9fa5]/.test(str[i])) {
                    if (index == x) {
                        return { str: str[i], index: index, i: i, x: x, isLast:isLast };
                    }
                }else{
                    if (index == x-1) {
                        return { str: str[i], index: index, i: i, x: x, isLast:isLast };
                    }
                }
            }else{
                if (index == x) {
                    return { str: str[i], index: index, i: i, x: x, isLast:isLast };
                }
            } 
        }
        return { str: "", index: null, i: null, x: x, isLast:isLast };
    },
    // 判断字符串类型
    stringType(str) {
        if (/^[\u4e00-\u9fa5]/.test(str)) {
            return true;
        } else {
            return false;
        }
    },
    // 删除字符串函数
    delFun(y,cols,term){
        let _this = this;
        y=parseInt(y);
        let delstr = this.delStr(y, cols);
        
        if(!isNaN(delstr.charCodeAt(0))){
            let len = this.strLen(this.inputData[y]);

            this.inputData[y] += delstr;
            
            delstr = delstr.split('');
            for(let i=0;i<delstr.length;i++){
                setTimeout(function(){
                    // 当前行删除后在尾部添加下一行字符串
                    term._core.buffer.lines.get(y).setCellFromCodePoint(
                        len+(i==0?0:_this.strLen(delstr[i-1])),
                        delstr[i].charCodeAt(0),
                        1,0,0
                    );
                    term._core.blur();
                    term._core.focus();
                    
                    term._core.updateRange(y);
                    term._core.refresh(0, cols);
                },0);
                _this.inputData[y+1] = _this.inputData[y+1].substr(delstr.length);
                if(_this.inputData[y+1] !=undefined){
                    term._core.buffer.lines.get(y+1).deleteCells(0, _this.strLen(delstr), delstr);
                    term._core.updateRange(y+1);
                    term._core.refresh(0, cols);
                    _this.delFun(y+1, cols, term);
                }
            }
        }
        for(let key in _this.inputData){
            if(_this.inputData[key].length == 0){
                delete _this.inputData[key];
            }
        }
    },
    // 删除输入的字符串
    delStr(y, cols){
        let _str = '';
        if(this.inputData[y+1]){
            let len = cols - this.strLen(this.inputData[y]);
            let str = this.inputData[y+1];
            let strlen = str.length;
            let index = 0;
            
            for(let i=0;i<strlen;i++){
                if (/^[\u4e00-\u9fa5]/.test(str[i])) {
                    index = index + 2;
                } else {
                    index = index + 1;
                }
                if(index <= len){
                    _str += str[i];

                }
            }
        }
        return _str;
    },
    // 插入字符串函数
    addFun(y, cols, term, str, x){
        let _this = this;
        y=parseInt(y);

        let start = _this.posInputDataX(_this.inputData[y], x);   // 开始位置
        let startStr = _this.inputData[y].slice(0,start);    // 开始位置截取的前段字符串长度
        let endStr = _this.inputData[y].slice(start);     // 开始位置截取的后段字符串长度
        let startStrLen = _this.strLen(startStr);
        let endStrLen = _this.strLen(endStr);
        let inputStrLen = _this.strLen(str);
        let preStr = '';
        let nextStr = '';
        let allStr = '';
        let copyX = parseInt(JSON.parse(JSON.stringify(x)));
        let beginX = 0;
        let flag = false;
        
        if(x != 0){
            // 当输入的字符串加当前行,长度小于等于cols
            if(startStrLen+inputStrLen+endStrLen <= cols){
                for(let i=0;i<str.length;i++){
                    start = _this.posInputDataX(_this.inputData[y], x); 
                    x = _this.addHandle(y, cols, term, str[i], x, start);
                    if(x >= cols){    // 当前大于等于cols
                        y++;
                        if(!_this.inputData[y]){
                            _this.inputData[y] = '';
                        }
                    }
                }
            }
            // 当输入的字符串加当前行,长度大于cols
            else{
                for(let j=0;j<endStr.length;j++){
                    term._core.buffer.lines.get(y).deleteCells(x, _this.strLen(endStr[j]), endStr[j]);
                    _this.inputData[y] = _this.inputData[y].slice(0,_this.inputData[y].length-1);
                }
                let counted = cols-startStrLen;
                let endS = '';
                if(counted>0){
                    allStr = str + endStr;
                    for(let k=0;k<allStr.length;k++){
                        if(_this.strLen(endS)+_this.strLen(allStr[k]) <= counted){
                            endS += allStr[k];
                        }else{
                            break;
                        }
                    }
                }
                preStr = allStr.slice(0,endS.length);
                nextStr = allStr.slice(endS.length);
                
                if(preStr.length>0){
                    for(let l=0;l<preStr.length;l++){
                        start = _this.posInputDataX(_this.inputData[y], x); 
                        x = _this.addHandle(y, cols, term, preStr[l], x, start);
                    }
                }
                
                _this.updateNextRow(y+1,cols,term, nextStr,0);
            }
        }else{
            // 处理当前行的上一行的末尾是否还能写入当前字符
            for(let i=0;i<str.length;i++){
                if(_this.inputData[y-1] && _this.strLen(_this.inputData[y-1]) + _this.strLen(str[i]) <= cols ){
                    // 若可以写入
                    copyX = _this.strLen(_this.inputData[y-1]);
                    x = _this.addHandle(y-1, cols, term, str[i], cols-1, _this.strLen(_this.inputData[y-1]));
                    
                    term._core.buffer.y --;
                    y--;
                }else{
                    // 若不能写入
                    start = _this.posInputDataX(_this.inputData[y], x);
                    x = _this.addHandle(y, cols, term, str[i], x, start);
                }
            }
        }
        
        // 更新x和y
        for(let h=0;h<str.length;h++){
            if(copyX + _this.strLen(str[h]) <= cols){
                copyX += _this.strLen(str[h]);
            }else{
                flag = true;
                beginX += _this.strLen(str[h]);
            }
        }
        if(flag){
            term._core.buffer.x = beginX;
            term._core.buffer.y ++;
        }else{
            if(copyX == cols || (copyX == _this.strLen(_this.inputData[y]) && nextStr.length>0)){
                term._core.buffer.x = 0;
                term._core.buffer.y ++;
            }else{
                term._core.buffer.x = copyX;
            }
        }
        copyX = null;
    },
    // 插入字符后,循环更新下一行首尾
    updateNextRow(y, cols, term, str, x){
        let _this = this;
        let counted = 0;
        let endStr = '';
        y = parseInt(y);
        if(!_this.inputData[y]){
            _this.inputData[y] = '';
        }
        let start = _this.strLen(_this.inputData[y]) - _this.strLen(str);
        
        _this.inputData[y] = `${str}${_this.inputData[y]}`;

        if(_this.strLen(_this.inputData[y]) > cols){
            for(let i =0;i<=_this.inputData[y].length;i++){
                if(counted + _this.strLen(_this.inputData[y][i]) <= cols){
                    counted += _this.strLen(_this.inputData[y][i]);
                }else{
                    endStr += _this.inputData[y][i];
                }
            }
            
            if(endStr.length>0){
                _this.inputData[y] = _this.inputData[y].slice(0,_this.inputData[y].length-endStr.length);
                for(let j=0;j<endStr.length;j++){
                    start+=_this.strLen(endStr[j])*(j);
                    term._core.buffer.lines.get(y).deleteCells(start, _this.strLen(endStr[j]), endStr[j]);
                }
            }
        }

        for(let i=0;i<str.length;i++){
            term._core.buffer.lines.get(y).insertCells(x, _this.strLen(str[i]), str[i]);   // 当前x位置插入字符宽度
        
            // 写入字符
            term._core.buffer.lines.get(y).setCellFromCodePoint(
                x, str[i].charCodeAt(0),1,0,0
            );
            term._core.blur();    // 失焦
            term._core.focus();   // 聚焦
            
            term._core.updateRange(y);    // 更新当前行
            term._core.refresh(0, cols);     // 刷新当前行
            x = x+_this.strLen(str[i]);    // 当前x加上字符宽度
        }

        if(endStr.length>0){
            _this.updateNextRow(y+1, cols, term, endStr, 0);
        }
    },
    addHandle(y, cols, term, str, x, start){
        let _this = this;
        term._core.buffer.lines.get(y).insertCells(x, _this.strLen(str), str);   // 当前x位置插入字符宽度
        // 更新当前_this.inputData[y]字符串
        _this.inputData[y] = `${_this.inputData[y].slice(0,start)}${str}${_this.inputData[y].slice(start)}`;
    
        // 写入字符
        term._core.buffer.lines.get(y).setCellFromCodePoint(
            x, str.charCodeAt(0),1,0,0
        );
        term._core.blur();    // 失焦
        term._core.focus();   // 聚焦
        
        term._core.updateRange(y);    // 更新当前行
        term._core.refresh(0, cols);     // 刷新当前行
        x = x+_this.strLen(str);    // 当前x加上字符宽度

        return x;
    },
    // 更新inputData
    updateInputData(str, cols, y){
        let that = this;
        str = this.prefix + str;
        let strLen = this.strLen(str);
        let col = Math.ceil(strLen/cols);
        
        let _j = 0;
        for(let i=0;i<col;i++){
            that.inputData[y+i] = '';
            for(let j=_j;j<str.length;j++){
                if(that.strLen(that.inputData[y+i]) + that.strLen(str[j]) <= cols){
                    that.inputData[y+i] += str[j];
                }else{
                    _j = j;
                    break;
                }
            }
        }
    },
    // 在某行某列插入字符时返回当前行插入的x坐标
    posInputDataX(str, x){
        if(x == 0 || !str) return 0;
        let total = 0;
        for(let i=0;i<str.length;i++){
            total ++;
            if(/^[\u4e00-\u9fa5]/.test(str[i])){
                total ++;
            }
            if(total == x){
                return i+1;
            }
        }
        return null;
    },
    // 更新input字符串
    updateInput(str, s, pos){
        str = str.slice(0, pos+1) + s + str.slice(pos+1);
        return str.slice(this.prefix.length);
    },
    backspaceRow(term, cols){
        if(this.input.length > 0){
            let temp = [];
            for(let key in this.inputData){
                temp.push(key);
                delete this.inputData[key];
            }
            for(let i=0;i<temp.length;i++){
                term.write('\x1b[2K\r');
                term.write('\x1b[A');
            }
            term.write("\r\n");
            term.write(this.prefix);
        }
    }
  },
};
</script>




/** 
 *  绑定socket到插件
 *  var socket = new WebSocket('wss://docker.example.com/containers/mycontainerid/attach/ws');
    term.attach(socket)

    // socket socoket实例
    // bidirectional 终端是否向套接字发送数据
    // bufferred 终端是否缓冲输出获得更好的性能
    attach(socket: WebSocket, bidirectional: Boolean, bufferred: Boolean)

    // 分离当前终端和scoket
    detach(socket)

    出处作者:Ashin5800
    出处:https://www.cnblogs.com/wzs5800/p/13221344.html
    源码地址:https://github.com/AshinWu/webterminal
*/
</script>

以上为xterm前端操作部分keyCode功能代码,如有bug,请留言,谢谢!

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值