diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 3812d6bcd..418f2b7c7 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -266,15 +266,6 @@ void UICodeEditor::draw() { for ( auto& plugin : mPlugins ) plugin->preDraw( this, startScroll, lineHeight, cursor ); - if ( mPluginsTopSpace > 0 ) { - Float curTopPos = 0.f; - for ( auto& plugin : mPluginTopSpaces ) { - plugin.plugin->drawTop( this, { screenStart.x, screenStart.y + curTopPos }, - { mSize.getWidth(), plugin.space }, charSize ); - curTopPos += plugin.space; - } - } - if ( !mLocked && mHighlightCurrentLine ) { for ( const auto& sel : mDoc->getSelections() ) { if ( mDocView.isFolded( sel.start().line(), true ) ) @@ -399,6 +390,15 @@ void UICodeEditor::draw() { if ( mMinimapEnabled ) drawMinimap( screenStart, lineRange, visualLineRange ); + if ( mPluginsTopSpace > 0 ) { + Float curTopPos = 0.f; + for ( auto& plugin : mPluginTopSpaces ) { + plugin.plugin->drawTop( this, { screenStart.x, screenStart.y + curTopPos }, + { mSize.getWidth(), plugin.space }, charSize ); + curTopPos += plugin.space; + } + } + if ( mLocked && mDisplayLockedIcon ) drawLockedIcon( start ); @@ -1380,8 +1380,11 @@ Uint32 UICodeEditor::onMouseDown( const Vector2i& position, const Uint32& flags setFocus(); auto textScreenPos( resolveScreenPosition( position.asFloat() ) ); + Vector2f localPos( convertToNodeSpace( position.asFloat() ) ); + if ( localPos.y < mPluginsTopSpace ) + return UIWidget::onMouseDown( position, flags ); + if ( flags & EE_BUTTON_LMASK ) { - Vector2f localPos( convertToNodeSpace( position.asFloat() ) ); if ( localPos.x < mPaddingPx.Left + getGutterWidth() ) { if ( mDoc->getFoldRangeService().isFoldingRegionInLine( textScreenPos.line() ) ) { if ( mDocView.isFolded( textScreenPos.line() ) ) { @@ -1468,15 +1471,17 @@ Uint32 UICodeEditor::onMouseMove( const Vector2i& position, const Uint32& flags return 1; } + Vector2f localPos( convertToNodeSpace( position.asFloat() ) ); + if ( !minimapHover && isTextSelectionEnabled() && !getUISceneNode()->getEventDispatcher()->isNodeDragging() && NULL != mFont && mMouseDown && - ( flags & EE_BUTTON_LMASK ) ) { + ( flags & EE_BUTTON_LMASK ) && localPos.y >= mPluginsTopSpace ) { TextRange selection = mDoc->getSelection(); selection.setStart( resolveScreenPosition( position.asFloat() ) ); mDoc->setSelection( selection ); } - if ( minimapHover ) { + if ( minimapHover || localPos.y <= mPluginsTopSpace ) { getUISceneNode()->setCursor( Cursor::Arrow ); } else { checkMouseOverColor( position ); @@ -1487,7 +1492,6 @@ Uint32 UICodeEditor::onMouseMove( const Vector2i& position, const Uint32& flags } if ( mShowFoldingRegion && !mFoldsAlwaysVisible ) { - Vector2f localPos( convertToNodeSpace( position.asFloat() ) ); bool oldFoldVisible = mFoldsVisible; mFoldsVisible = localPos.x <= mPaddingPx.Left + getGutterWidth(); if ( oldFoldVisible != mFoldsVisible ) @@ -3777,7 +3781,8 @@ void UICodeEditor::drawLineNumbers( const DocumentLineRange& lineRange, const Ve bool foldVisible = mShowFoldingRegion && mDoc->getFoldRangeService().canFold(); if ( foldVisible ) w += mFoldRegionWidth; - primitives.drawRectangle( Rectf( screenStart, Sizef( w, mSize.getHeight() ) ) ); + primitives.drawRectangle( Rectf( { screenStart.x, screenStart.y + mPluginsTopSpace }, + Sizef( w, mSize.getHeight() - mPluginsTopSpace ) ) ); TextRange selection = mDoc->getSelection( true ); Float lineOffset = getLineOffset(); @@ -4248,6 +4253,8 @@ void UICodeEditor::resetPreviewColor() { } Float UICodeEditor::getMinimapWidth() const { + if ( !mMinimapEnabled ) + return 0.f; Float w = PixelDensity::dpToPx( mMinimapConfig.width ); // Max [mMinimapConfig.maxPercentWidth]% of the editor view width if ( w / getPixelsSize().getWidth() > mMinimapConfig.maxPercentWidth ) @@ -4842,10 +4849,11 @@ bool UICodeEditor::isNotMonospace() const { void UICodeEditor::updateMouseCursor( const Vector2f& position ) { if ( getScreenBounds().contains( position ) ) { - bool overGutter = convertToNodeSpace( position ).x < getGutterWidth(); + auto localPos( convertToNodeSpace( position ) ); + bool overGutterOrTop = localPos.x < getGutterWidth() || localPos.y < mPluginsTopSpace; getUISceneNode()->setCursor( mHandShown ? Cursor::Hand - : ( !overGutter && !mLocked ? Cursor::IBeam : Cursor::Arrow ) ); + : ( !overGutterOrTop && !mLocked ? Cursor::IBeam : Cursor::Arrow ) ); } } diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp index cab723a26..c1af2e141 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp @@ -162,7 +162,7 @@ void AutoCompletePlugin::load( PluginManager* pluginManager ) { if ( j.contains( "config" ) ) { auto& config = j["config"]; if ( config.contains( "suggestions_syntax_highlight" ) ) - mHighlightSuggestions = config.value( "suggestions_syntax_highlight", false ); + mHighlightSuggestions = config.value( "suggestions_syntax_highlight", true ); else { config["suggestions_syntax_highlight"] = mHighlightSuggestions; updateConfigFile = true; diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index db0b3c701..9d5f58287 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -1,5 +1,6 @@ #include "lspclientplugin.hpp" #include "../../version.hpp" +#include #include #include #include @@ -10,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -334,10 +336,13 @@ PluginRequestHandle LSPClientPlugin::processTextDocumentSymbol( const PluginMess const LSPSymbolInformationList& symbols = msg.type == PluginMessageType::TextDocumentSymbol ? getDocumentSymbols( uri ) : getDocumentFlattenSymbols( uri ); - if ( !symbols.empty() ) { - mManager->sendResponse( this, msg.type, PluginMessageFormat::SymbolInformation, &symbols, - uri.toString() ); - return { uri.toString() }; + { + Lock l( mDocSymbolsMutex ); + if ( !symbols.empty() ) { + mManager->sendResponse( this, msg.type, PluginMessageFormat::SymbolInformation, + &symbols, uri.toString() ); + return { uri.toString() }; + } } LSPClientServer* server = mClientManager.getOneLSPClientServer( uri ); @@ -439,17 +444,30 @@ void LSPClientPlugin::setDocumentSymbols( const URI& docURI, LSPSymbolInformatio ? LSPSymbolInformationListHelper::flatten( res ) : res; mDocSymbols[docURI] = std::move( res ); + + getManager()->getSplitter()->forEachDoc( [docURI, this]( TextDocument& doc ) { + if ( doc.getURI() == docURI ) + updateCurrentSymbol( doc ); + } ); } void LSPClientPlugin::setDocumentSymbolsFromResponse( const PluginIDType& id, const URI& docURI, LSPSymbolInformationList&& res ) { setDocumentSymbols( docURI, std::move( res ) ); - mManager->sendResponse( this, PluginMessageType::TextDocumentSymbol, - PluginMessageFormat::SymbolInformation, &getDocumentSymbols( docURI ), - id ); - mManager->sendResponse( this, PluginMessageType::TextDocumentFlattenSymbol, - PluginMessageFormat::SymbolInformation, - &getDocumentFlattenSymbols( docURI ), id ); + + { + const auto& docSymbols = getDocumentSymbols( docURI ); + Lock l( mDocSymbolsMutex ); + mManager->sendResponse( this, PluginMessageType::TextDocumentSymbol, + PluginMessageFormat::SymbolInformation, &docSymbols, id ); + } + + { + const auto& docFlattenSymbols = getDocumentFlattenSymbols( docURI ); + Lock l( mDocSymbolsMutex ); + mManager->sendResponse( this, PluginMessageType::TextDocumentFlattenSymbol, + PluginMessageFormat::SymbolInformation, &docFlattenSymbols, id ); + } } const LSPSymbolInformationList& LSPClientPlugin::getDocumentSymbols( const URI& docURI ) { @@ -966,6 +984,16 @@ void LSPClientPlugin::loadLSPConfig( std::vector& lsps, const std } else { config["disable_semantic_highlighting_lang"] = json::array(); } + + if ( config.contains( "breadcrumb_navigation" ) ) + mBreadcrumb = config.value( "breadcrumb_navigation", true ); + else if ( updateConfigFile ) + config["breadcrumb_navigation"] = mBreadcrumb; + + if ( config.contains( "breadcrumb_height" ) ) + mBreadcrumbHeight = config.value( "breadcrumb_height", "20dp" ); + else if ( updateConfigFile ) + config["breadcrumb_height"] = mBreadcrumbHeight.toString(); } if ( mKeyBindings.empty() ) { @@ -1315,12 +1343,14 @@ void LSPClientPlugin::onRegister( UICodeEditor* editor ) { editor->addEventListener( Event::OnDocumentLoaded, [this, editor]( const Event* ) { mEditorDocs[editor] = editor->getDocumentRef().get(); mClientManager.run( editor->getDocumentRef() ); + updateCurrentSymbol( editor->getDocument() ); } ) ); listeners.push_back( editor->addEventListener( Event::OnCursorPosChange, [this, editor]( const Event* ) { if ( mSymbolInfoShowing ) hideTooltip( editor ); + updateCurrentSymbol( editor->getDocument() ); } ) ); listeners.push_back( @@ -1329,9 +1359,19 @@ void LSPClientPlugin::onRegister( UICodeEditor* editor ) { TextDocument* newDoc = editor->getDocumentRef().get(); Lock l( mDocMutex ); mDocs.erase( oldDoc ); + mDocCurrentSymbols.erase( oldDoc->getURI() ); mEditorDocs[editor] = newDoc; + updateCurrentSymbol( editor->getDocument() ); } ) ); + if ( mBreadcrumb ) { + mPluginTopSpace = mBreadcrumbHeight.asPixels( + getUISceneNode()->getPixelsSize().getWidth(), getUISceneNode()->getPixelsSize(), + getUISceneNode()->getDPI(), getUISceneNode()->getUIThemeManager()->getDefaultFontSize(), + getUISceneNode()->getUIThemeManager()->getDefaultFontSize() ); + editor->registerTopSpace( this, mPluginTopSpace, 0 ); + } + mEditors.insert( { editor, listeners } ); mEditorsTags.insert( { editor, UnorderedSet{} } ); mEditorDocs[editor] = editor->getDocumentRef().get(); @@ -1417,6 +1457,10 @@ void LSPClientPlugin::onUnregister( UICodeEditor* editor ) { const auto& cbs = mEditors[editor]; for ( auto listener : cbs ) editor->removeEventListener( listener ); + + if ( mBreadcrumb ) + editor->unregisterTopSpace( this ); + mEditors.erase( editor ); mEditorsTags.erase( editor ); mEditorDocs.erase( editor ); @@ -1436,6 +1480,7 @@ void LSPClientPlugin::onUnregister( UICodeEditor* editor ) { } mDocs.erase( doc ); + mDocCurrentSymbols.erase( doc->getURI() ); } bool LSPClientPlugin::onCreateContextMenu( UICodeEditor* editor, UIPopUpMenu* menu, @@ -1664,6 +1709,129 @@ void LSPClientPlugin::onVersionUpgrade( Uint32 oldVersion, Uint32 ) { } } +void LSPClientPlugin::drawTop( UICodeEditor* editor, const Vector2f& screenStart, const Sizef& size, + const Float& /*fontSize*/ ) { + Float width = size.getWidth() - editor->getMinimapWidth(); + Primitives p; + Color backColor( editor->getColorScheme().getEditorColor( SyntaxStyleTypes::Background ) ); + p.setColor( backColor ); + p.drawRectangle( Rectf( screenStart, Sizef( width, mPluginTopSpace ) ) ); + + Color lineColor( editor->getColorScheme().getEditorColor( SyntaxStyleTypes::LineBreakColumn ) ); + p.setColor( lineColor ); + Vector2f p1( screenStart.x, screenStart.y + size.getHeight() ); + p.drawLine( { p1, { p1.x + width, p1.y } } ); + + Font* font = getUISceneNode()->getUIThemeManager()->getDefaultFont(); + if ( !font ) + return; + + Float fontSize = editor->getUISceneNode()->getUIThemeManager()->getDefaultFontSize(); + + bool isPath = true; + std::string path( editor->getDocument().getFilePath() ); + if ( path.empty() ) { + path = editor->getDocument().getFilename(); + isPath = false; + } + + Color textColor( editor->getColorScheme().getEditorColor( SyntaxStyleTypes::LineNumber2 ) ); + const auto& workspace = getManager()->getWorkspaceFolder(); + if ( isPath && !workspace.empty() && String::startsWith( path, workspace ) ) + path = path.substr( workspace.size() ); + Float textOffsetY = eefloor( ( size.getHeight() - font->getLineSpacing( fontSize ) ) * 0.5f ); + + Vector2f pos( screenStart.x + eefloor( PixelDensity::dpToPx( 8 ) ), + screenStart.y + textOffsetY ); + + auto drawn = Text::draw( String::fromUtf8( path ), pos, font, fontSize, textColor ); + + Lock l( mDocSymbolsMutex ); + auto symbolsInfoIt = mDocCurrentSymbols.find( editor->getDocument().getURI() ); + if ( symbolsInfoIt == mDocCurrentSymbols.end() ) + return; + + pos.x += drawn.getWidth(); + UIIcon* icon = getUISceneNode()->findIcon( "chevron-right" ); + Float textHeight = drawn.getHeight(); + + const auto drawSep = [&pos, textHeight, icon, textColor, &drawn, &screenStart, textOffsetY]() { + if ( icon ) { + pos.x += eefloor( PixelDensity::dpToPx( 8 ) ); + Float iconSize = PixelDensity::dpToPxI( drawn.getHeight() * 0.5f ); + auto iconDrawable = icon->getSize( iconSize ); + Color c = iconDrawable->getColor(); + iconDrawable->setColor( textColor ); + Float iconHeight = iconDrawable->getPixelsSize().getHeight(); + Vector2f iconPos( { pos.x, screenStart.y + textOffsetY + + eefloor( ( textHeight - iconHeight ) * 0.5f ) } ); + iconDrawable->draw( iconPos ); + pos.x += + iconDrawable->getPixelsSize().getWidth() + eefloor( PixelDensity::dpToPx( 8 ) ); + iconDrawable->setColor( c ); + } else { + pos.x += eefloor( PixelDensity::dpToPx( 16 ) ); + } + }; + + const auto& symbolsInfo = symbolsInfoIt->second; + + for ( const auto& info : symbolsInfo ) { + drawSep(); + UIIcon* iconKind = getUISceneNode()->findIcon( info.icon ); + if ( iconKind ) { + auto iconDrawable = iconKind->getSize( fontSize ); + Color c = iconDrawable->getColor(); + iconDrawable->setColor( textColor ); + Float iconHeight = iconDrawable->getPixelsSize().getHeight(); + iconDrawable->draw( { pos.x, screenStart.y + textOffsetY + + eefloor( ( textHeight - iconHeight ) * 0.5f ) } ); + pos.x += iconDrawable->getPixelsSize().getWidth() + PixelDensity::dpToPxI( 4 ); + iconDrawable->setColor( c ); + } + + drawn = Text::draw( String::fromUtf8( info.name ), pos, font, fontSize, textColor ); + pos.x += drawn.getWidth(); + } +} + +void LSPClientPlugin::updateCurrentSymbol( TextDocument& doc ) { + if ( !mBreadcrumb ) + return; + + Lock l( mDocSymbolsMutex ); + URI uri = doc.getURI(); + auto symbolsIt = mDocSymbols.find( uri ); + if ( symbolsIt == mDocSymbols.end() ) { + mDocCurrentSymbols[uri] = {}; + return; + } + + LSPSymbolInformationList* list = &symbolsIt->second; + auto sel = doc.getSelection(); + LSPSymbolInformationList::iterator foundIt; + std::vector symbolsInfo; + + bool found = false; + do { + foundIt = std::lower_bound( list->begin(), list->end(), sel, + []( const LSPSymbolInformation& cur, const TextRange& sel ) { + return cur.range < sel; + } ); + found = foundIt != list->end() && foundIt->range.contains( sel ); + if ( found ) { + symbolsInfo.push_back( { String::fromUtf8( foundIt->name ), + LSPSymbolKindHelper::toIconString( foundIt->kind ) } ); + if ( foundIt->children.empty() ) + break; + list = &foundIt->children; + } else + break; + } while ( found ); + + mDocCurrentSymbols[uri] = std::move( symbolsInfo ); +} + const LSPClientServerManager& LSPClientPlugin::getClientManager() const { return mClientManager; } diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp index 4860b4e7b..b6e849d4c 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp @@ -93,6 +93,9 @@ class LSPClientPlugin : public Plugin { void onVersionUpgrade( Uint32 oldVersion, Uint32 currentVersion ); + void drawTop( UICodeEditor* editor, const Vector2f& screenStart, const Sizef& size, + const Float& fontSize ); + protected: friend class LSPDocumentClient; friend class LSPClientServer; @@ -114,6 +117,8 @@ class LSPClientPlugin : public Plugin { bool mSemanticHighlighting{ true }; bool mSilence{ false }; bool mTrimLogs{ false }; + bool mBreadcrumb{ true }; + StyleSheetLength mBreadcrumbHeight{ "20dp" }; UnorderedMap mKeyBindings; /* cmd, shortcut */ UnorderedMap> mDelayedDocs; Uint32 mHoverWaitCb{ 0 }; @@ -126,6 +131,12 @@ class LSPClientPlugin : public Plugin { String::HashType mConfigHash{ 0 }; Color mOldBackgroundColor; std::string mOldMaxWidth; + Float mPluginTopSpace{ 0 }; + struct DisplaySymbolInfo { + String name; + std::string icon; + }; + UnorderedMap> mDocCurrentSymbols; LSPClientPlugin( PluginManager* pluginManager, bool sync ); @@ -189,6 +200,8 @@ class LSPClientPlugin : public Plugin { void processDiagnosticsCodeAction( const PluginMessage& msg ); void renameSymbol( UICodeEditor* editor ); + + void updateCurrentSymbol( TextDocument& doc ); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.cpp b/src/tools/ecode/plugins/lsp/lspclientserver.cpp index 1ddb31f0b..21dcb2c67 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.cpp @@ -35,6 +35,7 @@ static const char* MEMBER_POSITION = "position"; static const char* MEMBER_POSITIONS = "positions"; static const char* MEMBER_LOCATION = "location"; static const char* MEMBER_RANGE = "range"; +static const char* MEMBER_SELECTION_RANGE = "selectionRange"; static const char* MEMBER_LINE = "line"; static const char* MEMBER_CHARACTER = "character"; static const char* MEMBER_KIND = "kind"; @@ -394,8 +395,12 @@ static bool isPositionValid( const TextPosition& pos ) { struct LSPSymbolInformationTmp { LSPSymbolInformationTmp() = default; LSPSymbolInformationTmp( const std::string& _name, LSPSymbolKind _kind, TextRange _range, - const std::string& _detail ) : - name( _name ), detail( _detail ), kind( _kind ), range( _range ) {} + const std::string& _detail, TextRange _selectionRange ) : + name( _name ), + detail( _detail ), + kind( _kind ), + range( _range ), + selectionRange( _selectionRange ) {} std::string name; std::string detail; LSPSymbolKind kind{ LSPSymbolKind::File }; @@ -415,6 +420,10 @@ struct LSPSymbolInformationTmp { info.score = std::move( tmp.score ); for ( const auto& child : tmp.children ) info.children.push_back( fromTmp( child ) ); + std::sort( info.children.begin(), info.children.end(), + []( const LSPSymbolInformation& left, const LSPSymbolInformation& right ) { + return left.range < right.range; + } ); return info; } }; @@ -430,7 +439,13 @@ static LSPSymbolInformationList parseDocumentSymbols( const json& result, bool i const auto& mrange = symbol.contains( MEMBER_RANGE ) ? symbol.at( MEMBER_RANGE ) : symbol[MEMBER_LOCATION].at( MEMBER_RANGE ); + + const auto& srange = symbol.contains( MEMBER_SELECTION_RANGE ) + ? symbol.at( MEMBER_SELECTION_RANGE ) + : symbol[MEMBER_LOCATION].at( MEMBER_SELECTION_RANGE ); + auto range = parseRange( mrange ); + auto selectionRange = parseRange( srange ); auto it = index.end(); if ( !parent ) { auto container = @@ -453,7 +468,7 @@ static LSPSymbolInformationList parseDocumentSymbols( const json& result, bool i auto name = symbol.at( ( "name" ) ).get(); auto kind = static_cast( symbol.at( MEMBER_KIND ).get() ); auto detail = symbol.value( MEMBER_DETAIL, "" ); - list->push_back( { name, kind, range, detail } ); + list->push_back( { name, kind, range, detail, selectionRange } ); index.insert( std::make_pair( name, &list->back() ) ); if ( symbol.contains( "children" ) ) { const auto& children = symbol.at( ( "children" ) );