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

Commit 4503b1d

Browse files
committed
feature: new plugin implementation for python
1 parent 4770e04 commit 4503b1d

File tree

9 files changed

+292
-0
lines changed

9 files changed

+292
-0
lines changed

CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,13 @@ else()
5151
endif()
5252

5353
find_package(Boost 1.54 COMPONENTS system filesystem REQUIRED)
54+
find_package(PythonLibs 3 REQUIRED)
5455
find_package(ASPELL REQUIRED)
5556
include(FindPkgConfig)
5657
pkg_check_modules(GTKMM gtkmm-3.0 REQUIRED)
5758
pkg_check_modules(GTKSVMM gtksourceviewmm-3.0 REQUIRED)
5859
pkg_check_modules(LIBGIT2 libgit2 REQUIRED)
60+
pkg_check_modules(PYGOBJECT pygobject-3.0 REQUIRED)
5961

6062
if(MSYS)
6163
set(global_libraries winpthread)
@@ -68,6 +70,8 @@ set(global_libraries ${global_libraries}
6870
${LIBLLDB_LIBRARIES}
6971
${ASPELL_LIBRARIES}
7072
${LIBGIT2_LIBRARIES}
73+
${PYTHON_LIBRARIES}
74+
${PYGOBJECT_LIBRARIES}
7175
)
7276

