From 51381a38dbb92cb080bcb35dd7cbae030c0fa2b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Mon, 13 Mar 2023 02:23:00 -0300 Subject: [PATCH] ecode: Added textDocument/rename support ("Rename Symbol Under Cursor"). --- include/eepp/ui/doc/syntaxhighlighter.hpp | 2 + include/eepp/ui/doc/syntaxtokenizer.hpp | 6 +++ include/eepp/ui/doc/textdocument.hpp | 8 ++++ src/eepp/ui/doc/syntaxhighlighter.cpp | 15 +++++++ src/eepp/ui/doc/textdocument.cpp | 21 ++++++++++ .../ecode/plugins/lsp/lspclientplugin.cpp | 39 +++++++++++++++++-- .../ecode/plugins/lsp/lspclientplugin.hpp | 2 + .../ecode/plugins/lsp/lspclientserver.cpp | 31 +++++++++++++-- .../ecode/plugins/lsp/lspclientserver.hpp | 7 ++++ .../plugins/lsp/lspclientservermanager.cpp | 11 ++++++ .../plugins/lsp/lspclientservermanager.hpp | 2 + 11 files changed, 137 insertions(+), 7 deletions(-) diff --git a/include/eepp/ui/doc/syntaxhighlighter.hpp b/include/eepp/ui/doc/syntaxhighlighter.hpp index 26ec47cba..91e26c8a7 100644 --- a/include/eepp/ui/doc/syntaxhighlighter.hpp +++ b/include/eepp/ui/doc/syntaxhighlighter.hpp @@ -36,6 +36,8 @@ class EE_API SyntaxHighlighter { std::string getTokenTypeAt( const TextPosition& pos ); + SyntaxTokenPosition getTokenPositionAt( const TextPosition& pos ); + protected: TextDocument* mDoc; std::map mLines; diff --git a/include/eepp/ui/doc/syntaxtokenizer.hpp b/include/eepp/ui/doc/syntaxtokenizer.hpp index 782abde91..75478fdc3 100644 --- a/include/eepp/ui/doc/syntaxtokenizer.hpp +++ b/include/eepp/ui/doc/syntaxtokenizer.hpp @@ -18,6 +18,12 @@ struct EE_API SyntaxToken { size_t len{ 0 }; }; +struct EE_API SyntaxTokenPosition { + std::string type; + Int64 pos{ 0 }; + size_t len{ 0 }; +}; + struct EE_API SyntaxTokenComplete { std::string type; std::string text; diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index 9340f65f5..32125fefd 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -547,6 +547,14 @@ class EE_API TextDocument { SyntaxHighlighter* getHighlighter() const; + TextRange getWordRangeInPosition( const TextPosition& pos ); + + TextRange getWordRangeInPosition(); + + String getWordInPosition( const TextPosition& pos ); + + String getWordInPosition(); + protected: friend class UndoStack; diff --git a/src/eepp/ui/doc/syntaxhighlighter.cpp b/src/eepp/ui/doc/syntaxhighlighter.cpp index e0bf9bbac..0fdd3f175 100644 --- a/src/eepp/ui/doc/syntaxhighlighter.cpp +++ b/src/eepp/ui/doc/syntaxhighlighter.cpp @@ -129,4 +129,19 @@ std::string SyntaxHighlighter::getTokenTypeAt( const TextPosition& pos ) { return "normal"; } +SyntaxTokenPosition SyntaxHighlighter::getTokenPositionAt( const TextPosition& pos ) { + if ( !pos.isValid() || pos.line() < 0 || pos.line() >= (Int64)mDoc->linesCount() ) + return {}; + const std::vector& tokens = getLine( pos.line() ); + if ( tokens.empty() ) + return {}; + Int64 col = 0; + for ( const auto& token : tokens ) { + col += token.len; + if ( col > pos.column() ) + return { token.type, static_cast( col - token.len ), token.len }; + } + return {}; +} + }}} // namespace EE::UI::Doc diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index fa9b89d06..b15721e83 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -1567,6 +1567,27 @@ void TextDocument::selectToNextWord() { mergeSelection(); } +TextRange TextDocument::getWordRangeInPosition( const TextPosition& pos ) { + if ( mHighlighter ) { + auto type( mHighlighter->getTokenPositionAt( pos ) ); + return { { pos.line(), type.pos }, { pos.line(), type.pos + (Int64)type.len } }; + } + + return { nextWordBoundary( pos, false ), previousWordBoundary( pos, false ) }; +} + +TextRange TextDocument::getWordRangeInPosition() { + return getWordRangeInPosition( getSelection().start() ); +} + +String TextDocument::getWordInPosition( const TextPosition& pos ) { + return getText( getWordRangeInPosition( pos ) ); +} + +String TextDocument::getWordInPosition() { + return getWordInPosition( getSelection().start() ); +} + void TextDocument::selectWord( bool withMulticursor ) { if ( !hasSelection() ) { setSelection( { nextWordBoundary( getSelection().start(), false ), diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index 34e54e1b2..d504f919a 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -722,14 +722,16 @@ void LSPClientPlugin::loadLSPConfig( std::vector& lsps, const std mKeyBindings["lsp-go-to-definition"] = "f2"; mKeyBindings["lsp-symbol-info"] = "f1"; mKeyBindings["lsp-symbol-code-action"] = "alt+return"; + mKeyBindings["lsp-rename-symbol-under-cursor"] = "ctrl+shift+r"; } if ( j.contains( "keybindings" ) ) { auto& kb = j["keybindings"]; - auto list = { - "lsp-go-to-definition", "lsp-go-to-declaration", "lsp-go-to-implementation", - "lsp-go-to-type-definition", "lsp-switch-header-source", "lsp-symbol-info", - "lsp-symbol-references", "lsp-memory-usage", "lsp-symbol-code-action" }; + auto list = { "lsp-go-to-definition", "lsp-go-to-declaration", + "lsp-go-to-implementation", "lsp-go-to-type-definition", + "lsp-switch-header-source", "lsp-symbol-info", + "lsp-symbol-references", "lsp-memory-usage", + "lsp-symbol-code-action", "lsp-rename-symbol-under-cursor" }; for ( const auto& key : list ) { if ( kb.contains( key ) ) { if ( !kb[key].empty() ) @@ -905,6 +907,29 @@ void LSPClientPlugin::codeAction( UICodeEditor* editor ) { } ); } +void LSPClientPlugin::renameSymbol( UICodeEditor* editor ) { + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::INPUT, editor->getUISceneNode()->i18n( + "new_symbol_under_cursor_name", + "New name (caution: not all references may be replaced)" ) ); + msgBox->setTitle( editor->getUISceneNode()->i18n( "rename", "Rename" ) ); + msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } ); + TextPosition pos = editor->getDocument().getSelection().start(); + msgBox->getTextInput()->setText( + editor->getDocument().getWordInPosition( editor->getDocument().getSelection().start() ) ); + msgBox->getTextInput()->getDocument().selectAll(); + msgBox->showWhenReady(); + msgBox->addEventListener( Event::OnConfirm, [this, pos, editor, msgBox]( const Event* ) { + String newName( msgBox->getTextInput()->getText() ); + mClientManager.renameSymbol( editor->getDocumentRef()->getURI(), pos, newName ); + msgBox->closeWindow(); + } ); + msgBox->addEventListener( Event::OnClose, [&]( const Event* ) { + if ( mManager->getSplitter() && mManager->getSplitter()->getCurWidget() ) + mManager->getSplitter()->getCurWidget()->setFocus(); + } ); +} + void LSPClientPlugin::onRegister( UICodeEditor* editor ) { Lock l( mDocMutex ); mDocs.insert( editor->getDocumentRef().get() ); @@ -921,6 +946,9 @@ void LSPClientPlugin::onRegister( UICodeEditor* editor ) { getAndGoToLocation( editor, "textDocument/definition" ); } ); + doc.setCommand( "lsp-rename-symbol-under-cursor", + [this, editor]() { renameSymbol( editor ); } ); + doc.setCommand( "lsp-go-to-declaration", [&, editor]() { getAndGoToLocation( editor, "textDocument/declaration" ); } ); @@ -1060,6 +1088,9 @@ bool LSPClientPlugin::onCreateContextMenu( UICodeEditor* editor, UIPopUpMenu* me if ( cap.implementationProvider ) addFn( "lsp-go-to-implementation", "Go To Implementation" ); + if ( cap.renameProvider ) + addFn( "lsp-rename-symbol-under-cursor", "Rename Symbol Under Cursor" ); + if ( cap.referencesProvider ) addFn( "lsp-symbol-references", "Find References to Symbol Under Cursor" ); diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp index fa173f3d8..42a3cf63f 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp @@ -171,6 +171,8 @@ class LSPClientPlugin : public UICodeEditorPlugin { LSPSymbolInformationList&& res ); void processDiagnosticsCodeAction( const PluginMessage& msg ); + + void renameSymbol( UICodeEditor* editor ); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.cpp b/src/tools/ecode/plugins/lsp/lspclientserver.cpp index db3e4a682..05baa8dcd 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.cpp @@ -508,9 +508,9 @@ static LSPWorkspaceEdit parseWorkSpaceEdit( const json& result ) { } } if ( result.contains( "documentChanges" ) ) { - auto& documentChanges = result.at( "documentChanges" ); + const auto& documentChanges = result.at( "documentChanges" ); // resourceOperations not supported for now - for ( auto& edit : documentChanges ) { + for ( const auto& edit : documentChanges ) { ret.documentChanges.push_back( parseTextDocumentEdit( edit ) ); } } @@ -520,7 +520,7 @@ static LSPWorkspaceEdit parseWorkSpaceEdit( const json& result ) { static LSPCommand parseCommand( const json& result ) { auto title = result.at( MEMBER_TITLE ).get(); auto command = result.at( MEMBER_COMMAND ).get(); - auto& args = result.at( MEMBER_ARGUMENTS ); + const auto& args = result.at( MEMBER_ARGUMENTS ); return { title, command, args }; } @@ -908,6 +908,13 @@ static json applyWorkspaceEditResponse( const PluginIDType& msgid, return j; } +static json renameParams( const URI& document, const TextPosition& pos, + const std::string& newName ) { + auto params = textDocumentPositionParams( document, pos ); + params["newName"] = newName; + return params; +} + void LSPClientServer::registerCapabilities( const json& jcap ) { if ( !jcap.is_object() || !jcap.contains( "registrations" ) || !jcap["registrations"].is_array() ) @@ -923,6 +930,9 @@ void LSPClientServer::registerCapabilities( const json& jcap ) { } else if ( reg["method"] == "textDocument/documentSymbol" ) { mCapabilities.documentSymbolProvider = true; registered = true; + } else if ( reg["method"] == "textDocument/rename" ) { + mCapabilities.renameProvider = true; + registered = true; } } } @@ -975,6 +985,7 @@ void LSPClientServer::initialize() { { "selectionRange", json{ { "dynamicRegistration", false } } }, { "hover", json{ { "contentFormat", { "markdown", "plaintext" } } } }, { "completion", completionItem }, + { "rename", json{ { "dynamicRegistration", true } } }, } }, { "window", json{ { "workDoneProgress", true }, { "showMessage", showMessage }, @@ -1812,6 +1823,20 @@ LSPClientServer::documentFormatting( const URI& document, const json& options, } ); } +void LSPClientServer::documentRename( const URI& document, const TextPosition& pos, + const std::string& newName, const JsonReplyHandler& h ) { + auto params = renameParams( document, pos, newName ); + sendAsync( newRequest( "textDocument/rename", params ), h ); +} + +void LSPClientServer::documentRename( const URI& document, const TextPosition& pos, + const std::string& newName, const WorkspaceEditHandler& h ) { + documentRename( document, pos, newName, [h]( const IdType& id, const json& json ) { + if ( h ) + h( id, parseWorkSpaceEdit( json ) ); + } ); +} + void LSPClientServer::memoryUsage( const JsonReplyHandler& h ) { return sendAsync( newRequest( "$/memoryUsage" ), h ); } diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.hpp b/src/tools/ecode/plugins/lsp/lspclientserver.hpp index b6a853ebb..a225c538e 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.hpp @@ -43,6 +43,7 @@ class LSPClientServer { using SignatureHelpHandler = ReplyHandler; using LocationHandler = ReplyHandler>; using TextEditArrayHandler = ReplyHandler>; + using WorkspaceEditHandler = ReplyHandler; class LSPRequestHandle : public PluginRequestHandle { public: @@ -196,6 +197,12 @@ class LSPClientServer { LSPRequestHandle documentFormatting( const URI& document, const json& options, const TextEditArrayHandler& h ); + void documentRename( const URI& document, const TextPosition& pos, + const std::string& newName, const JsonReplyHandler& h ); + + void documentRename( const URI& document, const TextPosition& pos, + const std::string& newName, const WorkspaceEditHandler& h ); + void memoryUsage( const JsonReplyHandler& h ); void memoryUsage(); diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp index 2534611c0..8e9fc4804 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp @@ -186,6 +186,17 @@ void LSPClientServerManager::applyWorkspaceEdit( } ); } +void LSPClientServerManager::renameSymbol( const URI& uri, const TextPosition& pos, + const std::string& newName ) { + auto* server = getOneLSPClientServer( uri ); + if ( !server ) + return; + server->documentRename( uri, pos, newName, + [this]( const PluginIDType&, const LSPWorkspaceEdit& edit ) { + applyWorkspaceEdit( edit, []( const auto& ) {} ); + } ); +} + void LSPClientServerManager::run( const std::shared_ptr& doc ) { mThreadPool->run( [&, doc]() { tryRunServer( doc ); } ); } diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp index 1babff8e4..dcdee192b 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp @@ -92,6 +92,8 @@ class LSPClientServerManager { const LSPWorkspaceEdit& edit, const std::function& resCb ); + void renameSymbol( const URI& uri, const TextPosition& pos, const std::string& newName ); + protected: friend class LSPClientServer; PluginManager* mPluginManager{ nullptr };