From 10fdd7a0b1f5ed7cae4bd36d7e1bdb0cd59d5c48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sat, 13 Sep 2025 01:37:30 -0300 Subject: [PATCH] Warn before closing terminal that is running some process (SpartanJ/ecode#644), probably still WIP, not tested outside Linux (it might not build). Build ecode with -g1, it should improve crash information. Regain editor focus after escaping from settings menu. Prevent crash when during widget splitting (couldn't reproduce it but this just avoid crashing, SpartanJ/ecode#650). --- include/eepp/system/sys.hpp | 13 +- .../eepp/ui/tools/uicodeeditorsplitter.hpp | 5 + premake4.lua | 9 +- premake5.lua | 6 +- src/eepp/system/sys.cpp | 153 ++++++++++++++++-- src/eepp/ui/tools/uicodeeditorsplitter.cpp | 24 +++ src/tools/ecode/appconfig.cpp | 2 + src/tools/ecode/appconfig.hpp | 1 + src/tools/ecode/ecode.cpp | 58 +++++++ src/tools/ecode/ecode.hpp | 2 + src/tools/ecode/settingsmenu.cpp | 42 ++++- 11 files changed, 286 insertions(+), 29 deletions(-) diff --git a/include/eepp/system/sys.hpp b/include/eepp/system/sys.hpp index f653a0c70..43ded9f17 100644 --- a/include/eepp/system/sys.hpp +++ b/include/eepp/system/sys.hpp @@ -8,6 +8,8 @@ namespace EE { namespace System { +using ProcessID = Uint64; + class EE_API Sys { public: enum class PlatformType { @@ -28,7 +30,7 @@ class EE_API Sys { }; /** @return The current process id */ - static Uint64 getProcessID(); + static ProcessID getProcessID(); /** @return the current date time */ static std::string getDateTimeStr(); @@ -126,19 +128,22 @@ class EE_API Sys { static std::unordered_map getEnvironmentVariables(); /** @return The process ids found with the corresponding process / binary / executable name */ - static std::vector pidof( const std::string& processName ); + static std::vector pidof( const std::string& processName ); /** @return A list of the current running processes */ - static std::vector> listProcesses(); + static std::vector> listProcesses(); /** @returns The unix timestamp of the process creation time */ - static Int64 getProcessCreationTime( Uint64 pid ); + static Int64 getProcessCreationTime( ProcessID pid ); /** @returns The target destination of a windows shortcut path (.lnk files) */ static std::string getShortcutTarget( const std::string& lnkFilePath ); /** @returns The user home directory */ static std::string getUserDirectory(); + + /** @returns True if the process has any childrens */ + static bool processHasChildren( ProcessID pid ); }; }} // namespace EE::System diff --git a/include/eepp/ui/tools/uicodeeditorsplitter.hpp b/include/eepp/ui/tools/uicodeeditorsplitter.hpp index 7298cce50..405239188 100644 --- a/include/eepp/ui/tools/uicodeeditorsplitter.hpp +++ b/include/eepp/ui/tools/uicodeeditorsplitter.hpp @@ -365,6 +365,10 @@ class EE_API UICodeEditorSplitter { std::shared_ptr getTextDocumentRef( TextDocument* doc ); + void setTabTryCloseCallback( UITabWidget::TabTryCloseCallback cb ); + + bool isEditorInAnyWidget( UICodeEditor* ) const; + protected: UISceneNode* mUISceneNode{ nullptr }; std::shared_ptr mThreadPool; @@ -391,6 +395,7 @@ class EE_API UICodeEditorSplitter { size_t mNavigationHistoryPos{ std::numeric_limits::max() }; std::function mOnTabWidgetCreateCb; Float mVisualSplitEdgePercent{ 0.1 }; + UITabWidget::TabTryCloseCallback mTabTryCloseCb; UICodeEditorSplitter( UICodeEditorSplitter::Client* client, UISceneNode* sceneNode, std::shared_ptr threadPool, diff --git a/premake4.lua b/premake4.lua index aa2f2b977..63d15068f 100644 --- a/premake4.lua +++ b/premake4.lua @@ -1668,7 +1668,14 @@ solution "eepp" build_link_configuration( "ecode", false ) configuration { "release", "windows" } if not is_vs() then - linkoptions { "-Wl,--export-all-symbols" } + buildoptions { "-g1" } + end + configuration { "release" } + if os.is_real("linux") or os.is_real("macosx") or os.is_real("bsd") or os.is_real("haiku") then + buildoptions { "-g1", "-fvisibility=default" } + end + if os.is_real("linux") or os.is_real("bsd") then + linkoptions { "-rdynamic" } end project "eterm" diff --git a/premake5.lua b/premake5.lua index fa60cb6cf..a666fa481 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1507,6 +1507,10 @@ workspace "eepp" links { "dw" } defines { "ECODE_HAS_DW" } end + filter { "system:linux or system:macosx or system:haiku or system:bsd", "configurations:release*" } + buildoptions { "-g1", "-fvisibility=default" } + filter { "system:linux or system:bsd", "configurations:release*" } + linkoptions { "-rdynamic" } filter { "system:windows" } links { "dbghelp", "psapi" } filter "system:haiku" @@ -1514,7 +1518,7 @@ workspace "eepp" filter "system:bsd" links { "util" } filter { "system:windows", "action:not vs*", "configurations:release*" } - linkoptions { "-Wl,--export-all-symbols" } + buildoptions { "-g1" } project "eterm" set_kind() diff --git a/src/eepp/system/sys.cpp b/src/eepp/system/sys.cpp index 98b62141b..1b907f317 100644 --- a/src/eepp/system/sys.cpp +++ b/src/eepp/system/sys.cpp @@ -700,7 +700,7 @@ double Sys::getSystemTime() { #endif } -Uint64 Sys::getProcessID() { +ProcessID Sys::getProcessID() { #if EE_PLATFORM == EE_PLATFORM_WIN return GetCurrentProcessId(); #elif EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN @@ -1181,9 +1181,9 @@ std::vector Sys::getEnvSplit( const std::string& name ) { static ULONG_PTR GetParentProcessId() { ULONG_PTR pbi[6]; ULONG ulSize = 0; - LONG( WINAPI * NtQueryInformationProcess ) - ( HANDLE ProcessHandle, ULONG ProcessInformationClass, PVOID ProcessInformation, - ULONG ProcessInformationLength, PULONG ReturnLength ); + LONG( WINAPI * NtQueryInformationProcess )( + HANDLE ProcessHandle, ULONG ProcessInformationClass, PVOID ProcessInformation, + ULONG ProcessInformationLength, PULONG ReturnLength ); *(FARPROC*)&NtQueryInformationProcess = GetProcAddress( LoadLibraryA( "NTDLL.DLL" ), "NtQueryInformationProcess" ); if ( NtQueryInformationProcess ) { @@ -1338,7 +1338,7 @@ std::string Sys::getProcessFilePath() { #endif } -Int64 Sys::getProcessCreationTime( Uint64 pid ) { +Int64 Sys::getProcessCreationTime( ProcessID pid ) { Int64 creationTime = -1; #if EE_PLATFORM == EE_PLATFORM_WIN @@ -1426,9 +1426,9 @@ Int64 Sys::getProcessCreationTime( Uint64 pid ) { return creationTime; } -std::vector Sys::pidof( const std::string& processName ) { +std::vector Sys::pidof( const std::string& processName ) { #if EE_PLATFORM == EE_PLATFORM_WIN - std::vector pids; + std::vector pids; std::vector extensions = getEnvSplit( "PATHEXT" ); HMODULE hPsapi = LoadLibrary( TEXT( "psapi.dll" ) ); @@ -1496,7 +1496,7 @@ std::vector Sys::pidof( const std::string& processName ) { return pids; #elif EE_PLATFORM == EE_PLATFORM_LINUX || EE_PLATFORM == EE_PLATFORM_ANDROID - std::vector pids; + std::vector pids; DIR* dir = opendir( "/proc" ); if ( !dir ) { return pids; @@ -1524,7 +1524,7 @@ std::vector Sys::pidof( const std::string& processName ) { closedir( dir ); return pids; #elif EE_PLATFORM == EE_PLATFORM_MACOS - std::vector pids; + std::vector pids; int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; size_t len; @@ -1555,7 +1555,7 @@ std::vector Sys::pidof( const std::string& processName ) { free( procs ); return pids; #elif EE_PLATFORM == EE_PLATFORM_BSD - std::vector pids; + std::vector pids; int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0 }; size_t len; @@ -1586,7 +1586,7 @@ std::vector Sys::pidof( const std::string& processName ) { free( procs ); return pids; #elif EE_PLATFORM == EE_PLATFORM_HAIKU - std::vector pids; + std::vector pids; int32 cookie = 0; team_info teamInfo; while ( get_next_team_info( &cookie, &teamInfo ) == B_OK ) { @@ -1600,9 +1600,9 @@ std::vector Sys::pidof( const std::string& processName ) { #endif } -std::vector> Sys::listProcesses() { +std::vector> Sys::listProcesses() { #if EE_PLATFORM == EE_PLATFORM_WIN - std::vector> pids; + std::vector> pids; std::vector extensions = getEnvSplit( "PATHEXT" ); HMODULE hPsapi = LoadLibrary( TEXT( "psapi.dll" ) ); @@ -1655,7 +1655,7 @@ std::vector> Sys::listProcesses() { FreeLibrary( hPsapi ); return pids; #elif EE_PLATFORM == EE_PLATFORM_LINUX || EE_PLATFORM == EE_PLATFORM_ANDROID - std::vector> pids; + std::vector> pids; DIR* dir = opendir( "/proc" ); if ( !dir ) { return pids; @@ -1681,7 +1681,7 @@ std::vector> Sys::listProcesses() { closedir( dir ); return pids; #elif EE_PLATFORM == EE_PLATFORM_MACOS - std::vector> pids; + std::vector> pids; int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; size_t len; @@ -1711,7 +1711,7 @@ std::vector> Sys::listProcesses() { free( procs ); return pids; #elif EE_PLATFORM == EE_PLATFORM_BSD - std::vector> pids; + std::vector> pids; int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0 }; size_t len; @@ -1741,7 +1741,7 @@ std::vector> Sys::listProcesses() { free( procs ); return pids; #elif EE_PLATFORM == EE_PLATFORM_HAIKU - std::vector> pids; + std::vector> pids; int32 cookie = 0; team_info teamInfo; while ( get_next_team_info( &cookie, &teamInfo ) == B_OK ) { @@ -1832,4 +1832,123 @@ std::string Sys::getUserDirectory() { #endif } +bool Sys::processHasChildren( ProcessID pid ) { +#if EE_PLATFORM == EE_PLATFORM_WIN + HANDLE hSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ); + if ( hSnapshot == INVALID_HANDLE_VALUE ) { + return false; + } + + PROCESSENTRY32 pe32; + pe32.dwSize = sizeof( PROCESSENTRY32 ); + + if ( !Process32First( hSnapshot, &pe32 ) ) { + CloseHandle( hSnapshot ); + return false; + } + + do { + if ( pe32.th32ParentProcessID == static_cast( pid ) ) { + CloseHandle( hSnapshot ); + return true; // Found a child + } + } while ( Process32Next( hSnapshot, &pe32 ) ); + + CloseHandle( hSnapshot ); + return false; +#elif EE_PLATFORM == EE_PLATFORM_LINUX || EE_PLATFORM == EE_PLATFORM_ANDROID + DIR* dir = opendir( "/proc" ); + if ( !dir ) { + return false; + } + + struct dirent* entry; + while ( ( entry = readdir( dir ) ) != nullptr ) { + char* endptr; + long tpid = strtol( entry->d_name, &endptr, 10 ); + if ( *endptr != '\0' || tpid <= 0 ) { // Skip if not a valid PID directory + continue; + } + std::string status_path = std::string( "/proc/" ) + entry->d_name + "/status"; + std::ifstream status_file( status_path ); + if ( status_file.is_open() ) { + std::string line; + while ( std::getline( status_file, line ) ) { + if ( line.rfind( "PPid:", 0 ) == 0 ) { + try { + long ppid = std::stol( line.substr( 5 ) ); + if ( ppid == (Int64)pid ) { + closedir( dir ); + return true; + } + } catch ( const std::invalid_argument& ) { + } + break; + } + } + } + } + + closedir( dir ); + return false; +#elif EE_PLATFORM == EE_PLATFORM_MACOS || EE_PLATFORM == EE_PLATFORM_IOS || \ + EE_PLATFORM == EE_PLATFORM_BSD + struct kinfo_proc* proc_list = nullptr; + size_t proc_count = 0; + + // MIB (Management Information Base) for sysctl + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; + + // First, call sysctl to get the size of the buffer needed + if ( sysctl( mib, 4, nullptr, &proc_count, nullptr, 0 ) < 0 ) { + return false; + } + + proc_list = (struct kinfo_proc*)malloc( proc_count ); + if ( !proc_list ) { + return false; + } + + // Now, call sysctl again to populate the buffer + if ( sysctl( mib, 4, proc_list, &proc_count, nullptr, 0 ) < 0 ) { + free( proc_list ); + return false; + } + + size_t num_procs = proc_count / sizeof( struct kinfo_proc ); + for ( size_t i = 0; i < num_procs; i++ ) { + pid_t ppid; +#if defined( __APPLE__ ) + ppid = proc_list[i].kp_eproc.e_ppid; +#else // FreeBSD + ppid = proc_list[i].ki_ppid; +#endif + if ( static_cast( ppid ) == pid ) { + free( proc_list ); + return true; // Found a child + } + } + + free( proc_list ); + return false; + +#elif EE_PLATFORM == EE_PLATFORM_HAIKU + int32 cookie = 0; + team_info ti; + while ( get_next_team_info( &cookie, &ti ) == B_OK ) { + if ( static_cast( ti.parent ) == pid ) { + return true; // Found a child + } + } + return false; +#elif EE_PLATFORM == EE_PLATFORM_EMSCRIPTEN + return false; +#else +#warning "Unsupported operating system: processHasChildren not implemented." + return false; // To satisfy compiler, though #error should halt compilation. +#endif + + return false; +} + }} // namespace EE::System diff --git a/src/eepp/ui/tools/uicodeeditorsplitter.cpp b/src/eepp/ui/tools/uicodeeditorsplitter.cpp index e47337f54..01bfc189c 100644 --- a/src/eepp/ui/tools/uicodeeditorsplitter.cpp +++ b/src/eepp/ui/tools/uicodeeditorsplitter.cpp @@ -486,6 +486,10 @@ void UICodeEditorSplitter::loadAsyncFileFromPathInNewTab( void UICodeEditorSplitter::setCurrentEditor( UICodeEditor* editor ) { eeASSERT( checkEditorExists( editor ) ); + if ( !isEditorInAnyWidget( editor ) ) { + eeASSERT( true ); // This should not happen by design + return; + } bool isNew = mCurEditor != editor; bool isNewW = mCurWidget != editor; mCurEditor = editor; @@ -646,6 +650,9 @@ UITabWidget* UICodeEditorSplitter::createTabWidget( Node* parent ) { } ); tabWidget->setTabTryCloseCallback( [this]( UITab* tab, UITabWidget::FocusTabBehavior focusTabBehavior ) -> bool { + if ( mTabTryCloseCb ) + return mTabTryCloseCb( tab, focusTabBehavior ); + if ( tab->getOwnedWidget()->isType( UI_TYPE_CODEEDITOR ) ) { tryTabClose( tab->getOwnedWidget()->asType(), focusTabBehavior ); return false; @@ -1429,6 +1436,18 @@ bool UICodeEditorSplitter::checkEditorExists( UICodeEditor* checkEditor ) const return found || checkEditor == nullptr || mAboutToAddEditor == checkEditor || mFirstCodeEditor; } +bool UICodeEditorSplitter::isEditorInAnyWidget( UICodeEditor* checkEditor ) const { + bool found = false; + forEachEditorStoppable( [&found, checkEditor]( UICodeEditor* editor ) { + if ( editor == checkEditor ) { + found = true; + return true; + } + return false; + } ); + return found; +} + bool UICodeEditorSplitter::curEditorExists() const { bool found = false; forEachEditorStoppable( [&]( UICodeEditor* editor ) { @@ -1791,4 +1810,9 @@ std::shared_ptr UICodeEditorSplitter::getTextDocumentRef( TextDocu return ret; } +void UICodeEditorSplitter::setTabTryCloseCallback( UITabWidget::TabTryCloseCallback cb ) { + mTabTryCloseCb = cb; + forEachTabWidget( [&cb]( UITabWidget* tw ) { tw->setTabTryCloseCallback( cb ); } ); +} + }}} // namespace EE::UI::Tools diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp index 769f7847c..cfeb1c183 100644 --- a/src/tools/ecode/appconfig.cpp +++ b/src/tools/ecode/appconfig.cpp @@ -222,6 +222,7 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath, term.unsupportedOSWarnDisabled = ini.getValueB( "terminal", "unsupported_os_warn_disabled", false ); term.closeTerminalTabOnExit = ini.getValueB( "terminal", "close_terminal_tab_on_exit", false ); + term.warnBeforeClosingTab = ini.getValueB( "terminal", "warn_before_closing_tab", true ); workspace.restoreLastSession = ini.getValueB( "workspace", "restore_last_session", false ); workspace.checkForUpdatesAtStartup = @@ -380,6 +381,7 @@ void AppConfig::save( const std::vector& recentFiles, ini.setValue( "terminal", "scrollback", String::toString( term.scrollback ) ); ini.setValueB( "terminal", "unsupported_os_warn_disabled", term.unsupportedOSWarnDisabled ); ini.setValueB( "terminal", "close_terminal_tab_on_exit", term.closeTerminalTabOnExit ); + ini.setValueB( "terminal", "warn_before_closing_tab", term.warnBeforeClosingTab ); ini.setValueB( "window", "vsync", context.VSync ); ini.setValue( "window", "glversion", diff --git a/src/tools/ecode/appconfig.hpp b/src/tools/ecode/appconfig.hpp index 269829187..d74684753 100644 --- a/src/tools/ecode/appconfig.hpp +++ b/src/tools/ecode/appconfig.hpp @@ -176,6 +176,7 @@ struct TerminalConfig { Uint64 scrollback{ 10000 }; bool unsupportedOSWarnDisabled{ false }; bool closeTerminalTabOnExit{ false }; + bool warnBeforeClosingTab{ true }; }; struct WorkspaceConfig { diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 0105a63dc..92a718ad0 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -60,6 +60,19 @@ void appLoop() { appInstance->mainLoop(); } +bool App::isAnyTerminalDirty() const { + bool dirty = false; + mSplitter->forEachWidgetTypeStoppable( UI_TYPE_TERMINAL, [&dirty]( UIWidget* widget ) -> bool { + ProcessID pid = widget->asType()->getTerm()->getTerminal()->getProcess()->pid(); + if ( Sys::processHasChildren( pid ) ) { + dirty = true; + return true; + } + return false; + } ); + return dirty; +} + bool App::onCloseRequestCallback( EE::Window::Window* ) { if ( mSplitter->isAnyEditorDirty() && ( !mConfig.workspace.sessionSnapshot || mCurrentProject.empty() ) ) { @@ -81,6 +94,26 @@ bool App::onCloseRequestCallback( EE::Window::Window* ) { mCloseMsgBox->center(); mCloseMsgBox->showWhenReady(); return false; + } else if ( mConfig.term.warnBeforeClosingTab && isAnyTerminalDirty() ) { + if ( mCloseMsgBox ) + return false; + mCloseMsgBox = UIMessageBox::New( + UIMessageBox::OK_CANCEL, i18n( "confirm_ecode_exit_terminal_close_warn", + "Do you really want to close the code editor?\nAt " + "least one terminal is still running a process." ) + .unescape() ); + + mCloseMsgBox->on( Event::OnConfirm, [this]( const Event* ) { + saveProject(); + saveConfig(); + mWindow->close(); + } ); + mCloseMsgBox->on( Event::OnWindowClose, [this]( auto ) { mCloseMsgBox = nullptr; } ); + mCloseMsgBox->setTitle( + String::format( i18n( "close_title", "Close %s?" ).toUtf8(), mWindowTitle ) ); + mCloseMsgBox->center(); + mCloseMsgBox->showWhenReady(); + return false; } else { saveProject(); saveConfig(); @@ -4119,6 +4152,31 @@ void App::init( InitParameters& params ) { tabWidget->getTabBar()->onDoubleClick( [this]( const MouseEvent* ) { mSplitter->createEditorInNewTab(); } ); } ); + mSplitter->setTabTryCloseCallback( + [this]( UITab* tab, UITabWidget::FocusTabBehavior focusTabBehavior ) -> bool { + if ( tab->getOwnedWidget()->isType( UI_TYPE_CODEEDITOR ) ) { + mSplitter->tryTabClose( tab->getOwnedWidget()->asType(), + focusTabBehavior ); + return false; + } else if ( mConfig.term.warnBeforeClosingTab && + tab->getOwnedWidget()->isType( UI_TYPE_TERMINAL ) ) { + UITerminal* term = tab->getOwnedWidget()->asType(); + ProcessID pid = term->getTerm()->getTerminal()->getProcess()->pid(); + if ( Sys::processHasChildren( pid ) ) { + UIMessageBox* msgBox = + UIMessageBox::New( UIMessageBox::OK_CANCEL, + i18n( "terminal_close_warn", + "Are you sure you want to close this " + "terminal?\nIt's still running a process." ) ); + msgBox->on( Event::OnConfirm, [tab]( auto ) { tab->removeTab(); } ); + msgBox->setTitle( "ecode" ); + msgBox->center(); + msgBox->showWhenReady(); + return false; + } + } + return true; + } ); mPluginManager->setSplitter( mSplitter ); Log::info( "Base UI took: %.2f ms", globalClock.getElapsedTime().asMilliseconds() ); diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index 7198389c1..32a4c0af0 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -708,6 +708,8 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider { std::string titleFromEditor( UICodeEditor* editor ); + bool isAnyTerminalDirty() const; + bool onCloseRequestCallback( EE::Window::Window* ); void addRemainingTabWidgets( Node* widget ); diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp index f8ca55dcd..741a18a3b 100644 --- a/src/tools/ecode/settingsmenu.cpp +++ b/src/tools/ecode/settingsmenu.cpp @@ -168,8 +168,17 @@ void SettingsMenu::createSettingsMenu( App* app, UIMenuBar* menuBar ) { : menuButton ); } ); - if ( menuBarIndex == 0 ) - menu->on( Event::OnMenuHide, [menuHint]( auto ) { menuHint->setVisible( false ); } ); + if ( menuBarIndex == 0 ) { + menu->on( Event::OnMenuHide, [menuHint, this]( auto ) { + menuHint->setVisible( false ); + + if ( mSplitter->getUISceneNode()->getEventDispatcher()->getFocusNode() == + mSettingsMenu && + mSplitter->getCurWidget() ) { + mSplitter->getCurWidget()->setFocus(); + } + } ); + } menu->on( Event::OnVisibleChange, [this, menuBarIndex]( const Event* event ) { if ( mApp->getConfig().ui.showMenuBar ) { @@ -963,11 +972,26 @@ UIMenu* SettingsMenu::createTerminalMenu() { mTerminalMenu->addSubMenu( i18n( "new_terminal_behavior", "New Terminal Behavior" ), findIcon( "terminal" ), newTerminalBehaviorSubMenu ); + mTerminalMenu->addSeparator(); + mTerminalMenu ->addCheckBox( i18n( "close_terminal_tab_on_exit", "Close Terminal Tab on Exit" ), mApp->getConfig().term.closeTerminalTabOnExit ) + ->setTooltipText( i18n( "close_terminal_tab_on_exit_tooltip", + "Closes the terminal tab when the main process exits.\nIf " + "disabled, a new shell process starts instead." ) ) ->setId( "close-terminal-tab-on-exit" ); + mTerminalMenu + ->addCheckBox( i18n( "warn_before_closing_tab", "Warn Before Closing Tab" ), + mApp->getConfig().term.warnBeforeClosingTab ) + ->setTooltipText( i18n( "warn_before_closing_tab_tooltip", + "Prompts for confirmation if a program is still running when " + "closing a terminal tab." ) ) + ->setId( "warn-before-closing-tab" ); + + mTerminalMenu->addSeparator(); + mTerminalMenu ->add( i18n( "configure_terminal_shell", "Configure Terminal Shell" ), findIcon( "terminal" ), getKeybind( "configure-terminal-shell" ) ) @@ -991,6 +1015,9 @@ UIMenu* SettingsMenu::createTerminalMenu() { mSplitter->forEachWidgetType( UI_TYPE_TERMINAL, [active]( UIWidget* widget ) { widget->asType()->getTerm()->setKeepAlive( !active ); } ); + } else if ( "warn-before-closing-tab" == id ) { + bool active = event->getNode()->asType()->isActive(); + mApp->getConfig().term.warnBeforeClosingTab = active; } else if ( mSplitter->getCurWidget() && mSplitter->getCurWidget()->isType( UI_TYPE_TERMINAL ) ) { UITerminal* terminal = mSplitter->getCurWidget()->asType(); @@ -1010,6 +1037,7 @@ UIMenu* SettingsMenu::createTerminalMenu() { UIMenu* SettingsMenu::createEditMenu() { mEditMenu = UIPopUpMenu::New(); + mEditMenu->setId( "edit_menu" ); mEditMenu->add( i18n( "undo", "Undo" ), findIcon( "undo" ), getKeybind( "undo" ) ) ->setId( "undo" ); mEditMenu->add( i18n( "redo", "Redo" ), findIcon( "redo" ), getKeybind( "redo" ) ) @@ -1034,7 +1062,9 @@ UIMenu* SettingsMenu::createEditMenu() { ->add( i18n( "find_replace", "Find/Replace" ), findIcon( "find-replace" ), getKeybind( "find-replace" ) ) ->setId( "find-replace" ); + mEditMenu->addSeparator(); + mEditMenu ->add( i18n( "open_containing_folder_ellipsis", "Open Containing Folder..." ), findIcon( "folder-open" ), getKeybind( "open-containing-folder" ) ) @@ -1080,11 +1110,11 @@ UIMenu* SettingsMenu::createEditMenu() { mEditMenu->getItemId( "cut" )->setEnabled( false ); mEditMenu->getItemId( "open-containing-folder" )->setVisible( false ); mEditMenu->getItemId( "copy-containing-folder-path" )->setVisible( false ); + moveSep->setEnabled( false )->setVisible( false ); mEditMenu->getItemId( "open-in-new-window" )->setVisible( false ); mEditMenu->getItemId( "move-to-new-window" )->setVisible( false ); + fileSep->setEnabled( false )->setVisible( false ); mEditMenu->getItemId( "copy-file-path" )->setVisible( false ); - moveSep->setVisible( false ); - fileSep->setVisible( false ); return; } auto doc = mSplitter->getCurEditor()->getDocumentRef(); @@ -1094,11 +1124,11 @@ UIMenu* SettingsMenu::createEditMenu() { mEditMenu->getItemId( "cut" )->setEnabled( doc->hasSelection() ); mEditMenu->getItemId( "open-containing-folder" )->setVisible( doc->hasFilepath() ); mEditMenu->getItemId( "copy-containing-folder-path" )->setVisible( doc->hasFilepath() ); + moveSep->setEnabled( true )->setVisible( true ); mEditMenu->getItemId( "open-in-new-window" )->setVisible( doc->hasFilepath() ); mEditMenu->getItemId( "move-to-new-window" )->setVisible( doc->hasFilepath() ); + fileSep->setEnabled( doc->hasFilepath() )->setVisible( doc->hasFilepath() ); mEditMenu->getItemId( "copy-file-path" )->setVisible( doc->hasFilepath() ); - moveSep->setVisible( true ); - fileSep->setVisible( doc->hasFilepath() ); } ); return mEditMenu; }