Started implementing LSP workspace diagnostic to find that not a single LSP implements it, so I just wasted 2 hours of my life. I'll keep the base structure for the future.

This commit is contained in:
Martín Lucas Golini
2024-11-26 00:51:23 -03:00
parent 9f18c81dfb
commit 0e97236369
10 changed files with 120 additions and 5 deletions
-1
View File
@@ -55,7 +55,6 @@ void addJSX() {
{ "NaN", "keyword2" }, { "this", "keyword2" },
},
"//" } )
.setAutoCloseXMLTags( true )
.setLSPName( "javascriptreact" );
sd.setFoldRangeType( FoldRangeType::Braces ).setFoldBraces( { { '{', '}' } } );
-1
View File
@@ -103,7 +103,6 @@ void addTypeScript() {
"//" } )
.setSymbols( ts.getSymbols() )
.setLSPName( "typescriptreact" )
.setAutoCloseXMLTags( true )
.setFoldRangeType( FoldRangeType::Braces )
.setFoldBraces( { { '{', '}' } } );
;
@@ -434,6 +434,31 @@ PluginRequestHandle LSPClientPlugin::processWorkspaceSymbol( const PluginMessage
return hdl;
}
PluginRequestHandle LSPClientPlugin::processWorkspaceDiagnostic( const PluginMessage& msg ) {
if ( !( ( msg.isBroadcast() && msg.type == PluginMessageType::LanguageServerReady && msg.data &&
msg.format == PluginMessageFormat::LSPClientServer ) ||
( msg.isRequest() && msg.type == PluginMessageType::WorkspaceDiagnostic &&
msg.format == PluginMessageFormat::LSPClientServer ) ) )
return {};
/* NOTE: I couldn't find a single LSP server supporting this feature so I cannot test it.
* I'll leave the current implementation here to continue with it later in the future.
* Since I started implementing it assuming it was commonly supported...
LSPClientServer* server =
const_cast<LSPClientServer*>( reinterpret_cast<const LSPClientServer*>( msg.data ) );
if ( !server->getCapabilities().diagnosticProvider.workspaceDiagnostics )
return {};
server->workspaceDiagnosticAsync(
[this]( const PluginIDType&, LSPWorkspaceDiagnosticReport&& report ) {
mManager->sendBroadcast( this, PluginMessageType::WorkspaceDiagnostic,
PluginMessageFormat::WorkspaceDiagnosticReport, &report );
} );
*/
return PluginRequestHandle::broadcast();
}
PluginRequestHandle LSPClientPlugin::processTextDocumentSymbol( const PluginMessage& msg ) {
if ( !msg.isRequest() ||
( msg.type != PluginMessageType::TextDocumentSymbol &&
@@ -938,6 +963,11 @@ PluginRequestHandle LSPClientPlugin::processMessage( const PluginMessage& msg )
processFoldingRanges( msg );
break;
}
case PluginMessageType::WorkspaceDiagnostic:
case PluginMessageType::LanguageServerReady: {
processWorkspaceDiagnostic( msg );
break;
}
default:
break;
}
@@ -27,7 +27,7 @@ class LSPClientPlugin : public Plugin {
public:
static PluginDefinition Definition() {
return { "lspclient", "LSP Client", "Language Server Protocol Client.",
LSPClientPlugin::New, { 0, 2, 7 }, LSPClientPlugin::NewSync };
LSPClientPlugin::New, { 0, 2, 8 }, LSPClientPlugin::NewSync };
}
static Plugin* New( PluginManager* pluginManager );
@@ -171,6 +171,8 @@ class LSPClientPlugin : public Plugin {
PluginRequestHandle processWorkspaceSymbol( const PluginMessage& msg );
PluginRequestHandle processWorkspaceDiagnostic( const PluginMessage& msg );
void tryHideTooltip( UICodeEditor* editor, const Vector2i& position );
void hideTooltip( UICodeEditor* editor );
@@ -55,6 +55,7 @@ static const char* MEMBER_QUERY = "query";
static const char* MEMBER_SUCCESS = "success";
static const char* MEMBER_LIMIT = "limit";
static const char* MEMBER_OPTIONS = "options";
static const char* MEMBER_PREVIOUS_RESULT_IDS = "previousResultIds";
static json newRequest( const std::string& method, const json& params = json{} ) {
json j;
@@ -366,6 +367,11 @@ static void fromJson( LSPServerCapabilities& caps, const json& json ) {
caps.documentHighlightProvider = toBoolOrObject( json, "documentHighlightProvider" );
caps.documentFormattingProvider = toBoolOrObject( json, "documentFormattingProvider" );
caps.workspaceSymbolProvider = toBoolOrObject( json, "workspaceSymbolProvider" );
if ( json.contains( "diagnosticProvider" ) ) {
caps.diagnosticProvider.workspaceDiagnostics = json.value( "workspaceDiagnostics", false );
caps.diagnosticProvider.interFileDependencies =
json.value( "interFileDependencies", false );
}
caps.foldingRangeProvider = toBoolOrObject( json, "foldingRangeProvider" );
if ( json.contains( "documentRangeFormattingProvider" ) )
caps.documentRangeFormattingProvider =
@@ -1041,6 +1047,24 @@ static std::vector<LSPFoldingRange> parseFoldingRange( const json& result ) {
return ranges;
}
static LSPWorkspaceDiagnosticReport parseWorkspaceDiagnosticReport( const json& res ) {
LSPWorkspaceDiagnosticReport report;
if ( res.contains( "items" ) ) {
for ( const auto& item : res["items"] ) {
if ( item.contains( "kind" ) && item.contains( "uri" ) ) {
LSPFullDocumentDiagnosticReport docReport;
URI uri = item.at( "uri" ).get<std::string>();
docReport.uri = uri;
docReport.kind = item.at( "kind" ).get<std::string>();
docReport.resultId = item.value<std::string>( "resultId", "" );
docReport.items = parseDiagnostics( item["item"] );
report.items[uri] = std::move( docReport );
}
}
}
return report;
}
void LSPClientServer::registerCapabilities( const json& jcap ) {
if ( !jcap.is_object() || !jcap.contains( "registrations" ) ||
!jcap["registrations"].is_array() )
@@ -1194,6 +1218,10 @@ void LSPClientServer::initialize() {
mManager->getPluginManager()->sendBroadcast(
mManager->getPlugin(), PluginMessageType::LanguageServerCapabilities,
PluginMessageFormat::LanguageServerCapabilities, &mCapabilities );
mManager->getPluginManager()->sendBroadcast(
nullptr, PluginMessageType::LanguageServerReady,
PluginMessageFormat::LSPClientServer, this );
},
[]( const IdType&, const json& ) {} );
}
@@ -1720,6 +1748,18 @@ LSPClientServer::workspaceSymbol( const std::string& querySymbol, const SymbolIn
limit );
}
void LSPClientServer::workspaceDiagnosticAsync( const JsonReplyHandler& h ) {
auto params = json{ { MEMBER_PREVIOUS_RESULT_IDS, json::array() } };
sendAsync( newRequest( "workspace/diagnostic", params ), h );
}
void LSPClientServer::workspaceDiagnosticAsync( const WorkspaceDiagnosticHandler& h ) {
workspaceDiagnosticAsync( [h]( const IdType& id, const json& json ) {
if ( h )
h( id, parseWorkspaceDiagnosticReport( json ) );
} );
}
void fromJson( LSPWorkDoneProgressValue& value, const json& data ) {
if ( !data.empty() ) {
json ob = data;
@@ -47,6 +47,7 @@ class LSPClientServer {
using TextEditArrayHandler = ReplyHandler<std::vector<LSPTextEdit>>;
using WorkspaceEditHandler = ReplyHandler<LSPWorkspaceEdit>;
using SemanticTokensDeltaHandler = WReplyHandler<LSPSemanticTokensDelta>;
using WorkspaceDiagnosticHandler = WReplyHandler<LSPWorkspaceDiagnosticReport>;
class LSPRequestHandle : public PluginRequestHandle {
public:
@@ -199,6 +200,10 @@ class LSPClientServer {
const SymbolInformationHandler& h,
const size_t& limit = 100 );
void workspaceDiagnosticAsync( const JsonReplyHandler& h );
void workspaceDiagnosticAsync( const WorkspaceDiagnosticHandler& h );
LSPRequestHandle selectionRange( const URI& document,
const std::vector<TextPosition>& positions,
const JsonReplyHandler& h );
@@ -488,6 +488,11 @@ void LSPClientServerManager::didChangeWorkspaceFolders( const std::string& folde
Lock l( mClientsMutex );
for ( auto& server : mClients ) {
server.second->didChangeWorkspaceFolders( newWorkspaceFolder, oldLSPWorkspaceFolder, true );
if ( server.second->getCapabilities().diagnosticProvider.workspaceDiagnostics ) {
mPlugin->getManager()->sendRequest( PluginMessageType::WorkspaceDiagnostic,
PluginMessageFormat::LSPClientServer,
server.second.get() );
}
// If there's a workspace folder change, but the server don't support it, we need to close
// the server because the current workspace will be broken if we don't reopen the server in
// the correct rootUri/rootPath
@@ -133,6 +133,11 @@ struct LSPCodeLensOptions {
bool resolveProvider = false;
};
struct LSPDiagnosticOptions {
bool interFileDependencies = false;
bool workspaceDiagnostics = false;
};
struct LSPServerCapabilities {
bool ready = false;
std::vector<std::string> languages;
@@ -154,6 +159,7 @@ struct LSPServerCapabilities {
bool foldingRangeProvider = false;
LSPCodeLensOptions codeLensProvider;
bool workspaceSymbolProvider = false;
LSPDiagnosticOptions diagnosticProvider;
LSPDocumentOnTypeFormattingOptions documentOnTypeFormattingProvider;
bool renameProvider = false;
// CodeActionOptions not useful/considered at present
@@ -623,6 +629,27 @@ struct LSPFoldingRange {
LSPFoldingRangeKind kind{ LSPFoldingRangeKind::Region };
};
struct LSPPreviousResultId {
URI uri;
std::string value;
};
using LSPPreviousResultIds = std::vector<LSPPreviousResultId>;
static constexpr auto LSPDocumentDiagnosticReportKindFull = "full";
static constexpr auto LSPDocumentDiagnosticReportKindUnchanged= "unchanged";
struct LSPFullDocumentDiagnosticReport {
URI uri;
std::string kind;
std::string resultId;
std::vector<LSPDiagnostic> items;
};
struct LSPWorkspaceDiagnosticReport {
std::unordered_map<URI, LSPFullDocumentDiagnosticReport> items;
};
} // namespace ecode
#endif // ECODE_LSPCLIENTPROTOCOL_HPP
+1 -1
View File
@@ -222,7 +222,7 @@ void PluginManager::sendBroadcast( Plugin* pluginWho, PluginMessageType type,
subscribedPlugins = mSubscribedPlugins;
}
for ( const auto& plugin : subscribedPlugins )
if ( pluginWho->getId() != plugin.first )
if ( nullptr == pluginWho || pluginWho->getId() != plugin.first )
plugin.second( { type, format, data, -1 } );
}
+9 -1
View File
@@ -93,6 +93,8 @@ enum class PluginMessageType {
UIReady, // Informs the Plugins that the UI is ready to be used
UIThemeReloaded, // Informs the plugins that the UI theme has been reloaded
FoldingRanges, // Request to the LSP server the folding ranges of a document
WorkspaceDiagnostic, // Informs the current workspace diagnostic
LanguageServerReady, // Informs that an LSP server is ready
Undefined
};
@@ -108,7 +110,9 @@ enum class PluginMessageFormat {
ShowDocument,
SymbolInformation,
DiagnosticsCodeAction,
FoldingRanges
FoldingRanges,
WorkspaceDiagnosticReport,
LSPClientServer,
};
class PluginIDType {
@@ -206,6 +210,10 @@ struct PluginMessage {
return *static_cast<const LSPDiagnosticsCodeAction*>( data );
}
const LSPWorkspaceDiagnosticReport& asLSPWorkspaceDiagnosticReport() const {
return *static_cast<const LSPWorkspaceDiagnosticReport*>( data );
}
const PluginIDType& asPluginID() const { return *static_cast<const PluginIDType*>( data ); }
bool isResponse() const { return -1 != responseID && 0 != responseID; }