xterm.js是什么?
xterm 是大多数 web-terminal 的解决方案,如vscode、atom等等。支持 bash,vim 和 tmux。并且还支持webgl渲染。
安装
1 2 3 4 5 6 7 8 9
| // 1、安装 xterm npm install --save xterm
// 2、安装xterm-addon-fit // xterm.js的插件,使终端的尺寸适合包含元素。 npm install --save xterm-addon-fit
// 3、支持打开链接 npm install --save xterm-addon-web-links
|
初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { Terminal } from 'xterm'; import { FitAddon } from 'xterm-addon-fit' import { WebLinksAddon } from 'xterm-addon-web-links'; import 'xterm/css/xterm.css'
const initTerminal = (terminal) => { const fitAddon = new FitAddon()
terminal.open(document.getElementById('terminal-container')) terminal.loadAddon(fitAddon) // terminal 的尺寸与父元素匹配 fitAddon.fit() terminal.focus()
terminal.prompt = () => { terminal.write('Samonnite $') }
terminal.writeln('\x1b[1;1;32mwellcom to web terminal!\x1b[0m') terminal.prompt()
setTerminal(terminal) }
|
主要方法:监听、写入
1 2 3
| terminal!.onData((data: string) => { // xxxx }
|
纯前端实现也可以使用terminal!.onKey方法
1
| terminal!.write('Hello World');
|
前端只有在完成这两项工作后才能使用,否则只是个黑板。。。
通过onKey监听键盘实现纯前端Terminal
- 正常键盘输入后写入
- ctrl、command、shift、alt等特殊按键,实现删除、复制粘贴
- 上下左右键切换实现光标位置变化及命令回溯
- 回车提交换行
1 2 3 4 5 6 7 8 9
| const TERMINAL_INPUT_KEY = { BACK: 8, // 退格删除键 ENTER: 13, // 回车键 UP: 38, // 方向盘上键 DOWN: 40, // 方向盘键 LEFT: 37, // 方向盘左键 RIGHT: 39, // 方向盘右键 CHAR_C: 67 // 字符C }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| terminal.onKey(e => { const { key } = e const { keyCode, altKey, altGraphKey, ctrlKey, metaKey } = e.domEvent
const printAble = !(altKey || altGraphKey || ctrlKey || metaKey) // 禁止相关按键 const totalOffsetLength = inputText.length + prefix.length // 总偏移量 let currentOffsetLength = terminal._core.buffer.x // 当前x偏移量 console.log('currentOffsetLength ========= ', currentOffsetLength)
switch(keyCode) { case TERMINAL_INPUT_KEY.ENTER: handleInputText() inputText = '' break
case TERMINAL_INPUT_KEY.BACK: if (currentOffsetLength > prefix.length) { const cursorOffSetLength = getCursorOffsetLength(totalOffsetLength - currentOffsetLength, '\x1b[D') // 保留原来光标位置 terminal._core.buffer.x = currentOffsetLength - 1 terminal.write('\x1b[?K' + inputText.slice(currentOffsetLength-prefix.length)) terminal.write(cursorOffSetLength) inputText = `${inputText.slice(0, currentOffsetLength - prefix.length - 1)}${inputText.slice(currentOffsetLength - prefix.length)}` } break
case TERMINAL_INPUT_KEY.UP: { if (!inputTextList[currentIndex - 1]) break
const offsetLength = getCursorOffsetLength(inputText.length, '\x1b[D')
inputText = inputTextList[currentIndex - 1] terminal.write(offsetLength + '\x1b[?K' ) terminal.write(inputTextList[currentIndex - 1]) terminal._core.buffer.x = totalOffsetLength currentIndex-- break }
case TERMINAL_INPUT_KEY.DOWN: { if (!inputTextList[currentIndex + 1]) break
// 构造退格(模拟替换效果) \b \b标识退一格; \b\b \b\b表示退两格... const backLength = getCursorOffsetLength(inputTextList[currentIndex].length, '\b') const blackLength = getCursorOffsetLength(inputTextList[currentIndex].length, ' ') inputText = inputTextList[currentIndex + 1] terminal.write(backLength + blackLength + backLength) terminal.write(inputTextList[currentIndex + 1]) terminal._core.buffer.x = totalOffsetLength currentIndex++ break }
case TERMINAL_INPUT_KEY.LEFT: if (currentOffsetLength > prefix.length) { terminal.write(key) } break
case TERMINAL_INPUT_KEY.RIGHT: if (currentOffsetLength < totalOffsetLength) { terminal.write(key) } break
default: if(keyCode === TERMINAL_INPUT_KEY.CHAR_C && ctrlKey) { handleCtrlC() break } if (!printAble) break if (totalOffsetLength >= terminal.cols) break if (currentOffsetLength >= totalOffsetLength) { terminal.write(key) inputText += key break } const cursorOffSetLength = getCursorOffsetLength(totalOffsetLength - currentOffsetLength, '\x1b[D') terminal.write( '\x1b[?K' + `${key}${inputText.slice(currentOffsetLength - prefix.length)}`) // 在当前的坐标写上 key 和坐标后面的字符 terminal.write(cursorOffSetLength) // 移动停留在当前位置的光标 inputText = inputText.slice(0, currentOffsetLength) + key + inputText.slice(totalOffsetLength - currentOffsetLength) } })
|
实际生产项目中纯前端实现各种命令开发成本非常高,需利用websocket与后端通信,完成输入输出。
此时前端只负责输入、显示,所有的命令交互都交给后端来做。
如node-pty、gotty等等。
踩过的坑
- 屏幕onResize时需根据父元素宽高匹配行列数,xterm-addon-fit无法满足需求,总是会出现无法自适应屏幕问题,文字被遮挡。
解决方案:
获取父元素宽高,文字宽高,并配置rows、cols,即行数及列数。
vscode解决方案
- vim模式下粘贴内容丢失
解决方案:
对文本进行分片,以此进行输入。
其他