Skip to content

Commit 974dd71

Browse files
committed
feat: open in system default app in project and working set context menu
1 parent 3bf47fa commit 974dd71

File tree

6 files changed

+68
-3
lines changed

6 files changed

+68
-3
lines changed

src-node/utils.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,40 @@ function openNativeTerminal({cwd, usePowerShell = false}) {
226226
});
227227
}
228228

229+
/**
230+
* Opens a file in the default application for its type on Windows, macOS, and Linux.
231+
*
232+
* @param {string} fullPath - The path to the file/folder to open.
233+
* @returns {Promise<void>} - Resolves if the file/folder is opened successfully, rejects otherwise.
234+
*/
235+
function openInDefaultApp(fullPath) {
236+
return new Promise((resolve, reject) => {
237+
const platform = os.platform();
238+
let command;
239+
240+
if (platform === 'win32') {
241+
// Windows: Use 'start' command
242+
command = `start "" "${fullPath}"`;
243+
} else if (platform === 'darwin') {
244+
// macOS: Use 'open' command
245+
command = `open "${fullPath}"`;
246+
} else {
247+
// Linux: Use 'xdg-open' command
248+
command = `xdg-open "${fullPath}"`;
249+
}
250+
251+
// Execute the command
252+
exec(command, (error) => {
253+
if (error) {
254+
reject(new Error(`Failed to open file: ${error.message}`));
255+
} else {
256+
resolve();
257+
}
258+
});
259+
});
260+
}
261+
262+
229263

