From 4146bfef6f3054227e76dcb5a985aafaab768e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Wed, 9 Apr 2025 21:22:17 -0300 Subject: [PATCH] Implemented tab-stops support (SpartanJ/ecode#55). Not enabled by default until I'm sure I did not break anything. --- include/eepp/graphics/text.hpp | 50 +++++--- include/eepp/ui/doc/documentview.hpp | 14 ++- include/eepp/ui/uicodeeditor.hpp | 26 ++-- src/eepp/graphics/text.cpp | 176 ++++++++++++++++++--------- src/eepp/ui/doc/documentview.cpp | 45 ++++--- src/eepp/ui/uicodeeditor.cpp | 125 ++++++++++++------- src/tools/ecode/appconfig.cpp | 2 + src/tools/ecode/appconfig.hpp | 1 + src/tools/ecode/ecode.cpp | 1 + src/tools/ecode/settingsmenu.cpp | 8 ++ 10 files changed, 299 insertions(+), 149 deletions(-) diff --git a/include/eepp/graphics/text.hpp b/include/eepp/graphics/text.hpp index 1395c27a3..613daeed4 100644 --- a/include/eepp/graphics/text.hpp +++ b/include/eepp/graphics/text.hpp @@ -6,6 +6,8 @@ #include #include +#include + namespace EE { namespace Graphics { enum class CharacterAlignment : Uint32 { Left = 0, Center = 1, Right = 2 }; @@ -15,6 +17,7 @@ struct WhitespaceDisplayConfig { String::StringBaseType tabDisplayCharacter{ 0 }; CharacterAlignment tabAlign{ CharacterAlignment::Center }; Color color; + std::optional tabOffset; }; class EE_API Text { @@ -30,23 +33,30 @@ class EE_API Text { Shadow = 1 << 4 ///< Draw a shadow below the text }; + static Float tabAdvance( Float spaceHorizontalAdvance, Uint32 tabLength, + std::optional tabOffset ); + static std::string styleFlagToString( const Uint32& flags ); static Uint32 stringToStyleFlag( const std::string& str ); static Float getTextWidth( Font* font, const Uint32& fontSize, const String& string, const Uint32& style, const Uint32& tabWidth = 4, - const Float& outlineThickness = 0.f, Uint32 textDrawHints = 0 ); + const Float& outlineThickness = 0.f, Uint32 textDrawHints = 0, + std::optional tabOffset = {} ); static Float getTextWidth( Font* font, const Uint32& fontSize, const String::View& string, const Uint32& style, const Uint32& tabWidth = 4, - const Float& outlineThickness = 0.f, Uint32 textDrawHints = 0 ); + const Float& outlineThickness = 0.f, Uint32 textDrawHints = 0, + std::optional tabOffset = {} ); static Float getTextWidth( const String& string, const FontStyleConfig& config, - const Uint32& tabWidth = 4, Uint32 textDrawHints = 0 ); + const Uint32& tabWidth = 4, Uint32 textDrawHints = 0, + std::optional tabOffset = {} ); static Float getTextWidth( const String::View& string, const FontStyleConfig& config, - const Uint32& tabWidth = 4, Uint32 textDrawHints = 0 ); + const Uint32& tabWidth = 4, Uint32 textDrawHints = 0, + std::optional tabOffset = {} ); static Sizef draw( const String& string, const Vector2f& pos, Font* font, Float fontSize, const Color& fontColor, Uint32 style = 0, Float outlineThickness = 0.f, @@ -86,38 +96,45 @@ class EE_API Text { static Int32 findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font* font, const Uint32& fontSize, const String& string, const Uint32& style, const Uint32& tabWidth = 4, - const Float& outlineThickness = 0.f ); + const Float& outlineThickness = 0.f, + std::optional tabOffset = {} ); static Vector2f findCharacterPos( std::size_t index, Font* font, const Uint32& fontSize, const String& string, const Uint32& style, const Uint32& tabWidth = 4, const Float& outlineThickness = 0.f, + std::optional tabOffset = {}, bool allowNewLine = true ); 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 Float& outlineThickness = 0.f, + std::optional tabOffset = {} ); 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 Float& outlineThickness = 0.f, + std::optional tabOffset = {} ); static std::size_t findLastCharPosWithinLength( const String& string, Float maxWidth, const FontStyleConfig& config, - const Uint32& tabWidth = 4 ); + const Uint32& tabWidth = 4, + std::optional tabOffset = {} ); static std::size_t findLastCharPosWithinLength( const String::View& string, Float maxWidth, const FontStyleConfig& config, - const Uint32& tabWidth = 4 ); + const Uint32& tabWidth = 4, + std::optional tabOffset = {} ); 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 ); + const Float& outlineThickness = 0.f, + std::optional tabOffset = {} ); static bool wrapText( String& string, const Float& maxWidth, const FontStyleConfig& config, - const Uint32& tabWidth = 4 ); + const Uint32& tabWidth = 4, std::optional tabOffset = {} ); static Text* New(); @@ -321,7 +338,8 @@ class EE_API Text { template static Float getTextWidth( Font* font, const Uint32& fontSize, const StringType& string, const Uint32& style, const Uint32& tabWidth = 4, - const Float& outlineThickness = 0.f, Uint32 textDrawHints = 0 ); + const Float& outlineThickness = 0.f, Uint32 textDrawHints = 0, + std::optional tabOffset = {} ); template static Sizef @@ -340,16 +358,18 @@ class EE_API Text { 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 ); + const Float& outlineThickness = 0.f, + std::optional tabOffset = {} ); 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 ); + const Float& outlineThickness = 0.f, + std::optional tabOffset = {} ); template static bool wrapText( StringType& string, const Float& maxWidth, const FontStyleConfig& config, - const Uint32& tabWidth = 4 ); + const Uint32& tabWidth = 4, std::optional tabOffset = {} ); }; }} // namespace EE::Graphics diff --git a/include/eepp/ui/doc/documentview.hpp b/include/eepp/ui/doc/documentview.hpp index 0f44dad07..f12e052a3 100644 --- a/include/eepp/ui/doc/documentview.hpp +++ b/include/eepp/ui/doc/documentview.hpp @@ -37,6 +37,7 @@ class EE_API DocumentView { bool keepIndentation{ true }; Uint32 tabWidth{ 4 }; std::optional maxCharactersWidth; + bool tabStops{ false }; bool operator==( const Config& other ) { return mode == other.mode && keepIndentation == other.keepIndentation && tabWidth == other.tabWidth && maxCharactersWidth == other.maxCharactersWidth; @@ -64,22 +65,23 @@ class EE_API DocumentView { computeLineBreaks( const String::View& string, const FontStyleConfig& fontStyle, Float maxWidth, LineWrapMode mode, bool keepIndentation, Uint32 tabWidth = 4, Float whiteSpaceWidth = 0.f /* 0 = should calculate it */, - Uint32 textHints = TextHints::None, Float initialXOffset = 0.f ); + Uint32 textHints = TextHints::None, bool tabStops = false, + Float initialXOffset = 0.f ); static LineWrapInfo computeLineBreaks( const String& string, const FontStyleConfig& fontStyle, Float maxWidth, LineWrapMode mode, bool keepIndentation, Uint32 tabWidth = 4, Float whiteSpaceWidth = 0.f, Uint32 textHints = TextHints::None, - Float initialXOffset = 0.f ); + bool tabStops = false, Float initialXOffset = 0.f ); static LineWrapInfo computeLineBreaks( const TextDocument& doc, size_t line, const FontStyleConfig& fontStyle, Float maxWidth, LineWrapMode mode, bool keepIndentation, Uint32 tabWidth = 4, Float whiteSpaceWidth = 0.f, - Float initialXOffset = 0.f ); + bool tabStops = false, Float initialXOffset = 0.f ); static Float computeOffsets( const String::View& string, const FontStyleConfig& fontStyle, - Uint32 tabWidth, Float maxWidth = 0.f ); + Uint32 tabWidth, Float maxWidth = 0.f, bool tabStops = false ); DocumentView( std::shared_ptr doc, FontStyleConfig fontStyle, Config config ); @@ -161,6 +163,10 @@ class EE_API DocumentView { void setOnFoldUnfoldCb( std::function onFoldUnfoldCb ); + void setTabStops( bool enabled ); + + bool usesTabStops() const { return mConfig.tabStops; } + protected: std::shared_ptr mDoc; FontStyleConfig mFontStyle; diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index 1d0682694..3f713efbd 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -535,11 +535,11 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { size_t characterWidth( const String& str ) const; - Float getTextWidth( const String& text ) const; + Float getTextWidth( const String& text, std::optional tabOffset = {} ) const; size_t characterWidth( const String::View& str ) const; - Float getTextWidth( const String::View& text ) const; + Float getTextWidth( const String::View& text, std::optional tabOffset ) const; Float getLineHeight() const; @@ -798,6 +798,10 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { bool toggleFoldUnfold( Int64 docLineIdx ); + void setTabStops( bool enabled ); + + bool usesTabStops() { return mTabStops; } + protected: struct LastXOffset { TextPosition position{ 0, 0 }; @@ -806,10 +810,6 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { Font* mFont; UIFontStyleConfig mFontStyleConfig; std::shared_ptr mDoc; - DocumentView mDocView; - Clock mBlinkTimer; - Time mBlinkTime; - Time mFoldsRefreshTime; bool mDirtyEditor{ false }; bool mDirtyScroll{ false }; bool mCursorVisible{ false }; @@ -848,6 +848,11 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { bool mEnableFlashCursor{ false }; bool mDisableCursorBlinkingAfterAMinuteOfInactivity{ true }; bool mAllowSelectingTextFromGutter{ true }; + bool mTabStops{ false }; + DocumentView mDocView; + Clock mBlinkTimer; + Time mBlinkTime; + Time mFoldsRefreshTime; Uint32 mTabWidth; std::atomic mHighlightWordProcessing{ false }; TextRange mLinkPosition; @@ -1114,11 +1119,14 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { template size_t characterWidth( const StringType& str ) const; template - Float getTextWidth( const StringType& text, bool fromMonospaceLine ) const; + Float getTextWidth( const StringType& text, bool fromMonospaceLine, + std::optional tabOffset ) const; - Float getTextWidth( const String& text, bool fromMonospaceLine ) const; + Float getTextWidth( const String& text, bool fromMonospaceLine, + std::optional tabOffset ) const; - Float getTextWidth( const String::View& text, bool fromMonospaceLine ) const; + Float getTextWidth( const String::View& text, bool fromMonospaceLine, + std::optional tabOffset ) const; void updateIMELocation(); diff --git a/src/eepp/graphics/text.cpp b/src/eepp/graphics/text.cpp index b475dceb6..f0bda50cc 100644 --- a/src/eepp/graphics/text.cpp +++ b/src/eepp/graphics/text.cpp @@ -156,6 +156,18 @@ shapeAndRun( const String& string, const FontStyleConfig& config, } // namespace +Float Text::tabAdvance( Float hspace, Uint32 tabWidth, std::optional tabOffset ) { + Float advance = hspace * tabWidth; + if ( tabOffset ) { + Float offset = fmodf( *tabOffset, advance ); + advance = advance - offset; + // If there is not enough space until the next stop, skip it + if ( advance < hspace ) + advance += hspace * tabWidth; + } + return advance; +} + bool Text::TextShaperEnabled = false; std::string Text::styleFlagToString( const Uint32& flags ) { @@ -216,28 +228,33 @@ Uint32 Text::stringToStyleFlag( const std::string& str ) { Float Text::getTextWidth( Font* font, const Uint32& fontSize, const String& string, const Uint32& style, const Uint32& tabWidth, - const Float& outlineThickness, Uint32 textDrawHints ) { + const Float& outlineThickness, Uint32 textDrawHints, + std::optional tabOffset ) { return getTextWidth( font, fontSize, string, style, tabWidth, outlineThickness, - textDrawHints ); + textDrawHints, tabOffset ); } Float Text::getTextWidth( Font* font, const Uint32& fontSize, const String::View& string, const Uint32& style, const Uint32& tabWidth, - const Float& outlineThickness, Uint32 textDrawHints ) { + const Float& outlineThickness, Uint32 textDrawHints, + std::optional tabOffset ) { return getTextWidth( font, fontSize, string, style, tabWidth, outlineThickness, - textDrawHints ); + textDrawHints, tabOffset ); } Float Text::getTextWidth( const String& string, const FontStyleConfig& config, - const Uint32& tabWidth, Uint32 textDrawHints ) { + const Uint32& tabWidth, Uint32 textDrawHints, + std::optional tabOffset ) { return getTextWidth( config.Font, config.CharacterSize, string, config.Style, tabWidth, - config.OutlineThickness, textDrawHints ); + config.OutlineThickness, textDrawHints, tabOffset ); } Float Text::getTextWidth( const String::View& string, const FontStyleConfig& config, - const Uint32& tabWidth, Uint32 textDrawHints ) { + const Uint32& tabWidth, Uint32 textDrawHints, + std::optional tabOffset ) { return getTextWidth( config.Font, config.CharacterSize, string, config.Style, - tabWidth, config.OutlineThickness, textDrawHints ); + tabWidth, config.OutlineThickness, textDrawHints, + tabOffset ); } Sizef Text::draw( const String& string, const Vector2f& pos, Font* font, Float fontSize, @@ -412,19 +429,9 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Flo GlyphDrawable* spaceGlyph = nullptr; GlyphDrawable* tabGlyph = nullptr; Float hspace = font->getGlyph( ' ', fontSize, isBold, isItalic ).advance; - if ( whitespaceDisplayConfig.tabDisplayCharacter ) { + std::optional tabOffset{ whitespaceDisplayConfig.tabOffset }; + if ( whitespaceDisplayConfig.tabDisplayCharacter ) tabGlyph = font->getGlyphDrawable( whitespaceDisplayConfig.tabDisplayCharacter, fontSize ); - switch ( whitespaceDisplayConfig.tabAlign ) { - case CharacterAlignment::Center: - tabAlign = ( ( tabWidth * hspace ) - tabGlyph->getPixelsSize().getWidth() ) * 0.5f; - break; - case CharacterAlignment::Right: - tabAlign = ( tabWidth * hspace ) - tabGlyph->getPixelsSize().getWidth(); - break; - case CharacterAlignment::Left: - break; - } - } BR->setBlendMode( BlendMode::Alpha() ); BR->quadsBegin(); @@ -445,12 +452,30 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Flo cluster = curGlyph.cluster; ch = string[cluster]; if ( ch == '\t' ) { + Float advance = tabAdvance( hspace, tabWidth, + tabOffset ? cpos.x - pos.x + *tabOffset + : std::optional{} ); + + if ( whitespaceDisplayConfig.tabDisplayCharacter ) { + switch ( whitespaceDisplayConfig.tabAlign ) { + case CharacterAlignment::Center: + tabAlign = + ( advance - tabGlyph->getPixelsSize().getWidth() ) * 0.5f; + break; + case CharacterAlignment::Right: + tabAlign = advance - tabGlyph->getPixelsSize().getWidth(); + break; + case CharacterAlignment::Left: + break; + } + } + if ( tabGlyph ) { drawGlyph( BR, tabGlyph, { cpos.x + tabAlign, cpos.y }, whitespaceDisplayConfig.color, isItalic ); } - width += hspace * tabWidth; - cpos.x += hspace * tabWidth; + width += advance; + cpos.x += advance; } else { if ( style & Text::Shadow ) { auto* gds = font->getGlyphDrawableFromGlyphIndex( @@ -552,7 +577,23 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Flo case '\r': continue; case '\t': { - Float advance = hspace * tabWidth; + Float advance = + tabAdvance( hspace, tabWidth, + tabOffset ? cpos.x - pos.x + *tabOffset : std::optional{} ); + + if ( whitespaceDisplayConfig.tabDisplayCharacter ) { + switch ( whitespaceDisplayConfig.tabAlign ) { + case CharacterAlignment::Center: + tabAlign = ( advance - tabGlyph->getPixelsSize().getWidth() ) * 0.5f; + break; + case CharacterAlignment::Right: + tabAlign = advance - tabGlyph->getPixelsSize().getWidth(); + break; + case CharacterAlignment::Left: + break; + } + } + if ( tabGlyph ) { drawGlyph( BR, tabGlyph, { cpos.x + tabAlign, cpos.y }, whitespaceDisplayConfig.color, isItalic ); @@ -657,10 +698,12 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, const FontStyle template bool Text::wrapText( Font* font, const Uint32& fontSize, StringType& string, const Float& maxWidth, - const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness ) { + const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness, + std::optional tabOffset ) { if ( string.empty() || NULL == font ) return false; + Float x = 0.f; Float tCurWidth = 0.f; Float tWordWidth = 0.f; auto tChar = &string[0]; @@ -680,12 +723,13 @@ bool Text::wrapText( Font* font, const Uint32& fontSize, StringType& string, con Float fCharWidth = (Float)pChar.advance; if ( ( *tChar ) == '\t' ) - fCharWidth = hspace * tabWidth; + fCharWidth = tabAdvance( hspace, tabWidth, tabOffset ? ( *tabOffset + x ) : tabOffset ); else if ( ( *tChar ) == '\r' ) fCharWidth = 0; // Add the new char width to the current word width tWordWidth += fCharWidth; + x += fCharWidth; if ( *tChar != '\r' ) { tWordWidth += @@ -707,9 +751,11 @@ bool Text::wrapText( Font* font, const Uint32& fontSize, StringType& string, con *tLastSpace = '\n'; tChar = tLastSpace + 1; wrapped = true; + x = 0.f; } else { // The word is larger than the current possible width *tChar = '\n'; wrapped = true; + x = 0.f; } if ( tChar == tLastChar ) @@ -727,6 +773,7 @@ bool Text::wrapText( Font* font, const Uint32& fontSize, StringType& string, con } else if ( '\n' == *tChar ) { tWordWidth = 0.f; tCurWidth = 0.f; + x = 0.f; tLastSpace = NULL; tChar++; } else { @@ -739,19 +786,21 @@ bool Text::wrapText( Font* font, const Uint32& fontSize, StringType& string, con template bool Text::wrapText( StringType& string, const Float& maxWidth, const FontStyleConfig& config, - const Uint32& tabWidth ) { + const Uint32& tabWidth, std::optional tabOffset ) { return wrapText( config.Font, config.CharacterSize, string, maxWidth, config.Style, - tabWidth, config.OutlineThickness ); + tabWidth, config.OutlineThickness, tabOffset ); } 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 ); + const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness, + std::optional tabOffset ) { + return wrapText( font, fontSize, string, maxWidth, style, tabWidth, outlineThickness, + tabOffset ); } bool Text::wrapText( String& string, const Float& maxWidth, const FontStyleConfig& config, - const Uint32& tabWidth ) { - return wrapText( string, maxWidth, config, tabWidth ); + const Uint32& tabWidth, std::optional tabOffset ) { + return wrapText( string, maxWidth, config, tabWidth, tabOffset ); } Text::Text() {} @@ -1004,7 +1053,8 @@ void Text::findWordFromCharacterIndex( Int32 characterIndex, Int32& initCur, Int template Float Text::getTextWidth( Font* font, const Uint32& fontSize, const StringType& string, const Uint32& style, const Uint32& tabWidth, - const Float& outlineThickness, Uint32 textDrawHints ) { + const Float& outlineThickness, Uint32 textDrawHints, + std::optional tabOffset ) { if ( NULL == font || string.empty() ) return 0; Float width = 0; @@ -1028,7 +1078,10 @@ Float Text::getTextWidth( Font* font, const Uint32& fontSize, const StringType& if ( string[i] == '\n' ) { width = 0; } else { - width += ( string[i] == '\t' ) ? hspace * tabWidth : hspace; + width += + ( string[i] == '\t' ) + ? tabAdvance( hspace, tabWidth, tabOffset ? *tabOffset + width : tabOffset ) + : hspace; } maxWidth = eemax( width, maxWidth ); } @@ -1047,7 +1100,8 @@ Float Text::getTextWidth( Font* font, const Uint32& fontSize, const StringType& hb_glyph_info_t curGlyph = glyphInfo[i]; auto curChar = string[curGlyph.cluster]; if ( curChar == '\t' ) { - width += hspace * tabWidth; + width += tabAdvance( hspace, tabWidth, + tabOffset ? *tabOffset + width : tabOffset ); } else { const Glyph& glyph = font->getGlyphByIndex( curGlyph.codepoint, fontSize, bold, italic, outlineThickness, @@ -1081,7 +1135,7 @@ Float Text::getTextWidth( Font* font, const Uint32& fontSize, const StringType& codepoint = string.at( i ); Glyph glyph = font->getGlyph( codepoint, fontSize, bold, italic, outlineThickness ); if ( codepoint == '\t' ) { - width += hspace * tabWidth; + width += tabAdvance( hspace, tabWidth, tabOffset ? *tabOffset + width : tabOffset ); } else if ( codepoint == '\n' ) { width = 0; } else if ( codepoint != '\r' ) { @@ -1096,10 +1150,10 @@ 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 ) { +std::size_t +Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSize, const StringType& string, + Float maxWidth, const Uint32& style, const Uint32& tabWidth, + const Float& outlineThickness, std::optional tabOffset ) { if ( NULL == font || string.empty() ) return 0; String::StringBaseType codepoint; @@ -1127,7 +1181,8 @@ std::size_t Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSiz auto curChar = string[curGlyph.cluster]; if ( curChar == '\t' ) { - width += hspace * tabWidth; + width += tabAdvance( hspace, tabWidth, + tabOffset ? *tabOffset + width : tabOffset ); } else { const Glyph& glyph = font->getGlyphByIndex( curGlyph.codepoint, fontSize, bold, italic, outlineThickness, @@ -1170,7 +1225,7 @@ std::size_t Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSiz font->getKerning( prevChar, codepoint, fontSize, bold, italic, outlineThickness ); width += glyph.advance; } else if ( codepoint == '\t' ) - width += hspace * tabWidth; + width += tabAdvance( hspace, tabWidth, tabOffset ? *tabOffset + width : tabOffset ); if ( width > maxWidth ) return i > 0 ? i - 1 : 0; @@ -1183,7 +1238,8 @@ std::size_t Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSiz Vector2f Text::findCharacterPos( std::size_t index, Font* font, const Uint32& fontSize, const String& string, const Uint32& style, const Uint32& tabWidth, - const Float& outlineThickness, bool allowNewLine ) { + const Float& outlineThickness, std::optional tabOffset, + bool allowNewLine ) { // Make sure that we have a valid font if ( !font ) return Vector2f(); @@ -1227,7 +1283,9 @@ Vector2f Text::findCharacterPos( std::size_t index, Font* font, const Uint32& fo auto curChar = string[curGlyph.cluster]; if ( curChar == '\t' ) { - position.x += hspace * tabWidth; + position.x += + tabAdvance( hspace, tabWidth, + tabOffset ? *tabOffset + position.x : tabOffset ); } else { const Glyph& glyph = font->getGlyphByIndex( curGlyph.codepoint, fontSize, bold, italic, outlineThickness, @@ -1281,7 +1339,8 @@ Vector2f Text::findCharacterPos( std::size_t index, Font* font, const Uint32& fo position.x += hspace; continue; case '\t': - position.x += hspace * tabWidth; + position.x += + tabAdvance( hspace, tabWidth, tabOffset ? *tabOffset + position.x : tabOffset ); continue; case '\n': if ( allowNewLine ) { @@ -1306,7 +1365,8 @@ Vector2f Text::findCharacterPos( std::size_t index, Font* font, const Uint32& fo Int32 Text::findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font* font, const Uint32& fontSize, const String& string, const Uint32& style, - const Uint32& tabWidth, const Float& outlineThickness ) { + const Uint32& tabWidth, const Float& outlineThickness, + std::optional tabOffset ) { if ( NULL == font ) return 0; @@ -1341,7 +1401,8 @@ Int32 Text::findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font* lWidth = width; if ( curChar == '\t' ) { - width += hspace * tabWidth; + width += tabAdvance( hspace, tabWidth, + tabOffset ? *tabOffset + width : tabOffset ); } else { const Glyph& glyph = font->getGlyphByIndex( curGlyph.codepoint, fontSize, bold, italic, outlineThickness, @@ -1419,7 +1480,7 @@ Int32 Text::findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font* prevChar = codepoint; width += glyph.advance; } else if ( codepoint == '\t' ) { - width += hspace * tabWidth; + width += tabAdvance( hspace, tabWidth, tabOffset ? *tabOffset + width : tabOffset ); } if ( codepoint == '\n' ) { lWidth = 0; @@ -1462,32 +1523,37 @@ 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 Float& outlineThickness, + std::optional tabOffset ) { return findLastCharPosWithinLength( font, fontSize, string, maxWidth, style, tabWidth, - outlineThickness ); + outlineThickness, tabOffset ); } 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 Float& outlineThickness, + std::optional tabOffset ) { return findLastCharPosWithinLength( font, fontSize, string, maxWidth, style, - tabWidth, outlineThickness ); + tabWidth, outlineThickness, tabOffset ); } std::size_t Text::findLastCharPosWithinLength( const String& string, Float maxWidth, const FontStyleConfig& config, - const Uint32& tabWidth ) { + const Uint32& tabWidth, + std::optional tabOffset ) { return findLastCharPosWithinLength( config.Font, config.CharacterSize, string, maxWidth, - config.Style, tabWidth, config.OutlineThickness ); + config.Style, tabWidth, config.OutlineThickness, + tabOffset ); } std::size_t Text::findLastCharPosWithinLength( const String::View& string, Float maxWidth, const FontStyleConfig& config, - const Uint32& tabWidth ) { + const Uint32& tabWidth, + std::optional tabOffset ) { return findLastCharPosWithinLength( config.Font, config.CharacterSize, string, maxWidth, config.Style, tabWidth, - config.OutlineThickness ); + config.OutlineThickness, tabOffset ); } void Text::updateWidthCache() { diff --git a/src/eepp/ui/doc/documentview.cpp b/src/eepp/ui/doc/documentview.cpp index 374429977..28b45aef1 100644 --- a/src/eepp/ui/doc/documentview.cpp +++ b/src/eepp/ui/doc/documentview.cpp @@ -50,13 +50,12 @@ std::string DocumentView::fromLineWrapType( LineWrapType type ) { } Float DocumentView::computeOffsets( const String::View& string, const FontStyleConfig& fontStyle, - Uint32 tabWidth, Float maxWidth ) { - + Uint32 tabWidth, Float maxWidth, bool tabStops ) { static const String sepSpaces = " \t\n\v\f\r"; auto nonIndentPos = string.find_first_not_of( sepSpaces.data() ); if ( nonIndentPos != String::View::npos ) { Float w = Text::getTextWidth( string.substr( 0, nonIndentPos ), fontStyle, tabWidth, - TextHints::AllAscii ); + TextHints::AllAscii, tabStops ? 0 : std::optional{} ); return maxWidth != 0.f ? ( w > maxWidth ? 0.f : w ) : w; } return 0.f; @@ -67,7 +66,7 @@ DocumentView::LineWrapInfo DocumentView::computeLineBreaks( const String::View& Float maxWidth, LineWrapMode mode, bool keepIndentation, Uint32 tabWidth, Float whiteSpaceWidth, Uint32 textHints, - Float initialXOffset ) { + bool tabStops, Float initialXOffset ) { LineWrapInfo info; info.wraps.push_back( 0 ); if ( string.empty() || nullptr == fontStyle.Font || mode == LineWrapMode::NoWrap ) @@ -83,8 +82,8 @@ DocumentView::LineWrapInfo DocumentView::computeLineBreaks( const String::View& : whiteSpaceWidth; if ( keepIndentation ) { - info.paddingStart = - computeOffsets( string, fontStyle, tabWidth, eemax( maxWidth - hspace, hspace ) ); + info.paddingStart = computeOffsets( string, fontStyle, tabWidth, + eemax( maxWidth - hspace, hspace ), tabStops ); } Float xoffset = initialXOffset; @@ -108,7 +107,7 @@ DocumentView::LineWrapInfo DocumentView::computeLineBreaks( const String::View& : hspace; if ( curChar == '\t' ) - w = hspace * tabWidth; + w = Text::tabAdvance( hspace, tabWidth, tabStops ? w : std::optional{} ); if ( !isMonospace && curChar != '\r' ) { w += fontStyle.Font->getKerning( prevChar, curChar, fontStyle.CharacterSize, bold, @@ -143,22 +142,22 @@ DocumentView::LineWrapInfo DocumentView::computeLineBreaks( const String& string Float maxWidth, LineWrapMode mode, bool keepIndentation, Uint32 tabWidth, Float whiteSpaceWidth, Uint32 textHints, - Float initialXOffset ) { + bool tabStops, Float initialXOffset ) { return computeLineBreaks( string.view(), fontStyle, maxWidth, mode, keepIndentation, tabWidth, - whiteSpaceWidth, textHints, initialXOffset ); + whiteSpaceWidth, textHints, tabStops, initialXOffset ); } DocumentView::LineWrapInfo DocumentView::computeLineBreaks( const TextDocument& doc, size_t line, const FontStyleConfig& fontStyle, Float maxWidth, LineWrapMode mode, bool keepIndentation, Uint32 tabWidth, - Float whiteSpaceWidth, + Float whiteSpaceWidth, bool tabStops, Float initialXOffset ) { const auto& docLine = doc.line( line ); const auto& text = docLine.getText(); return computeLineBreaks( text.view().substr( 0, text.size() - 1 ), fontStyle, maxWidth, mode, keepIndentation, tabWidth, whiteSpaceWidth, docLine.getTextHints(), - initialXOffset ); + tabStops, initialXOffset ); } DocumentView::DocumentView( std::shared_ptr doc, FontStyleConfig fontStyle, @@ -260,7 +259,7 @@ void DocumentView::invalidateCache() { } else { auto lb = wrap ? computeLineBreaks( *mDoc, i, mFontStyle, mMaxWidth, mConfig.mode, mConfig.keepIndentation, mConfig.tabWidth, - mWhiteSpaceWidth ) + mWhiteSpaceWidth, mConfig.tabStops ) : LineWrapInfo{ { 0 }, 0.f }; mVisibleLinesOffset.emplace_back( lb.paddingStart ); bool first = true; @@ -526,9 +525,9 @@ void DocumentView::updateCache( Int64 fromLine, Int64 toLine, Int64 numLines ) { eemax( mMaxWidth - mWhiteSpaceWidth, mWhiteSpaceWidth ) ) ); mDocLineToVisibleIndex[i] = static_cast( VisibleIndex::invalid ); } else { - auto lb = - computeLineBreaks( *mDoc, i, mFontStyle, mMaxWidth, mConfig.mode, - mConfig.keepIndentation, mConfig.tabWidth, mWhiteSpaceWidth ); + auto lb = computeLineBreaks( *mDoc, i, mFontStyle, mMaxWidth, mConfig.mode, + mConfig.keepIndentation, mConfig.tabWidth, + mWhiteSpaceWidth, mConfig.tabStops ); mVisibleLinesOffset.insert( mVisibleLinesOffset.begin() + i, lb.paddingStart ); @@ -680,10 +679,11 @@ void DocumentView::changeVisibility( Int64 fromDocIdx, Int64 toDocIdx, bool visi } continue; } - auto lb = isWrapEnabled() ? computeLineBreaks( *mDoc, i, mFontStyle, mMaxWidth, - mConfig.mode, mConfig.keepIndentation, - mConfig.tabWidth, mWhiteSpaceWidth ) - : LineWrapInfo{ { 0 }, 0 }; + auto lb = isWrapEnabled() + ? computeLineBreaks( *mDoc, i, mFontStyle, mMaxWidth, mConfig.mode, + mConfig.keepIndentation, mConfig.tabWidth, + mWhiteSpaceWidth, mConfig.tabStops ) + : LineWrapInfo{ { 0 }, 0 }; if ( recomputeOffset ) mVisibleLinesOffset[i] = lb.paddingStart; for ( const auto& col : lb.wraps ) { @@ -824,4 +824,11 @@ void DocumentView::setOnFoldUnfoldCb( mOnFoldUnfoldCb = std::move( onFoldUnfoldCb ); } +void DocumentView::setTabStops( bool enabled ) { + if ( enabled != mConfig.tabStops ) { + mConfig.tabStops = enabled; + invalidateCache(); + } +} + }}} // namespace EE::UI::Doc diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 8bf52735e..eee93bd84 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -122,7 +122,7 @@ UICodeEditor::UICodeEditor( const std::string& elementTag, const bool& autoRegis UIWidget( elementTag ), mFont( FontManager::instance()->getByName( "monospace" ) ), mDoc( std::make_shared() ), - mDocView( mDoc, mFontStyleConfig, {} ), + mDocView( mDoc, mFontStyleConfig, { .tabStops = mTabStops } ), mBlinkTime( Seconds( 0.5f ) ), mFoldsRefreshTime( Seconds( 2.f ) ), mTabWidth( 4 ), @@ -2019,12 +2019,13 @@ Float UICodeEditor::getLineWidth( const Int64& docLine ) { auto found = mLinesWidthCache.find( docLine ); if ( found != mLinesWidthCache.end() && line.getHash() == found->second.first ) return found->second.second; - Float width = getTextWidth( line.getText() ); + Float width = getTextWidth( line.getText(), {}, mTabStops ? 0 : std::optional{} ); mLinesWidthCache[docLine] = { line.getHash(), width }; return width; } - return getTextWidth( mDoc->line( docLine ).getText(), isMonospaceLine ); + return getTextWidth( mDoc->line( docLine ).getText(), isMonospaceLine, + mTabStops ? 0 : std::optional{} ); } void UICodeEditor::updateScrollBar() { @@ -2455,7 +2456,8 @@ Vector2d UICodeEditor::getTextPositionOffset( const TextPosition& position, Float x = Text::findCharacterPos( position.column() - info.range.start().column(), mFont, getCharacterSize(), partialLine, mFontStyleConfig.Style, - mTabWidth, mFontStyleConfig.OutlineThickness, false ) + mTabWidth, mFontStyleConfig.OutlineThickness, + mTabStops ? 0 : std::optional(), false ) .x; if ( visualizeNewLine && allowVisualLineEnd && position.column() == (Int64)mDoc->line( position.line() ).getText().size() - 1 ) @@ -2468,7 +2470,8 @@ Vector2d UICodeEditor::getTextPositionOffset( const TextPosition& position, Int64 maxCol = eemin( position.column(), info.range.end().column() ); for ( auto i = info.range.start().column(); i < maxCol; i++ ) { if ( line[i] == '\t' ) { - x += glyphWidth * mTabWidth; + x += Text::tabAdvance( glyphWidth, mTabWidth, + mTabStops ? x : std::optional{} ); } else if ( visualizeNewLine || ( line[i] != '\n' && line[i] != '\r' ) ) { x += glyphWidth; } @@ -2483,11 +2486,12 @@ Vector2d UICodeEditor::getTextPositionOffset( const TextPosition& position, if ( !isMonospaceLine( position.line() ) ) { bool isLastChar = position.column() == (Int64)mDoc->line( position.line() ).getText().size(); - Float x = Text::findCharacterPos( - isLastChar ? position.column() - 1 : position.column(), mFont, - getCharacterSize(), mDoc->line( position.line() ).getText(), - mFontStyleConfig.Style, mTabWidth, mFontStyleConfig.OutlineThickness, false ) - .x; + Float x = + Text::findCharacterPos( + isLastChar ? position.column() - 1 : position.column(), mFont, getCharacterSize(), + mDoc->line( position.line() ).getText(), mFontStyleConfig.Style, mTabWidth, + mFontStyleConfig.OutlineThickness, mTabStops ? 0 : std::optional(), false ) + .x; if ( visualizeNewLine && isLastChar ) x += getGlyphWidth(); return { x, offsetY }; @@ -2499,7 +2503,7 @@ Vector2d UICodeEditor::getTextPositionOffset( const TextPosition& position, Int64 maxCol = eemin( (Int64)line.size(), position.column() ); for ( auto i = 0; i < maxCol; i++ ) { if ( line[i] == '\t' ) { - x += glyphWidth * mTabWidth; + x += Text::tabAdvance( glyphWidth, mTabWidth, mTabStops ? x : std::optional{} ); } else if ( visualizeNewLine || ( line[i] != '\n' && line[i] != '\r' ) ) { x += glyphWidth; } @@ -2519,31 +2523,34 @@ size_t UICodeEditor::characterWidth( const String& str ) const { return characterWidth( str ); } -Float UICodeEditor::getTextWidth( const String& text ) const { - return getTextWidth( text, false ); +Float UICodeEditor::getTextWidth( const String& text, std::optional tabOffset ) const { + return getTextWidth( text, false, tabOffset ); } size_t UICodeEditor::characterWidth( const String::View& str ) const { return characterWidth( str ); } -Float UICodeEditor::getTextWidth( const String::View& text ) const { - return getTextWidth( text, false ); +Float UICodeEditor::getTextWidth( const String::View& text, std::optional tabOffset ) const { + return getTextWidth( text, false, tabOffset ); } -Float UICodeEditor::getTextWidth( const String& text, bool fromMonospaceLine ) const { - return getTextWidth( text, fromMonospaceLine ); +Float UICodeEditor::getTextWidth( const String& text, bool fromMonospaceLine, + std::optional tabOffset ) const { + return getTextWidth( text, fromMonospaceLine, tabOffset ); } -Float UICodeEditor::getTextWidth( const String::View& text, bool fromMonospaceLine ) const { - return getTextWidth( text, fromMonospaceLine ); +Float UICodeEditor::getTextWidth( const String::View& text, bool fromMonospaceLine, + std::optional tabOffset ) const { + return getTextWidth( text, fromMonospaceLine, tabOffset ); } template -Float UICodeEditor::getTextWidth( const StringType& line, bool fromMonospaceLine ) const { +Float UICodeEditor::getTextWidth( const StringType& line, bool fromMonospaceLine, + std::optional tabOffset ) const { if ( !fromMonospaceLine && isNotMonospace() ) { return Text::getTextWidth( mFont, getCharacterSize(), line, mFontStyleConfig.Style, - mTabWidth ); + mTabWidth, 0.f, 0, tabOffset ); } Float glyphWidth = getGlyphWidth(); @@ -2551,7 +2558,11 @@ Float UICodeEditor::getTextWidth( const StringType& line, bool fromMonospaceLine line.length() ? ( line[line.length() - 1] == '\n' ? line.length() - 1 : line.length() ) : 0; Float x = 0; for ( size_t i = 0; i < len; i++ ) - x += ( line[i] == '\t' ) ? glyphWidth * mTabWidth : glyphWidth; + x += + ( line[i] == '\t' ) + ? Text::tabAdvance( glyphWidth, mTabWidth, + mTabStops ? ( x + ( tabOffset ? *tabOffset : 0 ) ) : tabOffset ) + : glyphWidth; return x; } @@ -3110,50 +3121,51 @@ Int64 UICodeEditor::getColFromXOffset( VisibleIndex visibleIndex, const Float& x return visibleIndexRange.start().column() + Text::findCharacterFromPos( Vector2i( eemax( -xOffset + x, 0.f ), 0 ), true, mFont, getCharacterSize(), line, - mFontStyleConfig.Style, mTabWidth ); + mFontStyleConfig.Style, mTabWidth, 0.f, + mTabStops ? 0 : std::optional() ); } Int64 len = line.length(); Float glyphWidth = getGlyphWidth(); - Float tabWidth = glyphWidth * mTabWidth; - Float hTab = tabWidth * 0.5f; for ( int i = 0; i < len; i++ ) { bool isTab = ( line[i] == '\t' ); + Float advance = isTab ? Text::tabAdvance( glyphWidth, mTabWidth, + mTabStops ? xOffset : std::optional{} ) + : glyphWidth; if ( xOffset >= x ) { - auto col = xOffset - x > ( isTab ? hTab : glyphWidth * 0.5f ) - ? eemax( 0, i - 1 ) - : i; + auto col = xOffset - x > advance * 0.5f ? eemax( 0, i - 1 ) : i; return visibleIndexRange.start().column() + col; - } else if ( isTab && ( xOffset + tabWidth > x ) ) { - auto col = x - xOffset > hTab ? eemin( i + 1, line.size() - 1 ) : i; + } else if ( isTab && ( xOffset + advance > x ) ) { + auto col = + x - xOffset > ( advance * 0.5f ) ? eemin( i + 1, line.size() - 1 ) : i; return visibleIndexRange.start().column() + col; } - xOffset += isTab ? tabWidth : glyphWidth; + xOffset += advance; } return visibleIndexRange.start().column() + static_cast( line.size() ) - 1; } if ( !isMonospaceLine( pos.line() ) ) { - return Text::findCharacterFromPos( Vector2i( x, 0 ), true, mFont, getCharacterSize(), - mDoc->line( pos.line() ).getText(), - mFontStyleConfig.Style, mTabWidth ); + return Text::findCharacterFromPos( + Vector2i( x, 0 ), true, mFont, getCharacterSize(), mDoc->line( pos.line() ).getText(), + mFontStyleConfig.Style, mTabWidth, 0.f, mTabStops ? 0 : std::optional() ); } const String& line = mDoc->line( pos.line() ).getText(); Int64 len = line.length(); Float glyphWidth = getGlyphWidth(); Float xOffset = 0; - Float tabWidth = glyphWidth * mTabWidth; - Float hTab = tabWidth * 0.5f; for ( int i = 0; i < len; i++ ) { bool isTab = ( line[i] == '\t' ); + Float advance = isTab ? Text::tabAdvance( glyphWidth, mTabWidth, + mTabStops ? xOffset : std::optional{} ) + : glyphWidth; if ( xOffset >= x ) { - return xOffset - x > ( isTab ? hTab : glyphWidth * 0.5f ) ? eemax( 0, i - 1 ) - : i; - } else if ( isTab && ( xOffset + tabWidth > x ) ) { - return x - xOffset > hTab ? eemin( i + 1, line.size() - 1 ) : i; + return xOffset - x > ( advance * 0.5f ) ? eemax( 0, i - 1 ) : i; + } else if ( isTab && ( xOffset + advance > x ) ) { + return x - xOffset > ( advance * 0.5f ) ? eemin( i + 1, line.size() - 1 ) : i; } - xOffset += isTab ? tabWidth : glyphWidth; + xOffset += advance; } return static_cast( line.size() ) - 1; } @@ -3822,6 +3834,7 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo bool ended = false; Float lineOffset = getLineOffset(); size_t pos = 0; + Float initX = position.x; Uint32 drawHints = docLine.getTextHints(); if ( mDoc->mightBeBinary() && mFont->getType() == FontType::TTF ) { FontTrueType* ttf = static_cast( mFont ); @@ -3832,7 +3845,8 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo } WhitespaceDisplayConfig whitespaceDisplayConfig = mShowWhitespaces ? WhitespaceDisplayConfig{ L'ยท', mTabIndentCharacter, mTabIndentAlignment, - Color( mWhitespaceColor ).blendAlpha( mAlpha ) } + Color( mWhitespaceColor ).blendAlpha( mAlpha ), + std::optional{} } : WhitespaceDisplayConfig{}; String::View buff; @@ -3869,12 +3883,17 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo if ( style.background != Color::Transparent ) { primitives.setColor( Color( style.background ).blendAlpha( mAlpha ) ); - primitives.drawRectangle( - Rectf( position, - Sizef( getTextWidth( text, isMonospace ), lineHeight ) ) ); + primitives.drawRectangle( Rectf( + position, Sizef( getTextWidth( text, isMonospace, + mTabStops ? position.x - initX + : std::optional{} ), + lineHeight ) ) ); } } + if ( mTabStops ) + whitespaceDisplayConfig.tabOffset = position.x - initX; + position.x += Text::draw( text, { position.x, position.y + lineOffset }, fontStyle, mTabWidth, drawHints, whitespaceDisplayConfig ) @@ -3913,7 +3932,12 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo } pos += token.len; - Float textWidth = isMonospace ? getTextWidth( text, isMonospace ) : 0; + if ( mTabStops ) + whitespaceDisplayConfig.tabOffset = position.x - initX; + + Float textWidth = + isMonospace ? getTextWidth( text, isMonospace, whitespaceDisplayConfig.tabOffset ) + : 0; if ( !isMonospace || ( position.x + textWidth >= mScreenPos.x && position.x <= mScreenPos.x + mSize.getWidth() ) ) { Int64 curCharsWidth = text.size(); @@ -4399,7 +4423,7 @@ void UICodeEditor::drawIndentationGuides( const DocumentLineRange& lineRange, p.setForceDraw( false ); Float w = eefloor( PixelDensity::dpToPx( 1 ) ); String idt( mDoc->getIndentString() ); - auto spaceW = getTextWidth( String( " " ) ); + auto spaceW = getTextWidth( String( " " ), {} ); p.setColor( Color( mWhitespaceColor ).blendAlpha( mAlpha ) ); auto indentSize = mDoc->getIndentType() == TextDocument::IndentType::IndentTabs ? getTabWidth() @@ -5352,4 +5376,11 @@ bool UICodeEditor::allowSelectingTextFromGutter() const { return mAllowSelectingTextFromGutter; } +void UICodeEditor::setTabStops( bool enabled ) { + if ( mTabStops != enabled ) { + mTabStops = enabled; + mDocView.setTabStops( enabled ); + } +} + }} // namespace EE::UI diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp index 2e29810bd..4c513d26b 100644 --- a/src/tools/ecode/appconfig.cpp +++ b/src/tools/ecode/appconfig.cpp @@ -141,6 +141,7 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath, doc.writeUnicodeBOM = ini.getValueB( "document", "write_bom", false ); doc.indentWidth = ini.getValueI( "document", "indent_width", 4 ); doc.indentSpaces = ini.getValueB( "document", "indent_spaces", false ); + doc.tabStops = ini.getValueB( "document", "tab_stops", false ); doc.lineEndings = TextFormat::stringToLineEnding( ini.getValue( "document", "line_endings", "LF" ) ); // Migrate old data @@ -303,6 +304,7 @@ void AppConfig::save( const std::vector& recentFiles, ini.setValueB( "document", "auto_detect_indent_type", doc.autoDetectIndentType ); ini.setValueB( "document", "write_bom", doc.writeUnicodeBOM ); ini.setValueI( "document", "indent_width", doc.indentWidth ); + ini.setValueB( "document", "tab_stops", doc.tabStops ); ini.setValueB( "document", "indent_spaces", doc.indentSpaces ); ini.setValue( "document", "line_endings", TextFormat::lineEndingToString( doc.lineEndings ) ); ini.setValueI( "document", "tab_width", doc.tabWidth ); diff --git a/src/tools/ecode/appconfig.hpp b/src/tools/ecode/appconfig.hpp index 0a878f419..7775a01b1 100644 --- a/src/tools/ecode/appconfig.hpp +++ b/src/tools/ecode/appconfig.hpp @@ -98,6 +98,7 @@ struct DocumentConfig { bool autoDetectIndentType{ true }; bool writeUnicodeBOM{ false }; bool indentSpaces{ false }; + bool tabStops{ false }; TextFormat::LineEnding lineEndings{ TextFormat::LineEnding::LF }; int indentWidth{ 4 }; int tabWidth{ 4 }; diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 306c72671..5fed0e04c 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -2368,6 +2368,7 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { editor->setFoldDrawable( findIcon( "chevron-down", PixelDensity::dpToPxI( 12 ) ) ); editor->setFoldedDrawable( findIcon( "chevron-right", PixelDensity::dpToPxI( 12 ) ) ); editor->setEnableFlashCursor( config.flashCursor ); + editor->setTabStops( mConfig.doc.tabStops ); doc.setAutoCloseBrackets( !mConfig.editor.autoCloseBrackets.empty() ); doc.setAutoCloseBracketsPairs( makeAutoClosePairs( mConfig.editor.autoCloseBrackets ) ); diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp index 6f3691d66..46c53e024 100644 --- a/src/tools/ecode/settingsmenu.cpp +++ b/src/tools/ecode/settingsmenu.cpp @@ -634,6 +634,9 @@ UIMenu* SettingsMenu::createDocumentMenu() { mApp->getConfig().doc.trimTrailingWhitespaces ) ->setId( "trim_whitespaces" ); + mGlobalMenu->addCheckBox( i18n( "tab_stops", "Tab Stops" ), mApp->getConfig().doc.tabStops ) + ->setId( "tab_stops" ); + mGlobalMenu ->addCheckBox( i18n( "force_new_line_at_end_of_file", "Force New Line at End of File" ), mApp->getConfig().doc.forceNewLineAtEndOfFile ) @@ -730,6 +733,11 @@ UIMenu* SettingsMenu::createDocumentMenu() { mApp->getConfig().workspace.sessionSnapshot = item->isActive(); } else if ( "allow_flash_cursor" == id ) { mApp->getConfig().editor.flashCursor = item->isActive(); + } else if ( "tab_stops" == id ) { + mApp->getConfig().doc.tabStops = item->isActive(); + mSplitter->forEachEditor( [this]( UICodeEditor* editor ) { + editor->setTabStops( mApp->getConfig().doc.tabStops ); + } ); } } else if ( "line_breaking_column" == id ) { mApp->getSettingsActions()->setLineBreakingColumn();