diff --git a/bin/assets/plugins/lspclient.json b/bin/assets/plugins/lspclient.json index e1a8ebf06..e8d437d57 100644 --- a/bin/assets/plugins/lspclient.json +++ b/bin/assets/plugins/lspclient.json @@ -69,7 +69,8 @@ "name": "rust-analyzer", "url": "https://rust-analyzer.github.io", "command": "rust-analyzer", - "file_patterns": ["%.rs$"] + "file_patterns": ["%.rs$"], + "rootIndicationFileNames": ["Cargo.lock", "Cargo.toml"] }, { "language": "lua", diff --git a/include/eepp/ui/doc/syntaxdefinition.hpp b/include/eepp/ui/doc/syntaxdefinition.hpp index 4e0208504..2da26f47d 100644 --- a/include/eepp/ui/doc/syntaxdefinition.hpp +++ b/include/eepp/ui/doc/syntaxdefinition.hpp @@ -30,8 +30,8 @@ class EE_API SyntaxDefinition { const std::vector& patterns, const std::unordered_map& symbols = std::unordered_map(), - const std::string& comment = "", - const std::vector headers = {} ); + const std::string& comment = "", const std::vector headers = {}, + const std::string& lspName = "" ); const std::string& getLanguageName() const; @@ -72,6 +72,8 @@ class EE_API SyntaxDefinition { void clearSymbols(); + const std::string& getLSPName() const; + protected: std::string mLanguageName; String::HashType mLanguageId; @@ -80,6 +82,7 @@ class EE_API SyntaxDefinition { std::unordered_map mSymbols; std::string mComment; std::vector mHeaders; + std::string mLSPName; }; }}} // namespace EE::UI::Doc diff --git a/projects/linux/ee.files b/projects/linux/ee.files index c88fac371..c7b854809 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -1169,7 +1169,6 @@ ../../src/tools/ecode/plugins/linter/linterplugin.hpp ../../src/tools/ecode/plugins/lsp/lspclientplugin.cpp ../../src/tools/ecode/plugins/lsp/lspclientplugin.hpp -../../src/tools/ecode/plugins/lsp/lspclientprotocol.hpp ../../src/tools/ecode/plugins/lsp/lspclientserver.cpp ../../src/tools/ecode/plugins/lsp/lspclientserver.hpp ../../src/tools/ecode/plugins/lsp/lspclientservermanager.cpp @@ -1177,6 +1176,7 @@ ../../src/tools/ecode/plugins/lsp/lspdefinition.hpp ../../src/tools/ecode/plugins/lsp/lspdocumentclient.cpp ../../src/tools/ecode/plugins/lsp/lspdocumentclient.hpp +../../src/tools/ecode/plugins/lsp/lspprotocol.hpp ../../src/tools/ecode/plugins/pluginmanager.cpp ../../src/tools/ecode/plugins/pluginmanager.hpp ../../src/tools/ecode/projectdirectorytree.cpp diff --git a/src/eepp/ui/doc/syntaxdefinition.cpp b/src/eepp/ui/doc/syntaxdefinition.cpp index 608c2bb74..cea8dc94c 100644 --- a/src/eepp/ui/doc/syntaxdefinition.cpp +++ b/src/eepp/ui/doc/syntaxdefinition.cpp @@ -11,14 +11,16 @@ SyntaxDefinition::SyntaxDefinition( const std::string& languageName, const std::vector& patterns, const std::unordered_map& symbols, const std::string& comment, - const std::vector headers ) : + const std::vector headers, + const std::string& lspName ) : mLanguageName( languageName ), mLanguageId( String::hash( String::toLower( languageName ) ) ), mFiles( files ), mPatterns( patterns ), mSymbols( symbols ), mComment( comment ), - mHeaders( headers ) {} + mHeaders( headers ), + mLSPName( lspName.empty() ? String::toLower( mLanguageName ) : lspName ) {} const std::vector& SyntaxDefinition::getFiles() const { return mFiles; @@ -109,6 +111,10 @@ void SyntaxDefinition::clearSymbols() { mSymbols.clear(); } +const std::string& SyntaxDefinition::getLSPName() const { + return mLSPName; +} + const std::string& SyntaxDefinition::getLanguageName() const { return mLanguageName; } diff --git a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp index 9aab6afca..ac681dded 100644 --- a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp +++ b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp @@ -656,7 +656,7 @@ void SyntaxDefinitionManager::addPython() { "#", { "^#!.*[ /]python", "^#!.*[ /]python3" } } ); } - void SyntaxDefinitionManager::addBash() { +void SyntaxDefinitionManager::addBash() { add( { "Bash", { "%.sh$", "%.bash$", "%.bashrc$", "%.bash_profile$" }, { @@ -682,7 +682,8 @@ void SyntaxDefinitionManager::addPython() { { "false", "literal" }, }, "#", - { "^#!.*[ /]bash", "^#!.*[ /]sh" } } ); + { "^#!.*[ /]bash", "^#!.*[ /]sh" }, + "shellscript" } ); } void SyntaxDefinitionManager::addCPP() { @@ -833,7 +834,9 @@ void SyntaxDefinitionManager::addCPP() { { "Rectf", "keyword2" }, { "NULL", "literal" }, }, - "//" } ); + "//", + {}, + "cpp" } ); } void SyntaxDefinitionManager::addPHP() { @@ -1352,7 +1355,8 @@ void SyntaxDefinitionManager::addIni() { { { "[a-z]+" }, "symbol" } }, { { "true", "literal" }, { "false", "literal" } }, "#", - { "^%[.-%]%f[^\n]" } } ); + { "^%[.-%]%f[^\n]" }, + "ini" } ); } void SyntaxDefinitionManager::addMakefile() { @@ -1424,7 +1428,9 @@ void SyntaxDefinitionManager::addCSharp() { { "record", "keyword" }, { "remove", "keyword" }, { "partial", "keyword" }, { "dynamic", "keyword" }, { "value", "keyword" }, { "global", "keyword" }, { "when", "keyword" } }, - "//" } ); + "//", + {}, + "csharp" } ); } void SyntaxDefinitionManager::addGo() { @@ -2170,7 +2176,9 @@ void SyntaxDefinitionManager::addBatchScript() { { { ":eof" }, "keyword" }, }, prepareBatchSymbols( batchSymTable ), - "rem" } ); + "rem", + {}, + "bat" } ); } void SyntaxDefinitionManager::addDiff() { @@ -2184,7 +2192,11 @@ void SyntaxDefinitionManager::addDiff() { { { "^@@.-\n" }, "number" }, { { "^%+.-\n" }, "function" }, { { "^%-.-\n" }, "keyword2" }, - } } ); + }, + {}, + "", + {}, + "diff" } ); } void SyntaxDefinitionManager::addJava() { @@ -2992,7 +3004,9 @@ void SyntaxDefinitionManager::addContainerfile() { { "ENTRYPOINT", "function" }, { "CMD", "function" }, }, - "#" } ); + "#", + {}, + "dockerfile" } ); } void SyntaxDefinitionManager::addOdin() { diff --git a/src/tools/ecode/plugins/linter/linterplugin.cpp b/src/tools/ecode/plugins/linter/linterplugin.cpp index b15e62627..8be1ca580 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.cpp +++ b/src/tools/ecode/plugins/linter/linterplugin.cpp @@ -89,6 +89,27 @@ void LinterPlugin::loadLinterConfig( const std::string& path ) { auto& config = j["config"]; if ( config.contains( "delay_time" ) ) setDelayTime( Time::fromString( config["delay_time"].get() ) ); + if ( config.contains( "enable_lsp_diagnostics" ) && + config["enable_lsp_diagnostics"].is_boolean() ) + setEnableLSPDiagnostics( config["enable_lsp_diagnostics"].get() ); + if ( config.contains( "disable_lsp_languages" ) && + config["disable_lsp_languages"].is_array() ) { + const auto& langs = config["disable_lsp_languages"]; + try { + mLSPLanguagesDisabled.clear(); + for ( const auto& lang : langs ) { + if ( lang.is_string() ) { + std::string lg = lang.get(); + if ( !lg.empty() ) + mLSPLanguagesDisabled.insert( lg ); + } + } + } catch ( const json::exception& e ) { + Log::debug( + "LinterPlugin::loadLinterConfig: Error parsing disable_lsp_languages: %s", + e.what() ); + } + } } if ( !j.contains( "linters" ) ) @@ -166,7 +187,107 @@ void LinterPlugin::loadLinterConfig( const std::string& path ) { } } +LinterType getLinterTypeFromSeverity( const LSPDiagnosticSeverity& severity ) { + switch ( severity ) { + case LSPDiagnosticSeverity::Error: + return LinterType::Error; + case LSPDiagnosticSeverity::Warning: + return LinterType::Warning; + default: + return LinterType::Notice; + } +} + +void LinterPlugin::eraseMatchesFromOrigin( TextDocument* doc, const MatchOrigin& origin ) { + Lock matchesLock( mMatchesMutex ); + auto& docMatches = mMatches[doc]; + + std::vector emptyMatchLines; + for ( auto& matchLine : docMatches ) { + bool found; + do { + found = false; + auto it = matchLine.second.begin(); + for ( ; it != matchLine.second.end(); ++it ) { + if ( it->origin == origin ) { + found = true; + break; + } + } + if ( found ) { + matchLine.second.erase( it ); + if ( matchLine.second.empty() ) + emptyMatchLines.push_back( matchLine.first ); + } + } while ( found ); + } + + for ( const auto& line : emptyMatchLines ) + docMatches.erase( line ); +} + +void LinterPlugin::insertMatches( TextDocument* doc, + std::map>& matches ) { + Lock matchesLock( mMatchesMutex ); + auto& docMatches = mMatches[doc]; + for ( auto& match : matches ) { + std::vector& vec = docMatches[match.first]; + vec.insert( vec.end(), match.second.begin(), match.second.end() ); + } +} + +void LinterPlugin::setMatches( TextDocument* doc, const MatchOrigin& origin, + std::map>& matches ) { + if ( !mEnableLSPDiagnostics ) { + Lock matchesLock( mMatchesMutex ); + mMatches[doc] = std::move( matches ); + } else { + eraseMatchesFromOrigin( doc, origin ); + insertMatches( doc, matches ); + } + + invalidateEditors( doc ); +} + +void LinterPlugin::processNotification( const PluginManager::Notification& notification ) { + if ( !mEnableLSPDiagnostics || + notification.type != PluginManager::NotificationType::PublishDiagnostics || + notification.format != PluginManager::NotificationFormat::Diagnostics ) + return; + const auto& diags = notification.asDiagnostics(); + TextDocument* doc = getDocumentFromURI( diags.uri ); + if ( doc == nullptr ) + return; + if ( mLSPLanguagesDisabled.find( String::toLower( + doc->getSyntaxDefinition().getLSPName() ) ) != mLSPLanguagesDisabled.end() ) + return; + + std::map> matches; + + for ( const auto& diag : diags.diagnostics ) { + LinterMatch match; + match.range = diag.range; + match.text = diag.message; + match.type = getLinterTypeFromSeverity( diag.severity ); + match.lineCache = doc->line( match.range.start().line() ).getHash(); + match.origin = MatchOrigin::Diagnostics; + matches[match.range.start().line()].emplace_back( std::move( match ) ); + } + + setMatches( doc, MatchOrigin::Diagnostics, matches ); +} + +TextDocument* LinterPlugin::getDocumentFromURI( const URI& uri ) { + for ( TextDocument* doc : mDocs ) { + if ( doc->getURI() == uri ) + return doc; + } + return nullptr; +} + void LinterPlugin::load( const PluginManager* pluginManager ) { + pluginManager->subscribeNotifications( + this, [&]( const auto& notification ) { processNotification( notification ); } ); std::vector paths; std::string path( pluginManager->getResourcesPath() + "plugins/linters.json" ); if ( FileSystem::fileExists( path ) ) @@ -274,6 +395,14 @@ void LinterPlugin::setDelayTime( const Time& delayTime ) { mDelayTime = delayTime; } +bool LinterPlugin::getEnableLSPDiagnostics() const { + return mEnableLSPDiagnostics; +} + +void LinterPlugin::setEnableLSPDiagnostics( bool enableLSPDiagnostics ) { + mEnableLSPDiagnostics = enableLSPDiagnostics; +} + void LinterPlugin::lintDoc( std::shared_ptr doc ) { ScopedOp op( [&]() { @@ -395,13 +524,35 @@ void LinterPlugin::runLinter( std::shared_ptr doc, const Linter& l if ( linter.columnsStartAtZero ) col++; } - linterMatch.pos = { line - 1, col > 0 ? col - 1 : 0 }; - linterMatch.lineCache = doc->line( line - 1 ).getHash(); + linterMatch.range.setStart( { line - 1, col > 0 ? col - 1 : 0 } ); + + const String& text = doc->line( linterMatch.range.start().line() ).getText(); + size_t minCol = + text.find_first_not_of( " \t\f\v\n\r", linterMatch.range.start().column() ); + if ( minCol == String::InvalidPos ) + minCol = linterMatch.range.start().column(); + minCol = std::max( (Int64)minCol, linterMatch.range.start().column() ); + if ( minCol >= text.size() ) + minCol = linterMatch.range.start().column(); + if ( minCol >= text.size() ) + minCol = text.size() - 1; + linterMatch.range.setStart( + { linterMatch.range.start().line(), (Int64)minCol } ); + TextPosition endPos; + endPos = ( minCol < text.size() - 1 ) + ? doc->nextWordBoundary( + { linterMatch.range.start().line(), (Int64)minCol } ) + : doc->previousWordBoundary( + { linterMatch.range.start().line(), (Int64)minCol } ); + + linterMatch.range.setEnd( endPos ); + linterMatch.range = linterMatch.range.normalized(); + linterMatch.lineCache = doc->line( linterMatch.range.start().line() ).getHash(); bool skip = false; if ( linter.deduplicate && matches.find( line - 1 ) != matches.end() ) { for ( auto& match : matches[line - 1] ) { - if ( match.pos == linterMatch.pos ) { + if ( match.range == linterMatch.range ) { match.text += "\n" + linterMatch.text; skip = true; break; @@ -433,12 +584,7 @@ void LinterPlugin::runLinter( std::shared_ptr doc, const Linter& l } } - { - Lock matchesLock( mMatchesMutex ); - mMatches[doc.get()] = std::move( matches ); - } - - invalidateEditors( doc.get() ); + setMatches( doc.get(), MatchOrigin::Linter, matches ); Log::info( "LinterPlugin::runLinter for %s took %.2fms. Found: %d matches. Errors: %d, " "Warnings: %d, Notices: %d.", @@ -481,33 +627,9 @@ void LinterPlugin::drawAfterLineText( UICodeEditor* editor, const Int64& index, line.setStyleConfig( editor->getFontStyleConfig() ); line.setColor( editor->getColorScheme().getEditorSyntaxStyle( getMatchString( match.type ) ).color ); - const String& text = doc->line( index ).getText(); - size_t minCol = text.find_first_not_of( " \t\f\v\n\r", match.pos.column() ); - if ( minCol == String::InvalidPos ) - minCol = match.pos.column(); - minCol = std::max( (Int64)minCol, match.pos.column() ); - if ( minCol >= text.size() ) - minCol = match.pos.column(); - if ( minCol >= text.size() ) - minCol = text.size() - 1; - - Int64 strSize = 0; - TextPosition endPos; - Vector2f pos; - - if ( minCol < text.size() - 1 ) { - endPos = doc->nextWordBoundary( { match.pos.line(), (Int64)minCol } ); - strSize = eemax( (Int64)0, static_cast( endPos.column() - minCol ) ); - pos = { position.x + editor->getXOffsetCol( { match.pos.line(), (Int64)minCol } ), - position.y }; - } else { - endPos = doc->previousWordBoundary( { match.pos.line(), (Int64)minCol } ); - strSize = eemax( (Int64)0, static_cast( minCol - endPos.column() ) ); - pos = { position.x + - editor->getXOffsetCol( { match.pos.line(), (Int64)endPos.column() } ), - position.y }; - } + Int64 strSize = match.range.end().column() - match.range.start().column(); + Vector2f pos = { position.x + editor->getXOffsetCol( match.range.start() ), position.y }; if ( strSize == 0 ) { strSize = 1; pos = { position.x, position.y }; diff --git a/src/tools/ecode/plugins/linter/linterplugin.hpp b/src/tools/ecode/plugins/linter/linterplugin.hpp index bf8aa332a..feca37e36 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.hpp +++ b/src/tools/ecode/plugins/linter/linterplugin.hpp @@ -16,6 +16,8 @@ namespace ecode { enum class LinterType { Notice, Warning, Error }; +enum class MatchOrigin { Linter, Diagnostics }; + struct Linter { std::vector files; std::vector warningPattern; @@ -36,10 +38,11 @@ struct Linter { struct LinterMatch { std::string text; - TextPosition pos; - String::HashType lineCache; - std::map box; + TextRange range; LinterType type{ LinterType::Error }; + String::HashType lineCache; + MatchOrigin origin{ MatchOrigin::Linter }; + std::map box; }; class LinterPlugin : public UICodeEditorPlugin { @@ -88,6 +91,10 @@ class LinterPlugin : public UICodeEditorPlugin { void setDelayTime( const Time& delayTime ); + bool getEnableLSPDiagnostics() const; + + void setEnableLSPDiagnostics( bool enableLSPDiagnostics ); + protected: std::shared_ptr mPool; std::vector mLinters; @@ -107,6 +114,8 @@ class LinterPlugin : public UICodeEditorPlugin { bool mReady{ false }; bool mShuttingDown{ false }; bool mHoveringMatch{ false }; + bool mEnableLSPDiagnostics{ true }; + std::set mLSPLanguagesDisabled; LinterPlugin( const PluginManager* pluginManager ); @@ -130,6 +139,17 @@ class LinterPlugin : public UICodeEditorPlugin { void loadLinterConfig( const std::string& path ); size_t linterFilePatternPosition( const std::vector& patterns ); + + void processNotification( const PluginManager::Notification& notification ); + + TextDocument* getDocumentFromURI( const URI& uri ); + + void eraseMatchesFromOrigin( TextDocument* doc, const MatchOrigin& origin ); + + void insertMatches( TextDocument* doc, std::map>& matches ); + + void setMatches( TextDocument* doc, const MatchOrigin& origin, + std::map>& matches ); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index 6ee22e14d..496cef7f1 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -25,7 +25,7 @@ LSPClientPlugin::LSPClientPlugin( const PluginManager* pluginManager ) : LSPClientPlugin::~LSPClientPlugin() { mClosing = true; Lock l( mDocMutex ); - for ( const auto &editor : mEditors ) { + for ( const auto& editor : mEditors ) { for ( auto& kb : mKeyBindings ) { editor.first->getKeyBindings().removeCommandKeybind( kb.first ); if ( editor.first->hasDocument() ) @@ -47,11 +47,10 @@ void LSPClientPlugin::update( UICodeEditor* ) { mClientManager.updateDirty(); } -void LSPClientPlugin::processNotification( PluginManager::Notification noti, - const nlohmann::json& obj ) { - switch ( noti ) { +void LSPClientPlugin::processNotification( const PluginManager::Notification& notification ) { + switch ( notification.type ) { case PluginManager::WorkspaceFolderChanged: { - mClientManager.didChangeWorkspaceFolders( obj["folder"] ); + mClientManager.didChangeWorkspaceFolders( notification.asJSON()["folder"] ); break; } default: @@ -61,7 +60,7 @@ void LSPClientPlugin::processNotification( PluginManager::Notification noti, void LSPClientPlugin::load( const PluginManager* pluginManager ) { pluginManager->subscribeNotifications( - this, [&]( auto noti, auto obj ) { processNotification( noti, obj ); } ); + this, [&]( const auto& notification ) { processNotification( notification ); } ); std::vector paths; std::string path( pluginManager->getResourcesPath() + "plugins/lspclient.json" ); if ( FileSystem::fileExists( path ) ) @@ -137,6 +136,28 @@ void LSPClientPlugin::loadLSPConfig( std::vector& lsps, const std auto& servers = j["servers"]; for ( auto& obj : servers ) { + // Allow disabling an LSP by redeclaring it in the user configuration file. + if ( obj.contains( "name" ) && obj.contains( "disabled" ) && + obj.at( "disabled" ).is_boolean() ) { + for ( auto& lsp : lsps ) { + if ( lsp.name == obj["name"] ) { + lsp.disabled = obj["disabled"].get(); + break; + } + } + } + + // Allow setting user command paramaters for an already declared LSP + if ( obj.contains( "name" ) && obj.contains( "command_parameters" ) && + obj.at( "command_parameters" ).is_string() ) { + for ( auto& lsp : lsps ) { + if ( lsp.name == obj["name"] ) { + lsp.commandParameters = obj.value( "command_parameters", "" ); + break; + } + } + } + if ( !obj.contains( "language" ) || !obj.contains( "file_patterns" ) ) { Log::warning( "LSP server without language or file_patterns, ignored..." ); continue; @@ -173,8 +194,8 @@ void LSPClientPlugin::loadLSPConfig( std::vector& lsps, const std lsp.name = obj["name"]; } - if ( obj.contains( "url" ) ) - lsp.url = obj["url"]; + lsp.url = obj.value( "url", "" ); + lsp.commandParameters = obj.value( "command_parameters", lsp.commandParameters ); auto fp = obj["file_patterns"]; diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp index 5db8ca8e0..489843c06 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp @@ -92,7 +92,7 @@ class LSPClientPlugin : public UICodeEditorPlugin { size_t lspFilePatternPosition( const std::vector& lsps, const std::vector& patterns ); - void processNotification( PluginManager::Notification, const nlohmann::json& ); + void processNotification( const PluginManager::Notification& notification ); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.cpp b/src/tools/ecode/plugins/lsp/lspclientserver.cpp index b978d7c25..b0c5d1ef6 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.cpp @@ -1,4 +1,5 @@ #include "lspclientserver.hpp" +#include "lspclientplugin.hpp" #include "lspclientservermanager.hpp" #include #include @@ -27,7 +28,7 @@ static const char* MEMBER_RESULT = "result"; static const char* MEMBER_START = "start"; static const char* MEMBER_END = "end"; static const char* MEMBER_POSITION = "position"; -// static const char* MEMBER_POSITIONS = "positions"; +static const char* MEMBER_POSITIONS = "positions"; static const char* MEMBER_LOCATION = "location"; static const char* MEMBER_RANGE = "range"; static const char* MEMBER_LINE = "line"; @@ -44,8 +45,8 @@ static const char* MEMBER_DIAGNOSTICS = "diagnostics"; static const char* MEMBER_TARGET_URI = "targetUri"; static const char* MEMBER_TARGET_RANGE = "targetRange"; static const char* MEMBER_TARGET_SELECTION_RANGE = "targetSelectionRange"; -// static const char* MEMBER_PREVIOUS_RESULT_ID = "previousResultId"; -// static const char* MEMBER_QUERY = "query"; +static const char* MEMBER_PREVIOUS_RESULT_ID = "previousResultId"; +static const char* MEMBER_QUERY = "query"; static json newRequest( const std::string& method, const json& params = json{} ) { json j; @@ -175,6 +176,20 @@ static json textDocumentPositionParams( const URI& document, TextPosition pos ) return params; } +static json toJson( const std::vector& positions ) { + json result; + for ( const auto& position : positions ) + result.push_back( toJson( position ) ); + return result; +} + +static json textDocumentPositionsParams( const URI& document, + const std::vector& positions ) { + auto params = textDocumentParams( document ); + params[MEMBER_POSITIONS] = toJson( positions ); + return params; +} + static void fromJson( std::vector& trigger, const json& json ) { if ( !json.empty() ) { const auto triggersArray = json; @@ -358,6 +373,37 @@ static std::vector parseDocumentSymbols( const json& resul return ret; } +static std::vector parseWorkspaceSymbols( const json& res ) { + std::vector symbols; + symbols.reserve( res.size() ); + + std::transform( + res.cbegin(), res.cend(), std::back_inserter( symbols ), []( const json& symbol ) { + LSPSymbolInformation symInfo; + + const auto location = symbol.at( MEMBER_LOCATION ); + const auto mrange = symbol.contains( MEMBER_RANGE ) ? symbol.at( MEMBER_RANGE ) + : location.at( MEMBER_RANGE ); + + auto containerName = symbol.value( "containerName", "" ); + if ( !containerName.empty() ) + containerName.append( "::" ); + symInfo.name = containerName + symbol.value( "name", "" ); + symInfo.kind = (LSPSymbolKind)symbol.value( MEMBER_KIND, 1 ); + symInfo.range = parseRange( mrange ); + symInfo.url = URI( location.value( MEMBER_URI, "" ) ); + symInfo.score = symbol.value( "score", 0.0 ); + return symInfo; + } ); + + std::sort( symbols.begin(), symbols.end(), + []( const LSPSymbolInformation& l, const LSPSymbolInformation& r ) { + return l.score > r.score; + } ); + + return symbols; +} + static LSPResponseError parseResponseError( const json& v ) { LSPResponseError ret; if ( v.is_object() ) { @@ -611,6 +657,32 @@ static std::vector parseDocumentCompletion( const json& resul return ret; } +static std::shared_ptr parseSelectionRange( const json& selectionRange ) { + auto current = std::make_shared( LSPSelectionRange{} ); + std::shared_ptr ret = current; + json selRange = std::move( selectionRange ); + + while ( selRange.is_object() ) { + current->range = parseRange( selRange[MEMBER_RANGE] ); + if ( !selRange["parent"].is_object() ) { + current->parent = nullptr; + break; + } + selRange = selRange["parent"]; + current->parent = std::make_shared( LSPSelectionRange{} ); + current = current->parent; + } + return ret; +} + +static std::vector> +parseSelectionRanges( const json& selectionRanges ) { + std::vector> ret; + for ( const auto& selectionRange : selectionRanges ) + ret.push_back( parseSelectionRange( selectionRange ) ); + return ret; +} + void LSPClientServer::initialize() { json codeAction{ { "codeActionLiteralSupport", json{ { "codeActionKind", json{ { "valueSet", json::array() } } } } } }; @@ -735,7 +807,7 @@ LSPClientServer::RequestHandle LSPClientServer::cancel( int reqid ) { LSPClientServer::RequestHandle LSPClientServer::write( const json& msg, const JsonReplyHandler& h, const JsonReplyHandler& eh, const int id ) { RequestHandle ret; - ret.mServer = this; + ret.server = this; if ( !mProcess.isAlive() ) return ret; @@ -746,7 +818,7 @@ LSPClientServer::RequestHandle LSPClientServer::write( const json& msg, const Js // notification == no handler if ( h ) { ob[MEMBER_ID] = ++mLastMsgId; - ret.mId = mLastMsgId; + ret.id = mLastMsgId; Lock l( mHandlersMutex ); mHandlers[mLastMsgId] = { h, eh }; } else if ( id ) { @@ -907,6 +979,21 @@ LSPClientServer::documentSymbols( const URI& document, } ); } +LSPClientServer::RequestHandle LSPClientServer::workspaceSymbol( const std::string& querySymbol, + const JsonReplyHandler& h ) { + auto params = json{ { MEMBER_QUERY, querySymbol } }; + return send( newRequest( "workspace/symbol", params ), h ); +} + +LSPClientServer::RequestHandle +LSPClientServer::workspaceSymbol( const std::string& querySymbol, + const SymbolInformationHandler& h ) { + return workspaceSymbol( querySymbol, [h]( const json& json ) { + if ( h ) + h( parseWorkspaceSymbols( json ) ); + } ); +} + void fromJson( LSPWorkDoneProgressValue& value, const json& json ) { if ( !json.empty() ) { auto ob = json; @@ -942,14 +1029,18 @@ static json newError( const LSPErrorCode& code, const std::string& msg ) { } void LSPClientServer::publishDiagnostics( const json& msg ) { - // should emmit event somewhere - auto res = parsePublishDiagnostics( msg[MEMBER_PARAMS] ); + LSPPublishDiagnosticsParams res = parsePublishDiagnostics( msg[MEMBER_PARAMS] ); + if ( mManager && mManager->getPluginManager() && mManager->getPlugin() ) { + mManager->getPluginManager()->pushNotification( mManager->getPlugin(), + PluginManager::PublishDiagnostics, + PluginManager::Diagnostics, &res ); + } Log::debug( "LSPClientServer::publishDiagnostics: %s - returned %zu items", res.uri.toString().c_str(), res.diagnostics.size() ); } void LSPClientServer::workDoneProgress( const LSPWorkDoneProgressParams& workDoneParams ) { - // should emmit event somewhere + // should emit event somewhere Log::debug( "LSPClientServer::workDoneProgress: %s", workDoneParams.token.is_string() ? workDoneParams.token.get().c_str() @@ -1187,11 +1278,6 @@ LSPClientServer::RequestHandle LSPClientServer::documentHover( const URI& docume } ); } -LSPClientServer::RequestHandle LSPClientServer::documentHover( TextDocument* doc, - const HoverHandler& h ) { - return documentHover( doc->getURI(), doc->getSelection().start(), h ); -} - LSPClientServer::RequestHandle LSPClientServer::documentCompletion( const URI& document, const TextPosition& pos, const JsonReplyHandler& h ) { @@ -1208,4 +1294,38 @@ LSPClientServer::RequestHandle LSPClientServer::documentCompletion( const URI& d } ); } +LSPClientServer::RequestHandle +LSPClientServer::selectionRange( const URI& document, const std::vector& positions, + const JsonReplyHandler& h ) { + auto params = textDocumentPositionsParams( document, positions ); + return send( newRequest( "textDocument/selectionRange", params ), h ); +} + +LSPClientServer::RequestHandle +LSPClientServer::selectionRange( const URI& document, const std::vector& positions, + const SelectionRangeHandler& h ) { + return selectionRange( document, positions, [h]( const json& json ) { + if ( h ) + h( parseSelectionRanges( json ) ); + } ); +} + +LSPClientServer::RequestHandle +LSPClientServer::documentSemanticTokensFull( const URI& document, bool delta, + const std::string& requestId, const TextRange& range, + const JsonReplyHandler& h ) { + auto params = textDocumentParams( document ); + // Delta + if ( delta && !requestId.empty() ) { + params[MEMBER_PREVIOUS_RESULT_ID] = requestId; + return send( newRequest( "textDocument/semanticTokens/full/delta", params ), h ); + } + // Range + if ( range.isValid() ) { + params[MEMBER_RANGE] = toJson( range ); + return send( newRequest( "textDocument/semanticTokens/range", params ), h ); + } + + return send( newRequest( "textDocument/semanticTokens/full", params ), h ); +} } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.hpp b/src/tools/ecode/plugins/lsp/lspclientserver.hpp index 19f4c6623..069afce29 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.hpp @@ -1,7 +1,7 @@ #ifndef ECODE_LSPCLIENTSERVER_HPP #define ECODE_LSPCLIENTSERVER_HPP -#include "lspclientprotocol.hpp" +#include "lspprotocol.hpp" #include "lspdefinition.hpp" #include "lspdocumentclient.hpp" #include @@ -31,16 +31,19 @@ class LSPClientServer { using CodeActionHandler = ReplyHandler>; using HoverHandler = ReplyHandler; using CompletionHandler = ReplyHandler>; + using SymbolInformationHandler = ReplyHandler>; + using SelectionRangeHandler = ReplyHandler>>; class RequestHandle { + private: friend class LSPClientServer; - LSPClientServer* mServer; - int mId = 0; + LSPClientServer* server; + int id = 0; public: RequestHandle& cancel() { - if ( mServer ) - mServer->cancel( mId ); + if ( server ) + server->cancel( id ); return *this; } }; @@ -60,51 +63,45 @@ class LSPClientServer { const std::shared_ptr& getThreadPool() const; - LSPClientServer::RequestHandle cancel( int id ); + RequestHandle cancel( int id ); - LSPClientServer::RequestHandle send( const json& msg, const JsonReplyHandler& h = nullptr, - const JsonReplyHandler& eh = nullptr ); + RequestHandle send( const json& msg, const JsonReplyHandler& h = nullptr, + const JsonReplyHandler& eh = nullptr ); const LSPDefinition& getDefinition() const { return mLSP; } - LSPClientServer::RequestHandle documentSymbols( const URI& document, const JsonReplyHandler& h, - const JsonReplyHandler& eh ); + RequestHandle documentSymbols( const URI& document, const JsonReplyHandler& h, + const JsonReplyHandler& eh ); - LSPClientServer::RequestHandle - documentSymbols( const URI& document, const ReplyHandler>& h, - const ReplyHandler& eh ); + RequestHandle documentSymbols( const URI& document, + const ReplyHandler>& h, + const ReplyHandler& eh ); - LSPClientServer::RequestHandle didOpen( const URI& document, const std::string& text, - int version ); + RequestHandle didOpen( const URI& document, const std::string& text, int version ); - LSPClientServer::RequestHandle didOpen( TextDocument* doc, int version ); + RequestHandle didOpen( TextDocument* doc, int version ); - LSPClientServer::RequestHandle didSave( const URI& document, const std::string& text ); + RequestHandle didSave( const URI& document, const std::string& text ); - LSPClientServer::RequestHandle didSave( TextDocument* doc ); + RequestHandle didSave( TextDocument* doc ); - LSPClientServer::RequestHandle didClose( const URI& document ); + RequestHandle didClose( const URI& document ); - LSPClientServer::RequestHandle didClose( TextDocument* document ); + RequestHandle didClose( TextDocument* document ); - LSPClientServer::RequestHandle - didChange( const URI& document, int version, const std::string& text, - const std::vector& change = {} ); + RequestHandle didChange( const URI& document, int version, const std::string& text, + const std::vector& change = {} ); - LSPClientServer::RequestHandle - didChange( TextDocument* doc, const std::vector& change = {} ); + RequestHandle didChange( TextDocument* doc, + const std::vector& change = {} ); - LSPClientServer::RequestHandle documentDefinition( const URI& document, - const TextPosition& pos ); + RequestHandle documentDefinition( const URI& document, const TextPosition& pos ); - LSPClientServer::RequestHandle documentDeclaration( const URI& document, - const TextPosition& pos ); + RequestHandle documentDeclaration( const URI& document, const TextPosition& pos ); - LSPClientServer::RequestHandle documentTypeDefinition( const URI& document, - const TextPosition& pos ); + RequestHandle documentTypeDefinition( const URI& document, const TextPosition& pos ); - LSPClientServer::RequestHandle documentImplementation( const URI& document, - const TextPosition& pos ); + RequestHandle documentImplementation( const URI& document, const TextPosition& pos ); void updateDirty(); @@ -112,42 +109,54 @@ class LSPClientServer { bool hasDocuments() const; - LSPClientServer::RequestHandle - didChangeWorkspaceFolders( const std::vector& added, - const std::vector& removed ); + RequestHandle didChangeWorkspaceFolders( const std::vector& added, + const std::vector& removed ); void publishDiagnostics( const json& msg ); void workDoneProgress( const LSPWorkDoneProgressParams& workDoneParams ); - LSPClientServer::RequestHandle getAndGoToLocation( const URI& document, const TextPosition& pos, - const std::string& search ); + RequestHandle getAndGoToLocation( const URI& document, const TextPosition& pos, + const std::string& search ); - LSPClientServer::RequestHandle switchSourceHeader( const URI& document ); + RequestHandle switchSourceHeader( const URI& document ); - LSPClientServer::RequestHandle documentCodeAction( const URI& document, const TextRange& range, - const std::vector& kinds, - std::vector diagnostics, - const JsonReplyHandler& h ); + RequestHandle documentCodeAction( const URI& document, const TextRange& range, + const std::vector& kinds, + std::vector diagnostics, + const JsonReplyHandler& h ); - LSPClientServer::RequestHandle documentCodeAction( const URI& document, const TextRange& range, - const std::vector& kinds, - std::vector diagnostics, - const CodeActionHandler& h ); + RequestHandle documentCodeAction( const URI& document, const TextRange& range, + const std::vector& kinds, + std::vector diagnostics, + const CodeActionHandler& h ); - LSPClientServer::RequestHandle documentHover( const URI& document, const TextPosition& pos, - const JsonReplyHandler& h ); + RequestHandle documentHover( const URI& document, const TextPosition& pos, + const JsonReplyHandler& h ); - LSPClientServer::RequestHandle documentHover( const URI& document, const TextPosition& pos, - const HoverHandler& h ); + RequestHandle documentHover( const URI& document, const TextPosition& pos, + const HoverHandler& h ); - LSPClientServer::RequestHandle documentHover( TextDocument* doc, const HoverHandler& h ); + RequestHandle documentCompletion( const URI& document, const TextPosition& pos, + const JsonReplyHandler& h ); - LSPClientServer::RequestHandle documentCompletion( const URI& document, const TextPosition& pos, - const JsonReplyHandler& h ); + RequestHandle documentCompletion( const URI& document, const TextPosition& pos, + const CompletionHandler& h ); - LSPClientServer::RequestHandle documentCompletion( const URI& document, const TextPosition& pos, - const CompletionHandler& h ); + RequestHandle workspaceSymbol( const std::string& querySymbol, const JsonReplyHandler& h ); + + RequestHandle workspaceSymbol( const std::string& querySymbol, + const SymbolInformationHandler& h ); + + RequestHandle selectionRange( const URI& document, const std::vector& positions, + const JsonReplyHandler& h ); + + RequestHandle selectionRange( const URI& document, const std::vector& positions, + const SelectionRangeHandler& h ); + + RequestHandle documentSemanticTokensFull( const URI& document, bool delta, + const std::string& requestId, const TextRange& range, + const JsonReplyHandler& h ); protected: LSPClientServerManager* mManager{ nullptr }; @@ -177,8 +186,8 @@ class LSPClientServer { void readStdErr( const char* bytes, size_t n ); - LSPClientServer::RequestHandle write( const json& msg, const JsonReplyHandler& h = nullptr, - const JsonReplyHandler& eh = nullptr, const int id = 0 ); + RequestHandle write( const json& msg, const JsonReplyHandler& h = nullptr, + const JsonReplyHandler& eh = nullptr, const int id = 0 ); void initialize(); diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp index 023e5b678..849754748 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp @@ -11,6 +11,7 @@ LSPClientServerManager::LSPClientServerManager() {} void LSPClientServerManager::load( LSPClientPlugin* plugin, const PluginManager* pluginManager, std::vector&& lsps ) { mPlugin = plugin; + mPluginManager = pluginManager; mThreadPool = pluginManager->getThreadPool(); mLSPs = lsps; } @@ -168,6 +169,14 @@ void LSPClientServerManager::getAndGoToLocation( const std::shared_ptrgetAndGoToLocation( doc->getURI(), doc->getSelection().start(), search ); } +const PluginManager* LSPClientServerManager::getPluginManager() const { + return mPluginManager; +} + +LSPClientPlugin* LSPClientServerManager::getPlugin() const { + return mPlugin; +} + void LSPClientServerManager::didChangeWorkspaceFolders( const std::string& folder ) { mLSPWorkspaceFolder = { "file://" + folder, FileSystem::fileNameFromPath( folder ) }; Lock l( mClientsMutex ); diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp index a1511ed4b..d998be635 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp @@ -47,9 +47,13 @@ class LSPClientServerManager { void getAndGoToLocation( const std::shared_ptr& doc, const std::string& search ); + const PluginManager* getPluginManager() const; + + LSPClientPlugin* getPlugin() const; + protected: friend class LSPClientServer; - + const PluginManager* mPluginManager{ nullptr }; LSPClientPlugin* mPlugin{ nullptr }; std::shared_ptr mThreadPool; std::map> mClients; diff --git a/src/tools/ecode/plugins/lsp/lspdefinition.hpp b/src/tools/ecode/plugins/lsp/lspdefinition.hpp index 2d2ea4f3a..22933888a 100644 --- a/src/tools/ecode/plugins/lsp/lspdefinition.hpp +++ b/src/tools/ecode/plugins/lsp/lspdefinition.hpp @@ -10,8 +10,10 @@ struct LSPDefinition { std::string name; std::vector filePatterns; std::string command; + std::string commandParameters; std::vector rootIndicationFileNames; std::string url; + bool disabled{ false }; }; } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientprotocol.hpp b/src/tools/ecode/plugins/lsp/lspprotocol.hpp similarity index 95% rename from src/tools/ecode/plugins/lsp/lspclientprotocol.hpp rename to src/tools/ecode/plugins/lsp/lspprotocol.hpp index 31102cdca..c95dd299d 100644 --- a/src/tools/ecode/plugins/lsp/lspclientprotocol.hpp +++ b/src/tools/ecode/plugins/lsp/lspprotocol.hpp @@ -9,6 +9,9 @@ using namespace EE; using namespace EE::UI::Doc; using namespace EE::Network; +// The LSP protocol will be consumed by any plugin, and it's not exclusive to the LSP plugin +// The protocol should be taken as a reference on how to implement the comunication between plugins + namespace ecode { enum class LSPErrorCode { @@ -219,10 +222,6 @@ enum class LSPSymbolKind { TypeParameter = 26, }; -enum class LSPSymbolTag : Uint8 { - Deprecated = 1, -}; - struct LSPSymbolInformation { LSPSymbolInformation() = default; LSPSymbolInformation( const std::string& _name, LSPSymbolKind _kind, TextRange _range, @@ -230,12 +229,11 @@ struct LSPSymbolInformation { name( _name ), detail( _detail ), kind( _kind ), range( _range ) {} std::string name; std::string detail; - LSPSymbolKind kind; + LSPSymbolKind kind{ LSPSymbolKind::File }; URI url; TextRange range; TextRange selectionRange; double score = 0.0; - LSPSymbolTag tags; std::vector children; }; @@ -259,7 +257,6 @@ struct LSPHover { TextRange range; }; - enum class LSPCompletionItemKind { Text = 1, Method = 2, @@ -300,6 +297,11 @@ struct LSPCompletionItem { std::vector additionalTextEdits; }; +struct LSPSelectionRange { + TextRange range; + std::shared_ptr parent; +}; + } // namespace ecode #endif // ECODE_LSPCLIENTPROTOCOL_HPP diff --git a/src/tools/ecode/plugins/pluginmanager.cpp b/src/tools/ecode/plugins/pluginmanager.cpp index 71cca2dc3..5e5e8ee25 100644 --- a/src/tools/ecode/plugins/pluginmanager.cpp +++ b/src/tools/ecode/plugins/pluginmanager.cpp @@ -104,23 +104,24 @@ const std::string& PluginManager::getWorkspaceFolder() const { void PluginManager::setWorkspaceFolder( const std::string& workspaceFolder ) { mWorkspaceFolder = workspaceFolder; - sendNotification( Notification::WorkspaceFolderChanged, - json{ { "folder", mWorkspaceFolder } } ); + json data{ { "folder", mWorkspaceFolder } }; + sendNotification( NotificationType::WorkspaceFolderChanged, NotificationFormat::JSON, &data ); } -void PluginManager::pushNotification( UICodeEditorPlugin* pluginWho, Notification notification, - const nlohmann::json& json ) const { +void PluginManager::pushNotification( UICodeEditorPlugin* pluginWho, NotificationType notification, + NotificationFormat format, void* data ) const { for ( const auto& plugin : mSubscribedPlugins ) if ( pluginWho->getId() != plugin.first ) - plugin.second( notification, json ); + plugin.second( { notification, format, data } ); } -void PluginManager::subscribeNotifications( - UICodeEditorPlugin* plugin, - std::function cb ) const { +void PluginManager::subscribeNotifications( UICodeEditorPlugin* plugin, + std::function cb ) const { const_cast( this )->mSubscribedPlugins[plugin->getId()] = cb; - if ( !mWorkspaceFolder.empty() ) - cb( Notification::WorkspaceFolderChanged, json{ { "folder", mWorkspaceFolder } } ); + if ( !mWorkspaceFolder.empty() ) { + json data{ { "folder", mWorkspaceFolder } }; + cb( { NotificationType::WorkspaceFolderChanged, NotificationFormat::JSON, &data } ); + } } void PluginManager::unsubscribeNotifications( UICodeEditorPlugin* plugin ) const { @@ -131,10 +132,10 @@ void PluginManager::setSplitter( UICodeEditorSplitter* splitter ) { mSplitter = splitter; } -void PluginManager::sendNotification( const Notification& notification, - const nlohmann::json& json ) { +void PluginManager::sendNotification( const NotificationType& notification, + const NotificationFormat& format, void* data ) { for ( const auto& plugin : mSubscribedPlugins ) - plugin.second( notification, json ); + plugin.second( { notification, format, data } ); } bool PluginManager::hasDefinition( const std::string& id ) { diff --git a/src/tools/ecode/plugins/pluginmanager.hpp b/src/tools/ecode/plugins/pluginmanager.hpp index 5160f4e5d..c1349385b 100644 --- a/src/tools/ecode/plugins/pluginmanager.hpp +++ b/src/tools/ecode/plugins/pluginmanager.hpp @@ -1,6 +1,7 @@ #ifndef ECODE_PLUGINMANAGER_HPP #define ECODE_PLUGINMANAGER_HPP +#include "lsp/lspprotocol.hpp" #include #include #include @@ -57,7 +58,19 @@ struct PluginDefinition { class PluginManager { public: - enum Notification { WorkspaceFolderChanged }; + enum NotificationType { WorkspaceFolderChanged, PublishDiagnostics }; + enum NotificationFormat { JSON, Diagnostics }; + struct Notification { + NotificationType type; + NotificationFormat format; + void* data; + + nlohmann::json& asJSON() const { return *static_cast( data ); } + + LSPPublishDiagnosticsParams& asDiagnostics() const { + return *static_cast( data ); + } + }; static constexpr int versionNumber( int major, int minor, int patch ) { return ( (major)*1000 + (minor)*100 + ( patch ) ); @@ -104,12 +117,11 @@ class PluginManager { void setWorkspaceFolder( const std::string& workspaceFolder ); - void pushNotification( UICodeEditorPlugin* pluginWho, Notification, - const nlohmann::json& ) const; + void pushNotification( UICodeEditorPlugin* pluginWho, NotificationType, NotificationFormat, + void* data ) const; - void - subscribeNotifications( UICodeEditorPlugin* plugin, - std::function cb ) const; + void subscribeNotifications( UICodeEditorPlugin* plugin, + std::function cb ) const; void unsubscribeNotifications( UICodeEditorPlugin* plugin ) const; @@ -123,14 +135,14 @@ class PluginManager { std::map mDefinitions; std::shared_ptr mThreadPool; UICodeEditorSplitter* mSplitter{ nullptr }; - std::map> - mSubscribedPlugins; + std::map> mSubscribedPlugins; bool hasDefinition( const std::string& id ); void setSplitter( UICodeEditorSplitter* splitter ); - void sendNotification( const Notification& notification, const nlohmann::json& json = {} ); + void sendNotification( const NotificationType& notification, const NotificationFormat& format, + void* data ); }; class PluginsModel : public Model {