230264
async function ESLintFile({text, fullFilePath, projectFullPath}) {
231265
return lintFile(text, fullFilePath, projectFullPath);
@@ -243,5 +277,6 @@ exports.openUrlInBrowser = openUrlInBrowser;
243277
exports.getEnvironmentVariable = getEnvironmentVariable;
244278
exports.ESLintFile = ESLintFile;
245279
exports.openNativeTerminal = openNativeTerminal;
280+
exports.openInDefaultApp = openInDefaultApp;
246281
exports._loadNodeExtensionModule = _loadNodeExtensionModule;
247282
exports._npmInstallInFolder = _npmInstallInFolder;

src/command/Commands.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,9 @@ define(function (require, exports, module) {
346346
/** Shows current file in open powershell in Windows os */
347347
exports.NAVIGATE_OPEN_IN_POWERSHELL = "navigate.openInPowerShell";
348348

349+
/** Open current file in the default associated app in the os */
350+
exports.NAVIGATE_OPEN_IN_DEFAULT_APP = "navigate.openInDefaultApp";
351+
349352
/** Opens quick open dialog */
350353
exports.NAVIGATE_QUICK_OPEN = "navigate.quickOpen"; // QuickOpen.js doFileSearch()
351354

src/command/DefaultMenus.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ define(function (require, exports, module) {
5858
return err;
5959
}
6060
_setContextMenuItemsVisible(isPresent, [Commands.FILE_RENAME,
61-
Commands.NAVIGATE_SHOW_IN_FILE_TREE, Commands.NAVIGATE_SHOW_IN_OS, Commands.NAVIGATE_OPEN_IN_TERMINAL]);
61+
Commands.NAVIGATE_SHOW_IN_FILE_TREE, Commands.NAVIGATE_SHOW_IN_OS,
62+
Commands.NAVIGATE_OPEN_IN_TERMINAL, Commands.NAVIGATE_OPEN_IN_POWERSHELL,
63+
Commands.NAVIGATE_OPEN_IN_DEFAULT_APP]);
6264
});
6365
}
6466
}
@@ -303,6 +305,7 @@ define(function (require, exports, module) {
303305
if (brackets.platform === "win") {
304306
subMenu.addMenuItem(Commands.NAVIGATE_OPEN_IN_POWERSHELL);
305307
}
308+
subMenu.addMenuItem(Commands.NAVIGATE_OPEN_IN_DEFAULT_APP);
306309
}
307310
workingset_cmenu.addMenuDivider();
308311
workingset_cmenu.addMenuItem(Commands.FILE_COPY);
@@ -342,6 +345,7 @@ define(function (require, exports, module) {
342345
if (brackets.platform === "win") {
343346
subMenu.addMenuItem(Commands.NAVIGATE_OPEN_IN_POWERSHELL);
344347
}
348+
subMenu.addMenuItem(Commands.NAVIGATE_OPEN_IN_DEFAULT_APP);
345349
}
346350
project_cmenu.addMenuDivider();
347351
project_cmenu.addMenuItem(Commands.FILE_CUT);

src/document/DocumentCommandHandlers.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1974,6 +1974,13 @@ define(function (require, exports, module) {
19741974
}
19751975
}
19761976

1977+
function openDefaultApp() {
1978+
const entry = ProjectManager.getSelectedItem();
1979+
if (entry && entry.fullPath) {
1980+
NodeUtils.openInDefaultApp(entry.fullPath, true);
1981+
}
1982+
}
1983+
19771984
function raceAgainstTime(promise, timeout = 2000) {
19781985
const timeoutPromise = new Promise((_resolve, reject) => {
19791986
setTimeout(() => {
@@ -2268,7 +2275,7 @@ define(function (require, exports, module) {
22682275
// Set some command strings
22692276
let quitString = Strings.CMD_QUIT,
22702277
showInOS = Strings.CMD_SHOW_IN_FILE_MANAGER,
2271-
defaultTerminal = Strings.CMD_OPEN_IN_TERMINAL;
2278+
defaultTerminal = Strings.CMD_OPEN_IN_TERMINAL_DO_NOT_TRANSLATE;
22722279
if (brackets.platform === "win") {
22732280
quitString = Strings.CMD_EXIT;
22742281
showInOS = Strings.CMD_SHOW_IN_EXPLORER;
@@ -2325,6 +2332,7 @@ define(function (require, exports, module) {
23252332
if (brackets.platform === "win") {
23262333
CommandManager.register(Strings.CMD_OPEN_IN_POWER_SHELL, Commands.NAVIGATE_OPEN_IN_POWERSHELL, openPowerShell);
23272334
}
2335+
CommandManager.register(Strings.CMD_OPEN_IN_DEFAULT_APP, Commands.NAVIGATE_OPEN_IN_DEFAULT_APP, openDefaultApp);
23282336
CommandManager.register(Strings.CMD_NEW_BRACKETS_WINDOW, Commands.FILE_NEW_WINDOW, handleFileNewWindow);
23292337
CommandManager.register(quitString, Commands.FILE_QUIT, handleFileCloseWindow);
23302338
CommandManager.register(Strings.CMD_SHOW_IN_TREE, Commands.NAVIGATE_SHOW_IN_FILE_TREE, handleShowInTree);

src/nls/root/strings.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,9 +598,10 @@ define({
598598
"CMD_SHOW_IN_EXPLORER": "Windows File Explorer",
599599
"CMD_SHOW_IN_FINDER": "macOS Finder",
600600
"CMD_SHOW_IN_FILE_MANAGER": "File Manager",
601-
"CMD_OPEN_IN_TERMINAL": "Terminal",
601+
"CMD_OPEN_IN_TERMINAL_DO_NOT_TRANSLATE": "Terminal",
602602
"CMD_OPEN_IN_CMD": "Command Prompt",
603603
"CMD_OPEN_IN_POWER_SHELL": "Power Shell",
604+
"CMD_OPEN_IN_DEFAULT_APP": "System Default App",
604605
"CMD_SWITCH_PANE_FOCUS": "Switch Pane Focus",
605606

606607
// Debug menu commands

src/utils/NodeUtils.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,19 @@ define(function (require, exports, module) {
176176
});
177177
}
178178

179+
/**
180+
* Opens a file in the default application for its type on Windows, macOS, and Linux.
181+
*
182+
* @param {string} fullPath - The path to the file/folder to open.
183+
* @returns {Promise<void>} - Resolves if the file/folder is opened successfully, rejects otherwise.
184+
*/
185+
async function openInDefaultApp(fullPath) {
186+
if(!Phoenix.isNativeApp) {
187+
throw new Error("openInDefaultApp not available in browser");
188+
}
189+
return utilsConnector.execPeer("openInDefaultApp", window.fs.getTauriPlatformPath(fullPath));
190+
}
191+
179192
if(NodeConnector.isNodeAvailable()) {
180193
// todo we need to update the strings if a user extension adds its translations. Since we dont support
181194
// node extensions for now, should consider when we support node extensions.
@@ -213,6 +226,7 @@ define(function (require, exports, module) {
213226
exports.ESLintFile = ESLintFile;
214227
exports.getEnvironmentVariable = getEnvironmentVariable;
215228
exports.openNativeTerminal = openNativeTerminal;
229+
exports.openInDefaultApp = openInDefaultApp;
216230

217231
/**
218232
* checks if Node connector is ready

0 commit comments

Comments
 (0)