From a78cdadfc37e9fa2e961d3e4800bd275f369a9ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 12 Mar 2023 21:19:14 -0300 Subject: [PATCH] eepp: Improved PopUpMenu positioning. ecode: Added quick-fix support. --- src/eepp/ui/uimenu.cpp | 18 ++++++ src/tools/ecode/ecode.cpp | 2 +- .../ecode/plugins/linter/linterplugin.cpp | 64 +++++++++++++++++-- .../ecode/plugins/lsp/lspclientplugin.cpp | 50 ++++++++++++--- .../ecode/plugins/lsp/lspclientplugin.hpp | 3 + .../ecode/plugins/lsp/lspclientserver.cpp | 9 +-- src/tools/ecode/plugins/lsp/lspprotocol.hpp | 1 + src/tools/ecode/plugins/pluginmanager.hpp | 8 ++- 8 files changed, 137 insertions(+), 18 deletions(-) diff --git a/src/eepp/ui/uimenu.cpp b/src/eepp/ui/uimenu.cpp index fc7366da2..c8ca1e3e9 100644 --- a/src/eepp/ui/uimenu.cpp +++ b/src/eepp/ui/uimenu.cpp @@ -688,6 +688,7 @@ void UIMenu::findBestMenuPos( Vector2f& pos, UIMenu* menu, UIMenu* parent, Rectf qScreen( 0.f, 0.f, sceneNode->getPixelsSize().getWidth(), sceneNode->getPixelsSize().getHeight() ); + Vector2f oriPos( pos ); Rectf qPos( pos.x, pos.y, pos.x + menu->getPixelsSize().getWidth(), pos.y + menu->getPixelsSize().getHeight() ); @@ -771,6 +772,23 @@ void UIMenu::findBestMenuPos( Vector2f& pos, UIMenu* menu, UIMenu* parent, pos.y += menu->getPixelsSize().getHeight(); qPos.Top += menu->getPixelsSize().getHeight(); qPos.Bottom += menu->getPixelsSize().getHeight(); + + if ( !qScreen.contains( qPos ) ) { + pos = oriPos; + pos.y -= menu->getPixelsSize().getHeight(); + qPos.Left = pos.x; + qPos.Right = qPos.Left + menu->getPixelsSize().getWidth(); + qPos.Top = pos.y; + qPos.Bottom = qPos.Top + menu->getPixelsSize().getHeight(); + + if ( !qScreen.contains( qPos ) ) { + pos.y = qScreen.Bottom - menu->getPixelsSize().getHeight(); + qPos.Left = pos.x; + qPos.Right = qPos.Left + menu->getPixelsSize().getWidth(); + qPos.Top = pos.y; + qPos.Bottom = qPos.Top + menu->getPixelsSize().getHeight(); + } + } } } } diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 26cf751e3..dc0728a0f 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -3279,7 +3279,7 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe { "symbol-boolean", 0xea8f }, { "symbol-array", 0xea8a }, { "symbol-object", 0xea8b }, { "symbol-key", 0xea93 }, { "symbol-null", 0xea8f }, { "collapse-all", 0xeac5 }, - { "chevron-right", 0xeab6 } }; + { "chevron-right", 0xeab6 }, { "lightbulb-autofix", 0xeb13 } }; for ( const auto& icon : codIcons ) iconTheme->add( UIGlyphIcon::New( icon.first, codIconFont, icon.second ) ); diff --git a/src/tools/ecode/plugins/linter/linterplugin.cpp b/src/tools/ecode/plugins/linter/linterplugin.cpp index e7514ead0..c648234b4 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.cpp +++ b/src/tools/ecode/plugins/linter/linterplugin.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -309,6 +310,38 @@ void LinterPlugin::setMatches( TextDocument* doc, const MatchOrigin& origin, } PluginRequestHandle LinterPlugin::processMessage( const PluginMessage& notification ) { + if ( notification.type == PluginMessageType::DiagnosticsCodeAction && + notification.format == PluginMessageFormat::JSON && notification.isRequest() ) { + PluginIDType id( std::numeric_limits::max() ); + TextDocument* doc = getDocumentFromURI( notification.asJSON().value( "uri", "" ) ); + if ( doc ) { + Lock l( mMatchesMutex ); + auto foundMatch = mMatches.find( doc ); + if ( foundMatch != mMatches.end() ) { + auto pos = TextPosition::fromString( notification.asJSON().value( "pos", "" ) ); + if ( pos.isValid() ) { + auto foundLine = foundMatch->second.find( pos.line() ); + if ( foundLine != foundMatch->second.end() ) { + LSPDiagnosticsCodeAction quickFix; + for ( const auto& match : foundLine->second ) { + if ( !match.codeActions.empty() ) { + for ( const auto& ca : match.codeActions ) { + quickFix = ca; + if ( quickFix.isPreferred ) + break; + } + } + } + mManager->sendResponse( this, PluginMessageType::DiagnosticsCodeAction, + PluginMessageFormat::DiagnosticsCodeAction, + &quickFix, id ); + } + } + } + } + return {}; + } + if ( !mEnableLSPDiagnostics || notification.type != PluginMessageType::Diagnostics || notification.format != PluginMessageFormat::Diagnostics ) return PluginRequestHandle::empty(); @@ -722,6 +755,7 @@ void LinterPlugin::drawAfterLineText( UICodeEditor* editor, const Int64& index, TextDocument* doc = matchIt->first; std::vector& matches = lineIt->second; Float sepSpace = PixelDensity::dpToPx( 24.f ); + bool quickFixRendered = false; for ( size_t i = 0; i < matches.size(); ++i ) { auto& match = matches[i]; @@ -763,10 +797,32 @@ void LinterPlugin::drawAfterLineText( UICodeEditor* editor, const Int64& index, match.box[editor] = box; line.draw( pos.x, pos.y + lineHeight * 0.5f ); + Float rLineWidth = 0; + + if ( !quickFixRendered && doc->getSelection().start().line() == index && + !match.codeActions.empty() ) { + rLineWidth = editor->getLineWidth( index ); + Color wcolor( editor->getColorScheme().getEditorSyntaxStyle( "warning" ).color ); + UIIcon* notification = + editor->getUISceneNode()->getUIIconThemeManager()->findIcon( "lightbulb-autofix" ); + Float size = lineHeight; + Drawable* drawable = notification->getSize( (int)eefloor( size ) ); + if ( drawable == nullptr ) + return; + + Color oldColor( drawable->getColor() ); + drawable->setColor( wcolor ); + drawable->draw( { position.x + rLineWidth, position.y } ); + drawable->setColor( oldColor ); + quickFixRendered = true; + } + if ( !mErrorLens || i != 0 ) continue; - Float lineWidth = editor->getLineWidth( index ) + sepSpace; + if ( rLineWidth == 0 ) + rLineWidth = editor->getLineWidth( index ); + Float lineWidth = rLineWidth + sepSpace; Float realSpace = editor->getViewportWidth(); Float spaceWidth = realSpace - lineWidth; if ( spaceWidth < sepSpace ) @@ -796,14 +852,14 @@ void LinterPlugin::minimapDrawBeforeLineText( UICodeEditor* editor, const Int64& if ( matchIt == mMatches.end() ) return; - std::map>& map = matchIt->second; + const std::map>& map = matchIt->second; auto lineIt = map.find( index ); if ( lineIt == map.end() ) return; TextDocument* doc = matchIt->first; - std::vector& matches = lineIt->second; + const std::vector& matches = lineIt->second; Primitives p; - for ( auto& match : matches ) { + for ( const auto& match : matches ) { if ( match.lineCache != doc->line( index ).getHash() ) return; Color col( diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index 56fd399a6..34e54e1b2 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -504,7 +504,20 @@ void LSPClientPlugin::createLocationsView( UICodeEditor* editor, void LSPClientPlugin::createCodeActionsView( UICodeEditor* editor, const std::vector& cas ) { - auto model = LSPCodeActionModel::create( mManager->getSplitter()->getUISceneNode(), cas ); + bool casEmpty = cas.empty(); + std::shared_ptr model; + if ( mQuickFix.title.empty() && mQuickFix.kind.empty() && + mQuickFix.edit.documentChanges.empty() && mQuickFix.edit.changes.empty() ) { + model = LSPCodeActionModel::create( mManager->getSplitter()->getUISceneNode(), cas ); + } else { + casEmpty = false; + auto casN( cas ); + LSPCodeAction action{ mQuickFix.title, mQuickFix.kind, {}, mQuickFix.edit, {}, + mQuickFix.isPreferred }; + casN.insert( casN.begin(), action ); + model = LSPCodeActionModel::create( mManager->getSplitter()->getUISceneNode(), casN ); + } + createListView( editor, model, [this, editor]( const ModelEvent* modelEvent ) { @@ -514,18 +527,21 @@ void LSPClientPlugin::createCodeActionsView( UICodeEditor* editor, const auto cam = static_cast( modelEvent->getModel() ); if ( cam->hasCodeActions() && editorExists( editor ) ) { const auto& ca = cam->getCodeAction( r ); - auto server = mClientManager.getOneLSPClientServer( editor->getDocumentRef() ); - if ( server && server->getCapabilities().executeCommandProvider ) { - mClientManager.executeCommand( editor->getDocumentRef(), ca.command ); - } else { + + if ( !ca.edit.changes.empty() || !ca.edit.documentChanges.empty() ) mClientManager.applyWorkspaceEdit( ca.edit, []( const auto& ) {} ); - } + + auto server = mClientManager.getOneLSPClientServer( editor->getDocumentRef() ); + if ( server && server->getCapabilities().executeCommandProvider ) + mClientManager.executeCommand( editor->getDocumentRef(), ca.command ); + editor->setFocus(); + } else if ( editorExists( editor ) ) { editor->setFocus(); } modelEvent->getNode()->close(); }, - [this, cas, editor]( UIListView* lv ) { - if ( cas.empty() ) { + [this, casEmpty, editor]( UIListView* lv ) { + if ( casEmpty ) { lv->runOnMainThread( [this, editor] { if ( editorExists( editor ) && @@ -604,6 +620,10 @@ PluginRequestHandle LSPClientPlugin::processMessage( const PluginMessage& msg ) processTextDocumentSymbol( msg ); break; } + case PluginMessageType::DiagnosticsCodeAction: { + processDiagnosticsCodeAction( msg ); + break; + } default: break; } @@ -863,7 +883,21 @@ void LSPClientPlugin::getAndGoToLocation( UICodeEditor* editor, const std::strin } ); } +void LSPClientPlugin::processDiagnosticsCodeAction( const PluginMessage& msg ) { + if ( !( msg.isResponse() && msg.type == PluginMessageType::DiagnosticsCodeAction && + msg.format == PluginMessageFormat::DiagnosticsCodeAction ) ) + return; + mQuickFix = msg.asDiasnosticsCodeAction(); +} + void LSPClientPlugin::codeAction( UICodeEditor* editor ) { + json j; + j["uri"] = editor->getDocument().getURI().toString(); + j["pos"] = editor->getDocument().getSelection().start().toString(); + mQuickFix = {}; + auto req = mManager->sendRequest( PluginMessageType::DiagnosticsCodeAction, + PluginMessageFormat::JSON, &j ); + mClientManager.codeAction( editor->getDocumentRef(), [&, editor]( const LSPClientServer::IdType&, const std::vector& res ) { diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp index fdc2485f0..fa173f3d8 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp @@ -111,6 +111,7 @@ class LSPClientPlugin : public UICodeEditorPlugin { Time mHoverDelay{ Seconds( 1.f ) }; Uint32 mOldTextStyle{ 0 }; Uint32 mOldTextAlign{ 0 }; + LSPDiagnosticsCodeAction mQuickFix; LSPClientPlugin( PluginManager* pluginManager, bool sync ); @@ -168,6 +169,8 @@ class LSPClientPlugin : public UICodeEditorPlugin { void setDocumentSymbolsFromResponse( const PluginIDType& id, const URI& docURI, LSPSymbolInformationList&& res ); + + void processDiagnosticsCodeAction( const PluginMessage& msg ); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.cpp b/src/tools/ecode/plugins/lsp/lspclientserver.cpp index 30e728e57..db3e4a682 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.cpp @@ -573,8 +573,9 @@ static std::vector parseCodeAction( const json& result ) { auto diagnostics = action.contains( MEMBER_DIAGNOSTICS ) ? parseDiagnostics( action.at( MEMBER_DIAGNOSTICS ) ) : std::vector{}; - LSPCodeAction action = { title, kind, diagnostics, edit, command }; - ret.push_back( action ); + auto isPreferred = action.value( "isPreferred", false ); + LSPCodeAction _action = { title, kind, diagnostics, edit, command, isPreferred }; + ret.push_back( _action ); } else { // Command auto command = parseCommand( action ); @@ -595,8 +596,8 @@ static std::vector parseDiagnosticsCodeAction( const j auto edit = action.contains( MEMBER_EDIT ) ? parseWorkSpaceEdit( action.at( MEMBER_EDIT ) ) : LSPWorkspaceEdit{}; - LSPDiagnosticsCodeAction action = { title, kind, isPreferred, edit }; - ret.push_back( action ); + LSPDiagnosticsCodeAction _action = { title, kind, isPreferred, edit }; + ret.push_back( _action ); } } return ret; diff --git a/src/tools/ecode/plugins/lsp/lspprotocol.hpp b/src/tools/ecode/plugins/lsp/lspprotocol.hpp index 0b132c6a7..fb52290db 100644 --- a/src/tools/ecode/plugins/lsp/lspprotocol.hpp +++ b/src/tools/ecode/plugins/lsp/lspprotocol.hpp @@ -201,6 +201,7 @@ struct LSPCodeAction { std::vector diagnostics; LSPWorkspaceEdit edit; LSPCommand command; + bool isPreferred{ false }; }; enum class LSPWorkDoneProgressKind { Begin, Report, End }; diff --git a/src/tools/ecode/plugins/pluginmanager.hpp b/src/tools/ecode/plugins/pluginmanager.hpp index 4283d9075..e0b2199f5 100644 --- a/src/tools/ecode/plugins/pluginmanager.hpp +++ b/src/tools/ecode/plugins/pluginmanager.hpp @@ -76,6 +76,7 @@ enum class PluginMessageType { WorkspaceSymbol, // Request to the LSP server to query workspace symbols TextDocumentSymbol, // Request to the LSP server the document symbols TextDocumentFlattenSymbol, // Request to the LSP server the document symbols flattened + DiagnosticsCodeAction, // Request a code action to anyone that can handle it Undefined }; @@ -88,7 +89,8 @@ enum class PluginMessageFormat { ProjectSearchResult, ShowMessage, ShowDocument, - SymbolInformation + SymbolInformation, + DiagnosticsCodeAction }; class PluginIDType { @@ -180,6 +182,10 @@ struct PluginMessage { return *static_cast( data ); } + const LSPDiagnosticsCodeAction& asDiasnosticsCodeAction() const { + return *static_cast( data ); + } + const PluginIDType& asPluginID() const { return *static_cast( data ); } bool isResponse() const { return -1 != responseID && 0 != responseID; }