diff --git a/include/eepp/graphics/text.hpp b/include/eepp/graphics/text.hpp index 815d07c61..356989a16 100644 --- a/include/eepp/graphics/text.hpp +++ b/include/eepp/graphics/text.hpp @@ -66,26 +66,29 @@ class EE_API Text { const Float& outlineThickness = 0.f ); static std::size_t findLastCharPosWithinLength( Font* font, const Uint32& fontSize, - const String& string, Float maxWidth, - const Uint32& style, - const Uint32& tabWidth = 4, - const Float& outlineThickness = 0.f ); + const String& string, Float maxWidth, + const Uint32& style, const Uint32& tabWidth = 4, + const Float& outlineThickness = 0.f ); static std::size_t findLastCharPosWithinLength( Font* font, const Uint32& fontSize, - const String::View& string, Float maxWidth, - const Uint32& style, - const Uint32& tabWidth = 4, - const Float& outlineThickness = 0.f ); + const String::View& string, Float maxWidth, + const Uint32& style, const Uint32& tabWidth = 4, + const Float& outlineThickness = 0.f ); - static std::size_t findLastCharPosWithinLength( const String& string, - Float maxWidth, - const FontStyleConfig& config, - const Uint32& tabWidth = 4 ); + static std::size_t findLastCharPosWithinLength( const String& string, Float maxWidth, + const FontStyleConfig& config, + const Uint32& tabWidth = 4 ); - static std::size_t findLastCharPosWithinLength( const String::View& string, - Float maxWidth, - const FontStyleConfig& config, - const Uint32& tabWidth = 4 ); + static std::size_t findLastCharPosWithinLength( const String::View& string, Float maxWidth, + const FontStyleConfig& config, + const Uint32& tabWidth = 4 ); + + static bool wrapText( Font* font, const Uint32& fontSize, String& string, const Float& maxWidth, + const Uint32& style, const Uint32& tabWidth = 4, + const Float& outlineThickness = 0.f ); + + static bool wrapText( String& string, const Float& maxWidth, const FontStyleConfig& config, + const Uint32& tabWidth = 4 ); static Text* New(); @@ -306,10 +309,19 @@ class EE_API Text { const Uint32& tabWidth = 4 ); template - static std::size_t - findLastCharPosWithinLength( Font* font, const Uint32& fontSize, const StringType& string, - Float width, const Uint32& style, const Uint32& tabWidth = 4, - const Float& outlineThickness = 0.f ); + static std::size_t findLastCharPosWithinLength( Font* font, const Uint32& fontSize, + const StringType& string, Float width, + const Uint32& style, const Uint32& tabWidth = 4, + const Float& outlineThickness = 0.f ); + + template + static bool wrapText( Font* font, const Uint32& fontSize, StringType& string, + const Float& maxWidth, const Uint32& style, const Uint32& tabWidth = 4, + const Float& outlineThickness = 0.f ); + + template + static bool wrapText( StringType& string, const Float& maxWidth, const FontStyleConfig& config, + const Uint32& tabWidth = 4 ); }; }} // namespace EE::Graphics diff --git a/projects/linux/ee.files b/projects/linux/ee.files index b18b64daf..61e37b69b 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -1375,6 +1375,10 @@ ../../src/tools/ecode/notificationcenter.cpp ../../src/tools/ecode/notificationcenter.hpp ../../src/tools/ecode/pathhelper.hpp +../../src/tools/ecode/plugins/git/git.cpp +../../src/tools/ecode/plugins/git/git.hpp +../../src/tools/ecode/plugins/git/gitplugin.cpp +../../src/tools/ecode/plugins/git/gitplugin.hpp ../../src/tools/ecode/plugins/linter/linterplugin.cpp ../../src/tools/ecode/plugins/linter/linterplugin.hpp ../../src/tools/ecode/plugins/lsp/lspclientplugin.cpp diff --git a/src/eepp/graphics/text.cpp b/src/eepp/graphics/text.cpp index a5792512d..73366d5cc 100644 --- a/src/eepp/graphics/text.cpp +++ b/src/eepp/graphics/text.cpp @@ -325,6 +325,104 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, const FontStyle config.ShadowColor, config.ShadowOffset, tabWidth ); } +template +bool Text::wrapText( Font* font, const Uint32& fontSize, StringType& string, const Float& maxWidth, + const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness ) { + if ( string.empty() || NULL == font ) + return false; + + Float tCurWidth = 0.f; + Float tWordWidth = 0.f; + Float tMaxWidth = (Float)maxWidth; + auto tChar = &string[0]; + decltype( tChar ) tLastSpace = NULL; + Uint32 prevChar = 0; + bool bold = ( style & Bold ) != 0; + bool italic = ( style & Italic ) != 0; + bool wrapped = false; + + Float hspace = static_cast( + font->getGlyph( L' ', fontSize, bold, italic, outlineThickness ).advance ); + + while ( *tChar ) { + Glyph pChar = font->getGlyph( *tChar, fontSize, bold, italic, outlineThickness ); + + Float fCharWidth = (Float)pChar.advance; + + if ( ( *tChar ) == '\t' ) + fCharWidth += hspace * tabWidth; + else if ( ( *tChar ) == '\r' ) + fCharWidth = 0; + + // Add the new char width to the current word width + tWordWidth += fCharWidth; + + if ( *tChar != '\r' ) { + tWordWidth += + font->getKerning( prevChar, *tChar, fontSize, bold, italic, outlineThickness ); + prevChar = *tChar; + } + + if ( ' ' == *tChar || '\0' == *( tChar + 1 ) ) { + // If current width plus word width is minor to the max width, continue adding + if ( tCurWidth + tWordWidth < tMaxWidth ) { + tCurWidth += tWordWidth; + tLastSpace = tChar; + + tChar++; + } else { + // If it was an space before, replace that space for an new line + // Start counting from the new line first character + if ( NULL != tLastSpace ) { + *tLastSpace = '\n'; + tChar = tLastSpace + 1; + } else { // The word is larger than the current possible width + *tChar = '\n'; + wrapped = true; + } + + if ( '\0' == *( tChar + 1 ) ) + tChar++; + + // Set the last spaces as null, because is a new line + tLastSpace = NULL; + + // New line, new current width + tCurWidth = 0.f; + } + + // New word, so we reset the current word width + tWordWidth = 0.f; + } else if ( '\n' == *tChar ) { + tWordWidth = 0.f; + tCurWidth = 0.f; + tLastSpace = NULL; + tChar++; + } else { + tChar++; + } + } + + return wrapped; +} + +template +bool Text::wrapText( StringType& string, const Float& maxWidth, const FontStyleConfig& config, + const Uint32& tabWidth ) { + return wrapText( config.Font, config.CharacterSize, string, maxWidth, config.Style, + tabWidth, config.OutlineThickness ); +} + +bool Text::wrapText( Font* font, const Uint32& fontSize, String& string, const Float& maxWidth, + const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness ) { + return wrapText( font, fontSize, string, maxWidth, style, tabWidth, outlineThickness ); +} + +bool Text::wrapText( String& string, const Float& maxWidth, const FontStyleConfig& config, + const Uint32& tabWidth ) { + return wrapText( string, maxWidth, config, tabWidth ); +} + Text::Text() {} Text::Text( const String& string, Font* font, unsigned int characterSize ) : @@ -603,9 +701,9 @@ Float Text::getTextWidth( Font* font, const Uint32& fontSize, const StringType& template std::size_t Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSize, - const StringType& string, Float maxWidth, - const Uint32& style, const Uint32& tabWidth, - const Float& outlineThickness ) { + const StringType& string, Float maxWidth, + const Uint32& style, const Uint32& tabWidth, + const Float& outlineThickness ) { if ( NULL == font || string.empty() ) return 0; String::StringBaseType rune; @@ -759,35 +857,34 @@ Int32 Text::findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font* } std::size_t Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSize, - const String& string, Float maxWidth, - const Uint32& style, const Uint32& tabWidth, - const Float& outlineThickness ) { + const String& string, Float maxWidth, + const Uint32& style, const Uint32& tabWidth, + const Float& outlineThickness ) { return findLastCharPosWithinLength( font, fontSize, string, maxWidth, style, tabWidth, - outlineThickness ); + outlineThickness ); } std::size_t Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSize, - const String::View& string, Float maxWidth, - const Uint32& style, const Uint32& tabWidth, - const Float& outlineThickness ) { + const String::View& string, Float maxWidth, + const Uint32& style, const Uint32& tabWidth, + const Float& outlineThickness ) { return findLastCharPosWithinLength( font, fontSize, string, maxWidth, style, - tabWidth, outlineThickness ); + tabWidth, outlineThickness ); } std::size_t Text::findLastCharPosWithinLength( const String& string, Float maxWidth, - const FontStyleConfig& config, - const Uint32& tabWidth ) { - return findLastCharPosWithinLength( config.Font, config.CharacterSize, string, - maxWidth, config.Style, tabWidth, - config.OutlineThickness ); + const FontStyleConfig& config, + const Uint32& tabWidth ) { + return findLastCharPosWithinLength( config.Font, config.CharacterSize, string, maxWidth, + config.Style, tabWidth, config.OutlineThickness ); } std::size_t Text::findLastCharPosWithinLength( const String::View& string, Float maxWidth, - const FontStyleConfig& config, - const Uint32& tabWidth ) { + const FontStyleConfig& config, + const Uint32& tabWidth ) { return findLastCharPosWithinLength( config.Font, config.CharacterSize, string, - maxWidth, config.Style, tabWidth, - config.OutlineThickness ); + maxWidth, config.Style, tabWidth, + config.OutlineThickness ); } void Text::updateWidthCache() { @@ -839,85 +936,8 @@ void Text::updateWidthCache() { } void Text::wrapText( const Uint32& maxWidth ) { - if ( !mString.size() || NULL == mFontStyleConfig.Font ) - return; - - Float tCurWidth = 0.f; - Float tWordWidth = 0.f; - Float tMaxWidth = (Float)maxWidth; - String::StringBaseType* tChar = &mString[0]; - String::StringBaseType* tLastSpace = NULL; - Uint32 prevChar = 0; - bool bold = ( mFontStyleConfig.Style & Bold ) != 0; - bool italic = ( mFontStyleConfig.Style & Italic ) != 0; - - Float hspace = static_cast( mFontStyleConfig.Font - ->getGlyph( L' ', mFontStyleConfig.CharacterSize, bold, - italic, mFontStyleConfig.OutlineThickness ) - .advance ); - - while ( *tChar ) { - Glyph pChar = mFontStyleConfig.Font->getGlyph( *tChar, mFontStyleConfig.CharacterSize, bold, - italic, mFontStyleConfig.OutlineThickness ); - - Float fCharWidth = (Float)pChar.advance; - - if ( ( *tChar ) == '\t' ) - fCharWidth += hspace * mTabWidth; - else if ( ( *tChar ) == '\r' ) - fCharWidth = 0; - - // Add the new char width to the current word width - tWordWidth += fCharWidth; - - if ( *tChar != '\r' ) { - tWordWidth += mFontStyleConfig.Font->getKerning( - prevChar, *tChar, mFontStyleConfig.CharacterSize, bold, italic, - mFontStyleConfig.OutlineThickness ); - prevChar = *tChar; - } - - if ( ' ' == *tChar || '\0' == *( tChar + 1 ) ) { - - // If current width plus word width is minor to the max width, continue adding - if ( tCurWidth + tWordWidth < tMaxWidth ) { - tCurWidth += tWordWidth; - tLastSpace = tChar; - - tChar++; - } else { - // If it was an space before, replace that space for an new line - // Start counting from the new line first character - if ( NULL != tLastSpace ) { - *tLastSpace = '\n'; - tChar = tLastSpace + 1; - } else { // The word is larger than the current possible width - *tChar = '\n'; - } - - if ( '\0' == *( tChar + 1 ) ) - tChar++; - - // Set the last spaces as null, because is a new line - tLastSpace = NULL; - - // New line, new current width - tCurWidth = 0.f; - } - - // New word, so we reset the current word width - tWordWidth = 0.f; - } else if ( '\n' == *tChar ) { - tWordWidth = 0.f; - tCurWidth = 0.f; - tLastSpace = NULL; - tChar++; - } else { - tChar++; - } - } - - invalidate(); + if ( wrapText( mString, maxWidth, mFontStyleConfig, mTabWidth ) ) + invalidate(); } void Text::invalidateColors() { diff --git a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp index f74c80258..dac89ab13 100644 --- a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp +++ b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp @@ -1778,10 +1778,10 @@ static json toJson( const SyntaxDefinition& def ) { } else { pattern["pattern"] = ptrn.patterns; } - if ( ptrn.types.size() == 1 ) { - pattern["type"] = ptrn.types[0]; + if ( ptrn.typesNames.size() == 1 ) { + pattern["type"] = ptrn.typesNames[0]; } else { - pattern["type"] = ptrn.types; + pattern["type"] = ptrn.typesNames; } if ( !ptrn.syntax.empty() ) pattern["syntax"] = ptrn.syntax; diff --git a/src/eepp/ui/uitooltip.cpp b/src/eepp/ui/uitooltip.cpp index 4363d7f28..62d295106 100644 --- a/src/eepp/ui/uitooltip.cpp +++ b/src/eepp/ui/uitooltip.cpp @@ -172,6 +172,7 @@ Graphics::Font* UITooltip::getFont() const { void UITooltip::setFont( Graphics::Font* font ) { if ( NULL != font && mTextCache->getFont() != font ) { mTextCache->setFont( font ); + mStyleConfig.Font = font; autoPadding(); onAutoSize(); autoAlign(); diff --git a/src/eepp/ui/uiwidget.cpp b/src/eepp/ui/uiwidget.cpp index f50515647..af6c34648 100644 --- a/src/eepp/ui/uiwidget.cpp +++ b/src/eepp/ui/uiwidget.cpp @@ -334,8 +334,10 @@ Uint32 UIWidget::onMouseOver( const Vector2i& position, const Uint32& flags ) { [this] { if ( isTooltipEnabled() && getEventDispatcher()->getMouseOverNode() == this ) { + bool createdTooltip = mTooltip == NULL; createTooltip(); - mTooltip->setPixelsPosition( getTooltipPosition() ); + if ( createdTooltip ) + mTooltip->setPixelsPosition( getTooltipPosition() ); mTooltip->show(); } }, diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp index f9a9d355b..c9baebe09 100644 --- a/src/tools/ecode/appconfig.cpp +++ b/src/tools/ecode/appconfig.cpp @@ -160,11 +160,13 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath, std::map pluginsEnabled; const auto& creators = pluginManager->getDefinitions(); - for ( const auto& creator : creators ) + for ( const auto& creator : creators ) { pluginsEnabled[creator.first] = ini.getValueB( "plugins", creator.first, "autocomplete" == creator.first || "linter" == creator.first || - "autoformatter" == creator.first || "lspclient" == creator.first ); + "autoformatter" == creator.first || "lspclient" == creator.first || + "git" == creator.first ); + } pluginManager->setPluginsEnabled( pluginsEnabled, sync ); languagesExtensions.priorities = ini.getKeyMap( "languages_extensions" ); diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index a73721e0f..3986a296a 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -4,6 +4,7 @@ #include "pathhelper.hpp" #include "plugins/autocomplete/autocompleteplugin.hpp" #include "plugins/formatter/formatterplugin.hpp" +#include "plugins/git/gitplugin.hpp" #include "plugins/linter/linterplugin.hpp" #include "plugins/lsp/lspclientplugin.hpp" #include "plugins/xmltools/xmltoolsplugin.hpp" @@ -438,6 +439,7 @@ void App::initPluginManager() { mPluginManager->registerPlugin( AutoCompletePlugin::Definition() ); mPluginManager->registerPlugin( LSPClientPlugin::Definition() ); mPluginManager->registerPlugin( XMLToolsPlugin::Definition() ); + mPluginManager->registerPlugin( GitPlugin::Definition() ); } bool App::loadConfig( const LogLevel& logLevel, const Sizeu& displaySize, bool sync, diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp index 5aef9022f..c2caa7e9a 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp @@ -187,12 +187,11 @@ void AutoCompletePlugin::onUnregister( UICodeEditor* editor ) { } bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event ) { - bool ret = false; if ( mSignatureHelpVisible ) { if ( event.getKeyCode() == KEY_ESCAPE ) { resetSignatureHelp(); editor->invalidateDraw(); - ret = true; + return true; } else if ( event.getKeyCode() == KEY_UP ) { if ( mSignatureHelp.signatures.size() > 1 ) { mSignatureHelpSelected = mSignatureHelpSelected == -1 ? 0 : mSignatureHelpSelected; @@ -262,12 +261,10 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event editor->invalidateDraw(); return true; } else if ( event.getKeyCode() == KEY_ESCAPE ) { - if ( !ret ) { - resetSuggestions( editor ); - resetSignatureHelp(); - editor->invalidateDraw(); - return true; - } + resetSuggestions( editor ); + resetSignatureHelp(); + editor->invalidateDraw(); + return true; } else if ( event.getKeyCode() == KEY_HOME ) { mSuggestionIndex = 0; mSuggestionsStartIndex = 0; @@ -310,7 +307,7 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event updateSuggestions( partialSymbol, editor ); return true; } - return ret; + return false; } void AutoCompletePlugin::requestSignatureHelp( UICodeEditor* editor ) { diff --git a/src/tools/ecode/plugins/git/git.cpp b/src/tools/ecode/plugins/git/git.cpp new file mode 100644 index 000000000..904e0981b --- /dev/null +++ b/src/tools/ecode/plugins/git/git.cpp @@ -0,0 +1,147 @@ +#include "git.hpp" +#include +#include +#include +#include +#include + +using namespace EE; +using namespace EE::System; + +using namespace std::literals; + +namespace ecode { + +static constexpr auto sNotCommitedYetHash = "0000000000000000000000000000000000000000"; + +Git::BlameData::BlameData( const std::string& error ) : error( error ) {} + +Git::BlameData::BlameData( std::string&& author, std::string&& authorEmail, std::string&& date, + std::string&& commitHash, std::string&& commitShortHash, + std::string&& commitMessage ) : + author( std::move( author ) ), + authorEmail( std::move( authorEmail ) ), + date( std::move( date ) ), + commitHash( std::move( commitHash ) ), + commitShortHash( std::move( commitShortHash ) ), + commitMessage( std::move( commitMessage ) ) {} + +Git::Git( const std::string& projectDir, const std::string& gitPath ) : mGitPath( gitPath ) { + if ( gitPath.empty() ) + mGitPath = Sys::which( "git" ); + if ( !projectDir.empty() ) + setProjectPath( projectDir ); +} + +void Git::git( const std::string& args, const std::string& projectDir, std::string& buf ) const { + Process p; + p.create( mGitPath, args, Process::CombinedStdoutStderr | Process::Options::NoWindow, + { { "LC_ALL", "en_US.UTF-8" } }, projectDir.empty() ? mProjectPath : projectDir ); + p.readAllStdOut( buf ); +} + +std::string Git::branch( std::string projectDir ) { + std::string buf; + git( "rev-parse --abbrev-ref HEAD", projectDir, buf ); + return String::rTrim( buf, '\n' ); +} + +bool Git::setProjectPath( std::string projectPath ) { + mProjectPath = ""; + FileInfo f( projectPath ); + if ( !f.isDirectory() ) + return false; + std::string oriPath( f.getDirectoryPath() ); + std::string path( oriPath ); + std::string lPath; + FileSystem::dirAddSlashAtEnd( path ); + while ( path != lPath ) { + if ( FileSystem::fileExists( path + ".git" ) ) { + mProjectPath = path; + return true; + } + lPath = path; + path = FileSystem::removeLastFolderFromPath( path ); + } + return false; +} + +Git::Status Git::status( std::string projectDir ) { + Status s; + std::string buf; + git( "diff --numstat", projectDir, buf ); + auto lastNL = 0; + auto nextNL = buf.find_first_of( '\n' ); + while ( nextNL != std::string_view::npos ) { + LuaPattern pattern( "(%d+)%s+(%d+)%s+(.+)" ); + LuaPattern::Range matches[4]; + if ( pattern.matches( buf.c_str(), lastNL, matches, nextNL ) ) { + auto inserted = buf.substr( matches[1].start, matches[1].end - matches[1].start ); + auto deleted = buf.substr( matches[2].start, matches[2].end - matches[2].start ); + auto file = buf.substr( matches[3].start, matches[3].end - matches[3].start ); + int inserts; + int deletes; + if ( String::fromString( inserts, inserted ) && + String::fromString( deletes, deleted ) ) { + s.modified.push_back( { std::move( file ), inserts, deletes } ); + s.totalInserts += inserts; + s.totalDeletions += deletes; + } + } + lastNL = nextNL; + nextNL = buf.find_first_of( '\n', nextNL + 1 ); + } + return s; +} + +Git::BlameData Git::blame( const std::string& filepath, std::size_t line ) const { + std::string buf; + const auto getText = [&buf]( const std::string_view& txt ) -> std::string { + std::string search = "\n" + txt + " "; + auto pos = buf.find( search ); + if ( pos != std::string::npos ) { + pos = pos + search.length(); + auto endPos = buf.find_first_of( '\n', pos ); + if ( endPos != std::string::npos ) + return buf.substr( pos, endPos - pos ); + } + return ""; + }; + + std::string workingDir( FileSystem::fileRemoveFileName( filepath ) ); + git( String::format( "blame %s -p -L%zu,%zu", filepath.data(), line, line ), workingDir, buf ); + + if ( String::startsWith( buf, "fatal: " ) ) + return { buf.substr( 7 ) }; + + auto hashEnd = buf.find_first_of( ' ' ); + + if ( hashEnd == std::string::npos ) + return { "No commit hash found" }; + + auto commitHash = buf.substr( 0, hashEnd ); + + if ( commitHash == sNotCommitedYetHash ) + return { "Not Committed Yet" }; + + auto author = getText( "author"sv ); + auto authorEmail = getText( "author-mail"sv ); + if ( authorEmail.size() > 3 ) + authorEmail = authorEmail.substr( 1, authorEmail.size() - 2 ); + auto datetime = getText( "author-time"sv ); + auto tz = getText( "author-tz"sv ); + Uint64 epoch; + if ( !datetime.empty() && String::fromString( epoch, datetime ) ) + datetime = Sys::epochToString( epoch ) + ( tz.empty() ? "" : " " + tz ); + + auto commitMessage = getText( "summary"sv ); + + git( String::format( "rev-parse --short %s", commitHash.c_str() ), workingDir, buf ); + + auto commitShortHash = String::rTrim( buf, '\n' ); + + return { std::move( author ), std::move( authorEmail ), std::move( datetime ), + std::move( commitHash ), std::move( commitShortHash ), std::move( commitMessage ) }; +} + +} // namespace ecode diff --git a/src/tools/ecode/plugins/git/git.hpp b/src/tools/ecode/plugins/git/git.hpp new file mode 100644 index 000000000..4505e9fb6 --- /dev/null +++ b/src/tools/ecode/plugins/git/git.hpp @@ -0,0 +1,57 @@ +#include +#include + +namespace ecode { + +class Git { + public: + struct BlameData { + BlameData( const std::string& error ); + + BlameData( std::string&& author, std::string&& authorEmail, std::string&& date, + std::string&& commitHash, std::string&& commitShortHash, + std::string&& commitMessage ); + + std::string author; + std::string authorEmail; + std::string date; + std::string commitHash; + std::string commitShortHash; + std::string commitMessage; + std::string error; + }; + + struct DiffFile { + std::string file; + int inserts{ 0 }; + int deletes{ 0 }; + }; + + struct Status { + std::vector modified; + int totalInserts{ 0 }; + int totalDeletions{ 0 }; + }; + + Git( const std::string& projectDir = "", const std::string& gitPath = "" ); + + void git( const std::string& args, const std::string& projectDir, std::string& buf ) const; + + BlameData blame( const std::string& filepath, std::size_t line ) const; + + std::string branch( std::string projectDir = "" ); + + Status status( std::string projectDir = "" ); + + bool setProjectPath( std::string projectPath ); + + const std::string& getGitPath() const { return mGitPath; } + + const std::string& getProjectPath() const { return mProjectPath; } + + protected: + std::string mGitPath; + std::string mProjectPath; +}; + +} // namespace ecode diff --git a/src/tools/ecode/plugins/git/gitplugin.cpp b/src/tools/ecode/plugins/git/gitplugin.cpp new file mode 100644 index 000000000..363af15b8 --- /dev/null +++ b/src/tools/ecode/plugins/git/gitplugin.cpp @@ -0,0 +1,250 @@ +#include "gitplugin.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace EE::UI::Doc; + +using json = nlohmann::json; +#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ ) +#define GIT_THREADED 1 +#else +#define GIT_THREADED 0 +#endif + +namespace ecode { + +UICodeEditorPlugin* GitPlugin::New( PluginManager* pluginManager ) { + return eeNew( GitPlugin, ( pluginManager, false ) ); +} + +UICodeEditorPlugin* GitPlugin::NewSync( PluginManager* pluginManager ) { + return eeNew( GitPlugin, ( pluginManager, true ) ); +} + +GitPlugin::GitPlugin( PluginManager* pluginManager, bool sync ) : PluginBase( pluginManager ) { + if ( sync ) { + load( pluginManager ); + } else { +#if defined( GIT_THREADED ) && GIT_THREADED == 1 + mThreadPool->run( [&, pluginManager] { load( pluginManager ); } ); +#else + load( pluginManager ); +#endif + } +} + +GitPlugin::~GitPlugin() { + mShuttingDown = true; +} + +void GitPlugin::load( PluginManager* pluginManager ) { + AtomicBoolScopedOp loading( mLoading, true ); + pluginManager->subscribeMessages( this, + [this]( const auto& notification ) -> PluginRequestHandle { + return processMessage( notification ); + } ); + + std::string path = pluginManager->getPluginsPath() + "git.json"; + if ( FileSystem::fileExists( path ) || + FileSystem::fileWrite( path, "{\n \"config\":{},\n \"keybindings\":{}\n}\n" ) ) { + mConfigPath = path; + } + std::string data; + if ( !FileSystem::fileGet( path, data ) ) + return; + mConfigHash = String::hash( data ); + + json j; + try { + j = json::parse( data, nullptr, true, true ); + } catch ( const json::exception& e ) { + Log::error( "GitPlugin::load - Error parsing config from path %s, error: %s, config " + "file content:\n%s", + path.c_str(), e.what(), data.c_str() ); + // Recreate it + j = json::parse( "{\n \"config\":{},\n \"keybindings\":{},\n}\n", nullptr, true, true ); + } + + bool updateConfigFile = false; + + if ( j.contains( "config" ) ) { + auto& config = j["config"]; + + if ( config.contains( "statusbar_display_branch" ) ) + mStatusBarDisplayBranch = config.value( "statusbar_display_branch", true ); + else { + config["statusbar_display_branch"] = mStatusBarDisplayBranch; + updateConfigFile = true; + } + + if ( config.contains( "statusbar_display_modifications" ) ) + mStatusBarDisplayModifications = + config.value( "statusbar_display_modifications", true ); + else { + config["statusbar_display_modifications"] = mStatusBarDisplayModifications; + updateConfigFile = true; + } + } + + if ( updateConfigFile ) { + std::string newData = j.dump( 2 ); + if ( newData != data ) { + FileSystem::fileWrite( path, newData ); + mConfigHash = String::hash( newData ); + } + } + + mGit = std::make_unique( "", pluginManager->getWorkspaceFolder() ); + mGitFound = !mGit->getGitPath().empty(); + + subscribeFileSystemListener(); + mReady = true; + fireReadyCbs(); + setReady(); +} + +PluginRequestHandle GitPlugin::processMessage( const PluginMessage& msg ) { + switch ( msg.type ) { + case PluginMessageType::WorkspaceFolderChanged: { + mGit->setProjectPath( msg.asJSON()["folder"] ); + break; + } + default: + break; + } + return PluginRequestHandle::empty(); +} + +void GitPlugin::onFileSystemEvent( const FileEvent& ev, const FileInfo& file ) { + PluginBase::onFileSystemEvent( ev, file ); + + if ( mShuttingDown || isLoading() ) + return; +} + +void GitPlugin::displayTooltip( UICodeEditor* editor, const Git::BlameData& blame, + const Vector2f& position ) { + // HACK: Gets the old font style to restore it when the tooltip is hidden + UITooltip* tooltip = editor->createTooltip(); + if ( tooltip == nullptr ) + return; + + String str( + blame.error.empty() + ? String::format( "%s: %s\n%s: %s (%s)\n%s: %s\n\n%s", + i18n( "commit", "Commit" ).toUtf8().c_str(), blame.commitHash.c_str(), + i18n( "author", "Author" ).toUtf8().c_str(), blame.author.c_str(), + blame.authorEmail.c_str(), i18n( "date", "Date" ).toUtf8().c_str(), + blame.date.c_str(), blame.commitMessage.c_str() ) + : blame.error ); + + Text::wrapText( str, PixelDensity::dpToPx( 400 ), tooltip->getFontStyleConfig(), + editor->getTabWidth() ); + + editor->setTooltipText( str ); + + mTooltipInfoShowing = true; + mOldTextStyle = tooltip->getFontStyle(); + mOldTextAlign = tooltip->getHorizontalAlign(); + mOldDontAutoHideOnMouseMove = tooltip->dontAutoHideOnMouseMove(); + mOldUsingCustomStyling = tooltip->getUsingCustomStyling(); + tooltip->setHorizontalAlign( UI_HALIGN_LEFT ); + tooltip->setPixelsPosition( tooltip->getTooltipPosition( position ) ); + tooltip->setDontAutoHideOnMouseMove( false ); + tooltip->setUsingCustomStyling( true ); + + // const auto& syntaxDef = SyntaxDefinitionManager::instance()->getByLSPName( "markdown" ); + + // SyntaxTokenizer::tokenizeText( syntaxDef, editor->getColorScheme(), *tooltip->getTextCache(), 0, + // 0xFFFFFFFF, true, "\n\t " ); + + // tooltip->notifyTextChangedFromTextCache(); + + if ( editor->hasFocus() && !tooltip->isVisible() ) + tooltip->show(); +} + +void GitPlugin::hideTooltip( UICodeEditor* editor ) { + mTooltipInfoShowing = false; + UITooltip* tooltip = nullptr; + if ( editor && ( tooltip = editor->getTooltip() ) && tooltip->isVisible() ) { + editor->setTooltipText( "" ); + tooltip->hide(); + // Restore old tooltip state + tooltip->setFontStyle( mOldTextStyle ); + tooltip->setHorizontalAlign( mOldTextAlign ); + tooltip->setUsingCustomStyling( mOldUsingCustomStyling ); + tooltip->setDontAutoHideOnMouseMove( mOldDontAutoHideOnMouseMove ); + } +} + +void GitPlugin::onRegisterListeners( UICodeEditor* editor, std::vector& listeners ) { + listeners.push_back( + editor->addEventListener( Event::OnCursorPosChange, [this, editor]( const Event* ) { + if ( mTooltipInfoShowing ) + hideTooltip( editor ); + } ) ); +} + +void GitPlugin::blame( UICodeEditor* editor ) { + if ( !mGitFound ) { + editor->setTooltipText( + i18n( "git_not_found", + "Git binary not found.\nPlease check that git is accesible via PATH" ) ); + return; + } + mThreadPool->run( [this, editor]() { + auto blame = mGit->blame( editor->getDocument().getFilePath(), + editor->getDocument().getSelection().start().line() ); + editor->runOnMainThread( [this, editor, blame] { + displayTooltip( + editor, blame, + editor->getScreenPosition( editor->getDocument().getSelection().start() ) + .getPosition() ); + } ); + } ); +} + +void GitPlugin::onRegister( UICodeEditor* editor ) { + PluginBase::onRegister( editor ); + if ( !editor->hasDocument() ) + return; + + auto& doc = editor->getDocument(); + doc.setCommand( "git-blame", [this]( TextDocument::Client* client ) { + blame( static_cast( client ) ); + } ); +} + +void GitPlugin::onUnregister( UICodeEditor* editor ) { + PluginBase::onUnregister( editor ); +} + +bool GitPlugin::onCreateContextMenu( UICodeEditor*, UIPopUpMenu* menu, const Vector2i& /*position*/, + const Uint32& /*flags*/ ) { + if ( !mGitFound ) + return false; + + menu->addSeparator(); + + auto addFn = [this, menu]( const std::string& txtKey, const std::string& txtVal, + const std::string& icon = "" ) { + menu->add( i18n( txtKey, txtVal ), + !icon.empty() ? mManager->getUISceneNode()->findIcon( icon )->getSize( + PixelDensity::dpToPxI( 12 ) ) + : nullptr, + KeyBindings::keybindFormat( mKeyBindings[txtKey] ) ) + ->setId( txtKey ); + }; + + addFn( "git-blame", "Git Blame" ); + + return false; +} + +} // namespace ecode diff --git a/src/tools/ecode/plugins/git/gitplugin.hpp b/src/tools/ecode/plugins/git/gitplugin.hpp new file mode 100644 index 000000000..c3fb56a67 --- /dev/null +++ b/src/tools/ecode/plugins/git/gitplugin.hpp @@ -0,0 +1,69 @@ +#ifndef ECODE_GITPLUGIN_HPP +#define ECODE_GITPLUGIN_HPP + +#include "../plugin.hpp" +#include "../pluginmanager.hpp" +#include "git.hpp" + +namespace ecode { + +class Git; + +class GitPlugin : public PluginBase { + public: + static PluginDefinition Definition() { + return { "git", "Git", "Git integration", GitPlugin::New, { 0, 0, 1 }, GitPlugin::NewSync }; + } + + static UICodeEditorPlugin* New( PluginManager* pluginManager ); + + static UICodeEditorPlugin* NewSync( PluginManager* pluginManager ); + + virtual ~GitPlugin(); + + std::string getId() override { return Definition().id; } + + std::string getTitle() override { return Definition().name; } + + std::string getDescription() override { return Definition().description; } + + virtual void onFileSystemEvent( const FileEvent& ev, const FileInfo& file ) override; + + virtual void onRegister( UICodeEditor* ) override; + + virtual void onUnregister( UICodeEditor* ) override; + + virtual bool onCreateContextMenu( UICodeEditor* editor, UIPopUpMenu* menu, + const Vector2i& position, const Uint32& flags ) override; + + protected: + std::unique_ptr mGit; + + GitPlugin( PluginManager* pluginManager, bool sync ); + + void load( PluginManager* pluginManager ); + + PluginRequestHandle processMessage( const PluginMessage& msg ); + + void displayTooltip( UICodeEditor* editor, const Git::BlameData& blame, + const Vector2f& position ); + + void hideTooltip( UICodeEditor* editor ); + + void onRegisterListeners( UICodeEditor*, std::vector& listeners ) override; + + bool mGitFound{ false }; + bool mTooltipInfoShowing{ false }; + bool mStatusBarDisplayBranch{ true }; + bool mStatusBarDisplayModifications{ true }; + bool mOldDontAutoHideOnMouseMove{ false }; + bool mOldUsingCustomStyling{ false }; + Uint32 mOldTextStyle{ 0 }; + Uint32 mOldTextAlign{ 0 }; + + void blame( UICodeEditor* editor ); +}; + +} // namespace ecode + +#endif // ECODE_GITPLUGIN_HPP diff --git a/src/tools/ecode/plugins/linter/linterplugin.cpp b/src/tools/ecode/plugins/linter/linterplugin.cpp index d27a42645..7810e61b8 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.cpp +++ b/src/tools/ecode/plugins/linter/linterplugin.cpp @@ -159,11 +159,16 @@ void LinterPlugin::loadLinterConfig( const std::string& path, bool updateConfigF config["disable_languages"] = json::array(); } - if ( config.contains( "go-to-ignore-warnings" ) && - config["go-to-ignore-warnings"].is_boolean() ) { - mGoToIgnoreWarnings = config["go-to-ignore-warnings"].get(); + if ( config.contains( "go-to-ignore-warnings" ) ) { + config["goto_ignore_warnings"] = config["go-to-ignore-warnings"]; + config.erase( "go-to-ignore-warnings" ); + } + + if ( config.contains( "goto_ignore_warnings" ) && + config["goto_ignore_warnings"].is_boolean() ) { + mGoToIgnoreWarnings = config["goto_ignore_warnings"].get(); } else if ( updateConfigFile ) { - config["go-to-ignore-warnings"] = false; + config["goto_ignore_warnings"] = false; } } diff --git a/src/tools/ecode/plugins/plugin.cpp b/src/tools/ecode/plugins/plugin.cpp index 7fd3f1dfa..24a7b64a5 100644 --- a/src/tools/ecode/plugins/plugin.cpp +++ b/src/tools/ecode/plugins/plugin.cpp @@ -7,8 +7,8 @@ namespace ecode { Plugin::Plugin( PluginManager* manager ) : mManager( manager ), mThreadPool( manager->getThreadPool() ), - mReady( false ), // All plugins will start as not ready until proved the contrary - mLoading( true ) // All plugins will start as loading until the load is complete, this is to + mReady( false ), // All plugins will start as "not ready" until proven the contrary + mLoading( true ) // All plugins will start as "loading" until the load is complete, this is to // avoid concurrency issues {} @@ -45,6 +45,10 @@ PluginManager* Plugin::getManager() const { return mManager; } +String Plugin::i18n( const std::string& key, const String& def ) const { + return getManager()->getUISceneNode()->i18n( key, def ); +} + void Plugin::onFileSystemEvent( const FileEvent& ev, const FileInfo& file ) { if ( ev.type != FileSystemEventType::Modified || mShuttingDown || isLoading() ) return; diff --git a/src/tools/ecode/plugins/plugin.hpp b/src/tools/ecode/plugins/plugin.hpp index bd37f48ce..7105531c2 100644 --- a/src/tools/ecode/plugins/plugin.hpp +++ b/src/tools/ecode/plugins/plugin.hpp @@ -36,6 +36,8 @@ class Plugin : public UICodeEditorPlugin { virtual void onFileSystemEvent( const FileEvent& ev, const FileInfo& file ); + String i18n( const std::string& key, const String& def ) const; + protected: PluginManager* mManager{ nullptr }; std::shared_ptr mThreadPool;