diff --git a/package-lock.json b/package-lock.json index 19cac402f0..9510374360 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5880,6 +5880,19 @@ "node": ">=4.0" } }, + "node_modules/xterm": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xterm/-/xterm-5.1.0.tgz", + "integrity": "sha512-LovENH4WDzpwynj+OTkLyZgJPeDom9Gra4DMlGAgz6pZhIDCQ+YuO7yfwanY+gVbn/mmZIStNOnVRU/ikQuAEQ==" + }, + "node_modules/xterm-addon-fit": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.7.0.tgz", + "integrity": "sha512-tQgHGoHqRTgeROPnvmtEJywLKoC/V9eNs4bLLz7iyJr1aW/QFzRwfd3MGiJ6odJd9xEfxcW36/xRU47JkD5NKQ==", + "peerDependencies": { + "xterm": "^5.0.0" + } + }, "node_modules/yallist": { "version": "4.0.0", "dev": true, @@ -6198,7 +6211,9 @@ "packages/web": { "version": "0.0.0", "dependencies": { - "codemirror": "^5.65.9" + "codemirror": "^5.65.9", + "xterm": "^5.1.0", + "xterm-addon-fit": "^0.7.0" } } }, @@ -9871,7 +9886,9 @@ "web": { "version": "file:packages/web", "requires": { - "codemirror": "^5.65.9" + "codemirror": "^5.65.9", + "xterm": "^5.1.0", + "xterm-addon-fit": "*" } }, "which": { @@ -9954,6 +9971,17 @@ "version": "11.0.1", "dev": true }, + "xterm": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xterm/-/xterm-5.1.0.tgz", + "integrity": "sha512-LovENH4WDzpwynj+OTkLyZgJPeDom9Gra4DMlGAgz6pZhIDCQ+YuO7yfwanY+gVbn/mmZIStNOnVRU/ikQuAEQ==" + }, + "xterm-addon-fit": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.7.0.tgz", + "integrity": "sha512-tQgHGoHqRTgeROPnvmtEJywLKoC/V9eNs4bLLz7iyJr1aW/QFzRwfd3MGiJ6odJd9xEfxcW36/xRU47JkD5NKQ==", + "requires": {} + }, "yallist": { "version": "4.0.0", "dev": true diff --git a/packages/web/package.json b/packages/web/package.json index 25449b39fe..2b21846455 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,9 +2,10 @@ "name": "web", "private": true, "version": "0.0.0", - "scripts": { - }, + "scripts": {}, "dependencies": { - "codemirror": "^5.65.9" + "codemirror": "^5.65.9", + "xterm": "^5.1.0", + "xterm-addon-fit": "^0.7.0" } } diff --git a/packages/web/src/components/xTermWrapper.css b/packages/web/src/components/xTermWrapper.css new file mode 100644 index 0000000000..895ccaaafd --- /dev/null +++ b/packages/web/src/components/xTermWrapper.css @@ -0,0 +1,22 @@ +/* + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +@import '../third_party/vscode/colors.css'; + +.xterm-wrapper .xterm-viewport { + background-color: var(--vscode-panel-background) !important; + color: var(--vscode-foreground) !important; +} diff --git a/packages/web/src/components/xtermWrapper.tsx b/packages/web/src/components/xtermWrapper.tsx new file mode 100644 index 0000000000..80b2b459ca --- /dev/null +++ b/packages/web/src/components/xtermWrapper.tsx @@ -0,0 +1,58 @@ +/* + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import * as React from 'react'; +import { Terminal } from 'xterm'; +import { FitAddon } from 'xterm-addon-fit'; +import 'xterm/css/xterm.css'; +import './xtermWrapper.css'; + +export type XTermDataSource = { + pending: (string | Uint8Array)[]; + write: (data: string | Uint8Array) => void; + resize: (cols: number, rows: number) => void; +}; + +export const XTermWrapper: React.FC<{ source: XTermDataSource }> = ({ + source +}) => { + const xtermElement = React.createRef(); + const [terminal, setTerminal] = React.useState(); + React.useEffect(() => { + if (terminal) + return; + if (!xtermElement.current) + return; + const newTerminal = new Terminal({ convertEol: true }); + const fitAddon = new FitAddon(); + newTerminal.loadAddon(fitAddon); + for (const p of source.pending) + newTerminal.write(p); + source.write = (data => { + newTerminal.write(data); + }); + newTerminal.open(xtermElement.current); + setTerminal(newTerminal); + fitAddon.fit(); + const resizeObserver = new ResizeObserver(() => { + source.resize(newTerminal.cols, newTerminal.rows); + fitAddon.fit(); + }); + resizeObserver.observe(xtermElement.current); + }, [terminal, xtermElement, source]); + return
+
; +};