diff --git a/bin/assets/fonts/codicon.ttf b/bin/assets/fonts/codicon.ttf index bab11139a..3d1dc3d86 100644 Binary files a/bin/assets/fonts/codicon.ttf and b/bin/assets/fonts/codicon.ttf differ diff --git a/bin/assets/fonts/nonicons.ttf b/bin/assets/fonts/nonicons.ttf index 7750e2228..cd5efdb8a 100644 Binary files a/bin/assets/fonts/nonicons.ttf and b/bin/assets/fonts/nonicons.ttf differ diff --git a/bin/assets/ui/breeze.css b/bin/assets/ui/breeze.css index 1b8489c9c..4eecec5ee 100644 --- a/bin/assets/ui/breeze.css +++ b/bin/assets/ui/breeze.css @@ -43,9 +43,9 @@ --highlight-primary: #FFFFFF7A; --disabled-color: #727679; --disabled-border: #43474c; - --theme-error: #cc0000; - --theme-warning: #cccc00; - --theme-success: #00cc00; + --theme-error: #ff4040; + --theme-warning: #ffff40; + --theme-success: #40ff40; droppable-hovering-color: #FFFFFF20; } @@ -771,6 +771,7 @@ Menu::RadioButton::shortcut { color: var(--menu-font); } +PushButton:disabled > PushButton::icon, Menu::Item:disabled > Menu::Item::text, Menu::Item:disabled > Menu::Item::shortcut, Menu::Item:disabled > Menu::Item::icon, diff --git a/include/eepp/system/fileinfo.hpp b/include/eepp/system/fileinfo.hpp index a49e2f5e8..56d7a514c 100644 --- a/include/eepp/system/fileinfo.hpp +++ b/include/eepp/system/fileinfo.hpp @@ -48,6 +48,8 @@ class EE_API FileInfo { std::string linksTo() const; + std::string getRealPath() const; + bool exists() const; void getInfo(); diff --git a/include/eepp/ui/uiwidgetcreator.hpp b/include/eepp/ui/uiwidgetcreator.hpp index 615d69ef7..944a11476 100644 --- a/include/eepp/ui/uiwidgetcreator.hpp +++ b/include/eepp/ui/uiwidgetcreator.hpp @@ -11,8 +11,9 @@ class EE_API UIWidgetCreator { typedef std::function CustomWidgetCb; typedef std::function RegisterWidgetCb; - typedef std::map WidgetCallbackMap; - typedef std::map RegisteredWidgetCallbackMap; + typedef std::unordered_map WidgetCallbackMap; + typedef std::unordered_map + RegisteredWidgetCallbackMap; static UIWidget* createFromName( std::string widgetName ); diff --git a/src/eepp/system/fileinfo.cpp b/src/eepp/system/fileinfo.cpp index 71cb611a5..3b3393f24 100644 --- a/src/eepp/system/fileinfo.cpp +++ b/src/eepp/system/fileinfo.cpp @@ -243,21 +243,16 @@ bool FileInfo::linksToDirectory() const { std::string FileInfo::linksTo() const { #if EE_PLATFORM != EE_PLATFORM_WIN - if ( isLink() ) { - char* ch = realpath( mFilepath.c_str(), NULL ); - - if ( NULL != ch ) { - std::string tstr( ch ); - - free( ch ); - - return tstr; - } - } + if ( isLink() ) + return getRealPath(); #endif return std::string( "" ); } +std::string FileInfo::getRealPath() const { + return FileSystem::getRealPath( getFilepath() ); +} + bool FileInfo::exists() const { if ( isDirectory() ) FileSystem::dirRemoveSlashAtEnd( mFilepath ); diff --git a/src/eepp/ui/abstract/uiabstracttableview.cpp b/src/eepp/ui/abstract/uiabstracttableview.cpp index e37a0c593..adcd3fd80 100644 --- a/src/eepp/ui/abstract/uiabstracttableview.cpp +++ b/src/eepp/ui/abstract/uiabstracttableview.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace EE { namespace UI { namespace Abstract { @@ -421,7 +422,12 @@ UITableRow* UIAbstractTableView::createRow() { rowWidget->addEventListener( Event::MouseDown, [&]( const Event* event ) { if ( !( event->asMouseEvent()->getFlags() & EE_BUTTON_LMASK ) || !isRowSelection() ) return; - getSelection().set( event->getNode()->asType()->getCurIndex() ); + auto index = event->getNode()->asType()->getCurIndex(); + if ( getUISceneNode()->getWindow()->getInput()->isControlPressed() ) { + getSelection().remove( index ); + } else { + getSelection().set( index ); + } } ); return rowWidget; } @@ -478,7 +484,11 @@ void UIAbstractTableView::bindNavigationClick( UIWidget* widget ) { onOpenModelIndex( idx, event ); } else if ( isCellSelection() && ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) ) { auto cellIdx = mouseEvent->getNode()->asType()->getCurIndex(); - getSelection().set( cellIdx ); + if ( getUISceneNode()->getWindow()->getInput()->isControlPressed() ) { + getSelection().remove( cellIdx ); + } else { + getSelection().set( cellIdx ); + } } } ) ); } diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index e1fad6f6c..7a41b77c4 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -3483,12 +3483,30 @@ TableView#locate_bar_table > tableview::row:selected > tableview::cell:nth-child .theme-none > treeview::cell::text, .theme-none > listview::cell::text, .none { - color: #b26818; + color: #d48838; } Anchor.success:hover, Anchor.error:hover { color: var(--primary); } +.status_build_output_cont > SelectButton { + font-size: 11dp; + padding: 2dp 8dp 2dp 8dp; + border-radius: 0dp; + border-width: 1dp; + border-color: transparent; + transition: all 0.2; +} +.status_build_output_cont > SelectButton:hover { + border-width: 1dp; + border-color: var(--primary); +} +#build_output_issues TableView::cell.theme-warning > TableView::cell::icon { + tint: var(--theme-warning); +} +#build_output_issues TableView::cell.theme-error > TableView::cell::icon { + tint: var(--theme-error); +} @@ -3559,17 +3577,17 @@ Anchor.error:hover { - + - - - - + + + + - + @@ -3583,86 +3601,80 @@ Anchor.error:hover { - + - + )html"; UIIconTheme* iconTheme = UIIconTheme::New( "ecode" ); mMenuIconSize = mConfig.ui.fontSize.asPixels( 0, Sizef(), mDisplayDPI ); - std::unordered_map icons = { - { "document-new", 0xecc3 }, - { "document-open", 0xed70 }, - { "document-save", 0xf0b3 }, - { "document-save-as", 0xf0b3 }, - { "document-close", 0xeb99 }, - { "quit", 0xeb97 }, - { "undo", 0xea58 }, - { "redo", 0xea5a }, - { "cut", 0xf0c1 }, - { "copy", 0xecd5 }, - { "paste", 0xeb91 }, - { "edit", 0xec86 }, - { "split-horizontal", 0xf17a }, - { "split-vertical", 0xf17b }, - { "find-replace", 0xed2b }, - // { "folder", 0xed54 }, - // { "folder-open", 0xed70 }, - { "folder-add", 0xed5a }, - // { "file", 0xecc3 }, - { "file-add", 0xecc9 }, - { "file-copy", 0xecd3 }, - { "file-code", 0xecd1 }, - { "file-edit", 0xecdb }, - { "font-size", 0xed8d }, - { "delete-bin", 0xec1e }, - { "delete-text", 0xec1e }, - { "zoom-in", 0xf2db }, - { "zoom-out", 0xf2dd }, - { "zoom-reset", 0xeb47 }, - { "fullscreen", 0xed9c }, - { "keybindings", 0xee75 }, - // { "tree-expanded", 0xea50 }, - // { "tree-contracted", 0xea54 }, - { "search", 0xf0d1 }, - { "go-up", 0xea78 }, - { "ok", 0xeb7a }, - { "cancel", 0xeb98 }, - { "color-picker", 0xf13d }, - { "pixel-density", 0xed8c }, - { "go-to-line", 0xf1f8 }, - { "table-view", 0xf1de }, - { "list-view", 0xecf1 }, - { "menu-unfold", 0xef40 }, - { "menu-fold", 0xef3d }, - { "download-cloud", 0xec58 }, - { "layout-left", 0xee94 }, - { "layout-right", 0xee9b }, - { "color-scheme", 0xebd4 }, - { "global-settings", 0xedcf }, - { "folder-user", 0xed84 }, - { "help", 0xf045 }, - { "terminal", 0xf1f6 }, - { "earth", 0xec7a }, - { "arrow-down", 0xea4c }, - { "arrow-up", 0xea76 }, - { "arrow-down-s", 0xea4e }, - { "arrow-up-s", 0xea78 }, - { "arrow-right-s", 0xea6e }, - { "match-case", 0xed8d }, - { "palette", 0xefc5 }, - { "file-code", 0xecd1 }, - { "cursor-pointer", 0xec09 }, - { "drive", 0xedf8 }, - { "refresh", 0xf064 }, - { "hearth-pulse", 0xee10 }, - { "add", 0xea12 }, - { "hammer", 0xedee }, - { "eraser", 0xec9e } }; + std::unordered_map icons = { { "document-new", 0xecc3 }, + { "document-open", 0xed70 }, + { "document-save", 0xf0b3 }, + { "document-save-as", 0xf0b3 }, + { "document-close", 0xeb99 }, + { "quit", 0xeb97 }, + { "undo", 0xea58 }, + { "redo", 0xea5a }, + { "cut", 0xf0c1 }, + { "copy", 0xecd5 }, + { "paste", 0xeb91 }, + { "edit", 0xec86 }, + { "split-horizontal", 0xf17a }, + { "split-vertical", 0xf17b }, + { "find-replace", 0xed2b }, + { "folder-add", 0xed5a }, + { "file-add", 0xecc9 }, + { "file-copy", 0xecd3 }, + { "file-code", 0xecd1 }, + { "file-edit", 0xecdb }, + { "font-size", 0xed8d }, + { "delete-bin", 0xec1e }, + { "delete-text", 0xec1e }, + { "zoom-in", 0xf2db }, + { "zoom-out", 0xf2dd }, + { "zoom-reset", 0xeb47 }, + { "fullscreen", 0xed9c }, + { "keybindings", 0xee75 }, + { "search", 0xf0d1 }, + { "go-up", 0xea78 }, + { "ok", 0xeb7a }, + { "cancel", 0xeb98 }, + { "color-picker", 0xf13d }, + { "pixel-density", 0xed8c }, + { "go-to-line", 0xf1f8 }, + { "table-view", 0xf1de }, + { "list-view", 0xecf1 }, + { "menu-unfold", 0xef40 }, + { "menu-fold", 0xef3d }, + { "download-cloud", 0xec58 }, + { "layout-left", 0xee94 }, + { "layout-right", 0xee9b }, + { "color-scheme", 0xebd4 }, + { "global-settings", 0xedcf }, + { "folder-user", 0xed84 }, + { "help", 0xf045 }, + { "terminal", 0xf1f6 }, + { "earth", 0xec7a }, + { "arrow-down", 0xea4c }, + { "arrow-up", 0xea76 }, + { "arrow-down-s", 0xea4e }, + { "arrow-up-s", 0xea78 }, + { "arrow-right-s", 0xea6e }, + { "match-case", 0xed8d }, + { "palette", 0xefc5 }, + { "file-code", 0xecd1 }, + { "cursor-pointer", 0xec09 }, + { "drive", 0xedf8 }, + { "refresh", 0xf064 }, + { "hearth-pulse", 0xee10 }, + { "add", 0xea12 }, + { "hammer", 0xedee }, + { "eraser", 0xec9e } }; for ( const auto& icon : icons ) iconTheme->add( UIGlyphIcon::New( icon.first, iconFont, icon.second ) ); @@ -3783,7 +3795,9 @@ Anchor.error:hover { { "chevron-right", 0xeab6 }, { "lightbulb-autofix", 0xeb13 }, { "layout-sidebar-left-off", 0xec02 }, - { "layout-sidebar-left", 0xebf3 } }; + { "layout-sidebar-left", 0xebf3 }, + { "warning", 0xea6c }, + { "error", 0xea87 } }; for ( const auto& icon : codIcons ) iconTheme->add( UIGlyphIcon::New( icon.first, codIconFont, icon.second ) ); @@ -3815,6 +3829,7 @@ Anchor.error:hover { UIWidgetCreator::registerWidget( "globalsearchbar", UIGlobalSearchBar::New ); UIWidgetCreator::registerWidget( "mainlayout", UIMainLayout::New ); UIWidgetCreator::registerWidget( "statusbar", UIStatusBar::New ); + UIWidgetCreator::registerWidget( "rellayce", UIRelativeLayoutCommandExecuter::New ); mUISceneNode->loadLayoutFromString( baseUI ); mUISceneNode->bind( "main_layout", mMainLayout ); mUISceneNode->bind( "code_container", mBaseLayout ); diff --git a/src/tools/ecode/featureshealth.cpp b/src/tools/ecode/featureshealth.cpp index 6854190d2..40ba1e27c 100644 --- a/src/tools/ecode/featureshealth.cpp +++ b/src/tools/ecode/featureshealth.cpp @@ -373,15 +373,18 @@ void FeaturesHealth::displayHealth( PluginManager* pluginManager, UISceneNode* s table->setFitAllColumnsToWidget( true ); table->setModel( model ); auto healthLangInfo = win->find( "health-lang-info" ); - table->setOnSelection( [table, healthLangInfo, sceneNode]( const ModelIndex& index ) { + table->setOnSelectionChange( [table, healthLangInfo, sceneNode]() { + if ( table->getSelection().isEmpty() || nullptr == table->getModel() ) { + healthLangInfo->setVisible( false ); + return; + } + ModelIndex index = table->getSelection().first(); static const std::string none = sceneNode->i18n( "none", "None" ); static const std::string notFound = sceneNode->i18n( "not_found_in_path", "Not found in $PATH" ); static const std::string patherr = String::format( "%s: %s", I18N( "path_is", "PATH is" ), std::getenv( "PATH" ) ); - if ( nullptr == table->getModel() ) - return; HealthModel* model = static_cast( table->getModel() ); const auto& lang = model->getHealthRow( index.row() ); healthLangInfo->childsCloseAll(); diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp index f9b423089..5482bddd4 100644 --- a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp @@ -285,25 +285,25 @@ void LSPDocumentClient::highlight() { start = deltaStart; } - auto& line = tokenizerLines[currentLine]; + auto* line = &tokenizerLines[currentLine]; if ( type >= 0 && type < (int)caps.legend.tokenTypes.size() ) { const auto& ltype = caps.legend.tokenTypes[type]; - line.tokens.push_back( + line->tokens.push_back( { semanticTokenTypeToSyntaxType( ltype, mDoc->getSyntaxDefinition() ), start, len } ); } else { - line.tokens.push_back( { "normal", start, len } ); + line->tokens.push_back( { "normal", start, len } ); } - line.hash = mDoc->line( currentLine ).getHash(); - line.updateSignature(); + line->hash = mDoc->line( currentLine ).getHash(); + line->updateSignature(); auto curSignature = mDoc->getHighlighter()->getTokenizedLineSignature( lastLine ); - if ( lastLinePtr && lastLinePtr->signature == curSignature ) { + if ( lastLine != currentLine && lastLinePtr && lastLinePtr->signature == curSignature ) { tokenizerLines.erase( lastLine ); } lastLine = currentLine; - lastLinePtr = &line; + lastLinePtr = line; } diff = clock.getElapsedTime(); diff --git a/src/tools/ecode/projectbuild.cpp b/src/tools/ecode/projectbuild.cpp index 9b3ef52b5..a6a4cf491 100644 --- a/src/tools/ecode/projectbuild.cpp +++ b/src/tools/ecode/projectbuild.cpp @@ -57,6 +57,7 @@ static void replaceVar( ProjectBuildStep& s, const std::string& var, const std:: String::replaceAll( s.cmd, var, val ); String::replaceAll( s.args, var, val ); String::replaceAll( s.workingDir, slashDup, FileSystem::getOSSlash() ); + FileSystem::dirAddSlashAtEnd( s.workingDir ); } ProjectBuildSteps ProjectBuild::replaceVars( const ProjectBuildSteps& steps ) const { @@ -686,27 +687,33 @@ void ProjectBuildManager::runBuild( const std::string& buildName, const std::str auto printElapsed = [&clock, &i18n, &progressFn]() { if ( progressFn ) { progressFn( - 100, Sys::getDateTimeStr() + ": " + - String::format( - i18n( "build_elapsed_time", "Elapsed Time: %s.\n" ).toUtf8().c_str(), - clock.getElapsedTime().toString().c_str() ) ); + 100, + Sys::getDateTimeStr() + ": " + + String::format( + i18n( "build_elapsed_time", "Elapsed Time: %s.\n" ).toUtf8().c_str(), + clock.getElapsedTime().toString().c_str() ), + nullptr ); } }; if ( progressFn ) { - progressFn( 0, Sys::getDateTimeStr() + ": " + - String::format( i18n( "running_steps_for_project", - "Running steps for project %s...\n" ) - .toUtf8() - .c_str(), - buildName.c_str() ) ); + progressFn( 0, + Sys::getDateTimeStr() + ": " + + String::format( + i18n( "running_steps_for_project", "Running steps for project %s...\n" ) + .toUtf8() + .c_str(), + buildName.c_str() ), + nullptr ); if ( !buildType.empty() ) progressFn( - 0, Sys::getDateTimeStr() + ": " + - String::format( - i18n( "using_build_type", "Using build type: %s.\n" ).toUtf8().c_str(), - buildType.c_str() ) ); + 0, + Sys::getDateTimeStr() + ": " + + String::format( + i18n( "using_build_type", "Using build type: %s.\n" ).toUtf8().c_str(), + buildType.c_str() ), + nullptr ); } int c = 0; @@ -742,7 +749,8 @@ void ProjectBuildManager::runBuild( const std::string& buildName, const std::str Sys::getDateTimeStr() + ": " + String::format( i18n( "starting_process", "Starting %s %s\n" ).toUtf8().c_str(), - cmd.cmd.c_str(), cmd.args.c_str() ) ); + cmd.cmd.c_str(), cmd.args.c_str() ), + nullptr ); std::string buffer( 1024, '\0' ); unsigned bytesRead = 0; @@ -751,7 +759,7 @@ void ProjectBuildManager::runBuild( const std::string& buildName, const std::str bytesRead = mProcess->readStdOut( buffer ); std::string data( buffer.substr( 0, bytesRead ) ); if ( progressFn ) - progressFn( progress, std::move( data ) ); + progressFn( progress, std::move( data ), &cmd ); } while ( bytesRead != 0 && mProcess->isAlive() && !mShuttingDown && !mCancelBuild ); if ( mShuttingDown || mCancelBuild ) { @@ -759,7 +767,7 @@ void ProjectBuildManager::runBuild( const std::string& buildName, const std::str mCancelBuild = false; printElapsed(); if ( doneFn ) - doneFn( EXIT_FAILURE ); + doneFn( EXIT_FAILURE, &cmd ); return; } @@ -773,11 +781,12 @@ void ProjectBuildManager::runBuild( const std::string& buildName, const std::str "The process \"%s\" exited with errors.\n" ) .toUtf8() .c_str(), - cmd.cmd.c_str() ) ); + cmd.cmd.c_str() ), + nullptr ); } printElapsed(); if ( doneFn ) - doneFn( returnCode ); + doneFn( returnCode, &cmd ); return; } else { if ( progressFn ) { @@ -786,22 +795,24 @@ void ProjectBuildManager::runBuild( const std::string& buildName, const std::str "The process \"%s\" exited normally.\n" ) .toUtf8() .c_str(), - cmd.cmd.c_str() ) ); + cmd.cmd.c_str() ), + nullptr ); } } } else { printElapsed(); if ( doneFn ) - doneFn( EXIT_FAILURE ); + doneFn( EXIT_FAILURE, nullptr ); return; } c++; + if ( c == (int)res.cmds.size() ) { + printElapsed(); + if ( doneFn ) + doneFn( EXIT_SUCCESS, &cmd ); + } } - - printElapsed(); - if ( doneFn ) - doneFn( EXIT_SUCCESS ); } void ProjectBuildManager::buildSidePanelTab() { diff --git a/src/tools/ecode/projectbuild.hpp b/src/tools/ecode/projectbuild.hpp index abe52794d..d2b66e1a0 100644 --- a/src/tools/ecode/projectbuild.hpp +++ b/src/tools/ecode/projectbuild.hpp @@ -95,6 +95,13 @@ struct ProjectBuildConfig { enum class ProjectOutputParserTypes { Error = 0, Warning = 1, Notice = 2 }; +struct PatternOrder { + int file{ 1 }; + int line{ 2 }; + int col{ 3 }; + int message{ 4 }; +}; + struct ProjectBuildOutputParserConfig { static std::string typeToString( ProjectOutputParserTypes type ) { switch ( type ) { @@ -110,12 +117,7 @@ struct ProjectBuildOutputParserConfig { ProjectOutputParserTypes type; std::string pattern; - struct { - int file{ 1 }; - int line{ 2 }; - int col{ 3 }; - int message{ 4 }; - } patternOrder; + PatternOrder patternOrder; }; class ProjectBuildOutputParser { @@ -223,8 +225,9 @@ struct ProjectBuildCommandsRes { bool isValid() { return errorMsg.empty(); } }; -using ProjectBuildProgressFn = std::function; -using ProjectBuildDoneFn = std::function; +using ProjectBuildProgressFn = + std::function; +using ProjectBuildDoneFn = std::function; using ProjectBuildi18nFn = std::function; diff --git a/src/tools/ecode/statusbuildoutputcontroller.cpp b/src/tools/ecode/statusbuildoutputcontroller.cpp index e590c0902..d3eb43eda 100644 --- a/src/tools/ecode/statusbuildoutputcontroller.cpp +++ b/src/tools/ecode/statusbuildoutputcontroller.cpp @@ -1,5 +1,6 @@ #include "statusbuildoutputcontroller.hpp" #include "ecode.hpp" +#include "widgetcommandexecuter.hpp" namespace ecode { @@ -40,9 +41,7 @@ void StatusBuildOutputController::hide() { void StatusBuildOutputController::show() { if ( nullptr == mContainer ) { mMainSplitter->updateLayout(); - mContainer = createContainer(); - mContainer->setId( "build_output" ); - mContainer->setVisible( false ); + createContainer(); } if ( !mContainer->isVisible() ) { @@ -55,7 +54,7 @@ void StatusBuildOutputController::show() { } mContainer->setParent( mMainSplitter ); mContainer->setVisible( true ); - mContainer->setFocus(); + mContainer->getFirstChild()->setFocus(); mApp->getStatusBar()->updateState(); } } @@ -90,6 +89,53 @@ UIPushButton* StatusBuildOutputController::getCleanButton( App* app ) { return nullptr; } +bool StatusBuildOutputController::searchFindAndAddStatusResult( + const std::vector& patterns, const std::string& text, + const ProjectBuildCommand* cmd ) { + LuaPattern::Range matches[12]; + for ( const auto& pattern : patterns ) { + if ( pattern.pattern.matches( text, matches ) ) { + StatusMessage status; + status.type = pattern.config.type; + + for ( int i = 0; i < (int)pattern.pattern.getNumMatches(); ++i ) { + if ( !matches[i].isValid() ) + break; + + if ( i == 0 ) { + status.output = text; + continue; + } + + std::string subtxt = text.substr( matches[i].start, matches[i].end ); + if ( pattern.config.patternOrder.message == i ) { + auto nl = subtxt.find_first_of( '\n' ); + if ( nl == std::string::npos ) { + status.message = std::move( subtxt ); + } else { + status.message = subtxt.substr( 0, nl ); + } + } else if ( pattern.config.patternOrder.file == i ) { + status.file = FileSystem::getRealPath( cmd->workingDir + subtxt ); + status.fileName = FileSystem::fileNameFromPath( status.file ); + } else if ( pattern.config.patternOrder.line == i ) { + int l; + if ( String::fromString( l, subtxt ) ) + status.line = l; + } else if ( pattern.config.patternOrder.col == i ) { + int c; + if ( String::fromString( c, subtxt ) ) + status.col = c; + } + } + + mStatusResults.emplace_back( status ); + return true; + } + } + return false; +} + void StatusBuildOutputController::runBuild( const std::string& buildName, const std::string& buildType, const ProjectBuildOutputParser& outputParser ) { @@ -99,17 +145,27 @@ void StatusBuildOutputController::runBuild( const std::string& buildName, auto pbm = mApp->getProjectBuildManager(); show(); + showBuildOutput(); - mContainer->getDocument().reset(); - mContainer->setScrollY( mContainer->getMaxScroll().y ); + mStatusResults.clear(); + if ( mTableIssues ) + mTableIssues->getSelection().clear(); + mBuildOutput->getDocument().reset(); + mBuildOutput->setScrollY( mBuildOutput->getMaxScroll().y ); std::vector patterns; + mPatternHolder.clear(); + mCurLineBuffer.clear(); + auto configs = { outputParser.getPresetConfig(), outputParser.getConfig() }; for ( const auto& config : configs ) { for ( const auto& parser : config ) { + mPatternHolder.push_back( { LuaPattern( parser.pattern ), parser } ); + SyntaxPattern ptn( { parser.pattern }, getProjectOutputParserTypeToString( parser.type ) ); + patterns.emplace_back( std::move( ptn ) ); } } @@ -123,9 +179,9 @@ void StatusBuildOutputController::runBuild( const std::string& buildName, SyntaxDefinition synDef( "custom_build", {}, patterns ); - mContainer->getDocument().setSyntaxDefinition( synDef ); - mContainer->getVScrollBar()->setValue( 1.f ); - mContainer->getDocument().getHighlighter()->setMaxTokenizationLength( 2048 ); + mBuildOutput->getDocument().setSyntaxDefinition( synDef ); + mBuildOutput->getVScrollBar()->setValue( 1.f ); + mBuildOutput->getDocument().getHighlighter()->setMaxTokenizationLength( 2048 ); UIPushButton* buildButton = getBuildButton( mApp ); if ( buildButton ) @@ -140,15 +196,33 @@ void StatusBuildOutputController::runBuild( const std::string& buildName, auto res = pbm->build( buildName, [this]( const auto& key, const auto& def ) { return mApp->i18n( key, def ); }, buildType, - [this]( auto, auto buffer ) { - mContainer->runOnMainThread( [this, buffer]() { - bool scrollToBottom = mContainer->getVScrollBar()->getValue() == 1.f; - mContainer->getDocument().textInput( buffer ); + [this]( auto, std::string buffer, const ProjectBuildCommand* cmd ) { + mBuildOutput->runOnMainThread( [this, buffer]() { + bool scrollToBottom = mBuildOutput->getVScrollBar()->getValue() == 1.f; + mBuildOutput->getDocument().textInput( buffer ); if ( scrollToBottom ) - mContainer->setScrollY( mContainer->getMaxScroll().y ); + mBuildOutput->setScrollY( mBuildOutput->getMaxScroll().y ); } ); + + if ( nullptr == cmd ) + return; + + do { + auto nl = buffer.find_first_of( '\n' ); + if ( nl != std::string::npos ) { + mCurLineBuffer += buffer.substr( 0, nl ); + searchFindAndAddStatusResult( mPatternHolder, mCurLineBuffer, cmd ); + buffer = buffer.substr( nl + 1 ); + mCurLineBuffer.clear(); + } else { + mCurLineBuffer += buffer; + buffer.clear(); + } + } while ( !buffer.empty() ); }, - [this, enableCleanButton]( auto exitCode ) { + [this, enableCleanButton]( auto exitCode, const ProjectBuildCommand* cmd ) { + if ( !mCurLineBuffer.empty() && nullptr != cmd ) + searchFindAndAddStatusResult( mPatternHolder, mCurLineBuffer, cmd ); String buffer; if ( EXIT_SUCCESS == exitCode ) { @@ -159,11 +233,11 @@ void StatusBuildOutputController::runBuild( const std::string& buildName, mApp->i18n( "build_failed", "Build run with errors\n" ); } - mContainer->runOnMainThread( [this, buffer]() { - bool scrollToBottom = mContainer->getVScrollBar()->getValue() == 1.f; - mContainer->getDocument().textInput( buffer ); + mBuildOutput->runOnMainThread( [this, buffer]() { + bool scrollToBottom = mBuildOutput->getVScrollBar()->getValue() == 1.f; + mBuildOutput->getDocument().textInput( buffer ); if ( scrollToBottom ) - mContainer->setScrollY( mContainer->getMaxScroll().y ); + mBuildOutput->setScrollY( mBuildOutput->getMaxScroll().y ); } ); UIPushButton* buildButton = getBuildButton( mApp ); @@ -192,8 +266,8 @@ void StatusBuildOutputController::runClean( const std::string& buildName, show(); - mContainer->getDocument().reset(); - mContainer->setScrollY( mContainer->getMaxScroll().y ); + mBuildOutput->getDocument().reset(); + mBuildOutput->setScrollY( mBuildOutput->getMaxScroll().y ); std::vector patterns; @@ -211,8 +285,8 @@ void StatusBuildOutputController::runClean( const std::string& buildName, SyntaxDefinition synDef( "custom_build", {}, patterns ); - mContainer->getDocument().setSyntaxDefinition( synDef ); - mContainer->getVScrollBar()->setValue( 1.f ); + mBuildOutput->getDocument().setSyntaxDefinition( synDef ); + mBuildOutput->getVScrollBar()->setValue( 1.f ); UIPushButton* buildButton = getBuildButton( mApp ); bool enableBuildButton = false; @@ -227,15 +301,15 @@ void StatusBuildOutputController::runClean( const std::string& buildName, auto res = pbm->clean( buildName, [this]( const auto& key, const auto& def ) { return mApp->i18n( key, def ); }, buildType, - [this]( auto, auto buffer ) { - mContainer->runOnMainThread( [this, buffer]() { - bool scrollToBottom = mContainer->getVScrollBar()->getValue() == 1.f; - mContainer->getDocument().textInput( buffer ); + [this]( auto, auto buffer, auto ) { + mBuildOutput->runOnMainThread( [this, buffer]() { + bool scrollToBottom = mBuildOutput->getVScrollBar()->getValue() == 1.f; + mBuildOutput->getDocument().textInput( buffer ); if ( scrollToBottom ) - mContainer->setScrollY( mContainer->getMaxScroll().y ); + mBuildOutput->setScrollY( mBuildOutput->getMaxScroll().y ); } ); }, - [this, enableBuildButton]( auto exitCode ) { + [this, enableBuildButton]( auto exitCode, auto ) { String buffer; if ( EXIT_SUCCESS == exitCode ) { @@ -246,11 +320,11 @@ void StatusBuildOutputController::runClean( const std::string& buildName, mApp->i18n( "clean_failed", "Clean run with errors\n" ); } - mContainer->runOnMainThread( [this, buffer]() { - bool scrollToBottom = mContainer->getVScrollBar()->getValue() == 1.f; - mContainer->getDocument().textInput( buffer ); + mBuildOutput->runOnMainThread( [this, buffer]() { + bool scrollToBottom = mBuildOutput->getVScrollBar()->getValue() == 1.f; + mBuildOutput->getDocument().textInput( buffer ); if ( scrollToBottom ) - mContainer->setScrollY( mContainer->getMaxScroll().y ); + mBuildOutput->setScrollY( mBuildOutput->getMaxScroll().y ); } ); UIPushButton* cleanButton = getCleanButton( mApp ); @@ -270,11 +344,130 @@ void StatusBuildOutputController::runClean( const std::string& buildName, } UICodeEditor* StatusBuildOutputController::getContainer() { - return mContainer; + return mBuildOutput; } -UICodeEditor* StatusBuildOutputController::createContainer() { - UICodeEditor* editor = UICodeEditor::NewOpt( true, true ); +void StatusBuildOutputController::showIssues() { + mBuildOutput->setVisible( false ); + mTableIssues->setVisible( true ); + mButOutput->setSelected( false ); + mButIssues->setSelected( true ); + mTableIssues->setFocus(); +} + +void StatusBuildOutputController::showBuildOutput() { + mBuildOutput->setVisible( true ); + mTableIssues->setVisible( false ); + mButOutput->setSelected( true ); + mButIssues->setSelected( false ); + mBuildOutput->setFocus(); +} + +class StatusMessageModel : public Model { + public: + static std::shared_ptr create( std::vector& data, + UISceneNode* sceneNode ) { + return std::make_shared( data, sceneNode ); + } + + explicit StatusMessageModel( std::vector& data, UISceneNode* sceneNode ) : + mData( data ), mSceneNode( sceneNode ) {} + + virtual size_t rowCount( const ModelIndex& ) const { return mData.size(); } + + virtual size_t columnCount( const ModelIndex& ) const { return 3; } + + virtual Variant data( const ModelIndex& index, ModelRole role ) const { + eeASSERT( index.row() < (Int64)mData.size() ); + static UIIcon* errorIcon = mSceneNode->findIcon( "error" ); + static UIIcon* warnIcon = mSceneNode->findIcon( "warning" ); + if ( role == ModelRole::Display ) { + switch ( index.column() ) { + case 2: + return Variant( mData[index.row()].line ); + case 1: + return Variant( mData[index.row()].fileName.c_str() ); + case 0: + default: + return Variant( mData[index.row()].message ); + } + } else if ( role == ModelRole::Icon && index.column() == 0 ) { + return Variant( mData[index.row()].type == ProjectOutputParserTypes::Error ? errorIcon + : warnIcon ); + } else if ( role == ModelRole::Class ) { + return Variant( mData[index.row()].type == ProjectOutputParserTypes::Error + ? "theme-error" + : "theme-warning" ); + } else if ( role == ModelRole::Custom ) { + switch ( index.column() ) { + case 0: + return Variant( mData[index.row()].file.c_str() ); + case 1: + return Variant( mData[index.row()].line ); + case 2: + return Variant( mData[index.row()].col ); + } + } + return {}; + } + + virtual void update() { onModelUpdate(); } + + virtual std::string columnName( const size_t& idx ) const { + switch ( idx ) { + case 2: + return mSceneNode->i18n( "message", "Message" ); + case 1: + return mSceneNode->i18n( "file", "File" ); + case 0: + return mSceneNode->i18n( "line", "Line" ); + } + return ""; + } + + virtual bool classModelRoleEnabled() { return true; } + + protected: + std::vector& mData; + UISceneNode* mSceneNode; +}; + +void StatusBuildOutputController::onLoadDone( const Variant& lineNum, const Variant& colNum ) { + if ( mSplitter->curEditorExistsAndFocused() && lineNum.isValid() && colNum.isValid() && + lineNum.is( Variant::Type::Int64 ) && colNum.is( Variant::Type::Int64 ) ) { + TextPosition pos{ lineNum.asInt64() > 0 ? lineNum.asInt64() - 1 : 0, colNum.asInt64() }; + mSplitter->getCurEditor()->getDocument().setSelection( pos ); + mSplitter->getCurEditor()->goToLine( pos ); + } +} + +void StatusBuildOutputController::setHeaderWidth() { + auto totWidth = mTableIssues->getPixelsSize().getWidth() - + ( mTableIssues->getVerticalScrollBar()->isVisible() + ? mTableIssues->getVerticalScrollBar()->getPixelsSize().getWidth() + : 0.f ); + mTableIssues->setColumnWidth( 0, totWidth * 0.80f ); + mTableIssues->setColumnWidth( 1, totWidth * 0.15f ); + mTableIssues->setColumnWidth( 2, totWidth * 0.05f ); +} + +void StatusBuildOutputController::createContainer() { + if ( mContainer ) + return; + const auto XML = R"xml( + + + + + + + + + )xml"; + mContainer = mApp->getUISceneNode() + ->loadLayoutFromString( XML, mMainSplitter ) + ->asType(); + auto editor = mContainer->find( "build_output_output" ); editor->setLocked( true ); editor->setLineBreakingColumn( 0 ); editor->setShowLineNumber( false ); @@ -282,7 +475,57 @@ UICodeEditor* StatusBuildOutputController::createContainer() { editor->getDocument().textInput( mApp->i18n( "no_build_has_been_run", "No build has been run" ) ); editor->setScrollY( editor->getMaxScroll().y ); - return editor; + mButOutput = mContainer->find( "but_build_output_output" ); + mButIssues = mContainer->find( "but_build_output_issues" ); + mTableIssues = mContainer->find( "build_output_issues" ); + mTableIssues->setHeadersVisible( true ); + mTableIssues->setModel( StatusMessageModel::create( mStatusResults, mApp->getUISceneNode() ) ); + setHeaderWidth(); + mTableIssues->on( Event::OnSizeChange, [this]( auto ) { setHeaderWidth(); } ); + mTableIssues->on( Event::OnModelEvent, [this]( const Event* event ) { + auto modelEvent = static_cast( event ); + auto idx = modelEvent->getModelIndex(); + if ( modelEvent->getModel() && modelEvent->getModelEventType() == ModelEventType::Open && + idx.isValid() ) { + auto model = modelEvent->getModel(); + Variant vPath( model->data( idx, ModelRole::Custom ) ); + if ( vPath.isValid() && vPath.is( Variant::Type::cstr ) ) { + std::string path( vPath.asCStr() ); + UITab* tab = mSplitter->isDocumentOpen( path ); + Variant lineNum( model->data( model->index( modelEvent->getModelIndex().row(), 1 ), + ModelRole::Custom ) ); + Variant colNum( model->data( model->index( modelEvent->getModelIndex().row(), 2 ), + ModelRole::Custom ) ); + if ( !tab ) { + FileInfo fileInfo( path ); + if ( fileInfo.exists() && fileInfo.isRegularFile() ) + mApp->loadFileFromPath( + path, true, nullptr, + [&, lineNum, colNum]( UICodeEditor*, const std::string& ) { + onLoadDone( lineNum, colNum ); + } ); + } else { + tab->getTabWidget()->setTabSelected( tab ); + onLoadDone( lineNum, colNum ); + } + } + } + } ); + mBuildOutput = editor; + mContainer->setVisible( false ); + auto cont = mContainer->getFirstChild()->asType(); + cont->setCommand( "build-output-show-build-output", [this]() { showBuildOutput(); } ); + cont->setCommand( "build-output-show-build-issues", [this]() { showIssues(); } ); + cont->getKeyBindings().addKeybind( { KEY_1, KeyMod::getDefaultModifier() }, + "build-output-show-build-output" ); + cont->getKeyBindings().addKeybind( { KEY_2, KeyMod::getDefaultModifier() }, + "build-output-show-build-issues" ); + mButOutput->onClick( [this]( auto ) { showBuildOutput(); } ); + mButIssues->onClick( [this]( auto ) { showIssues(); } ); + mButOutput->setTooltipText( + cont->getKeyBindings().getCommandKeybindString( "build-output-show-build-output" ) ); + mButIssues->setTooltipText( + cont->getKeyBindings().getCommandKeybindString( "build-output-show-build-issues" ) ); } } // namespace ecode diff --git a/src/tools/ecode/statusbuildoutputcontroller.hpp b/src/tools/ecode/statusbuildoutputcontroller.hpp index 52ea0785d..9c346c947 100644 --- a/src/tools/ecode/statusbuildoutputcontroller.hpp +++ b/src/tools/ecode/statusbuildoutputcontroller.hpp @@ -2,10 +2,13 @@ #define ECODE_STATUSBUILDOUTPUTCONTROLLER_HPP #include "projectbuild.hpp" +#include #include #include +#include #include #include +#include using namespace EE; using namespace EE::UI; @@ -15,6 +18,21 @@ namespace ecode { class App; +struct StatusMessage { + ProjectOutputParserTypes type; + String output; + String message; + std::string file; + std::string fileName; + Int64 line{ 0 }; + Int64 col{ 0 }; +}; + +struct PatternHolder { + LuaPattern pattern; + ProjectBuildOutputParserConfig config; +}; + class StatusBuildOutputController { public: StatusBuildOutputController( UISplitter* mainSplitter, UISceneNode* uiSceneNode, App* app ); @@ -33,18 +51,38 @@ class StatusBuildOutputController { UICodeEditor* getContainer(); + void showIssues(); + + void showBuildOutput(); + protected: UISplitter* mMainSplitter{ nullptr }; UISceneNode* mUISceneNode{ nullptr }; App* mApp{ nullptr }; UICodeEditorSplitter* mSplitter{ nullptr }; - UICodeEditor* mContainer{ nullptr }; - UICodeEditor* createContainer(); + UIRelativeLayout* mContainer{ nullptr }; + UICodeEditor* mBuildOutput{ nullptr }; + UISelectButton* mButOutput{ nullptr }; + UISelectButton* mButIssues{ nullptr }; + UITableView* mTableIssues{ nullptr }; + + std::vector mStatusResults; + std::vector mPatternHolder; + std::string mCurLineBuffer; + + void createContainer(); UIPushButton* getBuildButton( App* app ); UIPushButton* getCleanButton( App* app ); + + bool searchFindAndAddStatusResult( const std::vector& patterns, + const std::string& text, const ProjectBuildCommand* cmd ); + + void onLoadDone( const Variant& lineNum, const Variant& colNum ); + + void setHeaderWidth(); }; } // namespace ecode diff --git a/src/tools/ecode/uibuildsettings.cpp b/src/tools/ecode/uibuildsettings.cpp index 1bc049c43..3b55348ef 100644 --- a/src/tools/ecode/uibuildsettings.cpp +++ b/src/tools/ecode/uibuildsettings.cpp @@ -381,7 +381,7 @@ static const auto SETTINGS_PANEL_XML = R"xml( - + diff --git a/src/tools/ecode/widgetcommandexecuter.hpp b/src/tools/ecode/widgetcommandexecuter.hpp index 4e3ed317a..a36c8e729 100644 --- a/src/tools/ecode/widgetcommandexecuter.hpp +++ b/src/tools/ecode/widgetcommandexecuter.hpp @@ -44,7 +44,7 @@ class UIGlobalSearchBar : public UILinearLayout, public WidgetCommandExecuter { }; class UIMainLayout : public UIRelativeLayout, public WidgetCommandExecuter { - public: + public: static UIMainLayout* New() { return eeNew( UIMainLayout, () ); } UIMainLayout() : @@ -56,6 +56,21 @@ class UIMainLayout : public UIRelativeLayout, public WidgetCommandExecuter { } }; +class UIRelativeLayoutCommandExecuter : public UIRelativeLayout, public WidgetCommandExecuter { + public: + static UIRelativeLayoutCommandExecuter* New() { + return eeNew( UIRelativeLayoutCommandExecuter, () ); + } + + UIRelativeLayoutCommandExecuter() : + UIRelativeLayout( "rellayce" ), + WidgetCommandExecuter( getUISceneNode()->getWindow()->getInput() ) {} + + virtual Uint32 onKeyDown( const KeyEvent& event ) { + return WidgetCommandExecuter::onKeyDown( event ); + } +}; + } // namespace ecode #endif // ECODE_WIDGETCOMMANDEXECUTER_HPP