diff --git a/backend/server/dist/index.js b/backend/server/dist/index.js
index e13479f..43ebdcc 100644
--- a/backend/server/dist/index.js
+++ b/backend/server/dist/index.js
@@ -77,7 +77,6 @@ io.on("connection", (socket) => __awaiter(void 0, void 0, void 0, function* () {
fs_1.default.writeFile(filePath, file.data, function (err) {
if (err)
throw err;
- // console.log("Saved File:", file.id)
});
});
socket.emit("loaded", sandboxFiles.files);
@@ -85,7 +84,6 @@ io.on("connection", (socket) => __awaiter(void 0, void 0, void 0, function* () {
const file = sandboxFiles.fileData.find((f) => f.id === fileId);
if (!file)
return;
- // console.log("get file " + file.id + ": ", file.data.slice(0, 10) + "...")
callback(file.data);
});
// todo: send diffs + debounce for efficiency
@@ -94,7 +92,6 @@ io.on("connection", (socket) => __awaiter(void 0, void 0, void 0, function* () {
if (!file)
return;
file.data = body;
- // console.log("save file " + file.id + ": ", file.data)
fs_1.default.writeFile(path_1.default.join(dirName, file.id), body, function (err) {
if (err)
throw err;
@@ -139,15 +136,19 @@ io.on("connection", (socket) => __awaiter(void 0, void 0, void 0, function* () {
cols: 100,
cwd: path_1.default.join(dirName, "projects", data.id),
});
- pty.onData((data) => {
+ const onData = pty.onData((data) => {
console.log(data);
socket.emit("terminalResponse", {
// data: Buffer.from(data, "utf-8").toString("base64"),
data,
});
});
- pty.onExit((code) => console.log("exit :(", code));
- terminals[id] = pty;
+ const onExit = pty.onExit((code) => console.log("exit :(", code));
+ terminals[id] = {
+ terminal: pty,
+ onData,
+ onExit,
+ };
});
socket.on("terminalData", (id, data) => {
// socket.on("terminalData", (data: string) => {
@@ -159,13 +160,22 @@ io.on("connection", (socket) => __awaiter(void 0, void 0, void 0, function* () {
return;
}
try {
- terminals[id].write(data);
+ terminals[id].terminal.write(data);
}
catch (e) {
console.log("Error writing to terminal", e);
}
});
- socket.on("disconnect", () => { });
+ socket.on("disconnect", () => {
+ Object.entries(terminals).forEach((t) => {
+ const { terminal, onData, onExit } = t[1];
+ if (os_1.default.platform() !== "win32")
+ terminal.kill();
+ onData.dispose();
+ onExit.dispose();
+ delete terminals[t[0]];
+ });
+ });
}));
httpServer.listen(port, () => {
console.log(`Server running on port ${port}`);
diff --git a/backend/server/src/index.ts b/backend/server/src/index.ts
index 37f4b09..2f99f8b 100644
--- a/backend/server/src/index.ts
+++ b/backend/server/src/index.ts
@@ -10,7 +10,7 @@ import { z } from "zod"
import { User } from "./types"
import { createFile, getSandboxFiles, renameFile, saveFile } from "./utils"
import { Pty } from "./terminal"
-import { IPty, spawn } from "node-pty"
+import { IDisposable, IPty, spawn } from "node-pty"
dotenv.config()
@@ -24,7 +24,9 @@ const io = new Server(httpServer, {
},
})
-const terminals: { [id: string]: IPty } = {}
+const terminals: {
+ [id: string]: { terminal: IPty; onData: IDisposable; onExit: IDisposable }
+} = {}
const dirName = path.join(__dirname, "..")
@@ -83,7 +85,6 @@ io.on("connection", async (socket) => {
fs.mkdirSync(path.dirname(filePath), { recursive: true })
fs.writeFile(filePath, file.data, function (err) {
if (err) throw err
- // console.log("Saved File:", file.id)
})
})
@@ -93,7 +94,6 @@ io.on("connection", async (socket) => {
const file = sandboxFiles.fileData.find((f) => f.id === fileId)
if (!file) return
- // console.log("get file " + file.id + ": ", file.data.slice(0, 10) + "...")
callback(file.data)
})
@@ -102,7 +102,6 @@ io.on("connection", async (socket) => {
const file = sandboxFiles.fileData.find((f) => f.id === fileId)
if (!file) return
file.data = body
- // console.log("save file " + file.id + ": ", file.data)
fs.writeFile(path.join(dirName, file.id), body, function (err) {
if (err) throw err
@@ -158,7 +157,7 @@ io.on("connection", async (socket) => {
cwd: path.join(dirName, "projects", data.id),
})
- pty.onData((data) => {
+ const onData = pty.onData((data) => {
console.log(data)
socket.emit("terminalResponse", {
// data: Buffer.from(data, "utf-8").toString("base64"),
@@ -166,9 +165,13 @@ io.on("connection", async (socket) => {
})
})
- pty.onExit((code) => console.log("exit :(", code))
+ const onExit = pty.onExit((code) => console.log("exit :(", code))
- terminals[id] = pty
+ terminals[id] = {
+ terminal: pty,
+ onData,
+ onExit,
+ }
})
socket.on("terminalData", (id: string, data: string) => {
@@ -183,13 +186,21 @@ io.on("connection", async (socket) => {
}
try {
- terminals[id].write(data)
+ terminals[id].terminal.write(data)
} catch (e) {
console.log("Error writing to terminal", e)
}
})
- socket.on("disconnect", () => {})
+ socket.on("disconnect", () => {
+ Object.entries(terminals).forEach((t) => {
+ const { terminal, onData, onExit } = t[1]
+ if (os.platform() !== "win32") terminal.kill()
+ onData.dispose()
+ onExit.dispose()
+ delete terminals[t[0]]
+ })
+ })
})
httpServer.listen(port, () => {
diff --git a/backend/server/src/terminal.ts b/backend/server/src/terminal.ts
index 8717e75..d1bc371 100644
--- a/backend/server/src/terminal.ts
+++ b/backend/server/src/terminal.ts
@@ -44,9 +44,9 @@ export class Pty {
// kill() {
// console.log("killing terminal")
- // if (os.platform() !== "win32") {
- // this.ptyProcess.kill()
- // return
- // }
+ // if (os.platform() !== "win32") {
+ // this.ptyProcess.kill()
+ // return
+ // }
// }
}
diff --git a/frontend/components/editor/index.tsx b/frontend/components/editor/index.tsx
index a272b29..d3a8cb2 100644
--- a/frontend/components/editor/index.tsx
+++ b/frontend/components/editor/index.tsx
@@ -30,8 +30,6 @@ import EditorTerminal from "./terminal"
import { Terminal } from "@xterm/xterm"
import { FitAddon } from "@xterm/addon-fit"
-import { decodeTerminalResponse } from "@/lib/utils"
-
export default function CodeEditor({
userId,
sandboxId,
@@ -284,11 +282,11 @@ export default function CodeEditor({
minSize={20}
className="p-2 flex flex-col"
>
-
+
Node
Console
-
+
{socket ? : null}
diff --git a/frontend/components/editor/terminal.tsx b/frontend/components/editor/terminal/index.tsx
similarity index 90%
rename from frontend/components/editor/terminal.tsx
rename to frontend/components/editor/terminal/index.tsx
index 257a9a1..fffc95b 100644
--- a/frontend/components/editor/terminal.tsx
+++ b/frontend/components/editor/terminal/index.tsx
@@ -2,6 +2,7 @@
import { Terminal } from "@xterm/xterm"
import { FitAddon } from "@xterm/addon-fit"
+import "./xterm.css"
import { useEffect, useRef, useState } from "react"
import { Socket } from "socket.io-client"
@@ -14,7 +15,10 @@ export default function EditorTerminal({ socket }: { socket: Socket }) {
if (!terminalRef.current) return
const terminal = new Terminal({
- cursorBlink: false,
+ cursorBlink: true,
+ theme: {
+ background: "#262626",
+ },
})
setTerm(terminal)
@@ -67,9 +71,5 @@ export default function EditorTerminal({ socket }: { socket: Socket }) {
}
}, [term, terminalRef.current])
- return (
-
- )
+ return
}
diff --git a/frontend/components/editor/terminal/xterm.css b/frontend/components/editor/terminal/xterm.css
new file mode 100644
index 0000000..386a214
--- /dev/null
+++ b/frontend/components/editor/terminal/xterm.css
@@ -0,0 +1,219 @@
+/**
+ * Copyright (c) 2014 The xterm.js authors. All rights reserved.
+ * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
+ * https://github.com/chjj/term.js
+ * @license MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * Originally forked from (with the author's permission):
+ * Fabrice Bellard's javascript vt100 for jslinux:
+ * http://bellard.org/jslinux/
+ * Copyright (c) 2011 Fabrice Bellard
+ * The original design remains. The terminal itself
+ * has been extended to include xterm CSI codes, among
+ * other features.
+ */
+
+/**
+ * Default styles for xterm.js
+ */
+
+ .xterm {
+ cursor: text;
+ position: relative;
+ user-select: none;
+ -ms-user-select: none;
+ -webkit-user-select: none;
+ padding: 8px;
+}
+
+.xterm.focus,
+.xterm:focus {
+ outline: none;
+}
+
+.xterm .xterm-helpers {
+ position: absolute;
+ top: 0;
+ /**
+ * The z-index of the helpers must be higher than the canvases in order for
+ * IMEs to appear on top.
+ */
+ z-index: 5;
+}
+
+.xterm .xterm-helper-textarea {
+ padding: 0;
+ border: 0;
+ margin: 0;
+ /* Move textarea out of the screen to the far left, so that the cursor is not visible */
+ position: absolute;
+ opacity: 0;
+ left: -9999em;
+ top: 0;
+ width: 0;
+ height: 0;
+ z-index: -5;
+ /** Prevent wrapping so the IME appears against the textarea at the correct position */
+ white-space: nowrap;
+ overflow: hidden;
+ resize: none;
+}
+
+.xterm .composition-view {
+ /* TODO: Composition position got messed up somewhere */
+ background: transparent;
+ color: #FFF;
+ display: none;
+ position: absolute;
+ white-space: nowrap;
+ z-index: 1;
+}
+
+.xterm .composition-view.active {
+ display: block;
+}
+
+.xterm .xterm-viewport {
+ /* On OS X this is required in order for the scroll bar to appear fully opaque */
+ background-color: transparent;
+ overflow-y: scroll;
+ cursor: default;
+ position: absolute;
+ right: 0;
+ left: 0;
+ top: 0;
+ bottom: 0;
+}
+
+.xterm .xterm-screen {
+ position: relative;
+}
+
+.xterm .xterm-screen canvas {
+ position: absolute;
+ left: 0;
+ top: 0;
+}
+
+.xterm .xterm-scroll-area {
+ visibility: hidden;
+}
+
+.xterm-char-measure-element {
+ display: inline-block;
+ visibility: hidden;
+ position: absolute;
+ top: 0;
+ left: -9999em;
+ line-height: normal;
+}
+
+.xterm.enable-mouse-events {
+ /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
+ cursor: default;
+}
+
+.xterm.xterm-cursor-pointer,
+.xterm .xterm-cursor-pointer {
+ cursor: pointer;
+}
+
+.xterm.column-select.focus {
+ /* Column selection mode */
+ cursor: crosshair;
+}
+
+.xterm .xterm-accessibility:not(.debug),
+.xterm .xterm-message {
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ right: 0;
+ z-index: 10;
+ color: transparent;
+ pointer-events: none;
+}
+
+.xterm .xterm-accessibility-tree:not(.debug) *::selection {
+color: transparent;
+}
+
+.xterm .xterm-accessibility-tree {
+user-select: text;
+white-space: pre;
+}
+
+.xterm .live-region {
+ position: absolute;
+ left: -9999px;
+ width: 1px;
+ height: 1px;
+ overflow: hidden;
+}
+
+.xterm-dim {
+ /* Dim should not apply to background, so the opacity of the foreground color is applied
+ * explicitly in the generated class and reset to 1 here */
+ opacity: 1 !important;
+}
+
+.xterm-underline-1 { text-decoration: underline; }
+.xterm-underline-2 { text-decoration: double underline; }
+.xterm-underline-3 { text-decoration: wavy underline; }
+.xterm-underline-4 { text-decoration: dotted underline; }
+.xterm-underline-5 { text-decoration: dashed underline; }
+
+.xterm-overline {
+ text-decoration: overline;
+}
+
+.xterm-overline.xterm-underline-1 { text-decoration: overline underline; }
+.xterm-overline.xterm-underline-2 { text-decoration: overline double underline; }
+.xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; }
+.xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; }
+.xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; }
+
+.xterm-strikethrough {
+ text-decoration: line-through;
+}
+
+.xterm-screen .xterm-decoration-container .xterm-decoration {
+z-index: 6;
+position: absolute;
+}
+
+.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer {
+z-index: 7;
+}
+
+.xterm-decoration-overview-ruler {
+ z-index: 8;
+ position: absolute;
+ top: 0;
+ right: 0;
+ pointer-events: none;
+}
+
+.xterm-decoration-top {
+ z-index: 2;
+ position: relative;
+}
\ No newline at end of file