From a9a866e5df97d4ee7c7c52dc747cc54c813bcadb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 21 Jun 2020 22:43:21 -0300 Subject: [PATCH] Minor changes to the code editor and text document: Add command to comment selected lines. Option to trim trailing white spaces. Option to ensure new line at end of file option. Option to allow to select line endings type. --- TODO.md | 8 -- include/eepp/graphics/console.hpp | 4 +- include/eepp/scene/scenemanager.hpp | 2 + include/eepp/system/luapatternmatcher.hpp | 7 +- include/eepp/ui/doc/textdocument.hpp | 24 +++++- src/eepp/graphics/console.cpp | 14 ++-- src/eepp/scene/scenemanager.cpp | 4 + src/eepp/scene/scenenode.cpp | 7 ++ src/eepp/system/luapatternmatcher.cpp | 9 +++ src/eepp/ui/doc/syntaxdefinitionmanager.cpp | 40 ++++++++++ src/eepp/ui/doc/textdocument.cpp | 86 ++++++++++++++++++--- src/eepp/ui/uicodeeditor.cpp | 1 + src/tools/codeeditor/codeeditor.cpp | 13 +++- 13 files changed, 184 insertions(+), 35 deletions(-) diff --git a/TODO.md b/TODO.md index 58d1a886e..be1720470 100644 --- a/TODO.md +++ b/TODO.md @@ -13,14 +13,6 @@ * Add XML tags auto-close. -* Add command to comment selected lines. - -* On Save: trim white spaces. - -* On Save: Ensure new line at end of file option. - -* On Save: Allow to select line endings type. - ## Code Editor Keep improving it: diff --git a/include/eepp/graphics/console.hpp b/include/eepp/graphics/console.hpp index 2465f06c4..24bceaaee 100644 --- a/include/eepp/graphics/console.hpp +++ b/include/eepp/graphics/console.hpp @@ -119,7 +119,7 @@ class EE_API Console : protected LogReaderInterface { void addCommand( const String& Command, ConsoleCallback CB ); /** Draw the Console ( allways call it, visible or not ) */ - void draw(); + void draw( const Time& elapsedTime = Time::Zero ); /** Set the line height ( distance between lines ) */ void setLineHeight( const Float& LineHeight ); @@ -194,7 +194,7 @@ class EE_API Console : protected LogReaderInterface { void createDefaultCommands(); - void fade(); + void fade( const Time& elapsedTime ); /** Internal Callback for default command ( clear ) */ void cmdClear( const std::vector& params ); diff --git a/include/eepp/scene/scenemanager.hpp b/include/eepp/scene/scenemanager.hpp index e6839f930..77036d559 100644 --- a/include/eepp/scene/scenemanager.hpp +++ b/include/eepp/scene/scenemanager.hpp @@ -41,6 +41,8 @@ class EE_API SceneManager { void setCurrentUISceneNode( UISceneNode* uiSceneNode ); + Time getElapsed() const; + protected: Clock mClock; UISceneNode* mUISceneNode; diff --git a/include/eepp/system/luapatternmatcher.hpp b/include/eepp/system/luapatternmatcher.hpp index 1ad78b116..485c41032 100644 --- a/include/eepp/system/luapatternmatcher.hpp +++ b/include/eepp/system/luapatternmatcher.hpp @@ -9,12 +9,15 @@ namespace EE { namespace System { class EE_API LuaPatternMatcher { public: struct Match { - int start; - int end; + int start{-1}; + int end{-1}; + bool isValid() { return -1 != start && -1 != end; } }; static std::string match( const std::string& string, const std::string& pattern ); + static Match find( const std::string& string, const std::string& pattern ); + LuaPatternMatcher( const std::string& pattern ); bool matches( const char* stringSearch, int stringStartOffset, diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index aa49668a9..d49d5d33e 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -35,7 +35,9 @@ class EE_API TextDocument { virtual void onDocumentLineChanged( const Int64& lineIndex ) = 0; }; - enum IndentType { IndentSpaces, IndentTabs }; + enum class IndentType { IndentSpaces, IndentTabs }; + + enum class LineEnding { LF, CRLF }; TextDocument(); @@ -280,6 +282,8 @@ class EE_API TextDocument { const String& getNonWordChars() const; + void toggleLineComments(); + void setNonWordChars( const String& nonWordChars ); void resetSyntax(); @@ -288,6 +292,18 @@ class EE_API TextDocument { void setAutoDetectIndentType( bool autodetect ); + const LineEnding& getLineEnding() const; + + void setLineEnding( const LineEnding& lineEnding ); + + bool getForceNewLineAtEndOfFile() const; + + void setForceNewLineAtEndOfFile( bool forceNewLineAtEndOfFile ); + + bool getTrimTrailingWhitespaces() const; + + void setTrimTrailingWhitespaces( bool trimTrailingWhitespaces ); + protected: friend class UndoStack; UndoStack mUndoStack; @@ -295,11 +311,13 @@ class EE_API TextDocument { std::vector mLines; TextRange mSelection; std::unordered_set mClients; - bool mIsCLRF{false}; + LineEnding mLineEnding{LineEnding::LF}; bool mIsBOM{false}; bool mAutoDetectIndentType{true}; + bool mForceNewLineAtEndOfFile{false}; + bool mTrimTrailingWhitespaces{false}; Uint32 mIndentWidth{4}; - IndentType mIndentType{IndentTabs}; + IndentType mIndentType{IndentType::IndentTabs}; Clock mTimer; SyntaxDefinition mSyntaxDefinition; std::string mDefaultFileName; diff --git a/src/eepp/graphics/console.cpp b/src/eepp/graphics/console.cpp index 124150e03..c47ead9fa 100755 --- a/src/eepp/graphics/console.cpp +++ b/src/eepp/graphics/console.cpp @@ -216,9 +216,9 @@ void Console::addCommand( const String& Command, ConsoleCallback CB ) { mCallbacks[Command] = CB; } -void Console::draw() { +void Console::draw( const Time& elapsedTime ) { if ( mEnabled && NULL != mFontStyleConfig.Font ) { - fade(); + fade( elapsedTime == Time::Zero ? mWindow->getElapsed() : elapsedTime ); if ( mY > 0.0f ) { if ( mTexId == 0 ) { @@ -473,15 +473,15 @@ void Console::toggle() { fadeIn(); } -void Console::fade() { +void Console::fade( const Time& elapsedTime ) { if ( mCurSide ) { - mCurAlpha -= 255.f * mWindow->getElapsed().asMilliseconds() / mFadeSpeed.asMilliseconds(); + mCurAlpha -= 255.f * elapsedTime.asMilliseconds() / mFadeSpeed.asMilliseconds(); if ( mCurAlpha <= 0.0f ) { mCurAlpha = 0.0f; mCurSide = !mCurSide; } } else { - mCurAlpha += 255.f * mWindow->getElapsed().asMilliseconds() / mFadeSpeed.asMilliseconds(); + mCurAlpha += 255.f * elapsedTime.asMilliseconds() / mFadeSpeed.asMilliseconds(); if ( mCurAlpha >= 255.f ) { mCurAlpha = 255.f; mCurSide = !mCurSide; @@ -495,7 +495,7 @@ void Console::fade() { if ( mFadeIn ) { mFadeOut = false; - mY += mCurHeight * mWindow->getElapsed().asMilliseconds() / mFadeSpeed.asMilliseconds(); + mY += mCurHeight * elapsedTime.asMilliseconds() / mFadeSpeed.asMilliseconds(); mA = ( mY * mMaxAlpha / mCurHeight ); if ( mY > mCurHeight ) { @@ -507,7 +507,7 @@ void Console::fade() { if ( mFadeOut ) { mFadeIn = false; - mY -= mCurHeight * mWindow->getElapsed().asMilliseconds() / mFadeSpeed.asMilliseconds(); + mY -= mCurHeight * elapsedTime.asMilliseconds() / mFadeSpeed.asMilliseconds(); mA = ( mY * mMaxAlpha / mCurHeight ); if ( mY <= 0.0f ) { diff --git a/src/eepp/scene/scenemanager.cpp b/src/eepp/scene/scenemanager.cpp index f0530da4b..c0dd6dc21 100644 --- a/src/eepp/scene/scenemanager.cpp +++ b/src/eepp/scene/scenemanager.cpp @@ -75,4 +75,8 @@ void SceneManager::setCurrentUISceneNode( UISceneNode* uiSceneNode ) { mUISceneNode = uiSceneNode; } +Time SceneManager::getElapsed() const { + return mClock.getElapsedTime(); +} + }} // namespace EE::Scene diff --git a/src/eepp/scene/scenenode.cpp b/src/eepp/scene/scenenode.cpp index 1b2cc8548..2fa38fcab 100644 --- a/src/eepp/scene/scenenode.cpp +++ b/src/eepp/scene/scenenode.cpp @@ -352,6 +352,7 @@ bool SceneNode::getDrawDebugData() const { void SceneNode::setDrawBoxes( bool draw ) { mDrawBoxes = draw; + invalidateDraw(); } bool SceneNode::getDrawBoxes() const { @@ -360,6 +361,7 @@ bool SceneNode::getDrawBoxes() const { void SceneNode::setHighlightOver( bool Highlight ) { mHighlightOver = Highlight; + invalidateDraw(); } bool SceneNode::getHighlightOver() const { @@ -368,6 +370,7 @@ bool SceneNode::getHighlightOver() const { void SceneNode::setHighlightFocus( bool Highlight ) { mHighlightFocus = Highlight; + invalidateDraw(); } bool SceneNode::getHighlightFocus() const { @@ -376,6 +379,7 @@ bool SceneNode::getHighlightFocus() const { void SceneNode::setHighlightInvalidation( bool Highlight ) { mHighlightInvalidation = Highlight; + invalidateDraw(); } bool SceneNode::getHighlightInvalidation() const { @@ -384,6 +388,7 @@ bool SceneNode::getHighlightInvalidation() const { void SceneNode::setHighlightOverColor( const Color& color ) { mHighlightOverColor = color; + invalidateDraw(); } const Color& SceneNode::getHighlightOverColor() const { @@ -392,6 +397,7 @@ const Color& SceneNode::getHighlightOverColor() const { void SceneNode::setHighlightFocusColor( const Color& color ) { mHighlightFocusColor = color; + invalidateDraw(); } const Color& SceneNode::getHighlightFocusColor() const { @@ -400,6 +406,7 @@ const Color& SceneNode::getHighlightFocusColor() const { void SceneNode::setHighlightInvalidationColor( const Color& color ) { mHighlightInvalidationColor = color; + invalidateDraw(); } const Color& SceneNode::getHighlightInvalidationColor() const { diff --git a/src/eepp/system/luapatternmatcher.cpp b/src/eepp/system/luapatternmatcher.cpp index 2f3017f43..367ab4514 100644 --- a/src/eepp/system/luapatternmatcher.cpp +++ b/src/eepp/system/luapatternmatcher.cpp @@ -21,6 +21,15 @@ std::string LuaPatternMatcher::match( const std::string& string, const std::stri return ""; } +LuaPatternMatcher::Match LuaPatternMatcher::find( const std::string& string, + const std::string& pattern ) { + LuaPatternMatcher matcher( pattern ); + int start = 0, end = 0; + if ( matcher.find( string, start, end ) ) + return {start, end}; + return {-1, -1}; +} + LuaPatternMatcher::LuaPatternMatcher( const std::string& pattern ) : mPattern( pattern ) { if ( !sFailHandlerInitialized ) { sFailHandlerInitialized = true; diff --git a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp index d134f66c0..2bbfb239e 100644 --- a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp +++ b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp @@ -1687,6 +1687,46 @@ SyntaxDefinitionManager::SyntaxDefinitionManager() { {{"^%+.-\n"}, "function"}, {{"^%-.-\n"}, "keyword2"}, }} ); + + // Add Java + add( {"Java", + {"%.java$"}, + { + {{"//.-\n"}, "comment"}, + {{"/%*", "%*/"}, "comment"}, + {{"\"", "\"", "\\"}, "string"}, + {{"'", "'", "\\"}, "string"}, + {{"'\\x%x?%x?%x?%x'"}, "string"}, + {{"'\\u%x%x%x%x'"}, "string"}, + {{"'\\?.'"}, "string"}, + {{"-?0x%x+"}, "number"}, + {{"-?%d+[%d%.eE]*f?"}, "number"}, + {{"-?%.?%d+f?"}, "number"}, + {{"[%+%-=/%*%^%%<>!~|&]"}, "operator"}, + {{"[%a_][%w_]*%f[(]"}, "function"}, + {{"[%a_][%w_]*"}, "symbol"}, + }, + { + {"if", "keyword"}, {"then", "keyword"}, {"else", "keyword"}, + {"elseif", "keyword"}, {"do", "keyword"}, {"while", "keyword"}, + {"for", "keyword"}, {"new", "keyword"}, {"break", "keyword"}, + {"continue", "keyword"}, {"return", "keyword"}, {"goto", "keyword"}, + {"class", "keyword"}, {"implements", "keyword"}, {"extends", "keyword"}, + {"private", "keyword"}, {"protected", "keyword"}, {"public", "keyword"}, + {"abstract", "keyword"}, {"interface", "keyword"}, {"assert", "keyword"}, + {"import", "keyword"}, {"native", "keyword"}, {"package", "keyword"}, + {"super", "keyword"}, {"synchronized", "keyword"}, {"instanceof", "keyword"}, + {"enum", "keyword"}, {"catch", "keyword"}, {"throw", "keyword"}, + {"throws", "keyword"}, {"try", "keyword"}, {"transient", "keyword"}, + {"finally", "keyword"}, {"static", "keyword"}, {"volatile", "keyword"}, + {"final", "keyword"}, {"switch", "keyword"}, {"case", "keyword"}, + {"default", "keyword"}, {"void", "keyword"}, {"int", "keyword2"}, + {"short", "keyword2"}, {"byte", "keyword2"}, {"long", "keyword2"}, + {"float", "keyword2"}, {"double", "keyword2"}, {"char", "keyword2"}, + {"boolean", "keyword2"}, {"true", "literal"}, {"false", "literal"}, + {"null", "literal"}, + }, + "//"} ); } SyntaxDefinition& SyntaxDefinitionManager::add( SyntaxDefinition&& syntaxStyle ) { diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index 17c423694..6a9eb4e58 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -99,10 +99,10 @@ bool TextDocument::loadFromStream( IOStream& file ) { if ( lineBuffer[lineBuffer.size() - 1] == '\n' || !consume ) { if ( mLines.empty() && lineBuffer.size() > 1 && lineBuffer[lineBuffer.size() - 2] == '\r' ) { - mIsCLRF = true; + mLineEnding = LineEnding::CRLF; } - if ( mIsCLRF && lineBuffer.size() > 1 ) { + if ( mLineEnding == LineEnding::CRLF && lineBuffer.size() > 1 ) { lineBuffer[lineBuffer.size() - 2] = '\n'; lineBuffer.resize( lineBuffer.size() - 1 ); } @@ -175,9 +175,9 @@ void TextDocument::guessIndentType() { return; } if ( guessTabs > guessSpaces ) { - mIndentType = IndentTabs; + mIndentType = IndentType::IndentTabs; } else { - mIndentType = IndentSpaces; + mIndentType = IndentType::IndentSpaces; mIndentWidth = guessWidth.begin()->first; } } @@ -195,6 +195,30 @@ void TextDocument::setAutoDetectIndentType( bool autodetect ) { mAutoDetectIndentType = autodetect; } +const TextDocument::LineEnding& TextDocument::getLineEnding() const { + return mLineEnding; +} + +void TextDocument::setLineEnding( const LineEnding& lineEnding ) { + mLineEnding = lineEnding; +} + +bool TextDocument::getForceNewLineAtEndOfFile() const { + return mForceNewLineAtEndOfFile; +} + +void TextDocument::setForceNewLineAtEndOfFile( bool forceNewLineAtEndOfFile ) { + mForceNewLineAtEndOfFile = forceNewLineAtEndOfFile; +} + +bool TextDocument::getTrimTrailingWhitespaces() const { + return mTrimTrailingWhitespaces; +} + +void TextDocument::setTrimTrailingWhitespaces( bool trimTrailingWhitespaces ) { + mTrimTrailingWhitespaces = trimTrailingWhitespaces; +} + bool TextDocument::loadFromFile( const std::string& path ) { if ( !FileSystem::fileExists( path ) && PackManager::instance()->isFallbackToPacksActive() ) { std::string pathFix( path ); @@ -246,6 +270,7 @@ bool TextDocument::save( const std::string& path, const bool& utf8bom ) { bool TextDocument::save( IOStream& stream, const bool& utf8bom ) { if ( !stream.isOpen() || mLines.empty() ) return false; + const std::string whitespaces( " \t\f\v\n\r" ); char nl = '\n'; if ( utf8bom ) { unsigned char bom[] = {0xEF, 0xBB, 0xBF}; @@ -254,13 +279,30 @@ bool TextDocument::save( IOStream& stream, const bool& utf8bom ) { size_t lastLine = mLines.size() - 1; for ( size_t i = 0; i <= lastLine; i++ ) { std::string text( mLines[i].toUtf8() ); - if ( i == lastLine && !text.empty() && text[text.size() - 1] == '\n' ) { - // Last \n is added by the document but it's not part of the document. - text.pop_back(); - if ( text.empty() ) - continue; + if ( mTrimTrailingWhitespaces && text.size() > 1 && + whitespaces.find( text[text.size() - 2] ) != std::string::npos ) { + size_t pos = text.find_last_not_of( whitespaces ); + if ( pos != std::string::npos ) { + text.erase( pos + 1 ); + text += nl; + } else { + text = nl; + } + mLines[i].setText( text ); + notifyLineChanged( i ); + notifyTextChanged(); } - if ( mIsCLRF ) { + if ( i == lastLine ) { + if ( !text.empty() && text[text.size() - 1] == '\n' && !mForceNewLineAtEndOfFile ) { + // Last \n is added by the document but it's not part of the document. + text.pop_back(); + if ( text.empty() ) + continue; + } else if ( !text.empty() && text[text.size()] != '\n' && mForceNewLineAtEndOfFile ) { + text += mLineEnding == LineEnding::CRLF ? "\n\r" : "\n"; + } + } + if ( mLineEnding == LineEnding::CRLF ) { text[text.size() - 1] = '\r'; stream.write( text.c_str(), text.size() ); stream.write( &nl, 1 ); @@ -951,7 +993,7 @@ void TextDocument::appendLineIfLastLine( Int64 line ) { } String TextDocument::getIndentString() { - if ( IndentSpaces == mIndentType ) { + if ( IndentType::IndentSpaces == mIndentType ) { return String( std::string( mIndentWidth, ' ' ) ); } return String( "\t" ); @@ -1178,6 +1220,27 @@ const String& TextDocument::getNonWordChars() const { return mNonWordChars; } +void TextDocument::toggleLineComments() { + std::string comment = mSyntaxDefinition.getComment(); + if ( comment.empty() ) + return; + std::string commentText = comment + " "; + TextRange selection = getSelection( true ); + bool uncomment = true; + for ( Int64 i = selection.start().line(); i < selection.end().line(); i++ ) { + const String& text = mLines[i].getText(); + if ( text.find_first_not_of( " \t\n" ) != std::string::npos && + text.find( commentText ) == std::string::npos ) { + uncomment = false; + } + } + if ( uncomment ) { + removeFromStartOfSelectedLines( commentText, true ); + } else { + insertAtStartOfSelectedLines( commentText, true ); + } +} + void TextDocument::setNonWordChars( const String& nonWordChars ) { mNonWordChars = nonWordChars; } @@ -1257,6 +1320,7 @@ void TextDocument::initializeCommands() { mCommands["unindent"] = [&] { unindent(); }; mCommands["undo"] = [&] { undo(); }; mCommands["redo"] = [&] { redo(); }; + mCommands["toggle-line-comments"] = [&] { toggleLineComments(); }; } TextDocument::Client::~Client() {} diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 47a804a7c..bd91412fc 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -1653,6 +1653,7 @@ void UICodeEditor::registerKeybindings() { {{KEY_MINUS, KEYMOD_CTRL}, "font-size-shrink"}, {{KEY_KP_MINUS, KEYMOD_CTRL}, "font-size-shrink"}, {{KEY_0, KEYMOD_CTRL}, "font-size-reset"}, + {{KEY_KP_DIVIDE, KEYMOD_CTRL}, "toggle-line-comments" }, } ); } diff --git a/src/tools/codeeditor/codeeditor.cpp b/src/tools/codeeditor/codeeditor.cpp index 73d5b4483..0f89bec47 100644 --- a/src/tools/codeeditor/codeeditor.cpp +++ b/src/tools/codeeditor/codeeditor.cpp @@ -226,7 +226,15 @@ UICodeEditor* App::createCodeEditor() { codeEditor->getDocument().setCommand( "fullscreen-toggle", [&]() { mWindow->toggleFullscreen(); } ); codeEditor->getDocument().setCommand( "open-file", [&] { openFileDialog(); } ); - codeEditor->getDocument().setCommand( "console-toggle", [&] { mConsole->toggle(); } ); + codeEditor->getDocument().setCommand( "console-toggle", [&] { + mConsole->toggle(); + bool lock = mConsole->isActive(); + for ( auto tabW : mTabWidgets ) { + for ( size_t i = 0; i < tabW->getTabCount(); i++ ) { + tabW->getTab( i )->getOwnedWidget()->asType()->setLocked( lock ); + } + } + } ); codeEditor->getDocument().setCommand( "close-doc", [&] { tryTabClose( mCurEditor ); } ); codeEditor->getDocument().setCommand( "create-new", [&] { auto d = createCodeEditorInTabWidget( tabWidgetFromEditor( mCurEditor ) ); @@ -761,13 +769,14 @@ void App::mainLoop() { mUISceneNode->setDrawDebugData( !mUISceneNode->getDrawDebugData() ); } + Time elapsed = SceneManager::instance()->getElapsed(); SceneManager::instance()->update(); if ( SceneManager::instance()->getUISceneNode()->invalidated() || mConsole->isActive() || mConsole->isFading() ) { mWindow->clear(); SceneManager::instance()->draw(); - mConsole->draw(); + mConsole->draw( elapsed ); mWindow->display(); } else { Sys::sleep( Milliseconds( mWindow->hasFocus() ? 1 : 16 ) );