Single instance support (tested in Linux and Windows, pending the rest of the OSes, issue SpartanJ/ecode#58).

Refresh buttons state when Clear Menu is used (issue SpartanJ/ecode#339).
This commit is contained in:
Martín Lucas Golini
2024-09-28 01:01:14 -03:00
parent 63cc931dec
commit 49e56f05a2
16 changed files with 551 additions and 112 deletions

View File

@@ -124,6 +124,12 @@ class EE_API Sys {
/** @return The process environment variables */
static std::unordered_map<std::string, std::string> getEnvironmentVariables();
/** @return The process ids found with the correspoding process / binary / executable name */
static std::vector<Uint64> pidof( const std::string& processName );
/** @returns The unix timestamp of the process creation time */
static Uint64 getProcessCreationTime( Uint64 pid );
};
}} // namespace EE::System

View File

@@ -105,7 +105,7 @@ enum UINodeType {
UI_TYPE_STACK_LAYOUT,
UI_TYPE_MODULES = 10000,
UI_TYPE_TERMINAL = 10001,
UI_TYPE_USER = 100000
UI_TYPE_USER = 200000
};
enum class ScrollBarMode : Uint32 { Auto, AlwaysOn, AlwaysOff };

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 14.0.1, 2024-09-21T01:28:27. -->
<!-- Written by QtCreator 14.0.1, 2024-09-28T00:59:35. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>

View File

