diff --git a/include/eepp/core/string.hpp b/include/eepp/core/string.hpp index 410b14b3d..a88cea5f3 100644 --- a/include/eepp/core/string.hpp +++ b/include/eepp/core/string.hpp @@ -180,11 +180,20 @@ class EE_API String { static std::string_view lTrim( const std::string_view& str, char character = ' ' ); /** Removes the trailing suffix. */ - static std::string_view rTrim( const std::string_view& str, char character ); + static std::string_view rTrim( const std::string_view& str, char character = ' ' ); /** Removes all spaces ( or the specified character ) on the string */ static std::string_view trim( const std::string_view& str, char character = ' ' ); + /** Removes the trailing prefix. */ + static String::View lTrim( const String::View& str, char character = ' ' ); + + /** Removes the trailing suffix. */ + static String::View rTrim( const String::View& str, char character = ' ' ); + + /** Removes all spaces ( or the specified character ) on the string */ + static String::View trim( const String::View& str, char character = ' ' ); + /** Removes all spaces ( or the specified character ) on the string */ static void trimInPlace( std::string& str, char character = ' ' ); diff --git a/include/eepp/ui/doc/foldrangetype.hpp b/include/eepp/ui/doc/foldrangetype.hpp index 6d2874f87..a0707bb08 100644 --- a/include/eepp/ui/doc/foldrangetype.hpp +++ b/include/eepp/ui/doc/foldrangetype.hpp @@ -3,7 +3,7 @@ namespace EE { namespace UI { namespace Doc { -enum class FoldRangeType { Braces, Indentation, Tag, Undefined }; +enum class FoldRangeType { Braces, Indentation, Tag, Markdown, Undefined }; }}} // namespace EE::UI::Doc diff --git a/src/eepp/core/string.cpp b/src/eepp/core/string.cpp index 486bf401b..bea9c649f 100644 --- a/src/eepp/core/string.cpp +++ b/src/eepp/core/string.cpp @@ -989,6 +989,23 @@ std::string_view String::trim( const std::string_view& str, char character ) { pos2 == std::string::npos ? str.length() - 1 : pos2 - pos1 + 1 ); } +String::View String::lTrim( const String::View& str, char character ) { + String::View::size_type pos1 = str.find_first_not_of( character ); + return ( pos1 == String::View::npos ) ? str : str.substr( pos1 ); +} + +String::View String::rTrim( const String::View& str, char character ) { + String::View::size_type pos1 = str.find_last_not_of( character ); + return ( pos1 == String::View::npos ) ? str : str.substr( 0, pos1 + 1 ); +} + +String::View String::trim( const String::View& str, char character ) { + String::View::size_type pos1 = str.find_first_not_of( character ); + String::View::size_type pos2 = str.find_last_not_of( character ); + return str.substr( pos1 == String::View::npos ? 0 : pos1, + pos2 == String::View::npos ? str.length() - 1 : pos2 - pos1 + 1 ); +} + void String::trimInPlace( std::string& str, char character ) { // Trim only if there's something to trim if ( !str.empty() && ( str[0] == character || str[str.size() - 1] == character ) ) diff --git a/src/eepp/ui/doc/foldrangeservice.cpp b/src/eepp/ui/doc/foldrangeservice.cpp index 76b252ad5..e677ec835 100644 --- a/src/eepp/ui/doc/foldrangeservice.cpp +++ b/src/eepp/ui/doc/foldrangeservice.cpp @@ -99,6 +99,99 @@ static std::vector findFoldingRangesIndentation( TextDocument* doc ) return regions; } +static std::vector findFoldingRangesMarkdown( TextDocument* doc ) { + Clock c; // For performance logging, consistent with existing functions + std::vector regions; + + // Early return for small documents, as in other folding functions + if ( doc->linesCount() <= 2 ) + return regions; + + // Stack to track open heading sections: (line number, heading level) + std::vector> sectionStack; + + // State for code blocks + bool inCodeBlock = false; + Int64 codeBlockStart = -1; + + // Process each line + for ( size_t lineIdx = 0; lineIdx < doc->linesCount(); lineIdx++ ) { + const String& lineText = doc->line( lineIdx ).getText(); + String::View trimmed = + String::trim( lineText.view() ); // Remove leading and trailing whitespace + + if ( inCodeBlock ) { + // Check for code block end + if ( String::startsWith( trimmed, "```" ) ) { + // Ensure there's content to fold between start and end + if ( codeBlockStart != -1 && codeBlockStart <= static_cast( lineIdx ) - 1 ) { + regions.emplace_back( TextPosition( codeBlockStart, 0 ), // Start at opening ``` + TextPosition( lineIdx - 1, 0 ) // End before closing ``` + ); + } + inCodeBlock = false; + codeBlockStart = -1; + } + // Continue to next line if still in code block + } else { + if ( String::startsWith( trimmed, "```" ) ) { + // Start a new code block + inCodeBlock = true; + codeBlockStart = static_cast( lineIdx ); + } else if ( String::startsWith( trimmed, "#" ) ) { + // Check if it's a valid heading + size_t hashCount = 0; + while ( hashCount < trimmed.size() && trimmed[hashCount] == '#' ) + hashCount++; + // Valid heading: 1-6 # symbols followed by a space + if ( hashCount <= 6 && hashCount < trimmed.size() && trimmed[hashCount] == ' ' ) { + int level = static_cast( hashCount ); + + // Close sections with equal or greater level + while ( !sectionStack.empty() && sectionStack.back().second >= level ) { + auto [headingLine, _] = sectionStack.back(); + sectionStack.pop_back(); + // Create folding range if there's content to fold + if ( headingLine < static_cast( lineIdx ) ) { + regions.emplace_back( + TextPosition( headingLine, 0 ), // Start at heading + TextPosition( lineIdx - 1, 0 ) // End before next heading + ); + } + } + // Open new section + sectionStack.emplace_back( static_cast( lineIdx ), level ); + } + } + } + } + + // Close remaining open heading sections + while ( !sectionStack.empty() ) { + auto [headingLine, _] = sectionStack.back(); + sectionStack.pop_back(); + if ( headingLine < static_cast( doc->linesCount() ) ) { + regions.emplace_back( TextPosition( headingLine, 0 ), // Start at heading + TextPosition( doc->linesCount() - 1, 0 ) // End at document end + ); + } + } + + // Close any unterminated code block + if ( inCodeBlock && codeBlockStart != -1 && + codeBlockStart < static_cast( doc->linesCount() ) ) { + regions.emplace_back( TextPosition( codeBlockStart, 0 ), // Start at opening ``` + TextPosition( doc->linesCount() - 1, 0 ) // End at document end + ); + } + + // Log performance, matching style of existing functions + Log::debug( "findFoldingRangesMarkdown for \"%s\" took %s", doc->getFilePath(), + c.getElapsedTime().toString() ); + + return regions; +} + FoldRangeServive::FoldRangeServive( TextDocument* doc ) : mDoc( doc ) {} bool FoldRangeServive::canFold() const { @@ -107,14 +200,15 @@ bool FoldRangeServive::canFold() const { if ( mProvider && mProvider->foldingRangeProvider() ) return true; auto type = mDoc->getSyntaxDefinition().getFoldRangeType(); - return type == FoldRangeType::Braces || type == FoldRangeType::Indentation; + return type == FoldRangeType::Braces || type == FoldRangeType::Indentation || + type == FoldRangeType::Markdown; } void FoldRangeServive::findRegions() { if ( !mEnabled || mDoc == nullptr || !canFold() ) return; - if ( mProvider && mProvider->foldingRangeProvider() ){ + if ( mProvider && mProvider->foldingRangeProvider() ) { mProvider->requestFoldRange(); return; } @@ -125,6 +219,8 @@ void FoldRangeServive::findRegions() { break; case FoldRangeType::Indentation: setFoldingRegions( findFoldingRangesIndentation( mDoc ) ); + case FoldRangeType::Markdown: + setFoldingRegions( findFoldingRangesMarkdown( mDoc ) ); case FoldRangeType::Tag: case FoldRangeType::Undefined: break; diff --git a/src/eepp/ui/doc/languages/markdown.cpp b/src/eepp/ui/doc/languages/markdown.cpp index 0f859b3ab..5574d22ad 100644 --- a/src/eepp/ui/doc/languages/markdown.cpp +++ b/src/eepp/ui/doc/languages/markdown.cpp @@ -12,7 +12,7 @@ void addMarkdown() { return SyntaxDefinitionManager::instance()->findFromString( lang ).getLanguageName(); }; - SyntaxDefinitionManager::instance()->add( + auto& sd = SyntaxDefinitionManager::instance()->add( { "Markdown", { "%.md$", "%.markdown$" }, @@ -54,6 +54,8 @@ void addMarkdown() { {} } ); + + sd.setFoldRangeType( FoldRangeType::Markdown ); } }}}} // namespace EE::UI::Doc::Language diff --git a/src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languages/ada.cpp b/src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languages/ada.cpp index c3d6bacc0..89d29523d 100644 --- a/src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languages/ada.cpp +++ b/src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languages/ada.cpp @@ -5,7 +5,7 @@ namespace EE { namespace UI { namespace Doc { namespace Language { void addAda() { - auto sd = SyntaxDefinitionManager::instance()->add( + auto& sd = SyntaxDefinitionManager::instance()->add( { "Ada", { "%.adb$", "%.ads$", "%.ada$" }, diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index a2b734874..372186186 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -3524,7 +3524,7 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe mThreadPool->run( [this] { // Load language definitions Clock defClock; - SyntaxDefinitionManager::createSingleton( 108 ); + SyntaxDefinitionManager::createSingleton( 109 ); Language::LanguagesSyntaxHighlighting::load(); SyntaxDefinitionManager::instance()->setLanguageExtensionsPriority( mConfig.languagesExtensions.priorities );