Improve Git History navigation and asynchronous UI safety

Add a History ref selector for HEAD, local branches, remote branches, and tags without requiring checkout. Reuse cached branch-model display strings to keep large ref lists efficient.
Make deferred main-thread callbacks lifetime-safe across ecode controllers and plugins. Initialize plugin dispatchers during UIReady and extend MainThreadLifetime to support delayed and tagged dispatch.
Fix expandable Git History rows, empty merge projections, and commit-description expansion. Display only the physical first line while collapsed and avoid duplicating it in the expanded body.
Add coverage for history ref handling and preserve stale-request guards during repository and revision changes.
Fix UIImageViewer dimension reporting for sprites by displaying the underlying texture dimensions.
The Git History interface remains a work in progress.
This commit is contained in:
Martín Lucas Golini
2026-08-28 18:52:04 -03:00
parent baaa0a62c0
commit ef962fc61f
42 changed files with 601 additions and 247 deletions
+14 -2
View File
@@ -449,8 +449,20 @@ void UIImageViewer::updateTextDisplay() {
if ( ( mDisplayOptions & DisplayOptions::DisplayDimensions ) && mImage->getDrawable() ) {
str += i18n( "dimensions_colon", "Dimensions:" );
str += " ";
str += String::format( "%d x %d", (int)mImage->getDrawable()->getPixelsSize().x,
(int)mImage->getDrawable()->getPixelsSize().y );
if ( mImage->getDrawable()->getDrawableType() == Drawable::Type::SPRITE ) {
auto ref = mImage->getDrawable();
Sprite* spr = static_cast<Sprite*>( ref.get() );
if ( spr->getCurrentTextureRegion() ) {
auto tex = spr->getCurrentTextureRegion()->getTexture();
if ( tex ) {
str += String::format( "%d x %d", (int)tex->getImageWidth(),
(int)tex->getImageHeight() );
}
}
} else {
str += String::format( "%d x %d", (int)mImage->getDrawable()->getPixelsSize().x,
(int)mImage->getDrawable()->getPixelsSize().y );
}
str += "\n";
}
+1
View File
@@ -415,6 +415,7 @@ UITooltip* UIWidget::createTooltip() {
mTooltip->setTooltipOf( this );
if ( !mTooltipText.empty() )
mTooltip->setText( mTooltipText );
sendCommonEvent( Event::OnTooltipCreated );
return mTooltip;
}
+11 -1
View File
@@ -135,6 +135,14 @@ UTEST( GitHistory, PaginatesFirstParentWithoutDuplicates ) {
ASSERT_TRUE( third.success() );
ASSERT_EQ( 1u, third.commits.size() );
EXPECT_FALSE( third.hasMore );
ASSERT_EQ( EXIT_SUCCESS, run( { "branch", "older", "HEAD~2" } ) );
Git::HistoryQuery refQuery;
refQuery.revision = "refs/heads/older";
auto selectedRef = git.history( refQuery, temp.path.string() );
ASSERT_TRUE( selectedRef.success() );
ASSERT_EQ( 3u, selectedRef.commits.size() );
EXPECT_STREQ( "commit 2", selectedRef.commits.front().subject.c_str() );
}
UTEST( GitHistory, PropagatedExclusionsHideAlreadyRepresentedMainline ) {
@@ -205,12 +213,14 @@ UTEST( GitHistory, HandlesEmptyRepositoryUnicodeAndHardLimit ) {
ASSERT_EQ( EXIT_SUCCESS, run( { "config", "user.name", "Tést 🚀" } ) );
ASSERT_EQ( EXIT_SUCCESS, run( { "config", "user.email", "unicode@example.invalid" } ) );
const std::string subject = "unicode 🚀 | quote \" and tab\tend";
ASSERT_EQ( EXIT_SUCCESS, run( { "commit", "--allow-empty", "-m", subject } ) );
const std::string message = subject + "\ncontinuation on a physical second line";
ASSERT_EQ( EXIT_SUCCESS, run( { "commit", "--allow-empty", "-m", message } ) );
query.limit = 200;
auto page = git.history( query, temp.path.string() );
ASSERT_TRUE( page.success() );
ASSERT_EQ( 1u, page.commits.size() );
EXPECT_STREQ( subject.c_str(), page.commits.front().subject.c_str() );
EXPECT_STREQ( message.c_str(), page.commits.front().message.c_str() );
EXPECT_STREQ( "Tést 🚀", page.commits.front().authorName.c_str() );
}
+22 -16
View File
@@ -340,7 +340,8 @@ void App::setAppTitle( const std::string& title ) {
if ( Engine::isMainThread() ) {
mWindow->setTitle( fullTitle );
} else {
mUISceneNode->runOnMainThread( [this, fullTitle] { mWindow->setTitle( fullTitle ); } );
mLifetime.weakHandle().run(
[fullTitle]( App* app ) { app->mWindow->setTitle( fullTitle ); } );
}
}
}
@@ -729,8 +730,9 @@ void App::initPluginManager() {
} else {
// If plugin loads asynchronously and is not ready, delay the plugin enabled callback
plugin->addOnReadyCallback( [this]( UICodeEditorPlugin* plugin, const Uint32& cbId ) {
mUISceneNode->runOnMainThread(
[this, plugin]() { onPluginEnabled( static_cast<Plugin*>( plugin ) ); } );
mLifetime.weakHandle().run( [plugin]( App* app ) {
app->onPluginEnabled( static_cast<Plugin*>( plugin ) );
} );
plugin->removeReadyCallback( cbId );
} );
}
@@ -1041,6 +1043,7 @@ void App::onTextDropped( String text ) {
App::App( const size_t& jobs, const std::vector<std::string>& args ) :
mArgs( args ),
mLifetime( this, nullptr ),
mThreadPool(
ThreadPool::createShared( jobs > 0 ? jobs : eemax<int>( 4, Sys::getCPUCount() ) ) ),
mDateTimeController( std::make_unique<DateTimeController>( this ) ),
@@ -1051,6 +1054,7 @@ App::App( const size_t& jobs, const std::vector<std::string>& args ) :
}
App::~App() {
mLifetime.invalidate();
appInstance = nullptr;
mDestroyingApp = true;
@@ -3588,10 +3592,10 @@ void App::loadDirTree( const std::string& path ) {
Log::info( "DirTree read in: %s. Found %ld files.", clock.getElapsedTime().toString(),
dirTree.getFilesCount() );
mDirTreeReady = true;
mUISceneNode->runOnMainThread( [this] {
mUniversalLocator->updateFilesTable();
if ( mSplitter->curEditorExistsAndFocused() )
syncProjectTreeWithEditor( mSplitter->getCurEditor() );
mLifetime.weakHandle().run( []( App* app ) {
app->mUniversalLocator->updateFilesTable();
if ( app->mSplitter->curEditorExistsAndFocused() )
app->syncProjectTreeWithEditor( app->mSplitter->getCurEditor() );
} );
removeFolderWatches();
if ( mFileWatcher ) {
@@ -3772,11 +3776,11 @@ void App::newFile( const FileInfo& file ) {
errorMsgBox( i18n( "couldnt_create_file", "Couldn't create file." ) );
} else if ( mProjectTreeView ) {
// We wait 100 ms to get the notification from the file system
mUISceneNode->runOnMainThread(
[this, newFilePath] {
if ( !mFileSystemModel || !mProjectTreeView )
mLifetime.weakHandle().run(
[newFilePath]( App* app ) {
if ( !app->mFileSystemModel || !app->mProjectTreeView )
return;
loadFileFromPathOrFocus( newFilePath );
app->loadFileFromPathOrFocus( newFilePath );
},
Milliseconds( 100 ) );
}
@@ -3799,13 +3803,14 @@ void App::newFolder( const FileInfo& file ) {
errorMsgBox( i18n( "couldnt_create_directory", "Couldn't create directory." ) );
} else if ( mProjectTreeView ) {
// We wait 100 ms to get the notification from the file system
mUISceneNode->runOnMainThread(
[this, newFolderPath] {
if ( !mFileSystemModel || !mProjectTreeView )
mLifetime.weakHandle().run(
[newFolderPath]( App* app ) {
if ( !app->mFileSystemModel || !app->mProjectTreeView )
return;
std::string nfp( newFolderPath );
FileSystem::filePathRemoveBasePath( mFileSystemModel->getRootPath(), nfp );
mProjectTreeView->openRowWithPath( nfp );
FileSystem::filePathRemoveBasePath( app->mFileSystemModel->getRootPath(),
nfp );
app->mProjectTreeView->openRowWithPath( nfp );
},
Milliseconds( 100 ) );
}
@@ -4929,6 +4934,7 @@ void App::init( InitParameters& params ) {
eemax( mWindow->getScale(), mConfig.windowState.pixelDensity ) );
mUISceneNode = UISceneNode::New();
mLifetime.setDispatcher( mUISceneNode );
mUISceneNode->setThreadPool( mThreadPool );
mUIColorScheme = mConfig.ui.colorScheme;
+2
View File
@@ -19,6 +19,7 @@
#include "uistatusbar.hpp"
#include "universallocator.hpp"
#include <eepp/ee.hpp>
#include <eepp/scene/mainthreadlifetime.hpp>
#include <eepp/ui/uinodelink.hpp>
#include <efsw/efsw.hpp>
#include <eterm/ui/uiterminal.hpp>
@@ -672,6 +673,7 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
std::vector<std::string> mArgs;
EE::Window::Window* mWindow{ nullptr };
UISceneNode* mUISceneNode{ nullptr };
MainThreadLifetime<App> mLifetime;
UIConsole* mConsole{ nullptr };
std::string mCurWindowTitle;
std::string mWindowTitle{ "ecode" };
+20 -15
View File
@@ -7,7 +7,10 @@ static int LOCATEBAR_MAX_VISIBLE_ITEMS = 18;
GlobalSearchController::GlobalSearchController( UICodeEditorSplitter* editorSplitter,
UISceneNode* sceneNode, App* app ) :
mSplitter( editorSplitter ), mUISceneNode( sceneNode ), mApp( app ) {
mSplitter( editorSplitter ),
mUISceneNode( sceneNode ),
mLifetime( this, sceneNode ),
mApp( app ) {
mApp->getPluginManager()->subscribeMessages(
"GlobalSearchController", [this]( const PluginMessage& msg ) -> PluginRequestHandle {
return processMessage( msg );
@@ -831,20 +834,22 @@ void GlobalSearchController::doGlobalSearch( String text, String filter, bool ca
filter]( const ProjectSearch::ConsolidatedResult& res ) {
Log::info( "Global search for \"%s\" took %s", search.c_str(),
clock.getElapsedTime().toString() );
mUISceneNode->runOnMainThread( [this, res = std::move( res ), search, searchReplace,
searchAgain, escapeSequence, searchType, filter] {
mLastSearchConfig = std::move( res.first );
mLifetime.weakHandle().run( [res = std::move( res ), search, searchReplace, searchAgain,
escapeSequence, searchType,
filter]( GlobalSearchController* controller ) mutable {
controller->mLastSearchConfig = std::move( res.first );
auto model = ProjectSearch::asModel( res.second );
model->setOpType( searchType );
updateGlobalSearchHistory( model, search, filter, searchReplace, searchAgain,
escapeSequence );
updateGlobalSearchBarResults( search, model, searchReplace, escapeSequence );
auto* loader = mGlobalSearchLayout->getParent()->find( "loader" );
controller->updateGlobalSearchHistory( model, search, filter, searchReplace,
searchAgain, escapeSequence );
controller->updateGlobalSearchBarResults( search, model, searchReplace,
escapeSequence );
auto* loader = controller->mGlobalSearchLayout->getParent()->find( "loader" );
if ( loader ) {
loader->setVisible( false );
loader->close();
}
mCurSearch = nullptr;
controller->mCurSearch = nullptr;
} );
},
caseSensitive, wholeWord, searchType, filters, mApp->getCurrentProject(), openDocs );
@@ -951,12 +956,12 @@ PluginRequestHandle GlobalSearchController::processMessage( const PluginMessage&
sample.line.substr( sample.position.start().column(),
sample.position.end().column() - sample.position.start().column() );
mUISceneNode->runOnMainThread( [this, search, model] {
showGlobalSearch( false );
mGlobalSearchInput->setText( search );
mGlobalSearchWhereInput->setText( "" );
updateGlobalSearchHistory( model, search, "", false, false, false );
updateGlobalSearchBarResults( search, model, false, false );
mLifetime.weakHandle().run( [search, model]( GlobalSearchController* controller ) {
controller->showGlobalSearch( false );
controller->mGlobalSearchInput->setText( search );
controller->mGlobalSearchWhereInput->setText( "" );
controller->updateGlobalSearchHistory( model, search, "", false, false, false );
controller->updateGlobalSearchBarResults( search, model, false, false );
} );
return {};
@@ -5,6 +5,7 @@
#include "plugins/pluginmanager.hpp"
#include "projectsearch.hpp"
#include <eepp/ee.hpp>
#include <eepp/scene/mainthreadlifetime.hpp>
namespace ecode {
@@ -68,6 +69,7 @@ class GlobalSearchController {
protected:
UICodeEditorSplitter* mSplitter{ nullptr };
UISceneNode* mUISceneNode{ nullptr };
MainThreadLifetime<GlobalSearchController> mLifetime;
App* mApp{ nullptr };
UIGlobalSearchBar* mGlobalSearchBarLayout{ nullptr };
@@ -608,6 +608,7 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
UILinearLayout(),
WidgetCommandExecuter( getInput() ),
mManager( manager ),
mLifetime( this, getUISceneNode() ),
mDisplayReasoning( getPlugin() && getPlugin()->displayReasoning() ) {
setClass( "llm_chatui" );
setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent );
@@ -1171,6 +1172,7 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
}
LLMChatUI::~LLMChatUI() {
mLifetime.invalidate();
if ( mRequest ) {
mRequest->cancelCb = nullptr;
mRequest->doneCb = nullptr;
@@ -3343,11 +3345,12 @@ void LLMChatUI::showAttachFile() {
ctx->getDirTree()->asyncMatchTree(
ProjectDirectoryTree::MatchType::Fuzzy, text, 100,
[this, text]( auto res ) {
mUISceneNode->runOnMainThread( [this, res] {
mLocateTable->setModel( res );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
mLocateTable->scrollToTop();
updateLocateBarColumns();
mLifetime.weakHandle().run( [res]( LLMChatUI* chat ) {
chat->mLocateTable->setModel( res );
chat->mLocateTable->getSelection().set(
chat->mLocateTable->getModel()->index( 0 ) );
chat->mLocateTable->scrollToTop();
chat->updateLocateBarColumns();
} );
},
ctx->getCurrentProject() );
@@ -5,6 +5,7 @@
#include "llmchatcompletionrequest.hpp"
#include "protocol.hpp"
#include <eepp/scene/mainthreadlifetime.hpp>
#include <eepp/ui/uilinearlayout.hpp>
#include <eepp/ui/widgetcommandexecuter.hpp>
@@ -102,6 +103,7 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter {
std::string mSummary;
long mTimestamp{ 0 };
PluginManager* mManager{ nullptr };
MainThreadLifetime<LLMChatUI> mLifetime;
UISplitter* mChatSplitter{ nullptr };
UIWidget* mChatsList{ nullptr };
UICodeEditor* mChatInput{ nullptr };
@@ -189,7 +189,7 @@ void DebuggerClientListener::initUI() {
void DebuggerClientListener::stateChanged( DebuggerClient::State state, const SessionId& ) {
if ( state == DebuggerClient::State::Initializing ) {
mPlugin->getManager()->getUISceneNode()->runOnMainThread( [this] { initUI(); } );
mPlugin->mLifetime.weakHandle().run( [this]( DebuggerPlugin* ) { initUI(); } );
}
}
@@ -345,9 +345,9 @@ void DebuggerClientListener::outputProduced( const Output& output ) {
auto buffer = output.output;
auto sdc = getStatusDebuggerController();
if ( sdc == nullptr || sdc->getUIConsole() == nullptr ) {
mPlugin->getUISceneNode()->runOnMainThread(
[this, buffer = std::move( buffer )]() mutable {
mPlugin->initStatusDebuggerController();
mPlugin->mLifetime.weakHandle().run(
[this, buffer = std::move( buffer )]( DebuggerPlugin* plugin ) mutable {
plugin->initStatusDebuggerController();
auto sdc = getStatusDebuggerController();
sdc->insertConsoleBuffer( std::move( buffer ) );
} );
@@ -407,8 +407,9 @@ void DebuggerClientListener::changeScope( const StackFrame& f ) {
TextRange range{ { f.line - 1, f.column - 1 }, { f.line - 1, f.column - 1 } };
std::string path( f.source->path );
mPlugin->getUISceneNode()->runOnMainThread(
[this, path, range] { mPlugin->getPluginContext()->focusOrLoadFile( path, range ); } );
mPlugin->mLifetime.weakHandle().run( [path, range]( DebuggerPlugin* plugin ) {
plugin->getPluginContext()->focusOrLoadFile( path, range );
} );
auto sdc = getStatusDebuggerController();
if ( sdc && sdc->getUIStack() )
@@ -155,7 +155,7 @@ Plugin* DebuggerPlugin::NewSync( PluginManager* pluginManager ) {
}
DebuggerPlugin::DebuggerPlugin( PluginManager* pluginManager, bool sync ) :
PluginBase( pluginManager ) {
PluginBase( pluginManager ), mLifetime( this, getUISceneNode() ) {
if ( sync ) {
load( pluginManager );
} else {
@@ -164,6 +164,7 @@ DebuggerPlugin::DebuggerPlugin( PluginManager* pluginManager, bool sync ) :
}
DebuggerPlugin::~DebuggerPlugin() {
mLifetime.invalidate();
waitUntilLoaded();
mShuttingDown = true;
@@ -712,6 +713,7 @@ void DebuggerPlugin::loadProjectConfiguration( const std::string& path ) {
}
void DebuggerPlugin::loadProjectConfigurations() {
const auto lifetime = mLifetime.weakHandle();
if ( mProjectPath.empty() )
return;
@@ -720,7 +722,7 @@ void DebuggerPlugin::loadProjectConfigurations() {
mDapConfigs.clear();
}
mThreadPool->run( [this] {
mThreadPool->run( [this, lifetime] {
std::string config;
if ( mLoadVSCodeLaunchConfig ) {
config = mProjectPath + ".vscode/launch.json";
@@ -731,9 +733,9 @@ void DebuggerPlugin::loadProjectConfigurations() {
if ( FileSystem::fileExists( config ) )
loadProjectConfiguration( config );
getUISceneNode()->runOnMainThread( [this] {
updateDebuggerConfigurationList();
updateSelectedDebugConfig();
lifetime.run( []( DebuggerPlugin* plugin ) {
plugin->updateDebuggerConfigurationList();
plugin->updateSelectedDebugConfig();
} );
} );
}
@@ -744,11 +746,11 @@ PluginRequestHandle DebuggerPlugin::processMessage( const PluginMessage& msg ) {
mProjectPath = msg.asJSON()["folder"];
if ( getUISceneNode() && mSidePanel ) {
getUISceneNode()->runOnMainThread( [this] {
if ( mProjectPath.empty() ) {
hideSidePanel();
Lock l( mDapsMutex );
mDapConfigs.clear();
mLifetime.weakHandle().run( []( DebuggerPlugin* plugin ) {
if ( plugin->mProjectPath.empty() ) {
plugin->hideSidePanel();
Lock l( plugin->mDapsMutex );
plugin->mDapConfigs.clear();
}
} );
}
@@ -760,6 +762,7 @@ PluginRequestHandle DebuggerPlugin::processMessage( const PluginMessage& msg ) {
break;
}
case ecode::PluginMessageType::UIReady: {
mLifetime.setDispatcher( getUISceneNode() );
registerCommands( getPluginContext()->getMainLayout() );
for ( const auto& kb : mKeyBindings ) {
getPluginContext()->getMainLayout()->getKeyBindings().addKeybindString( kb.second,
@@ -784,9 +787,9 @@ void DebuggerPlugin::updateUI() {
if ( !getUISceneNode() )
return;
getUISceneNode()->runOnMainThread( [this] {
buildSidePanelTab();
buildStatusBar();
mLifetime.weakHandle().run( []( DebuggerPlugin* plugin ) {
plugin->buildSidePanelTab();
plugin->buildStatusBar();
} );
}
@@ -1495,15 +1498,16 @@ void DebuggerPlugin::registerCommands( TCommandRegister* executer ) {
if ( exitCode == 0 ) {
runCurrentConfig();
} else {
getPluginContext()->getUISceneNode()->runOnMainThread( [this] {
mLifetime.weakHandle().run( []( DebuggerPlugin* plugin ) {
auto msgBox = UIMessageBox::New(
UIMessageBox::YES_NO,
i18n( "build_failed_debug_anyways",
"Building the project failed, do you want to "
"debug the binary anyways?" ) );
msgBox->setTitle( i18n( "build_failed", "Build Failed" ) );
plugin->i18n( "build_failed_debug_anyways",
"Building the project failed, do you want to "
"debug the binary anyways?" ) );
msgBox->setTitle( plugin->i18n( "build_failed", "Build Failed" ) );
msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } );
msgBox->on( Event::OnConfirm, [this]( auto ) { runCurrentConfig(); } );
msgBox->on( Event::OnConfirm,
[plugin]( auto ) { plugin->runCurrentConfig(); } );
msgBox->showWhenReady();
} );
}
@@ -2348,11 +2352,12 @@ void DebuggerPlugin::run( const std::string& debugger, ProtocolSettings&& protoc
std::function<void( int )> doneFn ) {
if ( !FileSystem::fileExists( cmd ) )
cmd = FileSystem::fileNameFromPath( cmd );
getUISceneNode()->runOnMainThread( [this, isIntegrated, cmd = std::move( cmd ), cwd, args,
doneFn = std::move( doneFn ), env = std::move( env )] {
mLifetime.weakHandle().run( [isIntegrated, cmd = std::move( cmd ), cwd, args,
doneFn = std::move( doneFn ),
env = std::move( env )]( DebuggerPlugin* plugin ) {
if ( isIntegrated || !env.empty() ) {
UITerminal* term =
getPluginContext()->getTerminalManager()->createTerminalInSplitter(
plugin->getPluginContext()->getTerminalManager()->createTerminalInSplitter(
cwd, cmd, args, env, false, false );
doneFn( term && term->getTerm() && term->getTerm()->getTerminal() &&
@@ -2361,15 +2366,16 @@ void DebuggerPlugin::run( const std::string& debugger, ProtocolSettings&& protoc
: 0 );
} else {
std::string fcmd = cmd + ( !args.empty() ? " " : "" ) + String::join( args, ' ' );
doneFn(
getPluginContext()->getTerminalManager()->openInExternalTerminal( fcmd, cwd ) );
doneFn( plugin->getPluginContext()->getTerminalManager()->openInExternalTerminal(
fcmd, cwd ) );
}
} );
};
dap->runTargetCb = [this] {
getUISceneNode()->runOnMainThread(
[this] { getPluginContext()->runCommand( "project-run-executable" ); } );
mLifetime.weakHandle().run( []( DebuggerPlugin* plugin ) {
plugin->getPluginContext()->runCommand( "project-run-executable" );
} );
};
mDebugger->start();
@@ -6,6 +6,7 @@
#include "config.hpp"
#include "debuggerclientlistener.hpp"
#include "models/breakpointsmodel.hpp"
#include <eepp/scene/mainthreadlifetime.hpp>
using namespace EE::UI::Models;
using namespace EE::UI;
@@ -90,6 +91,7 @@ class DebuggerPlugin : public PluginBase {
protected:
friend class DebuggerClientListener;
MainThreadLifetime<DebuggerPlugin> mLifetime;
bool mInitialized{ false };
bool mFetchRegisters{ false };
@@ -141,11 +141,12 @@ StatusDebuggerController::getLocalDefaultKeybindings() {
StatusDebuggerController::StatusDebuggerController( UISplitter* mainSplitter,
UISceneNode* uiSceneNode,
PluginContextProvider* pluginContext ) :
StatusBarElement( mainSplitter, uiSceneNode, pluginContext ) {
StatusBarElement( mainSplitter, uiSceneNode, pluginContext ), mLifetime( this, uiSceneNode ) {
mSerializedLayout = mContext->getConfig().iniState.getValue( "debugger", "panel_layout" );
}
StatusDebuggerController::~StatusDebuggerController() {
mLifetime.invalidate();
mEventConnections.clear();
mTabWidgetEventConnections.clear();
if ( mContainer )
@@ -232,20 +233,20 @@ void StatusDebuggerController::setDebuggingState( State state ) {
if ( !mContainer )
return;
mUISceneNode->runOnMainThread( [this, state] {
mUIButStart->setVisible( state == State::NotStarted )
mLifetime.weakHandle().run( [state]( StatusDebuggerController* controller ) {
controller->mUIButStart->setVisible( state == State::NotStarted )
->setEnabled( state == State::NotStarted );
mUIButStop->setVisible( state != State::NotStarted )
controller->mUIButStop->setVisible( state != State::NotStarted )
->setEnabled( state != State::NotStarted );
mUIButContinue->setVisible( state != State::NotStarted )
controller->mUIButContinue->setVisible( state != State::NotStarted )
->setEnabled( state == State::Paused );
mUIButPause->setVisible( state != State::NotStarted )
controller->mUIButPause->setVisible( state != State::NotStarted )
->setEnabled( state == State::Running );
mUIButStepOver->setVisible( state != State::NotStarted )
controller->mUIButStepOver->setVisible( state != State::NotStarted )
->setEnabled( state == State::Paused );
mUIButStepInto->setVisible( state != State::NotStarted )
controller->mUIButStepInto->setVisible( state != State::NotStarted )
->setEnabled( state == State::Paused );
mUIButStepOut->setVisible( state != State::NotStarted )
controller->mUIButStepOut->setVisible( state != State::NotStarted )
->setEnabled( state == State::Paused );
} );
}
@@ -5,6 +5,7 @@
#include "models/breakpointsmodel.hpp"
#include <eepp/core/containers.hpp>
#include <eepp/scene/eventconnection.hpp>
#include <eepp/scene/mainthreadlifetime.hpp>
#include <eepp/system/luapattern.hpp>
#include <eepp/ui/tools/uicodeeditorsplitter.hpp>
#include <eepp/ui/tools/uitabwidgetsplitter.hpp>
@@ -113,6 +114,7 @@ class StatusDebuggerController : public StatusBarElement, public UITabWidgetSpli
bool mRestoringLayout{ false };
bool mRightPanelDropPreview{ false };
std::string mSerializedLayout;
MainThreadLifetime<StatusDebuggerController> mLifetime;
void createContainer();
+7 -2
View File
@@ -37,7 +37,7 @@ Git::HistoryPage Git::history( const HistoryQuery& query, const std::string& pro
"--first-parent",
String::format( "--max-count=%zu", query.limit + 1 ),
"-z",
"--format=%H%x00%h%x00%P%x00%an%x00%ae%x00%at%x00%ct%x00%s",
"--format=%H%x00%h%x00%P%x00%an%x00%ae%x00%at%x00%ct%x00%B",
query.continuation.empty() ? query.revision
: query.continuation };
if ( !query.exclusions.empty() ) {
@@ -86,7 +86,12 @@ Git::HistoryPage Git::history( const HistoryQuery& query, const std::string& pro
}
commit.authorName = fields[3];
commit.authorEmail = fields[4];
commit.subject = fields[7];
commit.message = fields[7];
while ( !commit.message.empty() &&
( commit.message.back() == '\n' || commit.message.back() == '\r' ) )
commit.message.pop_back();
const size_t subjectEnd = commit.message.find_first_of( "\r\n" );
commit.subject = commit.message.substr( 0, subjectEnd );
if ( !parseHistoryTimestamp( fields[5], commit.authorTime ) ||
!parseHistoryTimestamp( fields[6], commit.commitTime ) ) {
page.returnCode = EXIT_FAILURE;
+1
View File
@@ -206,6 +206,7 @@ class Git {
std::string shortHash;
std::vector<std::string> parents;
std::string subject;
std::string message;
std::string authorName;
std::string authorEmail;
int64_t authorTime{ 0 };
+27 -2
View File
@@ -37,8 +37,27 @@ GitBranchModel::GitBranchModel( std::vector<Git::Branch>&& branches, size_t hash
type.emplace_back( std::move( branch ) );
}
for ( auto& branch : branchTypes ) {
mBranches.emplace_back(
BranchData{ std::move( branch.first ), std::move( branch.second ) } );
BranchData data{ std::move( branch.first ), std::move( branch.second ), {} };
data.historyDisplay.reserve( data.data.size() );
for ( const auto& ref : data.data ) {
String kind;
switch ( ref.type ) {
case Git::RefType::Head:
kind = mPlugin->i18n( "git_local_branch", "Local" );
break;
case Git::RefType::Remote:
kind = mPlugin->i18n( "git_remote_branch", "Remote" );
break;
case Git::RefType::Tag:
kind = mPlugin->i18n( "git_tag", "Tag" );
break;
default:
break;
}
data.historyDisplay.emplace_back( kind + String{ " · " } +
String::fromUtf8( ref.name ) );
}
mBranches.emplace_back( std::move( data ) );
}
}
@@ -119,6 +138,8 @@ Variant GitBranchModel::data( const ModelIndex& index, ModelRole role ) const {
return Variant( branch.typeStr() );
case Column::LastCommit:
return Variant( branch.lastCommit.c_str() );
case Column::HistoryDisplay:
return Variant( &mBranches[index.internalId()].historyDisplay[index.row()] );
}
return Variant( GIT_EMPTY );
}
@@ -166,6 +187,10 @@ Variant GitBranchModel::data( const ModelIndex& index, ModelRole role ) const {
}
Git::Branch GitBranchModel::branch( const ModelIndex& index ) const {
return branchRef( index );
}
const Git::Branch& GitBranchModel::branchRef( const ModelIndex& index ) const {
return *static_cast<Git::Branch*>( index.internalData() );
}
@@ -25,11 +25,12 @@ class GitBranchModel : public Model {
static size_t hashBranches( const std::vector<Git::Branch>& branches );
enum Column { Name, Remote, Type, LastCommit };
enum Column { Name, Remote, Type, LastCommit, HistoryDisplay };
struct BranchData {
std::string branch;
std::vector<Git::Branch> data;
std::vector<String> historyDisplay;
};
std::string refTypeToString( Git::RefType type ) const;
@@ -40,7 +41,7 @@ class GitBranchModel : public Model {
size_t rowCount( const ModelIndex& index ) const;
size_t columnCount( const ModelIndex& ) const { return 4; }
size_t columnCount( const ModelIndex& ) const { return 5; }
ModelIndex parentIndex( const ModelIndex& index ) const;
@@ -58,6 +59,8 @@ class GitBranchModel : public Model {
Git::Branch branch( const ModelIndex& index ) const;
const Git::Branch& branchRef( const ModelIndex& index ) const;
std::optional<Git::Branch> branch( const std::string& name ) const;
ModelIndex refTypeIndex( Git::RefType refType ) const;
@@ -111,7 +111,7 @@ GitHistoryModel::commitNode( Git::Commit commit, Node* parent,
item->date = Sys::epochToString( item->commit.commitTime );
item->hash = String::fromUtf8( item->commit.shortHash );
item->tooltip = String::format( "%s\n%s <%s>\n%s", item->commit.hash, item->commit.authorName,
item->commit.authorEmail, item->commit.subject );
item->commit.authorEmail, item->commit.message );
item->message = item->date + String{ " · " } + item->author + String{ " · " } + item->hash;
if ( item->commit.parents.size() == 2 ) {
item->query = query;
@@ -207,6 +207,14 @@ void GitHistoryModel::setChildrenPage( Node* item, Git::HistoryPage page,
return;
Nodes children;
fillPage( children, item, std::move( page ), query );
if ( children.empty() ) {
auto child = std::make_unique<Node>();
child->type = NodeType::Empty;
child->parent = item;
child->message =
mPlugin->i18n( "git_history_no_additional_commits", "No additional commits" );
children.emplace_back( std::move( child ) );
}
item->childrenLoading = false;
item->childrenLoaded = true;
replaceChildren( item, std::move( children ) );
@@ -1,6 +1,7 @@
#include "githistorytreeview.hpp"
#include "githistorymodel.hpp"
#include <eepp/ui/uistyle.hpp>
#include <eepp/ui/uitooltip.hpp>
namespace ecode {
@@ -9,6 +10,12 @@ GitHistoryTreeViewCell::GitHistoryTreeViewCell() :
mHintColor( Color::fromString(
mUISceneNode->getRoot()->getUIStyle()->getVariable( "--font-hint" ).getValue() ) ) {
mTextBox->setTextOverflow( "ellipsis" );
on( Event::OnTooltipCreated, []( const Event* event ) {
auto* tooltip = event->getNode()->asType<UIWidget>()->getTooltip();
tooltip->setMaxWidthEq( "60%" );
tooltip->setWordWrap( true );
tooltip->setHorizontalAlign( UI_HALIGN_LEFT );
} );
}
Sizef GitHistoryTreeViewCell::updateLayout() {
@@ -58,4 +65,13 @@ UIWidget* GitHistoryTreeView::createCell( UIWidget* rowWidget, const ModelIndex&
return setupCell( widget, rowWidget, index );
}
UIWidget* GitHistoryTreeView::updateCell( const Vector2<Int64>& posIndex, const ModelIndex& index,
const size_t& indentLevel, const Float& yOffset ) {
UIWidget* widget = UITreeView::updateCell( posIndex, index, indentLevel, yOffset );
if ( index.isValid() && index.column() == static_cast<Int64>( getModel()->treeColumn() ) &&
getExpandersAsIcons() && getModel()->hasChildren( index ) )
static_cast<GitHistoryTreeViewCell*>( widget )->updateCell( getModel() );
return widget;
}
} // namespace ecode
@@ -33,6 +33,9 @@ class GitHistoryTreeView : public UITreeView {
protected:
GitHistoryTreeView() = default;
UIWidget* updateCell( const Vector2<Int64>& posIndex, const ModelIndex& index,
const size_t& indentLevel, const Float& yOffset ) override;
};
} // namespace ecode
+242 -89
View File
@@ -16,6 +16,7 @@
#include <eepp/ui/tools/uimergeview.hpp>
#include <eepp/ui/uicheckbox.hpp>
#include <eepp/ui/uidropdownlist.hpp>
#include <eepp/ui/uidropdownmodellist.hpp>
#include <eepp/ui/uiiconthememanager.hpp>
#include <eepp/ui/uiloader.hpp>
#include <eepp/ui/uipopupmenu.hpp>
@@ -39,6 +40,73 @@ using json = nlohmann::json;
namespace ecode {
GitHistoryRefModel::GitHistoryRefModel( std::shared_ptr<GitBranchModel> source, String headLabel ) :
mSource( std::move( source ) ), mHeadLabel( std::move( headLabel ) ) {
if ( !mSource )
return;
for ( size_t groupRow = 0; groupRow < mSource->rowCount( {} ); ++groupRow ) {
const ModelIndex group = mSource->index( groupRow, GitBranchModel::Name, {} );
for ( size_t row = 0; row < mSource->rowCount( group ); ++row ) {
const ModelIndex index = mSource->index( row, GitBranchModel::HistoryDisplay, group );
if ( mSource->branchRef( index ).type != Git::RefType::Stash )
mSourceIndexes.emplace_back( index );
}
}
}
ModelIndex GitHistoryRefModel::index( int row, int column, const ModelIndex& parent ) const {
if ( parent.isValid() || row < 0 || static_cast<size_t>( row ) >= rowCount() || column != 0 )
return {};
return createIndex( row, column );
}
Variant GitHistoryRefModel::data( const ModelIndex& index, ModelRole role ) const {
if ( role != ModelRole::Display || !index.isValid() || index.row() < 0 ||
static_cast<size_t>( index.row() ) >= rowCount() )
return {};
if ( index.row() == 0 )
return Variant( &mHeadLabel );
return mSource->data( mSourceIndexes[index.row() - 1], role );
}
std::string GitHistoryRefModel::revision( size_t index ) const {
if ( index == 0 || index > mSourceIndexes.size() )
return "HEAD";
const auto& branch = mSource->branchRef( mSourceIndexes[index - 1] );
switch ( branch.type ) {
case Git::RefType::Head:
return "refs/heads/" + branch.name;
case Git::RefType::Remote:
return "refs/remotes/" + branch.name;
case Git::RefType::Tag:
return "refs/tags/" + branch.name;
default:
return "HEAD";
}
}
bool GitHistoryRefModel::isRevision( size_t index, std::string_view revision ) const {
if ( index == 0 || index > mSourceIndexes.size() )
return revision == "HEAD";
const auto& branch = mSource->branchRef( mSourceIndexes[index - 1] );
std::string_view prefix;
switch ( branch.type ) {
case Git::RefType::Head:
prefix = "refs/heads/";
break;
case Git::RefType::Remote:
prefix = "refs/remotes/";
break;
case Git::RefType::Tag:
prefix = "refs/tags/";
break;
default:
return false;
}
return revision.size() == prefix.size() + branch.name.size() &&
revision.starts_with( prefix ) && revision.substr( prefix.size() ) == branch.name;
}
void GitPlugin::registerSettings( SettingsPage& page ) {
page.addGroup( i18n( "general", "General" ) );
page.addText( "ui-refresh-frequency", "/config/ui_refresh_frequency",
@@ -376,10 +444,8 @@ void GitPlugin::endModelStyler() {
void GitPlugin::updateUINow( bool force ) {
if ( !mGit || !getUISceneNode() )
return;
mLifetime.setDispatcher( getUISceneNode() );
if ( !mProjectPath.empty() )
getUISceneNode()->runOnMainThread( [this] { buildSidePanelTab(); } );
mLifetime.weakHandle().run( []( GitPlugin* plugin ) { plugin->buildSidePanelTab(); } );
updateStatus( force );
updateBranches();
@@ -389,7 +455,10 @@ void GitPlugin::updateUI() {
if ( !mGit || !getUISceneNode() )
return;
getUISceneNode()->debounce( [this] { updateUINow(); }, mRefreshFreq, GIT_STATUS_UPDATE_TAG );
const auto lifetime = mLifetime.weakHandle();
getUISceneNode()->debounce(
[lifetime] { lifetime.run( []( GitPlugin* plugin ) { plugin->updateUINow(); } ); },
mRefreshFreq, GIT_STATUS_UPDATE_TAG );
}
void GitPlugin::updateStatusBarSync() {
@@ -658,8 +727,6 @@ void GitPlugin::updateStatus( bool force ) {
}
PluginRequestHandle GitPlugin::processMessage( const PluginMessage& msg ) {
if ( getUISceneNode() )
mLifetime.setDispatcher( getUISceneNode() );
switch ( msg.type ) {
case PluginMessageType::WorkspaceFolderChanged: {
if ( mGit ) {
@@ -701,9 +768,9 @@ PluginRequestHandle GitPlugin::processMessage( const PluginMessage& msg ) {
}
if ( getUISceneNode() && mSidePanel ) {
getUISceneNode()->runOnMainThread( [this] {
if ( mProjectPath.empty() ) {
hideSidePanel();
mLifetime.weakHandle().run( []( GitPlugin* plugin ) {
if ( plugin->mProjectPath.empty() ) {
plugin->hideSidePanel();
}
} );
}
@@ -717,6 +784,7 @@ PluginRequestHandle GitPlugin::processMessage( const PluginMessage& msg ) {
break;
}
case ecode::PluginMessageType::UIReady: {
mLifetime.setDispatcher( getUISceneNode() );
if ( !mInitialized )
updateUINow();
if ( mModelStylerId == 0 )
@@ -895,32 +963,34 @@ void GitPlugin::checkout( Git::Branch branch ) {
if ( !mGit )
return;
const auto checkOutFn = [this, branch]( bool createLocal ) {
const auto lifetime = mLifetime.weakHandle();
const auto git = mGit;
const std::string repo = repoSelected();
const auto checkOutFn = [this, branch, lifetime, git, repo]( bool createLocal ) {
mLoader->setVisible( true );
mThreadPool->run( [this, branch, createLocal] {
auto result =
createLocal ? mGit->checkoutAndCreateLocalBranch( branch.name, "", repoSelected() )
: mGit->checkout( branch.name, repoSelected() );
if ( result.success() ) {
{
std::string repoSel = repoSelected();
Lock l( mGitBranchMutex );
mGitBranches[repoSel] = branch.name;
}
if ( mBranchesTree->getModel() ) {
if ( createLocal )
updateBranches();
else
mBranchesTree->getModel()->invalidate( Model::DontInvalidateIndexes );
}
} else {
showMessage( LSPMessageType::Warning, result.result );
}
getUISceneNode()->runOnMainThread( [this, success = result.success()] {
if ( success )
invalidateHistory();
mLoader->setVisible( false );
} );
mThreadPool->run( [branch, createLocal, lifetime, git, repo] {
auto result = createLocal ? git->checkoutAndCreateLocalBranch( branch.name, "", repo )
: git->checkout( branch.name, repo );
lifetime.run(
[branch, createLocal, repo, result = std::move( result )]( GitPlugin* plugin ) {
if ( result.success() ) {
{
Lock l( plugin->mGitBranchMutex );
plugin->mGitBranches[repo] = branch.name;
}
if ( plugin->mBranchesTree->getModel() ) {
if ( createLocal )
plugin->updateBranches();
else
plugin->mBranchesTree->getModel()->invalidate(
Model::DontInvalidateIndexes );
}
plugin->invalidateHistory();
} else {
plugin->showMessage( LSPMessageType::Warning, result.result );
}
plugin->mLoader->setVisible( false );
} );
} );
};
@@ -1352,8 +1422,9 @@ void GitPlugin::discard( const std::string& file ) {
}
void GitPlugin::openFile( const std::string& file ) {
getUISceneNode()->runOnMainThread( [this, file] {
mManager->getLoadFileFn()( mGit->getProjectPath() + file, []( auto, auto ) {} );
mLifetime.weakHandle().run( [file]( GitPlugin* plugin ) {
plugin->mManager->getLoadFileFn()( plugin->mGit->getProjectPath() + file,
[]( auto, auto ) {} );
} );
}
@@ -1956,13 +2027,13 @@ void GitPlugin::abortConflictOperation() {
}
void GitPlugin::diff( const Git::DiffMode mode, const std::string& repoPath ) {
mThreadPool->run( [this, mode, repoPath] {
const auto lifetime = mLifetime.weakHandle();
mThreadPool->run( [this, mode, repoPath, lifetime] {
auto res = mGit->diff( mode, repoPath );
if ( res.fail() )
return;
std::string repoName = this->repoName( repoPath );
getUISceneNode()->runOnMainThread( [this, mode, res, repoName, repoPath] {
lifetime.run( [mode, res = std::move( res ), repoPath]( GitPlugin* plugin ) {
std::string modeName;
switch ( mode ) {
case Git::DiffHead: {
@@ -1973,7 +2044,7 @@ void GitPlugin::diff( const Git::DiffMode mode, const std::string& repoPath ) {
modeName = "staged";
break;
}
getPluginContext()->loadDiffFromMemory(
plugin->getPluginContext()->loadDiffFromMemory(
res.result, UIDiffView::isMultiFileDiff( res.result ) ? modeName : "", "",
repoPath );
} );
@@ -1981,11 +2052,12 @@ void GitPlugin::diff( const Git::DiffMode mode, const std::string& repoPath ) {
}
void GitPlugin::diff( const std::string& file, Git::GitStatusType status ) {
mThreadPool->run( [this, file, status] {
const auto lifetime = mLifetime.weakHandle();
mThreadPool->run( [this, file, status, lifetime] {
auto filePath = fixFilePath( file );
if ( status == Git::GitStatusType::Untracked ) {
getUISceneNode()->runOnMainThread( [this, filePath = std::move( filePath )] {
getPluginContext()->loadDiffFromPaths( "", filePath );
lifetime.run( [filePath = std::move( filePath )]( GitPlugin* plugin ) {
plugin->getPluginContext()->loadDiffFromPaths( "", filePath );
} );
return;
}
@@ -2011,19 +2083,20 @@ void GitPlugin::diff( const std::string& file, Git::GitStatusType status ) {
}
}
getUISceneNode()->runOnMainThread(
[this, result = std::move( result ), filePath = std::move( filePath ),
oldImagePath = std::move( oldImagePath ), newImagePath = std::move( newImagePath )] {
getPluginContext()->loadDiffFromMemory(
result, newImagePath.empty() ? filePath : newImagePath, oldImagePath );
} );
lifetime.run( [result = std::move( result ), filePath = std::move( filePath ),
oldImagePath = std::move( oldImagePath ),
newImagePath = std::move( newImagePath )]( GitPlugin* plugin ) {
plugin->getPluginContext()->loadDiffFromMemory(
result, newImagePath.empty() ? filePath : newImagePath, oldImagePath );
} );
} );
}
void GitPlugin::diff( std::vector<Git::DiffFile> files ) {
if ( files.empty() )
return;
mThreadPool->run( [this, files = std::move( files )] {
const auto lifetime = mLifetime.weakHandle();
mThreadPool->run( [this, files = std::move( files ), lifetime] {
std::string patch;
std::string repoPath;
for ( const auto& file : files ) {
@@ -2042,10 +2115,10 @@ void GitPlugin::diff( std::vector<Git::DiffFile> files ) {
if ( !patch.empty() && patch.back() != '\n' )
patch += '\n';
}
getUISceneNode()->runOnMainThread(
[this, patch = std::move( patch ), repoPath = std::move( repoPath )] {
getPluginContext()->loadDiffFromMemory( patch, "selected files", "", repoPath );
} );
lifetime.run( [patch = std::move( patch ),
repoPath = std::move( repoPath )]( GitPlugin* plugin ) {
plugin->getPluginContext()->loadDiffFromMemory( patch, "selected files", "", repoPath );
} );
} );
}
@@ -2253,17 +2326,55 @@ void GitPlugin::invalidateHistory() {
}
void GitPlugin::updateHistoryHeader() {
if ( !mHistoryRefText )
if ( !mHistoryRefDropDown || !mHistoryRefModel )
return;
std::string branch;
auto rowCount = mHistoryRefModel->rowCount();
for ( size_t i = 0; i < rowCount; ++i ) {
if ( mHistoryRefModel->isRevision( i, mHistoryRevision ) ) {
mUpdatingHistoryRefs = true;
mHistoryRefDropDown->getListView()->getSelection().set(
mHistoryRefModel->index( i, 0 ) );
mUpdatingHistoryRefs = false;
return;
}
}
}
void GitPlugin::updateHistoryRefs( const std::shared_ptr<GitBranchModel>& model ) {
if ( !mHistoryRefDropDown )
return;
std::string currentBranch;
{
const std::string repo = repoSelected();
Lock l( mGitBranchMutex );
auto it = mGitBranches.find( repo );
if ( it != mGitBranches.end() )
branch = it->second;
currentBranch = it->second;
}
mHistoryRefText->setText( branch.empty() ? "HEAD" : branch );
String headLabel{ "HEAD" };
if ( !currentBranch.empty() )
headLabel = String::fromUtf8( String::format( "HEAD · %s", currentBranch ) );
auto historyRefModel = std::make_shared<GitHistoryRefModel>( model, std::move( headLabel ) );
size_t selected = 0;
bool found = false;
for ( size_t i = 0; i < historyRefModel->rowCount(); ++i ) {
if ( historyRefModel->isRevision( i, mHistoryRevision ) ) {
selected = i;
found = true;
break;
}
}
const bool revisionChanged = !found && mHistoryRevision != "HEAD";
if ( !found )
mHistoryRevision = "HEAD";
mUpdatingHistoryRefs = true;
mHistoryRefModel = std::move( historyRefModel );
mHistoryRefDropDown->setModel( mHistoryRefModel );
mHistoryRefDropDown->getListView()->getSelection().set(
mHistoryRefModel->index( selected, 0 ) );
mUpdatingHistoryRefs = false;
if ( revisionChanged )
invalidateHistory();
}
void GitPlugin::ensureHistoryLoaded() {
@@ -2288,6 +2399,7 @@ void GitPlugin::reloadHistory() {
mHistoryTree->clearViewMetadata();
mHistoryModel->setRootLoading();
Git::HistoryQuery query;
query.revision = mHistoryRevision;
auto git = mGit;
const auto lifetime = mLifetime.weakHandle();
++mRunningHistoryRequests;
@@ -2401,7 +2513,8 @@ void GitPlugin::openCommitDetails( const Git::Commit& commit ) {
</hbox>
<hbox lw="mp" lh="wc" margin-top="8dp">
<TextView id="git_commit_subject" lw="0" lw8="1" lh="wc"
font-size="14dp" word-wrap="true" focusable="false" />
font-size="14dp" word-wrap="false" text-overflow="ellipsis"
focusable="false" />
<PushButton id="git_commit_message_toggle"
text="@string(git_expand_commit_description, Expand Commit Description)"
icon="icon(unfold, 12dp)" text-as-fallback="true"
@@ -2579,11 +2692,19 @@ void GitPlugin::loadCommitFiles() {
( result.result.empty() ? "" : ": " + result.result ) );
return;
}
std::string body = std::move( result.message );
if ( body.compare( 0, commit.subject.size(), commit.subject ) == 0 ) {
body.erase( 0, commit.subject.size() );
while ( !body.empty() && ( body.front() == '\n' || body.front() == '\r' ) )
body.erase( body.begin() );
std::string message = std::move( result.message );
const size_t subjectEnd = message.find_first_of( "\r\n" );
const std::string_view subject{
message.data(), subjectEnd == std::string::npos ? message.size() : subjectEnd };
plugin->mCommitDetailsSubject->setText(
String::fromUtf8( subject.empty() ? commit.subject : std::string{ subject } ) );
std::string body;
if ( subjectEnd != std::string::npos ) {
size_t bodyStart = subjectEnd;
while ( bodyStart < message.size() &&
( message[bodyStart] == '\n' || message[bodyStart] == '\r' ) )
++bodyStart;
body = message.substr( bodyStart );
}
plugin->mCommitDetailsMessageBody = std::move( body );
plugin->mCommitDetailsMessage->setText(
@@ -2638,20 +2759,26 @@ void GitPlugin::updateBranches( bool force ) {
return;
if ( !mGit || mGit->getGitFolder().empty() ) {
getUISceneNode()->runOnMainThread( [this] { updateBranchesUI( nullptr ); } );
mLifetime.weakHandle().run(
[]( GitPlugin* plugin ) { plugin->updateBranchesUI( nullptr ); } );
return;
}
const std::string requestedRepo = repoSelected();
const auto lifetime = mLifetime.weakHandle();
mRunningUpdateBranches++;
mThreadPool->run(
[this] {
[this, requestedRepo, lifetime] {
if ( !mGit || mGit->getGitFolder().empty() ) {
getUISceneNode()->runOnMainThread( [this] { updateBranchesUI( nullptr ); } );
lifetime.run( [requestedRepo]( GitPlugin* plugin ) {
if ( requestedRepo == plugin->repoSelected() )
plugin->updateBranchesUI( nullptr );
} );
return;
}
auto prevBranch = updateReposBranches();
auto branches = mGit->getAllBranchesAndTags( Git::RefType::All, {}, repoSelected() );
auto branches = mGit->getAllBranchesAndTags( Git::RefType::All, {}, requestedRepo );
auto hash = GitBranchModel::hashBranches( branches );
auto model = GitBranchModel::asModel( std::move( branches ), hash, this );
@@ -2660,23 +2787,26 @@ void GitPlugin::updateBranches( bool force ) {
Lock l( mGitBranchMutex );
branchChanged = prevBranch != mGitBranches;
}
if ( mBranchesTree && mBranchesTree->getModel() &&
static_cast<GitBranchModel*>( mBranchesTree->getModel() )->getHash() == hash ) {
if ( branchChanged ) {
getUISceneNode()->runOnMainThread( [this] {
if ( mBranchesTree && mBranchesTree->getModel() )
mBranchesTree->getModel()->invalidate( Model::DontInvalidateIndexes );
updateHistoryHeader();
invalidateHistory();
} );
lifetime.run( [model, hash, branchChanged, requestedRepo]( GitPlugin* plugin ) {
if ( requestedRepo != plugin->repoSelected() )
return;
if ( plugin->mBranchesTree && plugin->mBranchesTree->getModel() &&
static_cast<GitBranchModel*>( plugin->mBranchesTree->getModel() )->getHash() ==
hash ) {
if ( plugin->mBranchesTree->getModel() ) {
if ( branchChanged )
plugin->mBranchesTree->getModel()->invalidate(
Model::DontInvalidateIndexes );
plugin->updateHistoryRefs( std::static_pointer_cast<GitBranchModel>(
plugin->mBranchesTree->getModelShared() ) );
}
if ( branchChanged )
plugin->invalidateHistory();
} else {
plugin->updateBranchesUI( model );
if ( branchChanged )
plugin->invalidateHistory();
}
return;
}
getUISceneNode()->runOnMainThread( [this, model, branchChanged] {
updateBranchesUI( model );
if ( branchChanged )
invalidateHistory();
} );
},
[this]( auto ) { mRunningUpdateBranches--; } );
@@ -2706,7 +2836,6 @@ void GitPlugin::updateRepos() {
void GitPlugin::updateBranchesUI( std::shared_ptr<GitBranchModel> model ) {
buildSidePanelTab();
updateHistoryHeader();
if ( !model ) {
mBranchesTree->setModel( model );
@@ -2716,6 +2845,7 @@ void GitPlugin::updateBranchesUI( std::shared_ptr<GitBranchModel> model ) {
mBranchesTree->expandAll();
}
updateHistoryRefs( model );
updateRepos();
std::vector<String> items;
@@ -2838,7 +2968,7 @@ void GitPlugin::buildSidePanelTab() {
</vbox>
<vbox id="git_history" lw="mp" lh="mp">
<hbox lw="mp" lh="wc" padding="4dp">
<TextView id="git_history_ref" text="HEAD" lw="0" lw8="1" lh="wc" layout_gravity="center_vertical" text-overflow="ellipsis" />
<DropDownModelList id="git_history_ref" lw="0" lw8="1" lh="wc" menu-width-mode="expand-if-needed" />
<PushButton id="git_history_refresh" text="@string(git_history_refresh, Refresh)" icon="icon(refresh, 12dp)" text-as-fallback="true" />
</hbox>
<GitHistoryTreeView id="git_history_tree" lw="mp" lh="0" lw8="1" />
@@ -2868,7 +2998,7 @@ void GitPlugin::buildSidePanelTab() {
mTabContents->bind( "git_branches_tree", mBranchesTree );
mTabContents->bind( "git_status_tree", mStatusTree );
mTabContents->bind( "git_history_tree", mHistoryTree );
mTabContents->bind( "git_history_ref", mHistoryRefText );
mTabContents->bind( "git_history_ref", mHistoryRefDropDown );
mTabContents->bind( "git_content", mGitContentView );
mTabContents->bind( "git_no_content", mGitNoContentView );
mTabContents->bind( "git_conflict_state", mConflictStateBar );
@@ -2880,7 +3010,24 @@ void GitPlugin::buildSidePanelTab() {
mTabContents->find( "branch_push" )->onClick( [this]( auto ) { push( repoSelected() ); } );
mTabContents->find( "branch_add" )->onClick( [this]( auto ) { branchCreate(); } );
mTabContents->find( "git_history_refresh" )->onClick( [this]( auto ) { reloadHistory(); } );
updateHistoryHeader();
mHistoryRefDropDown->getListView()->setColumnsVisible( { 0 } );
mHistoryRefDropDown->getListView()->setAutoExpandOnSingleColumn( true );
mHistoryRefDropDown->on( Event::OnItemSelected, [this]( const Event* ) {
if ( mUpdatingHistoryRefs )
return;
const ModelIndex selected = mHistoryRefDropDown->getListView()->getSelection().first();
if ( !selected.isValid() || !mHistoryRefModel )
return;
const std::string revision = mHistoryRefModel->revision( selected.row() );
if ( revision == mHistoryRevision )
return;
mHistoryRevision = revision;
invalidateHistory();
} );
updateHistoryRefs(
mBranchesTree && mBranchesTree->getModel()
? std::static_pointer_cast<GitBranchModel>( mBranchesTree->getModelShared() )
: nullptr );
mTabContents->find( "git_conflict_continue" )->onClick( [this]( auto ) {
continueConflictOperation();
} );
@@ -2954,8 +3101,12 @@ void GitPlugin::buildSidePanelTab() {
mPanelSwicher->on( Event::OnItemSelected, [this, listBox]( const Event* ) {
mStackWidget->setActiveWidget( mStackMap[listBox->getItemSelectedIndex()] );
if ( listBox->getItemSelectedIndex() == 2 )
if ( listBox->getItemSelectedIndex() == 2 ) {
if ( mBranchesTree && mBranchesTree->getModel() )
updateHistoryRefs(
std::static_pointer_cast<GitBranchModel>( mBranchesTree->getModelShared() ) );
ensureHistoryLoaded();
}
} );
listBox->setSelected( 0 );
mStackWidget->setActiveWidget( mStackMap[0] );
@@ -3178,6 +3329,8 @@ void GitPlugin::buildSidePanelTab() {
Lock l( mRepoMutex );
mRepoSelected = repo.first;
}
mHistoryRevision = "HEAD";
updateHistoryRefs( nullptr );
invalidateHistory();
updateBranches( true );
updateStatus( true );
+29 -1
View File
@@ -20,6 +20,7 @@ using namespace EE::Scene;
namespace EE::UI {
class UITreeView;
class UIDropDownList;
class UIDropDownModelList;
class UIStackWidget;
class UIListBoxItem;
class UIMenu;
@@ -31,6 +32,28 @@ namespace ecode {
class Git;
class GitBranchModel;
class GitHistoryRefModel : public Model {
public:
explicit GitHistoryRefModel( std::shared_ptr<GitBranchModel> source, String headLabel );
size_t rowCount( const ModelIndex& = {} ) const override { return mSourceIndexes.size() + 1; }
size_t columnCount( const ModelIndex& = {} ) const override { return 1; }
ModelIndex index( int row, int column, const ModelIndex& parent = {} ) const override;
Variant data( const ModelIndex& index, ModelRole role = ModelRole::Display ) const override;
std::string revision( size_t index ) const;
bool isRevision( size_t index, std::string_view revision ) const;
private:
std::shared_ptr<GitBranchModel> mSource;
std::vector<ModelIndex> mSourceIndexes;
String mHeadLabel;
};
static constexpr const char* GIT_EMPTY = "";
static constexpr const char* GIT_SUCCESS = "success";
static constexpr const char* GIT_ERROR = "error";
@@ -125,7 +148,8 @@ class GitPlugin : public PluginBase {
UITreeView* mBranchesTree{ nullptr };
UITreeView* mStatusTree{ nullptr };
UITreeView* mHistoryTree{ nullptr };
UITextView* mHistoryRefText{ nullptr };
UIDropDownModelList* mHistoryRefDropDown{ nullptr };
std::shared_ptr<GitHistoryRefModel> mHistoryRefModel;
std::shared_ptr<GitHistoryModel> mHistoryModel;
UIDropDownList* mPanelSwicher{ nullptr };
UIDropDownList* mRepoDropDown{ nullptr };
@@ -140,6 +164,8 @@ class GitPlugin : public PluginBase {
std::atomic<int> mRunningHistoryRequests{ 0 };
std::atomic<Uint64> mHistoryGeneration{ 0 };
std::string mHistoryRepo;
std::string mHistoryRevision{ "HEAD" };
bool mUpdatingHistoryRefs{ false };
bool mHistoryLoaded{ false };
UIWidget* mCommitDetailsView{ nullptr };
UITextView* mCommitDetailsSubject{ nullptr };
@@ -286,6 +312,8 @@ class GitPlugin : public PluginBase {
void updateHistoryHeader();
void updateHistoryRefs( const std::shared_ptr<GitBranchModel>& model );
void reloadHistory();
void invalidateHistory();
@@ -283,7 +283,7 @@ Plugin* LSPClientPlugin::NewSync( PluginManager* pluginManager ) {
}
LSPClientPlugin::LSPClientPlugin( PluginManager* pluginManager, bool sync ) :
Plugin( pluginManager ) {
Plugin( pluginManager ), mLifetime( this, getUISceneNode() ) {
if ( sync ) {
load( pluginManager );
} else {
@@ -292,6 +292,7 @@ LSPClientPlugin::LSPClientPlugin( PluginManager* pluginManager, bool sync ) :
}
LSPClientPlugin::~LSPClientPlugin() {
mLifetime.invalidate();
waitUntilLoaded();
mShuttingDown = true;
mManager->unsubscribeMessages( this );
@@ -408,9 +409,11 @@ PluginRequestHandle LSPClientPlugin::processDocumentFormatting( const PluginMess
auto ret = server.server->documentFormatting(
server.uri, msg.asJSON()["options"],
[this, server]( const PluginIDType&, const std::vector<LSPTextEdit>& edits ) {
mManager->getSplitter()->getUISceneNode()->runOnMainThread(
[this, server, edits] { processDocumentFormattingResponse( server.uri, edits ); } );
[lifetime = mLifetime.weakHandle(), server]( const PluginIDType&,
const std::vector<LSPTextEdit>& edits ) {
lifetime.run( [server, edits]( LSPClientPlugin* plugin ) {
plugin->processDocumentFormattingResponse( server.uri, edits );
} );
} );
return ret;
@@ -922,6 +925,7 @@ PluginRequestHandle LSPClientPlugin::processMessage( const PluginMessage& msg )
break;
}
case ecode::PluginMessageType::UIReady: {
mLifetime.setDispatcher( getUISceneNode() );
if ( mBrokenUserConfigFile )
displayBrokenUserConfigFileWarning();
break;
@@ -5,6 +5,7 @@
#include "../pluginmanager.hpp"
#include "lspclientservermanager.hpp"
#include <eepp/config.hpp>
#include <eepp/scene/mainthreadlifetime.hpp>
#include <eepp/system/clock.hpp>
#include <eepp/system/mutex.hpp>
#include <eepp/system/sys.hpp>
@@ -106,8 +107,10 @@ class LSPClientPlugin : public Plugin {
const Float& fontSize );
protected:
MainThreadLifetime<LSPClientPlugin> mLifetime;
friend class LSPDocumentClient;
friend class LSPClientServer;
friend class LSPClientServerManager;
Clock mClock;
Mutex mDocMutex;
@@ -203,7 +203,7 @@ void LSPClientServerManager::executeCommand( const std::shared_ptr<TextDocument>
void LSPClientServerManager::applyWorkspaceEdit(
const LSPWorkspaceEdit& edit,
const std::function<void( const LSPApplyWorkspaceEditResponse& res )>& resCb ) {
mPluginManager->getSplitter()->getUISceneNode()->runOnMainThread( [this, edit, resCb] {
mPlugin->mLifetime.weakHandle().run( [this, edit, resCb]( LSPClientPlugin* ) {
bool allDone = true;
for ( const auto& ed : edit.changes ) {
@@ -279,10 +279,9 @@ void LSPClientServerManager::rangeFormatting( std::shared_ptr<TextDocument> doc
server->documentRangeFormatting(
uri, doc->getSelection(), getURIJSON( doc ),
[this, uri]( const PluginIDType&, const std::vector<LSPTextEdit>& edits ) {
mPluginManager->getSplitter()->getUISceneNode()->runOnMainThread(
[this, edits, uri] {
mPlugin->processDocumentFormattingResponse( uri, edits );
} );
mPlugin->mLifetime.weakHandle().run( [edits, uri]( LSPClientPlugin* plugin ) {
plugin->processDocumentFormattingResponse( uri, edits );
} );
} );
}
} );
+6 -3
View File
@@ -19,11 +19,13 @@ ProjectDirectoryTree::ProjectDirectoryTree(
mClosing( false ),
mIgnoreMatcher( path ),
mPluginManager( pluginManager ),
mLoadFileFromPathOrFocusFn( std::move( loadFileFromPathOrFocusFn ) ) {
mLoadFileFromPathOrFocusFn( std::move( loadFileFromPathOrFocusFn ) ),
mLifetime( this, pluginManager ? pluginManager->getUISceneNode() : nullptr ) {
FileSystem::dirAddSlashAtEnd( mPath );
}
ProjectDirectoryTree::~ProjectDirectoryTree() {
mLifetime.invalidate();
mClosing = true;
if ( mPluginManager )
mPluginManager->unsubscribeMessages( "ProjectDirectoryTree" );
@@ -691,8 +693,9 @@ PluginRequestHandle ProjectDirectoryTree::processMessage( const PluginMessage& m
if ( !matchesMap.empty() ) {
if ( mPluginManager && mLoadFileFromPathOrFocusFn ) {
std::string filePath( matchesMap.begin()->second );
mPluginManager->getUISceneNode()->runOnMainThread(
[this, filePath]() { mLoadFileFromPathOrFocusFn( filePath ); } );
mLifetime.weakHandle().run( [filePath]( ProjectDirectoryTree* tree ) {
tree->mLoadFileFromPathOrFocusFn( filePath );
} );
}
}
+2
View File
@@ -3,6 +3,7 @@
#include "ignorematcher.hpp"
#include "plugins/pluginmanager.hpp"
#include <eepp/scene/mainthreadlifetime.hpp>
#include <eepp/scene/scenemanager.hpp>
#include <eepp/system/luapattern.hpp>
#include <eepp/system/mutex.hpp>
@@ -189,6 +190,7 @@ class ProjectDirectoryTree {
IgnoreMatcherManager mIgnoreMatcher;
PluginManager* mPluginManager{ nullptr };
std::function<void( const std::string& )> mLoadFileFromPathOrFocusFn;
MainThreadLifetime<ProjectDirectoryTree> mLifetime;
void getDirectoryFiles( std::vector<std::string>& files, std::vector<std::string>& names,
std::string directory, std::set<std::string> currentDirs,
+8 -3
View File
@@ -4,6 +4,9 @@
namespace ecode {
SettingsActions::SettingsActions( App* app ) :
mApp( app ), mLifetime( this, app ? app->getUISceneNode() : nullptr ) {}
void SettingsActions::checkForUpdatesResponse( Http::Response&& response, bool fromStartup ) {
auto updatesError = [this, fromStartup]() {
if ( fromStartup )
@@ -97,13 +100,15 @@ void SettingsActions::checkForUpdatesResponse( Http::Response&& response, bool f
}
void SettingsActions::checkForUpdates( bool fromStartup ) {
mLifetime.setDispatcher( mApp->getUISceneNode() );
Http::getAsync(
[this, fromStartup]( const Http&, Http::Request&, Http::Response& response ) {
if ( !SceneManager::isActive() )
return;
mApp->getUISceneNode()->runOnMainThread( [this, res = response, fromStartup]() mutable {
checkForUpdatesResponse( std::move( res ), fromStartup );
} );
mLifetime.weakHandle().run(
[res = response, fromStartup]( SettingsActions* actions ) mutable {
actions->checkForUpdatesResponse( std::move( res ), fromStartup );
} );
},
"https://api.github.com/repos/SpartanJ/ecode/releases/latest", Seconds( 30 ) );
}
+3 -1
View File
@@ -2,6 +2,7 @@
#define SETTINGSACTIONS_HPP
#include <eepp/ee.hpp>
#include <eepp/scene/mainthreadlifetime.hpp>
namespace ecode {
@@ -9,7 +10,7 @@ class App;
class SettingsActions {
public:
explicit SettingsActions( App* app ) : mApp( app ) {}
explicit SettingsActions( App* app );
void checkForUpdates( bool fromStartup = false );
@@ -29,6 +30,7 @@ class SettingsActions {
private:
App* mApp{ nullptr };
MainThreadLifetime<SettingsActions> mLifetime;
String i18n( const std::string& key, const String& def );
+5 -3
View File
@@ -308,7 +308,8 @@ static void disableTabFocusTree( Node* node ) {
disableTabFocusTree( child );
}
SettingsPanel::SettingsPanel( App* app ) : mApp( app ) {}
SettingsPanel::SettingsPanel( App* app ) :
mApp( app ), mLifetime( this, app ? app->getUISceneNode() : nullptr ) {}
SettingsPanel::PanelState& SettingsPanel::state( Scope scope ) {
return scope == Scope::User ? mUser : mProject;
@@ -2107,8 +2108,9 @@ void SettingsPanel::addPluginSettings( PanelState& panel ) {
auto* window = panel.window;
window->runOnMainThread( [this, window, path] {
window->closeWindow();
mApp->getUISceneNode()->runOnMainThread(
[this, path] { mApp->focusOrLoadFile( path ); } );
mLifetime.weakHandle().run( [path]( SettingsPanel* panel ) {
panel->mApp->focusOrLoadFile( path );
} );
} );
} );
panel.documents.emplace_back( std::move( document ) );
+2
View File
@@ -3,6 +3,7 @@
#include "settingsmodel.hpp"
#include <eepp/ee.hpp>
#include <eepp/scene/mainthreadlifetime.hpp>
#include <functional>
#include <string>
#include <vector>
@@ -59,6 +60,7 @@ class SettingsPanel {
};
App* mApp{ nullptr };
MainThreadLifetime<SettingsPanel> mLifetime;
PanelState mUser;
PanelState mProject;
+22 -16
View File
@@ -10,7 +10,7 @@ namespace ecode {
StatusBuildOutputController::StatusBuildOutputController( UISplitter* mainSplitter,
UISceneNode* uiSceneNode,
PluginContextProvider* pluginContext ) :
StatusBarElement( mainSplitter, uiSceneNode, pluginContext ) {}
StatusBarElement( mainSplitter, uiSceneNode, pluginContext ), mLifetime( this, uiSceneNode ) {}
static std::string getProjectOutputParserTypeToString( const ProjectOutputParserTypes& type ) {
switch ( type ) {
@@ -286,8 +286,8 @@ void StatusBuildOutputController::runBuild( const std::string& buildName,
updateBuildButton();
if ( !mContext->getWindow()->hasFocus() ) {
mContext->getUISceneNode()->runOnMainThread( [this] {
mContext->getWindow()->flash( WindowFlashOperation::UntilFocused );
mLifetime.weakHandle().run( []( StatusBuildOutputController* controller ) {
controller->mContext->getWindow()->flash( WindowFlashOperation::UntilFocused );
} );
}
@@ -527,19 +527,25 @@ void StatusBuildOutputController::createContainer() {
if ( !data.isValid() )
return;
std::string path = data.toString();
mUISceneNode->runOnMainThread( [this, path, lineNum, colNum] {
UITab* tab = mSplitter->isDocumentOpen( path );
if ( !tab ) {
mContext->loadFileFromPath(
path, true, nullptr,
[this, lineNum, colNum]( auto, auto ) {
onLoadDone( lineNum, colNum );
} );
} else {
tab->getTabWidget()->setTabSelected( tab );
onLoadDone( lineNum, colNum );
}
} );
mLifetime.weakHandle().run(
[path, lineNum,
colNum]( StatusBuildOutputController* controller ) {
UITab* tab = controller->mSplitter->isDocumentOpen( path );
if ( !tab ) {
controller->mContext->loadFileFromPath(
path, true, nullptr,
[lifetime = controller->mLifetime.weakHandle(),
lineNum, colNum]( auto, auto ) {
lifetime.run(
[lineNum, colNum]( auto* loaded ) {
loaded->onLoadDone( lineNum, colNum );
} );
} );
} else {
tab->getTabWidget()->setTabSelected( tab );
controller->onLoadDone( lineNum, colNum );
}
} );
} );
}
} else {
@@ -3,6 +3,7 @@
#include "projectbuild.hpp"
#include "uistatusbar.hpp"
#include <eepp/scene/mainthreadlifetime.hpp>
#include <eepp/system/luapattern.hpp>
#include <eepp/ui/tools/uicodeeditorsplitter.hpp>
#include <eepp/ui/uicodeeditor.hpp>
@@ -40,7 +41,7 @@ class StatusBuildOutputController : public StatusBarElement {
StatusBuildOutputController( UISplitter* mainSplitter, UISceneNode* uiSceneNode,
PluginContextProvider* pluginContext );
virtual ~StatusBuildOutputController() {};
virtual ~StatusBuildOutputController() { mLifetime.invalidate(); };
void runBuild( const std::string& buildName, const std::string& buildType,
const ProjectBuildOutputParser& outputParser = {}, bool isClean = false,
@@ -73,6 +74,7 @@ class StatusBuildOutputController : public StatusBarElement {
std::vector<PatternHolder> mPatternHolder;
std::string mCurLineBuffer;
bool mScrollLocked{ true };
MainThreadLifetime<StatusBuildOutputController> mLifetime;
void createContainer();
+38 -32
View File
@@ -185,6 +185,7 @@ UniversalLocator::UniversalLocator( UICodeEditorSplitter* editorSplitter, UIScen
App* app ) :
mSplitter( editorSplitter ),
mUISceneNode( sceneNode ),
mLifetime( this, sceneNode ),
mApp( app ),
mCommandPalette( mApp->getThreadPool() ) {
@@ -466,11 +467,12 @@ void UniversalLocator::updateFilesTable( bool useGlob ) {
: ProjectDirectoryTree::MatchType::Fuzzy,
text, LOCATEBAR_MAX_RESULTS,
[this, text]( auto res ) {
mUISceneNode->runOnMainThread( [this, res] {
mLocateTable->setModel( res );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
mLocateTable->scrollToTop();
updateLocateBarSync();
mLifetime.weakHandle().run( [res]( UniversalLocator* locator ) {
locator->mLocateTable->setModel( res );
locator->mLocateTable->getSelection().set(
locator->mLocateTable->getModel()->index( 0 ) );
locator->mLocateTable->scrollToTop();
locator->updateLocateBarSync();
} );
},
mApp->getCurrentProject() );
@@ -500,11 +502,12 @@ void UniversalLocator::updateCommandPaletteTable() {
if ( txt.size() > 1 ) {
mCommandPalette.asyncFuzzyMatch( txt.substr( 1 ).trim(), 10000, [this]( auto res ) {
mUISceneNode->runOnMainThread( [this, res] {
mLocateTable->setModel( res );
if ( mLocateTable->getModel()->hasChildren() )
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
mLocateTable->scrollToTop();
mLifetime.weakHandle().run( [res]( UniversalLocator* locator ) {
locator->mLocateTable->setModel( res );
if ( locator->mLocateTable->getModel()->hasChildren() )
locator->mLocateTable->getSelection().set(
locator->mLocateTable->getModel()->index( 0 ) );
locator->mLocateTable->scrollToTop();
} );
} );
} else if ( mCommandPalette.getCurModel() ) {
@@ -575,15 +578,17 @@ bool UniversalLocator::tryLocator( const String& txt ) {
query.trim();
const Uint64 generation = ++mLocatorModelGeneration;
locator->modelFn( query, [this, generation]( std::shared_ptr<Model> model ) {
mUISceneNode->runOnMainThread( [this, generation, model = std::move( model )] {
if ( generation != mLocatorModelGeneration || !mLocateBarLayout->isVisible() )
return;
mLocateTable->setModel( model );
if ( model && model->hasChildren() )
mLocateTable->getSelection().set( model->index( 0 ) );
mLocateTable->scrollToTop();
updateLocateBarSync();
} );
mLifetime.weakHandle().run(
[generation, model = std::move( model )]( UniversalLocator* locator ) {
if ( generation != locator->mLocatorModelGeneration ||
!locator->mLocateBarLayout->isVisible() )
return;
locator->mLocateTable->setModel( model );
if ( model && model->hasChildren() )
locator->mLocateTable->getSelection().set( model->index( 0 ) );
locator->mLocateTable->scrollToTop();
locator->updateLocateBarSync();
} );
} );
return true;
}
@@ -1232,17 +1237,17 @@ void UniversalLocator::requestWorkspaceSymbol() {
}
void UniversalLocator::updateWorkspaceSymbol( const LSPSymbolInformationList& res ) {
mUISceneNode->runOnMainThread( [this, res] {
if ( !mWorkspaceSymbolModel ) {
mWorkspaceSymbolModel =
LSPSymbolInfoModel::create( mApp->getUISceneNode(), mWorkspaceSymbolQuery, res );
mLifetime.weakHandle().run( [res]( UniversalLocator* locator ) {
if ( !locator->mWorkspaceSymbolModel ) {
locator->mWorkspaceSymbolModel = LSPSymbolInfoModel::create(
locator->mApp->getUISceneNode(), locator->mWorkspaceSymbolQuery, res );
} else {
mWorkspaceSymbolModel->setQuery( mWorkspaceSymbolQuery );
mWorkspaceSymbolModel->append( res );
locator->mWorkspaceSymbolModel->setQuery( locator->mWorkspaceSymbolQuery );
locator->mWorkspaceSymbolModel->append( res );
}
mLocateTable->setModel( mWorkspaceSymbolModel );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
mLocateTable->scrollToTop();
locator->mLocateTable->setModel( locator->mWorkspaceSymbolModel );
locator->mLocateTable->getSelection().set( locator->mLocateTable->getModel()->index( 0 ) );
locator->mLocateTable->scrollToTop();
} );
}
@@ -1293,10 +1298,11 @@ void UniversalLocator::updateDocumentSymbol( const LSPSymbolInformationList& res
} else {
asyncFuzzyMatchTextDocumentSymbol( res, mCurDocQuery, 100, [this]( const auto model ) {
mTextDocumentSymbolModel = model;
mUISceneNode->runOnMainThread( [this] {
mLocateTable->setModel( mTextDocumentSymbolModel );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
mLocateTable->scrollToTop();
mLifetime.weakHandle().run( []( UniversalLocator* locator ) {
locator->mLocateTable->setModel( locator->mTextDocumentSymbolModel );
locator->mLocateTable->getSelection().set(
locator->mLocateTable->getModel()->index( 0 ) );
locator->mLocateTable->scrollToTop();
} );
} );
}
+2
View File
@@ -6,6 +6,7 @@
#include "projectdirectorytree.hpp"
#include "widgetcommandexecuter.hpp"
#include <eepp/ee.hpp>
#include <eepp/scene/mainthreadlifetime.hpp>
#include <tuple>
#include <unordered_map>
@@ -110,6 +111,7 @@ class UniversalLocator {
UITextInput* mLocateInput{ nullptr };
UICodeEditorSplitter* mSplitter{ nullptr };
UISceneNode* mUISceneNode{ nullptr };
MainThreadLifetime<UniversalLocator> mLifetime;
std::shared_ptr<LSPSymbolInfoModel> mWorkspaceSymbolModel{ nullptr };
std::string mWorkspaceSymbolQuery;
App* mApp{ nullptr };