From 1ba74ae33829fdfca990ace60ad0a8283b5325df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sat, 8 Apr 2023 13:22:31 -0300 Subject: [PATCH] ecode: Project Build WIP. --- .ecode/project_build.json | 27 +++- include/eepp/ui/uicodeeditor.hpp | 5 + src/eepp/ui/doc/syntaxcolorscheme.cpp | 2 + src/eepp/ui/uicodeeditor.cpp | 8 ++ src/eepp/ui/uislider.cpp | 5 +- src/tools/ecode/ecode.cpp | 6 + src/tools/ecode/ecode.hpp | 13 ++ src/tools/ecode/projectbuild.cpp | 122 ++++++++++++++---- src/tools/ecode/projectbuild.hpp | 31 +++-- .../ecode/statusbuildoutputcontroller.cpp | 77 +++++++++++ .../ecode/statusbuildoutputcontroller.hpp | 4 + 11 files changed, 263 insertions(+), 37 deletions(-) diff --git a/.ecode/project_build.json b/.ecode/project_build.json index 56c99d1f3..0fcb4f5eb 100644 --- a/.ecode/project_build.json +++ b/.ecode/project_build.json @@ -27,9 +27,6 @@ "clear_sys_env": false, "enabled": true }, - "env": { - "SHELL": "fish" - }, "os": [ "linux" ], @@ -39,7 +36,29 @@ }, "error": [ { - "pattern": "([^:]*):(%d+):(%d+):%s?[%w%s]*error:%s?([^\n]*)", + "pattern": "([^:]+):(%d+):(%d+):%s?[%w%s]*error:%s?(.*)", + "pattern_order": { + "col": 3, + "file": 1, + "line": 2, + "message": 4 + } + } + ], + "notice": [ + { + "pattern": "([^:]+):(%d+):(%d+):%s?[%w%s]*notice:%s?(.*)", + "pattern_order": { + "col": 3, + "file": 1, + "line": 2, + "message": 4 + } + } + ], + "warning": [ + { + "pattern": "([^:]+):(%d+):(%d+):%s?[%w%s]*warning:%s?(.*)", "pattern_order": { "col": 3, "file": 1, diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index d0d6a57fc..6bf7aaa8b 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -594,8 +594,13 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { Vector2f getRelativeScreenPosition( const TextPosition& pos ); bool getShowLinesRelativePosition() const; + void showLinesRelativePosition( bool showLinesRelativePosition ); + UIScrollBar* getVScrollBar() const; + + UIScrollBar* getHScrollBar() const; + protected: struct LastXOffset { TextPosition position{ 0, 0 }; diff --git a/src/eepp/ui/doc/syntaxcolorscheme.cpp b/src/eepp/ui/doc/syntaxcolorscheme.cpp index 5a8f587dc..68fdb9f1e 100644 --- a/src/eepp/ui/doc/syntaxcolorscheme.cpp +++ b/src/eepp/ui/doc/syntaxcolorscheme.cpp @@ -207,6 +207,8 @@ const SyntaxColorScheme::Style& SyntaxColorScheme::getSyntaxStyle( const std::st return getSyntaxStyle( "symbol" ); else if ( type == "link" || type == "link_hover" ) return getSyntaxStyle( "function" ); + else if ( type == "error" || type == "warning" || type == "notice" ) + return getEditorSyntaxStyle( type ); else { auto foundIt = mStyleCache.find( type ); if ( foundIt != mStyleCache.end() ) diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index dd4d26d46..3785dd229 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -1392,6 +1392,14 @@ void UICodeEditor::showLinesRelativePosition( bool showLinesRelativePosition ) { mShowLinesRelativePosition = showLinesRelativePosition; } +UIScrollBar* UICodeEditor::getVScrollBar() const { + return mVScrollBar; +} + +UIScrollBar* UICodeEditor::getHScrollBar() const { + return mHScrollBar; +} + void UICodeEditor::drawCursor( const Vector2f& startScroll, const Float& lineHeight, const TextPosition& cursor ) { if ( mCursorVisible && !mLocked && isTextSelectionEnabled() ) { diff --git a/src/eepp/ui/uislider.cpp b/src/eepp/ui/uislider.cpp index 2a9256e69..dda996087 100644 --- a/src/eepp/ui/uislider.cpp +++ b/src/eepp/ui/uislider.cpp @@ -70,7 +70,10 @@ UISlider::UISlider( const std::string& tag, const UIOrientation& orientation ) : mSlider->setDragEnabled( true ); mSlider->setSize( 4, 4 ); mSlider->setPosition( 0, 0 ); - mSlider->addEventListener( Event::OnPositionChange, [&]( const Event* ) { fixSliderPos(); } ); + mSlider->addEventListener( Event::OnPositionChange, [&]( const Event* ) { + if ( !mUpdating && !mOnPosChange ) + fixSliderPos(); + } ); if ( UIOrientation::Horizontal == mOrientation ) mSlider->centerVertical(); diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index acaae70b3..9c7c73d0f 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -748,6 +748,10 @@ void App::showStatusBar( bool show ) { mStatusBar->setVisible( show ); } +ProjectBuildManager* App::getProjectBuildManager() const { + return mProjectBuildManager.get(); +} + void App::switchSidePanel() { mConfig.ui.showSidePanel = !mConfig.ui.showSidePanel; mSettings->getWindowMenu() @@ -1725,6 +1729,8 @@ std::vector App::getUnlockedCommands() { "open-locatebar", "open-command-palette", "open-global-search", + "project-build-start", + "project-build-cancel", "toggle-locatebar", "toggle-global-search", "toggle-status-build-output", diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index 518244d27..46f0cf2bd 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -234,6 +234,17 @@ class App : public UICodeEditorSplitter::Client { t.setCommand( "open-locatebar", [&] { mUniversalLocator->showLocateBar(); } ); t.setCommand( "toggle-locatebar", [&] { mUniversalLocator->toggleLocateBar(); } ); t.setCommand( "open-command-palette", [&] { mUniversalLocator->showCommandPalette(); } ); + t.setCommand( "project-build-start", [&] { + if ( mProjectBuildManager && !mProjectBuildManager->isBuilding() ) { + mStatusBuildOutputController->run( + "ecode", "debug", mProjectBuildManager->getOutputParser( "ecode" ) ); + } + } ); + t.setCommand( "project-build-cancel", [&] { + if ( mProjectBuildManager && mProjectBuildManager->isBuilding() ) { + mProjectBuildManager->cancelBuild(); + } + } ); t.setCommand( "open-workspace-symbol-search", [&] { mUniversalLocator->showWorkspaceSymbol(); } ); t.setCommand( "open-document-symbol-search", @@ -378,6 +389,8 @@ class App : public UICodeEditorSplitter::Client { void showStatusBar( bool show ); + ProjectBuildManager* getProjectBuildManager() const; + protected: std::vector mArgs; EE::Window::Window* mWindow{ nullptr }; diff --git a/src/tools/ecode/projectbuild.cpp b/src/tools/ecode/projectbuild.cpp index 52bf8e9fd..ad6c91fd9 100644 --- a/src/tools/ecode/projectbuild.cpp +++ b/src/tools/ecode/projectbuild.cpp @@ -59,17 +59,18 @@ ProjectBuildManager::ProjectBuildManager( const std::string& projectRoot, ProjectBuildManager::~ProjectBuildManager() { mShuttingDown = true; + mCancelBuild = true; while ( mLoading ) Sys::sleep( Milliseconds( 0.1f ) ); while ( mBuilding ) Sys::sleep( Milliseconds( 0.1f ) ); } -ProjectBuildCommandsRes -ProjectBuildManager::run( const std::string& buildName, - std::function i18n, - const std::string& buildType, const ProjectBuildProgressFn& progressFn, - const ProjectBuildDoneFn& doneFn ) { +ProjectBuildCommandsRes ProjectBuildManager::run( const std::string& buildName, + const ProjectBuildi18nFn& i18n, + const std::string& buildType, + const ProjectBuildProgressFn& progressFn, + const ProjectBuildDoneFn& doneFn ) { ProjectBuildCommandsRes res = generateBuildCommands( buildName, i18n, buildType ); if ( !res.isValid() ) return res; @@ -78,7 +79,9 @@ ProjectBuildManager::run( const std::string& buildName, return res; } - mThreadPool->run( [this, res, progressFn, doneFn]() { runBuild( res, progressFn, doneFn ); } ); + mThreadPool->run( [this, res, progressFn, doneFn, i18n, buildName, buildType]() { + runBuild( buildName, buildType, i18n, res, progressFn, doneFn ); + } ); return res; }; @@ -222,10 +225,9 @@ bool ProjectBuildManager::load() { return true; } -ProjectBuildCommandsRes ProjectBuildManager::generateBuildCommands( - const std::string& buildName, - std::function i18n, - const std::string& buildType ) { +ProjectBuildCommandsRes ProjectBuildManager::generateBuildCommands( const std::string& buildName, + const ProjectBuildi18nFn& i18n, + const std::string& buildType ) { if ( !mLoaded ) return { i18n( "project_build_not_loaded", "No project build loaded!" ) }; @@ -261,17 +263,66 @@ ProjectBuildCommandsRes ProjectBuildManager::generateBuildCommands( return res; } -void ProjectBuildManager::runBuild( const ProjectBuildCommandsRes& res, +ProjectBuildOutputParser ProjectBuildManager::getOutputParser( const std::string& buildName ) { + auto buildIt = mBuilds.find( buildName ); + if ( buildIt != mBuilds.end() ) + return buildIt->second.mOutputParser; + return {}; +} + +void ProjectBuildManager::cancelBuild() { + mCancelBuild = true; +} + +void ProjectBuildManager::runBuild( const std::string& buildName, const std::string& buildType, + const ProjectBuildi18nFn& i18n, + const ProjectBuildCommandsRes& res, const ProjectBuildProgressFn& progressFn, const ProjectBuildDoneFn& doneFn ) { ScopedOp scopedOp( [this]() { mBuilding = true; }, [this]() { mBuilding = false; } ); Clock clock; + + 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() ) ); + } + }; + + 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() ) ); + + if ( !buildType.empty() ) + progressFn( + 0, Sys::getDateTimeStr() + ": " + + String::format( + i18n( "using_build_type", "Using build type: %s.\n" ).toUtf8().c_str(), + buildType.c_str() ) ); + } + + int c = 0; for ( const auto& cmd : res.cmds ) { + int progress = c > 0 ? c / (Float)res.cmds.size() : 0; Process process; auto options = Process::SearchUserPath | Process::NoWindow | Process::CombinedStdoutStderr; if ( !cmd.config.clearSysEnv ) options |= Process::InheritEnvironment; if ( process.create( cmd.cmd, cmd.args, options, cmd.envs, cmd.workingDir ) ) { + if ( progressFn ) + progressFn( progress, + Sys::getDateTimeStr() + ": " + + String::format( + i18n( "starting_process", "Starting %s %s\n" ).toUtf8().c_str(), + cmd.cmd.c_str(), cmd.args.c_str() ) ); + std::string buffer( 1024, '\0' ); unsigned bytesRead = 0; int returnCode; @@ -279,30 +330,57 @@ void ProjectBuildManager::runBuild( const ProjectBuildCommandsRes& res, bytesRead = process.readStdOut( buffer ); std::string data( buffer.substr( 0, bytesRead ) ); if ( progressFn ) - progressFn( 50, std::move( data ) ); - } while ( bytesRead != 0 && process.isAlive() && !mShuttingDown ); + progressFn( progress, std::move( data ) ); + } while ( bytesRead != 0 && process.isAlive() && !mShuttingDown && !mCancelBuild ); - if ( mShuttingDown ) { + if ( mShuttingDown || mCancelBuild ) { process.kill(); - doneFn( EXIT_FAILURE ); + mCancelBuild = false; + printElapsed(); + if ( doneFn ) + doneFn( EXIT_FAILURE ); return; } - if ( progressFn ) - progressFn( 90, {} ); - process.join( &returnCode ); process.destroy(); - if ( doneFn && returnCode != EXIT_SUCCESS ) { - progressFn( 100, {} ); - doneFn( returnCode ); + if ( returnCode != EXIT_SUCCESS ) { + if ( progressFn ) { + progressFn( 100, + String::format( i18n( "process_exited_with_errors", + "The process \"%s\" exited with errors.\n" ) + .toUtf8() + .c_str(), + cmd.cmd.c_str() ) ); + } + printElapsed(); + if ( doneFn ) + doneFn( returnCode ); return; + } else { + if ( progressFn ) { + progressFn( progress, + String::format( i18n( "process_exited_normally", + "The process \"%s\" exited normally.\n" ) + .toUtf8() + .c_str(), + cmd.cmd.c_str() ) ); + } } + } else { + printElapsed(); + if ( doneFn ) + doneFn( EXIT_FAILURE ); + return; } + + c++; } - doneFn( EXIT_SUCCESS ); + printElapsed(); + if ( doneFn ) + doneFn( EXIT_SUCCESS ); } } // namespace ecode diff --git a/src/tools/ecode/projectbuild.hpp b/src/tools/ecode/projectbuild.hpp index 00f874428..30f617e65 100644 --- a/src/tools/ecode/projectbuild.hpp +++ b/src/tools/ecode/projectbuild.hpp @@ -100,6 +100,11 @@ struct ProjectBuildOutputParserConfig { }; class ProjectBuildOutputParser { + public: + const std::vector& getConfig() const { return mConfig; } + + bool useRelativeFilePaths() const { return mRelativeFilePaths; } + protected: friend class ProjectBuildManager; @@ -160,6 +165,8 @@ struct ProjectBuildCommandsRes { using ProjectBuildProgressFn = std::function; using ProjectBuildDoneFn = std::function; +using ProjectBuildi18nFn = + std::function; class ProjectBuildManager { public: @@ -167,16 +174,16 @@ class ProjectBuildManager { ~ProjectBuildManager(); - ProjectBuildCommandsRes - run( const std::string& buildName, - std::function i18n, - const std::string& buildType = "", const ProjectBuildProgressFn& progressFn = {}, - const ProjectBuildDoneFn& doneFn = {} ); + ProjectBuildCommandsRes run( const std::string& buildName, const ProjectBuildi18nFn& i18n, + const std::string& buildType = "", + const ProjectBuildProgressFn& progressFn = {}, + const ProjectBuildDoneFn& doneFn = {} ); - ProjectBuildCommandsRes generateBuildCommands( - const std::string& buildName, - std::function i18n, - const std::string& buildType = "" ); + ProjectBuildCommandsRes generateBuildCommands( const std::string& buildName, + const ProjectBuildi18nFn& i18n, + const std::string& buildType = "" ); + + ProjectBuildOutputParser getOutputParser( const std::string& buildName ); const ProjectBuildMap& getBuilds() const { return mBuilds; } @@ -190,6 +197,8 @@ class ProjectBuildManager { bool isBuilding() const { return mBuilding; } + void cancelBuild(); + protected: std::string mProjectRoot; std::string mProjectFile; @@ -199,8 +208,10 @@ class ProjectBuildManager { bool mLoading{ false }; bool mBuilding{ false }; bool mShuttingDown{ false }; + bool mCancelBuild{ false }; - void runBuild( const ProjectBuildCommandsRes& res, + void runBuild( const std::string& buildName, const std::string& buildType, + const ProjectBuildi18nFn& i18n, const ProjectBuildCommandsRes& res, const ProjectBuildProgressFn& progressFn = {}, const ProjectBuildDoneFn& doneFn = {} ); diff --git a/src/tools/ecode/statusbuildoutputcontroller.cpp b/src/tools/ecode/statusbuildoutputcontroller.cpp index 5fcea057a..c5a55b29b 100644 --- a/src/tools/ecode/statusbuildoutputcontroller.cpp +++ b/src/tools/ecode/statusbuildoutputcontroller.cpp @@ -60,6 +60,83 @@ void StatusBuildOutputController::show() { } } +static std::string getProjectOutputParserTypeToString( const ProjectOutputParserTypes& type ) { + switch ( type ) { + case ProjectOutputParserTypes::Error: + return "error"; + case ProjectOutputParserTypes::Warning: + return "warning"; + case ProjectOutputParserTypes::Notice: + return "notice"; + } + return "notice"; +} + +void StatusBuildOutputController::run( const std::string& buildName, const std::string& buildType, + const ProjectBuildOutputParser& outputParser ) { + if ( !mApp->getProjectBuildManager() ) + return; + + auto pbm = mApp->getProjectBuildManager(); + + show(); + + mContainer->getDocument().reset(); + mContainer->setScrollY( mContainer->getMaxScroll().y ); + + std::vector patterns; + + for ( const auto& parser : outputParser.getConfig() ) { + SyntaxPattern ptn( { parser.pattern }, getProjectOutputParserTypeToString( parser.type ) ); + patterns.emplace_back( std::move( ptn ) ); + } + + patterns.emplace_back( + SyntaxPattern( { "%d%d%d%d%-%d%d%-%d%d%s%d%d%:%d%d%:%d%d%:.*error.*[^\n]+" }, "error" ) ); + patterns.emplace_back( + SyntaxPattern( { "%d%d%d%d%-%d%d%-%d%d%s%d%d%:%d%d%:%d%d%:.*warning.*[^\n]+" }, "error" ) ); + patterns.emplace_back( + SyntaxPattern( { "%d%d%d%d%-%d%d%-%d%d%s%d%d%:%d%d%:%d%d%:[^\n]+" }, "notice" ) ); + + SyntaxDefinition synDef( "custom_build", {}, patterns ); + mContainer->getDocument().setSyntaxDefinition( synDef ); + mContainer->getVScrollBar()->setValue( 1.f ); + + auto res = pbm->run( + 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 ); + if ( scrollToBottom ) + mContainer->setScrollY( mContainer->getMaxScroll().y ); + } ); + }, + [this]( auto exitCode ) { + String buffer; + + if ( EXIT_SUCCESS == exitCode ) { + buffer = Sys::getDateTimeStr() + ": " + + mApp->i18n( "build_successful", "Build run successfully\n" ); + } else { + buffer = Sys::getDateTimeStr() + ": " + + mApp->i18n( "build_failed", "Build run with errors\n" ); + } + + mContainer->runOnMainThread( [this, buffer]() { + bool scrollToBottom = mContainer->getVScrollBar()->getValue() == 1.f; + mContainer->getDocument().textInput( buffer ); + if ( scrollToBottom ) + mContainer->setScrollY( mContainer->getMaxScroll().y ); + } ); + } ); + + if ( !res.isValid() ) { + mApp->getNotificationCenter()->addNotification( res.errorMsg ); + } +} + UICodeEditor* StatusBuildOutputController::createContainer() { UICodeEditor* editor = UICodeEditor::NewOpt( true, true ); editor->setLocked( true ); diff --git a/src/tools/ecode/statusbuildoutputcontroller.hpp b/src/tools/ecode/statusbuildoutputcontroller.hpp index 3c91a086a..5e1a73a8d 100644 --- a/src/tools/ecode/statusbuildoutputcontroller.hpp +++ b/src/tools/ecode/statusbuildoutputcontroller.hpp @@ -1,6 +1,7 @@ #ifndef ECODE_STATUSBUILDOUTPUTCONTROLLER_HPP #define ECODE_STATUSBUILDOUTPUTCONTROLLER_HPP +#include "projectbuild.hpp" #include #include #include @@ -24,6 +25,9 @@ class StatusBuildOutputController { void show(); + void run( const std::string& buildName, const std::string& buildType, + const ProjectBuildOutputParser& outputParser = {} ); + protected: UISplitter* mMainSplitter{ nullptr }; UISceneNode* mUISceneNode{ nullptr };