diff options
author | Yuri Sizov <yuris@humnom.net> | 2023-12-22 17:19:53 +0100 |
---|---|---|
committer | Yuri Sizov <yuris@humnom.net> | 2023-12-22 17:19:53 +0100 |
commit | 13d34d6a2b80391a038f441729610af0b207250b (patch) | |
tree | 9b8c1e8c899736a04f989a555b1d551e710dd997 | |
parent | 6b403712f1adc5e2ba96682bb163a7bd378ceb81 (diff) | |
parent | 0e97acff84ca6c859b48b22a6d42c4680ef1c432 (diff) | |
download | redot-engine-13d34d6a2b80391a038f441729610af0b207250b.tar.gz |
Merge pull request #85923 from Calinou/editor-filesystem-dock-add-open-in-terminal
Add a editor FileSystem dock action to open a terminal in selected folder
-rw-r--r-- | doc/classes/EditorSettings.xml | 15 | ||||
-rw-r--r-- | editor/editor_settings.cpp | 2 | ||||
-rw-r--r-- | editor/filesystem_dock.cpp | 143 | ||||
-rw-r--r-- | editor/filesystem_dock.h | 1 | ||||
-rw-r--r-- | editor/icons/Terminal.svg | 1 |
5 files changed, 160 insertions, 2 deletions
diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 93d2bb8548..5c317a5088 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -466,6 +466,21 @@ <member name="filesystem/external_programs/raster_image_editor" type="String" setter="" getter=""> 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. </member> + <member name="filesystem/external_programs/terminal_emulator" type="String" setter="" getter=""> + 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. + </member> + <member name="filesystem/external_programs/terminal_emulator_flags" type="String" setter="" getter=""> + 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. + </member> <member name="filesystem/external_programs/vector_image_editor" type="String" setter="" getter=""> 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. </member> diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 98706a10f2..e6ba39cae7 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -500,6 +500,8 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> 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 1b966fced1..e5d03942e3 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -2135,6 +2135,135 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } } break; + case FILE_OPEN_IN_TERMINAL: { + String fpath = current_path; + if (current_path == "Favorites") { + fpath = p_selected[0]; + } + + Vector<String> 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<String> test_args; // Required for `execute()`, as it doesn't accept `Vector<String>`. + 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<String> terminal_emulator_args; // Required for `execute()`, as it doesn't accept `Vector<String>`. +#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<String> 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, Vector<Str // Opening the system file manager is not supported on the Android and web editors. const bool is_directory = fpath.ends_with("/"); - const String item_text = is_directory ? TTR("Open in File Manager") : TTR("Show in File Manager"); + p_popup->add_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<InputEvent> 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<InputEvent> 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 f70afaa65e..d7203c15bd 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 0000000000..5d92bdf8be --- /dev/null +++ b/editor/icons/Terminal.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="m2 13 5.0000552-5-5.0000552-5"/><path d="m9 13h5"/></g></svg> |