mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-08-18 06:55:48 +03:00
More LSP implementation work.
This commit is contained in:
@@ -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 );
|
||||
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ class LinterPlugin : public UICodeEditorPlugin {
|
||||
|
||||
bool mReady{ false };
|
||||
bool mShuttingDown{ false };
|
||||
bool mHoveringMatch{ false };
|
||||
|
||||
LinterPlugin( const PluginManager* pluginManager );
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 );
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 );
|
||||
|
||||
|
||||
Reference in New Issue
Block a user