Added Window::flash().
FileSystemModel will not spam invalidations when not needed.
UIAbstractTableView and UIAbstractView won't queue more than 1 invalidation per frame when invalidations comes from a non-main thread.
ecode:
UniversalLocator now understands pasted file paths (with and withouth cursor position) and allows to go to that file and position.
This commit is contained in:
Martín Lucas Golini
2023-06-23 20:28:22 -03:00
parent 5ab1fa72d3
commit 5d54f01352
13 changed files with 263 additions and 196 deletions

View File

@@ -29,6 +29,12 @@ enum WindowStyle {
#endif
};
enum class WindowFlashOperation {
Cancel,
Briefly,
UntilFocused,
};
enum class WindowBackend : Uint32 { SDL2, Default };
#ifndef EE_SCREEN_KEYBOARD_ENABLED
@@ -195,6 +201,9 @@ class EE_API Window {
/** This will attempt to raise the window */
virtual void raise();
/** Request a window to demand attention from the user. */
virtual void flash( WindowFlashOperation op );
/** This will attempt to show the window */
virtual void show();
@@ -474,7 +483,7 @@ class EE_API Window {
/** Shows a native message box.
* @return True if message box was shown
*/
*/
virtual bool showMessageBox( const MessageBoxType& type, const std::string& title,
const std::string& message );

View File

@@ -1185,6 +1185,7 @@
../../src/tools/ecode/plugins/formatter/formatterplugin.hpp
../../src/tools/ecode/notificationcenter.cpp
../../src/tools/ecode/notificationcenter.hpp
../../src/tools/ecode/pathhelper.hpp
../../src/tools/ecode/plugins/linter/linterplugin.cpp
../../src/tools/ecode/plugins/linter/linterplugin.hpp
../../src/tools/ecode/plugins/lsp/lspclientplugin.cpp

View File

@@ -76,10 +76,14 @@ size_t UIAbstractTableView::getItemCount() const {
void UIAbstractTableView::onModelUpdate( unsigned flags ) {
if ( !Engine::instance()->isMainThread() ) {
runOnMainThread( [&, flags] {
modelUpdate( flags );
createOrUpdateColumns( true );
} );
static constexpr String::HashType tag = String::hash( "onModelUpdate" );
removeActionsByTag( tag );
runOnMainThread(
[&, flags] {
modelUpdate( flags );
createOrUpdateColumns( true );
},
Time::Zero, tag );
} else {
UIAbstractView::onModelUpdate( flags );
createOrUpdateColumns( true );

View File

@@ -122,7 +122,9 @@ void UIAbstractView::modelUpdate( unsigned flags ) {
void UIAbstractView::onModelUpdate( unsigned flags ) {
if ( !Engine::instance()->isMainThread() ) {
runOnMainThread( [&, flags] { modelUpdate( flags ); } );
static constexpr String::HashType tag = String::hash( "onModelUpdate" );
removeActionsByTag( tag );
runOnMainThread( [&, flags] { modelUpdate( flags ); }, Time::Zero, tag );
} else {
modelUpdate( flags );
}

View File

@@ -279,7 +279,7 @@ FileSystemModel::FileSystemModel( const std::string& rootPath, const FileSystemM
mDisplayConfig( displayConfig ) {
mRoot = std::make_unique<Node>( mRootPath, *this );
mInitOK = true;
onModelUpdate();
invalidate();
}
FileSystemModel::~FileSystemModel() {
@@ -317,8 +317,7 @@ FileSystemModel::Node* FileSystemModel::getNodeFromPath( std::string path, bool
if ( !folders.empty() ) {
for ( size_t i = 0; i < folders.size(); i++ ) {
auto& part = folders[i];
if ( ( foundNode = curNode->findChildName(
part, *this, invalidateTree || i == folders.size() - 1 ) ) ) {
if ( ( foundNode = curNode->findChildName( part, *this, invalidateTree ) ) ) {
curNode = foundNode;
} else {
return nullptr;
@@ -336,12 +335,12 @@ void FileSystemModel::reload() {
void FileSystemModel::refresh() {
Lock l( resourceMutex() );
mRoot->refresh( *this );
onModelUpdate();
invalidate();
}
void FileSystemModel::update() {
mRoot = std::make_unique<Node>( mRootPath, *this );
onModelUpdate();
invalidate();
}
const FileSystemModel::Node& FileSystemModel::node( const ModelIndex& index ) const {
@@ -599,58 +598,58 @@ bool FileSystemModel::handleFileEventLocked( const FileEvent& event ) {
: file.getDirectoryPath(),
true, false );
if ( parent ) {
auto* childNodeExists =
getNodeFromPath( file.getFilepath(), file.isDirectory(), false );
if ( childNodeExists )
return false;
if ( !parent )
return false;
Node* childNode = parent->createChild( file.getFileName(), *this );
auto* childNodeExists =
getNodeFromPath( file.getFilepath(), file.isDirectory(), false );
if ( childNodeExists )
return false;
if ( !childNode->getName().empty() ) {
size_t pos = getFileIndex( parent, file );
Node* childNode = parent->createChild( file.getFileName(), *this );
const auto& displayCfg = getDisplayConfig();
if ( childNode->getName().empty() )
return false;
if ( displayCfg.fileIsVisibleFn &&
!displayCfg.fileIsVisibleFn( file.getFilepath() ) )
return false;
size_t pos = getFileIndex( parent, file );
if ( pos == INDEX_ALREADY_EXISTS )
return false;
const auto& displayCfg = getDisplayConfig();
beginInsertRows( parent->index( *this, 0 ), pos, pos );
if ( displayCfg.fileIsVisibleFn && !displayCfg.fileIsVisibleFn( file.getFilepath() ) )
return false;
if ( pos >= parent->mChildren.size() ) {
parent->mChildren.emplace_back( childNode );
} else {
parent->mChildren.insert( parent->mChildren.begin() + pos, childNode );
}
if ( pos == INDEX_ALREADY_EXISTS )
return false;
endInsertRows();
beginInsertRows( parent->index( *this, 0 ), pos, pos );
forEachView( [&]( UIAbstractView* view ) {
std::vector<ModelIndex> newIndexes;
view->getSelection().forEachIndex( [&]( const ModelIndex& selectedIndex ) {
Node* curNode = static_cast<Node*>( selectedIndex.internalData() );
if ( curNode->getParent() == parent ) {
if ( selectedIndex.row() >= (Int64)pos ) {
newIndexes.emplace_back( this->index(
selectedIndex.row() + 1, selectedIndex.column(),
selectedIndex.parent() ) );
} else {
newIndexes.emplace_back( selectedIndex );
}
} else {
newIndexes.emplace_back( selectedIndex );
}
} );
view->getSelection().set( newIndexes, false );
} );
} else {
return false;
}
if ( pos >= parent->mChildren.size() ) {
parent->mChildren.emplace_back( childNode );
} else {
parent->mChildren.insert( parent->mChildren.begin() + pos, childNode );
}
endInsertRows();
forEachView( [&]( UIAbstractView* view ) {
std::vector<ModelIndex> newIndexes;
view->getSelection().forEachIndex( [&]( const ModelIndex& selectedIndex ) {
Node* curNode = static_cast<Node*>( selectedIndex.internalData() );
if ( curNode->getParent() == parent ) {
if ( selectedIndex.row() >= (Int64)pos ) {
newIndexes.emplace_back( this->index( selectedIndex.row() + 1,
selectedIndex.column(),
selectedIndex.parent() ) );
} else {
newIndexes.emplace_back( selectedIndex );
}
} else {
newIndexes.emplace_back( selectedIndex );
}
} );
view->getSelection().set( newIndexes, false );
} );
break;
}
case FileSystemEventType::Delete: {
@@ -710,92 +709,91 @@ bool FileSystemModel::handleFileEventLocked( const FileEvent& event ) {
case FileSystemEventType::Moved: {
FileInfo file( event.directory + event.filename, false );
if ( file.exists() ) {
auto* node = getNodeFromPath( event.directory + event.oldFilename, false, false );
if ( node ) {
ModelIndex index = node->index( *this, 0 );
if ( !index.isValid() )
return false;
if ( !file.exists() )
return false;
Node* parent = node->mParent;
if ( !parent )
return false;
if ( ( getMode() == Mode::DirectoriesOnly && !file.isDirectory() ) )
return false;
if ( !node->info().isHidden() && getDisplayConfig().ignoreHidden &&
file.isHidden() ) {
return handleFileEventLocked(
{ FileSystemEventType::Delete, event.directory, event.oldFilename } );
}
Node* childNode = parent->mChildren[index.row()];
childNode->rename( file );
parent->mChildren.erase( parent->mChildren.begin() + index.row() );
size_t pos = getFileIndex( node->getParent(), file );
// Don't add the file if already exists (if moved an old file to another old
// file)
if ( pos == INDEX_ALREADY_EXISTS ) {
eeDelete( childNode );
return false;
}
std::map<UIAbstractView*, std::vector<ModelIndex>> keptSelections;
std::map<UIAbstractView*, std::vector<std::string>> prevSelections;
std::map<UIAbstractView*, std::vector<ModelIndex>> prevSelectionsModelIndex;
forEachView( [&]( UIAbstractView* view ) {
view->getSelection().forEachIndex( [&]( const ModelIndex& selectedIndex ) {
Node* curNode = static_cast<Node*>( selectedIndex.internalData() );
if ( curNode->mParent == parent ) {
prevSelectionsModelIndex[view].emplace_back( selectedIndex );
prevSelections[view].emplace_back(
( curNode->getName() == event.oldFilename )
? event.filename
: curNode->getName() );
} else {
keptSelections[view].emplace_back( selectedIndex );
}
} );
} );
beginMoveRows( index.parent(), index.row(), index.row(), index.parent(), pos );
if ( pos >= parent->mChildren.size() ) {
parent->mChildren.emplace_back( childNode );
} else {
parent->mChildren.insert( parent->mChildren.begin() + pos, childNode );
}
endMoveRows();
forEachView( [&]( UIAbstractView* view ) {
std::vector<std::string> names = prevSelections[view];
std::vector<ModelIndex> newIndexes = keptSelections[view];
int i = 0;
for ( const auto& name : names ) {
Int64 row = parent->findChildRowFromName( name, *this );
if ( row >= 0 ) {
newIndexes.emplace_back(
this->index( row, prevSelectionsModelIndex[view][i].column(),
prevSelectionsModelIndex[view][i].parent() ) );
}
++i;
}
view->getSelection().set( newIndexes, false );
} );
} else {
return handleFileEventLocked(
{ FileSystemEventType::Add, event.directory, event.filename } );
}
auto* node = getNodeFromPath( event.directory + event.oldFilename, false, false );
if ( !node ) {
return handleFileEventLocked(
{ FileSystemEventType::Add, event.directory, event.filename } );
}
ModelIndex index = node->index( *this, 0 );
if ( !index.isValid() )
return false;
Node* parent = node->mParent;
if ( !parent )
return false;
if ( ( getMode() == Mode::DirectoriesOnly && !file.isDirectory() ) )
return false;
if ( !node->info().isHidden() && getDisplayConfig().ignoreHidden && file.isHidden() ) {
return handleFileEventLocked(
{ FileSystemEventType::Delete, event.directory, event.oldFilename } );
}
Node* childNode = parent->mChildren[index.row()];
childNode->rename( file );
parent->mChildren.erase( parent->mChildren.begin() + index.row() );
size_t pos = getFileIndex( node->getParent(), file );
// Don't add the file if already exists (if moved an old file to another old
// file)
if ( pos == INDEX_ALREADY_EXISTS ) {
eeDelete( childNode );
return false;
}
std::map<UIAbstractView*, std::vector<ModelIndex>> keptSelections;
std::map<UIAbstractView*, std::vector<std::string>> prevSelections;
std::map<UIAbstractView*, std::vector<ModelIndex>> prevSelectionsModelIndex;
forEachView( [&]( UIAbstractView* view ) {
view->getSelection().forEachIndex( [&]( const ModelIndex& selectedIndex ) {
Node* curNode = static_cast<Node*>( selectedIndex.internalData() );
if ( curNode->mParent == parent ) {
prevSelectionsModelIndex[view].emplace_back( selectedIndex );
prevSelections[view].emplace_back(
( curNode->getName() == event.oldFilename ) ? event.filename
: curNode->getName() );
} else {
keptSelections[view].emplace_back( selectedIndex );
}
} );
} );
beginMoveRows( index.parent(), index.row(), index.row(), index.parent(), pos );
if ( pos >= parent->mChildren.size() ) {
parent->mChildren.emplace_back( childNode );
} else {
parent->mChildren.insert( parent->mChildren.begin() + pos, childNode );
}
endMoveRows();
forEachView( [&]( UIAbstractView* view ) {
std::vector<std::string> names = prevSelections[view];
std::vector<ModelIndex> newIndexes = keptSelections[view];
int i = 0;
for ( const auto& name : names ) {
Int64 row = parent->findChildRowFromName( name, *this );
if ( row >= 0 ) {
newIndexes.emplace_back(
this->index( row, prevSelectionsModelIndex[view][i].column(),
prevSelectionsModelIndex[view][i].parent() ) );
}
++i;
}
view->getSelection().set( newIndexes, false );
} );
break;
}
case FileSystemEventType::Modified: {
break;
return false;
}
}
@@ -814,7 +812,8 @@ bool FileSystemModel::handleFileEvent( const FileEvent& event ) {
ret = handleFileEventLocked( event );
}
onModelUpdate( UpdateFlag::DontInvalidateIndexes );
if ( ret )
invalidate( UpdateFlag::DontInvalidateIndexes );
return ret;
}

View File

@@ -785,6 +785,15 @@ void WindowSDL::raise() {
SDL_RaiseWindow( mSDLWindow );
}
void WindowSDL::flash( WindowFlashOperation op ) {
SDL_FlashOperation sdlOp = SDL_FlashOperation::SDL_FLASH_BRIEFLY;
if ( op == WindowFlashOperation::Cancel )
sdlOp = SDL_FlashOperation::SDL_FLASH_CANCEL;
else if ( op == WindowFlashOperation::UntilFocused )
sdlOp = SDL_FlashOperation::SDL_FLASH_UNTIL_FOCUSED;
SDL_FlashWindow( mSDLWindow, sdlOp );
}
void WindowSDL::show() {
SDL_ShowWindow( mSDLWindow );
}

View File

@@ -67,6 +67,8 @@ class EE_API WindowSDL : public Window {
virtual void raise();
virtual void flash( WindowFlashOperation op );
virtual void show();
virtual void setPosition( int Left, int Top );

View File

@@ -517,6 +517,8 @@ void Window::hide() {}
void Window::raise() {}
void Window::flash( WindowFlashOperation ) {}
void Window::show() {}
void Window::setPosition( int, int ) {}

View File

@@ -1,5 +1,6 @@
#include "ecode.hpp"
#include "featureshealth.hpp"
#include "pathhelper.hpp"
#include "plugins/autocomplete/autocompleteplugin.hpp"
#include "plugins/formatter/formatterplugin.hpp"
#include "plugins/linter/linterplugin.hpp"
@@ -32,48 +33,6 @@ bool firstFrame = true;
bool firstUpdate = true;
App* appInstance = nullptr;
static bool pathHasPosition( const std::string& path ) {
#if EE_PLATFORM == EE_PLATFORM_WIN
bool countedSep = std::count( path.begin(), path.end(), ':' ) > 1;
#else
bool countedSep = std::count( path.begin(), path.end(), ':' ) > 0;
#endif
if ( countedSep ) {
auto seps = String::split( path, ':' );
return String::isNumber( seps.back() );
}
return false;
}
static std::pair<std::string, TextPosition> getPathAndPosition( const std::string& path ) {
if ( pathHasPosition( path ) ) {
auto parts = String::split( path, ':' );
if ( parts.size() >= 2 ) {
Int64 line = 0;
Int64 col = 0;
#if EE_PLATFORM == EE_PLATFORM_WIN
size_t partCount = 4;
#else
size_t partCount = 3;
#endif
int linePos = parts.size() >= partCount ? parts.size() - 2 : parts.size() - 1;
int colPos = parts.size() >= partCount ? parts.size() - 1 : -1;
if ( String::fromString( line, parts[linePos] ) ) {
if ( colPos > 0 )
String::fromString( col, parts[colPos] );
}
std::string npath( parts[0] );
if ( parts.size() >= 2 ) {
for ( Int64 i = 1; i < linePos; i++ ) {
npath += ":" + parts[i];
}
}
return { npath, { eemax( (Int64)0, line - 1 ), col } };
}
}
return { path, { 0, 0 } };
}
void appLoop() {
appInstance->mainLoop();
}

View File

@@ -0,0 +1,56 @@
#ifndef ECODE_PATHHELPER_HPP
#define ECODE_PATHHELPER_HPP
#include <algorithm>
#include <eepp/ui/doc/textposition.hpp>
using namespace EE;
using namespace EE::UI::Doc;
namespace ecode {
template <typename T> static bool pathHasPosition( const T& path ) {
#if EE_PLATFORM == EE_PLATFORM_WIN
bool countedSep = std::count( path.begin(), path.end(), ':' ) > 1;
#else
bool countedSep = std::count( path.begin(), path.end(), ':' ) > 0;
#endif
if ( countedSep ) {
auto seps = String::split( path, ':' );
return String::isNumber( seps.back() );
}
return false;
}
template <typename T> static std::pair<T, TextPosition> getPathAndPosition( const T& path ) {
if ( pathHasPosition( path ) ) {
auto parts = String::split( path, ':' );
if ( parts.size() >= 2 ) {
Int64 line = 0;
Int64 col = 0;
#if EE_PLATFORM == EE_PLATFORM_WIN
size_t partCount = 4;
#else
size_t partCount = 3;
#endif
int linePos = parts.size() >= partCount ? parts.size() - 2 : parts.size() - 1;
int colPos = parts.size() >= partCount ? parts.size() - 1 : -1;
if ( String::fromString( line, parts[linePos] ) ) {
if ( colPos > 0 )
String::fromString( col, parts[colPos] );
}
std::string npath( parts[0] );
if ( parts.size() >= 2 ) {
for ( Int64 i = 1; i < linePos; i++ ) {
npath += ":" + parts[i];
}
}
return { npath, { eemax( (Int64)0, line - 1 ), col } };
}
}
return { path, { 0, 0 } };
}
} // namespace ecode
#endif // ECODE_PATHHELPER_HPP

View File

@@ -257,6 +257,9 @@ void StatusBuildOutputController::runBuild( const std::string& buildName,
if ( cleanButton )
cleanButton->setEnabled( true );
}
if ( !mApp->getWindow()->hasFocus() )
mApp->getWindow()->flash( WindowFlashOperation::Briefly );
} );
if ( !res.isValid() ) {
@@ -344,6 +347,9 @@ void StatusBuildOutputController::runClean( const std::string& buildName,
if ( buildButton )
buildButton->setEnabled( true );
}
if ( !mApp->getWindow()->hasFocus() )
mApp->getWindow()->flash( WindowFlashOperation::Briefly );
} );
if ( !res.isValid() ) {

View File

@@ -1,5 +1,6 @@
#include "universallocator.hpp"
#include "ecode.hpp"
#include "pathhelper.hpp"
#include <algorithm>
namespace ecode {
@@ -136,22 +137,28 @@ void UniversalLocator::toggleLocateBar() {
}
void UniversalLocator::updateFilesTable() {
auto text = mLocateInput->getText();
if ( pathHasPosition( text ) ) {
auto pathAndPos = getPathAndPosition( text );
text = pathAndPos.first;
}
if ( !mApp->isDirTreeReady() ) {
mLocateTable->setModel( ProjectDirectoryTree::emptyModel( getLocatorCommands() ) );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
} else if ( !mLocateInput->getText().empty() ) {
#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ )
mApp->getDirTree()->asyncFuzzyMatchTree(
mLocateInput->getText(), LOCATEBAR_MAX_RESULTS, [&]( auto res ) {
mUISceneNode->runOnMainThread( [&, res] {
text, LOCATEBAR_MAX_RESULTS, [this, text]( auto res ) {
mUISceneNode->runOnMainThread( [this, res] {
mLocateTable->setModel( res );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
mLocateTable->scrollToTop();
} );
} );
#else
mLocateTable->setModel(
mApp->getDirTree()->fuzzyMatchTree( mLocateInput->getText(), LOCATEBAR_MAX_RESULTS ) );
mLocateTable->setModel( mApp->getDirTree()->fuzzyMatchTree( text, LOCATEBAR_MAX_RESULTS ) );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
mLocateTable->scrollToTop();
#endif
@@ -335,22 +342,13 @@ void UniversalLocator::initLocateBar( UILocateBar* locateBar, UITextInput* locat
auto range = rangeStr.isValid()
? TextRange::fromString( rangeStr.asStdString() )
: TextRange();
UITab* tab = mSplitter->isDocumentOpen( path, true );
if ( !tab ) {
FileInfo fileInfo( path );
if ( fileInfo.exists() && fileInfo.isRegularFile() )
mApp->loadFileFromPath( path, true, nullptr,
[range]( UICodeEditor* editor, auto ) {
if ( range.isValid() )
editor->goToLine( range.start() );
} );
} else {
tab->getTabWidget()->setTabSelected( tab );
if ( range.isValid() ) {
UICodeEditor* editor = tab->getOwnedWidget()->asType<UICodeEditor>();
editor->goToLine( range.start() );
}
if ( !range.isValid() && !FileSystem::isRelativePath( path ) &&
pathHasPosition( mLocateInput->getText() ) &&
String::startsWith( mLocateInput->getText().toUtf8(), path ) ) {
auto pathAndPos = getPathAndPosition( mLocateInput->getText() );
range = { pathAndPos.second, pathAndPos.second };
}
focusOrLoadFile( path, range );
mLocateBarLayout->execute( "close-locatebar" );
} else {
Variant rangeStr( modelEvent->getModel()->data(
@@ -525,6 +523,24 @@ std::shared_ptr<FileListModel> UniversalLocator::openDocumentsModel( const std::
return std::make_shared<FileListModel>( ffiles, fnames );
}
void UniversalLocator::focusOrLoadFile( const std::string& path, const TextRange& range ) {
UITab* tab = mSplitter->isDocumentOpen( path, true );
if ( !tab ) {
FileInfo fileInfo( path );
if ( fileInfo.exists() && fileInfo.isRegularFile() )
mApp->loadFileFromPath( path, true, nullptr, [range]( UICodeEditor* editor, auto ) {
if ( range.isValid() )
editor->goToLine( range.start() );
} );
} else {
tab->getTabWidget()->setTabSelected( tab );
if ( range.isValid() ) {
UICodeEditor* editor = tab->getOwnedWidget()->asType<UICodeEditor>();
editor->goToLine( range.start() );
}
}
}
void UniversalLocator::updateOpenDocumentsTable() {
mLocateTable->setModel(
openDocumentsModel( mLocateInput->getText().substr( 2 ).trim().toUtf8() ) );

View File

@@ -86,6 +86,8 @@ class UniversalLocator {
void updateOpenDocumentsTable();
std::shared_ptr<FileListModel> openDocumentsModel( const std::string& match );
void focusOrLoadFile( const std::string& path, const TextRange& range = {} );
};
} // namespace ecode