diff --git a/7.png b/7.png new file mode 100644 index 00000000..b615872a Binary files /dev/null and b/7.png differ diff --git a/README.md b/README.md index 43f9bc61..93673db2 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ + 重命名文件/目录:`Ctrl + 右键文件/目录` + 切换文档预览显示:`Alt + V` 或点击状态栏 `切换预览` 按钮 + 切换编辑器自动换行:`Alt + W` 或点击状态栏 `切换换行` 按钮 ++ 切换内置终端展示:`Alt + O` 或点击状态栏 `切换终端` 按钮 + 将外链或 BASE64 图片转换为本地图片:`Ctrl + Shift + 单击图片` + 默认文件存储在 `./data` 目录下,添加更多仓库请编辑 `./config.json` 文件 + 默认快捷键参考 [vscode](https://code.visualstudio.com/) @@ -88,6 +89,7 @@ + katex 公式解析 [markdown-it-katex](https://github.com/waylonflinn/markdown-it-katex) + 元素属性书写 [markdown-it-attrs](https://github.com/arve0/markdown-it-attrs) + 表格解析增强,支持表格标题多行文本,列表等特性 [markdown-it-multimd-table](https://github.com/RedBug312/markdown-it-multimd-table) ++ 内置终端 [XTERM.JS](https://xtermjs.org/) [node-pty](https://github.com/Microsoft/node-pty) ## 开发 + 后端: `cd backend; node main.js` @@ -124,6 +126,7 @@ + [x] 支持多根目录 + [x] 状态栏 + [x] toc 生成 ++ [x] 内置终端 + [ ] 代码块渲染增加行号 + [ ] 信息流,定时任务 + [ ] 标签支持 @@ -141,3 +144,4 @@    + diff --git a/backend/main.js b/backend/main.js index c227db49..34563cef 100644 --- a/backend/main.js +++ b/backend/main.js @@ -4,6 +4,7 @@ const bodyParser = require('koa-body') const static = require('koa-static') const mime = require('mime') const request = require('request') +const pty = require('node-pty') const file = require('./file') const dataRepository = require('./repository') @@ -171,6 +172,23 @@ app.use(async (ctx, next) => await wrapper(ctx, next, repository)) app.use(async (ctx, next) => await wrapper(ctx, next, proxy)) const port = 3000 -app.listen(port) + +const server = require('http').createServer(app.callback()) +const io = require('socket.io')(server, {path: '/ws'}) + +io.on('connection', socket => { + const ptyProcess = pty.spawn('bash', ['--login'], { + name: 'xterm-color', + cols: 80, + rows: 24, + cwd: path.join(__dirname, '../'), + env: process.env + }) + ptyProcess.on('data', data => socket.emit('output', data)) + socket.on('input', data => ptyProcess.write(data)) + socket.on('resize', size => ptyProcess.resize(size[0], size[1])) +}) + +server.listen(port) console.log(`访问地址:http://localhost:${port}`) diff --git a/backend/package.json b/backend/package.json index 0baa70e5..8162baeb 100644 --- a/backend/package.json +++ b/backend/package.json @@ -7,8 +7,10 @@ "mime": "^2.2.0", "natural-orderby": "^1.0.2", "node-plantuml": "^0.6.2", + "node-pty": "^0.8.1", "opn": "^5.3.0", "request": "^2.88.0", - "safe-buffer": "^5.1.2" + "safe-buffer": "^5.1.2", + "socket.io": "^2.2.0" } } diff --git a/backend/yarn.lock b/backend/yarn.lock index 2ca2a585..dd53b0c1 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -9,6 +9,17 @@ accepts@^1.2.2: mime-types "~2.1.16" negotiator "0.6.1" +accepts@~1.3.4: + version "1.3.5" + resolved "http://registry.npm.taobao.org/accepts/download/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" + dependencies: + mime-types "~2.1.18" + negotiator "0.6.1" + +after@0.8.2: + version "0.8.2" + resolved "http://registry.npm.taobao.org/after/download/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" + ajv@^5.3.0: version "5.5.2" resolved "http://registry.npm.taobao.org/ajv/download/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" @@ -22,6 +33,10 @@ any-promise@^1.0.0, any-promise@^1.1.0: version "1.3.0" resolved "http://registry.npm.taobao.org/any-promise/download/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" +arraybuffer.slice@~0.0.7: + version "0.0.7" + resolved "http://registry.npm.taobao.org/arraybuffer.slice/download/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" + asn1@~0.2.3: version "0.2.4" resolved "http://registry.npm.taobao.org/asn1/download/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" @@ -32,6 +47,10 @@ assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "http://registry.npm.taobao.org/assert-plus/download/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" +async-limiter@~1.0.0: + version "1.0.0" + resolved "http://registry.npm.taobao.org/async-limiter/download/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" + asynckit@^0.4.0: version "0.4.0" resolved "http://registry.npm.taobao.org/asynckit/download/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -44,16 +63,42 @@ aws4@^1.8.0: version "1.8.0" resolved "http://registry.npm.taobao.org/aws4/download/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" +backo2@1.0.2: + version "1.0.2" + resolved "http://registry.npm.taobao.org/backo2/download/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" + +base64-arraybuffer@0.1.5: + version "0.1.5" + resolved "http://registry.npm.taobao.org/base64-arraybuffer/download/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" + +base64id@1.0.0: + version "1.0.0" + resolved "http://registry.npm.taobao.org/base64id/download/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" + bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "http://registry.npm.taobao.org/bcrypt-pbkdf/download/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" dependencies: tweetnacl "^0.14.3" +better-assert@~1.0.0: + version "1.0.2" + resolved "http://registry.npm.taobao.org/better-assert/download/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" + dependencies: + callsite "1.0.0" + +blob@0.0.5: + version "0.0.5" + resolved "http://registry.npm.taobao.org/blob/download/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" + bytes@3.0.0: version "3.0.0" resolved "http://registry.npm.taobao.org/bytes/download/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" +callsite@1.0.0: + version "1.0.0" + resolved "http://registry.npm.taobao.org/callsite/download/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" + caseless@~0.12.0: version "0.12.0" resolved "http://registry.npm.taobao.org/caseless/download/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -81,6 +126,18 @@ commander@^2.8.1: version "2.16.0" resolved "http://registry.npm.taobao.org/commander/download/commander-2.16.0.tgz#f16390593996ceb4f3eeb020b31d78528f7f8a50" +component-bind@1.0.0: + version "1.0.0" + resolved "http://registry.npm.taobao.org/component-bind/download/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" + +component-emitter@1.2.1: + version "1.2.1" + resolved "http://registry.npm.taobao.org/component-emitter/download/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + +component-inherit@0.0.3: + version "0.0.3" + resolved "http://registry.npm.taobao.org/component-inherit/download/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" + content-disposition@~0.5.0: version "0.5.2" resolved "http://registry.npm.taobao.org/content-disposition/download/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" @@ -89,6 +146,10 @@ content-type@^1.0.0: version "1.0.4" resolved "http://registry.npm.taobao.org/content-type/download/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" +cookie@0.3.1: + version "0.3.1" + resolved "http://registry.npm.taobao.org/cookie/download/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + cookies@~0.7.0: version "0.7.1" resolved "http://registry.npm.taobao.org/cookies/download/cookies-0.7.1.tgz#7c8a615f5481c61ab9f16c833731bcb8f663b99b" @@ -106,7 +167,7 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -debug@*: +debug@*, debug@~3.1.0: version "3.1.0" resolved "http://registry.npm.taobao.org/debug/download/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" dependencies: @@ -118,6 +179,12 @@ debug@^2.6.3, debug@^2.6.8: dependencies: ms "2.0.0" +debug@~4.1.0: + version "4.1.1" + resolved "http://registry.npm.taobao.org/debug/download/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + dependencies: + ms "^2.1.1" + deep-equal@~1.0.1: version "1.0.1" resolved "http://registry.npm.taobao.org/deep-equal/download/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -153,6 +220,43 @@ ee-first@1.1.1: version "1.1.1" resolved "http://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" +engine.io-client@~3.3.1: + version "3.3.2" + resolved "http://registry.npm.taobao.org/engine.io-client/download/engine.io-client-3.3.2.tgz#04e068798d75beda14375a264bb3d742d7bc33aa" + dependencies: + component-emitter "1.2.1" + component-inherit "0.0.3" + debug "~3.1.0" + engine.io-parser "~2.1.1" + has-cors "1.1.0" + indexof "0.0.1" + parseqs "0.0.5" + parseuri "0.0.5" + ws "~6.1.0" + xmlhttprequest-ssl "~1.5.4" + yeast "0.1.2" + +engine.io-parser@~2.1.0, engine.io-parser@~2.1.1: + version "2.1.3" + resolved "http://registry.npm.taobao.org/engine.io-parser/download/engine.io-parser-2.1.3.tgz#757ab970fbf2dfb32c7b74b033216d5739ef79a6" + dependencies: + after "0.8.2" + arraybuffer.slice "~0.0.7" + base64-arraybuffer "0.1.5" + blob "0.0.5" + has-binary2 "~1.0.2" + +engine.io@~3.3.1: + version "3.3.2" + resolved "http://registry.npm.taobao.org/engine.io/download/engine.io-3.3.2.tgz#18cbc8b6f36e9461c5c0f81df2b830de16058a59" + dependencies: + accepts "~1.3.4" + base64id "1.0.0" + cookie "0.3.1" + debug "~3.1.0" + engine.io-parser "~2.1.0" + ws "~6.1.0" + error-inject@~1.0.0: version "1.0.0" resolved "http://registry.npm.taobao.org/error-inject/download/error-inject-1.0.0.tgz#e2b3d91b54aed672f309d950d154850fa11d4f37" @@ -230,6 +334,16 @@ har-validator@~5.1.0: ajv "^5.3.0" har-schema "^2.0.0" +has-binary2@~1.0.2: + version "1.0.3" + resolved "http://registry.npm.taobao.org/has-binary2/download/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" + dependencies: + isarray "2.0.1" + +has-cors@1.1.0: + version "1.1.0" + resolved "http://registry.npm.taobao.org/has-cors/download/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" + http-assert@^1.1.0: version "1.3.0" resolved "http://registry.npm.taobao.org/http-assert/download/http-assert-1.3.0.tgz#a31a5cf88c873ecbb5796907d4d6f132e8c01e4a" @@ -258,6 +372,10 @@ iconv-lite@0.4.19: version "0.4.19" resolved "http://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" +indexof@0.0.1: + version "0.0.1" + resolved "http://registry.npm.taobao.org/indexof/download/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" + inflation@^2.0.0: version "2.0.0" resolved "http://registry.npm.taobao.org/inflation/download/inflation-2.0.0.tgz#8b417e47c28f925a45133d914ca1fd389107f30f" @@ -278,6 +396,10 @@ is-wsl@^1.1.0: version "1.1.0" resolved "http://registry.npm.taobao.org/is-wsl/download/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" +isarray@2.0.1: + version "2.0.1" + resolved "http://registry.npm.taobao.org/isarray/download/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" + isstream@~0.1.2: version "0.1.2" resolved "http://registry.npm.taobao.org/isstream/download/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -402,6 +524,10 @@ mime-db@~1.35.0: version "1.35.0" resolved "http://registry.npm.taobao.org/mime-db/download/mime-db-1.35.0.tgz#0569d657466491283709663ad379a99b90d9ab47" +mime-db@~1.37.0: + version "1.37.0" + resolved "http://registry.npm.taobao.org/mime-db/download/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" + mime-types@^2.0.7, mime-types@~2.1.15, mime-types@~2.1.16: version "2.1.17" resolved "http://registry.npm.taobao.org/mime-types/download/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" @@ -414,6 +540,12 @@ mime-types@^2.1.12, mime-types@~2.1.19: dependencies: mime-db "~1.35.0" +mime-types@~2.1.18: + version "2.1.21" + resolved "http://registry.npm.taobao.org/mime-types/download/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" + dependencies: + mime-db "~1.37.0" + mime@^2.2.0: version "2.2.0" resolved "http://registry.npm.taobao.org/mime/download/mime-2.2.0.tgz#161e541965551d3b549fa1114391e3a3d55b923b" @@ -422,6 +554,10 @@ ms@2.0.0: version "2.0.0" resolved "http://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" +ms@^2.1.1: + version "2.1.1" + resolved "http://registry.npm.taobao.org/ms/download/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + mz@^2.6.0: version "2.7.0" resolved "http://registry.npm.taobao.org/mz/download/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" @@ -430,6 +566,10 @@ mz@^2.6.0: object-assign "^4.0.1" thenify-all "^1.0.0" +nan@2.12.1: + version "2.12.1" + resolved "http://registry.npm.taobao.org/nan/download/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" + natural-orderby@^1.0.2: version "1.0.2" resolved "http://registry.npm.taobao.org/natural-orderby/download/natural-orderby-1.0.2.tgz#287a4778217fbf6af0d704129d55ae90450f7a88" @@ -459,6 +599,12 @@ node-plantuml@^0.6.2: node-nailgun-server "^0.1.3" plantuml-encoder "^1.2.5" +node-pty@^0.8.1: + version "0.8.1" + resolved "http://registry.npm.taobao.org/node-pty/download/node-pty-0.8.1.tgz#94b457bec013e7a09b8d9141f63b0787fa25c23f" + dependencies: + nan "2.12.1" + oauth-sign@~0.9.0: version "0.9.0" resolved "http://registry.npm.taobao.org/oauth-sign/download/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" @@ -467,6 +613,10 @@ object-assign@^4.0.1: version "4.1.1" resolved "http://registry.npm.taobao.org/object-assign/download/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" +object-component@0.0.3: + version "0.0.3" + resolved "http://registry.npm.taobao.org/object-component/download/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" + on-finished@^2.1.0: version "2.3.0" resolved "http://registry.npm.taobao.org/on-finished/download/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -487,6 +637,18 @@ pako@1.0.3: version "1.0.3" resolved "http://registry.npm.taobao.org/pako/download/pako-1.0.3.tgz#5f515b0c6722e1982920ae8005eacb0b7ca73ccf" +parseqs@0.0.5: + version "0.0.5" + resolved "http://registry.npm.taobao.org/parseqs/download/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" + dependencies: + better-assert "~1.0.0" + +parseuri@0.0.5: + version "0.0.5" + resolved "http://registry.npm.taobao.org/parseuri/download/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" + dependencies: + better-assert "~1.0.0" + parseurl@^1.3.0: version "1.3.2" resolved "http://registry.npm.taobao.org/parseurl/download/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" @@ -575,6 +737,48 @@ setprototypeof@1.0.3: version "1.0.3" resolved "http://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" +socket.io-adapter@~1.1.0: + version "1.1.1" + resolved "http://registry.npm.taobao.org/socket.io-adapter/download/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b" + +socket.io-client@2.2.0: + version "2.2.0" + resolved "http://registry.npm.taobao.org/socket.io-client/download/socket.io-client-2.2.0.tgz#84e73ee3c43d5020ccc1a258faeeb9aec2723af7" + dependencies: + backo2 "1.0.2" + base64-arraybuffer "0.1.5" + component-bind "1.0.0" + component-emitter "1.2.1" + debug "~3.1.0" + engine.io-client "~3.3.1" + has-binary2 "~1.0.2" + has-cors "1.1.0" + indexof "0.0.1" + object-component "0.0.3" + parseqs "0.0.5" + parseuri "0.0.5" + socket.io-parser "~3.3.0" + to-array "0.1.4" + +socket.io-parser@~3.3.0: + version "3.3.0" + resolved "http://registry.npm.taobao.org/socket.io-parser/download/socket.io-parser-3.3.0.tgz#2b52a96a509fdf31440ba40fed6094c7d4f1262f" + dependencies: + component-emitter "1.2.1" + debug "~3.1.0" + isarray "2.0.1" + +socket.io@^2.2.0: + version "2.2.0" + resolved "http://registry.npm.taobao.org/socket.io/download/socket.io-2.2.0.tgz#f0f633161ef6712c972b307598ecd08c9b1b4d5b" + dependencies: + debug "~4.1.0" + engine.io "~3.3.1" + has-binary2 "~1.0.2" + socket.io-adapter "~1.1.0" + socket.io-client "2.2.0" + socket.io-parser "~3.3.0" + sshpk@^1.7.0: version "1.14.2" resolved "http://registry.npm.taobao.org/sshpk/download/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98" @@ -606,6 +810,10 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" +to-array@0.1.4: + version "0.1.4" + resolved "http://registry.npm.taobao.org/to-array/download/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" + tough-cookie@~2.4.3: version "2.4.3" resolved "http://registry.npm.taobao.org/tough-cookie/download/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" @@ -657,3 +865,17 @@ verror@1.10.0: assert-plus "^1.0.0" core-util-is "1.0.2" extsprintf "^1.2.0" + +ws@~6.1.0: + version "6.1.3" + resolved "http://registry.npm.taobao.org/ws/download/ws-6.1.3.tgz#d2d2e5f0e3c700ef2de89080ebc0ac6e1bf3a72d" + dependencies: + async-limiter "~1.0.0" + +xmlhttprequest-ssl@~1.5.4: + version "1.5.5" + resolved "http://registry.npm.taobao.org/xmlhttprequest-ssl/download/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" + +yeast@0.1.2: + version "0.1.2" + resolved "http://registry.npm.taobao.org/yeast/download/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" diff --git a/frontend/config/index.js b/frontend/config/index.js index eb8a7c1a..fc6f02de 100644 --- a/frontend/config/index.js +++ b/frontend/config/index.js @@ -13,6 +13,10 @@ module.exports = { proxyTable: { '/api': { target: 'http://localhost:3000' + }, + '/ws': { + target: 'http://localhost:3000', + ws: true } }, diff --git a/frontend/package.json b/frontend/package.json index 53ead003..4c2484d3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,9 +26,11 @@ "mermaid": "^7.1.2", "monaco-editor": "^0.13.1", "normalize.css": "^7.0.0", + "socket.io-client": "^2.2.0", "vue": "^2.5.2", "vue-bus": "^1.1.0", - "vue-router": "^3.0.1" + "vue-router": "^3.0.1", + "xterm": "^3.11.0" }, "devDependencies": { "autoprefixer": "^7.1.2", diff --git a/frontend/src/components/RunPlugin.js b/frontend/src/components/RunPlugin.js index b82e69b5..b85e2cfd 100644 --- a/frontend/src/components/RunPlugin.js +++ b/frontend/src/components/RunPlugin.js @@ -43,9 +43,15 @@ const RunPlugin = (md) => { resultDiv.innerText = e.message }) ` + const runInXterm = ` + var code = '${encodeURIComponent(code).replace(/'/g, '\\\'')}' + code = decodeURIComponent(code) + runInXterm('${token.info}', code, !arguments[0].ctrlKey) + ` + const result = localStorage[`${cachePrefix}${hash}`] || '' - const resultDiv = `