Files
eepp/src/tools/ecode/universallocator.cpp
2024-09-25 01:19:15 -03:00

1178 lines
40 KiB
C++

#include "universallocator.hpp"
#include "ecode.hpp"
#include "pathhelper.hpp"
#include "settingsmenu.hpp"
#include <algorithm>
namespace ecode {
class LSPSymbolInfoModel : public Model {
public:
static std::shared_ptr<LSPSymbolInfoModel> create( UISceneNode* uiSceneNode,
const std::string& query,
const LSPSymbolInformationList& data,
bool displayLine = false ) {
return std::make_shared<LSPSymbolInfoModel>( uiSceneNode, query, data, displayLine );
}
explicit LSPSymbolInfoModel( UISceneNode* uiSceneNode, const std::string& query,
const LSPSymbolInformationList& info, bool displayLine ) :
mUISceneNode( uiSceneNode ), mQuery( query ), mInfo( info ), mDisplayLine( displayLine ) {}
size_t rowCount( const ModelIndex& ) const override { return mInfo.size(); };
size_t columnCount( const ModelIndex& ) const override { return 2; }
Variant data( const ModelIndex& index, ModelRole role ) const override {
if ( index.row() >= (Int64)mInfo.size() )
return {};
if ( role == ModelRole::Display ) {
switch ( index.column() ) {
case 0:
return Variant( mInfo[index.row()].name.c_str() );
case 1: {
if ( mDisplayLine ) {
std::string detail( !mInfo[index.row()].detail.empty()
? mInfo[index.row()].detail + " (" +
mInfo[index.row()].range.toString() + ")"
: mInfo[index.row()].range.toString() );
return Variant( detail );
} else {
return Variant( mInfo[index.row()].url.getFSPath() );
}
}
}
} else if ( role == ModelRole::Icon && index.column() == 0 ) {
return mUISceneNode->findIcon(
LSPSymbolKindHelper::toIconString( mInfo[index.row()].kind ) );
} else if ( role == ModelRole::Custom && index.column() == 1 ) {
return Variant( mInfo[index.row()].range.toString() );
}
return {};
}
const LSPSymbolInformationList& getInfo() const { return mInfo; }
LSPSymbolInformation at( const size_t& idx ) {
eeASSERT( idx < mInfo.size() );
return mInfo[idx];
}
const std::string& getQuery() const { return mQuery; }
void append( const LSPSymbolInformationList& res ) {
mInfo.insert( mInfo.end(), res.begin(), res.end() );
std::sort( mInfo.begin(), mInfo.end(),
[]( const LSPSymbolInformation& l, const LSPSymbolInformation& r ) {
return l.score > r.score;
} );
onModelUpdate();
}
void setQuery( const std::string& query ) {
if ( mQuery != query ) {
clear();
mQuery = query;
onModelUpdate();
}
}
void clear() { mInfo.clear(); }
protected:
UISceneNode* mUISceneNode{ nullptr };
std::string mQuery;
LSPSymbolInformationList mInfo;
bool mDisplayLine{ false };
};
LSPSymbolInformationList fuzzyMatchTextDocumentSymbol( const LSPSymbolInformationList& list,
const std::string& query,
const size_t& limit ) {
LSPSymbolInformationList nl;
std::map<int, LSPSymbolInformation, std::greater<int>> matchesMap;
for ( const auto& l : list ) {
int matchName = String::fuzzyMatch( l.name, query );
matchesMap.insert( { matchName, l } );
}
while ( matchesMap.size() > limit )
matchesMap.erase( std::prev( matchesMap.end() ) );
for ( auto& m : matchesMap ) {
m.second.score = m.first;
nl.emplace_back( std::move( m.second ) );
}
return nl;
}
static constexpr auto LOCATEBAR_MAX_VISIBLE_ITEMS = 18;
static constexpr auto LOCATEBAR_MAX_RESULTS = 100;
UniversalLocator::UniversalLocator( UICodeEditorSplitter* editorSplitter, UISceneNode* sceneNode,
App* app ) :
mSplitter( editorSplitter ),
mUISceneNode( sceneNode ),
mApp( app ),
mCommandPalette( mApp->getThreadPool() ) {
mLocatorProviders.push_back(
{ ">", mUISceneNode->i18n( "search_in_command_palette", "Search in Command Palette" ),
[this]( auto ) {
showCommandPalette();
return true;
},
[this]( const Variant&, const ModelEvent* modelEvent ) {
ModelIndex idx(
modelEvent->getModel()->index( modelEvent->getModelIndex().row(), 2 ) );
if ( idx.isValid() ) {
String cmd = modelEvent->getModel()->data( idx, ModelRole::Display ).toString();
mApp->runCommand( cmd );
if ( mSplitter->getCurWidget()->isType( UI_TYPE_CODEEDITOR ) &&
mSplitter->curEditorIsNotNull() &&
mSplitter->getCurEditor()->getDocument().hasCommand( cmd ) )
mSplitter->getCurEditor()->setFocus();
if ( cmd != "open-locatebar" && cmd != "open-workspace-symbol-search" &&
cmd != "open-document-symbol-search" && cmd != "go-to-line" &&
cmd != "show-open-documents" && cmd != "open-locatebar-glob-search" ) {
hideLocateBar();
} else {
mLocateInput->setFocus();
}
}
},
nullptr, false } );
mLocatorProviders.push_back(
{ ":", mUISceneNode->i18n( "search_for_workspace_symbols", "Search for Workspace Symbols" ),
[this]( auto ) {
showWorkspaceSymbol();
return true;
},
nullptr } );
mLocatorProviders.push_back(
{ ".",
mUISceneNode->i18n( "search_for_symbols_in_current_document",
"Search for Symbols in Current Document" ),
[this]( auto ) {
showDocumentSymbol();
return true;
},
[this]( const Variant&, const ModelEvent* modelEvent ) {
Variant rangeStr( modelEvent->getModel()->data(
modelEvent->getModel()->index( modelEvent->getModelIndex().row(), 1 ),
ModelRole::Custom ) );
auto range = rangeStr.isValid() ? TextRange::fromString( rangeStr.asStdString() )
: TextRange();
if ( !range.isValid() )
return;
UITab* tab = mSplitter->isDocumentOpen( URI( mCurDocURI ), true );
if ( tab ) {
tab->getTabWidget()->setTabSelected( tab );
UICodeEditor* editor = tab->getOwnedWidget()->asType<UICodeEditor>();
editor->goToLine( range.start() );
mSplitter->addEditorPositionToNavigationHistory( editor );
mLocateBarLayout->execute( "close-locatebar" );
}
} } );
mLocatorProviders.push_back(
{ "l",
mUISceneNode->i18n( "go_to_line_in_current_document", "Go To Line in Current Document" ),
[this]( const String& inputTxt ) {
if ( mSplitter->curEditorExistsAndFocused() ) {
String lineColInput( inputTxt.substr( 2 ) );
std::vector<String> parts = lineColInput.split( ':' );
Int64 line = 0;
if ( parts.size() > 0 && String::fromString( line, parts[0] ) && line - 1 >= 0 )
mLocateTable->setVisible( false );
}
return true;
},
nullptr,
[this]( const String& inputTxt ) {
if ( mSplitter->curEditorExistsAndFocused() ) {
String lineColInput( inputTxt.substr( 2 ) );
std::vector<String> parts = lineColInput.split( ':' );
Int64 line, column = 0;
if ( parts.size() > 0 && String::fromString( line, parts[0] ) && line - 1 >= 0 ) {
if ( parts.size() > 1 ) {
String::fromString( column, parts[1] );
if ( column < 0 ) {
column = 0;
}
}
if ( mSplitter->curEditorExistsAndFocused() ) {
mSplitter->getCurEditor()->goToLine( { line - 1, column } );
mSplitter->addCurrentPositionToNavigationHistory();
}
mLocateBarLayout->execute( "close-locatebar" );
return true;
}
}
return false;
},
false } );
mLocatorProviders.push_back( { "o", mUISceneNode->i18n( "open_documents", "Open Documents" ),
[this]( auto ) {
showOpenDocuments();
return true;
},
nullptr, nullptr, false } );
mLocatorProviders.push_back(
{ "g",
mUISceneNode->i18n( "search_files_with_glob_match", "Search files with glob matching" ),
[this]( auto ) {
showLocateTableGlob();
return true;
},
nullptr, nullptr, false } );
// clang-format off
mLocatorProviders.push_back( { "sb", mUISceneNode->i18n( "switch_build", "Switch Build" ),
[this](auto) {
showSwitchBuild();
return true;
},
[this]( const Variant& vName, const ModelEvent* ) {
auto pbm = mApp->getProjectBuildManager();
if ( nullptr == pbm )
return;
auto cfg = pbm->getConfig();
std::string buildName = vName.toString();
if ( pbm->hasBuild( buildName ) ) {
cfg.buildName = buildName;
pbm->setConfig( cfg );
mLocateBarLayout->execute( "close-locatebar" );
}
} } );
// clang-format on
mLocatorProviders.push_back(
{ "sbt", mUISceneNode->i18n( "switch_build_type", "Switch Build Type" ),
[this]( auto ) {
showSwitchBuildType();
return true;
},
[this]( const Variant& vName, const ModelEvent* ) {
auto pbm = mApp->getProjectBuildManager();
if ( nullptr == pbm )
return;
auto cfg = pbm->getConfig();
auto build = pbm->getBuild( cfg.buildName );
if ( build != nullptr ) {
std::string buildType = vName.toString();
if ( build->buildTypes().find( buildType ) != build->buildTypes().end() ) {
cfg.buildType = buildType;
pbm->setConfig( cfg );
mLocateBarLayout->execute( "close-locatebar" );
}
}
} } );
mLocatorProviders.push_back(
{ "srt", mUISceneNode->i18n( "switch_run_target", "Switch Run Target" ),
[this]( auto ) {
showSwitchRunTarget();
return true;
},
[this]( const Variant& vName, const ModelEvent* ) {
auto pbm = mApp->getProjectBuildManager();
if ( nullptr == pbm )
return;
auto cfg = pbm->getConfig();
auto build = pbm->getBuild( cfg.buildName );
if ( build != nullptr ) {
std::string runTarget = vName.toString();
auto it = std::find_if( build->runConfigs().begin(), build->runConfigs().end(),
[&runTarget]( const ProjectBuildStep& run ) {
return run.name == runTarget;
} );
if ( it != build->runConfigs().end() ) {
cfg.runName = runTarget;
pbm->setConfig( cfg );
mLocateBarLayout->execute( "close-locatebar" );
}
}
} } );
mLocatorProviders.push_back(
{ "ft", mUISceneNode->i18n( "switch_file_type", "Switch File Type" ),
[this]( auto ) {
showSwitchFileType();
return true;
},
[this]( const Variant& vName, const ModelEvent* ) {
if ( mSplitter->getCurEditor() ) {
const auto& df =
SyntaxDefinitionManager::instance()->getByLanguageName( vName.toString() );
mSplitter->getCurEditor()->setSyntaxDefinition( df );
mApp->getSettingsMenu()->updateCurrentFileType();
mLocateBarLayout->execute( "close-locatebar" );
}
} } );
mApp->getPluginManager()->subscribeMessages(
"universallocator", [this]( const PluginMessage& msg ) -> PluginRequestHandle {
return processResponse( msg );
} );
}
void UniversalLocator::hideLocateBar() {
mLocateBarLayout->setVisible( false );
mLocateTable->setVisible( false );
mApp->getStatusBar()->updateState();
}
void UniversalLocator::toggleLocateBar() {
if ( mLocateBarLayout->isVisible() ) {
mLocateBarLayout->execute( "close-locatebar" );
} else {
showLocateBar();
}
}
void UniversalLocator::updateFilesTable( bool useGlob ) {
auto text = mLocateInput->getText();
if ( pathHasPosition( text ) ) {
auto pathAndPos = getPathAndPosition( text );
text = pathAndPos.first;
}
if ( useGlob && String::startsWith( text, "g " ) )
text = text.substr( 2 );
if ( !mApp->isDirTreeReady() ) {
mLocateTable->setModel(
ProjectDirectoryTree::emptyModel( getLocatorCommands(), mApp->getCurrentProject() ) );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
} else if ( !mLocateInput->getText().empty() ) {
#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ )
mApp->getDirTree()->asyncMatchTree(
useGlob ? ProjectDirectoryTree::MatchType::Glob
: 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();
} );
},
mApp->getCurrentProject() );
#else
mLocateTable->setModel(
useGlob ? mApp->getDirTree()->globMatchTree( text, LOCATEBAR_MAX_RESULTS,
mApp->getCurrentProject() )
: mApp->getDirTree()->fuzzyMatchTree( text, LOCATEBAR_MAX_RESULTS,
mApp->getCurrentProject() ) );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
mLocateTable->scrollToTop();
#endif
} else {
mLocateTable->setModel( mApp->getDirTree()->asModel(
LOCATEBAR_MAX_RESULTS, getLocatorCommands(), mApp->getCurrentProject() ) );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
}
}
void UniversalLocator::updateCommandPaletteTable() {
if ( !mCommandPalette.isSet() )
mCommandPalette.setCommandPalette( mApp->getMainLayout()->getCommandList(),
mApp->getMainLayout()->getKeyBindings() );
if ( !mCommandPalette.isEditorSet() && mSplitter->curEditorIsNotNull() )
mCommandPalette.setEditorCommandPalette(
mSplitter->getCurEditor()->getDocumentRef()->getCommandList(),
mSplitter->getCurEditor()->getKeyBindings() );
mCommandPalette.setCurModel( mSplitter->curEditorIsNotNull() ? mCommandPalette.getEditorModel()
: mCommandPalette.getBaseModel() );
auto txt( mLocateInput->getText() );
txt.trim();
if ( txt.size() > 1 ) {
#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ )
mCommandPalette.asyncFuzzyMatch( txt.substr( 1 ), 1000, [this]( auto res ) {
mUISceneNode->runOnMainThread( [this, res] {
mLocateTable->setModel( res );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
mLocateTable->scrollToTop();
} );
} );
#else
mLocateTable->setModel( mCommandPalette.fuzzyMatch( txt.substr(), 1000 ) );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
mLocateTable->scrollToTop();
#endif
} else if ( mCommandPalette.getCurModel() ) {
mLocateTable->setModel( mCommandPalette.getCurModel() );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
}
}
void UniversalLocator::showLocateTable() {
mLocateTable->setVisible( true );
Vector2f pos( mLocateInput->convertToWorldSpace( { 0, 0 } ) );
pos.y -= mLocateTable->getPixelsSize().getHeight();
mLocateTable->setPixelsPosition( pos );
updateFilesTable();
}
void UniversalLocator::showLocateTableGlob() {
mLocateTable->setVisible( true );
Vector2f pos( mLocateInput->convertToWorldSpace( { 0, 0 } ) );
pos.y -= mLocateTable->getPixelsSize().getHeight();
mLocateTable->setPixelsPosition( pos );
updateFilesTable( true );
}
void UniversalLocator::goToLine() {
showLocateBar();
mLocateInput->setText( "l " );
}
bool UniversalLocator::isCommand( const std::string& filename ) {
const auto isLocator = [this]( const std::string& filename ) {
return std::find_if( mLocatorProviders.begin(), mLocatorProviders.end(),
[&filename]( const LocatorProvider& provider ) {
return filename == provider.symbolTrigger;
} ) != mLocatorProviders.end();
};
return !filename.empty() && isLocator( filename );
}
std::optional<UniversalLocator::LocatorProvider> UniversalLocator::getLocator( const String& txt ) {
for ( const auto& locator : mLocatorProviders )
if ( String::startsWith( txt, locator.symbolTrigger ) )
return locator;
return {};
}
bool UniversalLocator::isLocator( const String& txt ) {
if ( !txt.empty() ) {
for ( const auto& locator : mLocatorProviders )
if ( String::startsWith( txt, locator.symbolTrigger ) )
return true;
}
return false;
}
bool UniversalLocator::tryLocator( const String& txt ) {
if ( txt.empty() )
return false;
for ( const auto& locator : mLocatorProviders ) {
if ( String::startsWith( txt, locator.symbolTrigger ) && locator.switchFn( txt ) )
return true;
}
return false;
}
bool UniversalLocator::openLocator( const String& txt, const Variant& vName,
const ModelEvent* modelEvent ) {
for ( const auto& locator : mLocatorProviders ) {
if ( String::startsWith( txt, locator.symbolTrigger ) && locator.openFn ) {
locator.openFn( vName, modelEvent );
return true;
}
}
return false;
}
bool UniversalLocator::pressEnterLocator( const String& txt ) {
for ( const auto& locator : mLocatorProviders ) {
if ( String::startsWith( txt, locator.symbolTrigger ) && locator.pressEnterFn &&
locator.pressEnterFn( txt ) )
return true;
}
return false;
}
void UniversalLocator::initLocateBar( UILocateBar* locateBar, UITextInput* locateInput ) {
mLocateBarLayout = locateBar;
mLocateInput = locateInput;
auto addClickListener = [this]( UIWidget* widget, std::string cmd ) {
widget->on( Event::MouseClick, [this, cmd]( const Event* event ) {
const MouseEvent* mouseEvent = static_cast<const MouseEvent*>( event );
if ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) {
mLocateBarLayout->execute( cmd );
}
} );
};
mLocateTable = UITableView::New();
mLocateTable->setId( "locate_bar_table" );
mLocateTable->setParent( mUISceneNode->getRoot() );
mLocateTable->setHeadersVisible( false );
mLocateTable->setVisible( false );
mLocateInput->on( Event::OnTextChanged, [this]( const Event* ) {
const String& inputTxt = mLocateInput->getText();
if ( !tryLocator( inputTxt ) )
showLocateTable();
updateLocateBarSync();
} );
mLocateInput->on( Event::OnPressEnter, [this]( const Event* ) {
KeyEvent keyEvent( mLocateTable, Event::KeyDown, KEY_RETURN, SCANCODE_UNKNOWN, 0, 0 );
const String& inputTxt = mLocateInput->getText();
if ( pressEnterLocator( inputTxt ) )
return;
mLocateTable->forceKeyDown( keyEvent );
} );
mLocateInput->on( Event::KeyDown, [this]( const Event* event ) {
const KeyEvent* keyEvent = static_cast<const KeyEvent*>( event );
mLocateTable->forceKeyDown( *keyEvent );
} );
mLocateBarLayout->setCommand( "close-locatebar", [this] {
hideLocateBar();
if ( mSplitter->getCurWidget() )
mSplitter->getCurWidget()->setFocus();
} );
mLocateBarLayout->getKeyBindings().addKeybindsString( {
{ "escape", "close-locatebar" },
} );
mLocateTable->on( Event::KeyDown, [this]( const Event* event ) {
const KeyEvent* keyEvent = static_cast<const KeyEvent*>( event );
if ( keyEvent->getKeyCode() == KEY_ESCAPE )
mLocateBarLayout->execute( "close-locatebar" );
} );
addClickListener( mLocateBarLayout->find<UIWidget>( "locatebar_close" ), "close-locatebar" );
mLocateTable->on( Event::OnModelEvent, [this]( const Event* event ) {
const ModelEvent* modelEvent = static_cast<const ModelEvent*>( event );
if ( modelEvent->getModelEventType() == ModelEventType::Open ) {
Variant vName( modelEvent->getModel()->data(
modelEvent->getModel()->index( modelEvent->getModelIndex().row(), 0 ),
ModelRole::Display ) );
if ( isCommand( vName.toString() ) ) {
mLocateInput->setText( vName.toString() );
return;
}
if ( openLocator( mLocateInput->getText(), vName, modelEvent ) )
return;
Variant vPath( modelEvent->getModel()->data(
modelEvent->getModel()->index( modelEvent->getModelIndex().row(), 1 ),
ModelRole::Display ) );
if ( !vPath.isValid() )
return;
std::string path( vPath.toString() );
if ( path.empty() )
return;
Variant rangeStr( modelEvent->getModel()->data(
modelEvent->getModel()->index( modelEvent->getModelIndex().row(), 1 ),
ModelRole::Custom ) );
auto range =
rangeStr.isValid() ? TextRange::fromString( rangeStr.toString() ) : TextRange();
if ( FileSystem::isRelativePath( path ) )
path = mApp->getCurrentProject() + path;
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" );
}
} );
}
void UniversalLocator::updateLocateBarSync() {
Float width = eeceil( mLocateInput->getPixelsSize().getWidth() );
mLocateTable->setPixelsSize( width,
mLocateTable->getRowHeight() * LOCATEBAR_MAX_VISIBLE_ITEMS );
width -= mLocateTable->getVerticalScrollBar()->getPixelsSize().getWidth();
if ( mLocateTable->getModel() && mLocateTable->getModel()->columnCount() >= 3 ) {
mLocateTable->setColumnsVisible( { 0, 1, 2 } );
mLocateTable->setColumnWidth( 0, eefloor( width * 0.5 ) );
mLocateTable->setColumnWidth( 1, eefloor( width * 0.25 ) );
mLocateTable->setColumnWidth( 2, eefloor( width * 0.25 ) );
} else if ( mLocateTable->getModel() && mLocateTable->getModel()->columnCount() >= 2 ) {
mLocateTable->setColumnsVisible( { 0, 1 } );
mLocateTable->setColumnWidth( 0, eeceil( width * 0.5 ) );
mLocateTable->setColumnWidth( 1, width - mLocateTable->getColumnWidth( 0 ) );
} else {
mLocateTable->setColumnsVisible( { 0 } );
mLocateTable->setColumnWidth( 0, width );
}
Vector2f pos( mLocateInput->convertToWorldSpace( { 0, 0 } ) );
pos.y -= mLocateTable->getPixelsSize().getHeight();
mLocateTable->setPixelsPosition( pos );
}
void UniversalLocator::updateLocateBar() {
mLocateBarLayout->runOnMainThread( [this] { updateLocateBarSync(); } );
}
void UniversalLocator::showBar() {
mApp->hideGlobalSearchBar();
mApp->hideSearchBar();
mApp->hideStatusTerminal();
mApp->hideStatusBuildOutput();
mApp->hideStatusAppOutput();
mLocateBarLayout->setVisible( true );
mLocateInput->setFocus();
mLocateTable->setVisible( true );
const String& text = mLocateInput->getText();
if ( isLocator( text ) ) {
auto locator = *getLocator( text );
mLocateInput->getDocument().setSelection(
{ { 0, mLocateInput->getDocument().endOfLine( { 0, 0 } ).column() },
{ 0, static_cast<Int64>( locator.symbolTrigger.size() ) } } );
} else {
mLocateInput->getDocument().selectAll();
}
mLocateInput->on( Event::OnSizeChange, [this]( const Event* ) { updateLocateBar(); } );
}
void UniversalLocator::showLocateBar( bool useGlob ) {
showBar();
if ( isLocator( mLocateInput->getText() ) )
mLocateInput->setText( useGlob ? "g " : "" );
if ( mApp->getDirTree() && !mLocateTable->getModel() ) {
mLocateTable->setModel( mApp->getDirTree()->asModel(
LOCATEBAR_MAX_RESULTS, getLocatorCommands(), mApp->getCurrentProject() ) );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
} else if ( !mLocateTable->getModel() ) {
mLocateTable->setModel(
ProjectDirectoryTree::emptyModel( getLocatorCommands(), mApp->getCurrentProject() ) );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
}
updateLocateBar();
mApp->getStatusBar()->updateState();
}
void UniversalLocator::showCommandPalette() {
showBar();
if ( mLocateInput->getText().empty() || mLocateInput->getText()[0] != '>' )
mLocateInput->setText( "> " );
updateCommandPaletteTable();
updateLocateBar();
mApp->getStatusBar()->updateState();
}
void UniversalLocator::showWorkspaceSymbol() {
showBar();
if ( mLocateInput->getText().empty() || mLocateInput->getText()[0] != ':' )
mLocateInput->setText( ": " );
requestWorkspaceSymbol();
updateLocateBar();
mApp->getStatusBar()->updateState();
}
void UniversalLocator::showDocumentSymbol() {
showBar();
if ( mLocateInput->getText().empty() || mLocateInput->getText()[0] != '.' )
mLocateInput->setText( ". " );
requestDocumentSymbol();
updateLocateBar();
mApp->getStatusBar()->updateState();
}
void UniversalLocator::showOpenDocuments() {
showBar();
if ( mLocateInput->getText().empty() || mLocateInput->getText()[0] != 'o' )
mLocateInput->setText( "o " );
if ( mLocateInput->getText().size() >= 2 )
updateOpenDocumentsTable();
updateLocateBar();
mApp->getStatusBar()->updateState();
}
std::shared_ptr<FileListModel> UniversalLocator::openDocumentsModel( const std::string& match ) {
std::vector<std::string> docs;
mApp->getSplitter()->forEachDoc( [&docs]( TextDocument& doc ) {
if ( doc.hasFilepath() &&
std::find( docs.begin(), docs.end(), doc.getFilePath() ) == docs.end() ) {
docs.push_back( doc.getFilePath() );
}
} );
std::sort( docs.begin(), docs.end(), []( const auto& left, const auto& right ) {
return FileSystem::fileNameFromPath( left ) < FileSystem::fileNameFromPath( right );
} );
std::vector<std::string> files;
std::vector<std::string> names;
for ( const auto& doc : docs ) {
names.emplace_back( FileSystem::fileNameFromPath( doc ) );
files.emplace_back( std::move( doc ) );
}
if ( match.empty() )
return std::make_shared<FileListModel>( files, names );
std::multimap<int, int, std::greater<int>> matchesMap;
for ( size_t i = 0; i < names.size(); i++ ) {
int matchName = String::fuzzyMatch( names[i], match, true, true );
int matchPath = String::fuzzyMatch( files[i], match, true, true );
matchesMap.insert( { std::max( matchName, matchPath ), i } );
}
std::vector<std::string> ffiles;
std::vector<std::string> fnames;
for ( auto& res : matchesMap ) {
fnames.emplace_back( std::move( names[res.second] ) );
ffiles.emplace_back( std::move( files[res.second] ) );
}
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, [this, range]( UICodeEditor* editor, auto ) {
if ( range.isValid() ) {
editor->goToLine( range.start() );
mSplitter->addEditorPositionToNavigationHistory( editor );
}
} );
} else {
tab->getTabWidget()->setTabSelected( tab );
if ( range.isValid() ) {
UICodeEditor* editor = tab->getOwnedWidget()->asType<UICodeEditor>();
editor->goToLine( range.start() );
mSplitter->addEditorPositionToNavigationHistory( editor );
}
}
}
void UniversalLocator::updateOpenDocumentsTable() {
mLocateTable->setModel(
openDocumentsModel( mLocateInput->getText().substr( 2 ).trim().toUtf8() ) );
if ( mLocateTable->getModel()->rowCount() > 0 )
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
mLocateTable->scrollToTop();
}
void UniversalLocator::showSwitchBuild() {
showBar();
if ( mLocateInput->getText().empty() || !String::startsWith( mLocateInput->getText(), "sb " ) )
mLocateInput->setText( "sb " );
if ( mLocateInput->getText().size() >= 3 )
updateSwitchBuildTable();
updateLocateBar();
mApp->getStatusBar()->updateState();
}
std::shared_ptr<ItemListOwnerModel<std::string>>
UniversalLocator::openBuildModel( const std::string& match ) {
if ( nullptr == mApp->getProjectBuildManager() )
return ItemListOwnerModel<std::string>::create( {} );
const auto& builds = mApp->getProjectBuildManager()->getBuilds();
std::vector<std::string> buildNames;
if ( builds.empty() )
return ItemListOwnerModel<std::string>::create( {} );
buildNames.reserve( builds.size() );
for ( const auto& build : builds ) {
if ( match.empty() ||
String::startsWith( String::toLower( build.first ), String::toLower( match ) ) )
buildNames.push_back( build.first );
}
std::sort( buildNames.begin(), buildNames.end() );
return ItemListOwnerModel<std::string>::create( buildNames );
}
void UniversalLocator::updateSwitchBuildTable() {
mLocateTable->setModel( openBuildModel( mLocateInput->getText().substr( 3 ).trim().toUtf8() ) );
if ( mLocateTable->getModel()->rowCount() > 0 ) {
ModelIndex idx =
mLocateTable->findRowWithText( mApp->getProjectBuildManager()->getConfig().buildName );
mLocateTable->getSelection().set( idx.isValid() ? idx
: mLocateTable->getModel()->index( 0 ) );
}
mLocateTable->scrollToTop();
}
void UniversalLocator::showSwitchBuildType() {
showBar();
if ( mLocateInput->getText().empty() || !String::startsWith( mLocateInput->getText(), "sbt " ) )
mLocateInput->setText( "sbt " );
if ( mLocateInput->getText().size() >= 4 )
updateSwitchBuildTypeTable();
updateLocateBar();
mApp->getStatusBar()->updateState();
}
void UniversalLocator::showSwitchRunTarget() {
showBar();
if ( mLocateInput->getText().empty() || !String::startsWith( mLocateInput->getText(), "srt " ) )
mLocateInput->setText( "srt " );
if ( mLocateInput->getText().size() >= 4 )
updateSwitchRunTargetTable();
updateLocateBar();
mApp->getStatusBar()->updateState();
}
std::shared_ptr<ItemListOwnerModel<std::string>>
UniversalLocator::openBuildTypeModel( const std::string& match ) {
if ( nullptr == mApp->getProjectBuildManager() )
return ItemListOwnerModel<std::string>::create( {} );
const auto& builds = mApp->getProjectBuildManager()->getBuilds();
const auto& cfg = mApp->getProjectBuildManager()->getConfig();
auto buildIt = builds.find( cfg.buildName );
if ( buildIt == builds.end() )
return ItemListOwnerModel<std::string>::create( {} );
const auto& build = buildIt->second;
const auto& buildTypes = build.buildTypes();
if ( buildTypes.empty() )
return ItemListOwnerModel<std::string>::create( {} );
std::vector<std::string> buildTypeNames;
buildTypeNames.reserve( buildTypes.size() );
for ( const auto& build : buildTypes ) {
if ( match.empty() ||
String::startsWith( String::toLower( build ), String::toLower( match ) ) )
buildTypeNames.push_back( build );
}
std::sort( buildTypeNames.begin(), buildTypeNames.end() );
return ItemListOwnerModel<std::string>::create( buildTypeNames );
}
std::shared_ptr<ItemListOwnerModel<std::string>>
UniversalLocator::openRunTargetModel( const std::string& match ) {
if ( nullptr == mApp->getProjectBuildManager() )
return ItemListOwnerModel<std::string>::create( {} );
const auto& builds = mApp->getProjectBuildManager()->getBuilds();
const auto& cfg = mApp->getProjectBuildManager()->getConfig();
auto buildIt = builds.find( cfg.buildName );
if ( buildIt == builds.end() )
return ItemListOwnerModel<std::string>::create( {} );
const auto& build = buildIt->second;
const auto& runs = build.runConfigs();
if ( runs.empty() )
return ItemListOwnerModel<std::string>::create( {} );
std::vector<std::string> runTargetNames;
runTargetNames.reserve( runs.size() );
for ( const auto& run : runs ) {
if ( match.empty() ||
String::startsWith( String::toLower( run.name ), String::toLower( match ) ) )
runTargetNames.push_back( run.name );
}
std::sort( runTargetNames.begin(), runTargetNames.end() );
return ItemListOwnerModel<std::string>::create( runTargetNames );
}
void UniversalLocator::updateSwitchBuildTypeTable() {
mLocateTable->setModel(
openBuildTypeModel( mLocateInput->getText().substr( 4 ).trim().toUtf8() ) );
if ( mLocateTable->getModel()->rowCount() > 0 && nullptr != mApp->getProjectBuildManager() ) {
ModelIndex idx =
mLocateTable->findRowWithText( mApp->getProjectBuildManager()->getConfig().buildType );
mLocateTable->getSelection().set( idx.isValid() ? idx
: mLocateTable->getModel()->index( 0 ) );
}
mLocateTable->scrollToTop();
mLocateTable->setColumnsVisible( { 0 } );
}
void UniversalLocator::updateSwitchRunTargetTable() {
mLocateTable->setModel(
openRunTargetModel( mLocateInput->getText().substr( 4 ).trim().toUtf8() ) );
if ( mLocateTable->getModel()->rowCount() > 0 && nullptr != mApp->getProjectBuildManager() ) {
ModelIndex idx =
mLocateTable->findRowWithText( mApp->getProjectBuildManager()->getConfig().runName );
mLocateTable->getSelection().set( idx.isValid() ? idx
: mLocateTable->getModel()->index( 0 ) );
}
mLocateTable->scrollToTop();
mLocateTable->setColumnsVisible( { 0 } );
}
void UniversalLocator::showSwitchFileType() {
showBar();
if ( mLocateInput->getText().empty() || !String::startsWith( mLocateInput->getText(), "ft " ) )
mLocateInput->setText( "ft " );
if ( mLocateInput->getText().size() >= 3 )
updateSwitchFileTypeTable();
updateLocateBar();
mApp->getStatusBar()->updateState();
}
std::shared_ptr<ItemListOwnerModel<std::string>>
UniversalLocator::openFileTypeModel( const std::string& match ) {
if ( nullptr == mApp->getSplitter()->getCurEditor() )
return ItemListOwnerModel<std::string>::create( {} );
const auto& defs = SyntaxDefinitionManager::instance()->getDefinitions();
std::vector<std::string> fileTypeNames;
fileTypeNames.reserve( defs.size() );
for ( const auto& def : defs ) {
if ( match.empty() || String::startsWith( String::toLower( def.getLanguageName() ),
String::toLower( match ) ) )
fileTypeNames.push_back( def.getLanguageName() );
}
std::sort( fileTypeNames.begin(), fileTypeNames.end() );
return ItemListOwnerModel<std::string>::create( fileTypeNames );
}
void UniversalLocator::updateSwitchFileTypeTable() {
mLocateTable->setModel(
openFileTypeModel( mLocateInput->getText().substr( 3 ).trim().toUtf8() ) );
if ( mLocateTable->getModel()->rowCount() > 0 && mApp->getSplitter()->getCurEditor() ) {
ModelIndex idx = mLocateTable->findRowWithText( mApp->getSplitter()
->getCurEditor()
->getDocumentRef()
->getSyntaxDefinition()
.getLanguageName() );
mLocateTable->setSelection( idx.isValid() ? idx : mLocateTable->getModel()->index( 0 ) );
} else {
mLocateTable->scrollToTop();
}
mLocateTable->setColumnsVisible( { 0 } );
}
void UniversalLocator::onCodeEditorFocusChange( UICodeEditor* editor ) {
if ( !mLocateTable || !mLocateTable->isVisible() )
return;
if ( String::startsWith( mLocateInput->getText(), ". " ) &&
editor->getDocument().getURI().toString() != mCurDocURI )
showDocumentSymbol();
}
std::shared_ptr<LSPSymbolInfoModel> UniversalLocator::emptyModel( const String& defTxt,
const std::string& query ) {
LSPSymbolInformation info;
info.name = defTxt.toUtf8();
info.url = "";
return LSPSymbolInfoModel::create( mUISceneNode, query, { info } );
}
bool UniversalLocator::findCapability( PluginCapability capability ) {
json capa;
capa["capability"] = capability;
capa["uri"] = getCurDocURI();
PluginRequestHandle resp = mApp->getPluginManager()->sendRequest(
PluginMessageType::QueryPluginCapability, PluginMessageFormat::JSON, &capa );
if ( resp.isResponse() && resp.getResponse().data.is_boolean() )
return resp.getResponse().data.get<bool>();
return false;
}
String UniversalLocator::getDefQueryText( PluginCapability capability ) {
bool hasCapability = findCapability( capability );
if ( !hasCapability )
return mUISceneNode->i18n( "no_running_lsp_server", "No running LSP server" );
return mUISceneNode->i18n( "insert_search_query", "Insert search query" );
}
nlohmann::json UniversalLocator::pluginID( const PluginIDType& id ) {
json r;
r["uri"] = getCurDocURI();
if ( id.isInteger() )
r["id"] = id.asInt();
else
r["id"] = id.asString();
return r;
}
void UniversalLocator::requestWorkspaceSymbol() {
if ( mLocateInput->getText().empty() )
return;
auto txt( mLocateInput->getText().substr( 1 ).trim() );
if ( mWorkspaceSymbolQuery != txt.toUtf8() || mWorkspaceSymbolQuery.empty() ) {
mWorkspaceSymbolQuery = txt.toUtf8();
if ( !mWorkspaceSymbolModel ) {
auto defTxt = getDefQueryText( PluginCapability::WorkspaceSymbol );
mWorkspaceSymbolModel = emptyModel( defTxt, mWorkspaceSymbolQuery );
}
mLocateTable->setModel( mWorkspaceSymbolModel );
if ( mQueryWorkspaceLastId.isValid() ) {
json r( pluginID( mQueryWorkspaceLastId ) );
mApp->getPluginManager()->sendBroadcast( PluginMessageType::CancelRequest,
PluginMessageFormat::JSON, &r );
}
mApp->getThreadPool()->run( [this] {
json j;
j["query"] = mWorkspaceSymbolQuery;
auto hdl = mApp->getPluginManager()->sendRequest( PluginMessageType::WorkspaceSymbol,
PluginMessageFormat::JSON, &j );
mQueryWorkspaceLastId = hdl.id();
} );
} else if ( mWorkspaceSymbolModel && mWorkspaceSymbolModel.get() != mLocateTable->getModel() ) {
mLocateTable->setModel( mWorkspaceSymbolModel );
}
}
void UniversalLocator::updateWorkspaceSymbol( const LSPSymbolInformationList& res ) {
mUISceneNode->runOnMainThread( [this, res] {
if ( !mWorkspaceSymbolModel ) {
mWorkspaceSymbolModel =
LSPSymbolInfoModel::create( mApp->getUISceneNode(), mWorkspaceSymbolQuery, res );
} else {
mWorkspaceSymbolModel->setQuery( mWorkspaceSymbolQuery );
mWorkspaceSymbolModel->append( res );
}
mLocateTable->setModel( mWorkspaceSymbolModel );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
mLocateTable->scrollToTop();
} );
}
void UniversalLocator::requestDocumentSymbol() {
if ( mLocateInput->getText().empty() )
return;
auto txt( mLocateInput->getText().substr( 1 ).trim() );
bool needsUpdate = false;
if ( txt != mCurDocQuery || getCurDocURI() != mCurDocURI ) {
mCurDocQuery = txt;
mCurDocURI = getCurDocURI();
needsUpdate = true;
}
mLocateTable->setModel( mTextDocumentSymbolModel );
if ( mSplitter->curEditorIsNotNull() ) {
if ( needsUpdate ) {
mTextDocumentSymbolModel =
emptyModel( getDefQueryText( PluginCapability::TextDocumentSymbol ) );
mLocateTable->setModel( mTextDocumentSymbolModel );
json j;
j["uri"] = mCurDocURI = getCurDocURI();
auto hdl = mApp->getPluginManager()->sendRequest(
PluginMessageType::TextDocumentFlattenSymbol, PluginMessageFormat::JSON, &j );
} else {
if ( !mTextDocumentSymbolModel ) {
mTextDocumentSymbolModel =
emptyModel( getDefQueryText( PluginCapability::TextDocumentSymbol ) );
}
mLocateTable->setModel( mTextDocumentSymbolModel );
}
} else {
mTextDocumentSymbolModel =
emptyModel( mUISceneNode->i18n( "no_open_document", "No open document" ) );
mLocateTable->setModel( mTextDocumentSymbolModel );
}
}
void UniversalLocator::updateDocumentSymbol( const LSPSymbolInformationList& res ) {
#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ )
if ( mCurDocQuery.empty() ) {
mTextDocumentSymbolModel =
LSPSymbolInfoModel::create( mApp->getUISceneNode(), mCurDocQuery, res, true );
mLocateTable->setModel( mTextDocumentSymbolModel );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
mLocateTable->scrollToTop();
} 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();
} );
} );
}
#else
mUISceneNode->runOnMainThread( [this, res] {
if ( mCurDocQuery.empty() ) {
mTextDocumentSymbolModel =
LSPSymbolInfoModel::create( mApp->getUISceneNode(), mCurDocQuery, res, true );
} else {
mTextDocumentSymbolModel = LSPSymbolInfoModel::create(
mApp->getUISceneNode(), mCurDocQuery,
fuzzyMatchTextDocumentSymbol( res, mCurDocQuery, 100 ), true );
}
mLocateTable->setModel( mTextDocumentSymbolModel );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
mLocateTable->scrollToTop();
} );
#endif
}
std::string UniversalLocator::getCurDocURI() {
return mSplitter->curEditorIsNotNull()
? mSplitter->getCurEditor()->getDocument().getURI().toString()
: "";
}
PluginRequestHandle UniversalLocator::processResponse( const PluginMessage& msg ) {
if ( msg.isResponse() && msg.type == PluginMessageType::WorkspaceSymbol &&
msg.format == PluginMessageFormat::SymbolInformation ) {
mQueryWorkspaceLastId = PluginIDType();
updateWorkspaceSymbol( msg.asSymbolInformation() );
} else if ( msg.isResponse() && msg.type == PluginMessageType::TextDocumentFlattenSymbol &&
msg.format == PluginMessageFormat::SymbolInformation ) {
if ( String::startsWith( mLocateInput->getText(), ". " ) && msg.responseID.isString() &&
mCurDocURI == msg.responseID.asString() )
updateDocumentSymbol( msg.asSymbolInformation() );
}
return {};
}
void UniversalLocator::asyncFuzzyMatchTextDocumentSymbol(
const LSPSymbolInformationList& list, const std::string& query, const size_t& limit,
std::function<void( std::shared_ptr<LSPSymbolInfoModel> )> cb ) {
mApp->getThreadPool()->run( [this, list, query, limit, cb] {
cb( LSPSymbolInfoModel::create(
mUISceneNode, query, fuzzyMatchTextDocumentSymbol( list, query, limit ), true ) );
} );
}
std::vector<ProjectDirectoryTree::CommandInfo> UniversalLocator::getLocatorCommands() const {
std::vector<ProjectDirectoryTree::CommandInfo> vec;
UIIcon* icon = mUISceneNode->findIcon( "chevron-right" );
bool isOpenFolder = !mApp->getCurrentProject().empty();
for ( const auto& locator : mLocatorProviders ) {
if ( !isOpenFolder && locator.projectNeeded )
continue;
vec.push_back( { locator.symbolTrigger, locator.description, icon } );
}
return vec;
}
} // namespace ecode