From 63c822c887a9a19202c5ae31f49b8badcfd0c0d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Lien=20Sell=C3=A6g?= Date: Mon, 25 Feb 2019 21:57:26 +0100 Subject: [PATCH] bind terminal and add type caster for filesystem path --- CMakeLists.txt | 1 + src/CMakeLists.txt | 1 + src/config.cpp | 9 ++++++ src/config.hpp | 7 +++++ src/files.hpp | 5 ++++ src/juci.cpp | 13 ++++---- src/juci.hpp | 13 ++++++-- src/plugins.h | 55 ++++++++++++++++++++++++++++++++++ src/python_bind.h | 3 ++ src/python_interpreter.cc | 31 ++++++++++++++++++++ src/python_interpreter.h | 13 ++++++++ src/python_module.h | 62 +++++++++++++++++++++++++++++++++++++++ src/window.cpp | 18 +++++++----- src/window.hpp | 11 +++---- tests/CMakeLists.txt | 1 - 15 files changed, 218 insertions(+), 25 deletions(-) create mode 100644 src/plugins.h create mode 100644 src/python_bind.h create mode 100644 src/python_interpreter.cc create mode 100644 src/python_interpreter.h create mode 100644 src/python_module.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8dec029..74b2a17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,6 +173,7 @@ include_directories( ${LIBGIT2_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS} ${PYGOBJECT_INCLUDE_DIRS} + ${CMAKE_SOURCE_DIR}/lib/pybind11/include ) add_subdirectory("src") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9a41714..24f1462 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,6 +54,7 @@ set(JUCI_SOURCES selection_dialog.cc tooltips.cc window.cc + python_interpreter.cc ) if(APPLE) diff --git a/src/config.cpp b/src/config.cpp index da9a25e..941f77e 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -3,6 +3,7 @@ #include "filesystem.hpp" #include "terminal.hpp" #include +#include #include #include @@ -212,4 +213,12 @@ void Config::read(const boost::property_tree::ptree &cfg) { log.libclang = cfg.get("log.libclang"); log.language_server = cfg.get("log.language_server"); + + auto plugins_path = cfg.get("plugins.path"); + boost::replace_all(plugins_path, "", home_juci_path.string()); + plugins.path = plugins_path; + plugins.enabled = cfg.get("plugins.enabled"); + if (plugins.enabled) { + std::cout << "Plugins enabled" << std::endl; + } } diff --git a/src/config.hpp b/src/config.hpp index c954a78..629314d 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -113,6 +113,12 @@ public: bool language_server; }; + class Plugins { + public: + bool enabled; + std::string path; + }; + private: Config(); @@ -131,6 +137,7 @@ public: Project project; Source source; Log log; + Plugins plugins; boost::filesystem::path home_path; boost::filesystem::path home_juci_path; diff --git a/src/files.hpp b/src/files.hpp index e1f08c9..0c325ea 100644 --- a/src/files.hpp +++ b/src/files.hpp @@ -233,6 +233,11 @@ const std::string default_config_file = R"RAW({ "libclang_comment": "Outputs diagnostics for new C/C++ buffers", "libclang": false, "language_server": false + }, + "plugins": { + "enabled": true, + "path_comment": "Directory where plugins are loaded from.", + "path": "/plugins" } } )RAW"; diff --git a/src/juci.cpp b/src/juci.cpp index 41d31f1..ad0da2b 100644 --- a/src/juci.cpp +++ b/src/juci.cpp @@ -55,12 +55,11 @@ int Application::on_command_line(const Glib::RefPtr void Application::on_activate() { std::vector> file_offsets; std::string current_file; - Window::get().load_session(directories, files, file_offsets, current_file, directories.empty() && files.empty()); - - Window::get().add_widgets(); - - add_window(Window::get()); - Window::get().show(); + window.init(); + window.load_session(directories, files, file_offsets, current_file, directories.empty() && files.empty()); + window.add_widgets(); + add_window(window); + window.show(); bool first_directory = true; for(auto &directory : directories) { @@ -132,7 +131,7 @@ void Application::on_startup() { } } -Application::Application() : Gtk::Application("no.sout.juci", Gio::APPLICATION_NON_UNIQUE | Gio::APPLICATION_HANDLES_COMMAND_LINE) { +Application::Application() : Gtk::Application("no.sout.juci", Gio::APPLICATION_NON_UNIQUE | Gio::APPLICATION_HANDLES_COMMAND_LINE), window(plugins) { Glib::set_application_name("juCi++"); //Gtk::MessageDialog without buttons caused text to be selected, this prevents that diff --git a/src/juci.hpp b/src/juci.hpp index fc20fbe..45b36c1 100644 --- a/src/juci.hpp +++ b/src/juci.hpp @@ -1,4 +1,10 @@ #pragma once +#include +#include +#include "window.h" +#ifdef JUCI_ENABLE_PLUGINS +#include "plugins.h" +#endif /** \mainpage juCi++ is a lightweight C++ IDE written in C++ @@ -25,9 +31,6 @@ [juCi++] --> [tiny-process-library] : use \enduml */ -#include -#include - class Application : public Gtk::Application { public: Application(); @@ -39,4 +42,8 @@ private: std::vector directories; std::vector> files; std::vector errors; + Window window; +#ifdef JUCI_ENABLE_PLUGINS + Plugins plugins; +#endif }; diff --git a/src/plugins.h b/src/plugins.h new file mode 100644 index 0000000..98a0d5e --- /dev/null +++ b/src/plugins.h @@ -0,0 +1,55 @@ +#pragma once + +#include "config.h" +#include "menu.h" +#include "python_interpreter.h" +#include "python_module.h" +#include +#include + +class __attribute__((visibility("default"))) +Plugins { +public: + Plugins() : jucipp_module("Jucipp", Module::init_jucipp_module) { + auto &config = Config::get(); + config.load(); + auto sys = py::module::import("sys"); + sys.attr("path").cast().append(config.plugins.path); + // sys.attr("excepthook") = py::cpp_function([](py::object type, py::object value, py::object traceback) { + // Terminal::get().print("const std::string &message\n"); + // }); + } + void load() { + boost::filesystem::directory_iterator end_it; + for(boost::filesystem::directory_iterator it(Config::get().plugins.path); it != end_it; it++) { + auto module_name = it->path().stem().string(); + if(module_name.empty()) + continue; + const auto is_directory = boost::filesystem::is_directory(it->path()); + const auto has_py_extension = it->path().extension() == ".py"; + const auto is_pycache = module_name == "__pycache__"; + if((is_directory && !is_pycache) || has_py_extension) { + try { + auto module = interpreter.add_module(module_name); + if (module) { + Terminal::get().print("Reloading plugin ´" + module_name + "´\n"); + interpreter.reload_module(module); + } else { + Terminal::get().print("Loading plugin ´" + module_name + "´\n"); + py::module::import(module_name.c_str()); + } + + } + catch(py::error_already_set &error) { + Terminal::get().print("Error loading plugin `" + module_name + "`:\n" + error.what() + "\n"); + } + } + if(interpreter.error()) + std::cerr << py::error_already_set().what() << std::endl; + } + } + +private: + py::detail::embedded_module jucipp_module; + Python::Interpreter interpreter; +}; diff --git a/src/python_bind.h b/src/python_bind.h new file mode 100644 index 0000000..4ece974 --- /dev/null +++ b/src/python_bind.h @@ -0,0 +1,3 @@ +#pragma once +#include +namespace py = pybind11; diff --git a/src/python_interpreter.cc b/src/python_interpreter.cc new file mode 100644 index 0000000..bc03174 --- /dev/null +++ b/src/python_interpreter.cc @@ -0,0 +1,31 @@ +#include "python_interpreter.h" + +pybind11::module Python::Interpreter::add_module(const std::string &module_name) { + return pybind11::reinterpret_borrow(PyImport_AddModule(module_name.c_str())); +} + +pybind11::object Python::Interpreter::error() { + return pybind11::reinterpret_borrow(PyErr_Occurred()); +} + +pybind11::module Python::Interpreter::reload_module(pybind11::module &module) { + auto reload = pybind11::reinterpret_steal(PyImport_ReloadModule(module.ptr())); + if(!reload) { + throw pybind11::error_already_set(); + } + return reload; +} + +#include +#include + +Python::Interpreter::~Interpreter() { + if (error()){ + std::cout << py::error_already_set().what() << std::endl; + } + py::finalize_interpreter(); +} + +Python::Interpreter::Interpreter() { + py::initialize_interpreter(); +} diff --git a/src/python_interpreter.h b/src/python_interpreter.h new file mode 100644 index 0000000..1986ab0 --- /dev/null +++ b/src/python_interpreter.h @@ -0,0 +1,13 @@ +#pragma once +#include "python_bind.h" + +namespace Python { + class Interpreter { + public: + pybind11::module static add_module(const std::string &module_name); + pybind11::module static reload_module(pybind11::module &module); + pybind11::object static error(); + ~Interpreter(); + Interpreter(); + }; +}; // namespace Python diff --git a/src/python_module.h b/src/python_module.h new file mode 100644 index 0000000..8d7b853 --- /dev/null +++ b/src/python_module.h @@ -0,0 +1,62 @@ +#pragma once +#include "python_bind.h" +#include "terminal.h" +#include +#include + +namespace pybind11 { + namespace detail { + template <> + struct type_caster { + public: + PYBIND11_TYPE_CASTER(boost::filesystem::path, _("str")); + bool load(handle src, bool) { + const std::string path = pybind11::str(src); + value = path; + return !PyErr_Occurred(); + } + + static handle cast(boost::filesystem::path src, return_value_policy, handle) { + return pybind11::str(src.string()); + } + }; + } +} + +class Module { + static void init_terminal_module(pybind11::module &api) { + py::class_>(api, "Terminal") + .def(py::init([]() { return &(Terminal::get()); })) + .def("process", (int (Terminal::*)(const std::string &, const boost::filesystem::path &, bool)) & Terminal::process, + py::arg("command"), + py::arg("path") = "", + py::arg("use_pipes") = false) + .def("async_process", (void (Terminal::*)(const std::string &, const boost::filesystem::path &, const std::function &, bool)) & Terminal::async_process, + py::arg("command"), + py::arg("path") = "", + py::arg("callback") = nullptr, + py::arg("quiet") = false) + .def("kill_last_async_process", &Terminal::kill_last_async_process, + py::arg("force") = false) + .def("kill_async_processes", &Terminal::kill_async_processes, + py::arg("force") = false) + .def("print", &Terminal::print, + py::arg("message"), + py::arg("bold") = false) + .def("async_print", (void (Terminal::*)(const std::string &, bool)) & Terminal::async_print, + py::arg("message"), + py::arg("bold") = false) + .def("async_print", (void (Terminal::*)(size_t, const std::string &)) & Terminal::async_print, + py::arg("line_nr"), + py::arg("message")) + .def("configure", &Terminal::configure) + .def("clear", &Terminal::clear); + }; + +public: + static auto init_jucipp_module() { + auto api = py::module("Jucipp", "API"); + Module::init_terminal_module(api); + return api.ptr(); + }; +}; diff --git a/src/window.cpp b/src/window.cpp index 3d69dcc..f4a1057 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -16,24 +16,24 @@ #include "selection_dialog.hpp" #include "terminal.hpp" -Window::Window() { +Window::Window(Plugins &i) : plugins(i) { Gsv::init(); - set_title("juCi++"); get_style_context()->add_class("juci_window"); set_events(Gdk::POINTER_MOTION_MASK | Gdk::FOCUS_CHANGE_MASK | Gdk::SCROLL_MASK | Gdk::LEAVE_NOTIFY_MASK); +} +void Window::init() { auto visual = get_screen()->get_rgba_visual(); if(visual) gtk_widget_set_visual(reinterpret_cast(gobj()), visual->gobj()); - auto provider = Gtk::CssProvider::create(); auto screen = get_screen(); std::string border_radius_style; if(screen->get_rgba_visual()) border_radius_style = "border-radius: 5px; "; #if GTK_VERSION_GE(3, 20) - std::string notebook_style(".juci_notebook tab {border-radius: 5px 5px 0 0; padding: 0 4px; margin: 0;}"); +n std::string notebook_style(".juci_notebook tab {border-radius: 5px 5px 0 0; padding: 0 4px; margin: 0;}"); #else std::string notebook_style(".juci_notebook {-GtkNotebook-tab-overlap: 0px;} .juci_notebook tab {border-radius: 5px 5px 0 0; padding: 4px 4px;}"); #endif @@ -160,7 +160,10 @@ Window::Window() { about.set_comments("This is an open source IDE with high-end features to make your programming experience juicy"); about.set_license_type(Gtk::License::LICENSE_MIT_X11); about.set_transient_for(*this); -} // Window constructor + if(Config::get().plugins.enabled) { + plugins.load(); + } +} void Window::configure() { Config::get().load(); @@ -274,8 +277,9 @@ void Window::set_menu_actions() { auto &menu = Menu::get(); menu.add_action("about", [this]() { - about.show(); - about.present(); + plugins.load(); + // about.show(); + // about.present(); }); menu.add_action("preferences", []() { Notebook::get().open(Config::get().home_juci_path / "config" / "config.json"); diff --git a/src/window.hpp b/src/window.hpp index 20b37a0..ad3c3ba 100644 --- a/src/window.hpp +++ b/src/window.hpp @@ -3,24 +3,21 @@ #include #include #include +#include "plugins.h" class Window : public Gtk::ApplicationWindow { - Window(); - public: - static Window &get() { - static Window singleton; - return singleton; - } + Window(Plugins &p); void add_widgets(); void save_session(); void load_session(std::vector &directories, std::vector> &files, std::vector> &file_offsets, std::string ¤t_file, bool read_directories_and_files); - + void init(); protected: bool on_key_press_event(GdkEventKey *event) override; bool on_delete_event(GdkEventAny *event) override; private: + Plugins& plugins; Gtk::AboutDialog about; Gtk::ScrolledWindow directories_scrolled_window, terminal_scrolled_window; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 00322dc..f41cd14 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,7 +9,6 @@ include_directories( ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/libclangmm/src ${CMAKE_SOURCE_DIR}/tiny-process-library - ${CMAKE_SOURCE_DIR}/pybind11 ) add_library(test_stubs OBJECT