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).
This commit is contained in:
Martín Lucas Golini
2025-09-13 01:37:30 -03:00
parent 4c097fd3f7
commit 10fdd7a0b1
11 changed files with 286 additions and 29 deletions

View File

@@ -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<std::string, std::string> getEnvironmentVariables();
/** @return The process ids found with the corresponding process / binary / executable name */
static std::vector<Uint64> pidof( const std::string& processName );
static std::vector<ProcessID> pidof( const std::string& processName );
/** @return A list of the current running processes */
static std::vector<std::pair<Uint64, std::string>> listProcesses();
static std::vector<std::pair<ProcessID, std::string>> 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

View File

@@ -365,6 +365,10 @@ class EE_API UICodeEditorSplitter {
std::shared_ptr<TextDocument> getTextDocumentRef( TextDocument* doc );
void setTabTryCloseCallback( UITabWidget::TabTryCloseCallback cb );
bool isEditorInAnyWidget( UICodeEditor* ) const;
protected:
UISceneNode* mUISceneNode{ nullptr };
std::shared_ptr<ThreadPool> mThreadPool;
@@ -391,6 +395,7 @@ class EE_API UICodeEditorSplitter {
size_t mNavigationHistoryPos{ std::numeric_limits<size_t>::max() };
std::function<void( UITabWidget* )> mOnTabWidgetCreateCb;
Float mVisualSplitEdgePercent{ 0.1 };
UITabWidget::TabTryCloseCallback mTabTryCloseCb;
UICodeEditorSplitter( UICodeEditorSplitter::Client* client, UISceneNode* sceneNode,
std::shared_ptr<ThreadPool> threadPool,

View File

@@ -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"

View File

@@ -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()

View File

@@ -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<std::string> 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<Uint64> Sys::pidof( const std::string& processName ) {
std::vector<ProcessID> Sys::pidof( const std::string& processName ) {
#if EE_PLATFORM == EE_PLATFORM_WIN
std::vector<Uint64> pids;
std::vector<ProcessID> pids;
std::vector<std::string> extensions = getEnvSplit( "PATHEXT" );
HMODULE hPsapi = LoadLibrary( TEXT( "psapi.dll" ) );
@@ -1496,7 +1496,7 @@ std::vector<Uint64> Sys::pidof( const std::string& processName ) {
return pids;
#elif EE_PLATFORM == EE_PLATFORM_LINUX || EE_PLATFORM == EE_PLATFORM_ANDROID
std::vector<Uint64> pids;
std::vector<ProcessID> pids;
DIR* dir = opendir( "/proc" );
if ( !dir ) {
return pids;
@@ -1524,7 +1524,7 @@ std::vector<Uint64> Sys::pidof( const std::string& processName ) {
closedir( dir );
return pids;
#elif EE_PLATFORM == EE_PLATFORM_MACOS
std::vector<Uint64> pids;
std::vector<ProcessID> pids;
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
size_t len;
@@ -1555,7 +1555,7 @@ std::vector<Uint64> Sys::pidof( const std::string& processName ) {
free( procs );
return pids;
#elif EE_PLATFORM == EE_PLATFORM_BSD
std::vector<Uint64> pids;
std::vector<ProcessID> pids;
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0 };
size_t len;
@@ -1586,7 +1586,7 @@ std::vector<Uint64> Sys::pidof( const std::string& processName ) {
free( procs );
return pids;
#elif EE_PLATFORM == EE_PLATFORM_HAIKU
std::vector<Uint64> pids;
std::vector<ProcessID> pids;
int32 cookie = 0;
team_info teamInfo;
while ( get_next_team_info( &cookie, &teamInfo ) == B_OK ) {
@@ -1600,9 +1600,9 @@ std::vector<Uint64> Sys::pidof( const std::string& processName ) {
#endif
}
std::vector<std::pair<Uint64, std::string>> Sys::listProcesses() {
std::vector<std::pair<ProcessID, std::string>> Sys::listProcesses() {
#if EE_PLATFORM == EE_PLATFORM_WIN
std::vector<std::pair<Uint64, std::string>> pids;
std::vector<std::pair<ProcessID, std::string>> pids;
std::vector<std::string> extensions = getEnvSplit( "PATHEXT" );
HMODULE hPsapi = LoadLibrary( TEXT( "psapi.dll" ) );
@@ -1655,7 +1655,7 @@ std::vector<std::pair<Uint64, std::string>> Sys::listProcesses() {
FreeLibrary( hPsapi );
return pids;
#elif EE_PLATFORM == EE_PLATFORM_LINUX || EE_PLATFORM == EE_PLATFORM_ANDROID
std::vector<std::pair<Uint64, std::string>> pids;
std::vector<std::pair<ProcessID, std::string>> pids;
DIR* dir = opendir( "/proc" );
if ( !dir ) {
return pids;
@@ -1681,7 +1681,7 @@ std::vector<std::pair<Uint64, std::string>> Sys::listProcesses() {
closedir( dir );
return pids;
#elif EE_PLATFORM == EE_PLATFORM_MACOS
std::vector<std::pair<Uint64, std::string>> pids;
std::vector<std::pair<ProcessID, std::string>> pids;
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
size_t len;
@@ -1711,7 +1711,7 @@ std::vector<std::pair<Uint64, std::string>> Sys::listProcesses() {
free( procs );
return pids;
#elif EE_PLATFORM == EE_PLATFORM_BSD
std::vector<std::pair<Uint64, std::string>> pids;
std::vector<std::pair<ProcessID, std::string>> pids;
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0 };
size_t len;
@@ -1741,7 +1741,7 @@ std::vector<std::pair<Uint64, std::string>> Sys::listProcesses() {
free( procs );
return pids;
#elif EE_PLATFORM == EE_PLATFORM_HAIKU
std::vector<std::pair<Uint64, std::string>> pids;
std::vector<std::pair<ProcessID, std::string>> 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<DWORD>( 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<ProcessID>( 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<ProcessID>( 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

View File

@@ -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<UICodeEditor>(), 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<TextDocument> UICodeEditorSplitter::getTextDocumentRef( TextDocu
return ret;
}
void UICodeEditorSplitter::setTabTryCloseCallback( UITabWidget::TabTryCloseCallback cb ) {
mTabTryCloseCb = cb;
forEachTabWidget( [&cb]( UITabWidget* tw ) { tw->setTabTryCloseCallback( cb ); } );
}
}}} // namespace EE::UI::Tools

View File

@@ -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<std::string>& 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",

View File

@@ -176,6 +176,7 @@ struct TerminalConfig {
Uint64 scrollback{ 10000 };
bool unsupportedOSWarnDisabled{ false };
bool closeTerminalTabOnExit{ false };
bool warnBeforeClosingTab{ true };
};
struct WorkspaceConfig {

View File

@@ -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<UITerminal>()->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<UICodeEditor>(),
focusTabBehavior );
return false;
} else if ( mConfig.term.warnBeforeClosingTab &&
tab->getOwnedWidget()->isType( UI_TYPE_TERMINAL ) ) {
UITerminal* term = tab->getOwnedWidget()->asType<UITerminal>();
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() );

View File

@@ -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 );

View File

@@ -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<UITerminal>()->getTerm()->setKeepAlive( !active );
} );
} else if ( "warn-before-closing-tab" == id ) {
bool active = event->getNode()->asType<UIMenuCheckBox>()->isActive();
mApp->getConfig().term.warnBeforeClosingTab = active;
} else if ( mSplitter->getCurWidget() &&
mSplitter->getCurWidget()->isType( UI_TYPE_TERMINAL ) ) {
UITerminal* terminal = mSplitter->getCurWidget()->asType<UITerminal>();
@@ -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;
}