Răsfoiți Sursa

repo: 添加来自 matheuter 的 actor 补丁

zinface 1 an în urmă
părinte
comite
696471371e
1 a modificat fișierele cu 2881 adăugiri și 0 ștergeri
  1. 2881 0
      patchs/base-39b61c737a6f38aaf4bb59e8eb6f8c438e48c06e-actor.patch

+ 2881 - 0
patchs/base-39b61c737a6f38aaf4bb59e8eb6f8c438e48c06e-actor.patch

@@ -0,0 +1,2881 @@
+From 39b61c737a6f38aaf4bb59e8eb6f8c438e48c06e Mon Sep 17 00:00:00 2001
+From: matheuter <[email protected]>
+Date: Thu, 20 Apr 2023 16:39:20 +0800
+Subject: [PATCH 1/4] add actor to plugin_v2
+
+---
+ src/actor.h                                   | 129 +++++
+ src/actorprocessor.cpp                        |  67 +++
+ src/actorprocessor.h                          |  67 +++
+ src/cceditor/ccnotepad.cpp                    |  73 ++-
+ src/cceditor/ccnotepad.h                      |   9 +-
+ src/functiontraits.h                          |  81 +++
+ src/plugin.h                                  |   2 +
+ src/plugin/CMakeLists.txt                     |  16 +-
+ src/plugin/filetreeview/CMakeLists.txt        |  74 +++
+ src/plugin/filetreeview/abstractfile.cpp      |   6 +
+ src/plugin/filetreeview/abstractfile.h        |  11 +
+ src/plugin/filetreeview/actor.h               | 120 ++++
+ src/plugin/filetreeview/actorprocessor.cpp    |  67 +++
+ src/plugin/filetreeview/actorprocessor.h      |  66 +++
+ src/plugin/filetreeview/filesystemmodel.cpp   | 526 ++++++++++++++++++
+ src/plugin/filetreeview/filesystemmodel.h     | 193 +++++++
+ src/plugin/filetreeview/filetreeview.cpp      | 195 +++++++
+ src/plugin/filetreeview/filetreeview.h        |  73 +++
+ .../filetreeview/filetreeviewexport.cpp       |  77 +++
+ .../filetreeview/filetreeviewplugin.cpp       | 121 ++++
+ src/plugin/filetreeview/filetreeviewplugin.h  |  40 ++
+ 21 files changed, 2001 insertions(+), 12 deletions(-)
+ create mode 100644 src/actor.h
+ create mode 100644 src/actorprocessor.cpp
+ create mode 100644 src/actorprocessor.h
+ create mode 100644 src/functiontraits.h
+ create mode 100644 src/plugin/filetreeview/CMakeLists.txt
+ create mode 100644 src/plugin/filetreeview/abstractfile.cpp
+ create mode 100644 src/plugin/filetreeview/abstractfile.h
+ create mode 100644 src/plugin/filetreeview/actor.h
+ create mode 100644 src/plugin/filetreeview/actorprocessor.cpp
+ create mode 100644 src/plugin/filetreeview/actorprocessor.h
+ create mode 100644 src/plugin/filetreeview/filesystemmodel.cpp
+ create mode 100644 src/plugin/filetreeview/filesystemmodel.h
+ create mode 100644 src/plugin/filetreeview/filetreeview.cpp
+ create mode 100644 src/plugin/filetreeview/filetreeview.h
+ create mode 100644 src/plugin/filetreeview/filetreeviewexport.cpp
+ create mode 100644 src/plugin/filetreeview/filetreeviewplugin.cpp
+ create mode 100644 src/plugin/filetreeview/filetreeviewplugin.h
+
+diff --git a/src/actor.h b/src/actor.h
+new file mode 100644
+index 0000000..86378d0
+--- /dev/null
++++ b/src/actor.h
+@@ -0,0 +1,129 @@
++#pragma once
++#include <cstdint>
++#include <string>
++#include <memory>
++#include <tuple>
++#include <functional>
++
++#include "functiontraits.h"
++
++/**
++ * @brief Actor class, which encapsulates the event model
++ * @note enable_shared_from_this allows us to get safe Pointers internally
++ */
++class Actor : public std::enable_shared_from_this<Actor>
++{
++public:
++    using Ptr = std::shared_ptr<Actor>;
++    using FunctionWapper = std::function<void(void*, void*)>;
++
++public:
++    Actor(){}
++    ~Actor(){}
++
++    /**
++    * @brief Register the callback function and construct the anonymous function with std::bind
++    */
++    template<typename Function>
++    void operator+=(Function&& function_any) noexcept
++    {
++        m_invokeFunctionWapper =  { std::bind(&invoker<Function>::apply, function_any,  std::placeholders::_1, std::placeholders::_2) };
++    }
++
++    /**
++    * @brief Register the callback function and construct the anonymous function with std::bind
++    */
++    template<typename Function>
++    void registerFunction(Function&& function_any) noexcept
++    {
++        m_invokeFunctionWapper =  { std::bind(&invoker<Function>::apply, function_any,  std::placeholders::_1, std::placeholders::_2) };
++    }
++
++    /**
++    * @brief Register the callback function and construct the anonymous function with std::bind
++    */
++    template<typename ... Args>
++    void invoke(Args&& ... args) const noexcept
++    {
++        auto args_tuple = std::make_tuple(std::forward<Args>(args)...);
++        m_invokeFunctionWapper(&args_tuple, nullptr);
++    }
++
++    /**
++    * @brief Register the callback function and construct the anonymous function with std::bind
++    */
++    template<typename R, typename ... Args>
++    R invoke(Args&& ...args) const
++    {
++        using tuple_args_type = std::tuple<Args...>;
++        char data[sizeof(tuple_args_type)];
++        std::tuple<Args...>* tuples_pointer = new (data) tuple_args_type;
++
++        *tuples_pointer = std::make_tuple(std::forward<Args>(args)...);
++
++        R return_value;
++        m_invokeFunctionWapper(tuples_pointer, &return_value);
++        return return_value;
++    }
++
++    Ptr getSharedPtr()
++    {
++        return shared_from_this();
++    }
++
++public:
++    /**
++    * @brief This struct encapsulates a function,
++    * essentially storing the return value and parameters in two variables
++    * which can unify the way the function is stored.
++    */
++    template<typename Function>
++    struct invoker
++    {
++        /**
++         * @brief
++         */
++        static inline void apply(const Function& func, void* bl, void* result)
++        {
++            using tuple_type = typename function_traits<Function>::tuple_type;
++            const tuple_type* tp = static_cast<tuple_type*>(bl);
++            call(func, *tp, result);
++        }
++
++        /**
++         * @brief
++         */
++        template<typename F, typename ... Args>
++        static typename std::enable_if<std::is_void<typename std::result_of<F(Args...)>::type>::value>::type
++        call(const F& f, const std::tuple<Args...>& tp, void*)
++        {
++            call_helper(f, std::make_index_sequence<sizeof... (Args)>{}, tp);
++        }
++
++        /**
++         * @brief
++         */
++        template<typename F, typename ... Args>
++        static typename std::enable_if<!std::is_void<typename std::result_of<F(Args...)>::type>::value>::type
++        call(const F& f, const std::tuple<Args...>& tp, void* result)
++        {
++            auto r = call_helper(f, std::make_index_sequence<sizeof... (Args)>{}, tp);
++            *(decltype(r)*)result = r;
++        }
++
++        /**
++        * @brief
++        */
++        template<typename F, size_t... I, typename ... Args>
++        static auto call_helper(const F& f, const std::index_sequence<I...>& h, const std::tuple<Args...>& tup)
++        {
++            return f(std::get<I>(tup)...);
++        }
++    };
++private:
++    /**
++    * @note m_invokeFunctions is an anonymous lamba expression that encapsulates a function
++    * Functions include Lamba expressions, member functions, funtor, ordinary functions, and function Pointers
++    */
++    FunctionWapper m_invokeFunctionWapper;
++};
+diff --git a/src/actorprocessor.cpp b/src/actorprocessor.cpp
+new file mode 100644
+index 0000000..099d1f6
+--- /dev/null
++++ b/src/actorprocessor.cpp
+@@ -0,0 +1,67 @@
++#include "actorprocessor.h"
++ActorProcessor::ActorProcessor():
++    m_actorMap(new std::unordered_map<std::string, Actor*>)
++{}
++
++/**
++ * @brief
++ */
++ActorProcessor::~ActorProcessor()
++{
++    for (auto& item : (*m_actorMap))
++    {
++        if(item.second != nullptr)
++            delete item.second;
++    }
++    if (m_actorMap)
++        delete m_actorMap;
++}
++
++/**
++ * @brief
++ */
++void ActorProcessor::registerActor(const std::string &route, Actor *actor)
++{
++    m_actorMap->insert(std::make_pair(route,actor));
++}
++
++/**
++ * @brief
++ */
++void ActorProcessor::removeActor(const std::string &route)
++{
++    auto iter = (*m_actorMap).find(route);
++    if(iter != m_actorMap->end())
++    {
++        m_actorMap->erase(route);
++        delete iter->second;
++    }
++}
++
++/**
++ * @brief
++ */
++Actor *ActorProcessor::findActor(const std::string &route)
++{
++    auto iter = (*m_actorMap).find(route);
++    if(iter != m_actorMap->end())
++    {
++        return (*m_actorMap)[route];
++    }
++    return nullptr;
++}
++
++/**
++ * @brief
++ */
++bool ActorProcessor::resetActor(const std::string &route, Actor *actor)
++{
++    auto iter = (*m_actorMap).find(route);
++    if(iter != m_actorMap->end())
++    {
++        m_actorMap->erase(route);
++        delete iter->second;
++        return true;
++    }
++    return false;
++}
+diff --git a/src/actorprocessor.h b/src/actorprocessor.h
+new file mode 100644
+index 0000000..9026909
+--- /dev/null
++++ b/src/actorprocessor.h
+@@ -0,0 +1,67 @@
++#ifndef EVENTPROCESSOR_H
++#define EVENTPROCESSOR_H
++
++#include <string>
++#include <functional>
++#include <map>
++#include <iostream>
++#include <unordered_map>
++#include "actor.h"
++
++class  ActorProcessor
++{
++public:
++    ActorProcessor();
++    ~ActorProcessor();
++public:
++
++    /**
++    * @brief Register the callback function and construct the anonymous function with std::bind
++    */
++    template<typename ... Args>
++    void invoke(const std::string& route,Args&& ... args) const noexcept
++    {
++        if (m_actorMap->find(route) != m_actorMap->end())
++            (*m_actorMap)[route]->invoke(std::forward<Args>(args)...);
++    }
++
++    /**
++    * @brief Register the callback function and construct the anonymous function with std::bind
++    */
++    template<typename R, typename ... Args>
++    R invoke(const std::string& route,Args&& ...args) const
++    {
++        if (m_actorMap->find(route) != m_actorMap->end())
++            return (*m_actorMap)[route]->invoke(std::forward<Args>(args)...);
++        return nullptr;
++    }
++
++    /**
++    * @brief 注册一个actor,其中route是唯一的
++    */
++    void registerActor(const std::string& route, Actor*actor);
++
++    /**
++    * @brief 删除一个actor
++    */
++    void removeActor(const std::string& route);
++
++    /**
++    * @brief 查找一个actor, 返回其指针
++    */
++    Actor* findActor(const std::string& route);
++
++    /**
++    * @brief 重置一个actor,注意原有的actor会被删除
++    */
++    bool resetActor(const std::string& route,Actor*actor);
++
++private:
++    std::unordered_map<std::string, Actor*>* m_actorMap;
++private:
++    // not allow copy constroct
++    ActorProcessor(ActorProcessor&&)=delete;
++    ActorProcessor& operator=(const ActorProcessor&)=delete;
++};
++
++#endif // EVENTPROCESSOR_H
+diff --git a/src/cceditor/ccnotepad.cpp b/src/cceditor/ccnotepad.cpp
+index eb3ad4f..b74f1ca 100755
+--- a/src/cceditor/ccnotepad.cpp
++++ b/src/cceditor/ccnotepad.cpp
+@@ -32,6 +32,8 @@
+ #include "pluginmgr.h"
+ #include "plugin.h"
+ #include "pluginGl.h"
++#include "actor.h"
++#include "actorprocessor.h"
+ #endif
+ 
+ #ifdef Q_OS_WIN
+@@ -1211,6 +1213,10 @@ CCNotePad::CCNotePad(bool isMainWindows, QWidget *parent)
+ 		restoreGeometry(lastGeo);
+ 	}
+ #endif
++
++#ifdef NO_PLUGIN
++    processor = new ActorProcessor();
++#endif
+ 	}
+ 
+ CCNotePad::~CCNotePad()
+@@ -1748,9 +1754,10 @@ void CCNotePad::onPlugFound(NDD_PROC_DATA& procData, QMenu* pUserData)
+ 	{
+ 		QAction* pAction = new QAction(procData.m_strPlugName, pMenu);
+ 		pMenu->addAction(pAction);
+-	pAction->setText(procData.m_strPlugName);
+-	pAction->setData(procData.m_strFilePath);
+-	connect(pAction, &QAction::triggered, this, &CCNotePad::onPlugWork);
++        pAction->setText(procData.m_strPlugName);
++        pAction->setData(procData.m_strFilePath);
++        connect(pAction, &QAction::triggered, this, &CCNotePad::onPlugWork);
++        connect(pAction, &QAction::triggered, this, &CCNotePad::onPlugWorkV2);
+ 	}
+ 	else if (procData.m_menuType == 1)
+ 	{
+@@ -1761,6 +1768,7 @@ void CCNotePad::onPlugFound(NDD_PROC_DATA& procData, QMenu* pUserData)
+ 		//菜单句柄通过procData传递到插件中
+ 		procData.m_rootMenu = pluginMenu;
+ 		sendParaToPlugin(procData);
++        sendParaToPluginV2(procData);
+ 	}
+ 	else
+ 	{
+@@ -1796,6 +1804,39 @@ void CCNotePad::onPlugWork(bool check)
+ 	}
+ }
+ 
++//真正执行插件的工作
++void CCNotePad::onPlugWorkV2(bool check)
++{
++
++    qDebug() << "test1";
++    QAction* pAct = dynamic_cast<QAction*>(sender());
++    if (pAct != nullptr)
++    {
++        QString plugPath = pAct->data().toString();
++
++        QLibrary* pLib = new QLibrary(plugPath);
++
++        NDD_PROC_MAIN_V2_CALLBACK pMainCallBack;
++        pMainCallBack = (NDD_PROC_MAIN_V2_CALLBACK)pLib->resolve("NDD_PROC_MAIN_V2");
++
++        if (pMainCallBack != NULL)
++        {
++            std::function<QsciScintilla* ()> foundCallBack = std::bind(&CCNotePad::getCurEditView, this);
++            Actor* actor = new Actor;
++
++            actor->registerFunction([this](QString name, int num){openFile(name,num);});
++
++            processor->registerActor("openFile",actor);
++            pMainCallBack(this, plugPath, processor, nullptr);
++        }
++        else
++        {
++            ui.statusBar->showMessage(tr("plugin %1 load failed !").arg(plugPath), 10000);
++        }
++
++    }
++}
++
+ //把插件需要的参数,传递到插件中去
+ void CCNotePad::sendParaToPlugin(NDD_PROC_DATA& procData)
+ {
+@@ -1818,6 +1859,32 @@ void CCNotePad::sendParaToPlugin(NDD_PROC_DATA& procData)
+ 		}
+ }
+ 
++
++void CCNotePad::sendParaToPluginV2(NDD_PROC_DATA& procData)
++{
++        qDebug() <<"test5";
++        QString plugPath = procData.m_strFilePath;
++
++        QLibrary* pLib = new QLibrary(plugPath);
++
++        NDD_PROC_MAIN_V2_CALLBACK pMainCallBack;
++        pMainCallBack = (NDD_PROC_MAIN_V2_CALLBACK)pLib->resolve("NDD_PROC_MAIN_V2");
++
++        if (pMainCallBack != NULL)
++        {
++            Actor* actor = new Actor;
++
++            actor->registerFunction([this](QString name, int num){openFile(name,num);});
++
++            processor->registerActor("openFile",actor);
++            pMainCallBack(this, plugPath, processor, &procData);
++        }
++        else
++        {
++            ui.statusBar->showMessage(tr("plugin %1 load failed !").arg(plugPath), 10000);
++        }
++}
++
+ void CCNotePad::loadPluginProcs(QString strLibDir, QMenu* pMenu)
+ {
+ 	std::function<void(NDD_PROC_DATA&, QMenu*)> foundCallBack = std::bind(&CCNotePad::onPlugFound, this, std::placeholders::_1, std::placeholders::_2);
+diff --git a/src/cceditor/ccnotepad.h b/src/cceditor/ccnotepad.h
+index 58e5eec..d2939ad 100755
+--- a/src/cceditor/ccnotepad.h
++++ b/src/cceditor/ccnotepad.h
+@@ -34,6 +34,7 @@ struct HexFileMgr;
+ struct TextFileMgr;
+ struct BigTextEditFileMgr;
+ class QtLangSet;
++class ActorProcessor;
+ 
+ static const char* Tail_Thread = "tailthread";
+ 
+@@ -349,8 +350,10 @@ private slots:
+ 	void on_loadReceneFile();
+ 	void slot_pluginMgr();
+ #ifdef NO_PLUGIN
++    void onPlugWorkV2(bool check);
+ 	void onPlugWork(bool check);
+-	void sendParaToPlugin(NDD_PROC_DATA& procData);
++    void sendParaToPlugin(NDD_PROC_DATA& procData);
++    void sendParaToPluginV2(NDD_PROC_DATA& procData);
+ #endif
+ 	void slot_showWebAddr(bool check);
+ 	void slot_langFileSuffix();
+@@ -524,6 +527,10 @@ private:
+ 	
+ 
+ 	QSharedMemory* m_shareMem;
++#ifdef NO_PLUGIN
++    //actor执行与存储单元
++    ActorProcessor* processor;
++#endif
+ 
+ 
+ 	//最近打开的对比文件和目录列表。做一个环形区
+diff --git a/src/functiontraits.h b/src/functiontraits.h
+new file mode 100644
+index 0000000..314b58b
+--- /dev/null
++++ b/src/functiontraits.h
+@@ -0,0 +1,81 @@
++#ifndef FUNCTIONTRAITS_H
++#define FUNCTIONTRAITS_H
++
++#include <utility>
++#include <functional>
++#include <algorithm>
++#include <tuple>
++
++template<typename T>
++struct function_traits;
++
++template<typename ReturnType, typename... Args>
++struct function_traits<ReturnType(Args...)>
++{
++public:
++    enum {
++        value = sizeof...(Args)
++    };
++    using function_type         = ReturnType(*)(Args...);
++    using return_type           = ReturnType;
++    using stl_function_type     = std::function<function_type>;
++    using function_pointer_type = ReturnType(*)(Args...);
++    using lambda_function_type  = ReturnType(*)(Args...);
++
++    using tuple_type = std::tuple<std::remove_cv_t<std::remove_reference_t<Args>>...> ;
++    using bare_tuple_type =  std::tuple<std::remove_const_t<std::remove_reference_t<Args>>...>;
++
++    template<std::size_t N, typename = typename std::enable_if<(N < value)>::type>
++    using args = typename std::tuple_element<N, std::tuple<Args...>>;
++
++    template <std::size_t N>
++    struct arguments
++    {
++        static_assert(N < value, "[error]:invalid parameter index.");
++        using type = typename std::tuple_element<N, std::tuple<Args...>>::type;
++    };
++};
++
++template<typename ReturnType, typename... Args>
++struct function_traits<ReturnType(*)(Args...)> : function_traits<ReturnType(Args...)> {};
++
++template<typename ReturnType, typename... Args>
++struct function_traits<std::function<ReturnType(Args...)>> : function_traits<ReturnType(Args...)> {};
++
++template <typename ReturnType, typename ClassType, typename... Args>
++struct function_traits<ReturnType(ClassType::*)(Args...)> : function_traits<ReturnType(Args...)>{};
++
++template <typename ReturnType, typename ClassType, typename... Args>
++struct function_traits<ReturnType(ClassType::*)(Args...) const> : function_traits<ReturnType(Args...)> {};
++
++template <typename ReturnType, typename ClassType, typename... Args>
++struct function_traits<ReturnType(ClassType::*)(Args...) volatile> : function_traits<ReturnType(Args...)> {};
++
++template <typename ReturnType, typename ClassType, typename... Args>
++struct function_traits<ReturnType(ClassType::*)(Args...) const volatile> : function_traits<ReturnType(Args...)> {};
++
++template<typename Callable>
++struct function_traits : function_traits<decltype(&Callable::operator())> {};
++
++template<typename Function>
++typename function_traits<Function>::stl_function_type
++function_cast(const Function& lambda)
++{
++    return static_cast<typename function_traits<Function>::stl_function_type>(std::forward<Function>(lambda));
++}
++
++template<typename Function>
++typename function_traits<Function>::stl_function_type
++function_cast(Function&& lambda)
++{
++    return static_cast<typename function_traits<Function>::stl_function_type>(lambda);
++}
++
++template<typename Function>
++typename function_traits<Function>::function_pointer_type
++function_cast(const Function& lambda)
++{
++    return static_cast<typename function_traits<Function>::pointer>(lambda);
++}
++
++#endif // FUNCTIONTRAITS_H
+diff --git a/src/plugin.h b/src/plugin.h
+index c2731be..0f417a9 100755
+--- a/src/plugin.h
++++ b/src/plugin.h
+@@ -4,7 +4,9 @@
+ class QMenu;
+ class QsciScintilla;
+ class QWidget;
++class ActorProcessor;
+ 
+ typedef int (*NDD_PROC_MAIN_CALLBACK)(QWidget* parent, const QString& strFileName, std::function<QsciScintilla*()>getCurEdit, NDD_PROC_DATA* procData);
++typedef int (*NDD_PROC_MAIN_V2_CALLBACK)(QWidget* parent, const QString& strFileName,ActorProcessor*processor, NDD_PROC_DATA* procData);
+ 
+ int loadProc(const QString& strDirOut, std::function<void(NDD_PROC_DATA&, QMenu*)> funcallback, QMenu* pUserData);
+diff --git a/src/plugin/CMakeLists.txt b/src/plugin/CMakeLists.txt
+index c9b8d26..8e1db46 100644
+--- a/src/plugin/CMakeLists.txt
++++ b/src/plugin/CMakeLists.txt
+@@ -1,4 +1,4 @@
+-
++
+ option(USE_NOTEPAD_PLUGIN "构建 Notepad-- 内部插件" ON)
+ option(USE_NOTEPAD_PLUGIN_BASE "构建 Notepad-- 内部的插件基础模板" OFF)
+ 
+@@ -19,12 +19,12 @@ if(USE_NOTEPAD_PLUGIN)
+         add_subdirectory(template-plugins/base-secondary-menu-ui-plugin-v1)
+     endif(USE_NOTEPAD_PLUGIN_BASE)
+ 
+-    # 一个简单的插件示例,但依赖于外部的 opencc 项目(基于 git)
+-    add_subdirectory(opencc-demo-plugin)
+-    # 一个简单的版本更新检查的插件
+-    add_subdirectory(versionUpdate)
+-    # 一个简单的可二次扩展的插件
+-    add_subdirectory(external-plugin)
++#    # 一个简单的插件示例,但依赖于外部的 opencc 项目(基于 git)
++#    add_subdirectory(opencc-demo-plugin)
++#    # 一个简单的版本更新检查的插件
++#    add_subdirectory(versionUpdate)
++#    # 一个简单的可二次扩展的插件
++#    add_subdirectory(external-plugin)
+ 
+     # Linux 可构建的插件
+     if(UNIX AND NOT APPLE)
+@@ -33,6 +33,6 @@ if(USE_NOTEPAD_PLUGIN)
+     endif(UNIX AND NOT APPLE)
+     
+     # TTS 语音合成
+-    add_subdirectory(TTS-plugin)
++    add_subdirectory(filetreeview)
+ 
+ endif(USE_NOTEPAD_PLUGIN)
+diff --git a/src/plugin/filetreeview/CMakeLists.txt b/src/plugin/filetreeview/CMakeLists.txt
+new file mode 100644
+index 0000000..d74905a
+--- /dev/null
++++ b/src/plugin/filetreeview/CMakeLists.txt
+@@ -0,0 +1,74 @@
++set(LOCAL_PLUGIN_NAME "filetreeview")
++
++# TTS-plugin 核心构建
++# 在模块化构建中,这个部分代表着构建 TTS-plugin 插件
++# 1. 默认构建时产出的目标为 TTS-plugin
++# 2. 在此处可对 TTS-plugin 目标进行详细的构建计划
++
++if(TRUE)
++    # 准备构建 TTS-plugin 主程序扩展
++    spark_file_glob(LocalSources
++        ./*.h ./*.cpp ./*.ui
++    )
++    spark_add_library(${LOCAL_PLUGIN_NAME} SHARED ${LocalSources})
++    target_include_directories(${LOCAL_PLUGIN_NAME} PRIVATE
++        ${PROJECT_SOURCE_DIR}/src
++        ${PROJECT_SOURCE_DIR}/src/cceditor
++
++        ${PROJECT_SOURCE_DIR}/src/qscint/src
++        ${PROJECT_SOURCE_DIR}/src/qscint/src/Qsci
++        ${PROJECT_SOURCE_DIR}/src/qscint/scintilla/src
++        ${PROJECT_SOURCE_DIR}/src/qscint/scintilla/include
++        ${PROJECT_SOURCE_DIR}/src/qscint/scintilla/lexlib
++        ${PROJECT_SOURCE_DIR}/src/qscint/scintilla/boostregex
++    )
++    # target_link_libraries(${LOCAL_PLUGIN_NAME} QSci)
++    target_link_QSci(${LOCAL_PLUGIN_NAME})
++    if(USE_QT6)
++        # target_link_qt6_Core5Compat(${LOCAL_PLUGIN_NAME})   # 兼容性: Qt6 可使用 Core5Compat 少量更改 Qt5 部分
++        # target_link_qt6_PrintSupport(${LOCAL_PLUGIN_NAME})
++        # target_link_qt6_XmlPatterns(${LOCAL_PLUGIN_NAME}) # Bug 初期配置时无此依赖要求
++    else()
++        # target_link_qt5_PrintSupport(${LOCAL_PLUGIN_NAME})
++        # target_link_qt5_XmlPatterns(${LOCAL_PLUGIN_NAME})
++    endif(USE_QT6)
++
++    # 确保生成到 Notepad-- 的相对 plugin 目录下
++    set_target_properties(${LOCAL_PLUGIN_NAME}
++        PROPERTIES
++            RUNTIME_OUTPUT_DIRECTORY $<TARGET_FILE_DIR:${PROJECT_NAME}>/plugin
++            LIBRARY_OUTPUT_DIRECTORY $<TARGET_FILE_DIR:${PROJECT_NAME}>/plugin
++            ARCHIVE_OUTPUT_DIRECTORY $<TARGET_FILE_DIR:${PROJECT_NAME}>/plugin)
++
++    # 控制是否安装到 bin/plugin 而各种配方之下,每个位置或许都不一样(特别是 Linux)
++    # install(TARGETS ${LOCAL_PLUGIN_NAME} DESTINATION bin/plugin)
++endif(TRUE)
++
++
++# ----------------- TTS-plugin 构建宏支持相关  ----------------- #
++
++if(WIN32 AND NOTEPAD_BUILD_BY_SHARED)
++    # 在 Windows 中构建时,需要关注此库的构建形式,QScintilla 应该以何种方式编译
++    target_compile_definitions(${LOCAL_PLUGIN_NAME}
++        PRIVATE
++            NOTEPAD_PLUGIN_MANAGER
++            QSCINTILLA_DLL  # 目前在 Windows 中使用 QSci 库时应该采用 Q_DECL_IMPORT
++                            # 控制 QSCINTILLA_EXPORT 符号应为 Q_DECL_IMPORT
++    )
++else()
++    # 在 Windows 中构建时,需要关注此库的构建形式,QScintilla 应该以何种方式编译
++    target_compile_definitions(${LOCAL_PLUGIN_NAME}
++    PRIVATE
++        NOTEPAD_PLUGIN_MANAGER
++        # QSCINTILLA_DLL # 目前在 Windows 中使用 QSci 库时应该采用 Q_DECL_IMPORT
++                         # 控制 QSCINTILLA_EXPORT 符号应为 Q_DECL_IMPORT
++    )
++endif(WIN32 AND NOTEPAD_BUILD_BY_SHARED)
++
++if(UNIX)
++    # 默认在 Unix/Linux 中仅需要定义一个内部插件宏管理器
++    target_compile_definitions(${LOCAL_PLUGIN_NAME}
++        PRIVATE
++            NOTEPAD_PLUGIN_MANAGER
++    )
++endif(UNIX)
+diff --git a/src/plugin/filetreeview/abstractfile.cpp b/src/plugin/filetreeview/abstractfile.cpp
+new file mode 100644
+index 0000000..9b8673f
+--- /dev/null
++++ b/src/plugin/filetreeview/abstractfile.cpp
+@@ -0,0 +1,6 @@
++#include "abstractfile.h"
++
++AbstractFile::AbstractFile()
++{
++
++}
+diff --git a/src/plugin/filetreeview/abstractfile.h b/src/plugin/filetreeview/abstractfile.h
+new file mode 100644
+index 0000000..94ae7dc
+--- /dev/null
++++ b/src/plugin/filetreeview/abstractfile.h
+@@ -0,0 +1,11 @@
++#ifndef ABSTRACTFILE_H
++#define ABSTRACTFILE_H
++
++
++class AbstractFile
++{
++public:
++    AbstractFile();
++};
++
++#endif // ABSTRACTFILE_H
+diff --git a/src/plugin/filetreeview/actor.h b/src/plugin/filetreeview/actor.h
+new file mode 100644
+index 0000000..d3901a2
+--- /dev/null
++++ b/src/plugin/filetreeview/actor.h
+@@ -0,0 +1,120 @@
++#pragma once
++#include <cstdint>
++#include <string>
++#include <memory>
++#include <tuple>
++#include <functional>
++
++#include "functiontraits.h"
++
++/**
++ * @brief Actor class, which encapsulates the event model
++ * @note enable_shared_from_this allows us to get safe Pointers internally
++ */
++class Actor : public std::enable_shared_from_this<Actor>
++{
++public:
++    using Ptr = std::shared_ptr<Actor>;
++    using FunctionWapper = std::function<void(void*, void*)>;
++
++public:
++    Actor(){}
++    ~Actor(){}
++
++    /**
++    * @brief Register the callback function and construct the anonymous function with std::bind
++    */
++    template<typename Function>
++    void registerFunction(Function&& function_any) noexcept
++    {
++        m_invokeFunctionWapper =  { std::bind(&invoker<Function>::apply, function_any,  std::placeholders::_1, std::placeholders::_2) };
++    }
++
++    /**
++    * @brief Register the callback function and construct the anonymous function with std::bind
++    */
++    template<typename ... Args>
++    void invoke(Args&& ... args) const noexcept
++    {
++        auto args_tuple = std::make_tuple(std::forward<Args>(args)...);
++        m_invokeFunctionWapper(&args_tuple, nullptr);
++    }
++
++    /**
++    * @brief Register the callback function and construct the anonymous function with std::bind
++    */
++    template<typename R, typename ... Args>
++    R invoke(Args&& ...args) const
++    {
++        using tuple_args_type = std::tuple<Args...>;
++        char data[sizeof(tuple_args_type)];
++        std::tuple<Args...>* tuples_pointer = new (data) tuple_args_type;
++
++        *tuples_pointer = std::make_tuple(std::forward<Args>(args)...);
++
++        R return_value;
++        m_invokeFunctionWapper(tuples_pointer, &return_value);
++        return return_value;
++    }
++
++    Ptr getSharedPtr()
++    {
++        return shared_from_this();
++    }
++
++public:
++    /**
++    * @brief This struct encapsulates a function,
++    * essentially storing the return value and parameters in two variables
++    * which can unify the way the function is stored.
++    */
++    template<typename Function>
++    struct invoker
++    {
++        /**
++         * @brief
++         */
++        static inline void apply(const Function& func, void* bl, void* result)
++        {
++            using tuple_type = typename function_traits<Function>::tuple_type;
++            const tuple_type* tp = static_cast<tuple_type*>(bl);
++            call(func, *tp, result);
++        }
++
++        /**
++         * @brief
++         */
++        template<typename F, typename ... Args>
++        static typename std::enable_if<std::is_void<typename std::result_of<F(Args...)>::type>::value>::type
++        call(const F& f, const std::tuple<Args...>& tp, void*)
++        {
++            call_helper(f, std::make_index_sequence<sizeof... (Args)>{}, tp);
++        }
++
++        /**
++         * @brief
++         */
++        template<typename F, typename ... Args>
++        static typename std::enable_if<!std::is_void<typename std::result_of<F(Args...)>::type>::value>::type
++        call(const F& f, const std::tuple<Args...>& tp, void* result)
++        {
++            auto r = call_helper(f, std::make_index_sequence<sizeof... (Args)>{}, tp);
++            *(decltype(r)*)result = r;
++        }
++
++        /**
++        * @brief
++        */
++        template<typename F, size_t... I, typename ... Args>
++        static auto call_helper(const F& f, const std::index_sequence<I...>& h, const std::tuple<Args...>& tup)
++        {
++            return f(std::get<I>(tup)...);
++        }
++    };
++private:
++    /**
++    * @note m_invokeFunctions is an anonymous lamba expression that encapsulates a function
++    * Functions include Lamba expressions, member functions, funtor, ordinary functions, and function Pointers
++    */
++    FunctionWapper m_invokeFunctionWapper;
++};
+diff --git a/src/plugin/filetreeview/actorprocessor.cpp b/src/plugin/filetreeview/actorprocessor.cpp
+new file mode 100644
+index 0000000..099d1f6
+--- /dev/null
++++ b/src/plugin/filetreeview/actorprocessor.cpp
+@@ -0,0 +1,67 @@
++#include "actorprocessor.h"
++ActorProcessor::ActorProcessor():
++    m_actorMap(new std::unordered_map<std::string, Actor*>)
++{}
++
++/**
++ * @brief
++ */
++ActorProcessor::~ActorProcessor()
++{
++    for (auto& item : (*m_actorMap))
++    {
++        if(item.second != nullptr)
++            delete item.second;
++    }
++    if (m_actorMap)
++        delete m_actorMap;
++}
++
++/**
++ * @brief
++ */
++void ActorProcessor::registerActor(const std::string &route, Actor *actor)
++{
++    m_actorMap->insert(std::make_pair(route,actor));
++}
++
++/**
++ * @brief
++ */
++void ActorProcessor::removeActor(const std::string &route)
++{
++    auto iter = (*m_actorMap).find(route);
++    if(iter != m_actorMap->end())
++    {
++        m_actorMap->erase(route);
++        delete iter->second;
++    }
++}
++
++/**
++ * @brief
++ */
++Actor *ActorProcessor::findActor(const std::string &route)
++{
++    auto iter = (*m_actorMap).find(route);
++    if(iter != m_actorMap->end())
++    {
++        return (*m_actorMap)[route];
++    }
++    return nullptr;
++}
++
++/**
++ * @brief
++ */
++bool ActorProcessor::resetActor(const std::string &route, Actor *actor)
++{
++    auto iter = (*m_actorMap).find(route);
++    if(iter != m_actorMap->end())
++    {
++        m_actorMap->erase(route);
++        delete iter->second;
++        return true;
++    }
++    return false;
++}
+diff --git a/src/plugin/filetreeview/actorprocessor.h b/src/plugin/filetreeview/actorprocessor.h
+new file mode 100644
+index 0000000..d057e6d
+--- /dev/null
++++ b/src/plugin/filetreeview/actorprocessor.h
+@@ -0,0 +1,66 @@
++#ifndef EVENTPROCESSOR_H
++#define EVENTPROCESSOR_H
++
++#include <string>
++#include <functional>
++#include <map>
++#include <iostream>
++#include <unordered_map>
++#include "actor.h"
++
++class  ActorProcessor
++{
++public:
++    ActorProcessor();
++    ~ActorProcessor();
++public:
++
++    /**
++    * @brief Register the callback function and construct the anonymous function with std::bind
++    */
++    template<typename ... Args>
++    void invoke(const std::string& route,Args&& ... args) const noexcept
++    {
++        if (m_actorMap->find(route) != m_actorMap->end())
++            (*m_actorMap)[route]->invoke(std::forward<Args>(args)...);
++    }
++
++    /**
++    * @brief Register the callback function and construct the anonymous function with std::bind
++    */
++    template<typename R, typename ... Args>
++    R invoke(const std::string& route,Args&& ...args) const
++    {
++        if (m_actorMap->find(route) != m_actorMap->end())
++            return (*m_actorMap)[route]->invoke(std::forward<Args>(args)...);
++        return nullptr;
++    }
++
++    /**
++    * @brief 注册一个actor,其中route是唯一的
++    */
++    void registerActor(const std::string& route, Actor*actor);
++
++    /**
++    * @brief 删除一个actor
++    */
++    void removeActor(const std::string& route);
++
++    /**
++    * @brief 查找一个actor, 返回其指针
++    */
++    Actor* findActor(const std::string& route);
++
++    /**
++    * @brief 重置一个actor,注意原有的actor会被删除
++    */
++    bool resetActor(const std::string& route,Actor*actor);
++
++private:
++    std::unordered_map<std::string, Actor*>* m_actorMap;
++private:
++    ActorProcessor(ActorProcessor&&)=delete;
++    ActorProcessor& operator=(const ActorProcessor&)=delete;
++};
++
++#endif // EVENTPROCESSOR_H
+diff --git a/src/plugin/filetreeview/filesystemmodel.cpp b/src/plugin/filetreeview/filesystemmodel.cpp
+new file mode 100644
+index 0000000..5b825b5
+--- /dev/null
++++ b/src/plugin/filetreeview/filesystemmodel.cpp
+@@ -0,0 +1,526 @@
++#include "filesystemmodel.h"
++
++#include <QFileInfo>
++#include <QDir>
++#include <QIcon>
++#include <QFont>
++#include <QFileIconProvider>
++#include <QFileSystemWatcher>
++#include <QMutexLocker>
++#include <QDebug>
++
++FileNode::FileNode(FileSystemModel *model) :
++    m_model(model),
++    m_parent(0),
++    m_children(0),
++    m_bWatcher(false)
++{
++
++}
++
++FileNode::FileNode(FileSystemModel *model, const QString &path, FileNode *parent) :
++    m_model(model),
++    m_parent(parent),
++    m_children(0),
++    m_path(path),
++    m_bWatcher(false)
++{
++    QFileInfo info(path);
++    if (m_model->isRootPathNodeFillPath() && parent && parent->getNodeParent() == 0) {
++        m_text = QDir::toNativeSeparators(info.filePath());
++    } else {
++        m_text = info.fileName();
++    }
++}
++
++FileNode::~FileNode()
++{
++    clear();
++    if (m_children) {
++        delete m_children;
++    }
++    if (m_bWatcher)  {
++        m_model->removeWatcher(m_path);
++    }
++}
++
++QList<FileNode*>* FileNode::getChildrenList()
++{
++    if (m_children == 0) {
++        m_children = new QList<FileNode*>();
++        if (!m_path.isEmpty()) {
++            QFileInfo info(m_path);
++            if (info.isDir()) {
++                QDir dir(m_path);
++                foreach(QFileInfo childInfo, dir.entryInfoList(this->m_model->filter(),this->m_model->dirSort())) {
++                    if (!this->m_model->isShowHideFiles() && childInfo.isDir() && childInfo.fileName().startsWith(".")) {
++                       continue;
++                    }
++                    m_children->append(new FileNode(this->m_model,childInfo.filePath(),this));
++                }
++            }
++       }
++       if (!m_children->isEmpty() && !m_bWatcher) {
++           m_bWatcher = true;
++           m_model->addWatcher(m_path);
++       }
++    }
++    return m_children;
++}
++
++FileNode* FileNode::getNodeParent()
++{
++    return m_parent;
++}
++
++FileNode* FileNode::getChild(int row)
++{
++    return getChildrenList()->at(row);
++}
++
++int FileNode::childCount()
++{
++    return getChildrenList()->count();
++}
++
++int FileNode::row() const
++{
++    if (m_parent) {
++       return m_parent->getChildrenList()->indexOf(const_cast<FileNode*>(this));
++    }
++    return 0;
++}
++
++QString FileNode::getNodePath() const
++{
++    return m_path;
++}
++
++QString FileNode::text() const
++{
++    return m_text;
++}
++
++bool FileNode::isDir() const
++{
++    return QFileInfo(m_path).isDir();
++}
++
++bool FileNode::isFile() const
++{
++    return QFileInfo(m_path).isFile();
++}
++
++QFileInfo FileNode::getFileInformation() const
++{
++    return QFileInfo(m_path);
++}
++
++bool FileNode::isExist() const
++{
++    return QFileInfo(m_path).exists();
++}
++
++void FileNode::clear()
++{
++    if (m_children) {
++        qDeleteAll(m_children->begin(),m_children->end());
++        m_children->clear();
++    }
++}
++
++void FileNode::reload()
++{
++    clear();
++    if (m_children == 0) {
++        m_children = new QList<FileNode*>();
++    }
++    if (!m_path.isEmpty()) {
++        QFileInfo info(m_path);
++        if (info.isDir()) {
++            QDir dir(m_path);
++            foreach(QFileInfo childInfo, dir.entryInfoList(this->m_model->filter(),this->m_model->dirSort())) {
++                if (!this->m_model->isShowHideFiles() && childInfo.isDir() && childInfo.fileName().startsWith(".")) {
++                   continue;
++                }
++                m_children->append(new FileNode(this->m_model,childInfo.filePath(),this));
++            }
++        }
++    }
++    if (!m_children->isEmpty() && !m_bWatcher) {
++        m_bWatcher = true;
++        m_model->addWatcher(m_path);
++    }
++}
++
++FileNode *FileNode::findPath(const QString &path)
++{
++    if (!path.startsWith(m_path)) {
++        return 0;
++    }
++    if (path == m_path) {
++        return this;
++    }
++    QStringList nameList = path.right(path.length()-m_path.length()).split("/",QString::SkipEmptyParts);
++    FileNode *parent = this;
++    bool find = false;
++    foreach (QString name,nameList) {
++        find = false;
++        QList<FileNode*>* chilren = parent->getChildrenList();
++        for (int i = 0; i < chilren->count(); i++) {
++            FileNode *node = chilren->at(i);
++            if (!node->isDir()) {
++                continue;
++            }
++            if (node->m_text == name) {
++                parent = node;
++                find = true;
++                break;
++            }
++        }
++        if (!find) {
++            return 0;
++        }
++    }
++    return parent;
++}
++
++FileSystemModel::FileSystemModel(QObject *parent) :
++    QAbstractItemModel(parent),
++    m_rootNode(new FileNode(this)),
++    m_iconProvider(new QFileIconProvider),
++    m_fileWatcher(0)
++{
++    m_dirFilter = QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot;
++    m_sorts = QDir::DirsFirst | QDir::Name | QDir::IgnoreCase;// | QDir::Type;
++   // connect(m_fileWatcher,SIGNAL(directoryChanged(QString)),this,SLOT(directoryChanged(QString)));
++}
++
++FileSystemModel::~FileSystemModel()
++{
++    delete m_rootNode;
++    delete m_iconProvider;
++    if (m_fileWatcher) {
++        delete m_fileWatcher;
++    }
++}
++
++void FileSystemModel::reloadDirectory(const QString &path)
++{
++    this->beginResetModel();
++    QDir dir(path);
++    bool b = dir.exists();
++    if (!b) {
++        m_fileWatcher->removePath(path);
++    }
++    foreach(QModelIndex index,this->findPaths(path)) {
++        FileNode *node = nodeFromIndex(index);
++        if (b) {
++            node->reload();
++        } else {
++            FileNode *parent = node->getNodeParent();
++            if (parent) {
++                parent->reload();
++            }
++        }
++    }
++    this->endResetModel();
++}
++
++FileNode *FileSystemModel::nodeFromIndex(const QModelIndex &index) const
++{
++    if (index.isValid()) {
++        return static_cast<FileNode*>(index.internalPointer());
++    }
++    return m_rootNode;
++}
++
++void FileSystemModel::setStartIndex(const QModelIndex &index)
++{
++    m_startPath = filePath(index);
++}
++
++void FileSystemModel::setStartPath(const QString &path)
++{
++    m_startPath = path;
++}
++
++QModelIndex FileSystemModel::startIndex() const
++{
++    return findPath(m_startPath);
++}
++
++QString FileSystemModel::startPath() const
++{
++    return m_startPath;
++}
++
++QString FileSystemModel::filePath(const QModelIndex &index) const
++{
++    return nodeFromIndex(index)->getNodePath();
++}
++
++void FileSystemModel::setFilter(QDir::Filters filters)
++{
++    if (m_dirFilter != filters) {
++        m_dirFilter = filters;
++        this->reload();
++    }
++}
++
++void FileSystemModel::setDirSort(QDir::SortFlags flags)
++{
++    if (m_sorts != flags) {
++        m_sorts = flags;
++        this->reload();
++    }
++}
++
++QDir::Filters FileSystemModel::filter() const
++{
++    return m_dirFilter;
++}
++
++bool FileSystemModel::isShowHideFiles() const
++{
++    return m_dirFilter & QDir::Hidden;
++}
++
++QDir::SortFlags FileSystemModel::dirSort() const
++{
++    return m_sorts;
++}
++
++void FileSystemModel::clear()
++{
++    this->beginResetModel();
++    m_rootNode->clear();
++    m_pathList.clear();
++    m_startPath.clear();
++    this->endResetModel();
++}
++
++void FileSystemModel::reload()
++{
++    this->setRootPathList(this->rootPathList());
++}
++
++void FileSystemModel::setRootPath(const QString &path)
++{
++    this->setRootPathList(QStringList() << path);
++}
++
++bool FileSystemModel::removeRootPath(const QString &path)
++{
++    QString pathName = QDir::fromNativeSeparators(path);
++    FileNode *node = 0;
++    int index = -1;
++    for (int i = 0; i < m_rootNode->childCount(); i++) {
++        node = m_rootNode->getChildrenList()->at(i);
++        if (node && (node->getNodePath() == pathName)) {
++            index = i;
++            break;
++        }
++    }
++    if (index == -1) {
++        return false;
++    }
++    if (!m_pathList.removeAll(pathName)) {
++        return false;
++    }
++    this->beginRemoveRows(QModelIndex(),index,index);
++    m_rootNode->getChildrenList()->removeAt(index);
++    delete node;
++    this->endRemoveRows();
++    return true;
++}
++
++
++bool FileSystemModel::addRootPath(const QString &path)
++{
++    QString pathName = QDir::fromNativeSeparators(QDir::cleanPath(path));
++    if (m_pathList.contains(pathName)) {
++        return false;
++    }
++    this->beginInsertRows(QModelIndex(),m_rootNode->childCount(),m_rootNode->childCount());
++    m_pathList.append(pathName);
++    m_rootNode->getChildrenList()->append(new FileNode(this,pathName,m_rootNode));
++    this->endInsertRows();
++    return true;
++}
++
++void FileSystemModel::setRootPathList(const QStringList &pathList)
++{
++    this->beginResetModel();
++    m_rootNode->clear();
++    m_fileWatcherMap.clear();
++    m_pathList.clear();
++    if (m_fileWatcher) {
++        disconnect(m_fileWatcher,0);
++        delete m_fileWatcher;
++    }
++    m_fileWatcher = new QFileSystemWatcher;
++    connect(m_fileWatcher,SIGNAL(directoryChanged(QString)),this,SIGNAL(direcotryChanged(QString)));
++
++    foreach (QString path, pathList) {
++        m_pathList.append(QDir::fromNativeSeparators(QDir::cleanPath(path)));
++    }
++    m_pathList.removeDuplicates();
++
++    foreach(QString path, m_pathList) {
++        m_rootNode->getChildrenList()->append(new FileNode(this,path,m_rootNode));
++    }
++
++    if (m_startPath.isEmpty() && !pathList.isEmpty()) {
++        m_startPath = pathList.first();
++    }
++
++    this->endResetModel();
++}
++
++QStringList FileSystemModel::rootPathList() const
++{
++    return m_pathList;
++}
++
++QModelIndex FileSystemModel::findPathHelper(const QString &path, const QModelIndex &parentIndex) const
++{
++    FileNode *node = nodeFromIndex(parentIndex);
++    if (!path.startsWith(node->getNodePath())) {
++        return QModelIndex();
++    }
++    if (path == node->getNodePath()) {
++        return parentIndex;
++    }
++    QStringList nameList = path.right(path.length()-node->getNodePath().length()).split("/",QString::SkipEmptyParts);
++    QModelIndex parent = parentIndex;
++    bool find = false;
++    int count = nameList.count();
++    for (int i = 0; i < count; i++) {
++        find = false;
++        for (int j = 0; j < this->rowCount(parent); j++) {
++            QModelIndex index = this->index(j,0,parent);
++            FileNode *node = nodeFromIndex(index);
++            if ( ( (i == count-1) || node->isDir()) && node->text() == nameList.at(i)) {
++                parent = index;
++                find = true;
++                break;
++            }
++        }
++        if (!find) {
++            return QModelIndex();
++        }
++    }
++    return parent;
++}
++
++QList<QModelIndex> FileSystemModel::findPaths(const QString &path) const
++{
++    QList<QModelIndex> list;
++    QString cpath = QDir::fromNativeSeparators(QDir::cleanPath(path));
++    for (int i = 0; i < this->rowCount(); i++) {
++        QModelIndex find = findPathHelper(cpath,this->index(i,0));
++        if (find.isValid()) {
++            list.append(find);
++        }
++     }
++    return list;
++}
++
++QModelIndex FileSystemModel::findPath(const QString &path) const
++{
++    QList<QModelIndex> list = this->findPaths(path);
++    if (!list.isEmpty()) {
++        return list.last();
++    }
++    return QModelIndex();
++}
++
++int FileSystemModel::rowCount(const QModelIndex &parent) const
++{
++    FileNode *node = nodeFromIndex(parent);
++    return node->childCount();
++}
++
++int FileSystemModel::columnCount(const QModelIndex&) const
++{
++    return 1;
++}
++
++QModelIndex FileSystemModel::parent(const QModelIndex &child) const
++{
++    FileNode *node = nodeFromIndex(child);
++    FileNode *parent = node->getNodeParent();
++    if (parent == m_rootNode) {
++        return QModelIndex();
++    }
++    return createIndex(parent->row(),0,parent);
++}
++
++QModelIndex FileSystemModel::index(int row, int column,const QModelIndex &parent) const
++{
++    if (!hasIndex(row,column,parent))
++        return QModelIndex();
++    FileNode *node = nodeFromIndex(parent);
++    return createIndex(row,column,node->getChild(row));
++}
++
++QVariant FileSystemModel::data(const QModelIndex &index, int role) const
++{
++    FileNode *node = nodeFromIndex(index);
++    if (!node) {
++        return QVariant();
++    }
++    switch(role) {
++    case Qt::DisplayRole:
++        return node->text();
++    case Qt::ToolTipRole:
++        return QDir::toNativeSeparators(node->getNodePath());
++    case Qt::DecorationRole:
++        return m_iconProvider->icon(node->getFileInformation());
++/*
++    case Qt::FontRole: {
++        QFont font;
++        if (node->path() == m_startPath) {
++            font.setBold(true);
++        }
++        return font;
++    }
++*/
++    }
++    return QVariant();
++}
++
++bool FileSystemModel::isRootPathNode(FileNode *node) const
++{
++    return node->getNodeParent() == m_rootNode;
++}
++
++bool FileSystemModel::isRootPathNodeFillPath() const
++{
++    return false;
++}
++
++void FileSystemModel::addWatcher(const QString &path)
++{
++    QMutexLocker _(&m_mutex);
++    QString cpath = QDir::fromNativeSeparators(QDir::cleanPath(path));
++    int value = m_fileWatcherMap[cpath];
++    value++;
++    m_fileWatcherMap[cpath] = value;
++    if (value > 1) {
++        return;
++    }
++    m_fileWatcher->addPath(path);
++}
++void FileSystemModel::removeWatcher(const QString &path)
++{
++    QMutexLocker _(&m_mutex);
++    QString cpath = QDir::fromNativeSeparators(QDir::cleanPath(path));
++    int value = m_fileWatcherMap[cpath];
++    value--;
++    m_fileWatcherMap[cpath] = value;
++    if (value == 0) {
++        m_fileWatcher->removePath(cpath);
++    }
++}
+diff --git a/src/plugin/filetreeview/filesystemmodel.h b/src/plugin/filetreeview/filesystemmodel.h
+new file mode 100644
+index 0000000..f8816f8
+--- /dev/null
++++ b/src/plugin/filetreeview/filesystemmodel.h
+@@ -0,0 +1,193 @@
++#ifndef FILESYSTEMMODEL_H
++#define FILESYSTEMMODEL_H
++
++#include <QAbstractItemModel>
++#include <QStringList>
++#include <QIcon>
++#include <QFileInfo>
++#include <QDir>
++#include <QMutex>
++
++class FileSystemModel;
++class QFileSystemWatcher;
++
++class QFileIconProvider;
++class QFileSystemWatcher;
++class QTreeView;
++
++class FileNode
++{
++public:
++
++    FileNode(FileSystemModel *model);
++    FileNode(FileSystemModel *model,const QString &path, FileNode *parent);
++    virtual ~FileNode();
++
++    // 返回父节点
++    FileNode* getNodeParent(); 	
++
++    // 按照index返回
++    FileNode* getChild(int row);    
++
++    //! returns the number of children this node has.
++    int childCount();   
++
++    //! returns the row number of this node.
++    int row() const;    			
++
++    //! returns a list of all the children of this node. The list will be empty if this node has no children.
++    QList<FileNode*>* getChildrenList();  
++
++    //! returns the path of this node.
++    QString getNodePath() const;   
++
++    //! returns the text of this node.
++    QString text() const;   		
++
++    //! returns the QFileInfo for this node.
++    QFileInfo getFileInformation() const;  
++
++    //! returns true if this node exists on the file system.
++    bool isExist() const;   	
++
++    //! returns true if this node is a directory.
++    bool isDir() const; 
++
++    //! returns true if this node is a file.
++    bool isFile() const;  
++
++    //! clears this node.
++    void clear();   			
++
++    //! recreates the node from the file system.
++    void reload();  		
++
++    //! returns the node for the path specified.
++    FileNode *findPath(const QString &path);    
++
++protected:
++
++    //! The FileSystemModel is a QAbstractItemModel that provides a hierarchical view of the file system.
++    FileSystemModel *m_model;
++
++    //! The FileNode is a node in the tree of FileNodes.
++    FileNode *m_parent;
++
++    //! The list of FileNodes.
++    QList<FileNode*> *m_children;
++
++    //! The path of the FileNode.
++    QString m_path;
++
++    //! The text of the FileNode.
++    QString m_text;
++
++    //!< true if this node is being watched.  If so, the model will watch the node and will need to be manually updated.
++    bool    m_bWatcher;
++};
++
++class FileSystemModel : public QAbstractItemModel
++{
++    Q_OBJECT
++public:
++    explicit FileSystemModel(QObject *parent = 0);
++
++    ~FileSystemModel();
++
++    void clear();
++
++    void reload();
++
++    void setFilter(QDir::Filters filters);
++
++    void setDirSort(QDir::SortFlags flags);
++
++    QDir::Filters filter() const;
++
++    bool isShowHideFiles() const;
++
++    QDir::SortFlags dirSort() const;
++
++    bool removeRootPath(const QString &path);
++
++    bool addRootPath(const QString &path);
++
++    void setRootPathList(const QStringList &pathList);
++
++    void setRootPath(const QString &path);
++
++    QStringList rootPathList() const;
++
++    QList<QModelIndex> findPaths(const QString &path) const;
++
++    QModelIndex findPath(const QString &path) const;
++
++    QString filePath(const QModelIndex &index) const;
++
++    FileNode *nodeFromIndex(const QModelIndex &index) const;
++
++    void setStartIndex(const QModelIndex &index);
++
++    void setStartPath(const QString &path);
++
++    QModelIndex startIndex() const;
++
++    QString startPath() const;
++
++    virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
++
++    virtual int columnCount(const QModelIndex &parent = QModelIndex()) const;
++
++    virtual QModelIndex parent(const QModelIndex &child) const;
++
++    virtual QModelIndex index(int row, int column,const QModelIndex &parent = QModelIndex()) const;
++
++    virtual QVariant data(const QModelIndex &index, int role) const;
++
++    bool isRootPathNode(FileNode *node) const;
++
++    bool isRootPathNodeFillPath() const;
++
++    void addWatcher(const QString &path);
++
++    void removeWatcher(const QString &path);
++signals:
++    void direcotryChanged(QString);
++public slots:
++    void reloadDirectory(const QString &path);
++protected:
++    //! Finds the node at the given \a path in the model.
++    QModelIndex findPathHelper(const QString &path, const QModelIndex &parentIndex) const;
++
++    //! The list of paths that are currently being watched.
++    QStringList m_pathList;
++
++    //! The root node of the model.
++    FileNode  *m_rootNode;
++
++    //! The path to the directory to watch.
++    QString   m_startPath;
++
++    //! The file icon provider.
++    QFileIconProvider *m_iconProvider;
++
++    //! The file system watcher.
++    QFileSystemWatcher *m_fileWatcher;
++
++    //! A map of watched files and their watcher indices.
++    QMap<QString,int> m_fileWatcherMap;
++
++    //! The tree view.
++    QTreeView *m_treeView;
++
++    //! The filters to apply to the directory.
++    QDir::Filters m_dirFilter;
++
++    //! The sort order to apply to the directory.
++    QDir::SortFlags m_sorts;
++
++    //! The mutex to protect the model.
++    QMutex m_mutex;
++};
++
++#endif // FILESYSTEMMODEL_H
+diff --git a/src/plugin/filetreeview/filetreeview.cpp b/src/plugin/filetreeview/filetreeview.cpp
+new file mode 100644
+index 0000000..a1afb12
+--- /dev/null
++++ b/src/plugin/filetreeview/filetreeview.cpp
+@@ -0,0 +1,195 @@
++/**
++ ** This file is part of the NoteBook project.
++ ** Copyright 2022 ji wang <[email protected]>.
++ **
++ ** This program is free software: you can redistribute it and/or modify
++ ** it under the terms of the GNU Lesser General Public License as
++ ** published by the Free Software Foundation, either version 3 of the
++ ** License, or (at your option) any later version.
++ **
++ ** This program is distributed in the hope that it will be useful,
++ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
++ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ ** GNU Lesser General Public License for more details.
++ **
++ ** You should have received a copy of the GNU Lesser General Public License
++ ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
++ **/
++
++#include "filetreeview.h"
++
++#include <QTreeView>
++#include <QJsonObject>
++#include <QFileDialog>
++#include <QMenu>
++#include <QApplication>
++#include <QMessageBox>
++#include <QProcess>
++
++#include"actorprocessor.h"
++#include "filesystemmodel.h"
++
++class FileTreeViewPrivate
++{
++public:
++    FileTreeViewPrivate(FileTreeView *parent):
++        q_ptr(parent)
++    {}
++
++public:
++    ActorProcessor* m_processor;
++    QMenu*  m_fileMenu;
++    QMenu*  m_dirMenu;
++    QWidget* m_notepadWidget;
++
++private:
++    FileTreeView * const q_ptr;
++    Q_DECLARE_PUBLIC(FileTreeView);
++};
++
++FileTreeView::FileTreeView(QWidget *parent):
++    d_ptr(new FileTreeViewPrivate(this)),
++    QTreeView(parent)
++{
++    bindAction();
++    setContextMenuPolicy(Qt::CustomContextMenu);
++    connect(this, SIGNAL(doubleClicked(QModelIndex)),this, SLOT(slotDoubleCliecked(QModelIndex)));
++    connect(this,SIGNAL(customContextMenuRequested(const QPoint &)),this, SLOT(slotCustomContextMenu(const QPoint &)));
++}
++
++FileTreeView::~FileTreeView()
++{}
++
++void FileTreeView::onNewWorkspaceItem(bool)
++{
++    QString srcDirPath = QFileDialog::getExistingDirectory(
++                   this, "choose src Directory",
++                    "/");
++    if (srcDirPath.isEmpty()){
++        return;
++    }
++    FileSystemModel* model = static_cast<FileSystemModel*> (this->model());
++    model->addRootPath(srcDirPath);
++    model->reload();
++    this->update();
++}
++
++void FileTreeView::slotCustomContextMenu(const QPoint & pos)
++{
++    Q_D(FileTreeView);
++    auto model = static_cast<FileSystemModel*>(this->model());
++    auto index= this->indexAt(pos);
++    if (model->nodeFromIndex(index)->isDir()){
++        d->m_dirMenu->exec(QCursor::pos());
++    }
++    else {
++        d->m_fileMenu->exec(QCursor::pos());
++    }
++}
++
++void FileTreeView::slotTerminal(bool)
++{
++}
++
++void FileTreeView::slotNewFile(bool)
++{
++    
++}
++
++void FileTreeView::slotRemoveFile(bool)
++{
++}
++
++void FileTreeView::slotAddDir(bool)
++{
++    
++}
++
++void FileTreeView::slotRemoveDir(bool)
++{
++    Q_D(FileTreeView);
++    auto model = static_cast<FileSystemModel*>(this->model());
++    auto index= this->currentIndex();
++    if (model->nodeFromIndex(index)->isDir()){
++        (d->m_dirMenu)->exec(QCursor::pos());
++    }
++    else {
++        (d->m_fileMenu)->exec(QCursor::pos());
++    }
++}
++
++void FileTreeView::slotupdateDir(bool)
++{
++    auto model = static_cast<FileSystemModel*>(this->model());
++    auto index= this->currentIndex();
++    if (model->nodeFromIndex(index)->isDir()){
++        model->reloadDirectory(model->nodeFromIndex(index)->getNodePath());
++    }
++}
++
++void FileTreeView::initMenuFile(QMenu *menu)
++{
++    Q_D(FileTreeView);
++    d->m_fileMenu = menu;
++}
++
++void FileTreeView::initDirMenu(QMenu *menu)
++{
++    Q_D(FileTreeView);
++    d->m_dirMenu = menu;
++}
++
++void FileTreeView::bindAction()
++{
++    Q_D(FileTreeView);
++    QMenu* fileTreeMenu  = new QMenu("menu",this);
++    QMenu* dirTreeMenu  = new QMenu("file",this);
++
++    QAction *fileRename =           new QAction(tr("Rename"),fileTreeMenu);
++    QAction *fileDelete =           new QAction(tr("Delete"),fileTreeMenu);
++    QAction *fileOpenInExporter =   new QAction(tr("OpenInExporter"),fileTreeMenu);
++
++    QAction *removeDir =            new QAction(tr("Remove Dir"),dirTreeMenu);
++    QAction *update =               new QAction(tr("Update"),dirTreeMenu);
++    QAction *fold =                 new QAction(tr("Fold Dir"),dirTreeMenu);
++    QAction *unfold =               new QAction(tr("Unfold Dir"),dirTreeMenu);
++    QAction *terminal =             new QAction(tr("terminal"));
++
++    d->m_dirMenu = dirTreeMenu;
++    d->m_dirMenu->addActions({removeDir,update,fold,unfold,terminal});
++    d->m_fileMenu = fileTreeMenu;
++    d->m_fileMenu->addActions({fileRename,fileDelete,fileOpenInExporter,terminal});
++}
++
++void FileTreeView::setActorProcessor(ActorProcessor *processor)
++{
++    Q_D(FileTreeView);
++    d->m_processor = processor;
++}
++
++void FileTreeView::setMainWindow(QWidget *notepadWidget)
++{
++    Q_D(FileTreeView);
++    d->m_notepadWidget = notepadWidget;
++}
++
++
++
++void FileTreeView::slotCliecked(const QModelIndex &index)
++{
++}
++
++void FileTreeView::slotDoubleCliecked(const QModelIndex &index)
++{
++    Q_D(FileTreeView);
++    auto model = static_cast<FileSystemModel*>(this->model());
++    if (model->nodeFromIndex(index)->isDir()){
++    }
++    else {
++        d->m_processor->invoke("openFile",model->nodeFromIndex(index)->getNodePath(),-1);
++    }
++}
++
++
++
++
+diff --git a/src/plugin/filetreeview/filetreeview.h b/src/plugin/filetreeview/filetreeview.h
+new file mode 100644
+index 0000000..0a0e5e1
+--- /dev/null
++++ b/src/plugin/filetreeview/filetreeview.h
+@@ -0,0 +1,73 @@
++/**
++ ** This file is part of the NoteBook project.
++ ** Copyright 2022 ji wang <[email protected]>.
++ **
++ ** This program is free software: you can redistribute it and/or modify
++ ** it under the terms of the GNU Lesser General Public License as
++ ** published by the Free Software Foundation, either version 3 of the
++ ** License, or (at your option) any later version.
++ **
++ ** This program is distributed in the hope that it will be useful,
++ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
++ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ ** GNU Lesser General Public License for more details.
++ **
++ ** You should have received a copy of the GNU Lesser General Public License
++ ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
++ **/
++
++
++#ifndef WORKSPACE_H
++#define WORKSPACE_H
++
++#include <QObject>
++#include <QTreeView>
++
++class QModelIndex;
++class QWidget;
++class QMenu;
++class ActorProcessor;
++
++class FileTreeViewPrivate;
++
++class  FileTreeView : public QTreeView
++{
++    Q_OBJECT
++public:
++    FileTreeView(QWidget* parent = nullptr);
++
++    ~FileTreeView();
++
++    void initMenuFile(QMenu*);
++    void initDirMenu(QMenu*);
++
++    void bindAction();
++    void setActorProcessor(ActorProcessor*processor);
++
++    void setMainWindow(QWidget* notepadWidget);
++
++public slots:
++    void onNewWorkspaceItem(bool);
++    void slotCliecked(const QModelIndex &);
++    void slotDoubleCliecked(const QModelIndex &);
++    void slotCustomContextMenu(const QPoint &);
++    void slotTerminal(bool);
++    void slotNewFile(bool);
++    void slotRemoveFile(bool);
++    void slotAddDir(bool);
++    void slotRemoveDir(bool);
++    void slotupdateDir(bool);
++signals:
++    void signalNewFile();
++    void signalRemoveFile();
++    void signalAddDir();
++
++private:
++    FileTreeViewPrivate * const d_ptr;
++    Q_DECLARE_PRIVATE(FileTreeView);
++
++    FileTreeView(const FileTreeView &) = delete;
++    FileTreeView &operator=(const FileTreeView &) = delete;
++};
++
++#endif // WORKSPACE_H
+diff --git a/src/plugin/filetreeview/filetreeviewexport.cpp b/src/plugin/filetreeview/filetreeviewexport.cpp
+new file mode 100644
+index 0000000..4c43c84
+--- /dev/null
++++ b/src/plugin/filetreeview/filetreeviewexport.cpp
+@@ -0,0 +1,77 @@
++#include <qobject.h>
++#include <qstring.h>
++#include <pluginGl.h>
++#include <functional>
++#include <qsciscintilla.h>
++#include <QAction>
++#include <QDebug>
++
++#include "filetreeviewplugin.h"
++#include "actorprocessor.h"
++#define NDD_EXPORTDLL
++
++#if defined(Q_OS_WIN)
++#if defined(NDD_EXPORTDLL)
++#define NDD_EXPORT __declspec(dllexport)
++#else
++#define NDD_EXPORT __declspec(dllimport)
++#endif
++#else
++#define NDD_EXPORT __attribute__((visibility("default")))
++#endif
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++NDD_EXPORT bool NDD_PROC_IDENTIFY(NDD_PROC_DATA* pProcData);
++//NDD_EXPORT int NDD_PROC_MAIN(QWidget* pNotepad, const QString& strFileName, std::function<QsciScintilla* ()>getCurEdit, NDD_PROC_DATA* procData);
++NDD_EXPORT int NDD_PROC_MAIN_V2(QWidget* pNotepad, const QString& strFileName,ActorProcessor* processor, NDD_PROC_DATA* procData);
++
++#ifdef __cplusplus
++}
++#endif
++
++static NDD_PROC_DATA s_procData;
++static QWidget* s_pMainNotepad = nullptr;
++std::function<QsciScintilla* ()> s_getCurEdit;
++
++bool NDD_PROC_IDENTIFY(NDD_PROC_DATA* pProcData)
++{
++    if(pProcData == NULL)
++    {
++        return false;
++    }
++    pProcData->m_strPlugName = QObject::tr("File Tree");
++    pProcData->m_strComment = QObject::tr("file tree plugin");
++
++    pProcData->m_version = QString("1.0");
++    pProcData->m_auther = QString("[email protected]");
++
++    pProcData->m_menuType = 1;
++    return true;
++}
++
++int NDD_PROC_MAIN_V2(QWidget* pNotepad, const QString& strFileName,ActorProcessor* processor,NDD_PROC_DATA* pProcData)
++{
++    qDebug() << "test plugin";
++    //务必拷贝一份pProcData,在外面会释放。
++    if (pProcData != nullptr)
++    {
++        s_procData = *pProcData;
++    }
++    else
++    {
++        return -1;
++    }
++
++    s_pMainNotepad = pNotepad;
++    FileTreeViewPlugin* plugin = new FileTreeViewPlugin();
++    plugin->setPluginModulePath(strFileName);
++    plugin->setActorProcessor(processor);
++    plugin->setTopMenu(pProcData->m_rootMenu);
++    plugin->setMenuActions(s_procData.m_rootMenu);
++    plugin->setNotepad(s_pMainNotepad);
++
++    return 0;
++}
+diff --git a/src/plugin/filetreeview/filetreeviewplugin.cpp b/src/plugin/filetreeview/filetreeviewplugin.cpp
+new file mode 100644
+index 0000000..97eda35
+--- /dev/null
++++ b/src/plugin/filetreeview/filetreeviewplugin.cpp
+@@ -0,0 +1,121 @@
++#include "filetreeviewplugin.h"
++
++#include <QDockWidget>
++#include <QObject>
++#include <QAction>
++#include <QFileInfo>
++#include <QMainWindow>
++#include <QDir>
++#include <QTreeView>
++#include <QDebug>
++
++#include <qsciscintilla.h>
++
++#include "filetreeview.h"
++#include "filesystemmodel.h"
++#include "qheaderview.h"
++#include "actorprocessor.h"
++
++class FileTreeViewPluginPrivate
++{
++public:
++    FileTreeViewPluginPrivate(FileTreeViewPlugin *parent):
++        q_ptr(parent)
++    {}
++
++public:
++    ActorProcessor* m_processor;
++    QWidget* m_notepadWidget;
++    QMenu* m_topMenu;
++    QString m_pluginModulePath;
++    QDockWidget* m_dockWidget;
++    FileTreeView*m_treeview;
++    FileSystemModel * m_model;
++
++    std::function<QsciScintilla* ()> getCurrentEditFunc;
++
++private:
++    FileTreeViewPlugin * const q_ptr;
++    Q_DECLARE_PUBLIC(FileTreeViewPlugin);
++};
++
++FileTreeViewPlugin::FileTreeViewPlugin(QObject *parent):
++    d_ptr(new FileTreeViewPluginPrivate(this))
++{}
++
++FileTreeViewPlugin::~FileTreeViewPlugin()
++{
++}
++
++void FileTreeViewPlugin::setActorProcessor(ActorProcessor *processor)
++{
++    Q_D(FileTreeViewPlugin);
++    d->m_processor = processor;
++}
++
++void FileTreeViewPlugin::setPluginModulePath(const QString &newPluginModulePath)
++{
++    Q_D(FileTreeViewPlugin);
++    d->m_pluginModulePath = newPluginModulePath;
++}
++
++void FileTreeViewPlugin::bindAction()
++{
++}
++
++void FileTreeViewPlugin::setTopMenu(QMenu *newTopMenu)
++{
++    Q_D(FileTreeViewPlugin);
++    d->m_topMenu = newTopMenu;
++}
++
++void FileTreeViewPlugin::setNotepad(QWidget *newNotepad)
++{
++    Q_D(FileTreeViewPlugin);
++    d->m_notepadWidget = newNotepad;
++}
++
++void FileTreeViewPlugin::setMenuActions(QMenu *menu)
++{
++    Q_D(FileTreeViewPlugin);
++    QAction* pAction = new QAction("File Tree", menu);
++    menu->addAction(pAction);
++    connect(pAction, SIGNAL(triggered()),this,SLOT(slotMenuItemClick()));
++}
++
++void FileTreeViewPlugin::setCurrentEditFunc(std::function<QsciScintilla *()> func)
++{
++    Q_D(FileTreeViewPlugin);
++    d->getCurrentEditFunc = func;
++}
++
++void FileTreeViewPlugin::slotMenuItemClick()
++{
++    Q_D(FileTreeViewPlugin);
++
++    auto action = qobject_cast<QAction*>(sender());
++    auto mainWindow = dynamic_cast<QMainWindow*>(d->m_notepadWidget);
++    if (!mainWindow)
++        return;
++
++    if (!d->m_dockWidget) {
++        d->m_dockWidget = new QDockWidget(mainWindow);
++        mainWindow->addDockWidget(Qt::LeftDockWidgetArea, d->m_dockWidget);
++
++        d->m_treeview = new FileTreeView();
++        d->m_treeview->setActorProcessor(d->m_processor);
++        d->m_treeview->setMainWindow(d->m_notepadWidget);
++        d->m_treeview->header()->setVisible(false);
++        d->m_model = new FileSystemModel(d->m_treeview);
++        d->m_model->setRootPath("C:");
++
++        QAction* pAction = new QAction("Open Fold", d->m_topMenu);
++        d->m_topMenu->addAction(pAction);
++        connect(pAction, SIGNAL(triggered(bool)),d->m_treeview,SLOT(onNewWorkspaceItem(bool)));
++        d->m_treeview->setModel(d->m_model);
++        d->m_dockWidget->setWidget(d->m_treeview);
++    }
++
++    if (!d->m_dockWidget->isVisible())
++        d->m_dockWidget->show();
++}
+diff --git a/src/plugin/filetreeview/filetreeviewplugin.h b/src/plugin/filetreeview/filetreeviewplugin.h
+new file mode 100644
+index 0000000..fd35186
+--- /dev/null
++++ b/src/plugin/filetreeview/filetreeviewplugin.h
+@@ -0,0 +1,40 @@
++#ifndef FILETREEWIDGET_H
++#define FILETREEWIDGET_H
++
++#include <QWidget>
++#include <QMenu>
++#include <QString>
++
++class QsciScintilla;
++class FileTreeViewPluginPrivate;
++class ActorProcessor;
++
++class FileTreeViewPlugin :public QObject
++{
++    Q_OBJECT
++public:
++    FileTreeViewPlugin(QObject *parent = nullptr);
++    ~FileTreeViewPlugin();
++
++public:
++    void setActorProcessor(ActorProcessor*processor);
++    void setMenuActions(QMenu *menu);
++    void setCurrentEditFunc(std::function<QsciScintilla* ()> func);
++    void setNotepad(QWidget *newNotepad);
++    void setTopMenu(QMenu *newTopMenu);
++    void setPluginModulePath(const QString &newPluginModulePath);
++private:
++    void bindAction();
++
++public slots:
++    void slotMenuItemClick();
++
++private:
++    FileTreeViewPluginPrivate * const d_ptr;
++    Q_DECLARE_PRIVATE(FileTreeViewPlugin);
++
++    FileTreeViewPlugin(const FileTreeViewPlugin &) = delete;
++    FileTreeViewPlugin &operator=(const FileTreeViewPlugin &) = delete;
++};
++
++#endif // FILETREEWIDGET_H
+-- 
+2.33.1
+
+
+From 527db1baa78809845ce4589841d4e327ae6b5c1b Mon Sep 17 00:00:00 2001
+From: matheuter <[email protected]>
+Date: Thu, 20 Apr 2023 17:01:50 +0800
+Subject: [PATCH 2/4] add actor to plugin_v2
+
+---
+ src/plugin/filetreeview/actor.h | 35 +++++++++++++++++----------------
+ 1 file changed, 18 insertions(+), 17 deletions(-)
+
+diff --git a/src/plugin/filetreeview/actor.h b/src/plugin/filetreeview/actor.h
+index d3901a2..70cd9ff 100644
+--- a/src/plugin/filetreeview/actor.h
++++ b/src/plugin/filetreeview/actor.h
+@@ -1,3 +1,21 @@
++/**
++ ** This file is part of ndd file tree view plugin
++ ** Copyright 2022-2023 ji wang <[email protected]>.
++ **
++ ** This program is free software: you can redistribute it and/or modify
++ ** it under the terms of the GNU Lesser General Public License as
++ ** published by the Free Software Foundation, either version 3 of the
++ ** License, or (at your option) any later version.
++ **
++ ** This program is distributed in the hope that it will be useful,
++ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
++ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ ** GNU Lesser General Public License for more details.
++ **
++ ** You should have received a copy of the GNU Lesser General Public License
++ ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
++ **/
++
+ #pragma once
+ #include <cstdint>
+ #include <string>
+@@ -57,11 +75,6 @@ public:
+         return return_value;
+     }
+ 
+-    Ptr getSharedPtr()
+-    {
+-        return shared_from_this();
+-    }
+-
+ public:
+     /**
+     * @brief This struct encapsulates a function,
+@@ -71,9 +84,6 @@ public:
+     template<typename Function>
+     struct invoker
+     {
+-        /**
+-         * @brief
+-         */
+         static inline void apply(const Function& func, void* bl, void* result)
+         {
+             using tuple_type = typename function_traits<Function>::tuple_type;
+@@ -81,9 +91,6 @@ public:
+             call(func, *tp, result);
+         }
+ 
+-        /**
+-         * @brief
+-         */
+         template<typename F, typename ... Args>
+         static typename std::enable_if<std::is_void<typename std::result_of<F(Args...)>::type>::value>::type
+         call(const F& f, const std::tuple<Args...>& tp, void*)
+@@ -91,9 +98,6 @@ public:
+             call_helper(f, std::make_index_sequence<sizeof... (Args)>{}, tp);
+         }
+ 
+-        /**
+-         * @brief
+-         */
+         template<typename F, typename ... Args>
+         static typename std::enable_if<!std::is_void<typename std::result_of<F(Args...)>::type>::value>::type
+         call(const F& f, const std::tuple<Args...>& tp, void* result)
+@@ -102,9 +106,6 @@ public:
+             *(decltype(r)*)result = r;
+         }
+ 
+-        /**
+-        * @brief
+-        */
+         template<typename F, size_t... I, typename ... Args>
+         static auto call_helper(const F& f, const std::index_sequence<I...>& h, const std::tuple<Args...>& tup)
+         {
+-- 
+2.33.1
+
+
+From 4ab86338f19fcd2e4d4b8565ba34af189174eb82 Mon Sep 17 00:00:00 2001
+From: matheuter <[email protected]>
+Date: Thu, 20 Apr 2023 17:19:00 +0800
+Subject: [PATCH 3/4] update command
+
+---
+ src/actor.h                                   | 24 +----------
+ src/actorprocessor.cpp                        | 19 ++-------
+ src/actorprocessor.h                          | 23 ++---------
+ src/plugin/filetreeview/abstractfile.cpp      |  6 ---
+ src/plugin/filetreeview/abstractfile.h        | 11 -----
+ src/plugin/filetreeview/actor.h               |  5 ++-
+ src/plugin/filetreeview/actorprocessor.cpp    | 19 +++++++++
+ src/plugin/filetreeview/actorprocessor.h      | 41 ++++++++++---------
+ src/plugin/filetreeview/filetreeview.cpp      |  4 +-
+ src/plugin/filetreeview/filetreeview.h        |  5 +--
+ .../filetreeview/filetreeviewplugin.cpp       | 18 ++++++++
+ src/plugin/filetreeview/filetreeviewplugin.h  | 21 +++++++++-
+ 12 files changed, 93 insertions(+), 103 deletions(-)
+ delete mode 100644 src/plugin/filetreeview/abstractfile.cpp
+ delete mode 100644 src/plugin/filetreeview/abstractfile.h
+
+diff --git a/src/actor.h b/src/actor.h
+index 86378d0..0fb51c3 100644
+--- a/src/actor.h
++++ b/src/actor.h
+@@ -1,4 +1,4 @@
+-#pragma once
++
+ #include <cstdint>
+ #include <string>
+ #include <memory>
+@@ -7,10 +7,6 @@
+ 
+ #include "functiontraits.h"
+ 
+-/**
+- * @brief Actor class, which encapsulates the event model
+- * @note enable_shared_from_this allows us to get safe Pointers internally
+- */
+ class Actor : public std::enable_shared_from_this<Actor>
+ {
+ public:
+@@ -21,27 +17,18 @@ public:
+     Actor(){}
+     ~Actor(){}
+ 
+-    /**
+-    * @brief Register the callback function and construct the anonymous function with std::bind
+-    */
+     template<typename Function>
+     void operator+=(Function&& function_any) noexcept
+     {
+         m_invokeFunctionWapper =  { std::bind(&invoker<Function>::apply, function_any,  std::placeholders::_1, std::placeholders::_2) };
+     }
+ 
+-    /**
+-    * @brief Register the callback function and construct the anonymous function with std::bind
+-    */
+     template<typename Function>
+     void registerFunction(Function&& function_any) noexcept
+     {
+         m_invokeFunctionWapper =  { std::bind(&invoker<Function>::apply, function_any,  std::placeholders::_1, std::placeholders::_2) };
+     }
+ 
+-    /**
+-    * @brief Register the callback function and construct the anonymous function with std::bind
+-    */
+     template<typename ... Args>
+     void invoke(Args&& ... args) const noexcept
+     {
+@@ -49,9 +36,6 @@ public:
+         m_invokeFunctionWapper(&args_tuple, nullptr);
+     }
+ 
+-    /**
+-    * @brief Register the callback function and construct the anonymous function with std::bind
+-    */
+     template<typename R, typename ... Args>
+     R invoke(Args&& ...args) const
+     {
+@@ -66,12 +50,8 @@ public:
+         return return_value;
+     }
+ 
+-    Ptr getSharedPtr()
+-    {
+-        return shared_from_this();
+-    }
+-
+ public:
++
+     /**
+     * @brief This struct encapsulates a function,
+     * essentially storing the return value and parameters in two variables
+diff --git a/src/actorprocessor.cpp b/src/actorprocessor.cpp
+index 099d1f6..6dfd1f4 100644
+--- a/src/actorprocessor.cpp
++++ b/src/actorprocessor.cpp
+@@ -1,11 +1,9 @@
+-#include "actorprocessor.h"
++#include "actorprocessor.h"
++
+ ActorProcessor::ActorProcessor():
+     m_actorMap(new std::unordered_map<std::string, Actor*>)
+ {}
+ 
+-/**
+- * @brief
+- */
+ ActorProcessor::~ActorProcessor()
+ {
+     for (auto& item : (*m_actorMap))
+@@ -17,17 +15,11 @@ ActorProcessor::~ActorProcessor()
+         delete m_actorMap;
+ }
+ 
+-/**
+- * @brief
+- */
+ void ActorProcessor::registerActor(const std::string &route, Actor *actor)
+ {
+     m_actorMap->insert(std::make_pair(route,actor));
+ }
+ 
+-/**
+- * @brief
+- */
+ void ActorProcessor::removeActor(const std::string &route)
+ {
+     auto iter = (*m_actorMap).find(route);
+@@ -38,9 +30,6 @@ void ActorProcessor::removeActor(const std::string &route)
+     }
+ }
+ 
+-/**
+- * @brief
+- */
+ Actor *ActorProcessor::findActor(const std::string &route)
+ {
+     auto iter = (*m_actorMap).find(route);
+@@ -51,9 +40,7 @@ Actor *ActorProcessor::findActor(const std::string &route)
+     return nullptr;
+ }
+ 
+-/**
+- * @brief
+- */
++
+ bool ActorProcessor::resetActor(const std::string &route, Actor *actor)
+ {
+     auto iter = (*m_actorMap).find(route);
+diff --git a/src/actorprocessor.h b/src/actorprocessor.h
+index 9026909..def5b06 100644
+--- a/src/actorprocessor.h
++++ b/src/actorprocessor.h
+@@ -1,5 +1,5 @@
+-#ifndef EVENTPROCESSOR_H
+-#define EVENTPROCESSOR_H
++#ifndef _PROCESSOR_H
++#define _PROCESSOR_H
+ 
+ #include <string>
+ #include <functional>
+@@ -25,9 +25,6 @@ public:
+             (*m_actorMap)[route]->invoke(std::forward<Args>(args)...);
+     }
+ 
+-    /**
+-    * @brief Register the callback function and construct the anonymous function with std::bind
+-    */
+     template<typename R, typename ... Args>
+     R invoke(const std::string& route,Args&& ...args) const
+     {
+@@ -36,30 +33,16 @@ public:
+         return nullptr;
+     }
+ 
+-    /**
+-    * @brief 注册一个actor,其中route是唯一的
+-    */
+     void registerActor(const std::string& route, Actor*actor);
+ 
+-    /**
+-    * @brief 删除一个actor
+-    */
+     void removeActor(const std::string& route);
+-
+-    /**
+-    * @brief 查找一个actor, 返回其指针
+-    */
+     Actor* findActor(const std::string& route);
+-
+-    /**
+-    * @brief 重置一个actor,注意原有的actor会被删除
+-    */
+     bool resetActor(const std::string& route,Actor*actor);
+ 
+ private:
+     std::unordered_map<std::string, Actor*>* m_actorMap;
++
+ private:
+-    // not allow copy constroct
+     ActorProcessor(ActorProcessor&&)=delete;
+     ActorProcessor& operator=(const ActorProcessor&)=delete;
+ };
+diff --git a/src/plugin/filetreeview/abstractfile.cpp b/src/plugin/filetreeview/abstractfile.cpp
+deleted file mode 100644
+index 9b8673f..0000000
+--- a/src/plugin/filetreeview/abstractfile.cpp
++++ /dev/null
+@@ -1,6 +0,0 @@
+-#include "abstractfile.h"
+-
+-AbstractFile::AbstractFile()
+-{
+-
+-}
+diff --git a/src/plugin/filetreeview/abstractfile.h b/src/plugin/filetreeview/abstractfile.h
+deleted file mode 100644
+index 94ae7dc..0000000
+--- a/src/plugin/filetreeview/abstractfile.h
++++ /dev/null
+@@ -1,11 +0,0 @@
+-#ifndef ABSTRACTFILE_H
+-#define ABSTRACTFILE_H
+-
+-
+-class AbstractFile
+-{
+-public:
+-    AbstractFile();
+-};
+-
+-#endif // ABSTRACTFILE_H
+diff --git a/src/plugin/filetreeview/actor.h b/src/plugin/filetreeview/actor.h
+index 70cd9ff..30b6f00 100644
+--- a/src/plugin/filetreeview/actor.h
++++ b/src/plugin/filetreeview/actor.h
+@@ -1,6 +1,6 @@
+ /**
+- ** This file is part of ndd file tree view plugin
+- ** Copyright 2022-2023 ji wang <[email protected]>.
++ ** This file is part of ndd plugin file tree view
++ ** Copyright ji wang <[email protected]>.
+  **
+  ** This program is free software: you can redistribute it and/or modify
+  ** it under the terms of the GNU Lesser General Public License as
+@@ -16,6 +16,7 @@
+  ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  **/
+ 
++
+ #pragma once
+ #include <cstdint>
+ #include <string>
+diff --git a/src/plugin/filetreeview/actorprocessor.cpp b/src/plugin/filetreeview/actorprocessor.cpp
+index 099d1f6..96af367 100644
+--- a/src/plugin/filetreeview/actorprocessor.cpp
++++ b/src/plugin/filetreeview/actorprocessor.cpp
+@@ -1,3 +1,22 @@
++/**
++ ** This file is part of ndd plugin file tree view
++ ** Copyright ji wang <[email protected]>.
++ **
++ ** This program is free software: you can redistribute it and/or modify
++ ** it under the terms of the GNU Lesser General Public License as
++ ** published by the Free Software Foundation, either version 3 of the
++ ** License, or (at your option) any later version.
++ **
++ ** This program is distributed in the hope that it will be useful,
++ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
++ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ ** GNU Lesser General Public License for more details.
++ **
++ ** You should have received a copy of the GNU Lesser General Public License
++ ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
++ **/
++
++
+ #include "actorprocessor.h"
+ ActorProcessor::ActorProcessor():
+     m_actorMap(new std::unordered_map<std::string, Actor*>)
+diff --git a/src/plugin/filetreeview/actorprocessor.h b/src/plugin/filetreeview/actorprocessor.h
+index d057e6d..76c6035 100644
+--- a/src/plugin/filetreeview/actorprocessor.h
++++ b/src/plugin/filetreeview/actorprocessor.h
+@@ -1,5 +1,24 @@
+-#ifndef EVENTPROCESSOR_H
+-#define EVENTPROCESSOR_H
++/**
++ ** This file is part of ndd plugin file tree view
++ ** Copyright ji wang <[email protected]>.
++ **
++ ** This program is free software: you can redistribute it and/or modify
++ ** it under the terms of the GNU Lesser General Public License as
++ ** published by the Free Software Foundation, either version 3 of the
++ ** License, or (at your option) any later version.
++ **
++ ** This program is distributed in the hope that it will be useful,
++ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
++ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ ** GNU Lesser General Public License for more details.
++ **
++ ** You should have received a copy of the GNU Lesser General Public License
++ ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
++ **/
++
++
++#ifndef _PROCESSOR_H
++#define _PROCESSOR_H
+ 
+ #include <string>
+ #include <functional>
+@@ -15,9 +34,6 @@ public:
+     ~ActorProcessor();
+ public:
+ 
+-    /**
+-    * @brief Register the callback function and construct the anonymous function with std::bind
+-    */
+     template<typename ... Args>
+     void invoke(const std::string& route,Args&& ... args) const noexcept
+     {
+@@ -25,9 +41,6 @@ public:
+             (*m_actorMap)[route]->invoke(std::forward<Args>(args)...);
+     }
+ 
+-    /**
+-    * @brief Register the callback function and construct the anonymous function with std::bind
+-    */
+     template<typename R, typename ... Args>
+     R invoke(const std::string& route,Args&& ...args) const
+     {
+@@ -36,24 +49,12 @@ public:
+         return nullptr;
+     }
+ 
+-    /**
+-    * @brief 注册一个actor,其中route是唯一的
+-    */
+     void registerActor(const std::string& route, Actor*actor);
+ 
+-    /**
+-    * @brief 删除一个actor
+-    */
+     void removeActor(const std::string& route);
+ 
+-    /**
+-    * @brief 查找一个actor, 返回其指针
+-    */
+     Actor* findActor(const std::string& route);
+ 
+-    /**
+-    * @brief 重置一个actor,注意原有的actor会被删除
+-    */
+     bool resetActor(const std::string& route,Actor*actor);
+ 
+ private:
+diff --git a/src/plugin/filetreeview/filetreeview.cpp b/src/plugin/filetreeview/filetreeview.cpp
+index a1afb12..1259296 100644
+--- a/src/plugin/filetreeview/filetreeview.cpp
++++ b/src/plugin/filetreeview/filetreeview.cpp
+@@ -1,6 +1,6 @@
+ /**
+- ** This file is part of the NoteBook project.
+- ** Copyright 2022 ji wang <[email protected]>.
++ ** This file is part of ndd plugin file tree view
++ ** Copyright ji wang <[email protected]>.
+  **
+  ** This program is free software: you can redistribute it and/or modify
+  ** it under the terms of the GNU Lesser General Public License as
+diff --git a/src/plugin/filetreeview/filetreeview.h b/src/plugin/filetreeview/filetreeview.h
+index 0a0e5e1..9a1eda2 100644
+--- a/src/plugin/filetreeview/filetreeview.h
++++ b/src/plugin/filetreeview/filetreeview.h
+@@ -1,6 +1,6 @@
+ /**
+- ** This file is part of the NoteBook project.
+- ** Copyright 2022 ji wang <[email protected]>.
++ ** This file is part of ndd plugin file tree view
++ ** Copyright ji wang <[email protected]>.
+  **
+  ** This program is free software: you can redistribute it and/or modify
+  ** it under the terms of the GNU Lesser General Public License as
+@@ -16,7 +16,6 @@
+  ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  **/
+ 
+-
+ #ifndef WORKSPACE_H
+ #define WORKSPACE_H
+ 
+diff --git a/src/plugin/filetreeview/filetreeviewplugin.cpp b/src/plugin/filetreeview/filetreeviewplugin.cpp
+index 97eda35..83efcec 100644
+--- a/src/plugin/filetreeview/filetreeviewplugin.cpp
++++ b/src/plugin/filetreeview/filetreeviewplugin.cpp
+@@ -8,6 +8,24 @@
+ #include <QDir>
+ #include <QTreeView>
+ #include <QDebug>
++/**
++ ** This file is part of ndd plugin file tree view
++ ** Copyright ji wang <[email protected]>.
++ **
++ ** This program is free software: you can redistribute it and/or modify
++ ** it under the terms of the GNU Lesser General Public License as
++ ** published by the Free Software Foundation, either version 3 of the
++ ** License, or (at your option) any later version.
++ **
++ ** This program is distributed in the hope that it will be useful,
++ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
++ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ ** GNU Lesser General Public License for more details.
++ **
++ ** You should have received a copy of the GNU Lesser General Public License
++ ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
++ **/
++
+ 
+ #include <qsciscintilla.h>
+ 
+diff --git a/src/plugin/filetreeview/filetreeviewplugin.h b/src/plugin/filetreeview/filetreeviewplugin.h
+index fd35186..336bbc6 100644
+--- a/src/plugin/filetreeview/filetreeviewplugin.h
++++ b/src/plugin/filetreeview/filetreeviewplugin.h
+@@ -1,4 +1,23 @@
+-#ifndef FILETREEWIDGET_H
++/**
++ ** This file is part of ndd plugin file tree view
++ ** Copyright ji wang <[email protected]>.
++ **
++ ** This program is free software: you can redistribute it and/or modify
++ ** it under the terms of the GNU Lesser General Public License as
++ ** published by the Free Software Foundation, either version 3 of the
++ ** License, or (at your option) any later version.
++ **
++ ** This program is distributed in the hope that it will be useful,
++ ** but WITHOUT ANY WARRANTY; without even the implied warranty of
++ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ ** GNU Lesser General Public License for more details.
++ **
++ ** You should have received a copy of the GNU Lesser General Public License
++ ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
++ **/
++
++
++#ifndef FILETREEWIDGET_H
+ #define FILETREEWIDGET_H
+ 
+ #include <QWidget>
+-- 
+2.33.1
+
+
+From 2ad291e9753690727cf92ae18c0bcaa87d5ac169 Mon Sep 17 00:00:00 2001
+From: matheuter <[email protected]>
+Date: Thu, 20 Apr 2023 17:31:42 +0800
+Subject: [PATCH 4/4] fix actor error
+
+---
+ src/actor.h                                | 6 +++++-
+ src/plugin/filetreeview/actor.h            | 5 ++++-
+ src/plugin/filetreeview/actorprocessor.cpp | 1 -
+ 3 files changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/src/actor.h b/src/actor.h
+index 0fb51c3..17f1f16 100644
+--- a/src/actor.h
++++ b/src/actor.h
+@@ -1,4 +1,6 @@
+-
++#ifndef _ACTOR_H_
++#define _ACTOR_H_
++
+ #include <cstdint>
+ #include <string>
+ #include <memory>
+@@ -107,3 +109,5 @@ private:
+     */
+     FunctionWapper m_invokeFunctionWapper;
+ };
++
++#endif
+diff --git a/src/plugin/filetreeview/actor.h b/src/plugin/filetreeview/actor.h
+index 30b6f00..4de5752 100644
+--- a/src/plugin/filetreeview/actor.h
++++ b/src/plugin/filetreeview/actor.h
+@@ -16,8 +16,9 @@
+  ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  **/
+ 
++#ifndef _ACTOR_H_
++#define _ACTOR_H_
+ 
+-#pragma once
+ #include <cstdint>
+ #include <string>
+ #include <memory>
+@@ -120,3 +121,5 @@ private:
+     */
+     FunctionWapper m_invokeFunctionWapper;
+ };
++
++#endif
+diff --git a/src/plugin/filetreeview/actorprocessor.cpp b/src/plugin/filetreeview/actorprocessor.cpp
+index 96af367..972af41 100644
+--- a/src/plugin/filetreeview/actorprocessor.cpp
++++ b/src/plugin/filetreeview/actorprocessor.cpp
+@@ -16,7 +16,6 @@
+  ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  **/
+ 
+-
+ #include "actorprocessor.h"
+ ActorProcessor::ActorProcessor():
+     m_actorMap(new std::unordered_map<std::string, Actor*>)
+-- 
+2.33.1
+