@@ -1,11 +1,14 @@
#include <cerrno>
#include <climits>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <ctype.h>
#include <eepp/core/string.hpp>
#include <eepp/system/filesystem.hpp>
#include <eepp/system/log.hpp>
#include <eepp/system/sys.hpp>
#include <fstream>
#include <iomanip>
#include <iostream>
@@ -15,8 +18,10 @@
#endif
#if defined( EE_PLATFORM_POSIX )
#include <dirent.h>
#include <dlfcn.h>
#include <sys/utsname.h>
#include <unistd.h>
#endif
#if EE_PLATFORM == EE_PLATFORM_MACOS
@@ -31,12 +36,24 @@
#include <windows.h>
#undef GetDiskFreeSpace
#undef GetTempPath
// clang-format off
#include <psapi.h>
#include <tlhelp32.h>
// clang-format on
// Dynamically load PSAPI functions for Windows
typedef BOOL( WINAPI* EnumProcesses_t )( DWORD*, DWORD, DWORD* );
typedef BOOL( WINAPI* EnumProcessModules_t )( HANDLE, HMODULE*, DWORD, LPDWORD );
typedef DWORD( WINAPI* GetModuleBaseName_t )( HANDLE, HMODULE, LPSTR, DWORD );
#elif EE_PLATFORM == EE_PLATFORM_LINUX || EE_PLATFORM == EE_PLATFORM_ANDROID
#include <libgen.h>
#include <mntent.h>
#include <unistd.h>
#include <sys/sysinfo.h>
#elif EE_PLATFORM == EE_PLATFORM_HAIKU
#include <Directory.h>
#include <OS.h>
#include <Path.h>
#include <Volume.h>
#include <VolumeRoster.h>
@@ -47,10 +64,13 @@
#elif EE_PLATFORM == EE_PLATFORM_SOLARIS
#include <stdlib.h>
#elif EE_PLATFORM == EE_PLATFORM_BSD
#include <unistd.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/user.h>
#endif
#if EE_PLATFORM == EE_PLATFORM_MACOS || EE_PLATFORM == EE_PLATFORM_IOS
#include <libproc.h>
#include <mach-o/dyld.h>
#include <spawn.h>
#endif
@@ -1268,7 +1288,9 @@ std::string Sys::getProcessFilePath() {
#if EE_PLATFORM == EE_PLATFORM_WIN
std::wstring exename( _MAX_DIR, 0 );
GetModuleFileNameW( 0, &exename[0], _MAX_PATH );
DWORD size = GetModuleFileNameW( 0, &exename[0], _MAX_PATH );
if ( size > 0 && size < _MAX_PATH )
exename.resize( size ); // Resize to actual size without extra null characters
return String( exename ).toUtf8();
#elif EE_PLATFORM == EE_PLATFORM_LINUX || EE_PLATFORM == EE_PLATFORM_ANDROID
char path[] = "/proc/self/exe";
@@ -1300,4 +1322,243 @@ std::string Sys::getProcessFilePath() {
#endif
}
Uint64 Sys::getProcessCreationTime( Uint64 pid ) {
Uint64 creationTime = 0;
#if EE_PLATFORM == EE_PLATFORM_WIN
int rpid = static_cast<int>( pid );
HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, rpid );
if ( hProcess == NULL ) {
return -1;
}
FILETIME creationFileTime, exitFileTime, kernelFileTime, userFileTime;
if ( GetProcessTimes( hProcess, &creationFileTime, &exitFileTime, &kernelFileTime,
&userFileTime ) ) {
ULARGE_INTEGER ull;
ull.LowPart = creationFileTime.dwLowDateTime;
ull.HighPart = creationFileTime.dwHighDateTime;
// Convert from Windows file time to Unix timestamp
creationTime =
static_cast<time_t>( ( ull.QuadPart - 116444736000000000ULL ) / 10000000ULL );
} else {
creationTime = -1;
}
CloseHandle( hProcess );
#elif EE_PLATFORM == EE_PLATFORM_LINUX
std::ifstream statFile( "/proc/" + std::to_string( pid ) + "/stat" );
if ( !statFile.is_open() ) {
return -1;
}
std::string token;
long startTimeTicks = 0;
int field = 1;
while ( statFile >> token ) {
if ( field == 22 ) { // The 22nd field is the start time in clock ticks
startTimeTicks = std::stol( token );
break;
}
field++;
}
struct sysinfo sysInfo;
sysinfo( &sysInfo );
long uptime = sysInfo.uptime;
long clockTicksPerSecond = sysconf( _SC_CLK_TCK );
creationTime = time( NULL ) - uptime + ( startTimeTicks / clockTicksPerSecond );
statFile.close();
#elif EE_PLATFORM == EE_PLATFORM_MACOS
struct proc_bsdinfo procInfo;
int rpid = static_cast<int>( pid );
int status = proc_pidinfo( rpid, PROC_PIDTBSDINFO, 0, &procInfo, sizeof( procInfo ) );
if ( status <= 0 ) {
return -1;
}
creationTime = procInfo.pbi_start_tvsec;
#elif EE_PLATFORM == EE_PLATFORM_BSD
struct kinfo_proc proc;
int rpid = static_cast<int>( pid );
size_t procLen = sizeof( proc );
int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, rpid };
if ( sysctl( mib, 4, &proc, &procLen, NULL, 0 ) < 0 ) {
return -1;
}
creationTime = proc.ki_start.tv_sec;
#elif EE_PLATFORM == EE_PLATFORM_HAIKU
thread_info threadInfo;
int rpid = static_cast<int>( pid );
status_t result = get_thread_info( rpid, &threadInfo ); // Get thread info for the PID passed
if ( result == B_OK ) {
// Approximate creation time by subtracting CPU time (user_time + kernel_time) from current
// time
creationTime = time( NULL ) - ( threadInfo.user_time + threadInfo.kernel_time ) /
1000000; // Convert microseconds to seconds
} else {
return -1;
}
#endif
return creationTime;
}
std::vector<Uint64> Sys::pidof( const std::string& processName ) {
#if EE_PLATFORM == EE_PLATFORM_WIN
std::vector<Uint64> pids;
std::vector<std::string> extensions = getEnvSplitted( "PATHEXT" );
HMODULE hPsapi = LoadLibrary( TEXT( "psapi.dll" ) );
if ( !hPsapi )
return pids;
EnumProcesses_t EnumProcesses = (EnumProcesses_t)GetProcAddress( hPsapi, "EnumProcesses" );
EnumProcessModules_t EnumProcessModules =
(EnumProcessModules_t)GetProcAddress( hPsapi, "EnumProcessModules" );
GetModuleBaseName_t GetModuleBaseName =
(GetModuleBaseName_t)GetProcAddress( hPsapi, "GetModuleBaseNameA" );
if ( !EnumProcesses || !EnumProcessModules || !GetModuleBaseName ) {
FreeLibrary( hPsapi );
eePRINTL( "EnumProcesses or EnumProcessModules or GetModuleBaseName failed" );
return pids;
}
DWORD processIds[1024], cbNeeded;
if ( !EnumProcesses( processIds, sizeof( processIds ), &cbNeeded ) ) {
FreeLibrary( hPsapi );
eePRINTL( "EnumProcesses failed" );
return pids;
}
DWORD numProcesses = cbNeeded / sizeof( DWORD );
for ( DWORD i = 0; i < numProcesses; ++i ) {
if ( processIds[i] == 0 )
continue;
HANDLE hProcess =
OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processIds[i] );
if ( hProcess ) {
HMODULE hMod;
DWORD cbNeededMod;
if ( EnumProcessModules( hProcess, &hMod, sizeof( hMod ), &cbNeededMod ) ) {
char szProcessName[MAX_PATH];
if ( GetModuleBaseName( hProcess, hMod, szProcessName,
sizeof( szProcessName ) / sizeof( char ) ) ) {
std::string actualName( szProcessName, std::strlen( szProcessName ) );
// Check if the process name matches the input name with or without extensions
if ( actualName == processName ) {
pids.push_back( processIds[i] );
} else {
for ( const auto& ext : extensions ) {
std::string extName = processName + ext;
if ( actualName == extName ) {
pids.push_back( processIds[i] );
break;
}
}
}
}
}
CloseHandle( hProcess );
}
}
for ( auto pid : pids )
eePRINTL( "Found pid %d", pid );
FreeLibrary( hPsapi );
return pids;
#elif EE_PLATFORM == EE_PLATFORM_LINUX || EE_PLATFORM == EE_PLATFORM_ANDROID || \
EE_PLATFORM == EE_PLATFORM_MACOS
std::vector<Uint64> pids;
DIR* dir = opendir( "/proc" );
if ( !dir ) {
return pids;
}
struct dirent* entry;
while ( ( entry = readdir( dir ) ) != NULL ) {
if ( entry->d_type == DT_DIR && isdigit( entry->d_name[0] ) ) {
std::string pidDir = "/proc/" + std::string( entry->d_name );
std::string cmdPath = pidDir + "/comm";
FILE* cmdFile = fopen( cmdPath.c_str(), "r" );
if ( cmdFile ) {
char cmdline[256];
if ( fgets( cmdline, sizeof( cmdline ), cmdFile ) != NULL ) {
cmdline[strcspn( cmdline, "\n" )] = 0; // Remove newline
if ( processName == cmdline ) {
pids.push_back( atoi( entry->d_name ) );
}
}
fclose( cmdFile );
}
}
}
closedir( dir );
return pids;
#elif EE_PLATFORM == EE_PLATFORM_BSD
std::vector<Uint64> pids;
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0 };
size_t len;
if ( sysctl( mib, 4, NULL, &len, NULL, 0 ) == -1 ) {
return pids;
}
struct kinfo_proc* procs = (struct kinfo_proc*)malloc( len );
if ( !procs ) {
return pids;
}
if ( sysctl( mib, 4, procs, &len, NULL, 0 ) == -1 ) {
free( procs );
return pids;
}
int proc_count = len / sizeof( struct kinfo_proc );
for ( int i = 0; i < proc_count; i++ ) {
std::string name( procs[i].ki_comm );
if ( name == processName ) {
pids.push_back( procs[i].ki_pid );
}
}
free( procs );
return pids;
#elif EE_PLATFORM == EE_PLATFORM_HAIKU
std::vector<Uint64> pids;
team_info teamInfo;
int32 cookie = 0;
std::string lProcessName = String::toLower( processName );
while ( get_next_team_info( &cookie, &teamInfo ) == B_OK ) {
if ( lProcessName == String::toLower( FileSystem::fileNameFromPath( teamInfo.args ) ) ) {
pids.push_back( teamInfo.team );
}
}
return pids;
#else
#warning Platform not supported
return {};
#endif
}
}} // namespace EE::System

