diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 93d2bb85487..5c317a50882 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -466,6 +466,21 @@ The program that opens raster image files when clicking "Open in External Program" option in Filesystem Dock. If not specified, the file will be opened in the system's default program. + + The terminal emulator program to use when using [b]Open in Terminal[/b] context menu action in the FileSystem dock. You can enter an absolute path to a program binary, or a path to a program that is present in the [code]PATH[/code] environment variable. + If left empty, Godot will use the default terminal emulator for the system: + - [b]Windows:[/b] PowerShell + - [b]macOS:[/b] Terminal.app + - [b]Linux:[/b] The first terminal found on the system in this order: gnome-terminal, konsole, xfce4-terminal, lxterminal, kitty, alacritty, urxvt, xterm. + To use Command Prompt (cmd) instead of PowerShell on Windows, enter [code]cmd[/code] in this field and the correct flags will automatically be used. + On macOS, make sure to point to the actual program binary located within the [code]Programs/MacOS[/code] folder of the .app bundle, rather than the .app bundle directory. + If specifying a custom terminal emulator, you may need to override [member filesystem/external_programs/terminal_emulator_flags] so it opens in the correct folder. + + + The command-line arguments to pass to the terminal emulator that is run when using [b]Open in Terminal[/b] context menu action in the FileSystem dock. See also [member filesystem/external_programs/terminal_emulator]. + If left empty, the default flags are [code]{directory}[/code], which is replaced by the absolute path to the directory that is being opened in the terminal. + [b]Note:[/b] If the terminal emulator is set to PowerShell, cmd, or Konsole, Godot will automatically prepend arguments to this list, as these terminals require nonstandard arguments to open in the correct folder. + The program that opens vector image files when clicking "Open in External Program" option in Filesystem Dock. If not specified, the file will be opened in the system's default program. diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 98706a10f28..e6ba39cae72 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -500,6 +500,8 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/external_programs/vector_image_editor", "", "") EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/external_programs/audio_editor", "", "") EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/external_programs/3d_model_editor", "", "") + EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/external_programs/terminal_emulator", "", "") + EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_PLACEHOLDER_TEXT, "filesystem/external_programs/terminal_emulator_flags", "", "Call flags with placeholder: {directory}."); // Directories EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_DIR, "filesystem/directories/autoscan_project_path", "", "") diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 1b966fced11..e5d03942e38 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -2135,6 +2135,135 @@ void FileSystemDock::_file_option(int p_option, const Vector &p_selected } } break; + case FILE_OPEN_IN_TERMINAL: { + String fpath = current_path; + if (current_path == "Favorites") { + fpath = p_selected[0]; + } + + Vector terminal_emulators; + const String terminal_emulator_setting = EDITOR_GET("filesystem/external_programs/terminal_emulator"); + if (terminal_emulator_setting.is_empty()) { + // Figure out a default terminal emulator to use. +#if defined(WINDOWS_ENABLED) + // Default to PowerShell as done by Windows 10 and later. + terminal_emulators.push_back("powershell"); +#elif defined(MACOS_ENABLED) + terminal_emulators.push_back("/System/Applications/Utilities/Terminal.app"); +#elif defined(LINUXBSD_ENABLED) + // Try terminal emulators that ship with common Linux distributions first. + terminal_emulators.push_back("gnome-terminal"); + terminal_emulators.push_back("konsole"); + terminal_emulators.push_back("xfce4-terminal"); + terminal_emulators.push_back("lxterminal"); + terminal_emulators.push_back("kitty"); + terminal_emulators.push_back("alacritty"); + terminal_emulators.push_back("urxvt"); + terminal_emulators.push_back("xterm"); +#endif + } else { + // Use the user-specified terminal. + terminal_emulators.push_back(terminal_emulator_setting); + } + + String arguments = EDITOR_GET("filesystem/external_programs/terminal_emulator_flags"); + if (arguments.is_empty()) { + // NOTE: This default value is ignored further below if the terminal executable is `powershell` or `cmd`, + // due to these terminals requiring nonstandard syntax to start in a specified folder. + arguments = "{directory}"; + } + +#ifdef LINUXBSD_ENABLED + String chosen_terminal_emulator; + for (const String &terminal_emulator : terminal_emulators) { + List test_args; // Required for `execute()`, as it doesn't accept `Vector`. + test_args.push_back("-v"); + test_args.push_back(terminal_emulator); + // Silence command name being printed when found. (stderr is already silenced by `OS::execute()` by default.) + // FIXME: This doesn't appear to silence stdout. + test_args.push_back(">"); + test_args.push_back("/dev/null"); + int exit_code = 0; + const Error err = OS::get_singleton()->execute("command", test_args, nullptr, &exit_code); + if (err == OK && exit_code == EXIT_SUCCESS) { + chosen_terminal_emulator = terminal_emulator; + break; + } else if (err == ERR_CANT_FORK) { + ERR_PRINT_ED(vformat(TTR("Couldn't run external program to check for terminal emulator presence: command -v %s"), terminal_emulator)); + } + } +#else + // On Windows and macOS, the first (and only) terminal emulator in the list is always available. + String chosen_terminal_emulator = terminal_emulators[0]; +#endif + + List terminal_emulator_args; // Required for `execute()`, as it doesn't accept `Vector`. +#ifdef LINUXBSD_ENABLED + // Prepend default arguments based on the terminal emulator name. + // Use `String.ends_with()` so that installations in non-default paths + // or `/usr/local/bin` are detected correctly. + if (chosen_terminal_emulator.ends_with("konsole")) { + terminal_emulator_args.push_back("--workdir"); + } +#endif + + bool append_default_args = true; + +#ifdef WINDOWS_ENABLED + // Prepend default arguments based on the terminal emulator name. + // Use `String.get_basename().to_lower()` to handle Windows' case-insensitive paths + // with optional file extensions for executables in `PATH`. + if (chosen_terminal_emulator.get_basename().to_lower() == "powershell") { + terminal_emulator_args.push_back("-noexit"); + terminal_emulator_args.push_back("-command"); + terminal_emulator_args.push_back("cd '{directory}'"); + append_default_args = false; + } else if (chosen_terminal_emulator.get_basename().to_lower() == "cmd") { + terminal_emulator_args.push_back("/K"); + terminal_emulator_args.push_back("cd /d {directory}"); + append_default_args = false; + } +#endif + + Vector arguments_array = arguments.split(" "); + for (const String &argument : arguments_array) { + if (!append_default_args && argument == "{directory}") { + // Prevent appending a `{directory}` placeholder twice when using powershell or cmd. + // This allows users to enter the path to cmd or PowerShell in the custom terminal emulator path, + // and make it work without having to enter custom arguments. + continue; + } + terminal_emulator_args.push_back(argument); + } + + const bool is_directory = fpath.ends_with("/"); + for (int i = 0; i < terminal_emulator_args.size(); i++) { + if (is_directory) { + terminal_emulator_args[i] = terminal_emulator_args[i].replace("{directory}", ProjectSettings::get_singleton()->globalize_path(fpath)); + } else { + terminal_emulator_args[i] = terminal_emulator_args[i].replace("{directory}", ProjectSettings::get_singleton()->globalize_path(fpath).get_base_dir()); + } + } + + if (OS::get_singleton()->is_stdout_verbose()) { + // Print full command line to help with troubleshooting. + String command_string = chosen_terminal_emulator; + for (const String &arg : terminal_emulator_args) { + command_string += " " + arg; + } + print_line("Opening terminal emulator:", command_string); + } + + const Error err = OS::get_singleton()->create_process(chosen_terminal_emulator, terminal_emulator_args, nullptr, true); + if (err != OK) { + String args_string; + for (int i = 0; i < terminal_emulator_args.size(); i++) { + args_string += terminal_emulator_args[i]; + } + ERR_PRINT_ED(vformat(TTR("Couldn't run external terminal program (error code %d): %s %s\nCheck `filesystem/external_programs/terminal_emulator` and `filesystem/external_programs/terminal_emulator_flags` in the Editor Settings."), err, chosen_terminal_emulator, args_string)); + } + } break; + case FILE_OPEN: { // Open folders. TreeItem *selected = tree->get_root(); @@ -3055,12 +3184,16 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vectoradd_icon_shortcut(get_editor_theme_icon(SNAME("Filesystem")), ED_GET_SHORTCUT("filesystem_dock/show_in_explorer"), FILE_SHOW_IN_EXPLORER); - p_popup->set_item_text(p_popup->get_item_index(FILE_SHOW_IN_EXPLORER), item_text); + p_popup->set_item_text(p_popup->get_item_index(FILE_SHOW_IN_EXPLORER), is_directory ? TTR("Open in File Manager") : TTR("Show in File Manager")); + if (!is_directory) { p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("ExternalLink")), ED_GET_SHORTCUT("filesystem_dock/open_in_external_program"), FILE_OPEN_EXTERNAL); } + + p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Terminal")), ED_GET_SHORTCUT("filesystem_dock/open_in_terminal"), FILE_OPEN_IN_TERMINAL); + p_popup->set_item_text(p_popup->get_item_index(FILE_OPEN_IN_TERMINAL), is_directory ? TTR("Open in Terminal") : TTR("Open Containing Folder in Terminal")); #endif current_path = fpath; @@ -3105,6 +3238,7 @@ void FileSystemDock::_tree_empty_click(const Vector2 &p_pos, MouseButton p_butto // Opening the system file manager is not supported on the Android and web editors. tree_popup->add_separator(); tree_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Filesystem")), ED_GET_SHORTCUT("filesystem_dock/show_in_explorer"), FILE_SHOW_IN_EXPLORER); + tree_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Terminal")), ED_GET_SHORTCUT("filesystem_dock/open_in_terminal"), FILE_OPEN_IN_TERMINAL); #endif tree_popup->set_position(tree->get_screen_position() + p_pos); @@ -3283,6 +3417,8 @@ void FileSystemDock::_tree_gui_input(Ref p_event) { _tree_rmb_option(FILE_SHOW_IN_EXPLORER); } else if (ED_IS_SHORTCUT("filesystem_dock/open_in_external_program", p_event)) { _tree_rmb_option(FILE_OPEN_EXTERNAL); + } else if (ED_IS_SHORTCUT("filesystem_dock/open_in_terminal", p_event)) { + _tree_rmb_option(FILE_OPEN_IN_TERMINAL); } else if (ED_IS_SHORTCUT("editor/open_search", p_event)) { focus_on_filter(); } else { @@ -3343,6 +3479,8 @@ void FileSystemDock::_file_list_gui_input(Ref p_event) { _file_list_rmb_option(FILE_RENAME); } else if (ED_IS_SHORTCUT("filesystem_dock/show_in_explorer", p_event)) { _file_list_rmb_option(FILE_SHOW_IN_EXPLORER); + } else if (ED_IS_SHORTCUT("filesystem_dock/open_in_terminal", p_event)) { + _file_list_rmb_option(FILE_OPEN_IN_TERMINAL); } else if (ED_IS_SHORTCUT("editor/open_search", p_event)) { focus_on_filter(); } else { @@ -3547,6 +3685,7 @@ FileSystemDock::FileSystemDock() { // Opening the system file manager or opening in an external program is not supported on the Android and web editors. ED_SHORTCUT("filesystem_dock/show_in_explorer", TTR("Open in File Manager")); ED_SHORTCUT("filesystem_dock/open_in_external_program", TTR("Open in External Program")); + ED_SHORTCUT("filesystem_dock/open_in_terminal", TTR("Open in Terminal")); #endif // Properly translating color names would require a separate HashMap, so for simplicity they are provided as comments. diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index f70afaa65e0..d7203c15bd5 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -121,6 +121,7 @@ private: FILE_NEW, FILE_SHOW_IN_EXPLORER, FILE_OPEN_EXTERNAL, + FILE_OPEN_IN_TERMINAL, FILE_COPY_PATH, FILE_COPY_UID, FOLDER_EXPAND_ALL, diff --git a/editor/icons/Terminal.svg b/editor/icons/Terminal.svg new file mode 100644 index 00000000000..5d92bdf8bea --- /dev/null +++ b/editor/icons/Terminal.svg @@ -0,0 +1 @@ +