Skip to content
This repository has been archived by the owner on Jun 25, 2020. It is now read-only.

Python implementation using pybind11 and gobject introspection #181

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@
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
[submodule "pybind11"]
path = pybind11
url = https://github.com/pybind/pybind11.git
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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(
Expand All @@ -78,6 +81,8 @@ include_directories(
${LIBCLANG_INCLUDE_DIRS}
${ASPELL_INCLUDE_DIR}
${LIBGIT2_INCLUDE_DIRS}
${PYTHON_INCLUDE_DIRS}
${PYGOBJECT_INCLUDE_DIRS}
)

add_subdirectory("src")
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
4 changes: 2 additions & 2 deletions ci/update_ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
$TRAVIS_OS_NAME
2 changes: 1 addition & 1 deletion docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
1 change: 1 addition & 0 deletions pybind11
Submodule pybind11 added at ed07e4
4 changes: 4 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ target_link_libraries(juci_shared
${LIBGIT2_LIBRARIES}
clangmm
tiny-process-library
pybind11
${PYTHON_LIBRARIES}
${PYGOBJECT_LIBRARIES}
)

add_executable(juci
Expand All @@ -46,6 +49,7 @@ add_executable(juci
notebook.cc
project.cc
selection_dialog.cc
python_interpreter.cc
tooltips.cc
window.cc
)
Expand Down
3 changes: 3 additions & 0 deletions src/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<std::string>("python.plugin_directory",(home_juci_path/"plugins").string());
python.site_packages=cfg.get<std::string>("python.site_packages","");
}

bool Config::add_missing_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, std::string parent_path) {
Expand Down
7 changes: 7 additions & 0 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ class Config {

std::unordered_map<std::string, DocumentationSearch> documentation_searches;
};

class Python {
public:
std::string site_packages;
std::string plugin_directory;
};
private:
Config();
public:
Expand All @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/juci.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#ifndef _WIN32
#include <csignal>
#endif
#include "python_interpreter.h"

int Application::on_command_line(const Glib::RefPtr<Gio::ApplicationCommandLine> &cmd) {
Glib::set_prgname("juci");
Expand Down Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions src/menu.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Menu {
std::unique_ptr<Gtk::Menu> right_click_line_menu;
std::unique_ptr<Gtk::Menu> right_click_selected_menu;
std::function<void()> toggle_menu_items = []{};
Glib::RefPtr<Gio::Menu> plugin_menu;
private:
Glib::RefPtr<Gtk::Builder> builder;
};
155 changes: 155 additions & 0 deletions src/python_interpreter.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#include "python_interpreter.h"
#include "notebook.h"
#include "config.h"
#include <iostream>
#include <pygobject.h>
#include "menu.h"
#include "directories.h"
#include "terminal.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)
return pybind11::reinterpret_steal<pybind11::module>(pygobject_new(obj));
return pybind11::reinterpret_steal<pybind11::module>(Py_None);
}

Python::Interpreter::Interpreter(){

auto init_juci_api=[](){
auto module = pybind11::reinterpret_steal<pybind11::module>(pygobject_init(-1,-1,-1));
pybind11::module api("jucpp","Python bindings for juCi++");
api
.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")
.def("get_current_gtk_source_view",[](){
auto view=Notebook::get().get_current_view();
if(view)
return pyobject_from_gobj(view->gobj());
return pybind11::reinterpret_steal<pybind11::module>(Py_None);
})
.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",[](){
if(!Menu::get().plugin_menu){
Menu::get().plugin_menu=Gio::Menu::create();
Menu::get().plugin_menu->append("<empty>");
Menu::get().window_menu->append_submenu("_Plugins",Menu::get().plugin_menu);
}
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());})
.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();
configure_path();
Py_Initialize();
#ifdef _WIN32
long long unsigned size = 0L;
#else
long unsigned size = 0L;
#endif
argv=DecodeLocale("",&size);
PySys_SetArgv(0,&argv);
boost::filesystem::directory_iterator end_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())
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){
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::Interpreter::find_module(const std::string &module_name){
return pybind11::reinterpret_borrow<pybind11::module>(PyImport_AddModule(module_name.c_str()));
}

pybind11::module Python::Interpreter::reload(pybind11::module &module){
auto reload=pybind11::reinterpret_steal<pybind11::module>(PyImport_ReloadModule(module.ptr()));
if(!reload)
throw pybind11::error_already_set();
return reload;
}

void Python::Interpreter::configure_path(){
const std::vector<boost::filesystem::path> 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(){
if(Py_IsInitialized())
Py_Finalize();
if(error())
std::cerr << pybind11::error_already_set().what() << std::endl;
}

pybind11::object Python::Interpreter::error(){
return pybind11::reinterpret_borrow<pybind11::object>(PyErr_Occurred());
}

29 changes: 29 additions & 0 deletions src/python_interpreter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#ifndef JUCI_PYTHON_INTERPRETER_H_
#define JUCI_PYTHON_INTERPRETER_H_

#include <pybind11/pybind11.h>
#include <boost/filesystem/path.hpp>

#include <iostream>
using namespace std;

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 configure_path();
public:
static Interpreter& get(){
static Interpreter singleton;
return singleton;
}
};
};

#endif // JUCI_PYTHON_INTERPRETER_H_
32 changes: 32 additions & 0 deletions src/window.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -342,6 +343,37 @@ void Window::set_menu_actions() {
Notebook::get().configure(c);
}
}
const auto refresh_module = [](const std::string &stem){
auto module = Python::Interpreter::find_module(stem);
if(module) {
try {
module = pybind11::reinterpret_steal<pybind11::module>(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;
}
}
}
}
});
Expand Down
2 changes: 1 addition & 1 deletion tiny-process-library
Submodule tiny-process-library updated from a03481 to 0b9896