Skip to content

Commit 1f2b295

Browse files
committed
feat: add open in terminal, open in cmd and powershell feature
1 parent 64af81b commit 1f2b295

File tree

6 files changed

+141
-3
lines changed

6 files changed

+141
-3
lines changed

src-node/utils.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const { exec, execFile } = require('child_process');
33
const fs = require('fs');
44
const fsPromise = require('fs').promises;
55
const path = require('path');
6+
const os = require('os');
67
const {lintFile} = require("./ESLint/service");
78
let openModule, open; // dynamic import when needed
89

@@ -146,6 +147,86 @@ async function _npmInstallInFolder({moduleNativeDir}) {
146147
});
147148
}
148149

150+
/**
151+
* If it's a dir that exists, returns that
152+
* If it's a file, it returns the parent directory if it exists
153+
* If no parent exists, it returns the original path.
154+
*
155+
* @param {string} cwd - The path to validate.
156+
* @returns {string} - An existing directory or the original path.
157+
*/
158+
function _getValidDirectory(cwd) {
159+
let currentPath = path.resolve(cwd);
160+
const exists = fs.existsSync(currentPath);
161+
162+
if (exists) {
163+
const isPathDir = fs.statSync(currentPath).isDirectory();
164+
if(isPathDir){
165+
return currentPath;
166+
}
167+
return path.dirname(currentPath);
168+
}
169+
170+
currentPath = path.dirname(currentPath);
171+
if(fs.existsSync(currentPath)){
172+
return currentPath;
173+
}
174+
175+
// If no valid directory is found, fallback to the original cwd
176+
return cwd;
177+
}
178+
179+
/**
180+
* Opens a native terminal window with the specified current working directory.
181+
* Returns a Promise that resolves if the terminal starts successfully, or rejects if it fails.
182+
*
183+
* @param {string} cwd - The directory to open the terminal in.
184+
* @param {boolean} usePowerShell - Whether to use PowerShell instead of cmd on Windows.
185+
* @returns {Promise<void>} - Resolves if the terminal starts, rejects otherwise.
186+
*/
187+
function openNativeTerminal({cwd, usePowerShell = false}) {
188+
return new Promise((resolve, reject) => {
189+
const platform = os.platform();
190+
cwd = _getValidDirectory(cwd);
191+
let command;
192+
193+
if (platform === 'win32') {
194+
if (usePowerShell) {
195+
command = `start powershell -NoExit -Command "Set-Location -Path '${cwd}'"`;
196+
} else {
197+
command = `start cmd /K "cd /D ${cwd}"`;
198+
}
199+
} else if (platform === 'darwin') {
200+
command = `open -a Terminal "${cwd}"`;
201+
} else {
202+
command = `
203+
if command -v gnome-terminal > /dev/null 2>&1; then
204+
gnome-terminal --working-directory="${cwd}";
205+
elif command -v konsole > /dev/null 2>&1; then
206+
konsole --workdir "${cwd}";
207+
elif command -v xfce4-terminal > /dev/null 2>&1; then
208+
xfce4-terminal --working-directory="${cwd}";
209+
elif command -v xterm > /dev/null 2>&1; then
210+
xterm -e "cd '${cwd}' && bash";
211+
else
212+
echo "No supported terminal emulator found.";
213+
exit 1;
214+
fi
215+
`;
216+
}
217+
218+
// Execute the terminal command
219+
exec(command, (error) => {
220+
if (error) {
221+
reject(new Error(`Failed to start terminal: ${error.message}`));
222+
} else {
223+
resolve();
224+
}
225+
});
226+
});
227+
}
228+
229+
149230
async function ESLintFile({text, fullFilePath, projectFullPath}) {
150231
return lintFile(text, fullFilePath, projectFullPath);
151232
}
@@ -161,5 +242,6 @@ exports.getLinuxOSFlavorName = getLinuxOSFlavorName;
161242
exports.openUrlInBrowser = openUrlInBrowser;
162243
exports.getEnvironmentVariable = getEnvironmentVariable;
163244
exports.ESLintFile = ESLintFile;
245+
exports.openNativeTerminal = openNativeTerminal;
164246
exports._loadNodeExtensionModule = _loadNodeExtensionModule;
165247
exports._npmInstallInFolder = _npmInstallInFolder;

