diff --git a/include/eepp/system/process.hpp b/include/eepp/system/process.hpp index 01021b531..9e351423f 100644 --- a/include/eepp/system/process.hpp +++ b/include/eepp/system/process.hpp @@ -195,7 +195,7 @@ class EE_API Process { void* mProcess{ nullptr }; bool mShuttingDown{ false }; bool mIsAsync{ false }; - ssize_t mBufferSize{ 131072 }; + size_t mBufferSize{ 131072 }; std::thread mStdOutThread; std::thread mStdErrThread; Mutex mStdInMutex; diff --git a/include/eepp/ui/tools/uicodeeditorsplitter.hpp b/include/eepp/ui/tools/uicodeeditorsplitter.hpp index 537dc882d..91700a9d1 100644 --- a/include/eepp/ui/tools/uicodeeditorsplitter.hpp +++ b/include/eepp/ui/tools/uicodeeditorsplitter.hpp @@ -111,9 +111,11 @@ class EE_API UICodeEditorSplitter { UITabWidget* createEditorWithTabWidget( Node* parent, bool openCurEditor = true ); - UITab* isDocumentOpen( const std::string& path, bool checkOnlyInCurrentTabWidget = false, + UITab* isDocumentOpen( std::string path, bool checkOnlyInCurrentTabWidget = false, bool checkOpeningDocuments = false ) const; + UICodeEditor* editorFromTab( UITab* tab ) const; + UICodeEditor* findEditorFromPath( const std::string& path ); void applyColorScheme( const SyntaxColorScheme& colorScheme ); @@ -246,6 +248,8 @@ class EE_API UICodeEditorSplitter { } ); } + UISceneNode* getUISceneNode() const; + protected: UISceneNode* mUISceneNode{ nullptr }; UICodeEditor* mCurEditor{ nullptr }; diff --git a/src/eepp/system/process.cpp b/src/eepp/system/process.cpp index 4e478ee10..4604501fe 100644 --- a/src/eepp/system/process.cpp +++ b/src/eepp/system/process.cpp @@ -180,7 +180,7 @@ void Process::startAsyncRead( ReadFn readStdOut, ReadFn readStdErr ) { mReadStdOutFn = readStdOut; mReadStdErrFn = readStdErr; #if EE_PLATFORM == EE_PLATFORM_WIN - // TODO: Implement WaitForMultipleObjectsEx + // TODO: Implement WaitForMultipleObjects void* stdOutFd = SUBPROCESS_PTR_CAST( void*, _get_osfhandle( _fileno( PROCESS_PTR->stdout_file ) ) ); void* stdErrFd = @@ -195,7 +195,7 @@ void Process::startAsyncRead( ReadFn readStdOut, ReadFn readStdErr ) { static_cast( mBufferSize ), &n, nullptr ); if ( !bSuccess || n == 0 ) break; - if ( n < mBufferSize - 1 ) + if ( n < static_cast( mBufferSize - 1 ) ) buffer[n] = '\0'; mReadStdOutFn( buffer.c_str(), static_cast( n ) ); } @@ -211,7 +211,7 @@ void Process::startAsyncRead( ReadFn readStdOut, ReadFn readStdErr ) { static_cast( mBufferSize ), &n, nullptr ); if ( !bSuccess || n == 0 ) break; - if ( n < mBufferSize - 1 ) + if ( n < static_cast( mBufferSize - 1 ) ) buffer[n] = '\0'; mReadStdErrFn( buffer.c_str(), static_cast( n ) ); } @@ -250,7 +250,7 @@ void Process::startAsyncRead( ReadFn readStdOut, ReadFn readStdErr ) { if ( pollfds[i].revents & POLLIN ) { const ssize_t n = read( pollfds[i].fd, &buffer[0], mBufferSize ); if ( n > 0 ) { - if ( n < mBufferSize - 1 ) + if ( n < static_cast( mBufferSize - 1 ) ) buffer[n] = '\0'; if ( fdIsStdOut[i] ) mReadStdOutFn( buffer.c_str(), static_cast( n ) ); diff --git a/src/eepp/ui/tools/uicodeeditorsplitter.cpp b/src/eepp/ui/tools/uicodeeditorsplitter.cpp index a7e588d6b..ab5ffa97d 100644 --- a/src/eepp/ui/tools/uicodeeditorsplitter.cpp +++ b/src/eepp/ui/tools/uicodeeditorsplitter.cpp @@ -499,9 +499,10 @@ UITabWidget* UICodeEditorSplitter::createEditorWithTabWidget( Node* parent, bool return tabWidget; } -UITab* UICodeEditorSplitter::isDocumentOpen( const std::string& path, - bool checkOnlyInCurrentTabWidget, +UITab* UICodeEditorSplitter::isDocumentOpen( std::string path, bool checkOnlyInCurrentTabWidget, bool checkOpeningDocuments ) const { + if ( String::startsWith( path, "file://" ) ) + path = path.substr( 7 ); if ( checkOnlyInCurrentTabWidget ) { UITabWidget* tabWidget = tabWidgetFromEditor( mCurEditor ); if ( nullptr == tabWidget ) @@ -536,6 +537,12 @@ UITab* UICodeEditorSplitter::isDocumentOpen( const std::string& path, return nullptr; } +UICodeEditor* UICodeEditorSplitter::editorFromTab( UITab* tab ) const { + return tab->getOwnedWidget()->isType( UI_TYPE_CODEEDITOR ) + ? tab->getOwnedWidget()->asType() + : nullptr; +} + UICodeEditor* UICodeEditorSplitter::findEditorFromPath( const std::string& path ) { UICodeEditor* editor = nullptr; forEachEditorStoppable( [&, path]( UICodeEditor* curEditor ) -> bool { @@ -1000,6 +1007,10 @@ size_t UICodeEditorSplitter::countEditorsOpeningDoc( const TextDocument& doc ) c return count; } +UISceneNode* UICodeEditorSplitter::getUISceneNode() const { + return mUISceneNode; +} + UICodeEditor* UICodeEditorSplitter::getCurEditor() const { eeASSERT( curEditorExists() ); return mCurEditor; diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index abd22548c..ccc3c6dc1 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -3930,6 +3930,7 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe mSplitter = UICodeEditorSplitter::New( this, mUISceneNode, colorSchemes, mInitColorScheme ); mSplitter->setHideTabBarOnSingleTab( mConfig.editor.hideTabBarOnSingleTab ); + mPluginManager->setSplitter( mSplitter ); Log::info( "Base UI took: %.2f ms", globalClock.getElapsedTime().asMilliseconds() ); diff --git a/src/tools/ecode/plugins/formatter/formatterplugin.hpp b/src/tools/ecode/plugins/formatter/formatterplugin.hpp index 2db1a7e6e..a521477fc 100644 --- a/src/tools/ecode/plugins/formatter/formatterplugin.hpp +++ b/src/tools/ecode/plugins/formatter/formatterplugin.hpp @@ -77,8 +77,7 @@ class FormatterPlugin : public UICodeEditorPlugin { mNativeFormatters; Int32 mWorkersCount{ 0 }; std::string mConfigPath; - /* cmd, shortcut */ - std::map mKeyBindings; + std::map mKeyBindings; /* cmd, shortcut */ bool mAutoFormatOnSave{ false }; bool mShuttingDown{ false }; diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index 65ddb995c..dc1018b25 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -14,14 +14,19 @@ UICodeEditorPlugin* LSPClientPlugin::New( const PluginManager* pluginManager ) { } LSPClientPlugin::LSPClientPlugin( const PluginManager* pluginManager ) : - mPool( pluginManager->getThreadPool() ) { + mManager( pluginManager ), mPool( pluginManager->getThreadPool() ) { mPool->run( [&, pluginManager] { load( pluginManager ); }, [] {} ); } LSPClientPlugin::~LSPClientPlugin() { mClosing = true; Lock l( mDocMutex ); - for ( const auto& editor : mEditors ) { + for ( auto editor : mEditors ) { + for ( auto& kb : mKeyBindings ) { + editor.first->getKeyBindings().removeCommandKeybind( kb.first ); + if ( editor.first->hasDocument() ) + editor.first->getDocument().removeCommand( kb.first ); + } editor.first->unregisterPlugin( this ); } } @@ -54,7 +59,7 @@ void LSPClientPlugin::load( const PluginManager* pluginManager ) { } } - mClientManager.load( pluginManager, std::move( lsps ) ); + mClientManager.load( this, pluginManager, std::move( lsps ) ); mReady = mClientManager.clientCount() > 0; if ( mReady ) @@ -72,6 +77,11 @@ void LSPClientPlugin::loadLSPConfig( std::vector& lsps, const std return; } + if ( mKeyBindings.empty() ) + mKeyBindings["follow-symbol-under-cursor"] = "f2"; + if ( j.contains( "keybindings" ) && j["keybindings"].contains( "follow-symbol-under-cursor" ) ) + mKeyBindings["follow-symbol-under-cursor"] = j["keybindings"]["follow-symbol-under-cursor"]; + if ( !j.contains( "servers" ) ) return; auto& servers = j["servers"]; @@ -154,6 +164,15 @@ void LSPClientPlugin::onRegister( UICodeEditor* editor ) { Lock l( mDocMutex ); mDocs.insert( editor->getDocumentRef().get() ); + for ( auto& kb : mKeyBindings ) { + editor->getKeyBindings().addKeybindString( kb.second, kb.first ); + } + + if ( editor->hasDocument() ) + editor->getDocument().setCommand( "follow-symbol-under-cursor", [&, editor]() { + mClientManager.followSymbolUnderCursor( editor->getDocumentRef().get() ); + } ); + std::vector listeners; listeners.push_back( @@ -169,6 +188,12 @@ void LSPClientPlugin::onRegister( UICodeEditor* editor ) { } void LSPClientPlugin::onUnregister( UICodeEditor* editor ) { + for ( auto& kb : mKeyBindings ) { + editor->getKeyBindings().removeCommandKeybind( kb.first ); + if ( editor->hasDocument() ) + editor->getDocument().removeCommand( kb.first ); + } + if ( mClosing ) return; Lock l( mDocMutex ); @@ -184,4 +209,8 @@ void LSPClientPlugin::onUnregister( UICodeEditor* editor ) { mDocs.erase( doc ); } +const PluginManager* LSPClientPlugin::getManager() const { + return mManager; +} + } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp index f451e169b..6a64788a3 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp @@ -47,7 +47,12 @@ class LSPClientPlugin : public UICodeEditorPlugin { void onUnregister( UICodeEditor* ); + const std::unordered_map& getEditorDocs() { return mEditorDocs; }; + + const PluginManager* getManager() const; + protected: + const PluginManager* mManager{ nullptr }; std::shared_ptr mPool; Clock mClock; Mutex mDocMutex; @@ -58,6 +63,7 @@ class LSPClientPlugin : public UICodeEditorPlugin { std::string mConfigPath; bool mClosing{ false }; bool mReady{ false }; + std::map mKeyBindings; /* cmd, shortcut */ LSPClientPlugin( const PluginManager* pluginManager ); diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.cpp b/src/tools/ecode/plugins/lsp/lspclientserver.cpp index 48f63060b..0f7b3042a 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.cpp @@ -17,18 +17,18 @@ static const char* MEMBER_URI = "uri"; static const char* MEMBER_VERSION = "version"; static const char* MEMBER_TEXT = "text"; static const char* MEMBER_LANGID = "languageId"; -// static const char* MEMBER_ERROR = "error"; +static const char* MEMBER_ERROR = "error"; // static const char* MEMBER_CODE = "code"; // static const char* MEMBER_MESSAGE = "message"; -// 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_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_LOCATION = "location"; -// static const char* MEMBER_RANGE = "range"; -// static const char* MEMBER_LINE = "line"; -// static const char* MEMBER_CHARACTER = "character"; +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"; @@ -38,9 +38,9 @@ static const char* MEMBER_LANGID = "languageId"; // 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"; -// static const char* MEMBER_TARGET_SELECTION_RANGE = "targetSelectionRange"; +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"; @@ -73,6 +73,15 @@ static json textDocumentParams( const json& m ) { static json textDocumentParams( const URI& document, int version = -1 ) { return textDocumentParams( versionedTextDocumentIdentifier( document, version ) ); } +static json to_json( const TextPosition& pos ) { + return json{ { MEMBER_LINE, pos.line() }, { MEMBER_CHARACTER, pos.column() } }; +} + +static json textDocumentPositionParams( const URI& document, TextPosition pos ) { + auto params = textDocumentParams( document ); + params[MEMBER_POSITION] = to_json( pos ); + return params; +} LSPClientServer::LSPClientServer( LSPClientServerManager* manager, const String::HashType& id, const LSPDefinition& lsp, const std::string& rootPath ) : @@ -85,11 +94,12 @@ LSPClientServer::~LSPClientServer() { } bool LSPClientServer::start() { - bool ret = mProcess.create( mLSP.command, Process::getDefaultOptions(), {}, mRootPath ); + bool ret = mProcess.create( + mLSP.command, Process::getDefaultOptions() | (Uint32)Process::Options::CombinedStdoutStderr, + {}, mRootPath ); if ( ret ) { - mProcess.startAsyncRead( - [this]( const char* bytes, size_t n ) { readStdOut( bytes, n ); }, - [this]( const char* bytes, size_t n ) { readStdErr( bytes, n ); } ); + mProcess.startAsyncRead( [this]( const char* bytes, size_t n ) { readStdOut( bytes, n ); }, + []( const char*, size_t ) {} ); initialize(); } @@ -224,6 +234,10 @@ void LSPClientServer::updateDirty() { } } +bool LSPClientServer::hasDocument( TextDocument* doc ) const { + return std::find( mDocs.begin(), mDocs.end(), doc ) != mDocs.end(); +} + LSPClientServer::RequestHandle LSPClientServer::didClose( const URI& document ) { auto params = textDocumentParams( document ); return send( newRequest( "textDocument/didClose", params ) ); @@ -258,12 +272,63 @@ LSPClientServer::RequestHandle LSPClientServer::documentSymbols( const URI& docu return send( newRequest( "textDocument/documentSymbol", params ), h, eh ); } +void LSPClientServer::processNotification( const json& msg ) { + auto method = msg[MEMBER_METHOD].get(); + if ( method == "textDocument/publishDiagnostics" ) { + // publishDiagnostics( msg ); + } else if ( method == ( "window/showMessage" ) ) { + // showMessage( msg ); + } else if ( method == ( "window/logMessage" ) ) { + // logMessage( msg ); + } else if ( method == ( "$/progress" ) ) { + // workDoneProgress( msg ); + } else { + Log::warning( "LSPClientServer::processNotification msg discarded: %s", + msg.dump( 2, ' ' ) ); + } +} +void LSPClientServer::processRequest( const json& /*msg*/ ) { + // auto method = msg[MEMBER_METHOD].get(); + // auto msgid = msg[MEMBER_ID]; + // auto params = msg[MEMBER_PARAMS]; + // bool handled = false; +} + void LSPClientServer::readStdOut( const char* bytes, size_t /*n*/ ) { const char* skipLength = strstr( bytes, "\r\n\r\n" ); if ( nullptr != skipLength ) { try { - auto j = json::parse( skipLength + 4 ); - Log::debug( "LSP Server %s said: \n%s", mLSP.name.c_str(), j.dump( 2, ' ' ).c_str() ); + auto res = json::parse( skipLength + 4 ); + Log::debug( "LSP Server %s said: \n%s", mLSP.name.c_str(), res.dump( 2, ' ' ).c_str() ); + + int msgid = -1; + if ( res.contains( MEMBER_ID ) ) { + msgid = res[MEMBER_ID].get(); + } else { + processNotification( res ); + return; + } + + if ( res.contains( MEMBER_METHOD ) ) { + processRequest( res ); + return; + } + + auto it = mHandlers.find( msgid ); + if ( it != mHandlers.end() ) { + const auto handler = *it; + mHandlers.erase( it ); + auto& h = handler.second.first; + auto& eh = handler.second.second; + if ( res.contains( MEMBER_ERROR ) && eh ) { + eh( res[MEMBER_ERROR] ); + } else { + h( res[MEMBER_RESULT] ); + } + } else { + Log::debug( "LSPClientServer::readStdOut unexpected reply id: %d", msgid ); + } + return; } catch ( const json::exception& e ) { Log::debug( "LSP Server %s said: Coudln't parse json err: %s", mLSP.name.c_str(), @@ -273,54 +338,45 @@ void LSPClientServer::readStdOut( const char* bytes, size_t /*n*/ ) { Log::debug( "LSP Server %s said: \n%s", mLSP.name.c_str(), bytes ); } -void LSPClientServer::readStdErr( const char* bytes, size_t /*n*/ ) { - Log::debug( "LSP Server %s err said: \n%s", mLSP.name.c_str(), bytes ); -} - static std::vector supportedSemanticTokenTypes() { - return { ( "namespace" ), ( "type" ), ( "class" ), ( "enum" ), - ( "interface" ), ( "struct" ), ( "typeParameter" ), ( "parameter" ), - ( "variable" ), ( "property" ), ( "enumMember" ), ( "event" ), - ( "function" ), ( "method" ), ( "macro" ), ( "keyword" ), - ( "modifier" ), ( "comment" ), ( "string" ), ( "number" ), - ( "regexp" ), ( "operator" ) }; + 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 codeAction{ + { "codeActionLiteralSupport", json{ { "codeActionKind", json{ { "valueSet", {} } } } } } }; json semanticTokens{ - { ( "requests" ), - json{ { ( "range" ), true }, { ( "full" ), json{ { ( "delta" ), true } } } } }, - { ( "tokenTypes" ), supportedSemanticTokenTypes() }, - { ( "tokenModifiers" ), {} }, - { ( "formats" ), { ( "relative" ) } }, + { "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" ), { ( "markdown" ), ( "plaintext" ) } } } } }, + "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", { "markdown", "plaintext" } } } } }, }, - { ( "window" ), json{ { ( "workDoneProgress" ), true } } } }; + { "window", json{ { "workDoneProgress", true } } } }; - json params{ { ( "processId" ), Sys::getProcessID() }, - { ( "rootPath" ), !mRootPath.empty() ? mRootPath : "" }, - { ( "rootUri" ), !mRootPath.empty() ? "file://" + mRootPath : "" }, - { ( "capabilities" ), capabilities }, - { ( "initializationOptions" ), {} } }; + json params{ { "processId", Sys::getProcessID() }, + { "rootPath", !mRootPath.empty() ? mRootPath : "" }, + { "rootUri", !mRootPath.empty() ? "file://" + mRootPath : "" }, + { "capabilities", capabilities }, + { "initializationOptions", {} } }; write( - newRequest( ( "initialize" ), params ), + newRequest( "initialize", params ), [&]( const json& ) { }, @@ -329,4 +385,80 @@ void LSPClientServer::initialize() { } ); } +static TextPosition parsePosition( const json& m ) { + auto line = m[MEMBER_LINE].get(); + auto column = m[MEMBER_CHARACTER].get(); + return { line, column }; +} + +static TextRange parseRange( const json& range ) { + auto startpos = parsePosition( range[MEMBER_START] ); + auto endpos = parsePosition( range[MEMBER_END] ); + return { startpos, endpos }; +} + +static LSPLocation parseLocation( const json& loc ) { + auto uri = URI( loc[MEMBER_URI].get() ); + auto range = parseRange( loc[MEMBER_RANGE] ); + return { uri, range }; +} + +static LSPLocation parseLocationLink( const json& loc ) { + auto uri = URI( loc[MEMBER_TARGET_URI].get() ); + auto vrange = loc[MEMBER_TARGET_SELECTION_RANGE]; + if ( vrange.is_null() ) + vrange = loc[MEMBER_TARGET_RANGE]; + auto range = parseRange( vrange ); + return { uri, range }; +} + +static std::vector parseDocumentLocation( const json& result ) { + std::vector ret; + if ( result.is_array() ) { + const auto& locs = result; + for ( const auto& def : locs ) { + const auto& ob = def; + ret.push_back( parseLocation( ob ) ); + if ( ret.back().uri.empty() ) + ret.back() = parseLocationLink( ob ); + } + } else if ( result.is_object() ) { + ret.push_back( parseLocation( result ) ); + } + return ret; +} + +void LSPClientServer::goToLocation( const json& res ) { + auto locs = parseDocumentLocation( res ); + if ( !locs.empty() ) + mManager->goToLocation( locs.front() ); +} + +LSPClientServer::RequestHandle LSPClientServer::getAndGoToLocation( const URI& document, + const TextPosition& pos, + const std::string& search ) { + auto params = textDocumentPositionParams( document, pos ); + return send( newRequest( search, params ), [this]( json res ) { goToLocation( res ); } ); +} + +LSPClientServer::RequestHandle LSPClientServer::documentDefinition( const URI& document, + const TextPosition& pos ) { + return getAndGoToLocation( document, pos, "textDocument/definition" ); +} + +LSPClientServer::RequestHandle LSPClientServer::documentDeclaration( const URI& document, + const TextPosition& pos ) { + return getAndGoToLocation( document, pos, "textDocument/declaration" ); +} + +LSPClientServer::RequestHandle LSPClientServer::documentTypeDefinition( const URI& document, + const TextPosition& pos ) { + return getAndGoToLocation( document, pos, "textDocument/typeDefinition" ); +} + +LSPClientServer::RequestHandle LSPClientServer::documentImplementation( const URI& document, + const TextPosition& pos ) { + return getAndGoToLocation( document, pos, "textDocument/implementation" ); +} + } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.hpp b/src/tools/ecode/plugins/lsp/lspclientserver.hpp index 52899a039..e3947a46c 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.hpp @@ -20,6 +20,11 @@ namespace ecode { class LSPClientServerManager; +struct LSPLocation { + URI uri; + TextRange range; +}; + class LSPClientServer { public: template using ReplyHandler = std::function; @@ -54,8 +59,8 @@ class LSPClientServer { LSPClientServer::RequestHandle cancel( int id ); - RequestHandle send( const json& msg, const GenericReplyHandler& h = nullptr, - const GenericReplyHandler& eh = nullptr ); + LSPClientServer::RequestHandle send( const json& msg, const GenericReplyHandler& h = nullptr, + const GenericReplyHandler& eh = nullptr ); const LSPDefinition& getDefinition() const { return mLSP; } @@ -81,8 +86,21 @@ class LSPClientServer { LSPClientServer::RequestHandle didChange( TextDocument* doc ); + LSPClientServer::RequestHandle documentDefinition( const URI& document, + const TextPosition& pos ); + + LSPClientServer::RequestHandle documentDeclaration( const URI& document, + const TextPosition& pos ); + + LSPClientServer::RequestHandle documentTypeDefinition( const URI& document, + const TextPosition& pos ); + + LSPClientServer::RequestHandle documentImplementation( const URI& document, + const TextPosition& pos ); + void updateDirty(); + bool hasDocument( TextDocument* doc ) const; protected: LSPClientServerManager* mManager{ nullptr }; String::HashType mId; @@ -98,12 +116,20 @@ class LSPClientServer { void readStdOut( const char* bytes, size_t n ); - void readStdErr( const char* bytes, size_t n ); - LSPClientServer::RequestHandle write( const json& msg, const GenericReplyHandler& h = nullptr, const GenericReplyHandler& eh = nullptr, const int id = 0 ); + void initialize(); + + void processNotification( const json& msg ); + + void processRequest( const json& msg ); + + void goToLocation( const json& res ); + + LSPClientServer::RequestHandle getAndGoToLocation( const URI& document, const TextPosition& pos, + const std::string& search ); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp index 01928dce0..67713cba3 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp @@ -1,4 +1,5 @@ #include "lspclientservermanager.hpp" +#include "lspclientplugin.hpp" #include #include #include @@ -7,9 +8,10 @@ namespace ecode { LSPClientServerManager::LSPClientServerManager() {} -void LSPClientServerManager::load( const PluginManager* pluginManager, +void LSPClientServerManager::load( LSPClientPlugin* plugin, const PluginManager* pluginManager, std::vector&& lsps ) { - mPool = pluginManager->getThreadPool(); + mPlugin = plugin; + mThreadPool = pluginManager->getThreadPool(); mLSPs = lsps; } @@ -97,8 +99,32 @@ void LSPClientServerManager::notifyClose( const String::HashType& id ) { } } +void LSPClientServerManager::goToLocation( const LSPLocation& loc ) { + UICodeEditorSplitter* splitter = mPlugin->getManager()->getSplitter(); + if ( nullptr == splitter ) + return; + splitter->getUISceneNode()->runOnMainThread( [this, splitter, loc]() { + UITab* tab = splitter->isDocumentOpen( loc.uri.toString() ); + if ( !tab ) { + std::string path( loc.uri.getPath() ); + FileInfo fileInfo( path ); + if ( fileInfo.exists() && fileInfo.isRegularFile() ) { + splitter->loadAsyncFileFromPathInNewTab( path, mThreadPool, + [loc]( UICodeEditor* editor, auto ) { + editor->goToLine( loc.range.start() ); + editor->setFocus(); + } ); + } + } else { + tab->getTabWidget()->setTabSelected( tab ); + splitter->editorFromTab( tab )->goToLine( loc.range.start() ); + splitter->editorFromTab( tab )->setFocus(); + } + } ); +} + void LSPClientServerManager::run( const std::shared_ptr& doc ) { - mPool->run( [&, doc]() { tryRunServer( doc ); }, []() {} ); + mThreadPool->run( [&, doc]() { tryRunServer( doc ); }, []() {} ); } size_t LSPClientServerManager::clientCount() const { @@ -106,7 +132,7 @@ size_t LSPClientServerManager::clientCount() const { } const std::shared_ptr& LSPClientServerManager::getThreadPool() const { - return mPool; + return mThreadPool; } void LSPClientServerManager::updateDirty() { @@ -114,4 +140,13 @@ void LSPClientServerManager::updateDirty() { server.second->updateDirty(); } +void LSPClientServerManager::followSymbolUnderCursor( TextDocument* doc ) { + for ( auto& server : mClients ) { + if ( server.second->hasDocument( doc ) ) { + server.second->documentDefinition( doc->getURI(), doc->getSelection().start() ); + return; + } + } +} + } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp index f3701f478..248669292 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp @@ -10,11 +10,14 @@ using namespace EE; namespace ecode { +class LSPClientPlugin; + class LSPClientServerManager { public: LSPClientServerManager(); - void load( const PluginManager* pluginManager, std::vector&& lsps ); + void load( LSPClientPlugin*, const PluginManager* pluginManager, + std::vector&& lsps ); void run( const std::shared_ptr& doc ); @@ -24,10 +27,13 @@ class LSPClientServerManager { void updateDirty(); + void followSymbolUnderCursor( TextDocument* doc ); + protected: friend class LSPClientServer; - std::shared_ptr mPool; + LSPClientPlugin* mPlugin{ nullptr }; + std::shared_ptr mThreadPool; std::map> mClients; std::vector mLSPs; @@ -42,6 +48,8 @@ class LSPClientServerManager { void tryRunServer( const std::shared_ptr& doc ); void notifyClose( const String::HashType& id ); + + void goToLocation( const LSPLocation& loc ); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/pluginmanager.cpp b/src/tools/ecode/plugins/pluginmanager.cpp index 60b549fe4..8ec0bb834 100644 --- a/src/tools/ecode/plugins/pluginmanager.cpp +++ b/src/tools/ecode/plugins/pluginmanager.cpp @@ -91,6 +91,14 @@ const PluginDefinition* PluginManager::getDefinitionIndex( const Int64& index ) return def; } +UICodeEditorSplitter* PluginManager::getSplitter() const { + return mSplitter; +} + +void PluginManager::setSplitter( UICodeEditorSplitter* splitter ) { + mSplitter = splitter; +} + bool PluginManager::hasDefinition( const std::string& id ) { return mDefinitions.find( id ) != mDefinitions.end(); } diff --git a/src/tools/ecode/plugins/pluginmanager.hpp b/src/tools/ecode/plugins/pluginmanager.hpp index 1c0c63ba7..f9dea5949 100644 --- a/src/tools/ecode/plugins/pluginmanager.hpp +++ b/src/tools/ecode/plugins/pluginmanager.hpp @@ -2,6 +2,7 @@ #define ECODE_PLUGINMANAGER_HPP #include +#include #include #include #include @@ -12,6 +13,7 @@ using namespace EE; using namespace EE::System; using namespace EE::UI; using namespace EE::UI::Models; +using namespace EE::UI::Tools; namespace ecode { @@ -93,15 +95,21 @@ class PluginManager { const PluginDefinition* getDefinitionIndex( const Int64& index ) const; + UICodeEditorSplitter* getSplitter() const; + protected: + friend class App; std::string mResourcesPath; std::string mPluginsPath; std::map mPlugins; std::map mPluginsEnabled; std::map mDefinitions; std::shared_ptr mThreadPool; + UICodeEditorSplitter* mSplitter{ nullptr }; bool hasDefinition( const std::string& id ); + + void setSplitter( UICodeEditorSplitter* splitter ); }; class PluginsModel : public Model {