diff --git a/bin/assets/plugins/lspclient.json b/bin/assets/plugins/lspclient.json new file mode 100644 index 000000000..fc5746090 --- /dev/null +++ b/bin/assets/plugins/lspclient.json @@ -0,0 +1,82 @@ +{ + "config": { + }, + "servers": [ + { + "language": "typescript", + "name": "typescript-language-server", + "url": "https://github.com/theia-ide/typescript-language-server", + "command": "typescript-language-server --stdio", + "file_patterns": ["%.ts$"], + "rootIndicationFileNames": ["package.json", "package-lock.json"] + }, + { + "language": "javascript", + "use": "typescript-language-server", + "file_patterns": ["%.js$"] + }, + { + "language": "go", + "name": "gopls", + "url": "golang.org/x/tools/gopls", + "command": "gopls", + "file_patterns": ["%.go$"], + "rootIndicationFileNames": ["go.mod"] + }, + { + "language": "php", + "name": "intelephense", + "url": "https://intelephense.com/", + "command": "intelephense --stdio", + "file_patterns": ["%.php$", "%.php%d$"] + }, + { + "language": "c", + "name": "clangd", + "url": "https://clang.llvm.org/extra/clangd/", + "command": "clangd -log=error --background-index --limit-results=500 --completion-style=bundled", + "file_patterns": ["%.c$", "%.h$", "%.C$", "%.H$", "%.objc$"] + }, + { + "language": "cpp", + "use": "clangd", + "file_patterns": ["%.inl$", "%.cpp$", "%.hpp$", "%.cc$", "%.cxx$", "%.c++$", "%.hh$", "%.hxx$", "%.h++$", "%.objcpp$"] + }, + { + "language": "d", + "name": "serve-d", + "url": "https://github.com/Pure-D/serve-d", + "command": "serve-d", + "file_patterns": ["%.d$"], + "rootIndicationFileNames": ["dub.json"] + }, + { + "language": "zig", + "name": "zls", + "url": "https://github.com/zigtools/zls", + "command": "zls", + "file_patterns": ["%.zig$"] + }, + { + "language": "python", + "name": "pylsp", + "url": "https://github.com/python-lsp/python-lsp-server", + "command": "pylsp --check-parent-process", + "file_patterns": ["%.py$"] + }, + { + "language": "rust", + "name": "rust-language-server", + "url": "https://github.com/rust-lang/rls", + "command": "rls", + "file_patterns": ["%.rs$"] + }, + { + "language": "lua", + "name": "lua-language-server", + "url": "https://github.com/sumneko/lua-language-server", + "command": "lua-language-server", + "file_patterns": ["%.lua"] + } + ] +} diff --git a/include/eepp/system/sys.hpp b/include/eepp/system/sys.hpp index 76aa7259a..9278db21a 100644 --- a/include/eepp/system/sys.hpp +++ b/include/eepp/system/sys.hpp @@ -8,7 +8,10 @@ namespace EE { namespace System { class EE_API Sys { public: - /** Returns the current date time */ + /** @return The current process id */ + static Uint64 getProcessID(); + + /** @return the current date time */ static std::string getDateTimeStr(); /** Converts any epoch timestmap to a formatted string. */ diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index 652fd1746..a19f073a7 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,7 @@ class EE_API TextDocument { class EE_API Client { public: virtual ~Client(); + virtual void onDocumentLoaded( TextDocument* ) {}; virtual void onDocumentTextChanged() = 0; virtual void onDocumentUndoRedo( const UndoRedo& eventType ) = 0; virtual void onDocumentCursorChange( const TextPosition& ) = 0; @@ -290,7 +292,7 @@ class EE_API TextDocument { void setCommands( const std::map& cmds ); - void setCommand( const std::string& command, DocumentCommand func ); + void setCommand( const std::string& command, const DocumentCommand& func ); bool hasCommand( const std::string& command ); @@ -347,6 +349,8 @@ class EE_API TextDocument { const std::string& getFilePath() const; + URI getURI() const; + const FileInfo& getFileInfo() const; bool isDirty() const; @@ -469,6 +473,8 @@ class EE_API TextDocument { void cleanChangeId(); + void notifyDocumentLoaded(); + void notifyTextChanged(); void notifyCursorChanged(); diff --git a/include/eepp/ui/tools/uicodeeditorsplitter.hpp b/include/eepp/ui/tools/uicodeeditorsplitter.hpp index 7f672c5e3..537dc882d 100644 --- a/include/eepp/ui/tools/uicodeeditorsplitter.hpp +++ b/include/eepp/ui/tools/uicodeeditorsplitter.hpp @@ -111,8 +111,7 @@ class EE_API UICodeEditorSplitter { UITabWidget* createEditorWithTabWidget( Node* parent, bool openCurEditor = true ); - UITab* isDocumentOpen( const std::string& path, - bool checkOnlyInCurrentTabWidget = false, + UITab* isDocumentOpen( const std::string& path, bool checkOnlyInCurrentTabWidget = false, bool checkOpeningDocuments = false ) const; UICodeEditor* findEditorFromPath( const std::string& path ); @@ -187,6 +186,66 @@ class EE_API UICodeEditorSplitter { size_t countEditorsOpeningDoc( const TextDocument& doc ) const; + // T must implement setCommand( const std::string& command, const std::function& func ) + template void registerSplitterCommands( T& t ) { + t.setCommand( "switch-to-previous-split", [&] { switchPreviousSplit( mCurWidget ); } ); + t.setCommand( "switch-to-next-split", [&] { switchNextSplit( mCurWidget ); } ); + t.setCommand( "close-tab", [&] { tryTabClose( mCurWidget ); } ); + t.setCommand( "create-new", [&] { + auto d = createCodeEditorInTabWidget( tabWidgetFromWidget( mCurWidget ) ); + d.first->getTabWidget()->setTabSelected( d.first ); + } ); + t.setCommand( "next-tab", [&] { + UITabWidget* tabWidget = tabWidgetFromWidget( mCurWidget ); + if ( tabWidget && tabWidget->getTabCount() > 1 ) { + UITab* tab = (UITab*)mCurWidget->getData(); + Uint32 tabIndex = tabWidget->getTabIndex( tab ); + switchToTab( ( tabIndex + 1 ) % tabWidget->getTabCount() ); + } + } ); + t.setCommand( "previous-tab", [&] { + UITabWidget* tabWidget = tabWidgetFromWidget( mCurWidget ); + if ( tabWidget && tabWidget->getTabCount() > 1 ) { + UITab* tab = (UITab*)mCurWidget->getData(); + Uint32 tabIndex = tabWidget->getTabIndex( tab ); + Int32 newTabIndex = (Int32)tabIndex - 1; + switchToTab( newTabIndex < 0 ? tabWidget->getTabCount() - newTabIndex + : newTabIndex ); + } + } ); + for ( int i = 1; i <= 10; i++ ) + t.setCommand( String::format( "switch-to-tab-%d", i ), + [&, i] { switchToTab( i - 1 ); } ); + t.setCommand( "switch-to-first-tab", [&] { + UITabWidget* tabWidget = tabWidgetFromWidget( mCurWidget ); + if ( tabWidget && tabWidget->getTabCount() ) { + switchToTab( 0 ); + } + } ); + t.setCommand( "switch-to-last-tab", [&] { + UITabWidget* tabWidget = tabWidgetFromWidget( mCurWidget ); + if ( tabWidget && tabWidget->getTabCount() ) { + switchToTab( tabWidget->getTabCount() - 1 ); + } + } ); + t.setCommand( "split-right", [&] { + split( SplitDirection::Right, mCurWidget, curEditorExistsAndFocused() ); + } ); + t.setCommand( "split-bottom", [&] { + split( SplitDirection::Bottom, mCurWidget, curEditorExistsAndFocused() ); + } ); + t.setCommand( "split-left", [&] { + split( SplitDirection::Left, mCurWidget, curEditorExistsAndFocused() ); + } ); + t.setCommand( "split-top", [&] { + split( SplitDirection::Top, mCurWidget, curEditorExistsAndFocused() ); + } ); + t.setCommand( "split-swap", [&] { + if ( UISplitter* splitter = splitterFromWidget( mCurWidget ) ) + splitter->swap(); + } ); + } + protected: UISceneNode* mUISceneNode{ nullptr }; UICodeEditor* mCurEditor{ nullptr }; diff --git a/include/eepp/ui/uirelativelayout.hpp b/include/eepp/ui/uirelativelayout.hpp index 9a0c26ebc..9dee81127 100644 --- a/include/eepp/ui/uirelativelayout.hpp +++ b/include/eepp/ui/uirelativelayout.hpp @@ -9,8 +9,6 @@ class EE_API UIRelativeLayout : public UILayout { public: static UIRelativeLayout* New(); - UIRelativeLayout(); - virtual Uint32 getType() const; virtual bool isType( const Uint32& type ) const; @@ -20,6 +18,10 @@ class EE_API UIRelativeLayout : public UILayout { virtual void updateLayout(); protected: + UIRelativeLayout( const std::string& tagName ); + + UIRelativeLayout(); + virtual Uint32 onMessage( const NodeMessage* Msg ); void fixChildPos( UIWidget* widget ); diff --git a/include/eepp/ui/widgetcommandexecuter.hpp b/include/eepp/ui/widgetcommandexecuter.hpp index 6f73b24aa..2acba6f69 100644 --- a/include/eepp/ui/widgetcommandexecuter.hpp +++ b/include/eepp/ui/widgetcommandexecuter.hpp @@ -16,7 +16,11 @@ class EE_API WidgetCommandExecuter { WidgetCommandExecuter( const KeyBindings& keybindings ) : mKeyBindings( keybindings ) {} - void addCommand( const std::string& name, const CommandCallback& cb ) { mCommands[name] = cb; } + void setCommand( const std::string& name, const CommandCallback& cb ) { mCommands[name] = cb; } + + bool hasCommand( const std::string& name ) const { + return mCommands.find( name ) != mCommands.end(); + } void execute( const std::string& command ) { auto cmdIt = mCommands.find( command ); @@ -24,6 +28,8 @@ class EE_API WidgetCommandExecuter { cmdIt->second(); } + size_t commandCount() const { return mCommands.size(); } + KeyBindings& getKeyBindings() { return mKeyBindings; } protected: diff --git a/projects/linux/ee.files b/projects/linux/ee.files index d2969a823..24cecf98d 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -10,6 +10,7 @@ ../../bin/assets/layouts/test.xml ../../bin/assets/layouts/test2.xml ../../bin/assets/layouts/test_widgets.xml +../../bin/assets/plugins/lspclient.json ../../bin/assets/ui/breeze.css ../../bin/assets/ui/uitheme.css ../../docs/articles/cssspecification.md @@ -1166,12 +1167,15 @@ ../../src/tools/ecode/notificationcenter.hpp ../../src/tools/ecode/plugins/linter/linterplugin.cpp ../../src/tools/ecode/plugins/linter/linterplugin.hpp -../../src/tools/ecode/plugins/lsp/lspclient.cpp -../../src/tools/ecode/plugins/lsp/lspclient.hpp -../../src/tools/ecode/plugins/lsp/lspclientmanager.cpp -../../src/tools/ecode/plugins/lsp/lspclientmanager.hpp -../../src/tools/ecode/plugins/lsp/lspplugin.cpp -../../src/tools/ecode/plugins/lsp/lspplugin.hpp +../../src/tools/ecode/plugins/lsp/lspclientplugin.cpp +../../src/tools/ecode/plugins/lsp/lspclientplugin.hpp +../../src/tools/ecode/plugins/lsp/lspclientserver.cpp +../../src/tools/ecode/plugins/lsp/lspclientserver.hpp +../../src/tools/ecode/plugins/lsp/lspclientservermanager.cpp +../../src/tools/ecode/plugins/lsp/lspclientservermanager.hpp +../../src/tools/ecode/plugins/lsp/lspdefinition.hpp +../../src/tools/ecode/plugins/lsp/lspdocumentclient.cpp +../../src/tools/ecode/plugins/lsp/lspdocumentclient.hpp ../../src/tools/ecode/plugins/pluginmanager.cpp ../../src/tools/ecode/plugins/pluginmanager.hpp ../../src/tools/ecode/projectdirectorytree.cpp diff --git a/src/eepp/system/process.cpp b/src/eepp/system/process.cpp index ba77fe637..8951b1ce7 100644 --- a/src/eepp/system/process.cpp +++ b/src/eepp/system/process.cpp @@ -30,6 +30,7 @@ Process::Process( const std::string& command, const Uint32& options, } Process::~Process() { + mShuttingDown = true; if ( mStdOutThread.joinable() ) mStdOutThread.join(); if ( mStdErrThread.joinable() ) @@ -233,30 +234,31 @@ void Process::startAsyncRead( ReadFn readStdOut, ReadFn readStdErr ) { } auto buffer = std::unique_ptr( new char[mBufferSize] ); bool anyOpen = !pollfds.empty(); - while ( anyOpen && !mShuttingDown && - ( poll( pollfds.data(), static_cast( pollfds.size() ), -1 ) > 0 || - errno == EINTR ) ) { - anyOpen = false; - for ( size_t i = 0; i < pollfds.size(); ++i ) { - if ( pollfds[i].fd >= 0 ) { - if ( pollfds[i].revents & POLLIN ) { - const ssize_t n = read( pollfds[i].fd, buffer.get(), mBufferSize ); - if ( n > 0 ) { - if ( fdIsStdOut[i] ) - mReadStdOutFn( buffer.get(), static_cast( n ) ); - else - mReadStdErrFn( buffer.get(), static_cast( n ) ); - } else if ( n < 0 && errno != EINTR && errno != EAGAIN && - errno != EWOULDBLOCK ) { + while ( anyOpen && !mShuttingDown && errno != EINTR ) { + int res = poll( pollfds.data(), static_cast( pollfds.size() ), 100 ); + if ( res > 0 ) { + anyOpen = false; + for ( size_t i = 0; i < pollfds.size(); ++i ) { + if ( pollfds[i].fd >= 0 ) { + if ( pollfds[i].revents & POLLIN ) { + const ssize_t n = read( pollfds[i].fd, buffer.get(), mBufferSize ); + if ( n > 0 ) { + if ( fdIsStdOut[i] ) + mReadStdOutFn( buffer.get(), static_cast( n ) ); + else + mReadStdErrFn( buffer.get(), static_cast( n ) ); + } else if ( n < 0 && errno != EINTR && errno != EAGAIN && + errno != EWOULDBLOCK ) { + pollfds[i].fd = -1; + continue; + } + } + if ( pollfds[i].revents & ( POLLERR | POLLHUP | POLLNVAL ) ) { pollfds[i].fd = -1; continue; } + anyOpen = true; } - if ( pollfds[i].revents & ( POLLERR | POLLHUP | POLLNVAL ) ) { - pollfds[i].fd = -1; - continue; - } - anyOpen = true; } } } diff --git a/src/eepp/system/sys.cpp b/src/eepp/system/sys.cpp index ab97763a0..858b62286 100644 --- a/src/eepp/system/sys.cpp +++ b/src/eepp/system/sys.cpp @@ -594,6 +594,14 @@ double Sys::getSystemTime() { #endif } +Uint64 Sys::getProcessID() { +#if EE_PLATFORM == EE_PLATFORM_WIN + return GetCurrentProcessId(); +#else + return getpid(); +#endif +} + std::string Sys::getDateTimeStr() { time_t rawtime; time( &rawtime ); diff --git a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp index 86afd918a..9aab6afca 100644 --- a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp +++ b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp @@ -448,7 +448,7 @@ void SyntaxDefinitionManager::addMarkdown() { void SyntaxDefinitionManager::addC() { add( { "C", - { "%.c$", "%.h$", "%.icc" }, + { "%.c$", "%.C", "%.h$", "%.icc" }, { { { "//.-\n" }, "comment" }, { { "/%*", "%*/" }, "comment" }, @@ -656,8 +656,7 @@ void SyntaxDefinitionManager::addPython() { "#", { "^#!.*[ /]python", "^#!.*[ /]python3" } } ); } - -void SyntaxDefinitionManager::addBash() { + void SyntaxDefinitionManager::addBash() { add( { "Bash", { "%.sh$", "%.bash$", "%.bashrc$", "%.bash_profile$" }, { @@ -688,7 +687,7 @@ void SyntaxDefinitionManager::addBash() { void SyntaxDefinitionManager::addCPP() { add( { "C++", - { "%.cpp$", "%.cc$", "%.C$", "%.cxx$", "%.c++$", "%.hh$", "%.inl$", "%.hxx$", "%.hpp$", + { "%.cpp$", "%.cc$", "%.cxx$", "%.c++$", "%.hh$", "%.inl$", "%.hxx$", "%.hpp$", "%.h++$" }, { { { "R%\"xml%(", "%)xml%\"" }, "function", "XML" }, diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index 6eeba8830..bd2598f52 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -335,6 +335,7 @@ TextDocument::LoadStatus TextDocument::loadFromFile( const std::string& path ) { : FileInfo( mFilePath ); resetSyntax(); mLoading = false; + notifyDocumentLoaded(); return ret; } @@ -399,6 +400,7 @@ TextDocument::LoadStatus TextDocument::loadFromURL( const std::string& url, } mLoading = false; + notifyDocumentLoaded(); return LoadStatus::Failed; } @@ -428,6 +430,7 @@ bool TextDocument::loadAsyncFromURL( const std::string& url, onLoaded( this, false ); } mLoading = false; + notifyDocumentLoaded(); }, uri, Seconds( 10 ), progressCallback, headers, "", true, Http::getEnvProxyURI() ); return true; @@ -1466,6 +1469,10 @@ const std::string& TextDocument::getFilePath() const { return mFilePath; } +URI TextDocument::getURI() const { + return URI( "file://" + getFilePath() ); +} + const FileInfo& TextDocument::getFileInfo() const { return mFileRealPath; } @@ -1485,7 +1492,8 @@ void TextDocument::setCommands( const std::map& cm mCommands.insert( cmds.begin(), cmds.end() ); } -void TextDocument::setCommand( const std::string& command, TextDocument::DocumentCommand func ) { +void TextDocument::setCommand( const std::string& command, + const TextDocument::DocumentCommand& func ) { mCommands[command] = func; } @@ -1957,6 +1965,12 @@ void TextDocument::notifySelectionChanged() { } } +void TextDocument::notifyDocumentLoaded() { + for ( auto& client : mClients ) { + client->onDocumentLoaded( this ); + } +} + void TextDocument::notifyDocumentSaved() { for ( auto& client : mClients ) { client->onDocumentSaved( this ); diff --git a/src/eepp/ui/tools/uicodeeditorsplitter.cpp b/src/eepp/ui/tools/uicodeeditorsplitter.cpp index 451f860e8..a7e588d6b 100644 --- a/src/eepp/ui/tools/uicodeeditorsplitter.cpp +++ b/src/eepp/ui/tools/uicodeeditorsplitter.cpp @@ -130,7 +130,7 @@ UISplitter* UICodeEditorSplitter::splitterFromWidget( UIWidget* widget ) const { UICodeEditor* UICodeEditorSplitter::createCodeEditor() { UICodeEditor* editor = UICodeEditor::NewOpt( true, true ); TextDocument& doc = editor->getDocument(); - /* global commands */ + /* document commands */ doc.setCommand( "move-to-previous-line", [&] { if ( mCurEditor ) mCurEditor->moveToPreviousLine(); @@ -198,8 +198,9 @@ UICodeEditor* UICodeEditorSplitter::createCodeEditor() { } ); editor->addUnlockedCommand( "copy" ); editor->addUnlockedCommand( "select-all" ); - /* global commands */ + /* document commands */ + /* editor commands */ doc.setCommand( "switch-to-previous-colorscheme", [&] { auto it = mColorSchemes.find( mCurrentColorScheme ); auto prev = std::prev( it, 1 ); @@ -209,6 +210,7 @@ UICodeEditor* UICodeEditorSplitter::createCodeEditor() { setColorScheme( mColorSchemes.rbegin()->first ); } } ); + doc.setCommand( "switch-to-next-colorscheme", [&] { auto it = mColorSchemes.find( mCurrentColorScheme ); if ( ++it != mColorSchemes.end() ) @@ -217,64 +219,6 @@ UICodeEditor* UICodeEditorSplitter::createCodeEditor() { mCurrentColorScheme = mColorSchemes.begin()->first; applyColorScheme( mColorSchemes[mCurrentColorScheme] ); } ); - - /* Splitter commands */ - doc.setCommand( "switch-to-previous-split", [&] { switchPreviousSplit( mCurWidget ); } ); - doc.setCommand( "switch-to-next-split", [&] { switchNextSplit( mCurWidget ); } ); - doc.setCommand( "close-tab", [&] { tryTabClose( mCurWidget ); } ); - doc.setCommand( "create-new", [&] { - auto d = createCodeEditorInTabWidget( tabWidgetFromWidget( mCurWidget ) ); - d.first->getTabWidget()->setTabSelected( d.first ); - } ); - doc.setCommand( "next-tab", [&] { - UITabWidget* tabWidget = tabWidgetFromWidget( mCurWidget ); - if ( tabWidget && tabWidget->getTabCount() > 1 ) { - UITab* tab = (UITab*)mCurWidget->getData(); - Uint32 tabIndex = tabWidget->getTabIndex( tab ); - switchToTab( ( tabIndex + 1 ) % tabWidget->getTabCount() ); - } - } ); - doc.setCommand( "previous-tab", [&] { - UITabWidget* tabWidget = tabWidgetFromWidget( mCurWidget ); - if ( tabWidget && tabWidget->getTabCount() > 1 ) { - UITab* tab = (UITab*)mCurWidget->getData(); - Uint32 tabIndex = tabWidget->getTabIndex( tab ); - Int32 newTabIndex = (Int32)tabIndex - 1; - switchToTab( newTabIndex < 0 ? tabWidget->getTabCount() - newTabIndex : newTabIndex ); - } - } ); - for ( int i = 1; i <= 10; i++ ) - doc.setCommand( String::format( "switch-to-tab-%d", i ), [&, i] { switchToTab( i - 1 ); } ); - doc.setCommand( "switch-to-first-tab", [&] { - UITabWidget* tabWidget = tabWidgetFromWidget( mCurWidget ); - if ( tabWidget && tabWidget->getTabCount() ) { - switchToTab( 0 ); - } - } ); - doc.setCommand( "switch-to-last-tab", [&] { - UITabWidget* tabWidget = tabWidgetFromWidget( mCurWidget ); - if ( tabWidget && tabWidget->getTabCount() ) { - switchToTab( tabWidget->getTabCount() - 1 ); - } - } ); - doc.setCommand( "split-right", [&] { - split( SplitDirection::Right, mCurWidget, curEditorExistsAndFocused() ); - } ); - doc.setCommand( "split-bottom", [&] { - split( SplitDirection::Bottom, mCurWidget, curEditorExistsAndFocused() ); - } ); - doc.setCommand( "split-left", [&] { - split( SplitDirection::Left, mCurWidget, curEditorExistsAndFocused() ); - } ); - doc.setCommand( "split-top", [&] { - split( SplitDirection::Top, mCurWidget, curEditorExistsAndFocused() ); - } ); - doc.setCommand( "split-swap", [&] { - if ( UISplitter* splitter = splitterFromWidget( mCurWidget ) ) - splitter->swap(); - } ); - /* Splitter commands */ - doc.setCommand( "open-containing-folder", [&] { if ( mCurEditor ) mCurEditor->openContainingFolder(); @@ -283,6 +227,12 @@ UICodeEditor* UICodeEditorSplitter::createCodeEditor() { if ( mCurEditor ) mCurEditor->copyFilePath(); } ); + /* editor commands */ + + /* Splitter commands */ + registerSplitterCommands( doc ); + /* Splitter commands */ + editor->addEventListener( Event::OnFocus, [&]( const Event* event ) { setCurrentWidget( event->getNode()->asType() ); } ); diff --git a/src/eepp/ui/tools/uidocfindreplace.cpp b/src/eepp/ui/tools/uidocfindreplace.cpp index 597e9bb0f..5d7eca275 100644 --- a/src/eepp/ui/tools/uidocfindreplace.cpp +++ b/src/eepp/ui/tools/uidocfindreplace.cpp @@ -224,24 +224,24 @@ UIDocFindReplace::UIDocFindReplace( UIWidget* parent, const std::shared_ptrgetText() ); mReplaceInput->setFocus(); } ); - addCommand( "find-and-replace", + setCommand( "find-and-replace", [this] { findAndReplace( mSearchState, mReplaceInput->getText() ); } ); - addCommand( "find-prev", [this] { findPrevText( mSearchState ); } ); - addCommand( "replace-selection", + setCommand( "find-prev", [this] { findPrevText( mSearchState ); } ); + setCommand( "replace-selection", [this] { mDoc->replaceSelection( mReplaceInput->getText() ); } ); - addCommand( "change-case", + setCommand( "change-case", [&] { mCaseSensitive->setSelected( !mCaseSensitive->isSelected() ); } ); - addCommand( "change-whole-word", + setCommand( "change-whole-word", [&] { mWholeWord->setSelected( !mWholeWord->isSelected() ); } ); - addCommand( "change-escape-sequence", + setCommand( "change-escape-sequence", [&] { mEscapeSequences->setSelected( !mEscapeSequences->isSelected() ); } ); - addCommand( "toggle-lua-pattern", + setCommand( "toggle-lua-pattern", [&] { mLuaPattern->setSelected( !mLuaPattern->isSelected() ); } ); mCaseSensitive->setTooltipText( mCaseSensitive->getTooltipText() + " (" + diff --git a/src/eepp/ui/uirelativelayout.cpp b/src/eepp/ui/uirelativelayout.cpp index 7858ceeeb..b93616b00 100644 --- a/src/eepp/ui/uirelativelayout.cpp +++ b/src/eepp/ui/uirelativelayout.cpp @@ -11,6 +11,10 @@ UIRelativeLayout::UIRelativeLayout() : UILayout( "relativelayout" ) { mFlags |= UI_OWNS_CHILDS_POSITION; } +UIRelativeLayout::UIRelativeLayout( const std::string& tagName ) : UILayout( tagName ) { + mFlags |= UI_OWNS_CHILDS_POSITION; +} + Uint32 UIRelativeLayout::getType() const { return UI_TYPE_RELATIVE_LAYOUT; } diff --git a/src/tools/ecode/docsearchcontroller.cpp b/src/tools/ecode/docsearchcontroller.cpp index 576ac3db1..0508506be 100644 --- a/src/tools/ecode/docsearchcontroller.cpp +++ b/src/tools/ecode/docsearchcontroller.cpp @@ -100,7 +100,7 @@ void DocSearchController::initSearchBar( mEscapeSequenceChk->setChecked( true ); } } ); - mSearchBarLayout->addCommand( "close-searchbar", [&] { + mSearchBarLayout->setCommand( "close-searchbar", [&] { hideSearchBar(); if ( mEditorSplitter->getCurWidget() ) mEditorSplitter->getCurWidget()->setFocus(); @@ -111,29 +111,29 @@ void DocSearchController::initSearchBar( } } } ); - mSearchBarLayout->addCommand( "repeat-find", [this] { findNextText( mSearchState ); } ); - mSearchBarLayout->addCommand( "replace-all", [this] { + mSearchBarLayout->setCommand( "repeat-find", [this] { findNextText( mSearchState ); } ); + mSearchBarLayout->setCommand( "replace-all", [this] { size_t count = replaceAll( mSearchState, mReplaceInput->getText() ); mApp->getNotificationCenter()->addNotification( String::format( "Replaced %zu occurrences.", count ) ); mReplaceInput->setFocus(); } ); - mSearchBarLayout->addCommand( + mSearchBarLayout->setCommand( "find-and-replace", [this] { findAndReplace( mSearchState, mReplaceInput->getText() ); } ); - mSearchBarLayout->addCommand( "find-prev", [this] { findPrevText( mSearchState ); } ); - mSearchBarLayout->addCommand( "replace-selection", [this] { + mSearchBarLayout->setCommand( "find-prev", [this] { findPrevText( mSearchState ); } ); + mSearchBarLayout->setCommand( "replace-selection", [this] { replaceSelection( mSearchState, mReplaceInput->getText() ); } ); - mSearchBarLayout->addCommand( + mSearchBarLayout->setCommand( "change-case", [&] { mCaseSensitiveChk->setChecked( !mCaseSensitiveChk->isChecked() ); } ); - mSearchBarLayout->addCommand( "change-whole-word", + mSearchBarLayout->setCommand( "change-whole-word", [&] { mWholeWordChk->setChecked( !mWholeWordChk->isChecked() ); } ); - mSearchBarLayout->addCommand( "change-escape-sequence", [&] { + mSearchBarLayout->setCommand( "change-escape-sequence", [&] { mEscapeSequenceChk->setChecked( !mEscapeSequenceChk->isChecked() ); } ); - mSearchBarLayout->addCommand( + mSearchBarLayout->setCommand( "toggle-lua-pattern", [&] { mLuaPatternChk->setChecked( !mLuaPatternChk->isChecked() ); } ); - mSearchBarLayout->addCommand( "open-global-search", [&] { mApp->showGlobalSearch( false ); } ); + mSearchBarLayout->setCommand( "open-global-search", [&] { mApp->showGlobalSearch( false ); } ); addReturnListener( mFindInput, "repeat-find" ); addReturnListener( mReplaceInput, "find-and-replace" ); diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index d63069332..e18d713e5 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -2,7 +2,7 @@ #include "plugins/autocomplete/autocompleteplugin.hpp" #include "plugins/formatter/formatterplugin.hpp" #include "plugins/linter/linterplugin.hpp" -// #include "plugins/lsp/lspplugin.hpp" +//#include "plugins/lsp/lspclientplugin.hpp" #include "version.hpp" #include #include @@ -369,7 +369,7 @@ void App::initPluginManager() { mPluginManager->registerPlugin( LinterPlugin::Definition() ); mPluginManager->registerPlugin( FormatterPlugin::Definition() ); mPluginManager->registerPlugin( AutoCompletePlugin::Definition() ); - // mPluginManager->registerPlugin( LSPPlugin::Definition() ); + //mPluginManager->registerPlugin( LSPClientPlugin::Definition() ); } void App::loadConfig( const LogLevel& logLevel ) { @@ -454,6 +454,11 @@ bool App::trySendUnlockedCmd( const KeyEvent& keyEvent ) { { keyEvent.getKeyCode(), keyEvent.getMod() } ); if ( !cmd.empty() ) terminal->execute( cmd ); + } else { + std::string cmd = mMainLayout->getKeyBindings().getCommandFromKeyBind( + { keyEvent.getKeyCode(), keyEvent.getMod() } ); + if ( !cmd.empty() ) + mMainLayout->execute( cmd ); } return false; } @@ -2466,9 +2471,6 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { saveFileDialog( mSplitter->getCurEditor() ); } ); doc.setCommand( "save-all", [&] { saveAll(); } ); - doc.setCommand( "find-replace", [&] { showFindView(); } ); - doc.setCommand( "open-global-search", [&] { showGlobalSearch( false ); } ); - doc.setCommand( "open-locatebar", [&] { mFileLocator->showLocateBar(); } ); doc.setCommand( "repeat-find", [&] { mDocSearchController->findNextText( mDocSearchController->getSearchState() ); } ); @@ -2476,11 +2478,6 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { mDocSearchController->findPrevText( mDocSearchController->getSearchState() ); } ); doc.setCommand( "close-folder", [&] { closeFolder(); } ); - doc.setCommand( "close-app", [&] { closeApp(); } ); - doc.setCommand( "fullscreen-toggle", [&]() { fullscreenToggle(); } ); - doc.setCommand( "open-file", [&] { openFileDialog(); } ); - doc.setCommand( "open-folder", [&] { openFolderDialog(); } ); - doc.setCommand( "console-toggle", [&] { consoleToggle(); } ); doc.setCommand( "lock", [&] { if ( mSplitter->curEditorExistsAndFocused() ) { mSplitter->getCurEditor()->setLocked( true ); @@ -2499,41 +2496,9 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { updateDocumentMenu(); } } ); - doc.setCommand( "keybindings", [&] { loadFileFromPath( mKeybindingsPath ); } ); - doc.setCommand( "debug-draw-boxes-toggle", [&] { debugDrawBoxesToggle(); } ); - doc.setCommand( "debug-draw-highlight-toggle", [&] { debugDrawHighlightToggle(); } ); - doc.setCommand( "debug-draw-debug-data", [&] { debugDrawData(); } ); - doc.setCommand( "debug-widget-tree-view", [&] { createWidgetInspector(); } ); doc.setCommand( "go-to-line", [&] { mFileLocator->goToLine(); } ); doc.setCommand( "load-current-dir", [&] { loadCurrentDirectory(); } ); - doc.setCommand( "menu-toggle", [&] { toggleSettingsMenu(); } ); - doc.setCommand( "switch-side-panel", [&] { switchSidePanel(); } ); - doc.setCommand( "download-file-web", [&] { downloadFileWebDialog(); } ); - doc.setCommand( "move-panel-left", [&] { panelPosition( PanelPosition::Left ); } ); - doc.setCommand( "move-panel-right", [&] { panelPosition( PanelPosition::Right ); } ); - doc.setCommand( "create-new-terminal", [&] { mTerminalManager->createNewTerminal(); } ); - doc.setCommand( "terminal-split-right", [&] { - mSplitter->split( UICodeEditorSplitter::SplitDirection::Right, mSplitter->getCurWidget(), - false ); - doc.execute( "create-new-terminal" ); - } ); - doc.setCommand( "terminal-split-bottom", [&] { - mSplitter->split( UICodeEditorSplitter::SplitDirection::Bottom, mSplitter->getCurWidget(), - false ); - doc.execute( "create-new-terminal" ); - } ); - doc.setCommand( "terminal-split-left", [&] { - mSplitter->split( UICodeEditorSplitter::SplitDirection::Left, mSplitter->getCurWidget(), - false ); - doc.execute( "create-new-terminal" ); - } ); - doc.setCommand( "terminal-split-top", [&] { - mSplitter->split( UICodeEditorSplitter::SplitDirection::Top, mSplitter->getCurWidget(), - false ); - doc.execute( "create-new-terminal" ); - } ); - doc.setCommand( "reopen-closed-tab", [&] { reopenClosedTab(); } ); - doc.setCommand( "plugin-manager-open", [&] { createPluginManagerUI(); } ); + registerUnlockedCommands( doc ); editor->addEventListener( Event::OnDocumentSave, [&]( const Event* event ) { UICodeEditor* editor = event->getNode()->asType(); @@ -3692,7 +3657,7 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe opacity: 0.8; } - + @@ -3774,7 +3739,7 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe - + )html"; UIIconTheme* iconTheme = UIIconTheme::New( "ecode" ); @@ -3926,6 +3891,7 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe UIWidgetCreator::registerWidget( "searchbar", UISearchBar::New ); UIWidgetCreator::registerWidget( "locatebar", UILocateBar::New ); UIWidgetCreator::registerWidget( "globalsearchbar", UIGlobalSearchBar::New ); + UIWidgetCreator::registerWidget( "mainlayout", UIMainLayout::New ); mUISceneNode->loadLayoutFromString( baseUI ); mUISceneNode->bind( "main_layout", mMainLayout ); mUISceneNode->bind( "code_container", mBaseLayout ); @@ -4000,6 +3966,10 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe mConsole->setQuakeMode( true ); mConsole->setVisible( false ); + registerUnlockedCommands( *mMainLayout ); + mSplitter->registerSplitterCommands( *mMainLayout ); + mMainLayout->getKeyBindings().addKeybinds( getDefaultKeybindings() ); + Log::info( "Complete UI took: %.2f ms", globalClock.getElapsedTime().asMilliseconds() ); #if EE_PLATFORM == EE_PLATFORM_EMSCRIPTEN diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index d855f64e1..e21279a2e 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -165,12 +165,59 @@ class App : public UICodeEditorSplitter::Client { void toggleSidePanel(); + UIMainLayout* getMainLayout() const { return mMainLayout; } + + template void registerUnlockedCommands( T& t ) { + t.setCommand( "keybindings", [&] { loadFileFromPath( mKeybindingsPath ); } ); + t.setCommand( "debug-draw-boxes-toggle", [&] { debugDrawBoxesToggle(); } ); + t.setCommand( "debug-draw-highlight-toggle", [&] { debugDrawHighlightToggle(); } ); + t.setCommand( "debug-draw-debug-data", [&] { debugDrawData(); } ); + t.setCommand( "debug-widget-tree-view", [&] { createWidgetInspector(); } ); + t.setCommand( "menu-toggle", [&] { toggleSettingsMenu(); } ); + t.setCommand( "switch-side-panel", [&] { switchSidePanel(); } ); + t.setCommand( "download-file-web", [&] { downloadFileWebDialog(); } ); + t.setCommand( "move-panel-left", [&] { panelPosition( PanelPosition::Left ); } ); + t.setCommand( "move-panel-right", [&] { panelPosition( PanelPosition::Right ); } ); + t.setCommand( "create-new-terminal", [&] { mTerminalManager->createNewTerminal(); } ); + t.setCommand( "terminal-split-right", [&] { + mSplitter->split( UICodeEditorSplitter::SplitDirection::Right, + mSplitter->getCurWidget(), false ); + t.execute( "create-new-terminal" ); + } ); + t.setCommand( "terminal-split-bottom", [&] { + mSplitter->split( UICodeEditorSplitter::SplitDirection::Bottom, + mSplitter->getCurWidget(), false ); + t.execute( "create-new-terminal" ); + } ); + t.setCommand( "terminal-split-left", [&] { + mSplitter->split( UICodeEditorSplitter::SplitDirection::Left, mSplitter->getCurWidget(), + false ); + t.execute( "create-new-terminal" ); + } ); + t.setCommand( "terminal-split-top", [&] { + mSplitter->split( UICodeEditorSplitter::SplitDirection::Top, mSplitter->getCurWidget(), + false ); + t.execute( "create-new-terminal" ); + } ); + t.setCommand( "reopen-closed-tab", [&] { reopenClosedTab(); } ); + t.setCommand( "plugin-manager-open", [&] { createPluginManagerUI(); } ); + t.setCommand( "close-app", [&] { closeApp(); } ); + t.setCommand( "fullscreen-toggle", [&]() { fullscreenToggle(); } ); + t.setCommand( "open-file", [&] { openFileDialog(); } ); + t.setCommand( "open-folder", [&] { openFolderDialog(); } ); + t.setCommand( "console-toggle", [&] { consoleToggle(); } ); + t.setCommand( "find-replace", [&] { showFindView(); } ); + t.setCommand( "open-global-search", [&] { showGlobalSearch( false ); } ); + t.setCommand( "open-locatebar", [&] { mFileLocator->showLocateBar(); } ); + mSplitter->registerSplitterCommands( t ); + } + protected: EE::Window::Window* mWindow{ nullptr }; UISceneNode* mUISceneNode{ nullptr }; UIConsole* mConsole{ nullptr }; std::string mWindowTitle{ "ecode" }; - UILayout* mMainLayout{ nullptr }; + UIMainLayout* mMainLayout{ nullptr }; UILayout* mBaseLayout{ nullptr }; UILayout* mImageLayout{ nullptr }; UIPopUpMenu* mSettingsMenu{ nullptr }; diff --git a/src/tools/ecode/filelocator.cpp b/src/tools/ecode/filelocator.cpp index 9192e79f5..7ab0976f3 100644 --- a/src/tools/ecode/filelocator.cpp +++ b/src/tools/ecode/filelocator.cpp @@ -83,7 +83,7 @@ void FileLocator::initLocateBar( UILocateBar* locateBar, UITextInput* locateInpu const KeyEvent* keyEvent = static_cast( event ); mLocateTable->forceKeyDown( *keyEvent ); } ); - mLocateBarLayout->addCommand( "close-locatebar", [&] { + mLocateBarLayout->setCommand( "close-locatebar", [&] { hideLocateBar(); if ( mEditorSplitter->getCurWidget() ) mEditorSplitter->getCurWidget()->setFocus(); diff --git a/src/tools/ecode/globalsearchcontroller.cpp b/src/tools/ecode/globalsearchcontroller.cpp index 1e5e6d67e..66f1bda64 100644 --- a/src/tools/ecode/globalsearchcontroller.cpp +++ b/src/tools/ecode/globalsearchcontroller.cpp @@ -105,21 +105,21 @@ void GlobalSearchController::initGlobalSearchBar( mGlobalSearchHistoryList = mGlobalSearchBarLayout->find( "global_search_history" ); - mGlobalSearchBarLayout->addCommand( "global-search-clear-history", [&] { clearHistory(); } ); - mGlobalSearchBarLayout->addCommand( + mGlobalSearchBarLayout->setCommand( "global-search-clear-history", [&] { clearHistory(); } ); + mGlobalSearchBarLayout->setCommand( "search-in-files", [&, caseSensitiveChk, wholeWordChk, luaPatternChk, escapeSequenceChk] { doGlobalSearch( mGlobalSearchInput->getText(), caseSensitiveChk->isChecked(), wholeWordChk->isChecked(), luaPatternChk->isChecked(), escapeSequenceChk->isChecked(), false ); } ); - mGlobalSearchBarLayout->addCommand( + mGlobalSearchBarLayout->setCommand( "search-replace-in-files", [&, caseSensitiveChk, wholeWordChk, luaPatternChk, escapeSequenceChk] { doGlobalSearch( mGlobalSearchInput->getText(), caseSensitiveChk->isChecked(), wholeWordChk->isChecked(), luaPatternChk->isChecked(), escapeSequenceChk->isChecked(), true ); } ); - mGlobalSearchBarLayout->addCommand( + mGlobalSearchBarLayout->setCommand( "search-again", [&, caseSensitiveChk, wholeWordChk, luaPatternChk, escapeSequenceChk] { auto listBox = mGlobalSearchHistoryList->getListBox(); if ( listBox->getItemSelectedIndex() < mGlobalSearchHistory.size() ) { @@ -131,39 +131,39 @@ void GlobalSearchController::initGlobalSearchBar( mGlobalSearchTreeReplace == mGlobalSearchTree, true ); } } ); - mGlobalSearchBarLayout->addCommand( "search-set-string", [&] { + mGlobalSearchBarLayout->setCommand( "search-set-string", [&] { auto listBox = mGlobalSearchHistoryList->getListBox(); mGlobalSearchInput->setText( mGlobalSearchHistory[mGlobalSearchHistory.size() - 1 - listBox->getItemSelectedIndex()] .first ); ; } ); - mGlobalSearchBarLayout->addCommand( "close-global-searchbar", [&] { + mGlobalSearchBarLayout->setCommand( "close-global-searchbar", [&] { hideGlobalSearchBar(); if ( mEditorSplitter->getCurWidget() ) mEditorSplitter->getCurWidget()->setFocus(); } ); - mGlobalSearchBarLayout->addCommand( "expand-all", [&] { + mGlobalSearchBarLayout->setCommand( "expand-all", [&] { mGlobalSearchTree->expandAll(); mGlobalSearchTree->setFocus(); } ); - mGlobalSearchBarLayout->addCommand( "collapse-all", [&] { + mGlobalSearchBarLayout->setCommand( "collapse-all", [&] { mGlobalSearchTree->collapseAll(); mGlobalSearchTree->setFocus(); } ); - mGlobalSearchBarLayout->addCommand( "change-case", [&, caseSensitiveChk] { + mGlobalSearchBarLayout->setCommand( "change-case", [&, caseSensitiveChk] { caseSensitiveChk->setChecked( !caseSensitiveChk->isChecked() ); } ); - mGlobalSearchBarLayout->addCommand( "change-whole-word", [&, wholeWordChk] { + mGlobalSearchBarLayout->setCommand( "change-whole-word", [&, wholeWordChk] { wholeWordChk->setChecked( !wholeWordChk->isChecked() ); } ); - mGlobalSearchBarLayout->addCommand( "toggle-lua-pattern", [&, luaPatternChk] { + mGlobalSearchBarLayout->setCommand( "toggle-lua-pattern", [&, luaPatternChk] { luaPatternChk->setChecked( !luaPatternChk->isChecked() ); } ); - mGlobalSearchBarLayout->addCommand( "change-escape-sequence", [&, escapeSequenceChk] { + mGlobalSearchBarLayout->setCommand( "change-escape-sequence", [&, escapeSequenceChk] { escapeSequenceChk->setChecked( !escapeSequenceChk->isChecked() ); } ); - mGlobalSearchBarLayout->addCommand( "find-replace", [&] { mApp->showFindView(); } ); + mGlobalSearchBarLayout->setCommand( "find-replace", [&] { mApp->showFindView(); } ); mGlobalSearchInput->addEventListener( Event::OnPressEnter, [&]( const Event* ) { if ( mGlobalSearchInput->hasFocus() ) { mGlobalSearchBarLayout->execute( "search-in-files" ); @@ -244,7 +244,7 @@ void GlobalSearchController::initGlobalSearchBar( if ( keyEvent->getKeyCode() == KEY_ESCAPE ) mGlobalSearchBarLayout->execute( "close-global-searchbar" ); } ); - mGlobalSearchBarLayout->addCommand( "replace-in-files", [&, replaceInput, escapeSequenceChk] { + mGlobalSearchBarLayout->setCommand( "replace-in-files", [&, replaceInput, escapeSequenceChk] { auto listBox = mGlobalSearchHistoryList->getListBox(); if ( listBox->getItemSelectedIndex() < mGlobalSearchHistory.size() ) { const auto& replaceData = mGlobalSearchHistory[mGlobalSearchHistory.size() - 1 - diff --git a/src/tools/ecode/plugins/formatter/formatterplugin.cpp b/src/tools/ecode/plugins/formatter/formatterplugin.cpp index 6b24f9863..09069811a 100644 --- a/src/tools/ecode/plugins/formatter/formatterplugin.cpp +++ b/src/tools/ecode/plugins/formatter/formatterplugin.cpp @@ -198,7 +198,7 @@ void FormatterPlugin::load( const PluginManager* pluginManager ) { for ( const auto& path : paths ) { try { loadFormatterConfig( path ); - } catch ( json::exception& e ) { + } catch ( const json::exception& e ) { Log::error( "Parsing formatter \"%s\" failed:\n%s", path.c_str(), e.what() ); } } diff --git a/src/tools/ecode/plugins/linter/linterplugin.cpp b/src/tools/ecode/plugins/linter/linterplugin.cpp index 1d2107078..7c7f8c370 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.cpp +++ b/src/tools/ecode/plugins/linter/linterplugin.cpp @@ -95,11 +95,11 @@ void LinterPlugin::loadLinterConfig( const std::string& path ) { auto& linters = j["linters"]; for ( auto& obj : linters ) { - Linter linter; if ( !obj.contains( "file_patterns" ) || !obj.contains( "warning_pattern" ) || !obj.contains( "command" ) ) continue; + Linter linter; auto fp = obj["file_patterns"]; for ( auto& pattern : fp ) @@ -181,7 +181,7 @@ void LinterPlugin::load( const PluginManager* pluginManager ) { for ( const auto& path : paths ) { try { loadLinterConfig( path ); - } catch ( json::exception& e ) { + } catch ( const json::exception& e ) { Log::error( "Parsing linter \"%s\" failed:\n%s", path.c_str(), e.what() ); } } @@ -259,6 +259,8 @@ void LinterPlugin::update( UICodeEditor* editor ) { mDirtyDoc.erase( doc.get() ); #if LINTER_THREADED mPool->run( [&, doc] { lintDoc( doc ); }, [] {} ); +#else + lintDoc( doc ); #endif } } diff --git a/src/tools/ecode/plugins/lsp/lspclient.cpp b/src/tools/ecode/plugins/lsp/lspclient.cpp deleted file mode 100644 index 3d24d858c..000000000 --- a/src/tools/ecode/plugins/lsp/lspclient.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "lspclient.hpp" - -namespace ecode { - -LSPClient::LSPClient() {} - -} // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclient.hpp b/src/tools/ecode/plugins/lsp/lspclient.hpp deleted file mode 100644 index 9807c466f..000000000 --- a/src/tools/ecode/plugins/lsp/lspclient.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef ECODE_LSPCLIENT_HPP -#define ECODE_LSPCLIENT_HPP - -#include -#include -#include - -using namespace EE::System; -using namespace EE::UI; -using namespace EE::UI::Doc; - -namespace ecode { - -class LSPClient { - public: - static std::shared_ptr get( const std::shared_ptr& doc ); - - protected: - - LSPClient(); -}; - -} // namespace ecode - -#endif // ECODE_LSPCLIENT_HPP diff --git a/src/tools/ecode/plugins/lsp/lspclientmanager.cpp b/src/tools/ecode/plugins/lsp/lspclientmanager.cpp deleted file mode 100644 index 8098e643d..000000000 --- a/src/tools/ecode/plugins/lsp/lspclientmanager.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "lspclientmanager.hpp" - -namespace ecode { - -LSPClientManager::LSPClientManager() {} - -void LSPClientManager::load( const PluginManager* pluginManager ) {} - -size_t LSPClientManager::clientCount() const { - return mClients.size(); -} - -} // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientmanager.hpp b/src/tools/ecode/plugins/lsp/lspclientmanager.hpp deleted file mode 100644 index 67c2af091..000000000 --- a/src/tools/ecode/plugins/lsp/lspclientmanager.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef ECODE_LSPCLIENTMANAGER_HPP -#define ECODE_LSPCLIENTMANAGER_HPP - -#include "../pluginmanager.hpp" -#include "lspclient.hpp" -#include - -using namespace EE; - -namespace ecode { - -class LSPClientManager { - public: - LSPClientManager(); - - void load( const PluginManager* pluginManager ); - - size_t clientCount() const; - - protected: - std::map> mClients; -}; - -} // namespace ecode - -#endif // ECODE_LSPCLIENTMANAGER_HPP diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp new file mode 100644 index 000000000..a8f6dd3bd --- /dev/null +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -0,0 +1,183 @@ +#include "lspclientplugin.hpp" +#include +#include +#include +#include +#include + +using json = nlohmann::json; + +namespace ecode { + +UICodeEditorPlugin* LSPClientPlugin::New( const PluginManager* pluginManager ) { + return eeNew( LSPClientPlugin, ( pluginManager ) ); +} + +LSPClientPlugin::LSPClientPlugin( const PluginManager* pluginManager ) : + mPool( pluginManager->getThreadPool() ) { + mPool->run( [&, pluginManager] { load( pluginManager ); }, [] {} ); +} + +LSPClientPlugin::~LSPClientPlugin() { + mClosing = true; + Lock l( mDocMutex ); + for ( const auto& editor : mEditors ) { + editor.first->unregisterPlugin( this ); + } +} + +void LSPClientPlugin::load( const PluginManager* pluginManager ) { + std::vector paths; + std::string path( pluginManager->getResourcesPath() + "plugins/lspclient.json" ); + if ( FileSystem::fileExists( path ) ) + paths.emplace_back( path ); + path = pluginManager->getPluginsPath() + "lspclient.json"; + if ( FileSystem::fileExists( path ) || + FileSystem::fileWrite( path, "{\n\"config\":{},\n\"servers\":[]\n}\n" ) ) { + mConfigPath = path; + paths.emplace_back( path ); + } + if ( paths.empty() ) + return; + + std::vector lsps; + + for ( const auto& path : paths ) { + try { + loadLSPConfig( lsps, path ); + } catch ( const json::exception& e ) { + Log::error( "Parsing LSP \"%s\" failed:\n%s", path.c_str(), e.what() ); + } + } + + mClientManager.load( pluginManager, std::move( lsps ) ); + + mReady = mClientManager.clientCount() > 0; + if ( mReady ) + fireReadyCbs(); +} + +void LSPClientPlugin::loadLSPConfig( std::vector& lsps, const std::string& path ) { + std::string data; + if ( !FileSystem::fileGet( path, data ) ) + return; + json j; + try { + j = json::parse( data, nullptr, true, true ); + } catch ( ... ) { + return; + } + + if ( !j.contains( "servers" ) ) + return; + auto& servers = j["servers"]; + + for ( auto& obj : servers ) { + if ( !obj.contains( "language" ) || !obj.contains( "file_patterns" ) ) { + Log::warning( "LSP server without language or file_patterns, ignored..." ); + continue; + } + + if ( !obj.contains( "use" ) && !( obj.contains( "command" ) && obj.contains( "name" ) ) ) { + Log::warning( "LSP server without name+command or use, ignored..." ); + continue; + } + + LSPDefinition lsp; + if ( obj.contains( "use" ) ) { + std::string use = obj["use"]; + bool foundTlsp = false; + for ( const auto& tlsp : lsps ) { + if ( tlsp.name == use ) { + lsp.language = obj["language"]; + foundTlsp = true; + lsp.command = tlsp.command; + lsp.name = tlsp.name; + break; + } + } + if ( !foundTlsp ) { + Log::warning( "LSP server trying to use an undeclared LSP. Father LSP must be " + "declared first." ); + continue; + } + } else { + lsp.language = obj["language"]; + lsp.command = obj["command"]; + lsp.name = obj["name"]; + } + + if ( obj.contains( "url" ) ) + lsp.url = obj["url"]; + + auto fp = obj["file_patterns"]; + + for ( auto& pattern : fp ) + lsp.filePatterns.push_back( pattern.get() ); + + if ( obj.contains( "rootIndicationFileNames" ) ) { + auto fnms = obj["rootIndicationFileNames"]; + for ( auto& fn : fnms ) + lsp.rootIndicationFileNames.push_back( fn ); + } + + // If the file pattern is repeated, we will overwrite the previous LSP. + // The previous LSP should be the "default" LSP that comes with ecode. + size_t pos = lspFilePatternPosition( lsps, lsp.filePatterns ); + if ( pos != std::string::npos ) { + lsps[pos] = lsp; + } else { + lsps.emplace_back( std::move( lsp ) ); + } + } +} + +size_t LSPClientPlugin::lspFilePatternPosition( const std::vector& lsps, + const std::vector& patterns ) { + for ( size_t i = 0; i < lsps.size(); ++i ) { + for ( const std::string& filePattern : lsps[i].filePatterns ) { + for ( const std::string& pattern : patterns ) { + if ( filePattern == pattern ) { + return i; + } + } + } + } + return std::string::npos; +} + +void LSPClientPlugin::onRegister( UICodeEditor* editor ) { + Lock l( mDocMutex ); + mDocs.insert( editor->getDocumentRef().get() ); + + std::vector listeners; + + listeners.push_back( + editor->addEventListener( Event::OnDocumentLoaded, [&, editor]( const Event* ) { + mClientManager.run( editor->getDocumentRef() ); + } ) ); + + mEditors.insert( { editor, listeners } ); + mEditorDocs[editor] = editor->getDocumentRef().get(); + + if ( editor->hasDocument() && editor->getDocument().hasFilepath() ) + mClientManager.run( editor->getDocumentRef() ); +} + +void LSPClientPlugin::onUnregister( UICodeEditor* editor ) { + if ( mClosing ) + return; + Lock l( mDocMutex ); + TextDocument* doc = mEditorDocs[editor]; + auto cbs = mEditors[editor]; + for ( auto listener : cbs ) + editor->removeEventListener( listener ); + mEditors.erase( editor ); + mEditorDocs.erase( editor ); + for ( auto editor : mEditorDocs ) + if ( editor.second == doc ) + return; + mDocs.erase( doc ); +} + +} // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspplugin.hpp b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp similarity index 65% rename from src/tools/ecode/plugins/lsp/lspplugin.hpp rename to src/tools/ecode/plugins/lsp/lspclientplugin.hpp index 787a3ba41..fa94c7630 100644 --- a/src/tools/ecode/plugins/lsp/lspplugin.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp @@ -2,7 +2,7 @@ #define ECODE_LSPPLUGIN_HPP #include "../pluginmanager.hpp" -#include "lspclientmanager.hpp" +#include "lspclientservermanager.hpp" #include #include #include @@ -17,26 +17,21 @@ using namespace EE::UI; namespace ecode { -struct LSP { - std::string name; - std::string language; - const SyntaxDefinition* langDefinition; - std::vector filePatterns; - std::string command; - std::string url; - std::vector rootIndicationFileName; -}; - -class LSPPlugin : public UICodeEditorPlugin { +// Implementation of the LSP Client: +// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/ +class LSPClientPlugin : public UICodeEditorPlugin { public: static PluginDefinition Definition() { - return { - "lsp", "LSP Client", "Language Server Protocol Client.", LSPPlugin::New, { 0, 1, 0 } }; + return { "lspclient", + "LSP Client", + "Language Server Protocol Client.", + LSPClientPlugin::New, + { 0, 0, 1 } }; } static UICodeEditorPlugin* New( const PluginManager* pluginManager ); - virtual ~LSPPlugin(); + virtual ~LSPClientPlugin(); std::string getId() { return Definition().id; } @@ -57,16 +52,19 @@ class LSPPlugin : public UICodeEditorPlugin { std::unordered_map> mEditors; std::set mDocs; std::unordered_map mEditorDocs; - LSPClientManager mClientManager; + LSPClientServerManager mClientManager; std::string mConfigPath; bool mClosing{ false }; bool mReady{ false }; - LSPPlugin( const PluginManager* pluginManager ); + LSPClientPlugin( const PluginManager* pluginManager ); void load( const PluginManager* pluginManager ); - void loadLSPConfig( const std::string& path ); + void loadLSPConfig( std::vector& lsps, const std::string& path ); + + size_t lspFilePatternPosition( const std::vector& lsps, + const std::vector& patterns ); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.cpp b/src/tools/ecode/plugins/lsp/lspclientserver.cpp new file mode 100644 index 000000000..c1fc05312 --- /dev/null +++ b/src/tools/ecode/plugins/lsp/lspclientserver.cpp @@ -0,0 +1,238 @@ +#include "lspclientserver.hpp" +#include +#include +#include + +namespace ecode { + +static const char* MEMBER_ID = "id"; +static const char* MEMBER_METHOD = "method"; +static const char* MEMBER_PARAMS = "params"; +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_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_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_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"; +// static const char* MEMBER_TARGET_SELECTION_RANGE = "targetSelectionRange"; +// static const char* MEMBER_PREVIOUS_RESULT_ID = "previousResultId"; +// static const char* MEMBER_QUERY = "query"; + +static json newRequest( const std::string& method, const json& params ) { + json j; + j[MEMBER_METHOD] = method; + j[MEMBER_PARAMS] = params; + return j; +} + +static json versionedTextDocumentIdentifier( const URI& document, int version = -1 ) { + json map{ { MEMBER_URI, document.toString() } }; + if ( version >= 0 ) + map[MEMBER_VERSION] = version; + return map; +} + +static json textDocumentItem( const URI& document, const std::string& lang, const std::string& text, + int version ) { + auto map = versionedTextDocumentIdentifier( document, version ); + map[MEMBER_TEXT] = text; + map[MEMBER_LANGID] = lang; + return map; +} + +static json textDocumentParams( const json& m ) { + return json{ { "textDocument", m } }; +} + +static json textDocumentParams( const URI& document, int version = -1 ) { + return textDocumentParams( versionedTextDocumentIdentifier( document, version ) ); +} + +LSPClientServer::LSPClientServer( const LSPDefinition& lsp, const std::string& rootPath ) : + mLSP( lsp ), mRootPath( rootPath ) {} + +LSPClientServer::~LSPClientServer() { + for ( const auto& client : mClients ) { + client.first->unregisterClient( client.second.get() ); + } +} + +bool LSPClientServer::start() { + bool ret = mProcess.create( mLSP.command, Process::getDefaultOptions(), {}, 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 ); } ); + + initialize(); + } + return ret; +} + +bool LSPClientServer::registerDoc( const std::shared_ptr& doc ) { + for ( auto& cdoc : mDocs ) { + if ( cdoc.get() == doc.get() ) { + if ( mClients.find( doc.get() ) == mClients.end() ) { + mClients[doc.get()] = std::make_unique( this, doc.get() ); + return true; + } + return false; + } + } + + mClients[doc.get()] = std::make_unique( this, doc.get() ); + mDocs.emplace_back( doc ); + doc->registerClient( mClients[doc.get()].get() ); + return true; +} + +LSPClientServer::RequestHandle LSPClientServer::write( const json& msg, + const GenericReplyHandler& h, + const GenericReplyHandler& eh, + const int id ) { + RequestHandle ret; + ret.mServer = this; + + if ( !mProcess.isAlive() ) + return ret; + + auto ob = msg; + ob["jsonrpc"] = "2.0"; + + // notification == no handler + if ( h ) { + ob[MEMBER_ID] = ++mLastMsgId; + ret.mId = mLastMsgId; + mHandlers[mLastMsgId] = { h, eh }; + } else if ( id ) { + ob[MEMBER_ID] = id; + } + + std::string sjson = ob.dump(); + sjson = String::format( "Content-Length: %lu\r\n\r\n%s", sjson.length(), sjson.c_str() ); + + Log::info( "LSPClient calling %s", msg["method"].get().c_str() ); + Log::debug( "LSPClient sending message:\n%s", sjson.c_str() ); + + mProcess.write( sjson ); + + return ret; +} + +LSPClientServer::RequestHandle LSPClientServer::send( const json& msg, const GenericReplyHandler& h, + const GenericReplyHandler& eh ) { + if ( mProcess.isAlive() ) { + return write( msg, h, eh ); + } else { + Log::error( "LSPClientServer - Send for non-running server: %s - %s", mLSP.name, + mLSP.language ); + } + return RequestHandle(); +} + +LSPClientServer::RequestHandle LSPClientServer::didOpen( const URI& document, + const std::string& text, int version ) { + auto params = textDocumentParams( textDocumentItem( document, mLSP.language, text, version ) ); + return send( newRequest( "textDocument/didOpen", params ) ); +} + +LSPClientServer::RequestHandle LSPClientServer::documentSymbols( const URI& document, + const GenericReplyHandler& h, + const GenericReplyHandler& eh ) { + auto params = textDocumentParams( document ); + return send( newRequest( "textDocument/documentSymbol", params ), h, eh ); +} + +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() ); + return; + } catch ( const json::exception& e ) { + Log::debug( "LSP Server %s said: Coudln't parse json err: %s", mLSP.name.c_str(), + e.what() ); + } + } + 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" ) }; +} + +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" ), { ( "markdown" ), ( "plaintext" ) } } } } }, + }, + { ( "window" ), json{ { ( "workDoneProgress" ), true } } } }; + + json params{ { ( "processId" ), Sys::getProcessID() }, + { ( "rootPath" ), !mRootPath.empty() ? mRootPath : "" }, + { ( "rootUri" ), !mRootPath.empty() ? "file://" + mRootPath : "" }, + { ( "capabilities" ), capabilities }, + { ( "initializationOptions" ), {} } }; + + write( + newRequest( ( "initialize" ), params ), + [&]( const json& ) { + + }, + [&]( const json& ) { + + } ); +} + +} // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.hpp b/src/tools/ecode/plugins/lsp/lspclientserver.hpp new file mode 100644 index 000000000..949d292bd --- /dev/null +++ b/src/tools/ecode/plugins/lsp/lspclientserver.hpp @@ -0,0 +1,83 @@ +#ifndef ECODE_LSPCLIENTSERVER_HPP +#define ECODE_LSPCLIENTSERVER_HPP + +#include "lspdefinition.hpp" +#include "lspdocumentclient.hpp" +#include +#include +#include +#include +#include + +using json = nlohmann::json; + +using namespace EE; +using namespace EE::System; +using namespace EE::UI; +using namespace EE::UI::Doc; + +namespace ecode { + +class LSPClientServer { + public: + template using ReplyHandler = std::function; + + using GenericReplyHandler = ReplyHandler; + + class RequestHandle { + friend class LSPClientServer; + LSPClientServer* mServer; + int mId = 0; + + public: + RequestHandle& cancel() { + if ( mServer ) + mServer->cancel( mId ); + return *this; + } + }; + + LSPClientServer( const LSPDefinition& lsp, const std::string& rootPath ); + + ~LSPClientServer(); + + bool start(); + + bool registerDoc( const std::shared_ptr& doc ); + + int cancel( int id ) { return id; } + + RequestHandle send( const json& msg, const GenericReplyHandler& h = nullptr, + const GenericReplyHandler& eh = nullptr ); + + LSPClientServer::RequestHandle didOpen( const URI& document, const std::string& text, + int version ); + + const LSPDefinition& getDefinition() const { return mLSP; } + + LSPClientServer::RequestHandle documentSymbols( const URI& document, + const GenericReplyHandler& h, + const GenericReplyHandler& eh ); + + protected: + LSPDefinition mLSP; + std::string mRootPath; + Process mProcess; + std::vector> mDocs; + std::map> mClients; + std::map> mHandlers; + int mLastMsgId{ 0 }; + + 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(); +}; + +} // namespace ecode + +#endif // ECODE_LSPCLIENTSERVER_HPP diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp new file mode 100644 index 000000000..c7f716671 --- /dev/null +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp @@ -0,0 +1,101 @@ +#include "lspclientservermanager.hpp" +#include +#include +#include + +namespace ecode { + +LSPClientServerManager::LSPClientServerManager() {} + +void LSPClientServerManager::load( const PluginManager* pluginManager, + std::vector&& lsps ) { + mPool = pluginManager->getThreadPool(); + mLSPs = lsps; +} + +std::vector +LSPClientServerManager::supportsLSP( const std::shared_ptr& doc ) { + if ( !doc->hasFilepath() && doc->getLoadingFilePath().empty() ) + return {}; + std::string fileName( FileSystem::fileNameFromPath( + doc->getFilePath().empty() ? doc->getLoadingFilePath() : doc->getFilePath() ) ); + const auto& def = doc->getSyntaxDefinition(); + std::vector lsps; + + for ( auto& lsp : mLSPs ) { + for ( auto& ext : lsp.filePatterns ) { + if ( LuaPattern::find( fileName, ext ).isValid() ) { + lsps.push_back( lsp ); + break; + } + auto& files = def.getFiles(); + if ( std::find( files.begin(), files.end(), ext ) != files.end() ) { + lsps.push_back( lsp ); + break; + } + } + } + + return lsps; +} + +std::shared_ptr +LSPClientServerManager::runLSPServer( const LSPDefinition& lsp, const std::string& rootPath ) { + auto server = std::make_shared( lsp, rootPath ); + server->start(); + return server; +} + +std::string LSPClientServerManager::findRootPath( const LSPDefinition& lsp, + const std::shared_ptr& doc ) { + if ( lsp.rootIndicationFileNames.empty() || !doc->hasFilepath() ) + return ""; + std::string rootPath( doc->getFileInfo().getDirectoryPath() ); + std::string lRootPath; + FileSystem::dirAddSlashAtEnd( rootPath ); + + while ( rootPath != lRootPath ) { + for ( const auto& fileName : lsp.rootIndicationFileNames ) + if ( FileSystem::fileExists( rootPath + fileName ) ) + return rootPath; + + lRootPath = rootPath; + rootPath = FileSystem::removeLastFolderFromPath( rootPath ); + } + + return ""; +} + +void LSPClientServerManager::tryRunServer( const std::shared_ptr& doc ) { + auto lsps = supportsLSP( doc ); + if ( lsps.empty() ) + return; + + for ( const auto& lsp : lsps ) { + auto rootPath = findRootPath( lsp, doc ); + auto lspName = lsp.name.empty() ? lsp.command : lsp.name; + String::HashType id = String::hash( lspName + "|" + lsp.language + "|" + rootPath ); + auto clientIt = mClients.find( id ); + std::shared_ptr server; + if ( clientIt == mClients.end() ) { + server = runLSPServer( lsp, rootPath ); + if ( server.use_count() ) + mClients[id] = server; + } else { + server = clientIt->second; + } + if ( server.use_count() ) { + server->registerDoc( doc ); + } + } +} + +void LSPClientServerManager::run( const std::shared_ptr& doc ) { + mPool->run( [&, doc]() { tryRunServer( doc ); }, []() {} ); +} + +size_t LSPClientServerManager::clientCount() const { + return mClients.size(); +} + +} // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp new file mode 100644 index 000000000..3336b269d --- /dev/null +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp @@ -0,0 +1,40 @@ +#ifndef ECODE_LSPCLIENTMANAGER_HPP +#define ECODE_LSPCLIENTMANAGER_HPP + +#include "../pluginmanager.hpp" +#include "lspclientserver.hpp" +#include "lspdefinition.hpp" +#include + +using namespace EE; + +namespace ecode { + +class LSPClientServerManager { + public: + LSPClientServerManager(); + + void load( const PluginManager* pluginManager, std::vector&& lsps ); + + void run( const std::shared_ptr& doc ); + + size_t clientCount() const; + + protected: + std::shared_ptr mPool; + std::map> mClients; + std::vector mLSPs; + + std::vector supportsLSP( const std::shared_ptr& doc ); + + std::shared_ptr runLSPServer( const LSPDefinition& lsp, + const std::string& rootPath ); + + std::string findRootPath( const LSPDefinition& lsp, const std::shared_ptr& doc ); + + void tryRunServer( const std::shared_ptr& doc ); +}; + +} // namespace ecode + +#endif // ECODE_LSPCLIENTMANAGER_HPP diff --git a/src/tools/ecode/plugins/lsp/lspdefinition.hpp b/src/tools/ecode/plugins/lsp/lspdefinition.hpp new file mode 100644 index 000000000..2d2ea4f3a --- /dev/null +++ b/src/tools/ecode/plugins/lsp/lspdefinition.hpp @@ -0,0 +1,19 @@ +#ifndef ECODE_LSPDEFINITION_HPP +#define ECODE_LSPDEFINITION_HPP +#include +#include + +namespace ecode { + +struct LSPDefinition { + std::string language; + std::string name; + std::vector filePatterns; + std::string command; + std::vector rootIndicationFileNames; + std::string url; +}; + +} // namespace ecode + +#endif // ECODE_LSPDEFINITION_HPP diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp new file mode 100644 index 000000000..14e89eb6a --- /dev/null +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp @@ -0,0 +1,56 @@ +#include "lspdocumentclient.hpp" +#include "lspclientserver.hpp" +#include +#include + +using namespace EE::System; + +namespace ecode { + +LSPDocumentClient::LSPDocumentClient( LSPClientServer* server, TextDocument* doc ) : + mServer( server ), mDoc( doc ) { + notifyOpen(); + mServer->documentSymbols( + mDoc->getURI(), + [&]( const json& ) { + + }, + [&]( const json& ) { + + } ); +} + +void LSPDocumentClient::onDocumentTextChanged() {} + +void LSPDocumentClient::onDocumentUndoRedo( const TextDocument::UndoRedo& /*eventType*/ ) {} + +void LSPDocumentClient::onDocumentCursorChange( const TextPosition& ) {} + +void LSPDocumentClient::onDocumentSelectionChange( const TextRange& ) {} + +void LSPDocumentClient::onDocumentLineCountChange( const size_t& /*lastCount*/, + const size_t& /*newCount*/ ) {} + +void LSPDocumentClient::onDocumentLineChanged( const Int64& /*lineIndex*/ ) {} + +void LSPDocumentClient::onDocumentSaved( TextDocument* ) {} + +void LSPDocumentClient::onDocumentClosed( TextDocument* ) {} + +void LSPDocumentClient::onDocumentDirtyOnFileSystem( TextDocument* ) {} + +void LSPDocumentClient::onDocumentMoved( TextDocument* ) {} + +void LSPDocumentClient::notifyOpen() { + if ( mDoc->isDirty() ) { + IOStreamString text; + mDoc->save( text, true ); + mServer->didOpen( mDoc->getURI(), text.getStream(), mVersion ); + } else { + std::string text; + FileSystem::fileGet( mDoc->getFilePath(), text ); + mServer->didOpen( mDoc->getURI(), text, mVersion ); + } +} + +} // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp new file mode 100644 index 000000000..16e4cbede --- /dev/null +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp @@ -0,0 +1,37 @@ +#ifndef ECODE_LSPDOCUMENTCLIENT_HPP +#define ECODE_LSPDOCUMENTCLIENT_HPP + +#include + +using namespace EE; +using namespace EE::UI::Doc; + +namespace ecode { + +class LSPClientServer; + +class LSPDocumentClient : public TextDocument::Client { + public: + LSPDocumentClient( LSPClientServer* server, TextDocument* doc ); + + virtual void onDocumentTextChanged(); + virtual void onDocumentUndoRedo( const TextDocument::UndoRedo& eventType ); + virtual void onDocumentCursorChange( const TextPosition& ); + virtual void onDocumentSelectionChange( const TextRange& ); + virtual void onDocumentLineCountChange( const size_t& lastCount, const size_t& newCount ); + virtual void onDocumentLineChanged( const Int64& lineIndex ); + virtual void onDocumentSaved( TextDocument* ); + virtual void onDocumentClosed( TextDocument* ); + virtual void onDocumentDirtyOnFileSystem( TextDocument* ); + virtual void onDocumentMoved( TextDocument* ); + + void notifyOpen(); + protected: + LSPClientServer* mServer{ nullptr }; + TextDocument* mDoc{ nullptr }; + int mVersion{ 0 }; +}; + +} // namespace ecode + +#endif // ECODE_LSPDOCUMENTCLIENT_HPP diff --git a/src/tools/ecode/plugins/lsp/lspplugin.cpp b/src/tools/ecode/plugins/lsp/lspplugin.cpp deleted file mode 100644 index 8f524717a..000000000 --- a/src/tools/ecode/plugins/lsp/lspplugin.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#include "lspplugin.hpp" -#include -#include -#include -#include -#include - -using json = nlohmann::json; - -namespace ecode { - -#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ ) -#define LSP_THREADED 1 -#else -#define LSP_THREADED 0 -#endif - -UICodeEditorPlugin* LSPPlugin::New( const PluginManager* pluginManager ) { - return eeNew( LSPPlugin, ( pluginManager ) ); -} - -LSPPlugin::LSPPlugin( const PluginManager* pluginManager ) : - mPool( pluginManager->getThreadPool() ) { -#if LSP_THREADED - mPool->run( [&, pluginManager] { load( pluginManager ); }, [] {} ); -#else - load( pluginManager ); -#endif -} - -void LSPPlugin::load( const PluginManager* pluginManager ) { - std::vector paths; - std::string path( pluginManager->getResourcesPath() + "plugins/lsp.json" ); - if ( FileSystem::fileExists( path ) ) - paths.emplace_back( path ); - path = pluginManager->getPluginsPath() + "lsp.json"; - if ( FileSystem::fileExists( path ) || - FileSystem::fileWrite( path, "{\n\"config\":{},\n\"lsp\":[]\n}\n" ) ) { - mConfigPath = path; - paths.emplace_back( path ); - } - if ( paths.empty() ) - return; - for ( const auto& path : paths ) { - try { - loadLSPConfig( path ); - } catch ( json::exception& e ) { - Log::error( "Parsing linter \"%s\" failed:\n%s", path.c_str(), e.what() ); - } - } - mReady = mClientManager.clientCount() > 0; - if ( mReady ) - fireReadyCbs(); -} - -void LSPPlugin::loadLSPConfig( const std::string& path ) { - -} - -LSPPlugin::~LSPPlugin() { - mClosing = true; - Lock l( mDocMutex ); - for ( const auto& editor : mEditors ) { - for ( auto listener : editor.second ) - editor.first->removeEventListener( listener ); - editor.first->unregisterPlugin( this ); - } -} - -void LSPPlugin::onRegister( UICodeEditor* editor ) { - Lock l( mDocMutex ); - std::vector listeners; - listeners.push_back( editor->addEventListener( Event::OnDocumentLoaded, [&]( const Event* ) { - - } ) ); - - listeners.push_back( - editor->addEventListener( Event::OnDocumentClosed, [&]( const Event* event ) { - Lock l( mDocMutex ); - const DocEvent* docEvent = static_cast( event ); - TextDocument* doc = docEvent->getDoc(); - mDocs.erase( doc ); - } ) ); - - listeners.push_back( - editor->addEventListener( Event::OnDocumentChanged, [&, editor]( const Event* ) { - TextDocument* oldDoc = mEditorDocs[editor]; - TextDocument* newDoc = editor->getDocumentRef().get(); - Lock l( mDocMutex ); - mDocs.erase( oldDoc ); - mEditorDocs[editor] = newDoc; - } ) ); - - listeners.push_back( - editor->addEventListener( Event::OnCursorPosChange, [&, editor]( const Event* ) {} ) ); - - listeners.push_back( - editor->addEventListener( Event::OnDocumentSyntaxDefinitionChange, [&]( const Event* ev ) { - // const DocSyntaxDefEvent* event = static_cast( ev ); - } ) ); - - mEditors.insert( { editor, listeners } ); - mDocs.insert( editor->getDocumentRef().get() ); - mEditorDocs[editor] = editor->getDocumentRef().get(); -} - -void LSPPlugin::onUnregister( UICodeEditor* editor ) { - if ( mClosing ) - return; - Lock l( mDocMutex ); - TextDocument* doc = mEditorDocs[editor]; - auto cbs = mEditors[editor]; - for ( auto listener : cbs ) - editor->removeEventListener( listener ); - mEditors.erase( editor ); - mEditorDocs.erase( editor ); - for ( auto editor : mEditorDocs ) - if ( editor.second == doc ) - return; - mDocs.erase( doc ); -} - -} // namespace ecode diff --git a/src/tools/ecode/terminalmanager.cpp b/src/tools/ecode/terminalmanager.cpp index 5e6825a46..ab17e0607 100644 --- a/src/tools/ecode/terminalmanager.cpp +++ b/src/tools/ecode/terminalmanager.cpp @@ -195,114 +195,6 @@ UITerminal* TerminalManager::createNewTerminal( const std::string& title, UITabW { "open-file", "download-file-web", "open-folder", "debug-draw-highlight-toggle", "debug-draw-boxes-toggle", "debug-draw-debug-data", "debug-widget-tree-view", "open-locatebar", "open-global-search", "menu-toggle", "console-toggle", "go-to-line" } ); - term->setCommand( "switch-side-panel", [&] { mApp->switchSidePanel(); } ); - term->setCommand( "fullscreen-toggle", [&]() { mApp->fullscreenToggle(); } ); - for ( int i = 1; i <= 10; i++ ) - term->setCommand( String::format( "switch-to-tab-%d", i ), - [&, i] { mApp->getSplitter()->switchToTab( i - 1 ); } ); - term->setCommand( "switch-to-first-tab", [&] { - UITabWidget* tabWidget = - mApp->getSplitter()->tabWidgetFromWidget( mApp->getSplitter()->getCurWidget() ); - if ( tabWidget && tabWidget->getTabCount() ) { - mApp->getSplitter()->switchToTab( 0 ); - } - } ); - term->setCommand( "switch-to-last-tab", [&] { - UITabWidget* tabWidget = - mApp->getSplitter()->tabWidgetFromWidget( mApp->getSplitter()->getCurWidget() ); - if ( tabWidget && tabWidget->getTabCount() ) { - mApp->getSplitter()->switchToTab( tabWidget->getTabCount() - 1 ); - } - } ); - term->setCommand( "switch-to-previous-split", [&] { - mApp->getSplitter()->switchPreviousSplit( mApp->getSplitter()->getCurWidget() ); - } ); - term->setCommand( "switch-to-next-split", [&] { - mApp->getSplitter()->switchNextSplit( mApp->getSplitter()->getCurWidget() ); - } ); - term->setCommand( "close-tab", [&] { - mApp->getSplitter()->tryTabClose( mApp->getSplitter()->getCurWidget() ); - } ); - term->setCommand( "create-new", [&] { - auto d = mApp->getSplitter()->createCodeEditorInTabWidget( - mApp->getSplitter()->tabWidgetFromWidget( mApp->getSplitter()->getCurWidget() ) ); - d.first->getTabWidget()->setTabSelected( d.first ); - } ); - term->setCommand( "next-tab", [&] { - UITabWidget* tabWidget = - mApp->getSplitter()->tabWidgetFromWidget( mApp->getSplitter()->getCurWidget() ); - if ( tabWidget && tabWidget->getTabCount() > 1 ) { - UITab* tab = (UITab*)mApp->getSplitter()->getCurWidget()->getData(); - Uint32 tabIndex = tabWidget->getTabIndex( tab ); - mApp->getSplitter()->switchToTab( ( tabIndex + 1 ) % tabWidget->getTabCount() ); - } - } ); - term->setCommand( "previous-tab", [&] { - UITabWidget* tabWidget = - mApp->getSplitter()->tabWidgetFromWidget( mApp->getSplitter()->getCurWidget() ); - if ( tabWidget && tabWidget->getTabCount() > 1 ) { - UITab* tab = (UITab*)mApp->getSplitter()->getCurWidget()->getData(); - Uint32 tabIndex = tabWidget->getTabIndex( tab ); - Int32 newTabIndex = (Int32)tabIndex - 1; - mApp->getSplitter()->switchToTab( - newTabIndex < 0 ? tabWidget->getTabCount() - newTabIndex : newTabIndex ); - } - } ); - term->setCommand( "split-right", [&] { - mApp->getSplitter()->split( UICodeEditorSplitter::SplitDirection::Right, - mApp->getSplitter()->getCurWidget(), - mApp->getSplitter()->curEditorExistsAndFocused() ); - } ); - term->setCommand( "split-bottom", [&] { - mApp->getSplitter()->split( UICodeEditorSplitter::SplitDirection::Bottom, - mApp->getSplitter()->getCurWidget(), - mApp->getSplitter()->curEditorExistsAndFocused() ); - } ); - term->setCommand( "split-left", [&] { - mApp->getSplitter()->split( UICodeEditorSplitter::SplitDirection::Left, - mApp->getSplitter()->getCurWidget(), - mApp->getSplitter()->curEditorExistsAndFocused() ); - } ); - term->setCommand( "split-top", [&] { - mApp->getSplitter()->split( UICodeEditorSplitter::SplitDirection::Top, - mApp->getSplitter()->getCurWidget(), - mApp->getSplitter()->curEditorExistsAndFocused() ); - } ); - term->setCommand( "split-swap", [&] { - if ( UISplitter* splitter = - mApp->getSplitter()->splitterFromWidget( mApp->getSplitter()->getCurWidget() ) ) - splitter->swap(); - } ); - term->setCommand( "terminal-split-right", [&, term] { - mApp->getSplitter()->split( UICodeEditorSplitter::SplitDirection::Right, - mApp->getSplitter()->getCurWidget(), false ); - term->execute( "create-new-terminal" ); - } ); - term->setCommand( "terminal-split-bottom", [&, term] { - mApp->getSplitter()->split( UICodeEditorSplitter::SplitDirection::Bottom, - mApp->getSplitter()->getCurWidget(), false ); - term->execute( "create-new-terminal" ); - } ); - term->setCommand( "terminal-split-left", [&, term] { - mApp->getSplitter()->split( UICodeEditorSplitter::SplitDirection::Left, - mApp->getSplitter()->getCurWidget(), false ); - term->execute( "create-new-terminal" ); - } ); - term->setCommand( "terminal-split-top", [&, term] { - mApp->getSplitter()->split( UICodeEditorSplitter::SplitDirection::Top, - mApp->getSplitter()->getCurWidget(), false ); - term->execute( "create-new-terminal" ); - } ); - term->setCommand( "split-swap", [&] { - if ( UISplitter* splitter = - mApp->getSplitter()->splitterFromWidget( mApp->getSplitter()->getCurWidget() ) ) - splitter->swap(); - } ); - term->setCommand( UITerminal::getExclusiveModeToggleCommandName(), [term, this] { - term->setExclusiveMode( !term->getExclusiveMode() ); - mApp->updateTerminalMenu(); - } ); - term->setCommand( "create-new-terminal", [&] { createNewTerminal(); } ); term->setCommand( "terminal-rename", [&, term] { UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::INPUT, mApp->i18n( "new_terminal_name", "New terminal name:" ) ); @@ -317,17 +209,6 @@ UITerminal* TerminalManager::createNewTerminal( const std::string& title, UITabW term->setFocus(); } ); } ); - term->setCommand( "move-panel-left", [&] { mApp->panelPosition( PanelPosition::Left ); } ); - term->setCommand( "move-panel-right", [&] { mApp->panelPosition( PanelPosition::Right ); } ); - term->setCommand( "close-app", [&] { mApp->closeApp(); } ); - term->setCommand( "open-file", [&] { mApp->openFileDialog(); } ); - term->setCommand( "open-folder", [&] { mApp->openFolderDialog(); } ); - term->setCommand( "console-toggle", [&] { mApp->consoleToggle(); } ); - term->setCommand( "menu-toggle", [&] { mApp->toggleSettingsMenu(); } ); - term->setCommand( "open-global-search", [&] { mApp->showGlobalSearch( false ); } ); - term->setCommand( "open-locatebar", [&] { mApp->getFileLocator()->showLocateBar(); } ); - term->setCommand( "download-file-web", [&] { mApp->downloadFileWebDialog(); } ); - term->setCommand( "switch-to-previous-colorscheme", [&] { auto it = mTerminalColorSchemes.find( mTerminalCurrentColorScheme ); auto prev = std::prev( it, 1 ); @@ -343,12 +224,12 @@ UITerminal* TerminalManager::createNewTerminal( const std::string& title, UITabW ? it->first : mTerminalColorSchemes.begin()->first ); } ); - term->setCommand( "reopen-closed-tab", [&] { mApp->reopenClosedTab(); } ); - term->setCommand( "plugin-manager", [&] { mApp->createPluginManagerUI(); } ); - term->setCommand( "debug-draw-boxes-toggle", [&] { mApp->debugDrawBoxesToggle(); } ); - term->setCommand( "debug-draw-highlight-toggle", [&] { mApp->debugDrawHighlightToggle(); } ); - term->setCommand( "debug-draw-debug-data", [&] { mApp->debugDrawData(); } ); - term->setCommand( "debug-widget-tree-view", [&] { mApp->createWidgetInspector(); } ); + term->setCommand( UITerminal::getExclusiveModeToggleCommandName(), [term, this] { + term->setExclusiveMode( !term->getExclusiveMode() ); + mApp->updateTerminalMenu(); + } ); + mApp->registerUnlockedCommands( *term ); + mApp->getSplitter()->registerSplitterCommands( *term ); term->setFocus(); return term; #endif diff --git a/src/tools/ecode/widgetcommandexecuter.hpp b/src/tools/ecode/widgetcommandexecuter.hpp index 99fb16b53..4e3ed317a 100644 --- a/src/tools/ecode/widgetcommandexecuter.hpp +++ b/src/tools/ecode/widgetcommandexecuter.hpp @@ -43,6 +43,19 @@ class UIGlobalSearchBar : public UILinearLayout, public WidgetCommandExecuter { } }; +class UIMainLayout : public UIRelativeLayout, public WidgetCommandExecuter { + public: + static UIMainLayout* New() { return eeNew( UIMainLayout, () ); } + + UIMainLayout() : + UIRelativeLayout( "mainlayout" ), + WidgetCommandExecuter( getUISceneNode()->getWindow()->getInput() ) {} + + virtual Uint32 onKeyDown( const KeyEvent& event ) { + return WidgetCommandExecuter::onKeyDown( event ); + } +}; + } // namespace ecode #endif // ECODE_WIDGETCOMMANDEXECUTER_HPP