src/command/Commands.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,12 @@ define(function (require, exports, module) {
340340
/** Shows current file in OS file explorer */
341341
exports.NAVIGATE_SHOW_IN_OS = "navigate.showInOS"; // DocumentCommandHandlers.js handleShowInOS()
342342

343+
/** Shows current file in OS Terminal */
344+
exports.NAVIGATE_OPEN_IN_TERMINAL = "navigate.openInTerminal";
345+
346+
/** Shows current file in open powershell in Windows os */
347+
exports.NAVIGATE_OPEN_IN_POWERSHELL = "navigate.openInPowerShell";
348+
343349
/** Opens quick open dialog */
344350
exports.NAVIGATE_QUICK_OPEN = "navigate.quickOpen"; // QuickOpen.js doFileSearch()
345351

src/command/DefaultMenus.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ 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]);
61+
Commands.NAVIGATE_SHOW_IN_FILE_TREE, Commands.NAVIGATE_SHOW_IN_OS, Commands.NAVIGATE_OPEN_IN_TERMINAL]);
6262
});
6363
}
6464
}
@@ -299,6 +299,10 @@ define(function (require, exports, module) {
299299
if(Phoenix.isNativeApp){
300300
let subMenu = workingset_cmenu.addSubMenu(Strings.CMD_OPEN_IN, Commands.OPEN_IN_SUBMENU_WS);
301301
subMenu.addMenuItem(Commands.NAVIGATE_SHOW_IN_OS);
302+
subMenu.addMenuItem(Commands.NAVIGATE_OPEN_IN_TERMINAL);
303+
if (brackets.platform === "win") {
304+
subMenu.addMenuItem(Commands.NAVIGATE_OPEN_IN_POWERSHELL);
305+
}
302306
}
303307
workingset_cmenu.addMenuDivider();
304308
workingset_cmenu.addMenuItem(Commands.FILE_COPY);
@@ -334,6 +338,10 @@ define(function (require, exports, module) {
334338
if(Phoenix.isNativeApp){
335339
let subMenu = project_cmenu.addSubMenu(Strings.CMD_OPEN_IN, Commands.OPEN_IN_SUBMENU);
336340
subMenu.addMenuItem(Commands.NAVIGATE_SHOW_IN_OS);
341+
subMenu.addMenuItem(Commands.NAVIGATE_OPEN_IN_TERMINAL);
342+
if (brackets.platform === "win") {
343+
subMenu.addMenuItem(Commands.NAVIGATE_OPEN_IN_POWERSHELL);
344+
}
337345
}
338346
project_cmenu.addMenuDivider();
339347
project_cmenu.addMenuItem(Commands.FILE_CUT);

src/document/DocumentCommandHandlers.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ define(function (require, exports, module) {
5858
LanguageManager = require("language/LanguageManager"),
5959
NewFileContentManager = require("features/NewFileContentManager"),
6060
NodeConnector = require("NodeConnector"),
61+
NodeUtils = require("utils/NodeUtils"),
6162
_ = require("thirdparty/lodash");
6263

6364
/**
@@ -1959,6 +1960,20 @@ define(function (require, exports, module) {
19591960
}
19601961
}
19611962

1963+
function openDefaultTerminal() {
1964+
const entry = ProjectManager.getSelectedItem();
1965+
if (entry && entry.fullPath) {
1966+
NodeUtils.openNativeTerminal(entry.fullPath);
1967+
}
1968+
}
1969+
1970+
function openPowerShell() {
1971+
const entry = ProjectManager.getSelectedItem();
1972+
if (entry && entry.fullPath) {
1973+
NodeUtils.openNativeTerminal(entry.fullPath, true);
1974+
}
1975+
}
1976+
19621977
function raceAgainstTime(promise, timeout = 2000) {
19631978
const timeoutPromise = new Promise((_resolve, reject) => {
19641979
setTimeout(() => {
@@ -2251,11 +2266,13 @@ define(function (require, exports, module) {
22512266
exports._parseDecoratedPath = _parseDecoratedPath;
22522267

22532268
// Set some command strings
2254-
var quitString = Strings.CMD_QUIT,
2255-
showInOS = Strings.CMD_SHOW_IN_FILE_MANAGER;
2269+
let quitString = Strings.CMD_QUIT,
2270+
showInOS = Strings.CMD_SHOW_IN_FILE_MANAGER,
2271+
defaultTerminal = Strings.CMD_OPEN_IN_TERMINAL;
22562272
if (brackets.platform === "win") {
22572273
quitString = Strings.CMD_EXIT;
22582274
showInOS = Strings.CMD_SHOW_IN_EXPLORER;
2275+
defaultTerminal = Strings.CMD_OPEN_IN_CMD;
22592276
} else if (brackets.platform === "mac") {
22602277
showInOS = Strings.CMD_SHOW_IN_FINDER;
22612278
}
@@ -2304,6 +2321,10 @@ define(function (require, exports, module) {
23042321

23052322
// Special Commands
23062323
CommandManager.register(showInOS, Commands.NAVIGATE_SHOW_IN_OS, handleShowInOS);
2324+
CommandManager.register(defaultTerminal, Commands.NAVIGATE_OPEN_IN_TERMINAL, openDefaultTerminal);
2325+
if (brackets.platform === "win") {
2326+
CommandManager.register(Strings.CMD_OPEN_IN_POWER_SHELL, Commands.NAVIGATE_OPEN_IN_POWERSHELL, openPowerShell);
2327+
}
23072328
CommandManager.register(Strings.CMD_NEW_BRACKETS_WINDOW, Commands.FILE_NEW_WINDOW, handleFileNewWindow);
23082329
CommandManager.register(quitString, Commands.FILE_QUIT, handleFileCloseWindow);
23092330
CommandManager.register(Strings.CMD_SHOW_IN_TREE, Commands.NAVIGATE_SHOW_IN_FILE_TREE, handleShowInTree);

src/nls/root/strings.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,9 @@ 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",
602+
"CMD_OPEN_IN_CMD": "Command Prompt",
603+
"CMD_OPEN_IN_POWER_SHELL": "Power Shell",
601604
"CMD_SWITCH_PANE_FOCUS": "Switch Pane Focus",
602605

603606
// Debug menu commands

src/utils/NodeUtils.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,23 @@ define(function (require, exports, module) {
159159
});
160160
}
161161

162+
/**
163+
* Runs ESLint on a file
164+
* This is only available in the native app
165+
*
166+
* @param {string} cwd the working directory of terminal
167+
* @param {boolean} [usePowerShell]
168+
*/
169+
async function openNativeTerminal(cwd, usePowerShell = false) {
170+
if(!Phoenix.isNativeApp) {
171+
throw new Error("openNativeTerminal not available in browser");
172+
}
173+
return utilsConnector.execPeer("openNativeTerminal", {
174+
cwd: window.fs.getTauriPlatformPath(cwd),
175+
usePowerShell
176+
});
177+
}
178+
162179
if(NodeConnector.isNodeAvailable()) {
163180
// todo we need to update the strings if a user extension adds its translations. Since we dont support
164181
// node extensions for now, should consider when we support node extensions.
@@ -195,6 +212,7 @@ define(function (require, exports, module) {
195212
exports.openUrlInBrowser = openUrlInBrowser;
196213
exports.ESLintFile = ESLintFile;
197214
exports.getEnvironmentVariable = getEnvironmentVariable;
215+
exports.openNativeTerminal = openNativeTerminal;
198216

199217
/**
200218
* checks if Node connector is ready

0 commit comments

Comments
 (0)