#ifndef ECODE_PLUGINMANAGER_HPP #define ECODE_PLUGINMANAGER_HPP #include "../projectsearch.hpp" #include "lsp/lspprotocol.hpp" #include "plugincontextprovider.hpp" #include #include #include #include #include #include #include #include #include #include #include using namespace EE; using namespace EE::System; using namespace EE::UI; using namespace EE::UI::Models; using namespace EE::UI::Tools; namespace ecode { class PluginManager; class Plugin; class FileSystemListener; class ProjectBuildManager; typedef std::function PluginCreatorFn; #ifdef minor #undef minor #endif #ifdef major #undef major #endif struct PluginVersion { PluginVersion() {} PluginVersion( Uint8 major, Uint8 minor, Uint8 patch ) : major( major ), minor( minor ), patch( patch ), string( String::format( "%d.%d.%d", major, minor, patch ) ) {} Uint8 major{ 0 }; /**< major version */ Uint8 minor{ 0 }; /**< minor version */ Uint8 patch{ 0 }; /**< update version */ std::string string; Uint32 getVersion() const { return major * 1000 + minor * 100 + patch; } const std::string& getVersionString() const { return string; } }; struct PluginDefinition { std::string id; std::string name; std::string description; PluginCreatorFn creatorFn; PluginVersion version; PluginCreatorFn creatorSyncFn{ nullptr }; }; enum class PluginCapability { WorkspaceSymbol, TextDocumentSymbol, FoldingRange, Max }; enum class PluginMessageType { WorkspaceFolderChanged, // Broadcast the workspace folder from the application to the plugins Diagnostics, // Broadcast a document diagnostics from the LSP Client CodeCompletion, // Request the LSP Server to start a code completion in the requested document // and position LanguageServerCapabilities, // Request the language server capabilities of a language if there // is any available, it will be returned as a broadcast SignatureHelp, // Request the LSP Server to provide function/method signature help CancelRequest, // Cancel a request ID FindAndOpenClosestURI, // Request a component to find and open the closest path from an URI DocumentFormatting, // Request the LSP Server to format a document SymbolReference, // Request the LSP Server to find a symbol reference in the project ShowMessage, // The LSP server sends a request to the client to show a message on screen ShowDocument, // The LSP server sends a request to the client to show a document WorkspaceSymbol, // Request to the LSP server to query workspace symbols TextDocumentSymbol, // Request to the LSP server the document symbols TextDocumentFlattenSymbol, // Request to the LSP server the document symbols flattened DiagnosticsCodeAction, // Request a code action to anyone that can handle it FileSystemListenerReady, // Broadcast to inform the plugins that the file system listener is // available GetErrorOrWarning, // Request a component to provide the information of an error or warning in a // particular document location GetDiagnostics, // Request the diagnostic information from a cursor position QueryPluginCapability, // Requests / queries if a plugin providers a capability UIReady, // Informs the Plugins that the UI is ready to be used UIThemeReloaded, // Informs the plugins that the UI theme has been reloaded FoldingRanges, // Request to the LSP server the folding ranges of a document WorkspaceDiagnostic, // Informs the current workspace diagnostic LanguageServerReady, // Informs that an LSP server is ready Undefined }; enum class PluginMessageFormat { Empty, JSON, Diagnostics, CodeCompletion, LanguageServerCapabilities, SignatureHelp, ProjectSearchResult, ShowMessage, ShowDocument, SymbolInformation, DiagnosticsCodeAction, FoldingRanges, WorkspaceDiagnosticReport, LSPClientServer, }; class PluginIDType { public: enum class Type { Integer, String, Invalid }; PluginIDType() {} PluginIDType( Int64 val ) : mType( Type::Integer ), mInt( val ) {} PluginIDType( const std::string& val ) : mType( Type::String ), mString( val ) {} bool operator==( const PluginIDType& right ) { return mType == right.mType && ( ( mType == Type::Integer && mInt == right.mInt ) || ( mType == Type::String && mString == right.mString ) ); } bool operator!=( const PluginIDType& right ) { return !( *this == right ); } bool is( const Type& type ) const { return type == mType; } bool isString() const { return Type::String == mType; } bool isInteger() const { return Type::Integer == mType; } bool isValid() const { return mType != Type::Invalid; } std::string toString() { if ( mType == Type::Integer ) return String::toString( mInt ); return mString; } operator Int64() const { return mInt; } operator std::string() { return mString; } const Int64& asInt() const { return mInt; } const std::string& asString() const { return mString; } protected: Type mType{ Type::Invalid }; Int64 mInt{ std::numeric_limits::max() }; std::string mString; }; class LSPClientPlugin; struct PluginMessage { PluginMessageType type{ PluginMessageType::Undefined }; PluginMessageFormat format{ PluginMessageFormat::Empty }; const void* data{ nullptr }; PluginIDType responseID{ 0 }; // 0 if it's not a response; const void* asData() const { return data; } const nlohmann::json& asJSON() const { return *static_cast( data ); } bool isJSON() const { return format == PluginMessageFormat::JSON; } const LSPPublishDiagnosticsParams& asDiagnostics() const { return *static_cast( data ); } const LSPCompletionList& asCodeCompletion() const { return *static_cast( data ); } const LSPServerCapabilities& asLanguageServerCapabilities() const { return *static_cast( data ); } const LSPSignatureHelp& asSignatureHelp() const { return *static_cast( data ); } const ProjectSearch::Result& asProjectSearchResult() const { return *static_cast( data ); } const LSPShowMessageParams& asShowMessage() const { return *static_cast( data ); } const LSPShowDocumentParams& asShowDocument() const { return *static_cast( data ); } const LSPSymbolInformationList& asSymbolInformation() const { return *static_cast( data ); } const LSPDiagnosticsCodeAction& asDiasnosticsCodeAction() const { return *static_cast( data ); } const LSPWorkspaceDiagnosticReport& asLSPWorkspaceDiagnosticReport() const { return *static_cast( data ); } const PluginIDType& asPluginID() const { return *static_cast( data ); } bool isResponse() const { return -1 != responseID && 0 != responseID; } bool isRequest() const { return 0 == responseID; } bool isBroadcast() const { return -1 == responseID; } }; struct PluginImmediateResponse { PluginMessageType type{ PluginMessageType::Undefined }; nlohmann::json data; }; class PluginRequestHandle { public: static PluginRequestHandle broadcast() { return PluginRequestHandle( -1 ); } static PluginRequestHandle empty() { return PluginRequestHandle(); } PluginRequestHandle() {} PluginRequestHandle( PluginIDType id ) : mId( std::move( id ) ) {} explicit PluginRequestHandle( PluginImmediateResponse msg ) : mId( -2 ), mResponse( std::move( msg ) ) {} virtual const PluginIDType& id() const { return mId; } virtual void cancel() {} bool isEmpty() const { return mId == 0; } bool isBroadcast() const { return mId == -1; } const PluginImmediateResponse& getResponse() const { return mResponse; } bool isResponse() const { return mId == -2 && !mResponse.data.empty(); } protected: PluginIDType mId{ 0 }; PluginImmediateResponse mResponse; //! Some requests can be responded immediately, so the //! message comes in the handle }; class PluginManager { public: static constexpr int versionNumber( int major, int minor, int patch ) { return ( ( major ) * 1000 + ( minor ) * 100 + ( patch ) ); } static std::string versionString( int major, int minor, int patch ) { return String::format( "%d.%d.%.d", major, minor, patch ); } using OnFileLoadedCb = std::function; using OnLoadFileCb = std::function; PluginManager( const std::string& resourcesPath, const std::string& pluginsPath, const std::string& configPath, std::shared_ptr pool, const OnLoadFileCb& loadFileCb, PluginContextProvider* context ); ~PluginManager(); void registerPlugin( const PluginDefinition& def ); void setUIReady(); void setUIThemeReloaded(); Plugin* get( const std::string& id ); bool setEnabled( const std::string& id, bool enable, bool sync = false ); bool isEnabled( const std::string& id ) const; bool reload( const std::string& id ); const std::string& getResourcesPath() const; const std::string& getPluginsPath() const; const std::map& getPluginsEnabled() const; void onNewEditor( UICodeEditor* editor ); void setPluginsEnabled( const std::map& pluginsEnabled, bool sync ); const std::shared_ptr& getThreadPool() const; std::function onPluginEnabled; const std::map& getDefinitions() const; const PluginDefinition* getDefinitionIndex( const Int64& index ) const; /** This is the code editor splitter. Where documents/terminals/etc are opened */ UICodeEditorSplitter* getSplitter() const; /** This is the splitter between the code editor splitter and the bottom panel. */ UISplitter* getMainSplitter() const; UISceneNode* getUISceneNode() const; const std::string& getWorkspaceFolder() const; void setWorkspaceFolder( const std::string& workspaceFolder ); PluginRequestHandle sendRequest( PluginMessageType type, PluginMessageFormat format, const void* data ); PluginRequestHandle sendRequest( Plugin* pluginWho, PluginMessageType type, PluginMessageFormat format, const void* data ); void sendResponse( Plugin* pluginWho, PluginMessageType type, PluginMessageFormat format, const void* data, const PluginIDType& responseID ); void sendBroadcast( Plugin* pluginWho, PluginMessageType, PluginMessageFormat, const void* data ); void sendBroadcast( const PluginMessageType& notification, const PluginMessageFormat& format, void* data ); void subscribeMessages( Plugin* plugin, std::function cb ); void unsubscribeMessages( Plugin* plugin ); void subscribeMessages( const std::string& uniqueComponentId, std::function cb ); void unsubscribeMessages( const std::string& uniqueComponentId ); FileSystemListener* getFileSystemListener() const { return mFileSystemListener; }; const OnLoadFileCb& getLoadFileFn() const; bool isPluginReloadEnabled() const; void setPluginReloadEnabled( bool pluginReloadEnabled ); void subscribeFileSystemListener( Plugin* plugin ); void unsubscribeFileSystemListener( Plugin* plugin ); bool isClosing() const; PluginContextProvider* getPluginContext() const { return mPluginContext; } void forEachPlugin( std::function fn ); void setPluginsDisabled( bool pluginsDisabled ) { mPluginsDisabled = pluginsDisabled; } bool pluginsDisabled() const { return mPluginsDisabled; } protected: using SubscribedPlugins = std::map>; friend class App; std::string mResourcesPath; std::string mPluginsPath; std::string mConfigPath; std::string mWorkspaceFolder; std::map mPlugins; std::map mPluginsEnabled; std::map mDefinitions; std::shared_ptr mThreadPool; UICodeEditorSplitter* mSplitter{ nullptr }; UISplitter* mMainSplitter{ nullptr }; FileSystemListener* mFileSystemListener{ nullptr }; PluginContextProvider* mPluginContext{ nullptr }; Mutex mSubscribedPluginsMutex; Mutex mPluginsFSSubsMutex; SubscribedPlugins mSubscribedPlugins; OnLoadFileCb mLoadFileFn; Uint64 mFileSystemListenerCb{ 0 }; UnorderedSet mPluginsFSSubs; bool mClosing{ false }; bool mPluginReloadEnabled{ false }; bool mPluginsDisabled{ false }; bool hasDefinition( const std::string& id ); void setSplitter( UICodeEditorSplitter* splitter ); void setMainSplitter( UISplitter* splitter ); void setFileSystemListener( FileSystemListener* listener ); void subscribeFileSystemListener(); void unsubscribeFileSystemListener(); }; class PluginsModel : public Model { public: enum Columns { Id, Title, Enabled, Description, Version, Count }; static std::shared_ptr New( PluginManager* manager ); PluginsModel( PluginManager* manager ); virtual ~PluginsModel() {} virtual size_t rowCount( const ModelIndex& ) const; virtual size_t columnCount( const ModelIndex& ) const { return mColumnNames.size(); } virtual std::string columnName( const size_t& col ) const; virtual void setColumnName( const size_t& index, const std::string& name ) { eeASSERT( index <= Columns::Version ); mColumnNames[index] = name; } virtual Variant data( const ModelIndex& index, ModelRole role = ModelRole::Display ) const; PluginManager* getManager() const; protected: PluginManager* mManager; std::array mColumnNames{ "Id", "Title", "Enabled", "Description", "Version" }; }; class UIPluginManager { public: static UIWindow* New( UISceneNode* sceneNode, PluginManager* manager, std::function loadFileCb ); }; } // namespace ecode #endif // ECODE_PLUGINMANAGER_HPP