From e9f6c320465bc4cbacbf4b350b8242f1c43e17f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 28 Apr 2024 18:56:24 -0300 Subject: [PATCH] Snippet nav WIP. --- include/eepp/ui/doc/textdocument.hpp | 3 +- include/eepp/ui/doc/textrange.hpp | 8 ++ src/eepp/ui/doc/textdocument.cpp | 31 ++--- .../autocomplete/autocompleteplugin.cpp | 106 +++++++++++++++++- .../autocomplete/autocompleteplugin.hpp | 9 +- 5 files changed, 139 insertions(+), 18 deletions(-) diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index 5e4f05eca..b29ce7a59 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -204,7 +204,8 @@ class EE_API TextDocument { size_t remove( const size_t& cursorIdx, TextRange range ); - TextPosition positionOffset( TextPosition position, int columnOffset ) const; + TextPosition positionOffset( TextPosition position, int columnOffset, + bool sanitizeInput = true ) const; TextPosition positionOffset( TextPosition position, TextPosition offset ) const; diff --git a/include/eepp/ui/doc/textrange.hpp b/include/eepp/ui/doc/textrange.hpp index 70b0a13f8..18aad01f3 100644 --- a/include/eepp/ui/doc/textrange.hpp +++ b/include/eepp/ui/doc/textrange.hpp @@ -111,6 +111,14 @@ class EE_API TextRange { bool inSameLine() const { return isValid() && mStart.line() == mEnd.line(); } + Int64 length() const { + if ( !inSameLine() ) + return 0; + if ( mEnd.column() > mStart.column() ) + return mEnd.column() - mStart.column(); + return mStart.column() - mStart.column(); + } + std::string toString() const { return String::format( "%s - %s", mStart.toString().c_str(), mEnd.toString().c_str() ); } diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index 0e32b14d4..5ca6e6ff4 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -1285,15 +1285,19 @@ size_t TextDocument::remove( const size_t& cursorIdx, TextRange range, return linesRemoved; } -TextPosition TextDocument::positionOffset( TextPosition position, int columnOffset ) const { - position = sanitizePosition( position ); +TextPosition TextDocument::positionOffset( TextPosition position, int columnOffset, + bool sanitizeInput ) const { + if ( sanitizeInput ) + position = sanitizePosition( position ); position.setColumn( position.column() + columnOffset ); while ( position.line() > 0 && position.column() < 0 ) { position.setLine( position.line() - 1 ); - position.setColumn( eemax( 0, position.column() + mLines[position.line()].size() ) ); + position.setColumn( + eemax( 0, position.column() + (Int64)mLines[position.line()].size() ) ); } while ( position.line() < (Int64)mLines.size() - 1 && - position.column() > (Int64)eemax( 0, mLines[position.line()].size() - 1 ) ) { + position.column() > + (Int64)eemax( 0, (Int64)mLines[position.line()].size() - 1 ) ) { position.setColumn( position.column() - mLines[position.line()].size() ); position.setLine( position.line() + 1 ); } @@ -2340,7 +2344,7 @@ struct FindTypeResult { }; static FindTypeResult findType( const String& str, const String& findStr, - const TextDocument::FindReplaceType& type ) { + const TextDocument::FindReplaceType& type, int colOffset ) { switch ( type ) { case TextDocument::FindReplaceType::LuaPattern: { LuaPatternStorage words( findStr.toUtf8() ); @@ -2352,8 +2356,8 @@ static FindTypeResult findType( const String& str, const String& findStr, std::vector captures; captures.reserve( words.getNumMatches() - 1 ); for ( size_t i = 1; i < words.getNumMatches(); i++ ) { - result.captures.emplace_back( - LuaPattern::Range{ matches[i].start, matches[i].end } ); + result.captures.emplace_back( LuaPattern::Range{ + colOffset + matches[i].start, colOffset + matches[i].end } ); } } return result; @@ -2442,21 +2446,22 @@ TextDocument::SearchResult TextDocument::findText( String text, TextPosition fro FindTypeResult col; if ( i == from.line() ) { col = caseSensitive - ? findType( line( i ).getText().substr( from.column() ), text, type ) + ? findType( line( i ).getText().substr( from.column() ), text, type, + from.column() ) : findType( String::toLower( line( i ).getText() ).substr( from.column() ), - text, type ); + text, type, from.column() ); if ( String::StringType::npos != col.start ) { col.start += from.column(); col.end += from.column(); } } else if ( i == to.line() && to != endOfDoc() ) { col = caseSensitive - ? findType( line( i ).getText().substr( 0, to.column() ), text, type ) + ? findType( line( i ).getText().substr( 0, to.column() ), text, type, 0 ) : findType( String::toLower( line( i ).getText() ).substr( 0, to.column() ), - text, type ); + text, type, 0 ); } else { - col = caseSensitive ? findType( line( i ).getText(), text, type ) - : findType( String::toLower( line( i ).getText() ), text, type ); + col = caseSensitive ? findType( line( i ).getText(), text, type, 0 ) + : findType( String::toLower( line( i ).getText() ), text, type, 0 ); } if ( String::StringType::npos != col.start && ( !wholeWord || String::isWholeWord( line( i ).getText(), text, col.start ) ) ) { diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp index 946d58ec5..05484c0b8 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp @@ -10,9 +10,14 @@ using namespace EE::Graphics; using namespace EE::System; using json = nlohmann::json; +using namespace std::literals; namespace ecode { +static constexpr auto SNIPPET_PTRN1 = "%$%{%d+%}"sv; +static constexpr auto SNIPPET_PTRN2 = "%$%{%d+%:(%w+)}"sv; +static constexpr auto SNIPPET_PTRN3 = "%$%d+"sv; + #if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ ) #define AUTO_COMPLETE_THREADED 1 #else @@ -462,11 +467,10 @@ void AutoCompletePlugin::pickSuggestion( UICodeEditor* editor ) { std::string symbol( getPartialSymbol( editor->getDocumentRef().get() ) ); const auto& suggestion = mSuggestions[mSuggestionIndex]; auto doc = editor->getDocumentRef(); + auto prevSels = doc->getSelections(); if ( doc->getSelections().size() == 1 && suggestion.range.isValid() && doc->isValidRange( suggestion.range ) ) { - auto sels = doc->getSelections(); - doc->setSelection( suggestion.range ); doc->textInput( suggestion.insertText ); } else { @@ -475,10 +479,108 @@ void AutoCompletePlugin::pickSuggestion( UICodeEditor* editor ) { doc->textInput( suggestion.text ); } + // tryStartSnippetNav( suggestion, editor, prevSels ); + mReplacing = false; resetSuggestions( editor ); } +void AutoCompletePlugin::tryStartSnippetNav( const Suggestion& suggestion, UICodeEditor* editor, + const TextRanges& prevSels ) { + if ( !hasCompleteSteps( suggestion ) || !mSnippetCompletion.empty() ) + return; + + auto doc = editor->getDocumentRef(); + auto selections = doc->getSelections(); + TextRanges newSelections; + newSelections.reserve( selections.size() ); + size_t i = 0; + for ( const auto& sel : selections ) { + newSelections.emplace_back( prevSels[i].start(), sel.end() ); + i++; + } + std::vector ranges; + + for ( const auto& sel : newSelections ) { + TextRanges steps; + + auto res = doc->findAll( SNIPPET_PTRN1, true, false, + TextDocument::FindReplaceType::LuaPattern, sel ); + + auto res2 = doc->findAll( SNIPPET_PTRN2, true, false, + TextDocument::FindReplaceType::LuaPattern, sel ); + + auto res3 = doc->findAll( SNIPPET_PTRN3, true, false, + TextDocument::FindReplaceType::LuaPattern, sel ); + + res.reserve( res.size() + res2.size() + res3.size() ); + + for ( auto& r : res2 ) + res.emplace_back( std::move( r ) ); + + for ( auto& r : res3 ) + res.emplace_back( std::move( r ) ); + + if ( res.empty() ) + continue; + + std::sort( + res.begin(), res.end(), + []( const TextDocument::SearchResult& left, const TextDocument::SearchResult& right ) { + return left.result > right.result; + } ); + + for ( TextDocument::SearchResult& sr : res ) { + if ( !sr.isValid() ) + continue; + + doc->setSelection( sr.result ); + + if ( !sr.captures.empty() ) { + auto text = doc->getText( sr.captures[0] ); + auto pos = doc->replaceSelection( text ); + + for ( auto& step : steps ) { + if ( step.start().line() == sr.result.start().line() ) { + Int64 offset = sr.result.length() - text.size(); + step.setStart( doc->positionOffset( step.start(), -offset, false ) ); + step.setEnd( doc->positionOffset( step.end(), -offset, false ) ); + } + } + + steps.emplace_back( pos, doc->positionOffset( pos, -text.size() ) ); + } else { + auto pos = doc->replaceSelection( "" ); + + for ( auto& step : steps ) { + if ( step.start().line() == sr.result.start().line() ) { + Int64 offset = sr.result.length(); + step.setStart( doc->positionOffset( step.start(), -offset, false ) ); + step.setEnd( doc->positionOffset( step.end(), -offset, false ) ); + } + } + + steps.emplace_back( pos, pos ); + } + } + + ranges.emplace_back( steps ); + } + + mSnippetCompletion = std::move( ranges ); +} + +bool AutoCompletePlugin::hasCompleteSteps( const Suggestion& suggestion ) { + if ( suggestion.kind != LSPCompletionItemKind::Snippet ) + return false; + if ( LuaPattern::matches( suggestion.insertText, SNIPPET_PTRN1 ) || + LuaPattern::matches( suggestion.insertText, SNIPPET_PTRN2 ) || + LuaPattern::matches( suggestion.insertText, SNIPPET_PTRN3 ) ) { + return true; + } + return false; +} + PluginRequestHandle AutoCompletePlugin::processCodeCompletion( const LSPCompletionList& completion ) { SymbolsList suggestions; diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp index 8c566bdac..1b69322df 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp @@ -43,7 +43,7 @@ class AutoCompletePlugin : public Plugin { kind( kind ), text( std::move( text ) ), detail( std::move( detail ) ), - sortText( sortText.empty() ? std::string{ text } : std::move( sortText ) ), + sortText( sortText.empty() ? std::string{ this->text } : std::move( sortText ) ), range( range ), insertText( std::move( insertText ) ), documentation( doc ){}; @@ -130,7 +130,7 @@ class AutoCompletePlugin : public Plugin { }; std::unordered_map mDocCache; std::unordered_map mLangCache; - + std::vector mSnippetCompletion; std::vector mSuggestions; Mutex mSuggestionsEditorMutex; Mutex mSignatureHelpEditorMutex; @@ -188,6 +188,11 @@ class AutoCompletePlugin : public Plugin { void drawSignatureHelp( UICodeEditor* editor, const Vector2f& startScroll, const Float& lineHeight, bool drawUp ); + + bool hasCompleteSteps( const Suggestion& suggestion ); + + void tryStartSnippetNav( const Suggestion& suggestion, UICodeEditor* editor, + const TextRanges& prevSels ); }; } // namespace ecode