Skip to content

Commit

Permalink
xterm
Browse files Browse the repository at this point in the history
  • Loading branch information
chenyan-dfinity committed Sep 6, 2024
1 parent 9acc9ce commit df3bd92
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 50 deletions.
29 changes: 6 additions & 23 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"@types/react-dom": "^18.3.0",
"@types/styled-components": "^5.1.34",
"@webcontainer/api": "^1.2.4",
"ansi-to-html": "^0.7.2",
"@xterm/xterm": "^5.5.0",
"lodash.debounce": "^4.0.8",
"motoko": "^3.8.1",
"prettier-plugin-motoko": "^0.9.4",
Expand Down
11 changes: 9 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import { ProjectModal } from "./components/ProjectModal";
import { DeployModal, DeploySetter } from "./components/DeployModal";
import { backend, saved } from "./config/actor";
import { setupEditorIntegration } from "./integrations/editorIntegration";
import { npmRun, loadContainer } from "./webcontainer";
import { npmRun } from "./webcontainer";
import { Terminal } from "@xterm/xterm";

const MOC_VERSION = "0.12.1";

Expand Down Expand Up @@ -181,6 +182,11 @@ export function App() {
};

const logger = useLogging();
const terminal = new Terminal({
cursorBlink: true,
fontSize: 14,
fontFamily: "monospace",
});

function closeProjectModal() {
setIsProjectModalOpen(false);
Expand Down Expand Up @@ -242,7 +248,7 @@ export function App() {
});
logger.log(`moc version ${MOC_VERSION}`);
logger.log(`base library version ${baseInfo.version}`);
await npmRun(logger.log);
await npmRun(terminal);
// fetch code after loading base library
if (hasUrlParams) {
const files = await fetchFromUrlParams(workplaceDispatch);
Expand Down Expand Up @@ -318,6 +324,7 @@ export function App() {
<Editor
state={workplaceState}
logger={logger}
terminal={terminal}
deploySetter={deploySetter}
isDeploying={isDeploying}
setConsoleHeight={setConsoleHeight}
Expand Down
50 changes: 48 additions & 2 deletions src/components/Console.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { PanelHeader } from "./shared/PanelHeader";
import { RightContainer } from "./shared/RightContainer";
import { useLogging } from "./Logger";
import iconCaretDown from "../assets/images/icon-caret-down.svg";
import { Terminal } from "@xterm/xterm";
import "@xterm/xterm/css/xterm.css";

const LogHeader = styled(PanelHeader)`
padding: 0 1rem;
Expand All @@ -24,11 +26,40 @@ const Button = styled.button`
const CollapseIcon = styled.img<{ isExpanded: boolean }>`
${(props) => (!props.isExpanded ? "transform: rotate(180deg);" : "")}
`;
const TabContainer = styled.div`
display: flex;
border-bottom: 1px solid var(--grey300);
`;

const Tab = styled.button<{ active: boolean }>`
padding: 0.5rem 1rem;
background: ${(props) => (props.active ? "var(--grey200)" : "transparent")};
border: none;
cursor: pointer;
`;

const TerminalContainer = styled.div<{ isActive: boolean }>`
height: calc(var(--consoleHeight) - 2.4rem);
padding: 0.5rem;
display: ${(props) => (props.isActive ? "block" : "none")};
`;

export function Console({ setConsoleHeight, terminal }) {
const [activeTab, setActiveTab] = useState("terminal");
const terminalRef = useRef<HTMLDivElement>(null);

export function Console({ setConsoleHeight }) {
const [isExpanded, setIsExpanded] = useState(true);
const lastRef = useRef<HTMLInputElement>(null);
const logger = useLogging();
useEffect(() => {
if (
terminalRef.current &&
terminal &&
!terminalRef.current.querySelector(".xterm")
) {
terminal.open(terminalRef.current);
}
}, [terminal]);
useEffect(() => {
if (lastRef && lastRef.current) {
lastRef.current.scrollIntoView({ behavior: "smooth" });
Expand All @@ -54,7 +85,18 @@ export function Console({ setConsoleHeight }) {
</Button>
</RightContainer>
</LogHeader>
<LogContent>
<TabContainer>
<Tab active={activeTab === "log"} onClick={() => setActiveTab("log")}>
Log
</Tab>
<Tab
active={activeTab === "terminal"}
onClick={() => setActiveTab("terminal")}
>
Terminal
</Tab>
</TabContainer>
<LogContent style={{ display: activeTab === "log" ? "block" : "none" }}>
{logger.logLines.map((line, index) => (
<div
key={index}
Expand All @@ -70,6 +112,10 @@ export function Console({ setConsoleHeight }) {
</div>
))}
</LogContent>
<TerminalContainer
ref={terminalRef}
isActive={activeTab === "terminal"}
/>
</>
);
}
9 changes: 5 additions & 4 deletions src/components/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ type CodeEditor = import("monaco-editor").editor.IStandaloneCodeEditor;
export function Editor({
state,
logger,
terminal,
setConsoleHeight,
deploySetter,
isDeploying,
Expand All @@ -107,8 +108,8 @@ export function Editor({
const mainFile = fileName.endsWith(".mo")
? fileName
: state.files["Main.mo"]
? "Main.mo"
: "";
? "Main.mo"
: "";

const monaco = useMonaco();
const checkFileAddMarkers = async () => {
Expand All @@ -120,7 +121,7 @@ export function Editor({
// @ts-ignore
monaco?.editor.getModel(`file:///${fileName}`),
monaco,
fileName
fileName,
);
};
const saveChanges = async () => {
Expand Down Expand Up @@ -253,7 +254,7 @@ export function Editor({
}}
/>
</EditorContainer>
<Console setConsoleHeight={setConsoleHeight} />
<Console setConsoleHeight={setConsoleHeight} terminal={terminal} />
</EditorColumn>
);
}
22 changes: 4 additions & 18 deletions src/webcontainer.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { WebContainer } from "@webcontainer/api";
import Convert from "ansi-to-html";
import React from "react";

let containerPromise: Promise<WebContainer> | null = null;
const convert = new Convert();

export const loadContainer = async () => {
if (!containerPromise) {
Expand All @@ -28,27 +25,16 @@ export const loadContainer = async () => {
return containerPromise;
};

export async function npmRun(
logCallback: (line: string | React.ReactNode) => void,
) {
export async function npmRun(terminal) {
let container = await loadContainer();
const installProcess = await container.spawn("npm", ["install"]);
installProcess.output.pipeTo(
new WritableStream({
write(text) {
text.split("\n").forEach((line) => {
if (line.trim() !== "") {
// Convert ANSI to HTML
const htmlLine = convert.toHtml(line);
const htmlElement = React.createElement("pre", {
dangerouslySetInnerHTML: { __html: htmlLine },
});
logCallback(htmlElement);
}
});
write(data) {
terminal.write(data);
},
}),
);
const exitCode = await installProcess.exit;
logCallback(`npm install exited with code ${exitCode}`);
terminal.writeln(`\r\nnpm install exited with code ${exitCode}`);
}

0 comments on commit df3bd92

Please sign in to comment.