#ifndef ECODE_PLUGINMANAGER_HPP #define ECODE_PLUGINMANAGER_HPP #include "../projectsearch.hpp" #include "lsp/lspprotocol.hpp" #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 FileSystemListener; 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, 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 Undefined }; enum class PluginMessageFormat { Empty, JSON, Diagnostics, CodeCompletion, LanguageServerCapabilities, SignatureHelp, ProjectSearchResult, ShowMessage, ShowDocument, SymbolInformation, DiagnosticsCodeAction }; 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 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 PluginInmediateResponse { 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( PluginInmediateResponse 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 PluginInmediateResponse& getResponse() const { return mResponse; } bool isResponse() const { return mId == -2 && !mResponse.data.empty(); } protected: PluginIDType mId{ 0 }; PluginInmediateResponse mResponse; //! Some requests can be responded inmediatly, 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, std::shared_ptr pool, const OnLoadFileCb& loadFileCb ); ~PluginManager(); void registerPlugin( const PluginDefinition& def ); UICodeEditorPlugin* 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; UICodeEditorSplitter* getSplitter() 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( UICodeEditorPlugin* pluginWho, PluginMessageType type, PluginMessageFormat format, const void* data ); void sendResponse( UICodeEditorPlugin* pluginWho, PluginMessageType type, PluginMessageFormat format, const void* data, const PluginIDType& responseID ); void sendBroadcast( UICodeEditorPlugin* pluginWho, PluginMessageType, PluginMessageFormat, const void* data ); void sendBroadcast( const PluginMessageType& notification, const PluginMessageFormat& format, void* data ); void subscribeMessages( UICodeEditorPlugin* plugin, std::function cb ); void unsubscribeMessages( UICodeEditorPlugin* 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; protected: using SubscribedPlugins = std::map>; friend class App; std::string mResourcesPath; std::string mPluginsPath; std::string mWorkspaceFolder; std::map mPlugins; std::map mPluginsEnabled; std::map mDefinitions; std::shared_ptr mThreadPool; UICodeEditorSplitter* mSplitter{ nullptr }; FileSystemListener* mFileSystemListener{ nullptr }; Mutex mSubscribedPluginsMutex; SubscribedPlugins mSubscribedPlugins; OnLoadFileCb mLoadFileFn; bool mClosing{ false }; bool hasDefinition( const std::string& id ); void setSplitter( UICodeEditorSplitter* splitter ); void setFileSystemListener( FileSystemListener* listener ); }; class PluginsModel : public Model { public: enum Columns { Id, Title, Enabled, Description, Version }; static std::shared_ptr New( PluginManager* manager ); PluginsModel( PluginManager* manager ) : mManager( 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; virtual void update() { onModelUpdate(); } PluginManager* getManager() const; protected: PluginManager* mManager; std::vector mColumnNames{ "Id", "Title", "Enabled", "Description", "Version" }; }; class UIPluginManager { public: static UIWindow* New( UISceneNode* sceneNode, PluginManager* manager, std::function loadFileCb ); }; class Plugin : public UICodeEditorPlugin { public: explicit Plugin( PluginManager* manager ); void subscribeFileSystemListener(); void unsubscribeFileSystemListener(); bool isReady() const; bool isLoading() const { return mLoading; } bool isShuttingDown() const; virtual bool hasFileConfig(); virtual std::string getFileConfigPath(); PluginManager* getManager() const; virtual String::HashType getConfigFileHash() { return 0; } protected: PluginManager* mManager{ nullptr }; std::shared_ptr mThreadPool; Uint64 mFileSystemListenerCb{ 0 }; std::string mConfigPath; FileInfo mConfigFileInfo; bool mReady{ false }; bool mLoading{ false }; bool mShuttingDown{ false }; }; class PluginBase : public Plugin { public: explicit PluginBase( PluginManager* manager ) : Plugin( manager ) {} virtual ~PluginBase(); virtual void onRegister( UICodeEditor* ) override; virtual void onUnregister( UICodeEditor* ) override; virtual String::HashType getConfigFileHash() override { return mConfigHash; } protected: //! Keep track of the registered editors + all the listeners registered to each editor std::unordered_map> mEditors; //! Keep track of the documents opened std::set mDocs; //! Documents and Editors mutex Mutex mMutex; //! Keep track of the document pointer of each editor std::unordered_map mEditorDocs; //! Keep track of the key bindings managed by the plugin std::map mKeyBindings; /* cmd, shortcut */ //! If the configuration is stored in a file, keep track of the config hash String::HashType mConfigHash{ 0 }; virtual void onDocumentLoaded( TextDocument* ){}; virtual void onDocumentClosed( TextDocument* ){}; virtual void onDocumentChanged( UICodeEditor*, TextDocument* /*oldDoc*/ ){}; virtual void onRegisterListeners( UICodeEditor*, std::vector& /*listeners*/ ){}; //! Usually used to remove keybindings in an editor virtual void onBeforeUnregister( UICodeEditor* ){}; virtual void onRegisterDocument( TextDocument* ){}; virtual void onUnregisterEditor( UICodeEditor* ){}; //! Usually used to unregister commands in a document virtual void onUnregisterDocument( TextDocument* ){}; }; } // namespace ecode #endif // ECODE_PLUGINMANAGER_HPP