More LSP implementation work.

This commit is contained in:
Martín Lucas Golini
2022-11-05 03:37:21 -03:00
parent 14997a945c
commit 3671db4ba6
14 changed files with 686 additions and 115 deletions

View File

@@ -94,6 +94,8 @@ class EE_API URI {
/** Parses and assigns an URI from the given string. */
URI& operator=( const char* uri );
bool operator<( const URI& url ) const;
/** Swaps the URI with another one. */
void swap( URI& uri );

View File

@@ -57,6 +57,10 @@ class EE_API TextRange {
return true;
}
bool contains( const TextRange& range ) const {
return range.start() >= start() && range.end() <= end();
}
bool hasSelection() const { return isValid() && mStart != mEnd; }
bool inSameLine() const { return isValid() && mStart.line() == mEnd.line(); }

View File

@@ -539,6 +539,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
void showFindReplace();
TextPosition resolveScreenPosition( const Vector2f& position, bool clamp = true ) const;
protected:
struct LastXOffset {
TextPosition position;
@@ -716,8 +718,6 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
void resetCursor();
TextPosition resolveScreenPosition( const Vector2f& position, bool clamp = true ) const;
Vector2f getViewPortLineCount() const;
Sizef getMaxScroll() const;

View File

@@ -716,4 +716,39 @@ void URI::buildPath( const std::vector<std::string>& segments, bool leadingSlash
mPath += '/';
}
bool URI::operator<( const URI& url ) const {
int cmp;
cmp = mScheme.compare( url.mScheme );
if ( mScheme != url.mScheme )
return cmp < 0;
cmp = mUserInfo.compare( url.mUserInfo );
if ( cmp != 0 )
return cmp < 0;
cmp = mHost.compare( url.mHost );
if ( cmp != 0 )
return cmp < 0;
if ( mPort != url.mPort )
return mPort < url.mPort;
cmp = mPath.compare( url.mPath );
if ( cmp != 0 )
return cmp < 0;
if ( mQuery.empty() != url.mQuery.empty() )
return !url.mQuery.empty();
cmp = mQuery.compare( url.mQuery );
if ( cmp != 0 )
return cmp < 0;
if ( mFragment.empty() != url.mFragment.empty() )
return !url.mFragment.empty();
cmp = mFragment.compare( url.mFragment );
return cmp < 0;
}
}} // namespace EE::Network

View File