7377
set(global_includes
@@ -77,6 +81,9 @@ set(global_includes
7781
${LIBCLANG_INCLUDE_DIRS}
7882
${ASPELL_INCLUDE_DIR}
7983
${LIBGIT2_INCLUDE_DIRS}
84+
${PYTHON_INCLUDE_DIRS}
85+
${PYGOBJECT_INCLUDE_DIRS}
86+
${PROJECT_SOURCE_DIR}/pybind11/include
8087
${PROJECT_SOURCE_DIR}/libclangmm/src
8188
${PROJECT_SOURCE_DIR}/tiny-process-library
8289
${PROJECT_SOURCE_DIR}/src

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ set(project_files
1212
juci.cc
1313
notebook.cc
1414
project.cc
15+
python_interpreter.cc
1516
selection_dialog.cc
1617
terminal.cc
1718
tooltips.cc

src/config.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ void Config::find_or_create_config_files() {
5656
auto config_json = config_dir/"config.json";
5757

5858
boost::filesystem::create_directories(config_dir); // io exp captured by calling method
59+
boost::filesystem::create_directories(home/"plugins");
5960

6061
if (!boost::filesystem::exists(config_json))
6162
filesystem::write(config_json, default_config_file);
@@ -117,6 +118,8 @@ void Config::retrieve_config() {
117118
}
118119
}
119120
#endif
121+
python.plugin_directory=cfg.get<std::string>("python.plugin_directory",(home/"plugins").string());
122+
python.site_packages=cfg.get<std::string>("python.site_packages","/usr/lib/python3.5/site-packages");
120123
}
121124

122125
bool Config::add_missing_nodes(const boost::property_tree::ptree &default_cfg, std::string parent_path) {

src/config.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ class Config {
9393

9494
std::unordered_map<std::string, DocumentationSearch> documentation_searches;
9595
};
96+
97+
class Python {
98+
public:
99+
std::string site_packages;
100+
std::string plugin_directory;
101+
};
96102
private:
97103
Config();
98104
public:
@@ -108,6 +114,7 @@ class Config {
108114
Terminal terminal;
109115
Project project;
110116
Source source;
117+
Python python;
111118

112119
boost::filesystem::path home_path;
113120
boost::filesystem::path home_juci_path;

src/juci.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "directories.h"
55
#include "menu.h"
66
#include "config.h"
7+
#include "python_interpreter.h"
78

89
int Application::on_command_line(const Glib::RefPtr<Gio::ApplicationCommandLine> &cmd) {
910
Glib::set_prgname("juci");
@@ -113,6 +114,7 @@ void Application::on_startup() {
113114
set_app_menu(Menu::get().juci_menu);
114115
set_menubar(Menu::get().window_menu);
115116
}
117+
Python::Interpreter::get();
116118
}
117119

118120
Application::Application() : Gtk::Application("no.sout.juci", Gio::APPLICATION_NON_UNIQUE | Gio::APPLICATION_HANDLES_COMMAND_LINE) {

src/menu.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ class Menu {
2121

2222
Glib::RefPtr<Gio::Menu> juci_menu;
2323
Glib::RefPtr<Gio::Menu> window_menu;
24+
2425
std::unique_ptr<Gtk::Menu> right_click_line_menu;
2526
std::unique_ptr<Gtk::Menu> right_click_selected_menu;
27+
Glib::RefPtr<Gio::Menu> plugin_menu;
28+
2629
private:
2730
Glib::RefPtr<Gtk::Builder> builder;
2831
};

src/python_interpreter.cc

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
#include "python_interpreter.h"
2+
#include "notebook.h"
3+
#include "config.h"
4+
#include <iostream>
5+
#include <pygobject.h>
6+
#include "menu.h"
7+
#include "directories.h"
8+
9+
inline pybind11::module pyobject_from_gobj(gpointer ptr){
10+
auto obj=G_OBJECT(ptr);
11+
if(obj)
12+
return pybind11::module(pygobject_new(obj), false);
13+
return pybind11::module(Py_None, false);
14+
}
15+
16+
Python::Interpreter::Interpreter(){
17+
#ifdef _WIN32
18+
auto root_path=Config::get().terminal.msys2_mingw_path;
19+
append_path(root_path/"include/python3.5m");
20+
append_path(root_path/"lib/python3.5");
21+
long long unsigned size = 0L;
22+
#else
23+
long unsigned size = 0L;
24+
#endif
25+
auto init_juci_api=[](){
26+
pybind11::module(pygobject_init(-1,-1,-1),false);
27+
pybind11::module api("jucpp","Python bindings for juCi++");
28+
api
29+
.def("get_juci_home",[](){return Config::get().juci_home_path().string();})
30+
.def("get_plugin_folder",[](){return Config::get().python.plugin_directory;});
31+
api
32+
.def_submodule("editor")
33+
.def("get_current_gtk_source_view",[](){
34+
auto view=Notebook::get().get_current_view();
35+
if(view)
36+
return pyobject_from_gobj(view->gobj());
37+
return pybind11::module(Py_None,false);
38+
})
39+
.def("get_file_path",[](){
40+
auto view=Notebook::get().get_current_view();
41+
if(view)
42+
return view->file_path.string();
43+
return std::string();
44+
});
45+
api
46+
.def("get_gio_plugin_menu",[](){
47+
auto &plugin_menu=Menu::get().plugin_menu;
48+
if(!plugin_menu){
49+
plugin_menu=Gio::Menu::create();
50+
plugin_menu->append("<empty>");
51+
Menu::get().window_menu->append_submenu("_Plugins",plugin_menu);
52+
}
53+
return pyobject_from_gobj(plugin_menu->gobj());
54+
})
55+
.def("get_gio_window_menu",[](){return pyobject_from_gobj(Menu::get().window_menu->gobj());})
56+
.def("get_gio_juci_menu",[](){return pyobject_from_gobj(Menu::get().juci_menu->gobj());})
57+
.def("get_gtk_notebook",[](){return pyobject_from_gobj(Notebook::get().gobj());})
58+
.def_submodule("terminal")
59+
.def("get_gtk_text_view",[](){return pyobject_from_gobj(Terminal::get().gobj());})
60+
.def("println", [](const std::string &message){ Terminal::get().print(message +"\n"); });
61+
api.def_submodule("directories")
62+
.def("get_gtk_treeview",[](){return pyobject_from_gobj(Directories::get().gobj());})
63+
.def("open",[](const std::string &directory){Directories::get().open(directory);})
64+
.def("update",[](){Directories::get().update();});
65+
return api.ptr();
66+
};
67+
PyImport_AppendInittab("jucipp", init_juci_api);
68+
Config::get().load();
69+
auto plugin_path=Config::get().python.plugin_directory;
70+
add_path(Config::get().python.site_packages);
71+
add_path(plugin_path);
72+
Py_Initialize();
73+
argv=Py_DecodeLocale("",&size);
74+
PySys_SetArgv(0,&argv);
75+
auto sys=get_loaded_module("sys");
76+
auto exc_func=[](pybind11::object type,pybind11::object value,pybind11::object traceback){
77+
if(!given_exception_matches(type,PyExc_SyntaxError))
78+
Terminal::get().print(Error(type,value,traceback),true);
79+
else
80+
Terminal::get().print(SyntaxError(type,value,traceback),true);
81+
};
82+
sys.attr("excepthook")=pybind11::cpp_function(exc_func);
83+
boost::filesystem::directory_iterator end_it;
84+
for(boost::filesystem::directory_iterator it(plugin_path);it!=end_it;it++){
85+
auto module_name=it->path().stem().string();
86+
if(module_name.empty())
87+
break;
88+
auto is_directory=boost::filesystem::is_directory(it->path());
89+
auto has_py_extension=it->path().extension()==".py";
90+
auto is_pycache=module_name=="__pycache__";
91+
if((is_directory && !is_pycache)||has_py_extension){
92+
auto module=import(module_name);
93+
if(!module){
94+
auto msg="Error loading plugin `"+module_name+"`:\n";
95+
auto err=std::string(Error());
96+
Terminal::get().print(msg+err+"\n");
97+
}
98+
}
99+
}
100+
}
101+
102+
pybind11::module Python::get_loaded_module(const std::string &module_name){
103+
return pybind11::module(PyImport_AddModule(module_name.c_str()), true);
104+
}
105+
106+
pybind11::module Python::import(const std::string &module_name){
107+
return pybind11::module(PyImport_ImportModule(module_name.c_str()), false);
108+
}
109+
110+
pybind11::module Python::reload(pybind11::module &module){
111+
return pybind11::module(PyImport_ReloadModule(module.ptr()),false);
112+
}
113+
114+
Python::SyntaxError::SyntaxError(pybind11::object type,pybind11::object value,pybind11::object traceback)
115+
: Error(type,value,traceback){}
116+
117+
Python::Error::Error(pybind11::object type,pybind11::object value,pybind11::object traceback){
118+
exp=type;
119+
val=value;
120+
trace=traceback;
121+
}
122+
123+
void Python::Interpreter::add_path(const boost::filesystem::path &path){
124+
if(path.empty())
125+
return;
126+
std::wstring sys_path(Py_GetPath());
127+
if(!sys_path.empty())
128+
#ifdef _WIN32
129+
sys_path += ';';
130+
#else
131+
sys_path += ':';
132+
#endif
133+
sys_path += path.generic_wstring();
134+
Py_SetPath(sys_path.c_str());
135+
}
136+
137+
Python::Interpreter::~Interpreter(){
138+
auto err=Error();
139+
if(Py_IsInitialized())
140+
Py_Finalize();
141+
if(err)
142+
std::cerr << std::string(err) << std::endl;
143+
}
144+
145+
pybind11::object Python::error_occured(){
146+
return pybind11::object(PyErr_Occurred(),true);
147+
}
148+
149+
bool Python::thrown_exception_matches(pybind11::handle exception_type){
150+
return PyErr_ExceptionMatches(exception_type.ptr());
151+
}
152+
153+
bool Python::given_exception_matches(const pybind11::object &exception, pybind11::handle exception_type){
154+
return PyErr_GivenExceptionMatches(exception.ptr(),exception_type.ptr());
155+
}
156+
157+
Python::Error::Error(){
158+
if(error_occured()){
159+
try{
160+
PyErr_Fetch(&exp.ptr(),&val.ptr(),&trace.ptr());
161+
PyErr_NormalizeException(&exp.ptr(),&val.ptr(),&trace.ptr());
162+
}catch(const std::exception &e) {
163+
Terminal::get().print(e.what(),true);
164+
}
165+
}
166+
}
167+
168+
Python::Error::operator std::string(){
169+
return std::string(exp.str())+"\n"+std::string(val.str())+"\n";
170+
}
171+
172+
Python::SyntaxError::SyntaxError():Error(){
173+
if(val){
174+
_Py_IDENTIFIER(msg);
175+
_Py_IDENTIFIER(lineno);
176+
_Py_IDENTIFIER(offset);
177+
_Py_IDENTIFIER(text);
178+
exp=std::string(pybind11::str(_PyObject_GetAttrId(val.ptr(),&PyId_msg),false));
179+
text=std::string(pybind11::str(_PyObject_GetAttrId(val.ptr(),&PyId_text),false));
180+
pybind11::object py_line_number(_PyObject_GetAttrId(val.ptr(),&PyId_lineno),false);
181+
pybind11::object py_line_offset(_PyObject_GetAttrId(val.ptr(),&PyId_offset),false);
182+
line_number=pybind11::cast<int>(py_line_number);
183+
line_offset=pybind11::cast<int>(py_line_offset);
184+
}
185+
}
186+
187+
Python::SyntaxError::operator std::string(){
188+
return exp+" ("+std::to_string(line_number)+":"+std::to_string(line_offset)+"):\n"+text;
189+
}
190+
191+
Python::Error::operator bool(){
192+
return exp || trace || val;
193+
}

src/python_interpreter.h

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#ifndef JUCI_PYTHON_INTERPRETER_H_
2+
#define JUCI_PYTHON_INTERPRETER_H_
3+
4+
#include <pybind11/pybind11.h>
5+
#include <boost/filesystem/path.hpp>
6+
7+
#include <iostream>
8+
using namespace std;
9+
10+
class Python {
11+
public:
12+
class Interpreter {
13+
private:
14+
Interpreter();
15+
~Interpreter();
16+
wchar_t *argv;
17+
void add_path(const boost::filesystem::path &path);
18+
public:
19+
static Interpreter& get(){
20+
static Interpreter singleton;
21+
return singleton;
22+
}
23+
};
24+
25+
pybind11::module static get_loaded_module(const std::string &module_name);
26+
pybind11::module static import(const std::string &module_name);
27+
pybind11::module static reload(pybind11::module &module);
28+
29+
class Error {
30+
public:
31+
Error();
32+
Error(pybind11::object type,pybind11::object value,pybind11::object traceback);
33+
operator std::string();
34+
operator bool();
35+
pybind11::object exp, val, trace;
36+
};
37+
38+
class SyntaxError : public Error{
39+
public:
40+
SyntaxError();
41+
SyntaxError(pybind11::object type,pybind11::object value,pybind11::object traceback);
42+
operator std::string();
43+
std::string exp, text;
44+
int line_number, line_offset;
45+
};
46+
47+
bool static thrown_exception_matches(pybind11::handle exception_type);
48+
bool static given_exception_matches(const pybind11::object &exception,pybind11::handle exception_type);
49+
50+
private:
51+
pybind11::object static error_occured();
52+
};
53+
54+
#endif // JUCI_PYTHON_INTERPRETER_H_

src/window.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "menu.h"
44
#include "notebook.h"
55
#include "directories.h"
6+
#include "python_interpreter.h"
67
#include "dialogs.h"
78
#include "filesystem.h"
89
#include "project.h"
@@ -395,6 +396,27 @@ void Window::set_menu_actions() {
395396
Notebook::get().get_view(c)->configure();
396397
Notebook::get().configure(c);
397398
}
399+
if(view->file_path.extension().string()==".py"){
400+
auto file_path=view->file_path;
401+
while(file_path.has_parent_path()){
402+
auto parent=file_path.parent_path();
403+
if(parent==Config::get().python.plugin_directory){
404+
auto stem=file_path.stem().string();
405+
auto module=Python::get_loaded_module(stem);
406+
module=module ? Python::reload(module) : Python::import(stem);
407+
if(module)
408+
Terminal::get().print("Plugin `"+stem+"` was reloaded\n");
409+
else {
410+
if(Python::thrown_exception_matches(PyExc_SyntaxError))
411+
Terminal::get().print(Python::SyntaxError());
412+
else
413+
Terminal::get().print(Python::Error());
414+
}
415+
break;
416+
}
417+
file_path=parent;
418+
}
419+
}
398420
}
399421
}
400422
}

0 commit comments

Comments
 (0)