diff --git a/bin/assets/fonts/codicon.ttf b/bin/assets/fonts/codicon.ttf new file mode 100644 index 000000000..bab11139a Binary files /dev/null and b/bin/assets/fonts/codicon.ttf differ diff --git a/bin/assets/fonts/nonicons.ttf b/bin/assets/fonts/nonicons.ttf index c14aa53e9..7750e2228 100644 Binary files a/bin/assets/fonts/nonicons.ttf and b/bin/assets/fonts/nonicons.ttf differ diff --git a/include/eepp/scene/event.hpp b/include/eepp/scene/event.hpp index 52e2c0642..6e0f021e9 100644 --- a/include/eepp/scene/event.hpp +++ b/include/eepp/scene/event.hpp @@ -81,6 +81,7 @@ class EE_API Event { OnSelectionChanged, OnNodeDropped, OnDocumentSave, + OnDocumentUndoRedo, OnModelEvent, OnResourceChange, OnActiveWidgetChange, diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index e07817c28..918b98fc9 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -140,6 +140,10 @@ class EE_API TextDocument { String getSelectedText() const; + String::StringBaseType getPrevChar() const; + + String::StringBaseType getCurrentChar() const; + String::StringBaseType getChar( const TextPosition& position ) const; TextPosition insert( const TextPosition& position, const String& text ); diff --git a/projects/linux/ecode/build.app.sh b/projects/linux/ecode/build.app.sh index 6a12c8e08..f19369513 100755 --- a/projects/linux/ecode/build.app.sh +++ b/projects/linux/ecode/build.app.sh @@ -24,6 +24,7 @@ cp -r ../../../bin/assets/colorschemes ecode.app/assets/ cp -r ../../../bin/assets/fonts/DejaVuSansMono.ttf ecode.app/assets/fonts/ cp -r ../../../bin/assets/fonts/DejaVuSansMonoNerdFontComplete.ttf ecode.app/assets/fonts/ cp -r ../../../bin/assets/fonts/nonicons.ttf ecode.app/assets/fonts/ +cp -r ../../../bin/assets/fonts/codicon.ttf ecode.app/assets/fonts/ cp -r ../../../bin/assets/fonts/NotoSans-Regular.ttf ecode.app/assets/fonts/ cp -r ../../../bin/assets/fonts/remixicon.ttf ecode.app/assets/fonts/ cp -r ../../../bin/assets/fonts/NotoEmoji-Regular.ttf ecode.app/assets/fonts/ diff --git a/projects/macos/ecode/build.app.sh b/projects/macos/ecode/build.app.sh index 116f192eb..1e08ec625 100755 --- a/projects/macos/ecode/build.app.sh +++ b/projects/macos/ecode/build.app.sh @@ -20,6 +20,7 @@ mkdir -p ecode.app/Contents/MacOS/assets/fonts cp -r ../../../bin/assets/fonts/DejaVuSansMono.ttf ecode.app/Contents/MacOS/assets/fonts/ cp -r ../../../bin/assets/fonts/DejaVuSansMonoNerdFontComplete.ttf ecode.app/Contents/MacOS/assets/fonts/ cp -r ../../../bin/assets/fonts/nonicons.ttf ecode.app/Contents/MacOS/assets/fonts/ +cp -r ../../../bin/assets/fonts/codicon.ttf ecode.app/Contents/MacOS/assets/fonts/ cp -r ../../../bin/assets/fonts/NotoSans-Regular.ttf ecode.app/Contents/MacOS/assets/fonts/ cp -r ../../../bin/assets/fonts/remixicon.ttf ecode.app/Contents/MacOS/assets/fonts/ cp -r ../../../bin/assets/fonts/NotoEmoji-Regular.ttf ecode.app/Contents/MacOS/assets/fonts/ diff --git a/projects/mingw32/ecode/build.app.sh b/projects/mingw32/ecode/build.app.sh index 1abe0b02b..2d6d91c75 100755 --- a/projects/mingw32/ecode/build.app.sh +++ b/projects/mingw32/ecode/build.app.sh @@ -44,6 +44,7 @@ cp -r ../../../bin/assets/colorschemes ecode/assets/ cp -r ../../../bin/assets/fonts/DejaVuSansMono.ttf ecode/assets/fonts/ cp -r ../../../bin/assets/fonts/DejaVuSansMonoNerdFontComplete.ttf ecode/assets/fonts/ cp -r ../../../bin/assets/fonts/nonicons.ttf ecode/assets/fonts/ +cp -r ../../../bin/assets/fonts/codicon.ttf ecode/assets/fonts/ cp -r ../../../bin/assets/fonts/NotoSans-Regular.ttf ecode/assets/fonts/ cp -r ../../../bin/assets/fonts/remixicon.ttf ecode/assets/fonts/ cp -r ../../../bin/assets/fonts/NotoEmoji-Regular.ttf ecode/assets/fonts/ diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index 09f48e13f..7df314df2 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -668,6 +668,14 @@ String TextDocument::getSelectedText() const { return getText( getSelection() ); } +String::StringBaseType TextDocument::getPrevChar() const { + return getChar( positionOffset( getSelection().start(), -1 ) ); +} + +String::StringBaseType TextDocument::getCurrentChar() const { + return getChar( getSelection().start() ); +} + String::StringBaseType TextDocument::getChar( const TextPosition& position ) const { auto pos = sanitizePosition( position ); return mLines[pos.line()][pos.column()]; diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 53cce69e2..e34076364 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -1482,6 +1482,8 @@ void UICodeEditor::onDocumentLineChanged( const Int64& lineNumber ) { void UICodeEditor::onDocumentUndoRedo( const TextDocument::UndoRedo& ) { onDocumentSelectionChange( {} ); + DocEvent event( this, mDoc.get(), Event::OnDocumentUndoRedo ); + sendEvent( &event ); } void UICodeEditor::onDocumentSaved( TextDocument* doc ) { diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index cfa73e498..b5e2a26d7 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -3502,8 +3502,9 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe FontTrueType* iconFont = loadFont( "icon", "fonts/remixicon.ttf" ); FontTrueType* mimeIconFont = loadFont( "nonicons", "fonts/nonicons.ttf" ); + FontTrueType* codIconFont = loadFont( "codicon", "fonts/codicon.ttf" ); - if ( !mFont || !mFontMono || !iconFont || !mimeIconFont ) { + if ( !mFont || !mFontMono || !iconFont || !mimeIconFont || !codIconFont ) { printf( "Font not found!" ); Log::error( "Font not found!" ); return; @@ -3872,6 +3873,27 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe iconTheme->add( UIGlyphIcon::New( icon.first, mimeIconFont, icon.second ) ); } + if ( codIconFont && codIconFont->loaded() ) { + std::unordered_map codIcons = { + { "symbol-text", 0xea93 }, { "symbol-method", 0xea8c }, + { "symbol-function", 0xea8c }, { "symbol-constructor", 0xea8c }, + { "symbol-field", 0xeb5f }, { "symbol-variable", 0xea88 }, + { "symbol-class", 0xeb5b }, { "symbol-interface", 0xeb61 }, + { "symbol-module", 0xea8b }, { "symbol-property", 0xeb65 }, + { "symbol-unit", 0xea96 }, { "symbol-value", 0xea95 }, + { "symbol-enum", 0xea95 }, { "symbol-keyword", 0xeb62 }, + { "symbol-snippet", 0xeb66 }, { "symbol-color", 0xeb5c }, + { "symbol-file", 0xeb60 }, { "symbol-reference", 0xea94 }, + { "symbol-folder", 0xea83 }, { "symbol-enum-member", 0xeb5e }, + { "symbol-constant", 0xeb5d }, { "symbol-struct", 0xea91 }, + { "symbol-event", 0xea86 }, { "symbol-operator", 0xeb64 }, + { "symbol-type-parameter", 0xea92 }, + }; + + for ( const auto& icon : codIcons ) + iconTheme->add( UIGlyphIcon::New( icon.first, codIconFont, icon.second ) ); + } + mUISceneNode->getUIIconThemeManager()->setCurrentTheme( iconTheme ); UIWidgetCreator::registerWidget( "searchbar", UISearchBar::New ); diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp index db2b2585f..f8f4bb7e8 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp @@ -18,38 +18,54 @@ namespace ecode { #define AUTO_COMPLETE_THREADED 0 #endif -static std::vector +static json getURIAndPositionJSON( UICodeEditor* editor ) { + json data; + auto doc = editor->getDocumentRef(); + auto sel = doc->getSelection(); + data["uri"] = doc->getURI().toString(); + data["position"] = { { "line", sel.start().line() }, { "character", sel.start().column() } }; + return data; +} + +static AutoCompletePlugin::SymbolsList fuzzyMatchSymbols( const std::vector& symbolsVec, const std::string& match, const size_t& max ) { - std::multimap> matchesMap; - std::vector matches; + AutoCompletePlugin::SymbolsList matches; + matches.reserve( max ); int score; + bool firstHasSortText = !symbolsVec[0]->empty() + ? symbolsVec[0]->at( 0 ).sortText != symbolsVec[0]->at( 0 ).text + : false; for ( const auto& symbols : symbolsVec ) { for ( const auto& symbol : *symbols ) { - if ( ( score = String::fuzzyMatch( symbol, match ) ) > 0 ) { - matchesMap.insert( { score, symbol } ); + if ( ( score = String::fuzzyMatch( symbol.text, match ) ) > 0 || firstHasSortText ) { + if ( std::find( matches.begin(), matches.end(), symbol ) == matches.end() ) { + symbol.setScore( score ); + matches.push_back( symbol ); + } } } + if ( firstHasSortText ) + break; } - std::string prevMatch; - for ( auto& res : matchesMap ) { - if ( matches.size() < max ) { - if ( std::find( matches.begin(), matches.end(), res.second ) == matches.end() ) - matches.emplace_back( res.second ); - } + if ( firstHasSortText ) { + std::sort( matches.begin(), matches.end() ); + } else { + std::sort( matches.begin(), matches.end(), + []( const auto& left, const auto& right ) { return left.score > right.score; } ); } return matches; } -UICodeEditorPlugin* AutoCompletePlugin::New( const PluginManager* pluginManager ) { +UICodeEditorPlugin* AutoCompletePlugin::New( PluginManager* pluginManager ) { return eeNew( AutoCompletePlugin, ( pluginManager ) ); } -AutoCompletePlugin::AutoCompletePlugin( const PluginManager* pluginManager ) : +AutoCompletePlugin::AutoCompletePlugin( PluginManager* pluginManager ) : mManager( pluginManager ), mSymbolPattern( "[%a_ñàáâãäåèéêëìíîïòóôõöùúûüýÿÑÀÁÂÃÄÅÈÉÊËÌÍÎÏÒÓÔÕÖÙÚÛÜÝ][%w_" "ñàáâãäåèéêëìíîïòóôõöùúûüýÿÑÀÁÂÃÄÅÈÉÊËÌÍÎÏÒÓÔÕÖÙÚÛÜÝ]*" ), - mBoxPadding( PixelDensity::dpToPx( Rectf( 4, 4, 4, 4 ) ) ), + mBoxPadding( PixelDensity::dpToPx( Rectf( 4, 4, 12, 4 ) ) ), mPool( pluginManager->getThreadPool() ) { mManager->subscribeMessages( this, [&]( const PluginMessage& msg ) -> PluginRequestHandle { return processResponse( msg ); @@ -105,6 +121,12 @@ void AutoCompletePlugin::onRegister( UICodeEditor* editor ) { resetSuggestions( editor ); } ) ); + listeners.push_back( editor->addEventListener( + Event::OnFocusLoss, [&]( const Event* ) { resetSignatureHelp(); } ) ); + + listeners.push_back( editor->addEventListener( + Event::OnDocumentUndoRedo, [&]( const Event* ) { resetSignatureHelp(); } ) ); + listeners.push_back( editor->addEventListener( Event::OnDocumentSyntaxDefinitionChange, [&]( const Event* ev ) { const DocSyntaxDefEvent* event = static_cast( ev ); @@ -134,6 +156,8 @@ void AutoCompletePlugin::onUnregister( UICodeEditor* editor ) { return; if ( mSuggestionsEditor == editor ) resetSuggestions( editor ); + if ( mSignatureHelpEditor == editor ) + resetSignatureHelp(); Lock l( mDocMutex ); TextDocument* doc = mEditorDocs[editor]; auto cbs = mEditors[editor]; @@ -150,6 +174,28 @@ 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; + } else if ( event.getKeyCode() == EE::Window::KEY_BACKSPACE || + event.getKeyCode() == EE::Window::KEY_DELETE ) { + auto lang = editor->getDocumentRef()->getSyntaxDefinition().getLSPName(); + auto cap = mCapabilities.find( lang ); + if ( cap != mCapabilities.end() ) { + auto curChar = event.getKeyCode() == EE::Window::KEY_BACKSPACE + ? editor->getDocumentRef()->getPrevChar() + : editor->getDocumentRef()->getCurrentChar(); + const auto& signatureTrigger = cap->second.signatureHelpProvider.triggerCharacters; + if ( std::find( signatureTrigger.begin(), signatureTrigger.end(), curChar ) != + signatureTrigger.end() ) { + resetSignatureHelp(); + } + } + } + } if ( !mSuggestions.empty() ) { if ( event.getKeyCode() == KEY_DOWN ) { if ( mSuggestionIndex + 1 < (int)mSuggestions.size() ) { @@ -180,6 +226,7 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event return true; } else if ( event.getKeyCode() == KEY_ESCAPE ) { resetSuggestions( editor ); + resetSignatureHelp(); editor->invalidateDraw(); return true; } else if ( event.getKeyCode() == KEY_HOME ) { @@ -224,7 +271,25 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event updateSuggestions( partialSymbol, editor ); return true; } - return false; + return ret; +} + +void AutoCompletePlugin::requestSignatureHelp( UICodeEditor* editor ) { + mSignatureHelpEditor = editor; + auto doc = editor->getDocumentRef(); + mSignatureHelpPosition = editor->getDocumentRef()->getSelection().start(); + + mPool->run( [&, editor]() { + json data = getURIAndPositionJSON( editor ); + mManager->sendRequest( this, PluginMessageType::SignatureHelp, PluginMessageFormat::JSON, + &data ); + } ); +} + +void AutoCompletePlugin::requestCodeCompletion( UICodeEditor* editor ) { + json data = getURIAndPositionJSON( editor ); + mManager->sendRequest( this, PluginMessageType::CodeCompletion, PluginMessageFormat::JSON, + &data ); } bool AutoCompletePlugin::onTextInput( UICodeEditor* editor, const TextInputEvent& event ) { @@ -233,6 +298,20 @@ bool AutoCompletePlugin::onTextInput( UICodeEditor* editor, const TextInputEvent auto lang = editor->getDocumentRef()->getSyntaxDefinition().getLSPName(); auto cap = mCapabilities.find( lang ); if ( cap != mCapabilities.end() ) { + const auto& signatureTrigger = cap->second.signatureHelpProvider.triggerCharacters; + if ( std::find( signatureTrigger.begin(), signatureTrigger.end(), event.getChar() ) != + signatureTrigger.end() ) { + requestSignatureHelp( editor ); + } + + if ( mSignatureHelpVisible ) { + auto doc = editor->getDocumentRef(); + auto curPos = doc->getSelection().start(); + if ( curPos.line() != mSignatureHelpPosition.line() || + curPos < doc->startOfWord( doc->positionOffset( mSignatureHelpPosition, 1 ) ) ) + resetSignatureHelp(); + } + const auto& triggerCharacters = cap->second.completionProvider.triggerCharacters; if ( partialSymbol.size() >= 1 || std::find( triggerCharacters.begin(), triggerCharacters.end(), event.getChar() ) != @@ -294,48 +373,74 @@ void AutoCompletePlugin::pickSuggestion( UICodeEditor* editor ) { std::string symbol( getPartialSymbol( editor->getDocumentRef().get() ) ); if ( !symbol.empty() ) editor->getDocument().execute( "delete-to-previous-word" ); - editor->getDocument().textInput( mSuggestions[mSuggestionIndex] ); + editor->getDocument().textInput( mSuggestions[mSuggestionIndex].text ); mReplacing = false; resetSuggestions( editor ); } -PluginRequestHandle AutoCompletePlugin::processResponse( const PluginMessage& msg ) { - if ( msg.isResponse() && msg.type == PluginMessageType::CodeCompletion ) { - auto completion = msg.asCodeCompletion(); - SymbolsList suggestions; - for ( const auto& item : completion ) { - if ( !item.textEdit.text.empty() ) - suggestions.push_back( item.textEdit.text ); - else if ( !item.insertText.empty() ) - suggestions.push_back( item.insertText ); - else - suggestions.push_back( item.filterText ); - } - if ( suggestions.empty() || !mSuggestionsEditor ) - return {}; - std::string symbol( getPartialSymbol( mSuggestionsEditor->getDocumentRef().get() ) ); - const std::string& lang = - mSuggestionsEditor->getDocument().getSyntaxDefinition().getLanguageName(); +PluginRequestHandle +AutoCompletePlugin::processCodeCompletion( const std::vector& completion ) { + SymbolsList suggestions; + for ( const auto& item : completion ) { + if ( !item.textEdit.text.empty() ) + suggestions.push_back( { item.kind, item.textEdit.text, item.detail, item.sortText, + item.textEdit.range } ); + else if ( !item.insertText.empty() ) + suggestions.push_back( { item.kind, item.insertText, item.detail, item.sortText } ); + else + suggestions.push_back( { item.kind, item.filterText, item.detail, item.sortText } ); + } + if ( suggestions.empty() || !mSuggestionsEditor ) + return {}; + UICodeEditor* editor = nullptr; + { + Lock l( mSuggestionsEditorMutex ); + editor = mSuggestionsEditor; + } + if ( !editor ) + return {}; + std::string symbol( getPartialSymbol( editor->getDocumentRef().get() ) ); + const std::string& lang = editor->getDocument().getSyntaxDefinition().getLanguageName(); + bool hasLangSuggestions = false; + { Lock l2( mLangSymbolsMutex ); auto langSuggestions = mLangCache.find( lang ); - std::vector fuzzySuggestions; - if ( symbol.empty() ) { - Lock l( mSuggestionsMutex ); - mSuggestions = suggestions; - } else { - if ( langSuggestions == mLangCache.end() ) { - fuzzySuggestions = fuzzyMatchSymbols( { &suggestions }, symbol, - eemax( 100UL, suggestions.size() ) ); - } else { - auto& symbols = langSuggestions->second; - fuzzySuggestions = fuzzyMatchSymbols( { &suggestions, &symbols }, symbol, - eemax( 100UL, suggestions.size() ) ); - } - Lock l( mSuggestionsMutex ); - mSuggestions = fuzzySuggestions; + hasLangSuggestions = langSuggestions == mLangCache.end(); + } + if ( symbol.empty() || hasLangSuggestions ) { + Lock l( mSuggestionsMutex ); + mSuggestions = suggestions; + } else { + SymbolsList fuzzySuggestions; + { + Lock l2( mLangSymbolsMutex ); + auto& symbols = mLangCache[lang]; + fuzzySuggestions = fuzzyMatchSymbols( { &suggestions, &symbols }, symbol, + eemax( 100UL, suggestions.size() ) ); } + Lock l( mSuggestionsMutex ); + mSuggestions = fuzzySuggestions; + } - mSuggestionsEditor->runOnMainThread( [this] { mSuggestionsEditor->invalidateDraw(); } ); + editor->runOnMainThread( [editor] { editor->invalidateDraw(); } ); + + return {}; +} + +PluginRequestHandle +AutoCompletePlugin::processSignatureHelp( const LSPSignatureHelp& signatureHelp ) { + mSignatureHelpVisible = true; + mSignatureHelp = signatureHelp; + if ( mSignatureHelp.signatures.empty() ) + resetSignatureHelp(); + return {}; +} + +PluginRequestHandle AutoCompletePlugin::processResponse( const PluginMessage& msg ) { + if ( msg.isResponse() && msg.type == PluginMessageType::CodeCompletion ) { + return processCodeCompletion( msg.asCodeCompletion() ); + } else if ( msg.isResponse() && msg.type == PluginMessageType::SignatureHelp ) { + return processSignatureHelp( msg.asSignatureHelp() ); } else if ( msg.isBroadcast() && msg.type == PluginMessageType::LanguageServerCapabilities ) { if ( msg.asLanguageServerCapabilities().ready ) { LSPServerCapabilities cap = msg.asLanguageServerCapabilities(); @@ -383,25 +488,52 @@ void AutoCompletePlugin::update( UICodeEditor* ) { void AutoCompletePlugin::postDraw( UICodeEditor* editor, const Vector2f& startScroll, const Float& lineHeight, const TextPosition& cursor ) { - std::vector suggestions; + bool drawsSuggestions = + !( mSuggestions.empty() || !mSuggestionsEditor || mSuggestionsEditor != editor ); + bool drawsSignature = mSignatureHelpVisible && mSignatureHelpEditor == editor && + !mSignatureHelp.signatures.empty() && mSignatureHelpPosition.isValid(); + if ( !drawsSuggestions && !drawsSignature ) + return; + + TextPosition start = + editor->getDocument().startOfWord( editor->getDocument().startOfWord( cursor ) ); + Primitives primitives; + const SyntaxColorScheme& scheme = editor->getColorScheme(); + const auto& normalStyle = scheme.getEditorSyntaxStyle( "suggestion" ); + const auto& selectedStyle = scheme.getEditorSyntaxStyle( "suggestion_selected" ); + + if ( drawsSignature ) { + auto cursig = mSignatureHelp.signatures[mSignatureHelp.activeSignature]; + Vector2f pos( startScroll.x + editor->getXOffsetCol( mSignatureHelpPosition ), + startScroll.y + mSignatureHelpPosition.line() * lineHeight - lineHeight - + mBoxPadding.Top - mBoxPadding.Bottom ); + primitives.setColor( Color( selectedStyle.background ).blendAlpha( editor->getAlpha() ) ); + primitives.drawRoundedRectangle( + Rectf( pos, Sizef( editor->getTextWidth( cursig.label ) + mBoxPadding.Left + + mBoxPadding.Right, + mRowHeight ) ), + 0.f, Vector2f::One, 6 ); + Text text( "", editor->getFont(), editor->getFontSize() ); + text.setFillColor( normalStyle.color ); + text.setStyle( normalStyle.style ); + text.setString( cursig.label ); + text.draw( pos.x + mBoxPadding.Left, pos.y + mBoxPadding.Top ); + } + + if ( !drawsSuggestions ) + return; + + SymbolsList suggestions; { Lock l( mSuggestionsMutex ); - if ( mSuggestions.empty() || !mSuggestionsEditor || mSuggestionsEditor != editor ) - return; suggestions = mSuggestions; } - Primitives primitives; - TextPosition start = - editor->getDocument().startOfWord( editor->getDocument().startOfWord( cursor ) ); Vector2f cursorPos( startScroll.x + editor->getXOffsetCol( start ), startScroll.y + cursor.line() * lineHeight + lineHeight ); size_t largestString = 0; size_t max = eemin( mSuggestionsMaxVisible, suggestions.size() ); - const SyntaxColorScheme& scheme = editor->getColorScheme(); mRowHeight = lineHeight + mBoxPadding.Top + mBoxPadding.Bottom; - const auto& normalStyle = scheme.getEditorSyntaxStyle( "suggestion" ); - const auto& selectedStyle = scheme.getEditorSyntaxStyle( "suggestion_selected" ); const auto& barStyle = scheme.getEditorSyntaxStyle( "suggestion_scrollbar" ); if ( cursorPos.y + mRowHeight * max > editor->getPixelsSize().getHeight() ) cursorPos.y -= lineHeight + mRowHeight * max; @@ -410,31 +542,48 @@ void AutoCompletePlugin::postDraw( UICodeEditor* editor, const Vector2f& startSc eemin( mSuggestionsStartIndex + mSuggestionsMaxVisible, suggestions.size() ); for ( size_t i = mSuggestionsStartIndex; i < maxIndex; i++ ) - largestString = eemax( largestString, editor->getTextWidth( suggestions[i] ) ); - - Sizef bar( PixelDensity::dpToPx( 8 ), - mBoxRect.getSize().getHeight() * - ( mSuggestionsMaxVisible / (Float)suggestions.size() ) ); + largestString = eemax( largestString, editor->getTextWidth( suggestions[i].text ) ); + Sizef bar( PixelDensity::dpToPxI( 6 ), + mRowHeight * max * ( mSuggestionsMaxVisible / (Float)suggestions.size() ) ); + Sizef iconSpace( PixelDensity::dpToPxI( 16 ), mRowHeight ); mBoxRect = Rectf( Vector2f( cursorPos.x, cursorPos.y ) - editor->getScreenPos(), - Sizef( largestString + mBoxPadding.Left + mBoxPadding.Right + bar.getWidth(), + Sizef( largestString + mBoxPadding.Left + mBoxPadding.Right + + iconSpace.getWidth() + bar.getWidth(), mRowHeight * max ) ); size_t count = 0; + Rectf boxRect( { mBoxRect.getPosition() + editor->getScreenPos(), mBoxRect.getSize() } ); + primitives.setColor( Color( normalStyle.background ).blendAlpha( editor->getAlpha() ) ); + primitives.drawRoundedRectangle( boxRect, 0.f, Vector2f::One, 6 ); + for ( size_t i = mSuggestionsStartIndex; i < maxIndex; i++ ) { + if ( mSuggestionIndex == (int)i ) { + primitives.setColor( + Color( selectedStyle.background ).blendAlpha( editor->getAlpha() ) ); + primitives.drawRoundedRectangle( + Rectf( Vector2f( cursorPos.x, cursorPos.y + mRowHeight * count ), + Sizef( mBoxRect.getWidth(), mRowHeight ) ), + 0.f, Vector2f::One, 6 ); + } Text text( "", editor->getFont(), editor->getFontSize() ); text.setFillColor( mSuggestionIndex == (int)i ? selectedStyle.color : normalStyle.color ); text.setStyle( mSuggestionIndex == (int)i ? selectedStyle.style : normalStyle.style ); - text.setString( suggestions[i] ); - primitives.setColor( - Color( mSuggestionIndex == (int)i ? selectedStyle.background : normalStyle.background ) - .blendAlpha( editor->getAlpha() ) ); - primitives.drawRectangle( - Rectf( Vector2f( cursorPos.x, cursorPos.y + mRowHeight * count ), - Sizef( largestString + mBoxPadding.Left + mBoxPadding.Right + bar.getWidth(), - mRowHeight ) ) ); - text.draw( cursorPos.x + mBoxPadding.Left, + text.setString( suggestions[i].text ); + + text.draw( cursorPos.x + iconSpace.getWidth() + mBoxPadding.Left, cursorPos.y + mRowHeight * count + mBoxPadding.Top ); + + Drawable* icon = editor->getUISceneNode()->findIconDrawable( + LSPCompletionItemHelper::toIconString( suggestions[i].kind ), + PixelDensity::dpToPxI( 12 ) ); + + if ( icon ) { + Vector2f padding( + eefloor( ( iconSpace.getWidth() - icon->getSize().getWidth() ) * 0.5f ), + eefloor( ( iconSpace.getHeight() - icon->getSize().getHeight() ) * 0.5f ) ); + icon->draw( { cursorPos.x + padding.x, cursorPos.y + mRowHeight * count + padding.y } ); + } count++; } @@ -442,14 +591,19 @@ void AutoCompletePlugin::postDraw( UICodeEditor* editor, const Vector2f& startSc return; primitives.setColor( barStyle.color ); - Float yPos = mSuggestionsStartIndex > 0 - ? mSuggestionsStartIndex / - (Float)( ( suggestions.size() - 1 ) - mSuggestionsMaxVisible ) - : 0; - primitives.drawRectangle( - { Vector2f( cursorPos.x + mBoxRect.getWidth() - bar.getWidth(), - cursorPos.y + ( mBoxRect.getHeight() - bar.getHeight() ) * yPos ), - bar } ); + Float yPos = + mSuggestionsStartIndex > 0 + ? mSuggestionsStartIndex / (Float)( suggestions.size() - mSuggestionsMaxVisible ) + : 0; + Rectf barRect( { Vector2f( cursorPos.x + mBoxRect.getWidth() - bar.getWidth(), + cursorPos.y + ( mBoxRect.getHeight() - bar.getHeight() ) * yPos ), + bar } ); + if ( bar.getHeight() < 8 ) { + primitives.drawRectangle( barRect ); + } else { + primitives.drawRoundedRectangle( barRect, 0, Vector2f::One, + (int)eefloor( bar.getWidth() * 0.5f ) ); + } } bool AutoCompletePlugin::onMouseDown( UICodeEditor* editor, const Vector2i& position, @@ -457,25 +611,34 @@ bool AutoCompletePlugin::onMouseDown( UICodeEditor* editor, const Vector2i& posi if ( mSuggestions.empty() || !mSuggestionsEditor || mSuggestionsEditor != editor || !( flags & EE_BUTTON_LMASK ) ) return false; - Vector2f localPos( editor->convertToNodeSpace( position.asFloat() ) ); - if ( mBoxRect.contains( localPos ) ) + if ( mBoxRect.contains( localPos ) ) { + localPos -= { mBoxRect.Left, mBoxRect.Top }; + mSuggestionIndex = mSuggestionsStartIndex + localPos.y / mRowHeight; + editor->invalidateDraw(); return true; + } return false; } -bool AutoCompletePlugin::onMouseClick( UICodeEditor* editor, const Vector2i& position, - const Uint32& flags ) { - if ( mSuggestions.empty() || !mSuggestionsEditor || mSuggestionsEditor != editor || - !( flags & EE_BUTTON_LMASK ) ) +bool AutoCompletePlugin::onMouseUp( UICodeEditor* editor, const Vector2i& position, + const Uint32& flags ) { + if ( mSuggestions.empty() || !mSuggestionsEditor || mSuggestionsEditor != editor ) return false; Vector2f localPos( editor->convertToNodeSpace( position.asFloat() ) ); if ( mBoxRect.contains( localPos ) ) { - localPos -= { mBoxRect.Left, mBoxRect.Top }; - mSuggestionIndex = localPos.y / mRowHeight; - editor->invalidateDraw(); - return true; + if ( flags & EE_BUTTON_WUMASK ) { + mSuggestionsStartIndex = eemax( 0, mSuggestionsStartIndex - mSuggestionsMaxVisible ); + editor->invalidateDraw(); + return true; + } else if ( flags & EE_BUTTON_WDMASK ) { + mSuggestionsStartIndex = + eemax( 0, eemin( (int)mSuggestions.size() - mSuggestionsMaxVisible, + mSuggestionsStartIndex + mSuggestionsMaxVisible ) ); + editor->invalidateDraw(); + return true; + } } return false; } @@ -500,10 +663,12 @@ bool AutoCompletePlugin::onMouseMove( UICodeEditor* editor, const Vector2i& posi return false; Vector2f localPos( editor->convertToNodeSpace( position.asFloat() ) ); - if ( mBoxRect.contains( localPos ) ) + if ( mBoxRect.contains( localPos ) ) { editor->getUISceneNode()->setCursor( Cursor::Hand ); - else + return true; + } else { editor->getUISceneNode()->setCursor( !editor->isLocked() ? Cursor::IBeam : Cursor::Arrow ); + } return false; } @@ -551,12 +716,24 @@ void AutoCompletePlugin::resetSuggestions( UICodeEditor* editor ) { Lock l( mSuggestionsMutex ); mSuggestionIndex = 0; mSuggestionsStartIndex = 0; - mSuggestionsEditor = nullptr; + { + Lock l( mSuggestionsEditorMutex ); + mSuggestionsEditor = nullptr; + } mSuggestions.clear(); if ( editor && editor->hasFocus() ) editor->getUISceneNode()->setCursor( !editor->isLocked() ? Cursor::IBeam : Cursor::Arrow ); } +void AutoCompletePlugin::resetSignatureHelp() { + mSignatureHelpVisible = false; + mSignatureHelpEditor = nullptr; + mSignatureHelpPosition = {}; + mSignatureHelp.signatures.clear(); + mSignatureHelp.activeSignature = 0; + mSignatureHelp.activeParameter = 0; +} + AutoCompletePlugin::SymbolsList AutoCompletePlugin::getDocumentSymbols( TextDocument* doc ) { LuaPattern pattern( mSymbolPattern ); AutoCompletePlugin::SymbolsList symbols; @@ -583,17 +760,12 @@ AutoCompletePlugin::SymbolsList AutoCompletePlugin::getDocumentSymbols( TextDocu void AutoCompletePlugin::runUpdateSuggestions( const std::string& symbol, const SymbolsList& symbols, UICodeEditor* editor ) { { - mSuggestionsEditor = editor; - if ( tryRequestCapabilities( editor ) ) { - json data; - auto doc = editor->getDocumentRef(); - auto sel = doc->getSelection(); - data["uri"] = doc->getURI().toString(); - data["position"] = { { "line", sel.start().line() }, - { "character", sel.start().column() } }; - mManager->sendRequest( this, PluginMessageType::CodeCompletion, - PluginMessageFormat::JSON, &data ); + { + Lock l( mSuggestionsEditorMutex ); + mSuggestionsEditor = editor; } + if ( tryRequestCapabilities( editor ) ) + requestCodeCompletion( editor ); if ( symbol.empty() ) return; Lock l( mLangSymbolsMutex ); diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp index 9377a8fb1..189219b84 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp @@ -18,7 +18,38 @@ namespace ecode { class AutoCompletePlugin : public UICodeEditorPlugin { public: - typedef std::vector SymbolsList; + class Suggestion { + public: + LSPCompletionItemKind kind{ LSPCompletionItemKind::Text }; + std::string text; + std::string detail; + std::string sortText; + TextRange range; + double score{ 0 }; + + void setScore( const double& score ) const { + const_cast( this )->score = score; + } + + Suggestion( const std::string& text ) : text( text ), sortText( text ) {} + + Suggestion( const LSPCompletionItemKind& kind, const std::string& text, + const std::string& detail, const std::string& sortText, + const TextRange& range = {} ) : + kind( kind ), + text( text ), + detail( detail ), + sortText( sortText.empty() ? text : sortText ), + range( range ){}; + + bool operator<( const Suggestion& other ) { return getCmpStr() < other.getCmpStr(); } + + bool operator==( const Suggestion& other ) { return text == other.text; } + + protected: + const std::string* getCmpStr() const { return !sortText.empty() ? &sortText : &text; } + }; + typedef std::vector SymbolsList; static PluginDefinition Definition() { return { "autocomplete", @@ -29,7 +60,7 @@ class AutoCompletePlugin : public UICodeEditorPlugin { { 0, 1, 0 } }; } - static UICodeEditorPlugin* New( const PluginManager* pluginManager ); + static UICodeEditorPlugin* New( PluginManager* pluginManager ); virtual ~AutoCompletePlugin(); @@ -49,7 +80,7 @@ class AutoCompletePlugin : public UICodeEditorPlugin { void postDraw( UICodeEditor*, const Vector2f& startScroll, const Float& lineHeight, const TextPosition& cursor ); bool onMouseDown( UICodeEditor*, const Vector2i&, const Uint32& ); - bool onMouseClick( UICodeEditor*, const Vector2i&, const Uint32& ); + bool onMouseUp( UICodeEditor*, const Vector2i&, const Uint32& ); bool onMouseDoubleClick( UICodeEditor*, const Vector2i&, const Uint32& ); bool onMouseMove( UICodeEditor*, const Vector2i&, const Uint32& ); @@ -74,13 +105,7 @@ class AutoCompletePlugin : public UICodeEditorPlugin { void setDirty( bool dirty ); protected: - struct Suggestion { - std::string text; - std::string desc; - std::string sortText; - TextRange range; - }; - const PluginManager* mManager{ nullptr }; + PluginManager* mManager{ nullptr }; std::string mSymbolPattern; Rectf mBoxPadding; std::shared_ptr mPool; @@ -95,6 +120,7 @@ class AutoCompletePlugin : public UICodeEditorPlugin { bool mDirty{ false }; bool mClosing{ false }; bool mReplacing{ false }; + bool mSignatureHelpVisible{ false }; struct DocCache { Uint64 changeId{ static_cast( -1 ) }; SymbolsList symbols; @@ -103,18 +129,22 @@ class AutoCompletePlugin : public UICodeEditorPlugin { std::unordered_map mLangCache; SymbolsList mLangDirty; - std::vector mSuggestions; + std::vector mSuggestions; + Mutex mSuggestionsEditorMutex; UICodeEditor* mSuggestionsEditor{ nullptr }; + UICodeEditor* mSignatureHelpEditor{ nullptr }; Int32 mSuggestionIndex{ 0 }; Int32 mSuggestionsMaxVisible{ 8 }; Int32 mSuggestionsStartIndex{ 0 }; std::map mCapabilities; Mutex mCapabilitiesMutex; + LSPSignatureHelp mSignatureHelp; + TextPosition mSignatureHelpPosition; Float mRowHeight{ 0 }; Rectf mBoxRect; - AutoCompletePlugin( const PluginManager* pluginManager ); + AutoCompletePlugin( PluginManager* pluginManager ); void resetSuggestions( UICodeEditor* editor ); @@ -136,6 +166,16 @@ class AutoCompletePlugin : public UICodeEditorPlugin { PluginRequestHandle processResponse( const PluginMessage& msg ); bool tryRequestCapabilities( UICodeEditor* editor ); + + void requestCodeCompletion( UICodeEditor* editor ); + + void requestSignatureHelp( UICodeEditor* editor ); + + PluginRequestHandle processCodeCompletion( const std::vector& completion ); + + PluginRequestHandle processSignatureHelp( const LSPSignatureHelp& signatureHelp ); + + void resetSignatureHelp(); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/formatter/formatterplugin.cpp b/src/tools/ecode/plugins/formatter/formatterplugin.cpp index 67d3d1f70..d7c5d66f4 100644 --- a/src/tools/ecode/plugins/formatter/formatterplugin.cpp +++ b/src/tools/ecode/plugins/formatter/formatterplugin.cpp @@ -22,11 +22,11 @@ namespace ecode { #define FORMATTER_THREADED 0 #endif -UICodeEditorPlugin* FormatterPlugin::New( const PluginManager* pluginManager ) { +UICodeEditorPlugin* FormatterPlugin::New( PluginManager* pluginManager ) { return eeNew( FormatterPlugin, ( pluginManager ) ); } -FormatterPlugin::FormatterPlugin( const PluginManager* pluginManager ) : +FormatterPlugin::FormatterPlugin( PluginManager* pluginManager ) : mPool( pluginManager->getThreadPool() ) { #if FORMATTER_THREADED mPool->run( [&, pluginManager] { load( pluginManager ); }, [] {} ); @@ -183,7 +183,7 @@ void FormatterPlugin::loadFormatterConfig( const std::string& path ) { } } -void FormatterPlugin::load( const PluginManager* pluginManager ) { +void FormatterPlugin::load( PluginManager* pluginManager ) { registerNativeFormatters(); std::vector paths; diff --git a/src/tools/ecode/plugins/formatter/formatterplugin.hpp b/src/tools/ecode/plugins/formatter/formatterplugin.hpp index a521477fc..5cf79c908 100644 --- a/src/tools/ecode/plugins/formatter/formatterplugin.hpp +++ b/src/tools/ecode/plugins/formatter/formatterplugin.hpp @@ -29,7 +29,7 @@ class FormatterPlugin : public UICodeEditorPlugin { { 0, 1, 0 } }; } - static UICodeEditorPlugin* New( const PluginManager* pluginManager ); + static UICodeEditorPlugin* New( PluginManager* pluginManager ); virtual ~FormatterPlugin(); @@ -84,9 +84,9 @@ class FormatterPlugin : public UICodeEditorPlugin { bool mReady{ false }; Uint32 mOnDocumentSaveCb{ 0 }; - FormatterPlugin( const PluginManager* pluginManager ); + FormatterPlugin( PluginManager* pluginManager ); - void load( const PluginManager* pluginManager ); + void load( PluginManager* pluginManager ); void loadFormatterConfig( const std::string& path ); diff --git a/src/tools/ecode/plugins/linter/linterplugin.cpp b/src/tools/ecode/plugins/linter/linterplugin.cpp index 78012c7fe..77cbf855c 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.cpp +++ b/src/tools/ecode/plugins/linter/linterplugin.cpp @@ -22,11 +22,11 @@ namespace ecode { #define LINTER_THREADED 0 #endif -UICodeEditorPlugin* LinterPlugin::New( const PluginManager* pluginManager ) { +UICodeEditorPlugin* LinterPlugin::New( PluginManager* pluginManager ) { return eeNew( LinterPlugin, ( pluginManager ) ); } -LinterPlugin::LinterPlugin( const PluginManager* pluginManager ) : +LinterPlugin::LinterPlugin( PluginManager* pluginManager ) : mManager( pluginManager ), mPool( pluginManager->getThreadPool() ) { #if LINTER_THREADED mPool->run( [&, pluginManager] { load( pluginManager ); }, [] {} ); @@ -301,7 +301,7 @@ TextDocument* LinterPlugin::getDocumentFromURI( const URI& uri ) { return nullptr; } -void LinterPlugin::load( const PluginManager* pluginManager ) { +void LinterPlugin::load( PluginManager* pluginManager ) { pluginManager->subscribeMessages( this, [&]( const auto& notification ) -> PluginRequestHandle { return processMessage( notification ); } ); @@ -727,6 +727,7 @@ bool LinterPlugin::onMouseMove( UICodeEditor* editor, const Vector2i& pos, const mHoveringMatch = true; editor->runOnMainThread( [&, editor] { editor->setTooltipText( match.text ); + editor->getTooltip()->setHorizontalAlign( UI_HALIGN_LEFT ); editor->getTooltip()->setDontAutoHideOnMouseMove( true ); editor->getTooltip()->setPixelsPosition( Vector2f( pos.x, pos.y ) ); if ( !editor->getTooltip()->isVisible() ) diff --git a/src/tools/ecode/plugins/linter/linterplugin.hpp b/src/tools/ecode/plugins/linter/linterplugin.hpp index d89af8fd0..8205909f0 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.hpp +++ b/src/tools/ecode/plugins/linter/linterplugin.hpp @@ -55,7 +55,7 @@ class LinterPlugin : public UICodeEditorPlugin { LinterPlugin::New, { 0, 1, 0 } }; } - static UICodeEditorPlugin* New( const PluginManager* pluginManager ); + static UICodeEditorPlugin* New( PluginManager* pluginManager ); virtual ~LinterPlugin(); @@ -96,7 +96,7 @@ class LinterPlugin : public UICodeEditorPlugin { void setEnableLSPDiagnostics( bool enableLSPDiagnostics ); protected: - const PluginManager* mManager{ nullptr }; + PluginManager* mManager{ nullptr }; std::shared_ptr mPool; std::vector mLinters; std::unordered_map> mEditors; @@ -119,9 +119,9 @@ class LinterPlugin : public UICodeEditorPlugin { std::set mLanguagesDisabled; std::set mLSPLanguagesDisabled; - LinterPlugin( const PluginManager* pluginManager ); + LinterPlugin( PluginManager* pluginManager ); - void load( const PluginManager* pluginManager ); + void load( PluginManager* pluginManager ); void lintDoc( std::shared_ptr doc ); diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index 067d7d937..dddc7ab51 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -13,11 +13,11 @@ using json = nlohmann::json; namespace ecode { -UICodeEditorPlugin* LSPClientPlugin::New( const PluginManager* pluginManager ) { +UICodeEditorPlugin* LSPClientPlugin::New( PluginManager* pluginManager ) { return eeNew( LSPClientPlugin, ( pluginManager ) ); } -LSPClientPlugin::LSPClientPlugin( const PluginManager* pluginManager ) : +LSPClientPlugin::LSPClientPlugin( PluginManager* pluginManager ) : mManager( pluginManager ), mThreadPool( pluginManager->getThreadPool() ) { mThreadPool->run( [&, pluginManager] { load( pluginManager ); }, [] {} ); } @@ -48,11 +48,12 @@ void LSPClientPlugin::update( UICodeEditor* ) { mClientManager.updateDirty(); } -PluginRequestHandle LSPClientPlugin::processCodeCompletionRequest( const PluginMessage& msg ) { - if ( !msg.isRequest() || !msg.isJSON() ) - return {}; +struct LSPPositionAndServer { + LSPPosition loc; + LSPClientServer* server{ nullptr }; +}; - const auto& data = msg.asJSON(); +LSPPositionAndServer getLSPLocationFromJSON( LSPClientServerManager& manager, const json& data ) { if ( !data.contains( "uri" ) || !data.contains( "position" ) ) return {}; @@ -61,12 +62,23 @@ PluginRequestHandle LSPClientPlugin::processCodeCompletionRequest( const PluginM return {}; URI uri( data["uri"] ); - auto server = mClientManager.getOneLSPClientServer( uri ); + auto server = manager.getOneLSPClientServer( uri ); if ( !server ) return {}; + return { { uri, position }, server }; +} - auto ret = server->documentCompletion( - uri, position, [&]( const PluginIDType& id, const std::vector& items ) { +PluginRequestHandle LSPClientPlugin::processCodeCompletionRequest( const PluginMessage& msg ) { + if ( !msg.isRequest() || !msg.isJSON() ) + return {}; + + auto res = getLSPLocationFromJSON( mClientManager, msg.asJSON() ); + if ( !res.server ) + return {}; + + auto ret = res.server->documentCompletion( + res.loc.uri, res.loc.pos, + [&]( const PluginIDType& id, const std::vector& items ) { mManager->sendResponse( this, PluginMessageType::CodeCompletion, PluginMessageFormat::CodeCompletion, &items, id ); } ); @@ -74,6 +86,23 @@ PluginRequestHandle LSPClientPlugin::processCodeCompletionRequest( const PluginM return ret; } +PluginRequestHandle LSPClientPlugin::processSignatureHelpRequest( const PluginMessage& msg ) { + if ( !msg.isRequest() || !msg.isJSON() ) + return {}; + + auto res = getLSPLocationFromJSON( mClientManager, msg.asJSON() ); + if ( !res.server ) + return {}; + + auto ret = res.server->signatureHelp( + res.loc.uri, res.loc.pos, [&]( const PluginIDType& id, const LSPSignatureHelp& data ) { + mManager->sendResponse( this, PluginMessageType::SignatureHelp, + PluginMessageFormat::SignatureHelp, &data, id ); + } ); + + return ret; +} + PluginRequestHandle LSPClientPlugin::processMessage( const PluginMessage& msg ) { switch ( msg.type ) { case PluginMessageType::WorkspaceFolderChanged: { @@ -86,6 +115,12 @@ PluginRequestHandle LSPClientPlugin::processMessage( const PluginMessage& msg ) return ret; break; } + case PluginMessageType::SignatureHelp: { + auto ret = processSignatureHelpRequest( msg ); + if ( !ret.isEmpty() ) + return ret; + break; + } case PluginMessageType::LanguageServerCapabilities: { if ( msg.isRequest() && msg.isJSON() ) { const auto& data = msg.asJSON(); @@ -108,7 +143,7 @@ PluginRequestHandle LSPClientPlugin::processMessage( const PluginMessage& msg ) return PluginRequestHandle::empty(); } -void LSPClientPlugin::load( const PluginManager* pluginManager ) { +void LSPClientPlugin::load( PluginManager* pluginManager ) { pluginManager->subscribeMessages( this, [&]( const auto& notification ) -> PluginRequestHandle { return processMessage( notification ); } ); @@ -361,7 +396,7 @@ void LSPClientPlugin::onUnregister( UICodeEditor* editor ) { mDocs.erase( doc ); } -const PluginManager* LSPClientPlugin::getManager() const { +PluginManager* LSPClientPlugin::getManager() const { return mManager; } diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp index 8acafb3f3..1a99b89c0 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp @@ -29,7 +29,7 @@ class LSPClientPlugin : public UICodeEditorPlugin { { 0, 0, 1 } }; } - static UICodeEditorPlugin* New( const PluginManager* pluginManager ); + static UICodeEditorPlugin* New( PluginManager* pluginManager ); virtual ~LSPClientPlugin(); @@ -49,7 +49,7 @@ class LSPClientPlugin : public UICodeEditorPlugin { const std::unordered_map& getEditorDocs() { return mEditorDocs; }; - const PluginManager* getManager() const; + PluginManager* getManager() const; virtual bool onCreateContextMenu( UICodeEditor* editor, UIPopUpMenu* menu, const Vector2i& position, const Uint32& flags ); @@ -67,7 +67,7 @@ class LSPClientPlugin : public UICodeEditorPlugin { const LSPClientServerManager& getClientManager() const; protected: - const PluginManager* mManager{ nullptr }; + PluginManager* mManager{ nullptr }; std::shared_ptr mThreadPool; Clock mClock; Mutex mDocMutex; @@ -85,9 +85,9 @@ class LSPClientPlugin : public UICodeEditorPlugin { LSPHover mCurrentHover; Time mHoverDelay{ Seconds( 1.f ) }; - LSPClientPlugin( const PluginManager* pluginManager ); + LSPClientPlugin( PluginManager* pluginManager ); - void load( const PluginManager* pluginManager ); + void load( PluginManager* pluginManager ); void loadLSPConfig( std::vector& lsps, const std::string& path ); @@ -97,6 +97,8 @@ class LSPClientPlugin : public UICodeEditorPlugin { PluginRequestHandle processMessage( const PluginMessage& msg ); PluginRequestHandle processCodeCompletionRequest( const PluginMessage& msg ); + + PluginRequestHandle processSignatureHelpRequest( const PluginMessage& msg ); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.cpp b/src/tools/ecode/plugins/lsp/lspclientserver.cpp index 4c7b53dfe..86af6b444 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.cpp @@ -631,29 +631,33 @@ static std::vector parseDocumentCompletion( const json& resul std::vector ret; if ( result.empty() ) return {}; - const json& items = - ( result.is_object() && result.contains( "items" ) ) ? result["items"] : result; + try { + const json& items = + ( result.is_object() && result.contains( "items" ) ) ? result["items"] : result; - for ( const auto& item : items ) { - auto label = item.value( MEMBER_LABEL, "" ); - auto detail = item.value( MEMBER_DETAIL, "" ); - LSPMarkupContent doc = item.contains( MEMBER_DOCUMENTATION ) - ? parseMarkupContent( item.at( MEMBER_DOCUMENTATION ) ) - : LSPMarkupContent{}; - auto filterText = item.value( "filterText", label ); - auto insertText = item.value( "insertText", label ); - auto sortText = item.value( "sortText", label ); - LSPTextEdit textEdit; - if ( item.contains( "textEdit" ) ) - textEdit = parseTextEdit( item["textEdit"] ); - auto kind = static_cast( item.value( MEMBER_KIND, 1 ) ); - const std::vector additionalTextEdits = - item.contains( "additionalTextEdits" ) - ? parseTextEditArray( item.at( "additionalTextEdits" ) ) - : std::vector{}; + for ( const auto& item : items ) { + auto label = item.value( MEMBER_LABEL, "" ); + auto detail = item.value( MEMBER_DETAIL, "" ); + LSPMarkupContent doc = item.contains( MEMBER_DOCUMENTATION ) + ? parseMarkupContent( item.at( MEMBER_DOCUMENTATION ) ) + : LSPMarkupContent{}; + auto filterText = item.value( "filterText", label ); + auto insertText = item.value( "insertText", label ); + auto sortText = item.value( "sortText", label ); + LSPTextEdit textEdit; + if ( item.contains( "textEdit" ) ) + textEdit = parseTextEdit( item["textEdit"] ); + auto kind = static_cast( item.value( MEMBER_KIND, 1 ) ); + const std::vector additionalTextEdits = + item.contains( "additionalTextEdits" ) + ? parseTextEditArray( item.at( "additionalTextEdits" ) ) + : std::vector{}; - ret.push_back( { label, kind, detail, doc, sortText, insertText, filterText, textEdit, - additionalTextEdits } ); + ret.push_back( { label, kind, detail, doc, sortText, insertText, filterText, textEdit, + additionalTextEdits } ); + } + } catch ( const json::exception& err ) { + Log::warning( "Error parsing parseDocumentCompletion: %s", err.what() ); } return ret; } @@ -662,7 +666,8 @@ static LSPSignatureInformation parseSignatureInformation( const json& json ) { LSPSignatureInformation info; info.label = json.value( MEMBER_LABEL, "" ); - info.documentation = parseMarkupContent( json.value( MEMBER_DOCUMENTATION, {} ) ); + if ( json.contains( MEMBER_DOCUMENTATION ) ) + info.documentation = parseMarkupContent( json.at( MEMBER_DOCUMENTATION ) ); const auto& params = json.at( "parameters" ); for ( const auto& par : params ) { auto label = par.at( MEMBER_LABEL ); @@ -692,16 +697,21 @@ static LSPSignatureInformation parseSignatureInformation( const json& json ) { static LSPSignatureHelp parseSignatureHelp( const json& sig ) { LSPSignatureHelp ret; - const auto& sigInfos = sig.at( "signatures" ); - for ( const auto& info : sigInfos ) - ret.signatures.push_back( parseSignatureInformation( info ) ); - ret.activeSignature = sig.value( "activeSignature", 0 ); - ret.activeParameter = sig.value( "activeParameter", 0 ); - ret.activeSignature = eemin( eemax( ret.activeSignature, 0 ), (int)ret.signatures.size() ); - ret.activeParameter = eemax( ret.activeParameter, 0 ); - if ( !ret.signatures.empty() ) { - ret.activeParameter = eemin( - ret.activeParameter, (int)ret.signatures.at( ret.activeSignature ).parameters.size() ); + try { + const auto& sigInfos = sig.at( "signatures" ); + for ( const auto& info : sigInfos ) + ret.signatures.push_back( parseSignatureInformation( info ) ); + ret.activeSignature = sig.value( "activeSignature", 0 ); + ret.activeParameter = sig.value( "activeParameter", 0 ); + ret.activeSignature = eemin( eemax( ret.activeSignature, 0 ), (int)ret.signatures.size() ); + ret.activeParameter = eemax( ret.activeParameter, 0 ); + if ( !ret.signatures.empty() ) { + ret.activeParameter = + eemin( ret.activeParameter, + (int)ret.signatures.at( ret.activeSignature ).parameters.size() ); + } + } catch ( const json::exception& err ) { + Log::warning( "Error parsing parseSignatureHelp: %s", err.what() ); } return ret; } @@ -1293,8 +1303,7 @@ LSPClientServer::LSPRequestHandle LSPClientServer::switchSourceHeader( const URI return send( newRequest( "textDocument/switchSourceHeader", textDocumentURI( document ) ), [this]( const IdType&, json res ) { if ( res.is_string() ) { - mManager->goToLocation( - { res.get(), TextRange{ { 0, 0 }, { 0, 0 } } } ); + mManager->goToLocation( { res.get(), TextRange() } ); } } ); } diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp index d934d4548..3cb0aef7f 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp @@ -8,7 +8,7 @@ namespace ecode { LSPClientServerManager::LSPClientServerManager() {} -void LSPClientServerManager::load( LSPClientPlugin* plugin, const PluginManager* pluginManager, +void LSPClientServerManager::load( LSPClientPlugin* plugin, PluginManager* pluginManager, std::vector&& lsps ) { mPlugin = plugin; mPluginManager = pluginManager; @@ -117,15 +117,17 @@ void LSPClientServerManager::goToLocation( const LSPLocation& loc ) { std::string path( loc.uri.getPath() ); FileInfo fileInfo( path ); if ( fileInfo.exists() && fileInfo.isRegularFile() ) { - splitter->loadAsyncFileFromPathInNewTab( path, mThreadPool, - [loc]( UICodeEditor* editor, auto ) { - editor->goToLine( loc.range.start() ); - editor->setFocus(); - } ); + splitter->loadAsyncFileFromPathInNewTab( + path, mThreadPool, [loc]( UICodeEditor* editor, auto ) { + if ( loc.range.isValid() ) + editor->goToLine( loc.range.start() ); + editor->setFocus(); + } ); } } else { tab->getTabWidget()->setTabSelected( tab ); - splitter->editorFromTab( tab )->goToLine( loc.range.start() ); + if ( loc.range.isValid() ) + splitter->editorFromTab( tab )->goToLine( loc.range.start() ); splitter->editorFromTab( tab )->setFocus(); } } ); @@ -169,7 +171,7 @@ void LSPClientServerManager::getAndGoToLocation( const std::shared_ptrgetAndGoToLocation( doc->getURI(), doc->getSelection().start(), search ); } -const PluginManager* LSPClientServerManager::getPluginManager() const { +PluginManager* LSPClientServerManager::getPluginManager() const { return mPluginManager; } diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp index 1308ec199..80977ddcc 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp @@ -16,8 +16,7 @@ class LSPClientServerManager { public: LSPClientServerManager(); - void load( LSPClientPlugin*, const PluginManager* pluginManager, - std::vector&& lsps ); + void load( LSPClientPlugin*, PluginManager* pluginManager, std::vector&& lsps ); // async void run( const std::shared_ptr& doc ); @@ -55,13 +54,13 @@ class LSPClientServerManager { void getAndGoToLocation( const std::shared_ptr& doc, const std::string& search ); - const PluginManager* getPluginManager() const; + PluginManager* getPluginManager() const; LSPClientPlugin* getPlugin() const; protected: friend class LSPClientServer; - const PluginManager* mPluginManager{ nullptr }; + PluginManager* mPluginManager{ nullptr }; LSPClientPlugin* mPlugin{ nullptr }; std::shared_ptr mThreadPool; std::map> mClients; diff --git a/src/tools/ecode/plugins/lsp/lspprotocol.hpp b/src/tools/ecode/plugins/lsp/lspprotocol.hpp index 41ffd7800..022038a7b 100644 --- a/src/tools/ecode/plugins/lsp/lspprotocol.hpp +++ b/src/tools/ecode/plugins/lsp/lspprotocol.hpp @@ -31,6 +31,11 @@ enum class LSPErrorCode { ContentModified = -32801 }; +struct LSPPosition { + URI uri; + TextPosition pos; +}; + struct LSPLocation { URI uri; TextRange range; @@ -287,6 +292,65 @@ enum class LSPCompletionItemKind { TypeParameter = 25, }; +class LSPCompletionItemHelper { + public: + static std::string toIconString( const LSPCompletionItemKind& kind ) { + switch ( kind ) { + case LSPCompletionItemKind::Text: + return "symbol-text"; + case LSPCompletionItemKind::Method: + return "symbol-method"; + case LSPCompletionItemKind::Function: + return "symbol-function"; + case LSPCompletionItemKind::Constructor: + return "symbol-constructor"; + case LSPCompletionItemKind::Field: + return "symbol-field"; + case LSPCompletionItemKind::Variable: + return "symbol-variable"; + case LSPCompletionItemKind::Class: + return "symbol-class"; + case LSPCompletionItemKind::Interface: + return "symbol-interface"; + case LSPCompletionItemKind::Module: + return "symbol-module"; + case LSPCompletionItemKind::Property: + return "symbol-property"; + case LSPCompletionItemKind::Unit: + return "symbol-unit"; + case LSPCompletionItemKind::Value: + return "symbol-value"; + case LSPCompletionItemKind::Enum: + return "symbol-enum"; + case LSPCompletionItemKind::Keyword: + return "symbol-keyword"; + case LSPCompletionItemKind::Snippet: + return "symbol-snippet"; + case LSPCompletionItemKind::Color: + return "symbol-color"; + case LSPCompletionItemKind::File: + return "symbol-file"; + case LSPCompletionItemKind::Reference: + return "symbol-reference"; + case LSPCompletionItemKind::Folder: + return "symbol-folder"; + case LSPCompletionItemKind::EnumMember: + return "symbol-enum-member"; + case LSPCompletionItemKind::Constant: + return "symbol-constant"; + case LSPCompletionItemKind::Struct: + return "symbol-struct"; + case LSPCompletionItemKind::Event: + return "symbol-event"; + case LSPCompletionItemKind::Operator: + return "symbol-operator"; + case LSPCompletionItemKind::TypeParameter: + return "symbol-type-parameter"; + } + return "symbol-text"; + } +}; + struct LSPCompletionItem { std::string label; LSPCompletionItemKind kind; diff --git a/src/tools/ecode/plugins/pluginmanager.cpp b/src/tools/ecode/plugins/pluginmanager.cpp index 8db2673c0..a583599f7 100644 --- a/src/tools/ecode/plugins/pluginmanager.cpp +++ b/src/tools/ecode/plugins/pluginmanager.cpp @@ -41,7 +41,10 @@ bool PluginManager::setEnabled( const std::string& id, bool enable ) { } if ( !enable && plugin != nullptr ) { eeSAFE_DELETE( plugin ); - mSubscribedPlugins.erase( id ); + { + Lock l( mSubscribedPluginsMutex ); + mSubscribedPlugins.erase( id ); + } mPlugins.erase( id ); } return false; @@ -111,10 +114,15 @@ void PluginManager::setWorkspaceFolder( const std::string& workspaceFolder ) { PluginRequestHandle PluginManager::sendRequest( UICodeEditorPlugin* pluginWho, PluginMessageType type, PluginMessageFormat format, - const void* data ) const { + const void* data ) { if ( mClosing ) return PluginRequestHandle::empty(); - for ( const auto& plugin : mSubscribedPlugins ) { + SubscribedPlugins subscribedPlugins; + { + Lock l( mSubscribedPluginsMutex ); + subscribedPlugins = mSubscribedPlugins; + } + for ( const auto& plugin : subscribedPlugins ) { if ( pluginWho->getId() != plugin.first ) { auto handle = plugin.second( { type, format, data } ); if ( !handle.isEmpty() ) @@ -126,36 +134,50 @@ PluginRequestHandle PluginManager::sendRequest( UICodeEditorPlugin* pluginWho, void PluginManager::sendResponse( UICodeEditorPlugin* pluginWho, PluginMessageType type, PluginMessageFormat format, const void* data, - const PluginIDType& responseID ) const { + const PluginIDType& responseID ) { if ( mClosing ) return; - for ( const auto& plugin : mSubscribedPlugins ) + SubscribedPlugins subscribedPlugins; + { + Lock l( mSubscribedPluginsMutex ); + subscribedPlugins = mSubscribedPlugins; + } + for ( const auto& plugin : subscribedPlugins ) if ( pluginWho->getId() != plugin.first ) plugin.second( { type, format, data, responseID } ); } void PluginManager::sendBroadcast( UICodeEditorPlugin* pluginWho, PluginMessageType type, - PluginMessageFormat format, const void* data ) const { + PluginMessageFormat format, const void* data ) { if ( mClosing ) return; - for ( const auto& plugin : mSubscribedPlugins ) + SubscribedPlugins subscribedPlugins; + { + Lock l( mSubscribedPluginsMutex ); + subscribedPlugins = mSubscribedPlugins; + } + for ( const auto& plugin : subscribedPlugins ) if ( pluginWho->getId() != plugin.first ) plugin.second( { type, format, data, -1 } ); } void PluginManager::subscribeMessages( - UICodeEditorPlugin* plugin, - std::function cb ) const { - const_cast( this )->mSubscribedPlugins[plugin->getId()] = cb; + UICodeEditorPlugin* plugin, std::function cb ) { + { + Lock l( mSubscribedPluginsMutex ); + mSubscribedPlugins[plugin->getId()] = cb; + } if ( !mWorkspaceFolder.empty() ) { json data{ { "folder", mWorkspaceFolder } }; cb( { PluginMessageType::WorkspaceFolderChanged, PluginMessageFormat::JSON, &data } ); } } -void PluginManager::unsubscribeMessages( UICodeEditorPlugin* plugin ) const { - if ( !mClosing ) - const_cast( this )->mSubscribedPlugins.erase( plugin->getId() ); +void PluginManager::unsubscribeMessages( UICodeEditorPlugin* plugin ) { + if ( !mClosing ) { + Lock l( mSubscribedPluginsMutex ); + mSubscribedPlugins.erase( plugin->getId() ); + } } void PluginManager::setSplitter( UICodeEditorSplitter* splitter ) { @@ -166,7 +188,12 @@ void PluginManager::sendBroadcast( const PluginMessageType& notification, const PluginMessageFormat& format, void* data ) { if ( mClosing ) return; - for ( const auto& plugin : mSubscribedPlugins ) + SubscribedPlugins subscribedPlugins; + { + Lock l( mSubscribedPluginsMutex ); + subscribedPlugins = mSubscribedPlugins; + } + for ( const auto& plugin : subscribedPlugins ) plugin.second( { notification, format, data } ); } diff --git a/src/tools/ecode/plugins/pluginmanager.hpp b/src/tools/ecode/plugins/pluginmanager.hpp index 3f0aefc06..7a355f752 100644 --- a/src/tools/ecode/plugins/pluginmanager.hpp +++ b/src/tools/ecode/plugins/pluginmanager.hpp @@ -21,7 +21,7 @@ namespace ecode { class PluginManager; -typedef std::function PluginCreatorFn; +typedef std::function PluginCreatorFn; #ifdef minor #undef minor @@ -63,10 +63,17 @@ enum class PluginMessageType { // and position LanguageServerCapabilities, // Request the language server capabilities of a language if there // is any available, it will be returned as a broadcast + SignatureHelp, // Request the LSP Client to provide function/method signature help Undefined }; -enum class PluginMessageFormat { JSON, Diagnostics, CodeCompletion, LanguageServerCapabilities }; +enum class PluginMessageFormat { + JSON, + Diagnostics, + CodeCompletion, + LanguageServerCapabilities, + SignatureHelp +}; using PluginIDType = Int64; @@ -94,6 +101,10 @@ struct PluginMessage { return *static_cast( data ); } + const LSPSignatureHelp& asSignatureHelp() const { + return *static_cast( data ); + } + bool isResponse() const { return -1 != responseID && 0 != responseID; } bool isRequest() const { return -1 != responseID && 0 == responseID; } @@ -168,24 +179,26 @@ class PluginManager { void setWorkspaceFolder( const std::string& workspaceFolder ); PluginRequestHandle sendRequest( UICodeEditorPlugin* pluginWho, PluginMessageType type, - PluginMessageFormat format, const void* data ) const; + PluginMessageFormat format, const void* data ); void sendResponse( UICodeEditorPlugin* pluginWho, PluginMessageType type, PluginMessageFormat format, const void* data, - const PluginIDType& responseID ) const; + const PluginIDType& responseID ); void sendBroadcast( UICodeEditorPlugin* pluginWho, PluginMessageType, PluginMessageFormat, - const void* data ) const; + const void* data ); void sendBroadcast( const PluginMessageType& notification, const PluginMessageFormat& format, void* data ); void subscribeMessages( UICodeEditorPlugin* plugin, - std::function cb ) const; + std::function cb ); - void unsubscribeMessages( UICodeEditorPlugin* plugin ) const; + void unsubscribeMessages( UICodeEditorPlugin* plugin ); protected: + using SubscribedPlugins = + std::map>; friend class App; std::string mResourcesPath; std::string mPluginsPath; @@ -195,8 +208,8 @@ class PluginManager { std::map mDefinitions; std::shared_ptr mThreadPool; UICodeEditorSplitter* mSplitter{ nullptr }; - std::map> - mSubscribedPlugins; + Mutex mSubscribedPluginsMutex; + SubscribedPlugins mSubscribedPlugins; bool mClosing{ false }; bool hasDefinition( const std::string& id );