@@ -31,11 +31,11 @@ Process::Process( const std::string& command, const Uint32& options,
Process::~Process() {
mShuttingDown = true;
destroy();
if ( mStdOutThread.joinable() )
mStdOutThread.join();
if ( mStdErrThread.joinable() )
mStdErrThread.join();
destroy();
eeFree( mProcess );
}

View File

@@ -87,10 +87,8 @@ void LinterPlugin::loadLinterConfig( const std::string& path ) {
if ( j.contains( "config" ) ) {
auto& config = j["config"];
if ( config.contains( "delay_time" ) ) {
Time time( Time::fromString( config["delay_time"].get<std::string>() ) );
setDelayTime( time );
}
if ( config.contains( "delay_time" ) )
setDelayTime( Time::fromString( config["delay_time"].get<std::string>() ) );
}
if ( !j.contains( "linters" ) )
@@ -563,27 +561,30 @@ bool LinterPlugin::onMouseMove( UICodeEditor* editor, const Vector2i& pos, const
auto& matches = matchIt.second;
for ( auto& match : matches ) {
if ( match.box[editor].contains( localPos ) ) {
editor->setTooltipText( match.text );
editor->getTooltip()->setDontAutoHideOnMouseMove( true );
editor->getTooltip()->setPixelsPosition( Vector2f( pos.x, pos.y ) );
if ( !editor->getTooltip()->isVisible() )
editor->runOnMainThread(
[&, editor] { editor->getTooltip()->show(); } );
return false;
mHoveringMatch = true;
editor->runOnMainThread( [&, editor] {
editor->setTooltipText( match.text );
editor->getTooltip()->setDontAutoHideOnMouseMove( true );
editor->getTooltip()->setPixelsPosition( Vector2f( pos.x, pos.y ) );
if ( !editor->getTooltip()->isVisible() )
editor->getTooltip()->show();
} );
return true;
}
}
}
}
if ( editor->getTooltip() && editor->getTooltip()->isVisible() ) {
if ( mHoveringMatch && editor->getTooltip() && editor->getTooltip()->isVisible() ) {
editor->setTooltipText( "" );
editor->getTooltip()->hide();
mHoveringMatch = false;
}
}
return false;
}
bool LinterPlugin::onMouseLeave( UICodeEditor* editor, const Vector2i&, const Uint32& ) {
if ( editor->getTooltip() && editor->getTooltip()->isVisible() ) {
if ( mHoveringMatch && editor->getTooltip() && editor->getTooltip()->isVisible() ) {
editor->setTooltipText( "" );
editor->getTooltip()->hide();
}

View File

@@ -106,6 +106,7 @@ class LinterPlugin : public UICodeEditorPlugin {
bool mReady{ false };
bool mShuttingDown{ false };
bool mHoveringMatch{ false };
LinterPlugin( const PluginManager* pluginManager );

View File

@@ -3,6 +3,7 @@
#include <eepp/system/lock.hpp>
#include <eepp/system/luapattern.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/ui/uitooltip.hpp>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
@@ -99,6 +100,12 @@ void LSPClientPlugin::loadLSPConfig( std::vector<LSPDefinition>& lsps, const std
return;
}
if ( j.contains( "config" ) ) {
auto& config = j["config"];
if ( config.contains( "hover_delay" ) )
setHoverDelay( Time::fromString( config["hover_delay"].get<std::string>() ) );
}
if ( mKeyBindings.empty() )
mKeyBindings["lsp-go-to-definition"] = "f2";
@@ -306,4 +313,56 @@ bool LSPClientPlugin::onCreateContextMenu( UICodeEditor* editor, UIPopUpMenu* me
return false;
}
static void hideTooltip( UICodeEditor* editor ) {
if ( editor->getTooltip() && editor->getTooltip()->isVisible() ) {
editor->setTooltipText( "" );
editor->getTooltip()->hide();
}
}
bool LSPClientPlugin::onMouseMove( UICodeEditor* editor, const Vector2i& position, const Uint32& ) {
Uint32 tag = String::hash( editor->getDocument().getFilePath() );
editor->removeActionsByTag( tag );
editor->runOnMainThread(
[&, editor, position]() {
TextPosition cursorPosition = editor->resolveScreenPosition( position.asFloat() );
mThreadPool->run( [&, editor, position, cursorPosition]() {
mClientManager.getOneLSPClientServer( editor )->documentHover(
editor->getDocument().getURI(), cursorPosition,
[&, editor, position]( const LSPHover& resp ) {
mCurrentHover = resp;
if ( resp.range.isValid() && !resp.contents.empty() ) {
editor->runOnMainThread( [editor, resp, position]() {
editor->setTooltipText( resp.contents[0].value );
editor->getTooltip()->setHorizontalAlign( UI_HALIGN_LEFT );
editor->getTooltip()->setPixelsPosition( position.asFloat() );
editor->getTooltip()->setDontAutoHideOnMouseMove( true );
if ( editor->hasFocus() && !editor->getTooltip()->isVisible() )
editor->getTooltip()->show();
} );
}
} );
} );
},
mHoverDelay, tag );
TextPosition cursorPosition = editor->resolveScreenPosition( position.asFloat() );
if ( !mCurrentHover.range.isValid() || !mCurrentHover.range.contains( cursorPosition ) )
hideTooltip( editor );
return mCurrentHover.range.isValid() && editor->getTooltip() &&
editor->getTooltip()->isVisible();
}
void LSPClientPlugin::onFocusLoss( UICodeEditor* editor ) {
hideTooltip( editor );
}
const Time& LSPClientPlugin::getHoverDelay() const {
return mHoverDelay;
}
void LSPClientPlugin::setHoverDelay( const Time& hoverDelay ) {
mHoverDelay = hoverDelay;
}
} // namespace ecode

View File

@@ -54,6 +54,13 @@ class LSPClientPlugin : public UICodeEditorPlugin {
virtual bool onCreateContextMenu( UICodeEditor* editor, UIPopUpMenu* menu,
const Vector2i& position, const Uint32& flags );
virtual bool onMouseMove( UICodeEditor* editor, const Vector2i& position, const Uint32& flags );
virtual void onFocusLoss( UICodeEditor* editor );
const Time& getHoverDelay() const;
void setHoverDelay( const Time& hoverDelay );
protected:
const PluginManager* mManager{ nullptr };
std::shared_ptr<ThreadPool> mThreadPool;
@@ -68,6 +75,9 @@ class LSPClientPlugin : public UICodeEditorPlugin {
bool mReady{ false };
std::map<std::string, std::string> mKeyBindings; /* cmd, shortcut */
std::map<TextDocument*, std::shared_ptr<TextDocument>> mDelayedDocs;
Uint32 mHoverWaitCb;
LSPHover mCurrentHover;
Time mHoverDelay{ Seconds( 1.f ) };
LSPClientPlugin( const PluginManager* pluginManager );

View File

@@ -5,6 +5,7 @@
#include <nlohmann/json.hpp>
#include <string>
using namespace EE;
using namespace EE::UI::Doc;
using namespace EE::Network;
@@ -189,8 +190,114 @@ template <typename T> struct LSPProgressParams {
T value;
};
enum class LSPSymbolKind {
File = 1,
Module = 2,
Namespace = 3,
Package = 4,
Class = 5,
Method = 6,
Property = 7,
Field = 8,
Constructor = 9,
Enum = 10,
Interface = 11,
Function = 12,
Variable = 13,
Constant = 14,
String = 15,
Number = 16,
Boolean = 17,
Array = 18,
Object = 19,
Key = 20,
Null = 21,
EnumMember = 22,
Struct = 23,
Event = 24,
Operator = 25,
TypeParameter = 26,
};
enum class LSPSymbolTag : Uint8 {
Deprecated = 1,
};
struct LSPSymbolInformation {
LSPSymbolInformation() = default;
LSPSymbolInformation( const std::string& _name, LSPSymbolKind _kind, TextRange _range,
const std::string& _detail ) :
name( _name ), detail( _detail ), kind( _kind ), range( _range ) {}
std::string name;
std::string detail;
LSPSymbolKind kind;
URI url;
TextRange range;
TextRange selectionRange;
double score = 0.0;
LSPSymbolTag tags;
std::vector<LSPSymbolInformation> children;
};
using LSPWorkDoneProgressParams = LSPProgressParams<LSPWorkDoneProgressValue>;
struct LSPResponseError {
LSPErrorCode code{};
std::string message;
nlohmann::json data;
};
enum class LSPMarkupKind { None = 0, PlainText = 1, MarkDown = 2 };
struct LSPMarkupContent {
LSPMarkupKind kind = LSPMarkupKind::None;
std::string value;
};
struct LSPHover {
std::vector<LSPMarkupContent> contents;
TextRange range;
};
enum class LSPCompletionItemKind {
Text = 1,
Method = 2,
Function = 3,
Constructor = 4,
Field = 5,
Variable = 6,
Class = 7,
Interface = 8,
Module = 9,
Property = 10,
Unit = 11,
Value = 12,
Enum = 13,
Keyword = 14,
Snippet = 15,
Color = 16,
File = 17,
Reference = 18,
Folder = 19,
EnumMember = 20,
Constant = 21,
Struct = 22,
Event = 23,
Operator = 24,
TypeParameter = 25,
};
struct LSPCompletionItem {
std::string label;
LSPCompletionItemKind kind;
std::string detail;
LSPMarkupContent documentation;
std::string sortText;
std::string insertText;
std::vector<LSPTextEdit> additionalTextEdits;
};
} // namespace ecode
#endif // ECODE_LSPCLIENTPROTOCOL_HPP

View File

@@ -32,14 +32,14 @@ static const char* MEMBER_LOCATION = "location";
static const char* MEMBER_RANGE = "range";
static const char* MEMBER_LINE = "line";
static const char* MEMBER_CHARACTER = "character";
// static const char* MEMBER_KIND = "kind";
// static const char* MEMBER_LABEL = "label";
// static const char* MEMBER_DOCUMENTATION = "documentation";
// static const char* MEMBER_DETAIL = "detail";
// static const char* MEMBER_COMMAND = "command";
// static const char* MEMBER_EDIT = "edit";
// static const char* MEMBER_TITLE = "title";
// static const char* MEMBER_ARGUMENTS = "arguments";
static const char* MEMBER_KIND = "kind";
static const char* MEMBER_LABEL = "label";
static const char* MEMBER_DOCUMENTATION = "documentation";
static const char* MEMBER_DETAIL = "detail";
static const char* MEMBER_COMMAND = "command";
static const char* MEMBER_EDIT = "edit";
static const char* MEMBER_TITLE = "title";
static const char* MEMBER_ARGUMENTS = "arguments";
static const char* MEMBER_DIAGNOSTICS = "diagnostics";
static const char* MEMBER_TARGET_URI = "targetUri";
static const char* MEMBER_TARGET_RANGE = "targetRange";
@@ -295,14 +295,87 @@ static std::vector<LSPDiagnostic> parseDiagnosticsArr( const json& result ) {
return ret;
}
static LSPPublishDiagnosticsParams parseDiagnostics( const json& result ) {
static LSPPublishDiagnosticsParams parsePublishDiagnostics( const json& result ) {
LSPPublishDiagnosticsParams ret;
ret.uri = URI( result.at( MEMBER_URI ).get<std::string>() );
ret.diagnostics = parseDiagnosticsArr( result.at( MEMBER_DIAGNOSTICS ) );
return ret;
}
/*static std::vector<LSPTextEdit> parseTextEdit( const json& result ) {
static bool isPositionValid( const TextPosition& pos ) {
return pos.column() >= 0 && pos.line() >= 0;
}
static std::vector<LSPSymbolInformation> parseDocumentSymbols( const json& result ) {
// the reply could be old SymbolInformation[] or new (hierarchical) DocumentSymbol[]
// try to parse it adaptively in any case
// if new style, hierarchy is specified clearly in reply
// if old style, it is assumed the values enter linearly, that is;
// * a parent/container is listed before its children
// * if a name is defined/declared several times and then used as a parent,
// then we try to find such a parent whose range contains current range
// (otherwise fall back to using the last instance as a parent)
std::vector<LSPSymbolInformation> ret;
std::map<std::string, LSPSymbolInformation*> index;
std::function<void( const json& symbol, LSPSymbolInformation* parent )> parseSymbol =
[&]( const json& symbol, LSPSymbolInformation* parent ) {
const auto& mrange = symbol.contains( MEMBER_RANGE )
? symbol.at( MEMBER_RANGE )
: symbol[MEMBER_LOCATION].at( MEMBER_RANGE );
auto range = parseRange( mrange );
std::map<std::string, LSPSymbolInformation*>::iterator it = index.end();
// if flat list, try to find parent by name
if ( !parent ) {
auto container = symbol.value( "containerName", "" );
it = index.find( container );
// default to last inserted
if ( it != index.end() ) {
parent = it->second;
}
// but prefer a containing range
while ( it != index.end() && it->first == container ) {
if ( it->second->range.contains( range ) ) {
parent = it->second;
break;
}
++it;
}
}
auto list = parent ? &parent->children : &ret;
if ( isPositionValid( range.start() ) && isPositionValid( range.end() ) ) {
auto name = symbol.at( ( "name" ) ).get<std::string>();
auto kind = static_cast<LSPSymbolKind>( symbol.at( MEMBER_KIND ).get<int>() );
auto detail = symbol.value( MEMBER_DETAIL, "" );
list->push_back( { name, kind, range, detail } );
index.insert( std::pair( name, &list->back() ) );
// proceed recursively
if ( symbol.contains( "children" ) ) {
const auto& children = symbol.at( ( "children" ) );
for ( const auto& child : children )
parseSymbol( child, &list->back() );
}
}
};
const auto symInfos = result;
for ( const auto& info : symInfos )
parseSymbol( info, nullptr );
return ret;
}
static LSPResponseError parseResponseError( const json& v ) {
LSPResponseError ret;
if ( v.is_object() ) {
const auto& vm = v;
ret.code = LSPErrorCode( vm.at( MEMBER_CODE ).get<int>() );
ret.message = vm.at( MEMBER_MESSAGE ).get<std::string>();
ret.data = vm.value( "data", json() );
}
return ret;
}
static std::vector<LSPTextEdit> parseTextEdit( const json& result ) {
std::vector<LSPTextEdit> ret;
const auto textEdits = result;
for ( const auto& redit : textEdits ) {
@@ -352,20 +425,56 @@ static LSPCommand parseCommand( const json& result ) {
return { title, command, args };
}
static std::vector<LSPDiagnostic> parseDiagnostics( const json& result ) {
std::vector<LSPDiagnostic> ret;
for ( const auto& vdiag : result ) {
const auto& diag = vdiag;
auto range = parseRange( diag.at( MEMBER_RANGE ) );
auto severity = static_cast<LSPDiagnosticSeverity>( diag.value<int>( "severity", 0 ) );
std::string code;
if ( diag.contains( "code" ) ) {
if ( diag["code"].is_number_integer() )
code = String::toString( diag["code"].get<int>() );
else
code = diag.value( "code", "" );
}
auto source = diag.value( "source", "" );
auto message = diag.value( MEMBER_MESSAGE, "" );
std::vector<LSPDiagnosticRelatedInformation> relatedInfoList;
if ( diag.contains( "relatedInformation" ) ) {
const auto& relatedInfo = diag.at( "relatedInformation" );
for ( const auto& related : relatedInfo ) {
auto relLocation = parseLocation( related.at( MEMBER_LOCATION ) );
auto relMessage = related.value( MEMBER_MESSAGE, "" );
relatedInfoList.push_back( { relLocation, relMessage } );
}
}
ret.push_back( { range, severity, code, source, message, relatedInfoList } );
}
return ret;
}
static std::vector<LSPCodeAction> parseCodeAction( const json& result ) {
std::vector<LSPCodeAction> ret;
const auto codeActions = result;
const auto& codeActions = result;
for ( const auto& vaction : codeActions ) {
auto action = vaction;
auto& action = vaction;
// entry could be Command or CodeAction
if ( !action.at( MEMBER_COMMAND ).is_string() ) {
// CodeAction
auto title = action.at( MEMBER_TITLE ).get<std::string>();
auto kind = action.at( MEMBER_KIND ).get<std::string>();
auto command = parseCommand( action.at( MEMBER_COMMAND ) );
auto edit = parseWorkSpaceEdit( action.at( MEMBER_EDIT ) );
auto diagnostics = parseDiagnostics( action.at( MEMBER_DIAGNOSTICS ) );
ret.push_back( { title, kind, diagnostics, edit, command } );
auto kind = action.value( MEMBER_KIND, "" );
auto command = action.contains( MEMBER_COMMAND )
? parseCommand( action.at( MEMBER_COMMAND ) )
: LSPCommand{};
auto edit = action.at( MEMBER_EDIT ) ? parseWorkSpaceEdit( action.at( MEMBER_EDIT ) )
: LSPWorkspaceEdit{};
auto diagnostics = action.contains( MEMBER_DIAGNOSTICS )
? parseDiagnostics( action.at( MEMBER_DIAGNOSTICS ) )
: std::vector<LSPDiagnostic>{};
LSPCodeAction action = { title, kind, diagnostics, edit, command };
ret.push_back( action );
} else {
// Command
auto command = parseCommand( action );
@@ -373,7 +482,212 @@ static std::vector<LSPCodeAction> parseCodeAction( const json& result ) {
}
}
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 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();
}
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<int>( 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 );
}
}
result[( "relatedInformation" )] = relatedInfo;
return result;
}
static json codeActionParams( const URI& document, const TextRange& range,
const std::vector<std::string>& kinds,
const std::vector<LSPDiagnostic>& diagnostics ) {
auto params = textDocumentParams( document );
params[MEMBER_RANGE] = toJson( range );
json context;
json diags;
for ( const auto& diagnostic : diagnostics ) {
diags.push_back( toJson( diagnostic ) );
}
context[MEMBER_DIAGNOSTICS] = diags;
if ( !kinds.empty() )
context["only"] = json( kinds );
params["context"] = context;
return params;
}
static LSPMarkupContent parseMarkupContent( const json& v ) {
LSPMarkupContent ret;
if ( v.is_object() ) {
ret.value = v.at( "value" );
auto kind = v.value( MEMBER_KIND, "plaintext" );
if ( kind == "plaintext" ) {
ret.kind = LSPMarkupKind::PlainText;
} else if ( kind == "markdown" ) {
ret.kind = LSPMarkupKind::MarkDown;
}
} else if ( v.is_string() ) {
ret.kind = LSPMarkupKind::PlainText;
ret.value = v.get<std::string>();
}
return ret;
}
static LSPHover parseHover( const json& result ) {
LSPHover ret;
if ( result.is_null() )
return ret;
if ( result.contains( MEMBER_RANGE ) )
ret.range = parseRange( result.at( MEMBER_RANGE ) );
const auto& contents = result.at( "contents" );
if ( contents.is_array() ) {
for ( const auto& c : contents )
ret.contents.push_back( parseMarkupContent( c ) );
} else {
ret.contents.push_back( parseMarkupContent( contents ) );
}
return ret;
}
static std::vector<std::string> supportedSemanticTokenTypes() {
return { "namespace", "type", "class", "enum", "interface", "struct",
"typeParameter", "parameter", "variable", "property", "enumMember", "event",
"function", "method", "macro", "keyword", "modifier", "comment",
"string", "number", "regexp", "operator" };
}
static std::vector<LSPCompletionItem> parseDocumentCompletion( const json& result ) {
std::vector<LSPCompletionItem> ret;
if ( result.empty() )
return {};
const json& items =
( result.is_object() && result.contains( "items" ) ) ? result["items"] : result;
for ( const auto& item : items ) {
auto label = item.value( MEMBER_LABEL, "" );
auto detail = item.value( MEMBER_DETAIL, "" );
LSPMarkupContent doc = item.contains( MEMBER_DOCUMENTATION )
? parseMarkupContent( item.at( MEMBER_DOCUMENTATION ) )
: LSPMarkupContent{};
auto sortText = item.value( "sortText", "" );
if ( sortText.empty() )
sortText = label;
auto insertText = item.value( "insertText", "" );
if ( insertText.empty() )
insertText = label;
if ( item.contains( "textEdit" ) ) {
const auto& textEdit = item["textEdit"];
if ( !textEdit.empty() ) {
auto newText = textEdit.value( "newText", "" );
insertText = newText;
}
}
auto kind = static_cast<LSPCompletionItemKind>( item.value( MEMBER_KIND, 1 ) );
const std::vector<LSPTextEdit> additionalTextEdits =
item.contains( "additionalTextEdits" )
? parseTextEdit( item.at( "additionalTextEdits" ) )
: std::vector<LSPTextEdit>{};
ret.push_back( { label, kind, detail, doc, sortText, insertText,
additionalTextEdits /*, textEdit*/ } );
}
return ret;
}
void LSPClientServer::initialize() {
json codeAction{
{ "codeActionLiteralSupport", json{ { "codeActionKind", json{ { "valueSet", {} } } } } } };
json semanticTokens{
{ "requests", json{ { "range", true }, { "full", json{ { "delta", true } } } } },
{ "tokenTypes", supportedSemanticTokenTypes() },
{ "tokenModifiers", {} },
{ "formats", { "relative" } },
};
json capabilities{
{
"textDocument",
json{ { "documentSymbol", json{ { "hierarchicalDocumentSymbolSupport", true } } },
{ "publishDiagnostics", json{ { "relatedInformation", true } } },
{ "codeAction", codeAction },
{ "semanticTokens", semanticTokens },
{ "synchronization", json{ { "didSave", true } } },
{ "selectionRange", json{ { "dynamicRegistration", false } } },
{ "hover", json{ { "contentFormat", { "plaintext" } } } } },
},
{ "window", json{ { "workDoneProgress", true } } },
{ "general", json{ { "positionEncodings", json::array( { "utf-32" } ) } } } };
json params{ { "processId", Sys::getProcessID() },
{ "capabilities", capabilities },
{ "initializationOptions", {} } };
std::string rootPath = mRootPath;
if ( rootPath.empty() ) {
if ( !mManager->getLSPWorkspaceFolder().uri.empty() )
rootPath = mManager->getLSPWorkspaceFolder().uri.getPath();
else
rootPath = FileSystem::getCurrentWorkingDirectory();
}
std::string uriRootPath = "file://" + rootPath;
params["rootPath"] = rootPath;
params["rootUri"] = uriRootPath;
params["workspaceFolders"] =
toJson( { LSPWorkspaceFolder{ uriRootPath, FileSystem::fileNameFromPath( rootPath ) } } );
capabilities["workspace"] = json{ { "workspaceFolders", true }, { "configuration", false } };
write(
newRequest( "initialize", params ),
[&]( const json& resp ) {
#ifndef EE_DEBUG
try {
#endif
fromJson( mCapabilities, resp["capabilities"] );
#ifndef EE_DEBUG
} catch ( const json::exception& e ) {
Log::warning(
"LSPClientServer::initialize server %s error parsing capabilities: %s",
mLSP.name.c_str(), e.what() );
}
#endif
mReady = true;
write( newRequest( "initialized" ) );
sendQueuedMessages();
},
[&]( const json& ) {} );
}
LSPClientServer::LSPClientServer( LSPClientServerManager* manager, const String::HashType& id,
const LSPDefinition& lsp, const std::string& rootPath ) :
@@ -422,6 +736,7 @@ const LSPServerCapabilities& LSPClientServer::getCapabilities() const {
}
LSPClientServer::RequestHandle LSPClientServer::cancel( int reqid ) {
Lock l( mHandlersMutex );
if ( mHandlers.erase( reqid ) > 0 ) {
auto params = json{ MEMBER_ID, reqid };
return write( newRequest( "$/cancelRequest", params ) );
@@ -444,6 +759,7 @@ LSPClientServer::RequestHandle LSPClientServer::write( const json& msg, const Js
if ( h ) {
ob[MEMBER_ID] = ++mLastMsgId;
ret.mId = mLastMsgId;
Lock l( mHandlersMutex );
mHandlers[mLastMsgId] = { h, eh };
} else if ( id ) {
ob[MEMBER_ID] = id;
@@ -584,6 +900,22 @@ LSPClientServer::RequestHandle LSPClientServer::documentSymbols( const URI& docu
return send( newRequest( "textDocument/documentSymbol", params ), h, eh );
}
LSPClientServer::RequestHandle
LSPClientServer::documentSymbols( const URI& document,
const ReplyHandler<std::vector<LSPSymbolInformation>>& h,
const ReplyHandler<LSPResponseError>& eh ) {
return documentSymbols(
document,
[h]( const json& json ) {
if ( h )
h( parseDocumentSymbols( json ) );
},
[eh]( const json& json ) {
if ( eh )
eh( parseResponseError( json ) );
} );
}
void fromJson( LSPWorkDoneProgressValue& value, const json& json ) {
if ( !json.empty() ) {
auto ob = json;
@@ -620,7 +952,7 @@ static json newError( const LSPErrorCode& code, const std::string& msg ) {
void LSPClientServer::publishDiagnostics( const json& msg ) {
// should emmit event somewhere
auto res = parseDiagnostics( msg[MEMBER_PARAMS] );
auto res = parsePublishDiagnostics( msg[MEMBER_PARAMS] );
Log::debug( "LSPClientServer::publishDiagnostics: %s - returned %zu items",
res.uri.toString().c_str(), res.diagnostics.size() );
}
@@ -732,6 +1064,7 @@ void LSPClientServer::readStdOut( const char* bytes, size_t n ) {
Log::debug( "LSPClientServer::readStdOut server %s said: \n%s", mLSP.name.c_str(),
res.dump().c_str() );
Lock l( mHandlersMutex );
auto it = mHandlers.find( msgid );
if ( it != mHandlers.end() ) {
const auto handler = *it;
@@ -771,79 +1104,6 @@ void LSPClientServer::readStdErr( const char* bytes, size_t n ) {
}
}
static std::vector<std::string> supportedSemanticTokenTypes() {
return { "namespace", "type", "class", "enum", "interface", "struct",
"typeParameter", "parameter", "variable", "property", "enumMember", "event",
"function", "method", "macro", "keyword", "modifier", "comment",
"string", "number", "regexp", "operator" };
}
void LSPClientServer::initialize() {
json codeAction{
{ "codeActionLiteralSupport", json{ { "codeActionKind", json{ { "valueSet", {} } } } } } };
json semanticTokens{
{ "requests", json{ { "range", true }, { "full", json{ { "delta", true } } } } },
{ "tokenTypes", supportedSemanticTokenTypes() },
{ "tokenModifiers", {} },
{ "formats", { "relative" } },
};
json capabilities{
{
"textDocument",
json{ { "documentSymbol", json{ { "hierarchicalDocumentSymbolSupport", true } } },
{ "publishDiagnostics", json{ { "relatedInformation", true } } },
{ "codeAction", codeAction },
{ "semanticTokens", semanticTokens },
{ "synchronization", json{ { "didSave", true } } },
{ "selectionRange", json{ { "dynamicRegistration", false } } },
{ "hover", json{ { "contentFormat", { "plaintext" } } } } },
},
{ "window", json{ { "workDoneProgress", true } } },
{ "general", json{ { "positionEncodings", json::array( { "utf-32" } ) } } } };
json params{ { "processId", Sys::getProcessID() },
{ "capabilities", capabilities },
{ "initializationOptions", {} } };
std::string rootPath = mRootPath;
if ( rootPath.empty() ) {
if ( !mManager->getLSPWorkspaceFolder().uri.empty() )
rootPath = mManager->getLSPWorkspaceFolder().uri.getPath();
else
rootPath = FileSystem::getCurrentWorkingDirectory();
}
std::string uriRootPath = "file://" + rootPath;
params["rootPath"] = rootPath;
params["rootUri"] = uriRootPath;
params["workspaceFolders"] =
toJson( { LSPWorkspaceFolder{ uriRootPath, FileSystem::fileNameFromPath( rootPath ) } } );
capabilities["workspace"] = json{ { "workspaceFolders", true }, { "configuration", false } };
write(
newRequest( "initialize", params ),
[&]( const json& resp ) {
#ifndef EE_DEBUG
try {
#endif
fromJson( mCapabilities, resp["capabilities"] );
#ifndef EE_DEBUG
} catch ( const json::exception& e ) {
Log::warning(
"LSPClientServer::initialize server %s error parsing capabilities: %s",
mLSP.name.c_str(), e.what() );
}
#endif
mReady = true;
write( newRequest( "initialized" ) );
sendQueuedMessages();
},
[&]( const json& ) {} );
}
void LSPClientServer::sendQueuedMessages() {
for ( const auto& msg : mQueuedMessages )
write( msg.msg, msg.h, msg.eh );
@@ -900,4 +1160,57 @@ LSPClientServer::didChangeWorkspaceFolders( const std::vector<LSPWorkspaceFolder
return send( newRequest( "workspace/didChangeWorkspaceFolders", params ) );
}
LSPClientServer::RequestHandle LSPClientServer::documentCodeAction(
const URI& document, const TextRange& range, const std::vector<std::string>& kinds,
std::vector<LSPDiagnostic> diagnostics, const JsonReplyHandler& h ) {
auto params = codeActionParams( document, range, kinds, std::move( diagnostics ) );
return send( newRequest( "textDocument/codeAction", params ), h );
}
LSPClientServer::RequestHandle LSPClientServer::documentCodeAction(
const URI& document, const TextRange& range, const std::vector<std::string>& kinds,
std::vector<LSPDiagnostic> diagnostics, const CodeActionHandler& h ) {
return documentCodeAction( document, range, kinds, diagnostics, [h]( const json& json ) {
if ( h )
h( parseCodeAction( json ) );
} );
}
LSPClientServer::RequestHandle LSPClientServer::documentHover( const URI& document,
const TextPosition& pos,
const JsonReplyHandler& h ) {
auto params = textDocumentPositionParams( document, pos );
return send( newRequest( "textDocument/hover", params ), h );
}
LSPClientServer::RequestHandle LSPClientServer::documentHover( const URI& document,
const TextPosition& pos,
const HoverHandler& h ) {
return documentHover( document, pos, [h]( const json& json ) {
if ( h )
h( parseHover( json ) );
} );
}
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 ) {
auto params = textDocumentPositionParams( document, pos );
return send( newRequest( "textDocument/completion", params ), h );
}
LSPClientServer::RequestHandle LSPClientServer::documentCompletion( const URI& document,
const TextPosition& pos,
const CompletionHandler& h ) {
return documentCompletion( document, pos, [h]( const json& json ) {
if ( h )
h( parseDocumentCompletion( json ) );
} );
}
} // namespace ecode

View File

@@ -28,6 +28,9 @@ class LSPClientServer {
template <typename T> using ReplyHandler = std::function<void( const T& )>;
using JsonReplyHandler = ReplyHandler<json>;
using CodeActionHandler = ReplyHandler<std::vector<LSPCodeAction>>;
using HoverHandler = ReplyHandler<LSPHover>;
using CompletionHandler = ReplyHandler<std::vector<LSPCompletionItem>>;
class RequestHandle {
friend class LSPClientServer;
@@ -67,6 +70,10 @@ class LSPClientServer {
LSPClientServer::RequestHandle documentSymbols( const URI& document, const JsonReplyHandler& h,
const JsonReplyHandler& eh );
LSPClientServer::RequestHandle
documentSymbols( const URI& document, const ReplyHandler<std::vector<LSPSymbolInformation>>& h,
const ReplyHandler<LSPResponseError>& eh );
LSPClientServer::RequestHandle didOpen( const URI& document, const std::string& text,
int version );
@@ -118,6 +125,30 @@ class LSPClientServer {
LSPClientServer::RequestHandle switchSourceHeader( const URI& document );
LSPClientServer::RequestHandle documentCodeAction( const URI& document, const TextRange& range,
const std::vector<std::string>& kinds,
std::vector<LSPDiagnostic> diagnostics,
const JsonReplyHandler& h );
LSPClientServer::RequestHandle documentCodeAction( const URI& document, const TextRange& range,
const std::vector<std::string>& kinds,
std::vector<LSPDiagnostic> diagnostics,
const CodeActionHandler& h );
LSPClientServer::RequestHandle documentHover( const URI& document, const TextPosition& pos,
const JsonReplyHandler& h );
LSPClientServer::RequestHandle documentHover( const URI& document, const TextPosition& pos,
const HoverHandler& h );
LSPClientServer::RequestHandle documentHover( TextDocument* doc, const HoverHandler& h );
LSPClientServer::RequestHandle documentCompletion( const URI& document, const TextPosition& pos,
const JsonReplyHandler& h );
LSPClientServer::RequestHandle documentCompletion( const URI& document, const TextPosition& pos,
const CompletionHandler& h );
protected:
LSPClientServerManager* mManager{ nullptr };
String::HashType mId;
@@ -128,6 +159,7 @@ class LSPClientServer {
std::map<TextDocument*, std::unique_ptr<LSPDocumentClient>> mClients;
std::map<int, std::pair<JsonReplyHandler, JsonReplyHandler>> mHandlers;
Mutex mClientsMutex;
Mutex mHandlersMutex;
bool mReady{ false };
struct QueueMessage {
json msg;

View File

@@ -78,6 +78,7 @@ void LSPClientServerManager::tryRunServer( const std::shared_ptr<TextDocument>&
auto rootPath = findRootPath( lsp, doc );
auto lspName = lsp.name.empty() ? lsp.command : lsp.name;
String::HashType id = String::hash( lspName + "|" + lsp.language + "|" + rootPath );
Lock l( mClientsMutex );
auto clientIt = mClients.find( id );
LSPClientServer* server = nullptr;
if ( clientIt == mClients.end() ) {
@@ -97,6 +98,7 @@ void LSPClientServerManager::tryRunServer( const std::shared_ptr<TextDocument>&
void LSPClientServerManager::closeLSPServer( const String::HashType& id ) {
mThreadPool->run( [this, id]() {
Lock l( mClientsMutex );
auto it = mClients.find( id );
if ( it != mClients.end() ) {
mClients.erase( it );
@@ -145,13 +147,15 @@ const std::shared_ptr<ThreadPool>& LSPClientServerManager::getThreadPool() const
}
void LSPClientServerManager::updateDirty() {
for ( auto& server : mClients ) {
server.second->updateDirty();
{
Lock l( mClientsMutex );
for ( auto& server : mClients ) {
server.second->updateDirty();
if ( !server.second->hasDocuments() )
mLSPsToClose.push_back( server.first );
if ( !server.second->hasDocuments() )
mLSPsToClose.push_back( server.first );
}
}
if ( !mLSPsToClose.empty() )
for ( const auto& server : mLSPsToClose )
closeLSPServer( server );
@@ -166,9 +170,9 @@ void LSPClientServerManager::getAndGoToLocation( const std::shared_ptr<TextDocum
void LSPClientServerManager::didChangeWorkspaceFolders( const std::string& folder ) {
mLSPWorkspaceFolder = { "file://" + folder, FileSystem::fileNameFromPath( folder ) };
for ( auto& server : mClients ) {
Lock l( mClientsMutex );
for ( auto& server : mClients )
server.second->didChangeWorkspaceFolders( { mLSPWorkspaceFolder }, {} );
}
}
const LSPWorkspaceFolder& LSPClientServerManager::getLSPWorkspaceFolder() const {
@@ -182,6 +186,7 @@ std::vector<LSPClientServer*> LSPClientServerManager::getLSPClientServers( UICod
std::vector<LSPClientServer*>
LSPClientServerManager::getLSPClientServers( const std::shared_ptr<TextDocument>& doc ) {
std::vector<LSPClientServer*> servers;
Lock l( mClientsMutex );
for ( auto& server : mClients ) {
if ( server.second->hasDocument( doc.get() ) )
servers.push_back( server.second.get() );
@@ -195,6 +200,7 @@ LSPClientServer* LSPClientServerManager::getOneLSPClientServer( UICodeEditor* ed
LSPClientServer*
LSPClientServerManager::getOneLSPClientServer( const std::shared_ptr<TextDocument>& doc ) {
Lock l( mClientsMutex );
for ( auto& server : mClients ) {
if ( server.second->hasDocument( doc.get() ) )
return server.second.get();

View File

@@ -56,6 +56,7 @@ class LSPClientServerManager {
std::vector<LSPDefinition> mLSPs;
std::vector<String::HashType> mLSPsToClose;
LSPWorkspaceFolder mLSPWorkspaceFolder;
Mutex mClientsMutex;
std::vector<LSPDefinition> supportsLSP( const std::shared_ptr<TextDocument>& doc );