diff --git a/editor/src/dashboard/item.tsx b/editor/src/dashboard/item.tsx index f96374a9f..71fbdaeb8 100644 --- a/editor/src/dashboard/item.tsx +++ b/editor/src/dashboard/item.tsx @@ -131,6 +131,11 @@ export function DashboardProjectItem(props: IDashboardProjectItemProps) { execNodePty(`code "${dirname(props.project.absolutePath)}"`); } + function handleOpenInDefaultIde() { + const projectDir = dirname(props.project.absolutePath); + ipcRenderer.send("editor:open-with", projectDir); + } + return ( setContextMenuOpen(o)}> @@ -174,6 +179,9 @@ export function DashboardProjectItem(props: IDashboardProjectItemProps) { {`Show in ${isDarwin() ? "Finder" : "Explorer"}`} + handleOpenInDefaultIde()}> + Open in Default IDE + handleOpenInVisualStudioCode()}> Open in Visual Studio Code @@ -219,6 +227,9 @@ export function DashboardProjectItem(props: IDashboardProjectItemProps) { {`Show in ${isDarwin() ? "Finder" : "Explorer"}`} + handleOpenInDefaultIde()}> + Open in Default IDE + handleOpenInVisualStudioCode()}> Open in Visual Studio Code diff --git a/editor/src/editor/layout/assets-browser/items/item.tsx b/editor/src/editor/layout/assets-browser/items/item.tsx index d6e6209f5..938340614 100644 --- a/editor/src/editor/layout/assets-browser/items/item.tsx +++ b/editor/src/editor/layout/assets-browser/items/item.tsx @@ -250,7 +250,10 @@ export class AssetsBrowserItem extends Component { - // Nothing to do by default. + // If it's a file (not a directory), open it in the default editor + if (!this.state.isDirectory) { + ipcRenderer.send("editor:open-with", this.props.absolutePath); + } } private _handleDragStart(ev: DragEvent): void { @@ -392,12 +395,16 @@ export class AssetsBrowserItem extends Component + {!this.state.isDirectory && ( + ipcRenderer.send("editor:open-with", this.props.absolutePath)}> + Open + + )} + ipcRenderer.send("editor:show-item", this.props.absolutePath)}> - {`Show in ${isDarwin ? "Finder" : "Explorer"}`} + {`Show in ${isDarwin ? "Finder" : "Explorer"}`} - - {items.map((item, index) => ( {item} ))} @@ -418,7 +425,7 @@ export class AssetsBrowserItem extends Component this._handleTrashItem()}> - Remove + Delete ); diff --git a/editor/src/editor/layout/toolbar.tsx b/editor/src/editor/layout/toolbar.tsx index 7e23c7a7a..d2e9e1298 100644 --- a/editor/src/editor/layout/toolbar.tsx +++ b/editor/src/editor/layout/toolbar.tsx @@ -39,6 +39,7 @@ export class EditorToolbar extends Component { super(props); ipcRenderer.on("editor:open-project", () => this._handleOpenProject()); + ipcRenderer.on("editor:open-default-ide", () => this._handleOpenInDefaultIde()); ipcRenderer.on("editor:open-vscode", () => this._handleOpenVisualStudioCode()); this._nodeCommands = getNodeCommands(this.props.editor); @@ -90,6 +91,10 @@ export class EditorToolbar extends Component { + this._handleOpenInDefaultIde()}> + Open in Default IDE + + this._handleOpenVisualStudioCode()}> Open in Visual Studio Code @@ -258,4 +263,13 @@ export class EditorToolbar extends Component { const p = await execNodePty(`code "${join(dirname(this.props.editor.state.projectPath), "/")}"`); await p.wait(); } + + private _handleOpenInDefaultIde(): void { + if (!this.props.editor.state.projectPath) { + return; + } + + const projectDir = dirname(this.props.editor.state.projectPath); + ipcRenderer.send("editor:open-with", projectDir); + } } diff --git a/editor/src/editor/menu.ts b/editor/src/editor/menu.ts index a98116f5e..381c2f1a5 100644 --- a/editor/src/editor/menu.ts +++ b/editor/src/editor/menu.ts @@ -55,6 +55,10 @@ export function setupEditorMenu(): void { { type: "separator", }, + { + label: "Open in Default IDE", + click: () => BrowserWindow.getFocusedWindow()?.webContents.send("editor:open-default-ide"), + }, { label: "Open in Visual Studio Code", click: () => BrowserWindow.getFocusedWindow()?.webContents.send("editor:open-vscode"), diff --git a/editor/src/electron/events/shell.ts b/editor/src/electron/events/shell.ts index a574f0789..020c85bb7 100644 --- a/editor/src/electron/events/shell.ts +++ b/editor/src/electron/events/shell.ts @@ -1,5 +1,7 @@ import { platform } from "os"; import { ipcMain, shell } from "electron"; +import { exec } from "child_process"; +import { statSync } from "fs"; ipcMain.on("editor:trash-items", async (ev, items) => { const isWindows = platform() === "win32"; @@ -20,3 +22,118 @@ ipcMain.on("editor:show-item", (_, item) => { shell.showItemInFolder(item); }); + +ipcMain.on("editor:open-in-external-editor", (_, item) => { + const isWindows = platform() === "win32"; + item = isWindows ? item.replace(/\//g, "\\") : item.replace(/\\/g, "/"); + + shell.openPath(item); +}); + +async function checkCommandAvailable(command: string): Promise { + return new Promise((resolve) => { + exec(`${command} --version`, (error) => { + resolve(!error); + }); + }); +} + +async function openInIde(path: string, isDirectory: boolean): Promise { + const isWindows = platform() === "win32"; + const normalizedPath = isWindows ? path.replace(/\//g, "\\") : path.replace(/\\/g, "/"); + + if (isDirectory) { + // Try to open directory in IDEs + const ideCommands = [ + { command: "code", args: [normalizedPath] }, + { command: "cursor", args: [normalizedPath] }, + { command: "subl", args: [normalizedPath] }, // Sublime Text + ]; + + // Try each IDE in order + for (const ide of ideCommands) { + if (await checkCommandAvailable(ide.command)) { + const fullCommand = `${ide.command} "${normalizedPath}"`; + exec(fullCommand, (error) => { + if (error) { + console.error(`Failed to open with ${ide.command}:`, error); + } + }); + return; + } + } + + // On macOS, try JetBrains IDEs (PhpStorm, WebStorm, IntelliJ IDEA) + if (platform() === "darwin") { + exec(`open -a "PhpStorm" "${normalizedPath}"`, (error) => { + if (!error) { + return; + } + exec(`open -a "WebStorm" "${normalizedPath}"`, (error) => { + if (!error) { + return; + } + exec(`open -a "IntelliJ IDEA" "${normalizedPath}"`, (error) => { + if (!error) { + return; + } + exec(`open -a "IntelliJ IDEA CE" "${normalizedPath}"`, (error) => { + if (!error) { + return; + } + // Fallback to shell.openPath + shell.openPath(normalizedPath); + }); + }); + }); + }); + return; + } + + // On Windows, try JetBrains IDEs via CLI + if (isWindows) { + if (await checkCommandAvailable("phpstorm")) { + exec(`phpstorm "${normalizedPath}"`, (error) => { + if (error) { + console.error("Failed to open with PhpStorm:", error); + } + }); + return; + } + if (await checkCommandAvailable("webstorm")) { + exec(`webstorm "${normalizedPath}"`, (error) => { + if (error) { + console.error("Failed to open with WebStorm:", error); + } + }); + return; + } + if (await checkCommandAvailable("idea")) { + exec(`idea "${normalizedPath}"`, (error) => { + if (error) { + console.error("Failed to open with IntelliJ IDEA:", error); + shell.openPath(normalizedPath); + } + }); + return; + } + } + + // Fallback: open with default application + shell.openPath(normalizedPath); + } else { + // For files, use shell.openPath which uses OS default application + shell.openPath(normalizedPath); + } +} + +ipcMain.on("editor:open-with", async (_, item) => { + try { + const stats = statSync(item); + const isDirectory = stats.isDirectory(); + await openInIde(item, isDirectory); + } catch (e) { + // If stat fails, try as directory first, then as file + await openInIde(item, true); + } +});