diff --git a/.gitignore b/.gitignore index ec84ed505..729ca611d 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,7 @@ bin/eepp-* bin/ecode* ee.tag log.log -external_projects.lua +/external_projects*.lua *.user *.filters *.suo diff --git a/premake4.lua b/premake4.lua index 62fcb6f31..f4902a2d3 100644 --- a/premake4.lua +++ b/premake4.lua @@ -1476,3 +1476,7 @@ solution "eepp" if os.isfile("external_projects.lua") then dofile("external_projects.lua") end + +if os.isfile("external_projects_premake4.lua") then + dofile("external_projects_premake4.lua") +end diff --git a/premake5.lua b/premake5.lua index 756354788..d38ba3635 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1212,3 +1212,7 @@ workspace "eepp" if os.isfile("external_projects.lua") then dofile("external_projects.lua") end + +if os.isfile("external_projects_premake5.lua") then + dofile("external_projects_premake5.lua") +end diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index a814fde6b..d18bbda0a 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -1694,30 +1694,30 @@ void UICodeEditor::scrollTo( TextRange position, bool centered, bool forceExactP Int64 minDistance = mHScrollBar->isVisible() ? 3 : 2; - if ( forceExactPosition || position.start().line() <= (Int64)lineRange.first || - position.start().line() >= (Int64)lineRange.second - minDistance ) { + if ( forceExactPosition || position.end().line() <= (Int64)lineRange.first || + position.end().line() >= (Int64)lineRange.second - minDistance ) { // Vertical Scroll Float lineHeight = getLineHeight(); - Float min = eefloor( lineHeight * ( eemax( 0, position.start().line() - 1 ) ) ); + Float min = eefloor( lineHeight * ( eemax( 0, position.end().line() - 1 ) ) ); Float max = - eefloor( lineHeight * ( position.start().line() + minDistance ) - mSize.getHeight() ); + eefloor( lineHeight * ( position.end().line() + minDistance ) - mSize.getHeight() ); Float halfScreenLines = eefloor( mSize.getHeight() / lineHeight * 0.5f ); if ( forceExactPosition ) { setScrollY( lineHeight * - ( eemax( 0, position.start().line() - 1 - + ( eemax( 0, position.end().line() - 1 - ( centered ? halfScreenLines : 0 ) ) ) ); } else if ( min < mScroll.y ) { if ( centered ) { - if ( position.start().line() - 1 - halfScreenLines >= 0 ) - min = eefloor( lineHeight * ( eemax( 0, position.start().line() - 1 - + if ( position.end().line() - 1 - halfScreenLines >= 0 ) + min = eefloor( lineHeight * ( eemax( 0, position.end().line() - 1 - halfScreenLines ) ) ); } setScrollY( min ); } else if ( max > mScroll.y ) { if ( centered ) { max = eefloor( lineHeight * - ( position.start().line() + minDistance + halfScreenLines ) - + ( position.end().line() + minDistance + halfScreenLines ) - mSize.getHeight() ); max = eemin( max, getMaxScroll().y ); } diff --git a/src/thirdparty/efsw b/src/thirdparty/efsw index 960d474c7..03e36f9f8 160000 --- a/src/thirdparty/efsw +++ b/src/thirdparty/efsw @@ -1 +1 @@ -Subproject commit 960d474c745b8401caca7169a33410ea64aa797b +Subproject commit 03e36f9f80170881a145161e66b62fdf8bb30cc8 diff --git a/src/tools/ecode/plugins/linter/linterplugin.cpp b/src/tools/ecode/plugins/linter/linterplugin.cpp index fe8ddc5c0..d021f80cd 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.cpp +++ b/src/tools/ecode/plugins/linter/linterplugin.cpp @@ -339,6 +339,56 @@ void LinterPlugin::setMatches( TextDocument* doc, const MatchOrigin& origin, invalidateEditors( doc ); } +// TODO: Clean up this +static json toJson( const TextPosition& pos ) { + return json{ { "line", pos.line() }, { "character", pos.column() } }; +} + +static json toJson( const TextRange& pos ) { + return json{ { "start", toJson( pos.start() ) }, { "end", toJson( pos.end() ) } }; +} + +static json toJson( const LSPLocation& location ) { + if ( !location.uri.empty() ) { + return json{ { "uri", location.uri.toString() }, { "range", toJson( location.range ) } }; + } + return json(); +} + +static json toJson( const LSPDiagnosticRelatedInformation& related ) { + auto loc = toJson( related.location ); + if ( loc.is_object() ) { + return json{ { "location", toJson( related.location ) }, { "message", related.message } }; + } + return json(); +} + +static json toJson( const LSPDiagnostic& diagnostic ) { + // required + auto result = json(); + result["range"] = toJson( diagnostic.range ); + result["message"] = diagnostic.message; + // optional + if ( !diagnostic.code.empty() ) + result["code"] = diagnostic.code; + if ( diagnostic.severity != LSPDiagnosticSeverity::Unknown ) + result["severity"] = static_cast( diagnostic.severity ); + if ( !diagnostic.source.empty() ) + result["source"] = diagnostic.source; + json relatedInfo; + for ( const auto& vrelated : diagnostic.relatedInformation ) { + auto related = toJson( vrelated ); + if ( related.is_object() ) { + relatedInfo.push_back( related ); + } + } + if ( !relatedInfo.empty() ) + result["relatedInformation"] = relatedInfo; + if ( !diagnostic.data.empty() ) + result["data"] = diagnostic.data; + return result; +} + PluginRequestHandle LinterPlugin::processMessage( const PluginMessage& notification ) { if ( notification.type == PluginMessageType::FileSystemListenerReady ) { subscribeFileSystemListener(); @@ -352,27 +402,30 @@ PluginRequestHandle LinterPlugin::processMessage( const PluginMessage& notificat 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 ); + if ( foundMatch == mMatches.end() ) + return {}; + + auto pos = TextPosition::fromString( notification.asJSON().value( "pos", "" ) ); + if ( !pos.isValid() ) + return {}; + + auto foundLine = foundMatch->second.find( pos.line() ); + if ( foundLine == foundMatch->second.end() ) + return {}; + + LSPDiagnosticsCodeAction quickFix; + for ( const auto& match : foundLine->second ) { + if ( !match.diagnostic.codeActions.empty() ) { + for ( const auto& ca : match.diagnostic.codeActions ) { + quickFix = ca; + if ( quickFix.isPreferred ) + break; } } } + + mManager->sendResponse( this, PluginMessageType::DiagnosticsCodeAction, + PluginMessageFormat::DiagnosticsCodeAction, &quickFix, id ); } return {}; @@ -393,7 +446,7 @@ PluginRequestHandle LinterPlugin::processMessage( const PluginMessage& notificat if ( found == mMatches.end() ) return {}; - TextPosition pos( LSPConverter::fromJSON( j ) ); + TextPosition pos( LSPConverter::parsePosition( j ) ); const auto& docMatches = found->second; @@ -420,6 +473,46 @@ PluginRequestHandle LinterPlugin::processMessage( const PluginMessage& notificat return {}; } + if ( notification.type == PluginMessageType::GetDiagnostics && + notification.format == PluginMessageFormat::JSON && notification.isRequest() ) { + const json& j = notification.asJSON(); + if ( !j.contains( "uri" ) || !j.contains( "line" ) || !j.contains( "character" ) ) + return {}; + URI uri( j["uri"].get() ); + TextDocument* doc = getDocumentFromURI( uri ); + if ( nullptr == doc ) + return {}; + + Lock l( mMatchesMutex ); + auto found = mMatches.find( doc ); + if ( found == mMatches.end() ) + return {}; + + TextPosition pos( LSPConverter::parsePosition( j ) ); + + const auto& docMatches = found->second; + + auto foundLine = docMatches.find( pos.line() ); + if ( foundLine == docMatches.end() ) + return {}; + + const auto& matches = foundLine->second; + + for ( const auto& match : matches ) { + if ( pos.column() >= match.range.start().column() && + pos.column() <= match.range.end().column() ) { + PluginInmediateResponse msg; + msg.type = PluginMessageType::GetDiagnostics; + json rj; + rj["diagnostics"] = json::array( { toJson( match.diagnostic ) } ); + msg.data = std::move( rj ); + return PluginRequestHandle( msg ); + } + } + + return {}; + } + if ( !mEnableLSPDiagnostics || notification.type != PluginMessageType::Diagnostics || notification.format != PluginMessageFormat::Diagnostics ) return PluginRequestHandle::empty(); @@ -439,8 +532,7 @@ PluginRequestHandle LinterPlugin::processMessage( const PluginMessage& notificat match.type = getLinterTypeFromSeverity( diag.severity ); match.lineCache = doc->line( match.range.start().line() ).getHash(); match.origin = MatchOrigin::Diagnostics; - if ( !diag.codeActions.empty() ) - match.codeActions = diag.codeActions; + match.diagnostic = std::move( diag ); matches[match.range.start().line()].emplace_back( std::move( match ) ); } @@ -906,7 +998,7 @@ void LinterPlugin::drawAfterLineText( UICodeEditor* editor, const Int64& index, Float rLineWidth = 0; if ( !quickFixRendered && doc->getSelection().start().line() == index && - !match.codeActions.empty() ) { + !match.diagnostic.codeActions.empty() ) { rLineWidth = editor->getLineWidth( index ); Color wcolor( editor->getColorScheme().getEditorSyntaxStyle( "warning" ).color ); if ( nullptr == mLightbulbIcon ) { diff --git a/src/tools/ecode/plugins/linter/linterplugin.hpp b/src/tools/ecode/plugins/linter/linterplugin.hpp index d1aa3e1cf..8f03e9963 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.hpp +++ b/src/tools/ecode/plugins/linter/linterplugin.hpp @@ -42,10 +42,10 @@ struct LinterMatch { std::string text; TextRange range; LinterType type{ LinterType::Error }; - String::HashType lineCache; + String::HashType lineCache{ 0 }; MatchOrigin origin{ MatchOrigin::Linter }; std::map box; - std::vector codeActions; + LSPDiagnostic diagnostic; }; class LinterPlugin : public Plugin { diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index afb044523..b81d0f68b 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -184,7 +184,7 @@ LSPPositionAndServer getLSPLocationFromJSON( LSPClientServerManager& manager, co if ( !data.contains( "uri" ) || !data.contains( "position" ) ) return {}; - TextPosition position( LSPConverter::fromJSON( data["position"] ) ); + TextPosition position( LSPConverter::parsePosition( data["position"] ) ); if ( !position.isValid() ) return {}; @@ -1004,19 +1004,33 @@ 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; + // It seems that we don't require this hack anymore + // TODO: Check if this is needed in some situation + /*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 ); + PluginMessageFormat::JSON, &j );*/ + + json j2; + j2["uri"] = editor->getDocument().getURI().toString(); + j2["line"] = editor->getDocument().getSelection().start().line(); + j2["character"] = editor->getDocument().getSelection().start().column(); + + auto resp = + mManager->sendRequest( PluginMessageType::GetDiagnostics, PluginMessageFormat::JSON, &j2 ); + nlohmann::json diagnostics; + if ( resp.isResponse() ) + diagnostics = std::move( resp.getResponse().data ); mClientManager.codeAction( - editor->getDocumentRef(), + editor->getDocumentRef(), diagnostics, [&, editor]( const LSPClientServer::IdType&, const std::vector& res ) { createCodeActionsView( editor, res ); } ); diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.cpp b/src/tools/ecode/plugins/lsp/lspclientserver.cpp index f642b62d8..6bdd1c245 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.cpp @@ -289,6 +289,12 @@ static void fromJson( LSPWorkspaceFoldersServerCapabilities& options, const json } } +static void fromJson( LSPCodeLensOptions& options, const json& json ) { + options.supported = true; + if ( json.is_object() ) + options.resolveProvider = json.value( "resolveProvider", false ); +} + static void fromJson( LSPSemanticTokensOptions& options, const json& data ) { if ( data.empty() ) return; @@ -373,6 +379,8 @@ static void fromJson( LSPServerCapabilities& caps, const json& json ) { fromJson( caps.workspaceFolders, workspace["workspaceFolders"] ); } caps.selectionRangeProvider = toBoolOrObject( json, "selectionRangeProvider" ); + if ( json.contains( "codeLensProvider" ) ) + fromJson( caps.codeLensProvider, json["codeLensProvider"] ); caps.ready = true; } @@ -586,7 +594,17 @@ static std::vector parseDiagnostics( const json& result ) { relatedInfoList.push_back( { relLocation, relMessage } ); } } - ret.push_back( { range, severity, code, source, message, relatedInfoList, {} } ); + json data; + if ( diag.contains( "data" ) ) + data = diag["data"]; + ret.push_back( { std::move( range ), + std::move( severity ), + std::move( code ), + std::move( source ), + std::move( message ), + std::move( relatedInfoList ), + {}, + std::move( data ) } ); } return ret; } @@ -640,59 +658,39 @@ static std::vector parseDiagnosticsCodeAction( const j return ret; } -static json toJson( const LSPLocation& location ) { - if ( !location.uri.empty() ) { - return json{ { MEMBER_URI, location.uri.toString() }, - { MEMBER_RANGE, toJson( location.range ) } }; - } - return json(); -} +static std::vector parseCodeLens( const json& result ) { + if ( result.empty() || !result.is_array() ) + return {}; -static json toJson( const LSPDiagnosticRelatedInformation& related ) { - auto loc = toJson( related.location ); - if ( loc.is_object() ) { - return json{ { MEMBER_LOCATION, toJson( related.location ) }, - { MEMBER_MESSAGE, related.message } }; - } - return json(); -} + std::vector codeLens; -static json toJson( const LSPDiagnostic& diagnostic ) { - // required - auto result = json(); - result[MEMBER_RANGE] = toJson( diagnostic.range ); - result[MEMBER_MESSAGE] = diagnostic.message; - // optional - if ( !diagnostic.code.empty() ) - result[( "code" )] = diagnostic.code; - if ( diagnostic.severity != LSPDiagnosticSeverity::Unknown ) - result[( "severity" )] = static_cast( diagnostic.severity ); - if ( !diagnostic.source.empty() ) - result[( "source" )] = diagnostic.source; - json relatedInfo; - for ( const auto& vrelated : diagnostic.relatedInformation ) { - auto related = toJson( vrelated ); - if ( related.is_object() ) { - relatedInfo.push_back( related ); - } + for ( const auto& codeLen : result ) { + if ( !codeLen.contains( "range" ) ) + continue; + LSPCodeLens cl; + cl.range = parseRange( codeLen["range"] ); + if ( codeLen.contains( "command" ) ) + cl.command = parseCommand( codeLen["command"] ); + if ( codeLen.contains( "data" ) ) + cl.data = codeLen["data"]; + + codeLens.emplace_back( cl ); } - result[( "relatedInformation" )] = relatedInfo; - return result; + + return codeLens; } static json codeActionParams( const URI& document, const TextRange& range, - const std::vector& kinds, - const std::vector& diagnostics ) { + const std::vector& kinds, const json& diagnostics ) { auto params = textDocumentParams( document ); params[MEMBER_RANGE] = toJson( range ); json context; json diags = json::array(); - for ( const auto& diagnostic : diagnostics ) - diags.push_back( toJson( diagnostic ) ); context[MEMBER_DIAGNOSTICS] = diags; if ( !kinds.empty() ) context["only"] = json( kinds ); - params["context"] = context; + if ( !diagnostics.empty() ) + params["context"] = diagnostics; return params; } @@ -763,7 +761,12 @@ static std::vector parseDiagnosticsArr( const json& result ) { std::vector codeActions; if ( diag.contains( "codeActions" ) ) codeActions = parseDiagnosticsCodeAction( diag["codeActions"] ); - ret.push_back( { range, severity, code, source, message, relatedInfoList, codeActions } ); + nlohmann::json data; + if ( diag.contains( "data" ) ) + data = diag["data"]; + ret.push_back( { std::move( range ), std::move( severity ), std::move( code ), + std::move( source ), std::move( message ), std::move( relatedInfoList ), + std::move( codeActions ), std::move( data ) } ); } return ret; } @@ -990,21 +993,30 @@ void LSPClientServer::registerCapabilities( const json& jcap ) { for ( const auto& reg : registrations ) { if ( reg.contains( "method" ) ) { - if ( reg["method"] == "workspace/executeCommand" ) { + const auto& method = reg["method"]; + if ( method == "workspace/executeCommand" ) { mCapabilities.executeCommandProvider = true; registered = true; - } else if ( reg["method"] == "textDocument/documentSymbol" ) { + } else if ( method == "textDocument/documentSymbol" ) { mCapabilities.documentSymbolProvider = true; registered = true; - } else if ( reg["method"] == "textDocument/rename" ) { + } else if ( method == "textDocument/rename" ) { mCapabilities.renameProvider = true; registered = true; - } else if ( reg["method"] == "textDocument/formatting" ) { + } else if ( method == "textDocument/formatting" ) { mCapabilities.documentFormattingProvider = true; registered = true; - } else if ( reg["method"] == "textDocument/rangeFormatting" ) { + } else if ( method == "textDocument/rangeFormatting" ) { mCapabilities.documentRangeFormattingProvider = true; registered = true; + } else if ( method == "textDocument/codeLens" ) { + mCapabilities.codeLensProvider.supported = true; + if ( reg.contains( "registerOptions" ) ) + fromJson( mCapabilities.codeLensProvider, reg["registerOptions"] ); + registered = true; + } else if ( method == "textDocument/codeAction" ) { + mCapabilities.codeActionProvider = true; + registered = true; } } } @@ -1023,6 +1035,9 @@ void LSPClientServer::initialize() { { "codeActionLiteralSupport", json{ { "codeActionKind", json{ { "valueSet", json::array( { "quickfix", "refactor", "source" } ) } } } } } }; + codeAction["dataSupport"] = true; + codeAction["isPreferredSupport"] = true; + codeAction["dynamicRegistration"] = true; json semanticTokens{ { "requests", json{ { "range", true }, { "full", json{ { "delta", true } } } } }, @@ -1044,6 +1059,7 @@ void LSPClientServer::initialize() { workspace["executeCommand"] = json{ { "dynamicRegistration", true } }; workspace["workspaceFolders"] = true; workspace["semanticTokens"] = json{ { "refreshSupport", true } }; + workspace["codeLens"] = json{ { "refreshSupport", true } }; json capabilities{ { "textDocument", @@ -1061,6 +1077,7 @@ void LSPClientServer::initialize() { { "rename", json{ { "dynamicRegistration", true } } }, { "formatting", json{ { "dynamicRegistration", true } } }, { "rangeFormatting", json{ { "dynamicRegistration", true } } }, + { "codeLens", json{ { "dynamicRegistration", true } } }, } }, { "window", json{ { "workDoneProgress", true }, { "showMessage", showMessage }, @@ -1360,6 +1377,12 @@ void LSPClientServer::refreshSmenaticHighlighting() { } } +void LSPClientServer::refreshCodeLens() { + Lock l( mClientsMutex ); + for ( const auto& client : mClients ) + client.second->requestCodeLens(); +} + LSPClientServer::LSPRequestHandle LSPClientServer::didOpen( const URI& document, const std::string& text, int version ) { auto params = textDocumentParams( textDocumentItem( document, mLSP.language, text, version ) ); @@ -1631,6 +1654,10 @@ void LSPClientServer::processRequest( const json& msg ) { refreshSmenaticHighlighting(); write( newEmptyResult( msgid ) ); return; + } else if ( method == "workspace/codeLens/refresh" ) { + refreshCodeLens(); + write( newEmptyResult( msgid ) ); + return; } else if ( method == "client/registerCapability" ) { registerCapabilities( msg[MEMBER_PARAMS] ); write( newEmptyResult( msgid ) ); @@ -1890,15 +1917,15 @@ LSPClientServer::didChangeWorkspaceFolders( const std::vector& kinds, - std::vector diagnostics, + const nlohmann::json& diagnostics, const JsonReplyHandler& h ) { - auto params = codeActionParams( document, range, kinds, std::move( diagnostics ) ); + auto params = codeActionParams( document, range, kinds, diagnostics ); sendAsync( newRequest( "textDocument/codeAction", params ), h ); } void LSPClientServer::documentCodeAction( const URI& document, const TextRange& range, const std::vector& kinds, - std::vector diagnostics, + const nlohmann::json& diagnostics, const CodeActionHandler& h ) { documentCodeAction( document, range, kinds, diagnostics, [h]( const IdType& id, const json& json ) { @@ -1907,6 +1934,17 @@ void LSPClientServer::documentCodeAction( const URI& document, const TextRange& } ); } +void LSPClientServer::documentCodeLens( const URI& document, const JsonReplyHandler& h ) { + sendAsync( newRequest( "textDocument/codeLens", textDocumentParams( document ) ), h ); +} + +void LSPClientServer::documentCodeLens( const URI& document, const CodeLensHandler& h ) { + documentCodeLens( document, [h]( const IdType& id, const json& json ) { + if ( h ) + h( id, parseCodeLens( json ) ); + } ); +} + void LSPClientServer::documentHover( const URI& document, const TextPosition& pos, const JsonReplyHandler& h ) { auto params = textDocumentPositionParams( document, pos ); diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.hpp b/src/tools/ecode/plugins/lsp/lspclientserver.hpp index 1734d3e4a..431bf5554 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.hpp @@ -38,6 +38,7 @@ class LSPClientServer { using JsonReplyHandler = ReplyHandler; using CodeActionHandler = ReplyHandler>; + using CodeLensHandler = ReplyHandler>; using HoverHandler = ReplyHandler; using CompletionHandler = ReplyHandler; using SymbolInformationHandler = WReplyHandler; @@ -150,11 +151,15 @@ class LSPClientServer { void documentCodeAction( const URI& document, const TextRange& range, const std::vector& kinds, - std::vector diagnostics, const JsonReplyHandler& h ); + const nlohmann::json& diagnostics, const JsonReplyHandler& h ); void documentCodeAction( const URI& document, const TextRange& range, const std::vector& kinds, - std::vector diagnostics, const CodeActionHandler& h ); + const nlohmann::json& diagnostics, const CodeActionHandler& h ); + + void documentCodeLens( const URI& document, const JsonReplyHandler& h ); + + void documentCodeLens( const URI& document, const CodeLensHandler& h ); void documentHover( const URI& document, const TextPosition& pos, const JsonReplyHandler& h ); @@ -300,6 +305,8 @@ class LSPClientServer { const JsonReplyHandler& eh = nullptr ); void refreshSmenaticHighlighting(); + + void refreshCodeLens(); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp index bcedcd29e..a2f6aad37 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp @@ -436,18 +436,23 @@ void LSPClientServerManager::getSymbolReferences( std::shared_ptr } void LSPClientServerManager::codeAction( std::shared_ptr doc, + const nlohmann::json& diagnostics, const LSPClientServer::CodeActionHandler& h ) { auto* server = getOneLSPClientServer( doc ); if ( !server ) return; auto range = doc->getSelection(); - if ( !doc->hasSelection() ) { + if ( !diagnostics.empty() && diagnostics.contains( "diagnostics" ) && + diagnostics["diagnostics"].is_array() && !diagnostics["diagnostics"].empty() && + diagnostics["diagnostics"][0].contains( "range" ) ) { + range = LSPConverter::parseRange( diagnostics["diagnostics"][0]["range"] ); + } else if ( !doc->hasSelection() ) { range = { doc->startOfLine( range.start() ), doc->positionOffset( doc->endOfLine( range.end() ), 1 ) }; } - server->documentCodeAction( doc->getURI(), range, {}, {}, h ); + server->documentCodeAction( doc->getURI(), range, {}, diagnostics, h ); } void LSPClientServerManager::memoryUsage( std::shared_ptr doc ) { diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp index b1de49f52..2ca2e5d4f 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp @@ -71,7 +71,7 @@ class LSPClientServerManager { void getSymbolReferences( std::shared_ptr doc ); - void codeAction( std::shared_ptr doc, + void codeAction( std::shared_ptr doc, const nlohmann::json& diagnostics, const LSPClientServer::CodeActionHandler& h ); void memoryUsage( std::shared_ptr doc ); @@ -99,6 +99,7 @@ class LSPClientServerManager { void requestSymanticHighlighting( std::shared_ptr doc ); void rangeFormatting( std::shared_ptr doc ); + protected: friend class LSPClientServer; PluginManager* mPluginManager{ nullptr }; diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp index 41144851d..d6f05bb2e 100644 --- a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp @@ -33,6 +33,7 @@ LSPDocumentClient::~LSPDocumentClient() { void LSPDocumentClient::onDocumentLoaded( TextDocument* ) { requestSemanticHighlightingDelayed(); + // requestCodeLens(); } void LSPDocumentClient::onDocumentTextChanged( const DocumentContentChange& change ) { @@ -104,6 +105,7 @@ int LSPDocumentClient::getVersion() const { void LSPDocumentClient::onServerInitialized() { requestSymbols(); requestSemanticHighlighting(); + // requestCodeLens(); } void LSPDocumentClient::refreshTag() { @@ -178,6 +180,21 @@ bool LSPDocumentClient::isWaitingSemanticTokensResponse() const { return mWaitingSemanticTokensResponse; } +void LSPDocumentClient::requestCodeLens() { + if ( !mServer || !mServer->getCapabilities().codeLensProvider.supported ) + return; + + URI uri = mDoc->getURI(); + LSPClientServer* server = mServer; + LSPDocumentClient* docClient = this; + mServer->documentCodeLens( + uri, [uri, server, docClient]( const auto&, const std::vector& codeLens ) { + if ( server->hasDocument( uri ) ) { + docClient->mCodeLens = codeLens; + } + } ); +} + UISceneNode* LSPDocumentClient::getUISceneNode() { LSPClientServer* server = mServer; if ( !server || !server->getManager() || !server->getManager()->getPluginManager() || diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp index 6f462643f..7ca218a13 100644 --- a/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp @@ -61,6 +61,8 @@ class LSPDocumentClient : public TextDocument::Client { bool isWaitingSemanticTokensResponse() const; + void requestCodeLens(); + protected: LSPClientServer* mServer{ nullptr }; LSPClientServerManager* mServerManager{ nullptr }; @@ -70,6 +72,7 @@ class LSPDocumentClient : public TextDocument::Client { int mVersion{ 0 }; std::string mSemanticeResultId; LSPSemanticTokensDelta mSemanticTokens; + std::vector mCodeLens; bool mRunningSemanticTokens{ false }; bool mWaitingSemanticTokensResponse{ false }; bool mShutdown{ false }; diff --git a/src/tools/ecode/plugins/lsp/lspprotocol.hpp b/src/tools/ecode/plugins/lsp/lspprotocol.hpp index 31daf3e40..232aee72c 100644 --- a/src/tools/ecode/plugins/lsp/lspprotocol.hpp +++ b/src/tools/ecode/plugins/lsp/lspprotocol.hpp @@ -128,6 +128,11 @@ struct LSPWorkspaceFoldersServerCapabilities { bool changeNotifications = false; }; +struct LSPCodeLensOptions { + bool supported = false; + bool resolveProvider = false; +}; + struct LSPServerCapabilities { bool ready = false; std::vector languages; @@ -146,6 +151,7 @@ struct LSPServerCapabilities { bool documentHighlightProvider = false; bool documentFormattingProvider = false; bool documentRangeFormattingProvider = false; + LSPCodeLensOptions codeLensProvider; bool workspaceSymbolProvider = false; LSPDocumentOnTypeFormattingOptions documentOnTypeFormattingProvider; bool renameProvider = false; @@ -217,12 +223,13 @@ struct LSPDiagnosticsCodeAction { struct LSPDiagnostic { TextRange range; - LSPDiagnosticSeverity severity; + LSPDiagnosticSeverity severity{ LSPDiagnosticSeverity::Unknown }; std::string code; std::string source; std::string message; std::vector relatedInformation; std::vector codeActions; + nlohmann::json data; }; struct LSPPublishDiagnosticsParams { @@ -246,6 +253,12 @@ struct LSPCodeAction { bool isPreferred{ false }; }; +struct LSPCodeLens { + TextRange range; + LSPCommand command; + nlohmann::json data; +}; + enum class LSPWorkDoneProgressKind { Begin, Report, End }; struct LSPWorkDoneProgressValue { @@ -544,11 +557,19 @@ struct LSPSignatureHelp { }; struct LSPConverter { - static TextPosition fromJSON( const nlohmann::json& data ) { + static TextPosition parsePosition( const nlohmann::json& data ) { if ( data.contains( "line" ) && data.contains( "character" ) ) return { data["line"].get(), data["character"].get() }; return {}; } + + static TextRange parseRange( const nlohmann::json& range ) { + if ( !range.contains( "start" ) || !range.contains( "end" ) ) + return {}; + auto startpos = parsePosition( range["start"] ); + auto endpos = parsePosition( range["end"] ); + return { startpos, endpos }; + } }; struct LSPShowDocumentParams { diff --git a/src/tools/ecode/plugins/pluginmanager.hpp b/src/tools/ecode/plugins/pluginmanager.hpp index 4fd78cde6..c107dc7d7 100644 --- a/src/tools/ecode/plugins/pluginmanager.hpp +++ b/src/tools/ecode/plugins/pluginmanager.hpp @@ -83,6 +83,7 @@ enum class PluginMessageType { // available GetErrorOrWarning, // Request a component to provide the information of an error or warning in a // particular document location + GetDiagnostics, // Request the diagnostic information from a cursor position Undefined };