From dc4e75267251945cf3931f60b91a52af9d61d4b4 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sun, 10 Jun 2018 15:35:14 +0200 Subject: [PATCH 1/5] Updated tiny-process-library submodule that has moved to GitLab --- .gitmodules | 2 +- README.md | 2 +- tiny-process-library | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index 80ec1e2e..968739d4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/cppit/libclangmm [submodule "tiny-process-library"] path = tiny-process-library - url = https://github.com/eidheim/tiny-process-library + url = https://gitlab.com/eidheim/tiny-process-library diff --git a/README.md b/README.md index c235929f..4b6cea8b 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ See [enhancements](https://github.com/cppit/jucipp/labels/enhancement) for plann * lldb * libgit2 * [libclangmm](http://github.com/cppit/libclangmm/) (downloaded directly with git --recursive, no need to install) -* [tiny-process-library](http://github.com/eidheim/tiny-process-library/) (downloaded directly with git --recursive, no need to install) +* [tiny-process-library](http://gitlab.com/eidheim/tiny-process-library/) (downloaded directly with git --recursive, no need to install) ## Installation See [installation guide](docs/install.md). diff --git a/tiny-process-library b/tiny-process-library index a0348126..0b989631 160000 --- a/tiny-process-library +++ b/tiny-process-library @@ -1 +1 @@ -Subproject commit a034812647a89e924c9114db9f2d4c89c70226d1 +Subproject commit 0b989631f11156aab1b7532345116f0ea5f2f51c From 641120fbb6f607be4413d7db3a95e0c10a952e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Lien=20Sell=C3=A6g?= Date: Mon, 4 Apr 2016 10:08:41 +0200 Subject: [PATCH 2/5] feature: add pybind11 submodule --- .gitmodules | 3 +++ pybind11 | 1 + 2 files changed, 4 insertions(+) create mode 160000 pybind11 diff --git a/.gitmodules b/.gitmodules index 968739d4..2ecbc5fe 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "tiny-process-library"] path = tiny-process-library url = https://gitlab.com/eidheim/tiny-process-library +[submodule "pybind11"] + path = pybind11 + url = https://github.com/pybind/pybind11.git diff --git a/pybind11 b/pybind11 new file mode 160000 index 00000000..d2385e8f --- /dev/null +++ b/pybind11 @@ -0,0 +1 @@ +Subproject commit d2385e8fc6a3008cab96532c99db4c3d57541fc5 From 3dcb935d743786dd43bdfafce56463804135cebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Lien=20Sell=C3=A6g?= Date: Fri, 8 Apr 2016 14:40:46 +0200 Subject: [PATCH 3/5] feature: new plugin implementation for python --- CMakeLists.txt | 5 + src/CMakeLists.txt | 4 + src/config.cc | 3 + src/config.h | 7 ++ src/juci.cc | 2 + src/menu.h | 3 + src/python_interpreter.cc | 193 ++++++++++++++++++++++++++++++++++++++ src/python_interpreter.h | 54 +++++++++++ src/window.cc | 22 +++++ 9 files changed, 293 insertions(+) create mode 100644 src/python_interpreter.cc create mode 100644 src/python_interpreter.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 35b2373b..a00b1f57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ set(BUILD_TESTING_SAVED ${BUILD_TESTING}) set(BUILD_TESTING OFF CACHE BOOL "Disable sub-project tests" FORCE) add_subdirectory(libclangmm) add_subdirectory(tiny-process-library) +add_subdirectory(pybind11) set(BUILD_TESTING ${BUILD_TESTING_SAVED} CACHE BOOL "Set to previous value" FORCE) find_package(Boost 1.54 COMPONENTS system filesystem serialization REQUIRED) @@ -69,6 +70,8 @@ else() set(LIBLLDB_LIBRARIES "") message("liblldb not found. Building juCi++ without debugging support") endif() +find_package(PythonLibs 3 REQUIRED) +pkg_check_modules(PYGOBJECT pygobject-3.0 REQUIRED) # For both src and tests targets include_directories( @@ -78,6 +81,8 @@ include_directories( ${LIBCLANG_INCLUDE_DIRS} ${ASPELL_INCLUDE_DIR} ${LIBGIT2_INCLUDE_DIRS} + ${PYTHON_INCLUDE_DIRS} + ${PYGOBJECT_INCLUDE_DIRS} ) add_subdirectory("src") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9c32003c..254a4cd4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,6 +33,9 @@ target_link_libraries(juci_shared ${LIBGIT2_LIBRARIES} clangmm tiny-process-library + pybind11 + ${PYTHON_LIBRARIES} + ${PYGOBJECT_LIBRARIES} ) add_executable(juci @@ -46,6 +49,7 @@ add_executable(juci notebook.cc project.cc selection_dialog.cc + python_interpreter.cc tooltips.cc window.cc ) diff --git a/src/config.cc b/src/config.cc index 934b4388..f3d1a4e1 100644 --- a/src/config.cc +++ b/src/config.cc @@ -38,6 +38,7 @@ void Config::find_or_create_config_files() { auto config_json = config_dir/"config.json"; boost::filesystem::create_directories(config_dir); // io exp captured by calling method + boost::filesystem::create_directories(home_juci_path/"plugins"); if (!boost::filesystem::exists(config_json)) filesystem::write(config_json, default_config_file); @@ -101,6 +102,8 @@ void Config::make_version_dependent_corrections(boost::property_tree::ptree &cfg catch(const std::exception &e) { std::cerr << "Error correcting preferences: " << e.what() << std::endl; } + python.plugin_directory=cfg.get("python.plugin_directory",(home_juci_path/"plugins").string()); + python.site_packages=cfg.get("python.site_packages","/usr/lib/python3.5/site-packages"); } bool Config::add_missing_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, std::string parent_path) { diff --git a/src/config.h b/src/config.h index 1c1000be..7ff87331 100644 --- a/src/config.h +++ b/src/config.h @@ -93,6 +93,12 @@ class Config { std::unordered_map documentation_searches; }; + + class Python { + public: + std::string site_packages; + std::string plugin_directory; + }; private: Config(); public: @@ -108,6 +114,7 @@ class Config { Terminal terminal; Project project; Source source; + Python python; boost::filesystem::path home_path; boost::filesystem::path home_juci_path; diff --git a/src/juci.cc b/src/juci.cc index a0ad6773..952c82bf 100644 --- a/src/juci.cc +++ b/src/juci.cc @@ -8,6 +8,7 @@ #ifndef _WIN32 #include #endif +#include "python_interpreter.h" int Application::on_command_line(const Glib::RefPtr &cmd) { Glib::set_prgname("juci"); @@ -120,6 +121,7 @@ void Application::on_startup() { set_app_menu(Menu::get().juci_menu); set_menubar(Menu::get().window_menu); } + Python::Interpreter::get(); } Application::Application() : Gtk::Application("no.sout.juci", Gio::APPLICATION_NON_UNIQUE | Gio::APPLICATION_HANDLES_COMMAND_LINE) { diff --git a/src/menu.h b/src/menu.h index f1afec45..4411eb1b 100644 --- a/src/menu.h +++ b/src/menu.h @@ -20,9 +20,12 @@ class Menu { Glib::RefPtr juci_menu; Glib::RefPtr window_menu; + std::unique_ptr right_click_line_menu; std::unique_ptr right_click_selected_menu; std::function toggle_menu_items = []{}; + Glib::RefPtr plugin_menu; + private: Glib::RefPtr builder; }; diff --git a/src/python_interpreter.cc b/src/python_interpreter.cc new file mode 100644 index 00000000..406ece2a --- /dev/null +++ b/src/python_interpreter.cc @@ -0,0 +1,193 @@ +#include "python_interpreter.h" +#include "notebook.h" +#include "config.h" +#include +#include +#include "menu.h" +#include "directories.h" + +inline pybind11::module pyobject_from_gobj(gpointer ptr){ + auto obj=G_OBJECT(ptr); + if(obj) + return pybind11::module(pygobject_new(obj), false); + return pybind11::module(Py_None, false); +} + +Python::Interpreter::Interpreter(){ +#ifdef _WIN32 + auto root_path=Config::get().terminal.msys2_mingw_path; + append_path(root_path/"include/python3.5m"); + append_path(root_path/"lib/python3.5"); + long long unsigned size = 0L; +#else + long unsigned size = 0L; +#endif + auto init_juci_api=[](){ + pybind11::module(pygobject_init(-1,-1,-1),false); + pybind11::module api("jucpp","Python bindings for juCi++"); + api + .def("get_juci_home",[](){return Config::get().juci_home_path().string();}) + .def("get_plugin_folder",[](){return Config::get().python.plugin_directory;}); + api + .def_submodule("editor") + .def("get_current_gtk_source_view",[](){ + auto view=Notebook::get().get_current_view(); + if(view) + return pyobject_from_gobj(view->gobj()); + return pybind11::module(Py_None,false); + }) + .def("get_file_path",[](){ + auto view=Notebook::get().get_current_view(); + if(view) + return view->file_path.string(); + return std::string(); + }); + api + .def("get_gio_plugin_menu",[](){ + auto &plugin_menu=Menu::get().plugin_menu; + if(!plugin_menu){ + plugin_menu=Gio::Menu::create(); + plugin_menu->append(""); + Menu::get().window_menu->append_submenu("_Plugins",plugin_menu); + } + return pyobject_from_gobj(plugin_menu->gobj()); + }) + .def("get_gio_window_menu",[](){return pyobject_from_gobj(Menu::get().window_menu->gobj());}) + .def("get_gio_juci_menu",[](){return pyobject_from_gobj(Menu::get().juci_menu->gobj());}) + .def("get_gtk_notebook",[](){return pyobject_from_gobj(Notebook::get().gobj());}) + .def_submodule("terminal") + .def("get_gtk_text_view",[](){return pyobject_from_gobj(Terminal::get().gobj());}) + .def("println", [](const std::string &message){ Terminal::get().print(message +"\n"); }); + api.def_submodule("directories") + .def("get_gtk_treeview",[](){return pyobject_from_gobj(Directories::get().gobj());}) + .def("open",[](const std::string &directory){Directories::get().open(directory);}) + .def("update",[](){Directories::get().update();}); + return api.ptr(); + }; + PyImport_AppendInittab("jucipp", init_juci_api); + Config::get().load(); + auto plugin_path=Config::get().python.plugin_directory; + add_path(Config::get().python.site_packages); + add_path(plugin_path); + Py_Initialize(); + argv=Py_DecodeLocale("",&size); + PySys_SetArgv(0,&argv); + auto sys=get_loaded_module("sys"); + auto exc_func=[](pybind11::object type,pybind11::object value,pybind11::object traceback){ + if(!given_exception_matches(type,PyExc_SyntaxError)) + Terminal::get().print(Error(type,value,traceback),true); + else + Terminal::get().print(SyntaxError(type,value,traceback),true); + }; + sys.attr("excepthook")=pybind11::cpp_function(exc_func); + boost::filesystem::directory_iterator end_it; + for(boost::filesystem::directory_iterator it(plugin_path);it!=end_it;it++){ + auto module_name=it->path().stem().string(); + if(module_name.empty()) + break; + auto is_directory=boost::filesystem::is_directory(it->path()); + auto has_py_extension=it->path().extension()==".py"; + auto is_pycache=module_name=="__pycache__"; + if((is_directory && !is_pycache)||has_py_extension){ + auto module=import(module_name); + if(!module){ + auto msg="Error loading plugin `"+module_name+"`:\n"; + auto err=std::string(Error()); + Terminal::get().print(msg+err+"\n"); + } + } + } +} + +pybind11::module Python::get_loaded_module(const std::string &module_name){ + return pybind11::module(PyImport_AddModule(module_name.c_str()), true); +} + +pybind11::module Python::import(const std::string &module_name){ + return pybind11::module(PyImport_ImportModule(module_name.c_str()), false); +} + +pybind11::module Python::reload(pybind11::module &module){ + return pybind11::module(PyImport_ReloadModule(module.ptr()),false); +} + +Python::SyntaxError::SyntaxError(pybind11::object type,pybind11::object value,pybind11::object traceback) +: Error(type,value,traceback){} + +Python::Error::Error(pybind11::object type,pybind11::object value,pybind11::object traceback){ + exp=type; + val=value; + trace=traceback; +} + +void Python::Interpreter::add_path(const boost::filesystem::path &path){ + if(path.empty()) + return; + std::wstring sys_path(Py_GetPath()); + if(!sys_path.empty()) +#ifdef _WIN32 + sys_path += ';'; +#else + sys_path += ':'; +#endif + sys_path += path.generic_wstring(); + Py_SetPath(sys_path.c_str()); +} + +Python::Interpreter::~Interpreter(){ + auto err=Error(); + if(Py_IsInitialized()) + Py_Finalize(); + if(err) + std::cerr << std::string(err) << std::endl; +} + +pybind11::object Python::error_occured(){ + return pybind11::object(PyErr_Occurred(),true); +} + +bool Python::thrown_exception_matches(pybind11::handle exception_type){ + return PyErr_ExceptionMatches(exception_type.ptr()); +} + +bool Python::given_exception_matches(const pybind11::object &exception, pybind11::handle exception_type){ + return PyErr_GivenExceptionMatches(exception.ptr(),exception_type.ptr()); +} + +Python::Error::Error(){ + if(error_occured()){ + try{ + PyErr_Fetch(&exp.ptr(),&val.ptr(),&trace.ptr()); + PyErr_NormalizeException(&exp.ptr(),&val.ptr(),&trace.ptr()); + }catch(const std::exception &e) { + Terminal::get().print(e.what(),true); + } + } +} + +Python::Error::operator std::string(){ + return std::string(exp.str())+"\n"+std::string(val.str())+"\n"; +} + +Python::SyntaxError::SyntaxError():Error(){ + if(val){ + _Py_IDENTIFIER(msg); + _Py_IDENTIFIER(lineno); + _Py_IDENTIFIER(offset); + _Py_IDENTIFIER(text); + exp=std::string(pybind11::str(_PyObject_GetAttrId(val.ptr(),&PyId_msg),false)); + text=std::string(pybind11::str(_PyObject_GetAttrId(val.ptr(),&PyId_text),false)); + pybind11::object py_line_number(_PyObject_GetAttrId(val.ptr(),&PyId_lineno),false); + pybind11::object py_line_offset(_PyObject_GetAttrId(val.ptr(),&PyId_offset),false); + line_number=pybind11::cast(py_line_number); + line_offset=pybind11::cast(py_line_offset); + } +} + +Python::SyntaxError::operator std::string(){ + return exp+" ("+std::to_string(line_number)+":"+std::to_string(line_offset)+"):\n"+text; +} + +Python::Error::operator bool(){ + return exp || trace || val; +} diff --git a/src/python_interpreter.h b/src/python_interpreter.h new file mode 100644 index 00000000..0853793b --- /dev/null +++ b/src/python_interpreter.h @@ -0,0 +1,54 @@ +#ifndef JUCI_PYTHON_INTERPRETER_H_ +#define JUCI_PYTHON_INTERPRETER_H_ + +#include +#include + +#include +using namespace std; + +class Python { + public: + class Interpreter { + private: + Interpreter(); + ~Interpreter(); + wchar_t *argv; + void add_path(const boost::filesystem::path &path); + public: + static Interpreter& get(){ + static Interpreter singleton; + return singleton; + } + }; + + pybind11::module static get_loaded_module(const std::string &module_name); + pybind11::module static import(const std::string &module_name); + pybind11::module static reload(pybind11::module &module); + + class Error { + public: + Error(); + Error(pybind11::object type,pybind11::object value,pybind11::object traceback); + operator std::string(); + operator bool(); + pybind11::object exp, val, trace; + }; + + class SyntaxError : public Error{ + public: + SyntaxError(); + SyntaxError(pybind11::object type,pybind11::object value,pybind11::object traceback); + operator std::string(); + std::string exp, text; + int line_number, line_offset; + }; + + bool static thrown_exception_matches(pybind11::handle exception_type); + bool static given_exception_matches(const pybind11::object &exception,pybind11::handle exception_type); + +private: + pybind11::object static error_occured(); +}; + +#endif // JUCI_PYTHON_INTERPRETER_H_ diff --git a/src/window.cc b/src/window.cc index 62a6dc83..f7278147 100644 --- a/src/window.cc +++ b/src/window.cc @@ -3,6 +3,7 @@ #include "menu.h" #include "notebook.h" #include "directories.h" +#include "python_interpreter.h" #include "dialogs.h" #include "filesystem.h" #include "project.h" @@ -341,6 +342,27 @@ void Window::set_menu_actions() { Notebook::get().get_view(c)->configure(); Notebook::get().configure(c); } + if(view->file_path.extension().string()==".py"){ + auto file_path=view->file_path; + while(file_path.has_parent_path()){ + auto parent=file_path.parent_path(); + if(parent==Config::get().python.plugin_directory){ + auto stem=file_path.stem().string(); + auto module=Python::get_loaded_module(stem); + module=module ? Python::reload(module) : Python::import(stem); + if(module) + Terminal::get().print("Plugin `"+stem+"` was reloaded\n"); + else { + if(Python::thrown_exception_matches(PyExc_SyntaxError)) + Terminal::get().print(Python::SyntaxError()); + else + Terminal::get().print(Python::Error()); + } + break; + } + file_path=parent; + } + } } } } From 301bd0b20548bf0a758b61fc051e17e5cbc1f5dc Mon Sep 17 00:00:00 2001 From: milleniumbug Date: Tue, 19 Apr 2016 13:19:22 +0200 Subject: [PATCH 4/5] Backported Py_DecodeLocale from 3.5 --- src/python_interpreter.cc | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/python_interpreter.cc b/src/python_interpreter.cc index 406ece2a..af423590 100644 --- a/src/python_interpreter.cc +++ b/src/python_interpreter.cc @@ -6,6 +6,17 @@ #include "menu.h" #include "directories.h" +static wchar_t* DecodeLocale(const char* arg, size_t *size) +{ +#ifndef PY_VERSION_HEX +#error Python not included +#elif PY_VERSION_HEX < 0x03050000 + return _Py_char2wchar(arg, size); +#else + return Py_DecodeLocale(arg, size); +#endif +} + inline pybind11::module pyobject_from_gobj(gpointer ptr){ auto obj=G_OBJECT(ptr); if(obj) @@ -70,7 +81,7 @@ Python::Interpreter::Interpreter(){ add_path(Config::get().python.site_packages); add_path(plugin_path); Py_Initialize(); - argv=Py_DecodeLocale("",&size); + argv=DecodeLocale("",&size); PySys_SetArgv(0,&argv); auto sys=get_loaded_module("sys"); auto exc_func=[](pybind11::object type,pybind11::object value,pybind11::object traceback){ From 8d84ac5a1fef4284d53a7d519a8c830f7e0820d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Lien=20Sell=C3=A6g?= Date: Tue, 19 Apr 2016 13:41:56 +0200 Subject: [PATCH 5/5] feature: Update pybind11 to latest version --- ci/update_ci.sh | 4 +- docs/install.md | 2 +- pybind11 | 2 +- src/config.cc | 2 +- src/menu.h | 2 - src/python_interpreter.cc | 181 ++++++++++++++------------------------ src/python_interpreter.h | 37 ++------ src/window.cc | 48 ++++++---- 8 files changed, 106 insertions(+), 172 deletions(-) diff --git a/ci/update_ci.sh b/ci/update_ci.sh index 93ebb0e0..ef8ab197 100755 --- a/ci/update_ci.sh +++ b/ci/update_ci.sh @@ -32,11 +32,11 @@ function windows () { if [ "$PLATFORM" == "x86" ]; then arch=i686 fi - sh -c "pacman -S --noconfirm git mingw-w64-${arch}-cmake make mingw-w64-${arch}-toolchain mingw-w64-${arch}-clang mingw-w64-${arch}-gtkmm3 mingw-w64-${arch}-gtksourceviewmm3 mingw-w64-${arch}-boost mingw-w64-${arch}-aspell mingw-w64-${arch}-aspell-en mingw-w64-${arch}-libgit2" + sh -c "pacman -S --noconfirm git mingw-w64-${arch}-pygobject-devel mingw-w64-${arch}-cmake make mingw-w64-${arch}-toolchain mingw-w64-${arch}-clang mingw-w64-${arch}-gtkmm3 mingw-w64-${arch}-gtksourceviewmm3 mingw-w64-${arch}-boost mingw-w64-${arch}-aspell mingw-w64-${arch}-aspell-en mingw-w64-${arch}-libgit2" } if [ "$TRAVIS_OS_NAME" == "" ]; then TRAVIS_OS_NAME=windows fi -$TRAVIS_OS_NAME \ No newline at end of file +$TRAVIS_OS_NAME diff --git a/docs/install.md b/docs/install.md index 357ac587..ad90fa35 100644 --- a/docs/install.md +++ b/docs/install.md @@ -142,7 +142,7 @@ make install Install dependencies (replace `x86_64` with `i686` for 32-bit MSYS2 installs): ```sh -pacman -S git mingw-w64-x86_64-cmake make mingw-w64-x86_64-toolchain mingw-w64-x86_64-clang mingw-w64-x86_64-gtkmm3 mingw-w64-x86_64-gtksourceviewmm3 mingw-w64-x86_64-boost mingw-w64-x86_64-aspell mingw-w64-x86_64-aspell-en mingw-w64-x86_64-libgit2 mingw-w64-x86_64-universal-ctags-git +pacman -S git mingw-w64-x86_64-cmake make mingw-w64-x86_64-toolchain mingw-w64-x86_64-clang mingw-w64-x86_64-gtkmm3 mingw-w64-x86_64-gtksourceviewmm3 mingw-w64-x86_64-boost mingw-w64-x86_64-aspell mingw-w64-x86_64-aspell-en mingw-w64-x86_64-gobject-introspection mingw-w64-x86_64-pygobject-devel mingw-w64-x86_64-python3-gobject mingw-w64-x86_64-libgit2 mingw-w64-x86_64-universal-ctags-git ``` Note that juCi++ must be built and run in a MinGW Shell (for instance MinGW-w64 Win64 Shell). diff --git a/pybind11 b/pybind11 index d2385e8f..ed07e492 160000 --- a/pybind11 +++ b/pybind11 @@ -1 +1 @@ -Subproject commit d2385e8fc6a3008cab96532c99db4c3d57541fc5 +Subproject commit ed07e49236d937fd4ae98d0b0cb58a962cd269a6 diff --git a/src/config.cc b/src/config.cc index f3d1a4e1..f12cafd8 100644 --- a/src/config.cc +++ b/src/config.cc @@ -103,7 +103,7 @@ void Config::make_version_dependent_corrections(boost::property_tree::ptree &cfg std::cerr << "Error correcting preferences: " << e.what() << std::endl; } python.plugin_directory=cfg.get("python.plugin_directory",(home_juci_path/"plugins").string()); - python.site_packages=cfg.get("python.site_packages","/usr/lib/python3.5/site-packages"); + python.site_packages=cfg.get("python.site_packages",""); } bool Config::add_missing_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, std::string parent_path) { diff --git a/src/menu.h b/src/menu.h index 4411eb1b..a2a59673 100644 --- a/src/menu.h +++ b/src/menu.h @@ -20,12 +20,10 @@ class Menu { Glib::RefPtr juci_menu; Glib::RefPtr window_menu; - std::unique_ptr right_click_line_menu; std::unique_ptr right_click_selected_menu; std::function toggle_menu_items = []{}; Glib::RefPtr plugin_menu; - private: Glib::RefPtr builder; }; diff --git a/src/python_interpreter.cc b/src/python_interpreter.cc index af423590..d9aecd69 100644 --- a/src/python_interpreter.cc +++ b/src/python_interpreter.cc @@ -5,6 +5,7 @@ #include #include "menu.h" #include "directories.h" +#include "terminal.h" static wchar_t* DecodeLocale(const char* arg, size_t *size) { @@ -20,24 +21,17 @@ static wchar_t* DecodeLocale(const char* arg, size_t *size) inline pybind11::module pyobject_from_gobj(gpointer ptr){ auto obj=G_OBJECT(ptr); if(obj) - return pybind11::module(pygobject_new(obj), false); - return pybind11::module(Py_None, false); + return pybind11::reinterpret_steal(pygobject_new(obj)); + return pybind11::reinterpret_steal(Py_None); } Python::Interpreter::Interpreter(){ -#ifdef _WIN32 - auto root_path=Config::get().terminal.msys2_mingw_path; - append_path(root_path/"include/python3.5m"); - append_path(root_path/"lib/python3.5"); - long long unsigned size = 0L; -#else - long unsigned size = 0L; -#endif + auto init_juci_api=[](){ - pybind11::module(pygobject_init(-1,-1,-1),false); + auto module = pybind11::reinterpret_steal(pygobject_init(-1,-1,-1)); pybind11::module api("jucpp","Python bindings for juCi++"); api - .def("get_juci_home",[](){return Config::get().juci_home_path().string();}) + .def("get_juci_home",[](){return Config::get().home_juci_path.string();}) .def("get_plugin_folder",[](){return Config::get().python.plugin_directory;}); api .def_submodule("editor") @@ -45,7 +39,7 @@ Python::Interpreter::Interpreter(){ auto view=Notebook::get().get_current_view(); if(view) return pyobject_from_gobj(view->gobj()); - return pybind11::module(Py_None,false); + return pybind11::reinterpret_steal(Py_None); }) .def("get_file_path",[](){ auto view=Notebook::get().get_current_view(); @@ -55,13 +49,12 @@ Python::Interpreter::Interpreter(){ }); api .def("get_gio_plugin_menu",[](){ - auto &plugin_menu=Menu::get().plugin_menu; - if(!plugin_menu){ - plugin_menu=Gio::Menu::create(); - plugin_menu->append(""); - Menu::get().window_menu->append_submenu("_Plugins",plugin_menu); + if(!Menu::get().plugin_menu){ + Menu::get().plugin_menu=Gio::Menu::create(); + Menu::get().plugin_menu->append(""); + Menu::get().window_menu->append_submenu("_Plugins",Menu::get().plugin_menu); } - return pyobject_from_gobj(plugin_menu->gobj()); + return pyobject_from_gobj(Menu::get().plugin_menu->gobj()); }) .def("get_gio_window_menu",[](){return pyobject_from_gobj(Menu::get().window_menu->gobj());}) .def("get_gio_juci_menu",[](){return pyobject_from_gobj(Menu::get().juci_menu->gobj());}) @@ -76,129 +69,87 @@ Python::Interpreter::Interpreter(){ return api.ptr(); }; PyImport_AppendInittab("jucipp", init_juci_api); + Config::get().load(); - auto plugin_path=Config::get().python.plugin_directory; - add_path(Config::get().python.site_packages); - add_path(plugin_path); + configure_path(); Py_Initialize(); + #ifdef _WIN32 + long long unsigned size = 0L; + #else + long unsigned size = 0L; + #endif argv=DecodeLocale("",&size); PySys_SetArgv(0,&argv); - auto sys=get_loaded_module("sys"); - auto exc_func=[](pybind11::object type,pybind11::object value,pybind11::object traceback){ - if(!given_exception_matches(type,PyExc_SyntaxError)) - Terminal::get().print(Error(type,value,traceback),true); - else - Terminal::get().print(SyntaxError(type,value,traceback),true); - }; - sys.attr("excepthook")=pybind11::cpp_function(exc_func); boost::filesystem::directory_iterator end_it; - for(boost::filesystem::directory_iterator it(plugin_path);it!=end_it;it++){ + for(boost::filesystem::directory_iterator it(Config::get().python.plugin_directory);it!=end_it;it++){ auto module_name=it->path().stem().string(); if(module_name.empty()) - break; + continue; auto is_directory=boost::filesystem::is_directory(it->path()); auto has_py_extension=it->path().extension()==".py"; auto is_pycache=module_name=="__pycache__"; if((is_directory && !is_pycache)||has_py_extension){ - auto module=import(module_name); - if(!module){ - auto msg="Error loading plugin `"+module_name+"`:\n"; - auto err=std::string(Error()); - Terminal::get().print(msg+err+"\n"); + try { + pybind11::module::import(module_name.c_str()); + } catch (pybind11::error_already_set &error) { + Terminal::get().print("Error loading plugin `"+module_name+"`:\n"+error.what()+"\n"); } } } + auto sys=find_module("sys"); + if(sys){ + auto exc_func=[](pybind11::object type,pybind11::object value,pybind11::object traceback){ + std::cerr << "ERROR FUNCTION"; + }; + sys.attr("excepthook")=pybind11::cpp_function(exc_func); + } else { + std::cerr << "Failed to set exception hook\n"; + } } -pybind11::module Python::get_loaded_module(const std::string &module_name){ - return pybind11::module(PyImport_AddModule(module_name.c_str()), true); -} - -pybind11::module Python::import(const std::string &module_name){ - return pybind11::module(PyImport_ImportModule(module_name.c_str()), false); -} - -pybind11::module Python::reload(pybind11::module &module){ - return pybind11::module(PyImport_ReloadModule(module.ptr()),false); +pybind11::module Python::Interpreter::find_module(const std::string &module_name){ + return pybind11::reinterpret_borrow(PyImport_AddModule(module_name.c_str())); } -Python::SyntaxError::SyntaxError(pybind11::object type,pybind11::object value,pybind11::object traceback) -: Error(type,value,traceback){} - -Python::Error::Error(pybind11::object type,pybind11::object value,pybind11::object traceback){ - exp=type; - val=value; - trace=traceback; +pybind11::module Python::Interpreter::reload(pybind11::module &module){ + auto reload=pybind11::reinterpret_steal(PyImport_ReloadModule(module.ptr())); + if(!reload) + throw pybind11::error_already_set(); + return reload; } -void Python::Interpreter::add_path(const boost::filesystem::path &path){ - if(path.empty()) - return; - std::wstring sys_path(Py_GetPath()); - if(!sys_path.empty()) -#ifdef _WIN32 - sys_path += ';'; -#else - sys_path += ':'; -#endif - sys_path += path.generic_wstring(); +void Python::Interpreter::configure_path(){ + const std::vector python_path = { + "/usr/lib/python3.6", + "/usr/lib/python3.6/lib-dynload", + "/usr/lib/python3.6/site-packages", + Config::get().python.site_packages, + Config::get().python.plugin_directory + }; + std::wstring sys_path; + for(auto &path:python_path){ + if(path.empty()) + continue; + if(!sys_path.empty()){ + #ifdef _WIN32 + sys_path += ';'; + #else + sys_path += ':'; + #endif + } + sys_path += path.generic_wstring(); + } Py_SetPath(sys_path.c_str()); } Python::Interpreter::~Interpreter(){ - auto err=Error(); if(Py_IsInitialized()) Py_Finalize(); - if(err) - std::cerr << std::string(err) << std::endl; + if(error()) + std::cerr << pybind11::error_already_set().what() << std::endl; } -pybind11::object Python::error_occured(){ - return pybind11::object(PyErr_Occurred(),true); +pybind11::object Python::Interpreter::error(){ + return pybind11::reinterpret_borrow(PyErr_Occurred()); } -bool Python::thrown_exception_matches(pybind11::handle exception_type){ - return PyErr_ExceptionMatches(exception_type.ptr()); -} - -bool Python::given_exception_matches(const pybind11::object &exception, pybind11::handle exception_type){ - return PyErr_GivenExceptionMatches(exception.ptr(),exception_type.ptr()); -} - -Python::Error::Error(){ - if(error_occured()){ - try{ - PyErr_Fetch(&exp.ptr(),&val.ptr(),&trace.ptr()); - PyErr_NormalizeException(&exp.ptr(),&val.ptr(),&trace.ptr()); - }catch(const std::exception &e) { - Terminal::get().print(e.what(),true); - } - } -} - -Python::Error::operator std::string(){ - return std::string(exp.str())+"\n"+std::string(val.str())+"\n"; -} - -Python::SyntaxError::SyntaxError():Error(){ - if(val){ - _Py_IDENTIFIER(msg); - _Py_IDENTIFIER(lineno); - _Py_IDENTIFIER(offset); - _Py_IDENTIFIER(text); - exp=std::string(pybind11::str(_PyObject_GetAttrId(val.ptr(),&PyId_msg),false)); - text=std::string(pybind11::str(_PyObject_GetAttrId(val.ptr(),&PyId_text),false)); - pybind11::object py_line_number(_PyObject_GetAttrId(val.ptr(),&PyId_lineno),false); - pybind11::object py_line_offset(_PyObject_GetAttrId(val.ptr(),&PyId_offset),false); - line_number=pybind11::cast(py_line_number); - line_offset=pybind11::cast(py_line_offset); - } -} - -Python::SyntaxError::operator std::string(){ - return exp+" ("+std::to_string(line_number)+":"+std::to_string(line_offset)+"):\n"+text; -} - -Python::Error::operator bool(){ - return exp || trace || val; -} diff --git a/src/python_interpreter.h b/src/python_interpreter.h index 0853793b..b30ef1ae 100644 --- a/src/python_interpreter.h +++ b/src/python_interpreter.h @@ -7,48 +7,23 @@ #include using namespace std; -class Python { - public: +namespace Python { class Interpreter { + public: + pybind11::module static find_module(const std::string &module_name); + pybind11::module static reload(pybind11::module &module); + pybind11::object static error(); private: Interpreter(); ~Interpreter(); wchar_t *argv; - void add_path(const boost::filesystem::path &path); + void configure_path(); public: static Interpreter& get(){ static Interpreter singleton; return singleton; } }; - - pybind11::module static get_loaded_module(const std::string &module_name); - pybind11::module static import(const std::string &module_name); - pybind11::module static reload(pybind11::module &module); - - class Error { - public: - Error(); - Error(pybind11::object type,pybind11::object value,pybind11::object traceback); - operator std::string(); - operator bool(); - pybind11::object exp, val, trace; - }; - - class SyntaxError : public Error{ - public: - SyntaxError(); - SyntaxError(pybind11::object type,pybind11::object value,pybind11::object traceback); - operator std::string(); - std::string exp, text; - int line_number, line_offset; - }; - - bool static thrown_exception_matches(pybind11::handle exception_type); - bool static given_exception_matches(const pybind11::object &exception,pybind11::handle exception_type); - -private: - pybind11::object static error_occured(); }; #endif // JUCI_PYTHON_INTERPRETER_H_ diff --git a/src/window.cc b/src/window.cc index f7278147..dc7b2992 100644 --- a/src/window.cc +++ b/src/window.cc @@ -342,26 +342,36 @@ void Window::set_menu_actions() { Notebook::get().get_view(c)->configure(); Notebook::get().configure(c); } - if(view->file_path.extension().string()==".py"){ - auto file_path=view->file_path; - while(file_path.has_parent_path()){ - auto parent=file_path.parent_path(); - if(parent==Config::get().python.plugin_directory){ - auto stem=file_path.stem().string(); - auto module=Python::get_loaded_module(stem); - module=module ? Python::reload(module) : Python::import(stem); - if(module) - Terminal::get().print("Plugin `"+stem+"` was reloaded\n"); - else { - if(Python::thrown_exception_matches(PyExc_SyntaxError)) - Terminal::get().print(Python::SyntaxError()); - else - Terminal::get().print(Python::Error()); - } - break; - } - file_path=parent; + } + const auto refresh_module = [](const std::string &stem){ + auto module = Python::Interpreter::find_module(stem); + if(module) { + try { + module = pybind11::reinterpret_steal(PyImport_ReloadModule(module.ptr())); + } catch (const pybind11::error_already_set &error) { + Terminal::get().print("Plugin `"+stem+"` didn't reload\n"+error.what()+"\n"); + } + } else { + { pybind11::error_already_set(); } + try { + module = pybind11::module::import(stem.c_str()); + } catch (const pybind11::error_already_set &error) { + Terminal::get().print("Plugin `"+stem+"` didn't reload\n"+error.what()+"\n"); + } + } + if(module) + Terminal::get().print("Plugin `"+stem+"` was reloaded\n"); + }; + if(view->file_path.extension().string()==".py"){ + auto file_path=view->file_path; + while(file_path.has_parent_path()){ + auto parent=file_path.parent_path(); + if(parent==Config::get().python.plugin_directory){ + auto stem=file_path.stem().string(); + refresh_module(stem); + break; } + file_path=parent; } } }