View File

@@ -117,6 +117,7 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath,
ui.showStatusBar = ini.getValueB( "ui", "show_status_bar", true );
ui.showMenuBar = ini.getValueB( "ui", "show_menu_bar", false );
ui.welcomeScreen = ini.getValueB( "ui", "welcome_screen", true );
ui.singleInstance = ini.getValueB( "ui", "single_instance", true );
ui.panelPosition = panelPositionFromString( ini.getValue( "ui", "panel_position", "left" ) );
ui.serifFont = ini.getValue( "ui", "serif_font", "fonts/NotoSans-Regular.ttf" );
ui.monospaceFont = ini.getValue( "ui", "monospace_font", "fonts/DejaVuSansMono.ttf" );
@@ -274,6 +275,7 @@ void AppConfig::save( const std::vector<std::string>& recentFiles,
ini.setValueB( "ui", "show_status_bar", ui.showStatusBar );
ini.setValueB( "ui", "show_menu_bar", ui.showMenuBar );
ini.setValueB( "ui", "welcome_screen", ui.welcomeScreen );
ini.setValueB( "ui", "single_instance", ui.singleInstance );
ini.setValue( "ui", "panel_position", panelPositionToString( ui.panelPosition ) );
ini.setValue( "ui", "serif_font", ui.serifFont );
ini.setValue( "ui", "monospace_font", ui.monospaceFont );

View File

@@ -33,6 +33,7 @@ struct UIConfig {
bool showStatusBar{ true };
bool showMenuBar{ false };
bool welcomeScreen{ true };
bool singleInstance{ true };
PanelPosition panelPosition{ PanelPosition::Left };
std::string serifFont;
std::string monospaceFont;

View File

@@ -529,6 +529,7 @@ bool App::loadConfig( const LogLevel& logLevel, const Sizeu& displaySize, bool s
mThemesPath = mConfigPath + "themes";
mScriptsPath = mConfigPath + "scripts";
mPlaygroundPath = mConfigPath + "playground";
mIpcPath = mConfigPath + "ipc";
mColorSchemesPath = mConfigPath + "editor" + FileSystem::getOSSlash() + "colorschemes" +
FileSystem::getOSSlash();
mTerminalManager = std::make_unique<TerminalManager>( this );
@@ -557,6 +558,16 @@ bool App::loadConfig( const LogLevel& logLevel, const Sizeu& displaySize, bool s
FileSystem::makeDir( mPlaygroundPath );
FileSystem::dirAddSlashAtEnd( mPlaygroundPath );
if ( !FileSystem::fileExists( mIpcPath ) )
FileSystem::makeDir( mIpcPath );
FileSystem::dirAddSlashAtEnd( mIpcPath );
Uint64 pid = Sys::getProcessID();
mPidPath = mIpcPath + String::toString( pid );
FileSystem::dirAddSlashAtEnd( mPidPath );
if ( !FileSystem::fileExists( mPidPath ) )
FileSystem::makeDir( mPidPath );
mLogsPath = mConfigPath + "ecode.log";
#ifndef EE_DEBUG
@@ -754,6 +765,14 @@ App::App( const size_t& jobs, const std::vector<std::string>& args ) :
mSettingsActions( std::make_unique<SettingsActions>( this ) ) {
}
static void fsRemoveAll( const std::string& fpath ) {
#if EE_PLATFORM == EE_PLATFORM_WIN
fs::remove_all( std::filesystem::path( String( fpath ).toWideString() ) );
#else
fs::remove_all( fpath );
#endif
}
App::~App() {
if ( mProjectBuildManager )
mProjectBuildManager.reset();
@@ -772,10 +791,26 @@ App::~App() {
eeSAFE_DELETE( mSplitter );
if ( mFileSystemListener ) {
if ( mIpcListenerId )
mFileSystemListener->removeListener( mIpcListenerId );
delete mFileSystemListener;
mFileSystemListener = nullptr;
}
mDirTree.reset();
fsRemoveAll( mPidPath );
}
void App::updateRecentButtons() {
updateOpenRecentFolderBtn();
if ( mSplitter ) {
mSplitter->forEachWidgetType(
static_cast<UINodeType>( CustomWidgets::UI_TYPE_WELCOME_TAB ), []( UIWidget* widget ) {
UIWelcomeScreen* welcomeTab = static_cast<UIWelcomeScreen*>( widget );
welcomeTab->refresh();
} );
}
}
void App::updateRecentFiles() {
@@ -807,6 +842,7 @@ void App::updateRecentFiles() {
} else if ( id == "clear-menu" ) {
mRecentFiles.clear();
updateRecentFiles();
updateRecentButtons();
} else {
const String& txt = event->getNode()->asType<UIMenuItem>()->getText();
std::string path( txt.toUtf8() );
@@ -847,6 +883,7 @@ void App::updateRecentFolders() {
if ( id == "clear-menu" ) {
mRecentFolders.clear();
updateRecentFolders();
updateRecentButtons();
} else if ( id == "restore-last-session-at-startup" ) {
mConfig.workspace.restoreLastSession =
event->getNode()->asType<UIMenuCheckBox>()->isActive();
@@ -1575,51 +1612,50 @@ std::map<KeyBindings::Shortcut, std::string> App::getDefaultKeybindings() {
std::map<KeyBindings::Shortcut, std::string> App::getLocalKeybindings() {
return {
{ { KEY_RETURN, KEYMOD_LALT | KEYMOD_LCTRL }, "fullscreen-toggle" },
{ { KEY_F3, KEYMOD_NONE }, "repeat-find" }, { { KEY_F3, KEYMOD_SHIFT }, "find-prev" },
{ { KEY_F12, KEYMOD_NONE }, "console-toggle" },
{ { KEY_F, KeyMod::getDefaultModifier() }, "find-replace" },
{ { KEY_Q, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "close-app" },
{ { KEY_O, KeyMod::getDefaultModifier() }, "open-file" },
{ { KEY_W, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "download-file-web" },
{ { KEY_O, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-folder" },
{ { KEY_F11, KEYMOD_NONE }, "debug-widget-tree-view" },
{ { KEY_K, KeyMod::getDefaultModifier() }, "open-locatebar" },
{ { KEY_P, KeyMod::getDefaultModifier() }, "open-command-palette" },
{ { KEY_F, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-global-search" },
{ { KEY_L, KeyMod::getDefaultModifier() }, "go-to-line" },
{ { KEY_F3, KEYMOD_NONE }, "repeat-find" },
{ { KEY_F3, KEYMOD_SHIFT }, "find-prev" },
{ { KEY_F12, KEYMOD_NONE }, "console-toggle" },
{ { KEY_F, KeyMod::getDefaultModifier() }, "find-replace" },
{ { KEY_Q, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "close-app" },
{ { KEY_O, KeyMod::getDefaultModifier() }, "open-file" },
{ { KEY_W, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "download-file-web" },
{ { KEY_O, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-folder" },
{ { KEY_F11, KEYMOD_NONE }, "debug-widget-tree-view" },
{ { KEY_K, KeyMod::getDefaultModifier() }, "open-locatebar" },
{ { KEY_P, KeyMod::getDefaultModifier() }, "open-command-palette" },
{ { KEY_F, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-global-search" },
{ { KEY_L, KeyMod::getDefaultModifier() }, "go-to-line" },
#if EE_PLATFORM == EE_PLATFORM_MACOS
{ { KEY_M, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "menu-toggle" },
{ { KEY_M, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "menu-toggle" },
#else
{ { KEY_M, KeyMod::getDefaultModifier() }, "menu-toggle" },
{ { KEY_M, KeyMod::getDefaultModifier() }, "menu-toggle" },
#endif
{ { KEY_S, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "save-all" },
{ { KEY_F9, KEYMOD_LALT }, "switch-side-panel" },
{ { KEY_J, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-left" },
{ { KEY_L, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-right" },
{ { KEY_I, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-top" },
{ { KEY_K, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-bottom" },
{ { KEY_S, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-swap" },
{ { KEY_T, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"reopen-closed-tab" },
{ { KEY_1, KEYMOD_LALT }, "toggle-status-locate-bar" },
{ { KEY_2, KEYMOD_LALT }, "toggle-status-global-search-bar" },
{ { KEY_3, KEYMOD_LALT }, "toggle-status-terminal" },
{ { KEY_4, KEYMOD_LALT }, "toggle-status-build-output" },
{ { KEY_5, KEYMOD_LALT }, "toggle-status-app-output" },
{ { KEY_B, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "project-build-start" },
{ { KEY_C, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "project-build-cancel" },
{ { KEY_F5, KEYMOD_NONE }, "project-build-and-run" },
{ { KEY_O, KEYMOD_LALT | KEYMOD_SHIFT }, "show-open-documents" },
{ { KEY_K, KeyMod::getDefaultModifier() | KEYMOD_SHIFT },
"open-workspace-symbol-search" },
{ { KEY_P, KeyMod::getDefaultModifier() | KEYMOD_SHIFT },
"open-document-symbol-search" },
{ { KEY_N, KEYMOD_SHIFT | KEYMOD_LALT }, "create-new-window" },
{ { KEY_S, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "save-all" },
{ { KEY_F9, KEYMOD_LALT }, "switch-side-panel" },
{ { KEY_J, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-left" },
{ { KEY_L, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-right" },
{ { KEY_I, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-top" },
{ { KEY_K, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-bottom" },
{ { KEY_S, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-swap" },
{ { KEY_T, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"reopen-closed-tab" },
{ { KEY_1, KEYMOD_LALT }, "toggle-status-locate-bar" },
{ { KEY_2, KEYMOD_LALT }, "toggle-status-global-search-bar" },
{ { KEY_3, KEYMOD_LALT }, "toggle-status-terminal" },
{ { KEY_4, KEYMOD_LALT }, "toggle-status-build-output" },
{ { KEY_5, KEYMOD_LALT }, "toggle-status-app-output" },
{ { KEY_B, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "project-build-start" },
{ { KEY_C, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "project-build-cancel" },
{ { KEY_F5, KEYMOD_NONE }, "project-build-and-run" },
{ { KEY_O, KEYMOD_LALT | KEYMOD_SHIFT }, "show-open-documents" },
{ { KEY_K, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-workspace-symbol-search" },
{ { KEY_P, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-document-symbol-search" },
{ { KEY_N, KEYMOD_SHIFT | KEYMOD_LALT }, "create-new-window" },
};
}
@@ -1628,15 +1664,15 @@ std::map<KeyBindings::Shortcut, std::string> App::getLocalKeybindings() {
std::map<std::string, std::string> App::getMigrateKeybindings() {
return {
{ "fullscreen-toggle", "alt+return" }, { "switch-to-tab-1", "alt+1" },
{ "switch-to-tab-2", "alt+2" }, { "switch-to-tab-3", "alt+3" },
{ "switch-to-tab-4", "alt+4" }, { "switch-to-tab-5", "alt+5" },
{ "switch-to-tab-6", "alt+6" }, { "switch-to-tab-7", "alt+7" },
{ "switch-to-tab-8", "alt+8" }, { "switch-to-tab-9", "alt+9" },
{ "switch-to-last-tab", "alt+0" },
{ "switch-to-tab-2", "alt+2" }, { "switch-to-tab-3", "alt+3" },
{ "switch-to-tab-4", "alt+4" }, { "switch-to-tab-5", "alt+5" },
{ "switch-to-tab-6", "alt+6" }, { "switch-to-tab-7", "alt+7" },
{ "switch-to-tab-8", "alt+8" }, { "switch-to-tab-9", "alt+9" },
{ "switch-to-last-tab", "alt+0" },
#if EE_PLATFORM == EE_PLATFORM_MACOS
{ "menu-toggle", "mod+shift+m" },
{ "menu-toggle", "mod+shift+m" },
#endif
{ "lock-toggle", "mod+shift+l" },
{ "lock-toggle", "mod+shift+l" },
};
}
@@ -2657,6 +2693,20 @@ void App::consoleToggle() {
mSplitter->getCurWidget()->setFocus();
}
std::function<void( UICodeEditor* codeEditor, const std::string& path )>
App::getForcePositionFn( TextPosition initialPosition ) {
std::function<void( UICodeEditor * codeEditor, const std::string& path )> forcePosition;
if ( initialPosition.isValid() ) {
forcePosition = [this, initialPosition]( UICodeEditor* editor, const auto& ) {
editor->runOnMainThread( [this, initialPosition, editor] {
editor->goToLine( initialPosition );
mSplitter->addEditorPositionToNavigationHistory( editor );
} );
};
}
return forcePosition;
}
void App::initProjectTreeView( std::string path, bool openClean ) {
mProjectViewEmptyCont = mUISceneNode->find<UILinearLayout>( "project_view_empty" );
mProjectViewEmptyCont->find<UIPushButton>( "open_folder" )
@@ -2771,16 +2821,7 @@ void App::initProjectTreeView( std::string path, bool openClean ) {
if ( mFileSystemListener )
mFileSystemListener->setFileSystemModel( mFileSystemModel );
std::function<void( UICodeEditor * codeEditor, const std::string& path )>
forcePosition;
if ( initialPosition.isValid() ) {
forcePosition = [this, initialPosition]( UICodeEditor* editor, const auto& ) {
editor->runOnMainThread( [this, initialPosition, editor] {
editor->goToLine( initialPosition );
mSplitter->addEditorPositionToNavigationHistory( editor );
} );
};
}
auto forcePosition = getForcePositionFn( initialPosition );
if ( FileSystem::fileExists( rpath ) ) {
loadFileFromPath( rpath, false, nullptr, forcePosition );
@@ -3040,6 +3081,56 @@ FontTrueType* App::loadFont( const std::string& name, std::string fontPath,
return nullptr;
}
bool App::needsRedirectToRunningProcess( std::string file ) {
if ( !mConfig.ui.singleInstance || file.empty() )
return false;
bool hasPosition = pathHasPosition( file );
TextPosition position;
if ( hasPosition ) {
auto pathAndPosition = getPathAndPosition( file );
file = pathAndPosition.first;
position = pathAndPosition.second;
}
std::string rpath( FileSystem::getRealPath( file ) );
FileInfo finfo( rpath );
if ( !finfo.exists() || finfo.isDirectory() )
return false;
std::string processName( FileSystem::fileNameFromPath( Sys::getProcessFilePath() ) );
auto pids = Sys::pidof( processName );
if ( pids.size() <= 1 )
return false;
Uint64 processPid = Sys::getProcessID();
Uint64 latestPid = processPid;
Uint64 lastCreationTime = 0;
for ( const auto pid : pids ) {
if ( pid != Sys::getProcessID() ) {
Uint64 creationTime = Sys::getProcessCreationTime( pid );
if ( creationTime >= lastCreationTime ) {
latestPid = pid;
lastCreationTime = creationTime;
}
}
}
if ( latestPid == processPid )
return false;
std::string pidPath = mIpcPath + String::toString( latestPid );
if ( !FileSystem::isDirectory( pidPath ) )
return false;
FileSystem::dirAddSlashAtEnd( pidPath );
FileSystem::fileWrite( pidPath + MD5::fromString( finfo.getFilepath() ).toHexString(),
finfo.getFilepath() +
( position.isValid() ? position.toPositionString() : "" ) );
return true;
}
void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDensity,
const std::string& colorScheme, bool terminal, bool frameBuffer, bool benchmarkMode,
const std::string& css, bool health, const std::string& healthLang,
@@ -3073,6 +3164,9 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe
return;
}
if ( needsRedirectToRunningProcess( file ) )
return;
currentDisplay = displayManager->getDisplayIndex( mConfig.windowState.displayIndex <
displayManager->getDisplayCount()
? mConfig.windowState.displayIndex
@@ -3437,8 +3531,37 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe
mFileWatcher = new efsw::FileWatcher();
mFileSystemListener = new FileSystemListener( mSplitter, mFileSystemModel, { mLogsPath } );
mFileWatcher->addWatch( mPluginsPath, mFileSystemListener );
mFileWatcher->addWatch( mPidPath, mFileSystemListener );
mFileWatcher->watch();
mPluginManager->setFileSystemListener( mFileSystemListener );
mIpcListenerId = mFileSystemListener->addListener( [this]( const FileEvent& fe,
const FileInfo& fi ) {
if ( !( ( fe.type == FileSystemEventType::Add ||
fe.type == FileSystemEventType::Modified ) &&
fe.directory == mPidPath ) )
return;
std::string path;
FileSystem::fileGet( fi.getFilepath(), path );
String::trimInPlace( path, ' ' );
String::trimInPlace( path, '\n' );
bool hasPosition = pathHasPosition( path );
TextPosition initialPosition;
if ( hasPosition ) {
auto pathAndPosition = getPathAndPosition( path );
path = pathAndPosition.first;
initialPosition = pathAndPosition.second;
}
if ( FileSystem::fileExists( path ) ) {
mUISceneNode->runOnMainThread( [path, initialPosition, this] {
loadFileFromPath( path, true, nullptr, getForcePositionFn( initialPosition ) );
} );
if ( !mWindow->hasFocus() )
mWindow->raise();
}
FileSystem::fileRemove( fi.getFilepath() );
} );
#endif
mNotificationCenter = std::make_unique<NotificationCenter>(

View File

@@ -24,6 +24,10 @@
using namespace eterm::UI;
enum class CustomWidgets {
UI_TYPE_WELCOME_TAB = UI_TYPE_USER + 1,
};
namespace ecode {
class AutoCompletePlugin;
@@ -332,6 +336,8 @@ class App : public UICodeEditorSplitter::Client {
void updateRecentFolders();
void updateRecentButtons();
const CodeEditorConfig& getCodeEditorConfig() const;
AppConfig& getConfig();
@@ -495,6 +501,8 @@ class App : public UICodeEditorSplitter::Client {
std::string mi18nPath;
std::string mScriptsPath;
std::string mPlaygroundPath;
std::string mIpcPath;
std::string mPidPath;
Float mDisplayDPI{ 96 };
std::shared_ptr<ThreadPool> mThreadPool;
std::shared_ptr<ProjectDirectoryTree> mDirTree;
@@ -546,6 +554,7 @@ class App : public UICodeEditorSplitter::Client {
UIMenuBar* mMenuBar{ nullptr };
std::unique_ptr<SettingsActions> mSettingsActions;
std::vector<std::string> mPathsToLoad;
Uint64 mIpcListenerId{ 0 };
void saveAllProcess();
@@ -647,6 +656,11 @@ class App : public UICodeEditorSplitter::Client {
void insertRecentFileAndUpdateUI( const std::string& path );
void createWelcomeTab();
bool needsRedirectToRunningProcess( std::string file );
std::function<void( UICodeEditor*, const std::string& )>
getForcePositionFn( TextPosition initialPosition );
};
} // namespace ecode

View File

@@ -154,7 +154,7 @@ class AutoCompletePlugin : public Plugin {
Text mSuggestionDoc;
size_t mMaxLabelCharacters{ 100 };
String::HashType mConfigHash{ 0 };
UnorderedMap<std::string, std::string> mKeyBindings;
std::unordered_map<std::string, std::string> mKeyBindings;
std::unordered_map<std::string, KeyBindings::Shortcut> mShortcuts;
Float mRowHeight{ 0 };

View File

@@ -251,25 +251,27 @@ LSPClientPlugin::~LSPClientPlugin() {
mShuttingDown = true;
mManager->unsubscribeMessages( this );
unsubscribeFileSystemListener();
Lock l( mDocMutex );
for ( const auto& editor : mEditors ) {
for ( auto& kb : mKeyBindings ) {
editor.first->getKeyBindings().removeCommandKeybind( kb.first );
if ( editor.first->hasDocument() )
editor.first->getDocument().removeCommand( kb.first );
{
Lock l( mDocMutex );
for ( const auto& editor : mEditors ) {
for ( auto& kb : mKeyBindings ) {
editor.first->getKeyBindings().removeCommandKeybind( kb.first );
if ( editor.first->hasDocument() )
editor.first->getDocument().removeCommand( kb.first );
}
for ( auto listener : editor.second )
editor.first->removeEventListener( listener );
if ( mBreadcrumb )
editor.first->unregisterTopSpace( this );
editor.first->unregisterPlugin( this );
}
for ( auto listener : editor.second )
editor.first->removeEventListener( listener );
if ( mBreadcrumb )
editor.first->unregisterTopSpace( this );
editor.first->unregisterPlugin( this );
}
if ( nullptr == mManager->getSplitter() )
return;
for ( const auto& editor : mEditorsTags ) {
if ( mManager->getSplitter()->editorExists( editor.first ) ) {
for ( const auto& tag : editor.second )
editor.first->removeActionsByTag( tag );
if ( nullptr == mManager->getSplitter() )
return;
for ( const auto& editor : mEditorsTags ) {
if ( mManager->getSplitter()->editorExists( editor.first ) ) {
for ( const auto& tag : editor.second )
editor.first->removeActionsByTag( tag );
}
}
}
}

View File

@@ -126,7 +126,7 @@ class LSPClientPlugin : public Plugin {
bool mBreadcrumb{ true };
bool mHoveringBreadcrumb{ false };
StyleSheetLength mBreadcrumbHeight{ "20dp" };
UnorderedMap<std::string, std::string> mKeyBindings; /* cmd, shortcut */
std::unordered_map<std::string, std::string> mKeyBindings; /* cmd, shortcut */
UnorderedMap<TextDocument*, std::shared_ptr<TextDocument>> mDelayedDocs;
Uint32 mHoverWaitCb{ 0 };
LSPHover mCurrentHover;

View File

@@ -71,6 +71,11 @@ void Plugin::showMessage( LSPMessageType type, const std::string& message,
&msgReq );
}
Plugin::~Plugin() {
while ( mLoading )
Sys::sleep( Milliseconds( 1 ) );
}
void Plugin::onFileSystemEvent( const FileEvent& ev, const FileInfo& file ) {
if ( ev.type != FileSystemEventType::Modified || mShuttingDown || isLoading() )
return;

View File

@@ -17,6 +17,8 @@ class Plugin : public UICodeEditorPlugin {
public:
explicit Plugin( PluginManager* manager );
virtual ~Plugin();
void subscribeFileSystemListener();
void unsubscribeFileSystemListener();
@@ -89,23 +91,23 @@ class PluginBase : public Plugin {
//! If the configuration is stored in a file, keep track of the config hash
String::HashType mConfigHash{ 0 };
virtual void onDocumentLoaded( TextDocument* ){};
virtual void onDocumentLoaded( TextDocument* ) {};
virtual void onDocumentClosed( TextDocument* ){};
virtual void onDocumentClosed( TextDocument* ) {};
virtual void onDocumentChanged( UICodeEditor*, TextDocument* /*oldDoc*/ ){};
virtual void onDocumentChanged( UICodeEditor*, TextDocument* /*oldDoc*/ ) {};
virtual void onRegisterListeners( UICodeEditor*, std::vector<Uint32>& /*listeners*/ ){};
virtual void onRegisterListeners( UICodeEditor*, std::vector<Uint32>& /*listeners*/ ) {};
//! Usually used to remove keybindings in an editor
virtual void onBeforeUnregister( UICodeEditor* ){};
virtual void onBeforeUnregister( UICodeEditor* ) {};
virtual void onRegisterDocument( TextDocument* ){};
virtual void onRegisterDocument( TextDocument* ) {};
virtual void onUnregisterEditor( UICodeEditor* ){};
virtual void onUnregisterEditor( UICodeEditor* ) {};
//! Usually used to unregister commands in a document
virtual void onUnregisterDocument( TextDocument* ){};
virtual void onUnregisterDocument( TextDocument* ) {};
};
} // namespace ecode

View File

@@ -1169,6 +1169,14 @@ UIMenu* SettingsMenu::createWindowMenu() {
->setId( "zoom-reset" );
mWindowMenu->addSeparator();
mWindowMenu
->addCheckBox( i18n( "single_instance_enable", "Enable Single Instance" ),
mApp->getConfig().ui.singleInstance )
->setTooltipText(
i18n( "single_instance_desc",
"Newly opened files will be opened in the latest opened ecode instance." ) )
->setId( "single-instance-enable" );
mWindowMenu
->addCheckBox( i18n( "welcome_screen_enable", "Enable Welcome Screen" ),
mApp->getConfig().ui.welcomeScreen )
@@ -1187,6 +1195,10 @@ UIMenu* SettingsMenu::createWindowMenu() {
} else if ( "welcome-screen-enable" == item->getId() ) {
bool active = item->asType<UIMenuCheckBox>()->isActive();
mApp->getConfig().ui.welcomeScreen = active;
} else if ( "single-instance-enable" == item->getId() ) {
bool active = item->asType<UIMenuCheckBox>()->isActive();
mApp->getConfig().ui.singleInstance = active;
mApp->saveConfig();
} else {
String text = String( event->getNode()->asType<UIMenuItem>()->getId() ).toLower();
String::replaceAll( text, " ", "-" );

View File

@@ -177,11 +177,20 @@ UIWelcomeScreen* UIWelcomeScreen::New( App* app ) {
return eeNew( UIWelcomeScreen, ( app ) );
}
Uint32 UIWelcomeScreen::getType() const {
return static_cast<Uint32>( CustomWidgets::UI_TYPE_WELCOME_TAB );
}
bool UIWelcomeScreen::isType( const Uint32& type ) const {
return UIWelcomeScreen::getType() == type ? true : UIRelativeLayout::isType( type );
}
UIWelcomeScreen::UIWelcomeScreen( App* app ) :
UIRelativeLayout(),
WidgetCommandExecuter( getUISceneNode()->getWindow()->getInput() ),
mApp( app ) {
setId( "welcome_ecode" );
addClass( "welcome_tab" );
setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent );
app->registerUnlockedCommands( *this );
getUISceneNode()->loadLayoutFromString( LAYOUT, this, String::hash( "UIWelcomeScreen" ) );
@@ -192,8 +201,7 @@ UIWelcomeScreen::UIWelcomeScreen( App* app ) :
return;
node->setTooltipText( getKeyBindings().getCommandKeybindString( id ) );
node->onClick(
[this]( const MouseEvent* event ) { mApp->runCommand( event->getNode()->getId() ); },
EE_BUTTON_LEFT );
[this]( const MouseEvent* event ) { mApp->runCommand( event->getNode()->getId() ); } );
};
auto bindBtns = [bindBtn]( const std::initializer_list<std::string> ids ) {
@@ -205,26 +213,14 @@ UIWelcomeScreen::UIWelcomeScreen( App* app ) :
"check-for-updates", "plugin-manager-open", "keybindings" } );
auto recentFolders = find( "recent-folders" );
if ( !mApp->getRecentFolders().empty() ) {
recentFolders->onClick(
[this]( const MouseEvent* event ) {
mApp->createAndShowRecentFolderPopUpMenu( event->getNode() );
},
EE_BUTTON_LEFT );
} else {
recentFolders->setEnabled( false );
}
recentFolders->onClick( [this]( const MouseEvent* event ) {
mApp->createAndShowRecentFolderPopUpMenu( event->getNode() );
} );
auto recentFiles = find( "recent-files" );
if ( !mApp->getRecentFiles().empty() ) {
recentFiles->onClick(
[this]( const MouseEvent* event ) {
mApp->createAndShowRecentFilesPopUpMenu( event->getNode() );
},
EE_BUTTON_LEFT );
} else {
recentFiles->setEnabled( false );
}
recentFiles->onClick( [this]( const MouseEvent* event ) {
mApp->createAndShowRecentFilesPopUpMenu( event->getNode() );
} );
find<UITextView>( "main_menu_shortcut" )->setText( mApp->getKeybind( "menu-toggle" ) );
@@ -244,6 +240,15 @@ UIWelcomeScreen::UIWelcomeScreen( App* app ) :
welcomeDisabledChk->on( Event::OnValueChange, [welcomeDisabledChk, this]( auto ) {
mApp->getConfig().ui.welcomeScreen = !welcomeDisabledChk->isChecked();
} );
refresh();
}
void UIWelcomeScreen::refresh() {
auto recentFolders = find( "recent-folders" );
auto recentFiles = find( "recent-files" );
recentFolders->setEnabled( !mApp->getRecentFolders().empty() );
recentFiles->setEnabled( !mApp->getRecentFiles().empty() );
}
} // namespace ecode

View File

@@ -24,6 +24,12 @@ class UIWelcomeScreen : public UIRelativeLayout, public WidgetCommandExecuter {
return WidgetCommandExecuter::onKeyDown( event );
}
void refresh();
Uint32 getType() const;
bool isType( const Uint32& type ) const;
protected:
App* mApp{ nullptr };
};