diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 7bb1106dc1..cd99f9dcfb 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -50,6 +50,7 @@ import { fsPlugin } from './app/plugins/electron/fsPlugin' import { isoGitPlugin } from './app/plugins/electron/isoGitPlugin' import { electronConfig } from './app/plugins/electron/electronConfigPlugin' import { electronTemplates } from './app/plugins/electron/templatesPlugin' +import { xtermPlugin } from './app/plugins/electron/xtermPlugin' @@ -340,6 +341,8 @@ class AppComponent { this.engine.register([electronConfigPlugin]) const templatesPlugin = new electronTemplates() this.engine.register([templatesPlugin]) + const xterm = new xtermPlugin() + this.engine.register([xterm]) } // LAYOUT & SYSTEM VIEWS @@ -458,7 +461,7 @@ class AppComponent { await this.appManager.activatePlugin(['solidity-script', 'remix-templates']) if(isElectron()){ - await this.appManager.activatePlugin(['fs', 'isogit', 'electronconfig', 'electronTemplates']) + await this.appManager.activatePlugin(['fs', 'isogit', 'electronconfig', 'electronTemplates', 'xterm']) } this.appManager.on( diff --git a/apps/remix-ide/src/app/plugins/electron/xtermPlugin.ts b/apps/remix-ide/src/app/plugins/electron/xtermPlugin.ts new file mode 100644 index 0000000000..960c48b5b3 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/electron/xtermPlugin.ts @@ -0,0 +1,15 @@ +import { ElectronPlugin } from '@remixproject/engine-electron'; + +export class xtermPlugin extends ElectronPlugin { + constructor(){ + super({ + displayName: 'xterm', + name: 'xterm', + description: 'xterm', + }) + } + + onActivation(){ + console.log('xtermPlugin Activated') + } +} \ No newline at end of file diff --git a/apps/remixdesktop/package.json b/apps/remixdesktop/package.json index 28d449e238..deca2d378e 100644 --- a/apps/remixdesktop/package.json +++ b/apps/remixdesktop/package.json @@ -26,7 +26,8 @@ "start:production": "tsc && cross-env NODE_ENV=production electron .", "pack": "tsc && electron-builder", "dist": "tsc && electron-builder", - "postinstall": "electron-builder install-app-deps" + "postinstall": "electron-builder install-app-deps", + "rebuild-node-pty": "electron-rebuild -f -o node-pty" }, "devDependencies": { "@electron/rebuild": "^3.2.13", diff --git a/apps/remixdesktop/src/plugins/fsPlugin.ts b/apps/remixdesktop/src/plugins/fsPlugin.ts index 9168c99e40..b1c7a722d4 100644 --- a/apps/remixdesktop/src/plugins/fsPlugin.ts +++ b/apps/remixdesktop/src/plugins/fsPlugin.ts @@ -55,7 +55,7 @@ export class FSPlugin extends ElectronBasePlugin { } }else{ console.log('no opened folders') - //createWindow() + createWindow() } } diff --git a/apps/remixdesktop/src/plugins/xtermPlugin.ts b/apps/remixdesktop/src/plugins/xtermPlugin.ts index 8cc50b58da..510395862f 100644 --- a/apps/remixdesktop/src/plugins/xtermPlugin.ts +++ b/apps/remixdesktop/src/plugins/xtermPlugin.ts @@ -5,6 +5,36 @@ import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plug import os from 'os'; import * as pty from "node-pty" +import process from 'node:process'; +import {userInfo} from 'node:os'; + +export const detectDefaultShell = () => { + const {env} = process; + + if (process.platform === 'win32') { + return env.COMSPEC || 'cmd.exe'; + } + + try { + const {shell} = userInfo(); + if (shell) { + return shell; + } + } catch {} + + if (process.platform === 'darwin') { + return env.SHELL || '/bin/zsh'; + } + + return env.SHELL || '/bin/sh'; +}; + +// Stores default shell when imported. +const defaultShell = detectDefaultShell(); + +export default defaultShell; + + const profile: Profile = { name: 'xterm', displayName: 'xterm', @@ -41,7 +71,7 @@ class XtermPluginClient extends ElectronBasePluginClient { } async createTerminal(path?: string): Promise { - const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash'; + const shell = defaultShell; const ptyProcess = pty.spawn(shell, [], { name: 'xterm-color', diff --git a/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx b/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx index 31cbe3dc79..70a0fd2cbe 100644 --- a/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx @@ -10,6 +10,7 @@ import DialogViewPlugin from './components/modals/dialogViewPlugin' import { AppContext } from './context/context' import { IntlProvider } from 'react-intl' import { CustomTooltip } from '@remix-ui/helper'; +import { RemixUiXterminals } from '@remix-ui/xterm' interface IRemixAppUi { app: any @@ -90,6 +91,7 @@ const RemixApp = (props: IRemixAppUi) => {
+ { + const { plugin } = props + const [files, setFiles] = useState([]) + const [workingDir, setWorkingDir] = useState('') + + useEffect(() => { + plugin.on('fs', 'workingDirChanged', async (path: string) => { + console.log('workingDirChanged') + setWorkingDir(path) + await readdir() + }) + }, []) + + const readdir = async () => { + const files = await plugin.call('fs', 'readdir', '/') + console.log('files', files) + setFiles(files) + } + + return ( + <> +

RemixUIFileDialog

+ + +
+ {workingDir} +
+ + {files.map(file =>
{file}
)} + + ) +} \ No newline at end of file diff --git a/libs/remix-ui/xterm/src/lib/components/remix-ui-xterm.tsx b/libs/remix-ui/xterm/src/lib/components/remix-ui-xterm.tsx new file mode 100644 index 0000000000..3fc848c20c --- /dev/null +++ b/libs/remix-ui/xterm/src/lib/components/remix-ui-xterm.tsx @@ -0,0 +1,44 @@ +import React, { useState, useEffect, forwardRef } from 'react' // eslint-disable-line +import { ElectronPlugin } from '@remixproject/engine-electron' +import { XTerm } from 'xterm-for-react' + + +export interface RemixUiXtermProps { + plugin: ElectronPlugin + pid: number + send: (data: string, pid: number) => void + timeStamp: number + setTerminalRef: (pid: number, ref: any) => void +} + +const RemixUiXterm = (props: RemixUiXtermProps) => { + const { plugin, pid, send, timeStamp } = props + const xtermRef = React.useRef(null) + + useEffect(() => { + console.log('remix-ui-xterm ref', xtermRef.current) + props.setTerminalRef(pid, xtermRef.current) + }, [xtermRef.current]) + + const onKey = (event: { key: string; domEvent: KeyboardEvent }) => { + send(event.key, pid) + } + + const onData = (data: string) => { + console.log('onData', data) + } + + const closeTerminal = () => { + plugin.call('xterm', 'close', pid) + } + + return ( + <> + + + + ) + +} + +export default RemixUiXterm \ No newline at end of file diff --git a/libs/remix-ui/xterm/src/lib/components/remix-ui-xterminals.tsx b/libs/remix-ui/xterm/src/lib/components/remix-ui-xterminals.tsx new file mode 100644 index 0000000000..735d478496 --- /dev/null +++ b/libs/remix-ui/xterm/src/lib/components/remix-ui-xterminals.tsx @@ -0,0 +1,103 @@ +import React, { useState, useEffect } from 'react' // eslint-disable-line +import { ElectronPlugin } from '@remixproject/engine-electron' +import RemixUiXterm from './remix-ui-xterm' + +export interface RemixUiXterminalsProps { + plugin: ElectronPlugin +} + +export interface xtermState { + pid: number + queue: string + timeStamp: number + ref: any +} + +export const RemixUiXterminals = (props: RemixUiXterminalsProps) => { + const [terminals, setTerminals] = useState([]) + const [workingDir, setWorkingDir] = useState('') + const { plugin } = props + + useEffect(() => { + setTimeout(async () => { + plugin.on('xterm', 'loaded', async () => { + }) + plugin.on('xterm', 'data', async (data: string, pid: number) => { + writeToTerminal(data, pid) + }) + + plugin.on('xterm', 'close', async (pid: number) => { + setTerminals(prevState => { + return prevState.filter(xtermState => xtermState.pid !== pid) + }) + }) + + plugin.on('fs', 'workingDirChanged', (path: string) => { + setWorkingDir(path) + }) + }, 5000) + }, []) + + const writeToTerminal = (data: string, pid: number) => { + setTerminals(prevState => { + const terminal = prevState.find(xtermState => xtermState.pid === pid) + if (terminal.ref && terminal.ref.terminal) { + terminal.ref.terminal.write(data) + }else { + terminal.queue += data + } + return [...prevState] + }) + } + + + useEffect(() => { + console.log('terminals', terminals) + }, [terminals]) + + const send = (data: string, pid: number) => { + plugin.call('xterm', 'keystroke', data, pid) + } + + const createTerminal = async () => { + const pid = await plugin.call('xterm', 'createTerminal', workingDir) + + setTerminals(prevState => { + return [...prevState, { + pid: pid, + queue: '', + timeStamp: Date.now(), + ref: null + }] + }) + } + + const setTerminalRef = (pid: number, ref: any) => { + console.log('setTerminalRef', pid, ref) + setTerminals(prevState => { + const terminal = prevState.find(xtermState => xtermState.pid === pid) + terminal.ref = ref + if(terminal.queue) { + ref.terminal.write(terminal.queue) + terminal.queue = '' + } + return [...prevState] + }) + } + + + return (<> + + + {terminals.map((xtermState) => { + return ( +
{xtermState.pid} + +
+ ) + })} + ) +} + diff --git a/package.json b/package.json index ed24d8c20c..c0df3cbe74 100644 --- a/package.json +++ b/package.json @@ -219,7 +219,8 @@ "wagmi": "^0.12.7", "web3": "^1.8.0", "winston": "^3.3.3", - "ws": "^7.3.0" + "ws": "^7.3.0", + "xterm-for-react": "^1.0.4" }, "devDependencies": { "@babel/cli": "^7.19.3", diff --git a/tsconfig.paths.json b/tsconfig.paths.json index 4427bf5e3c..a10b478f41 100644 --- a/tsconfig.paths.json +++ b/tsconfig.paths.json @@ -154,6 +154,10 @@ "@remixproject/walletconnect-plugin": [ "apps/walletconnect/src/index.ts" ], + "@remix-ui/xterm": [ + "libs/remix-ui/xterm/src/index.ts" + ], + } } } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index bba278dc7a..65ade41641 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29342,6 +29342,19 @@ xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== +xterm-for-react@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/xterm-for-react/-/xterm-for-react-1.0.4.tgz#6b35b9b037a0f9d979e7b57bb1d7c6ab7565b380" + integrity sha512-DCkLR9ZXeW907YyyaCTk/3Ol34VRHfCnf3MAPOkj3dUNA85sDqHvTXN8efw4g7bx7gWdJQRsEpGt2tJOXKG3EQ== + dependencies: + prop-types "^15.7.2" + xterm "^4.5.0" + +xterm@^4.5.0: + version "4.19.0" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0.tgz#c0f9d09cd61de1d658f43ca75f992197add9ef6d" + integrity sha512-c3Cp4eOVsYY5Q839dR5IejghRPpxciGmLWWaP9g+ppfMeBChMeLa1DCA+pmX/jyDZ+zxFOmlJL/82qVdayVoGQ== + y18n@^3.2.1: version "3.2.2" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696"