diff --git a/docs/install.md b/docs/install.md index 3e06b5e..345a815 100644 --- a/docs/install.md +++ b/docs/install.md @@ -6,7 +6,7 @@ First dependencies: ```sh $ sudo apt-get install libboost-python-dev libboost-filesystem-dev libboost-log-dev libboost-test-dev libboost-thread-dev libboost-system-dev libgtkmm-3.0-dev libgtksourceview2.0-dev libgtksourceviewmm-3.0-dev -libpython-dev libclang-dev make cmake gcc +libpython-dev libclang-dev make cmake gcc g++ ``` Install the project: ```sh diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 54c9549..5f892eb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -79,7 +79,9 @@ if(${validation}) tooltips.h tooltips.cc singletons.h - singletons.cc) + singletons.cc + cmake.h + cmake.cc) add_library(${module} SHARED api diff --git a/src/cmake.cc b/src/cmake.cc new file mode 100644 index 0000000..0d9fefc --- /dev/null +++ b/src/cmake.cc @@ -0,0 +1,263 @@ +#include "cmake.h" +#include "sourcefile.h" +#include +#include "singletons.h" + +#include //TODO: remove +using namespace std; //TODO: remove + +CMake::CMake(const boost::filesystem::path &path) { + const auto find_cmake_project=[this](const boost::filesystem::path &cmake_path) { + for(auto &line: juci::filesystem::read_lines(cmake_path)) { + const std::regex project_regex("^ *project *\\(.*$"); + std::smatch sm; + if(std::regex_match(line, sm, project_regex)) { + return true; + } + } + return false; + }; + + auto search_path=boost::filesystem::path(path); + auto search_cmake_path=search_path; + search_cmake_path+="/CMakeLists.txt"; + if(boost::filesystem::exists(search_cmake_path)) + paths.emplace(paths.begin(), search_cmake_path); + if(find_cmake_project(search_cmake_path)) + project_path=search_path; + else { + do { + search_path=search_path.parent_path(); + search_cmake_path=search_path; + search_cmake_path+="/CMakeLists.txt"; + if(boost::filesystem::exists(search_cmake_path)) + paths.emplace(paths.begin(), search_cmake_path); + if(find_cmake_project(search_cmake_path)) { + project_path=search_path; + break; + } + } while(search_path!=search_path.root_directory()); + } + if(project_path!="") { + if(boost::filesystem::exists(project_path.string()+"/CMakeLists.txt") && !boost::filesystem::exists(project_path.string()+"/compile_commands.json")) + create_compile_commands(project_path.string()); + } +} + +bool CMake::create_compile_commands(const std::string &path) { + Singleton::terminal()->print("Creating "+boost::filesystem::path(path+"/compile_commands.json").string()+"\n"); + //TODO: Windows... + if(Singleton::terminal()->execute("cmake . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON 2>&1", path)==EXIT_SUCCESS) + return true; + return false; +} + +void CMake::read_files() { + for(auto &path: paths) + files.emplace_back(juci::filesystem::read(path)); +} + +void CMake::remove_tabs() { + for(auto &file: files) { + for(auto &chr: file) { + if(chr=='\t') + chr=' '; + } + } +} + +void CMake::remove_comments() { + for(auto &file: files) { + size_t pos=0; + size_t comment_start; + bool inside_comment=false; + while(posstart_line) { + auto line=file.substr(start_line, end_line-start_line); + const std::regex set_regex("^ *set *\\( *([A-Za-z_][A-Za-z_0-9]*) +(.*)\\) *$"); + std::smatch sm; + if(std::regex_match(line, sm, set_regex)) { + std::string data=sm[2]; + while(data.size()>0 && data.back()==' ') + data.pop_back(); + parse_variable_parameters(data); + variables[sm[1]]=data; + } + } + pos=end_line+1; + } + } +} + +void CMake::parse_variable_parameters(std::string &data) { + size_t pos=0; + bool inside_quote=false; + char last_char=0; + while(pos CMake::get_function_parameters(std::string &data) { + std::vector parameters; + size_t pos=0; + size_t parameter_pos=0; + bool inside_quote=false; + char last_char=0; + while(pos > > CMake::get_functions_parameters(const std::string &name) { + if(!parsed) + parse(); + std::vector > > functions; + size_t file_c=0; + for(auto &file: files) { + size_t pos=0; + while(posstart_line) { + auto line=file.substr(start_line, end_line-start_line); + const std::regex function_regex("^ *"+name+" *\\( *(.*)\\) *$"); + std::smatch sm; + if(std::regex_match(line, sm, function_regex)) { + std::string data=sm[1]; + while(data.size()>0 && data.back()==' ') + data.pop_back(); + auto parameters=get_function_parameters(data); + functions.emplace(functions.begin(), paths[file_c], parameters); + } + } + pos=end_line+1; + } + file_c++; + } + return functions; +} diff --git a/src/cmake.h b/src/cmake.h new file mode 100644 index 0000000..f300f15 --- /dev/null +++ b/src/cmake.h @@ -0,0 +1,28 @@ +#ifndef JUCI_CMAKE_H_ +#define JUCI_CMAKE_H_ +#include +#include +#include + +class CMake { +public: + CMake(const boost::filesystem::path &path); + std::vector > > get_functions_parameters(const std::string &name); + static bool create_compile_commands(const std::string &path); + + std::vector paths; + std::vector files; + boost::filesystem::path project_path; + std::unordered_map variables; +private: + void read_files(); + void remove_tabs(); + void remove_comments(); + void remove_newlines_inside_parentheses(); + void find_variables(); + void parse_variable_parameters(std::string &data); + void parse(); + std::vector get_function_parameters(std::string &data); + bool parsed=false; +}; +#endif //JUCI_CMAKE_H_ diff --git a/src/config.cc b/src/config.cc index 604efd1..ed7d4e9 100644 --- a/src/config.cc +++ b/src/config.cc @@ -12,7 +12,6 @@ MainConfig::MainConfig() { GenerateSource(); GenerateTheme(); GenerateDirectoryFilter(); - GenerateTerminalCommands(); } void MainConfig::GenerateTheme() { @@ -54,17 +53,6 @@ void MainConfig::GenerateSource() { DEBUG("Source cfg fetched"); } -void MainConfig::GenerateTerminalCommands() { - auto terminal_cfg=Singleton::Config::terminal(); - boost::property_tree::ptree source_json = cfg.get_child("project"); - boost::property_tree::ptree compile_commands_json = source_json.get_child("compile_commands"); - boost::property_tree::ptree run_commands_json = source_json.get_child("run_commands"); - for (auto &i : compile_commands_json) - terminal_cfg->compile_commands.emplace_back(i.second.get_value()); - for (auto &i : run_commands_json) - terminal_cfg->run_command=(i.second.get_value()); //TODO: run_commands array->one run_command? -} - void MainConfig::GenerateDirectoryFilter() { auto dir_cfg=Singleton::Config::directories(); DEBUG("Fetching directory filter"); diff --git a/src/config.h b/src/config.h index 86e8c5d..4362fb0 100644 --- a/src/config.h +++ b/src/config.h @@ -12,10 +12,7 @@ public: void GenerateSource(); void GenerateDirectoryFilter(); void GenerateTheme(); - void GenerateTerminalCommands(); - private: boost::property_tree::ptree cfg; - }; #endif diff --git a/src/directories.cc b/src/directories.cc index edac27b..088a0f7 100644 --- a/src/directories.cc +++ b/src/directories.cc @@ -5,6 +5,9 @@ #include #include "boost/algorithm/string.hpp" +#include //TODO: remove +using namespace std; //TODO: remove + namespace sigc { SIGC_FUNCTORS_DEDUCE_RESULT_TYPE_WITH_DECLTYPE } @@ -38,21 +41,39 @@ Directories::Directories() { } void Directories::open_folder(const boost::filesystem::path& dir_path) { + auto new_path=dir_path; INFO("Open folder"); + if(new_path=="") { + if(current_path=="") + return; + new_path=current_path; + } + std::vector expanded_paths; - if(last_dir_path==dir_path) { + if(current_path==new_path) { tree_view.map_expanded_rows([&expanded_paths](Gtk::TreeView* tree_view, const Gtk::TreeModel::Path& path){ expanded_paths.emplace_back(path); }); } tree_store->clear(); - tree_view.get_column(0)->set_title(get_cmakelists_variable(dir_path, "project")); - add_paths(dir_path, Gtk::TreeModel::Row(), 0); + + if(dir_path!="") + cmake=std::unique_ptr(new CMake(new_path)); + auto project=cmake->get_functions_parameters("project"); + if(project.size()>0 && project[0].second.size()>0) + tree_view.get_column(0)->set_title(project[0].second[0]); + else + tree_view.get_column(0)->set_title(""); + add_paths(new_path, Gtk::TreeModel::Row(), 0); for(auto &path: expanded_paths) tree_view.expand_row(path, false); - last_dir_path=dir_path; + + current_path=new_path; + + if(selected_path.size()>0) + select_path(selected_path); DEBUG("Folder opened"); } @@ -62,6 +83,7 @@ void Directories::select_path(const std::string &path) { auto tree_path=Gtk::TreePath(iter); tree_view.expand_to_path(tree_path); tree_view.set_cursor(tree_path); + selected_path=path; return true; } return false; @@ -118,67 +140,3 @@ void Directories::add_paths(const boost::filesystem::path& dir_path, const Gtk:: } } } - -std::string Directories::get_cmakelists_variable(const boost::filesystem::path& dir_path, std::string command_name) { - INFO("fetches cmake variable value for: "+command_name); - std::string project_name; - std::string project_name_var; - boost::filesystem::directory_iterator end_itr; - for (boost::filesystem::directory_iterator itr( dir_path );itr != end_itr;++itr ) { - if (itr->path().filename().string() == "CMakeLists.txt") { - for (auto &line : juci::filesystem::read_lines(itr->path())) { - if (line.find(command_name+"(", 0) != std::string::npos - || line.find(command_name+" (", 0) != std::string::npos ) { - size_t variable_start = line.find("{", 0); - size_t variable_end = line.find("}", variable_start); - project_name_var = line.substr(variable_start+1, - (variable_end)-variable_start-1); - boost::algorithm::trim(project_name_var); - if (variable_start == std::string::npos) { // not a variabel - variable_start = line.find("(", 0); - - variable_end = line.find(' ', variable_start); - if(variable_end != std::string::npos){ - return line.substr(variable_start+1, - (variable_end)-variable_start-1); - } - variable_end = line.find("#", variable_start); - if(variable_end != std::string::npos){ - return line.substr(variable_start+1, - (variable_end)-variable_start-1); - } - variable_end = line.find(")", variable_start); - return line.substr(variable_start+1, - (variable_end)-variable_start-1); - if (variable_start == std::string::npos) { // not a variable - variable_start = line.find("(", 0); - variable_end = line.find(")", variable_start); - INFO("Wasn't a variable, returning value"); - return line.substr(variable_start+1, - (variable_end)-variable_start-1); - } - break; - } - } - } - for (auto &line : juci::filesystem::read_lines(itr->path())) { - if (line.find("set(", 0) != std::string::npos - || line.find("set (", 0) != std::string::npos) { - if( line.find(project_name_var, 0) != std::string::npos) { - size_t variable_start = line.find(project_name_var, 0) - +project_name_var.length(); - size_t variable_end = line.find(")", variable_start); - project_name = line.substr(variable_start+1, - variable_end-variable_start-1); - boost::algorithm::trim(project_name); - INFO("found variable, returning value"); - return project_name; - } - } - } - break; - } - } - INFO("Couldn't find value in CMakeLists.txt"); - return "no project name"; -} diff --git a/src/directories.h b/src/directories.h index ba545ec..57d36da 100644 --- a/src/directories.h +++ b/src/directories.h @@ -5,6 +5,7 @@ #include #include #include "boost/filesystem.hpp" +#include "cmake.h" class Directories : public Gtk::ScrolledWindow { public: @@ -27,11 +28,12 @@ public: }; Directories(); - void open_folder(const boost::filesystem::path& dir_path); + void open_folder(const boost::filesystem::path& dir_path=""); void select_path(const std::string &path); - std::string get_cmakelists_variable(const boost::filesystem::path& dir_path, std::string command_name); std::function on_row_activated; + std::unique_ptr cmake; + boost::filesystem::path current_path; private: void add_paths(const boost::filesystem::path& dir_path, const Gtk::TreeModel::Row &row, unsigned depth); @@ -39,7 +41,7 @@ private: Gtk::TreeView tree_view; Glib::RefPtr tree_store; ColumnRecord column_record; - boost::filesystem::path last_dir_path; + std::string selected_path; }; #endif // JUCI_DIRECTORIES_H_ diff --git a/src/files.h b/src/files.h index 6a34e45..14a3610 100644 --- a/src/files.h +++ b/src/files.h @@ -68,27 +68,6 @@ const std::string configjson = " \"cmakelists.txt\",\n" " \"in-lowercase.pls\"\n" " ]\n" -" },\n" -" \"project\": {\n" -" \"run_commands\": [\n" -" \"./.build/\"\n" -" ],\n" -" \"compile_commands\": [\n" -" \"rm -rf ./.build\",\n" -" \"mkdir ./.build\",\n" -" \"cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -B./.build -H.\",\n" -" \"cd ./.build/; make\",\n" -" \"cp ./.build/compile_commands.json compile_commands.json\"\n" -" ]\n" -" },\n" -" \"example\": {\n" -" \"key\": \"value\",\n" -" \"key2\": [\n" -" \"val1\",\n" -" \"val2\",\n" -" 3\n" -" ],\n" -" \"key3\": \"value\"\n" " }\n" "}\n"; diff --git a/src/juci.cc b/src/juci.cc index 5d9cd9a..e6a377b 100644 --- a/src/juci.cc +++ b/src/juci.cc @@ -37,7 +37,6 @@ void app::on_activate() { add_window(*window); window->show(); if(directory!="") { - window->notebook.project_path=directory; window->directories.open_folder(directory); } for(auto &f: files) diff --git a/src/notebook.cc b/src/notebook.cc index b0226cf..e77e000 100644 --- a/src/notebook.cc +++ b/src/notebook.cc @@ -3,6 +3,8 @@ #include "sourcefile.h" #include "singletons.h" #include +#include +#include "cmake.h" #include //TODO: remove using namespace std; //TODO: remove @@ -11,7 +13,7 @@ namespace sigc { SIGC_FUNCTORS_DEDUCE_RESULT_TYPE_WITH_DECLTYPE } -Notebook::Notebook() : Gtk::Notebook() { +Notebook::Notebook(Directories &directories) : Gtk::Notebook(), directories(directories) { Gsv::init(); } @@ -50,19 +52,26 @@ void Notebook::open(std::string path) { } can_read.close(); - auto tmp_project_path=project_path; - if(tmp_project_path=="") { - tmp_project_path=boost::filesystem::path(path).parent_path().string(); - } auto language=Source::guess_language(path); if(language && (language->get_id()=="chdr" || language->get_id()=="c" || language->get_id()=="cpp" || language->get_id()=="objc")) { - if(boost::filesystem::exists(tmp_project_path+"/CMakeLists.txt") && !boost::filesystem::exists(tmp_project_path+"/compile_commands.json")) { - make_compile_commands(); + std::string project_path; + if(directories.cmake && directories.cmake->project_path!="") + project_path=directories.cmake->project_path.string(); + else { + auto parent_path=boost::filesystem::path(path).parent_path(); + project_path=parent_path.string(); + CMake cmake(parent_path); + if(cmake.project_path!="") { + project_path=cmake.project_path.string(); + Singleton::terminal()->print("Project path for "+path+" set to "+project_path+"\n"); + } + else + Singleton::terminal()->print("Error: could not find project path for "+path+"\n"); } - source_views.emplace_back(new Source::ClangView(path, tmp_project_path)); + source_views.emplace_back(new Source::ClangView(path, project_path)); } else - source_views.emplace_back(new Source::GenericView(path, tmp_project_path, language)); + source_views.emplace_back(new Source::GenericView(path, language)); scrolled_windows.emplace_back(new Gtk::ScrolledWindow()); hboxes.emplace_back(new Gtk::HBox()); @@ -111,11 +120,13 @@ bool Notebook::save(int page) { Singleton::terminal()->print("File saved to: " +view->file_path+"\n"); //If CMakeLists.txt have been modified: + //TODO: recreate cmake even without directories open? if(boost::filesystem::path(view->file_path).filename().string()=="CMakeLists.txt") { - if(make_compile_commands()) { + if(directories.cmake && directories.cmake->project_path!="" && boost::filesystem::path(view->file_path)>=directories.cmake->project_path && CMake::create_compile_commands(directories.cmake->project_path.string())) { + directories.open_folder(); for(auto source_view: source_views) { if(auto source_clang_view=dynamic_cast(source_view)) { - if(project_path==source_view->project_path) { + if(directories.cmake->project_path.string()==source_clang_view->project_path) { if(source_clang_view->restart_parse()) Singleton::terminal()->print("Reparsing "+source_clang_view->file_path+"\n"); else @@ -133,18 +144,6 @@ bool Notebook::save(int page) { return false; } -bool Notebook::make_compile_commands() { - if(project_path.size()>0) { - Singleton::terminal()->print("Creating "+boost::filesystem::path(project_path+"/compile_commands.json").string()+"\n"); - //TODO: Windows... - if(Singleton::terminal()->execute(project_path, "cmake . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON 2>&1")) { - //TODO: refresh directories - return true; - } - } - return false; -} - bool Notebook::save_current() { INFO("Notebook save current file"); if(get_current_page()==-1) @@ -168,7 +167,7 @@ bool Notebook::close_current_page() { scrolled_windows.erase(scrolled_windows.begin()+page); hboxes.erase(hboxes.begin()+page); if(auto source_clang_view=dynamic_cast(source_view)) - source_clang_view->delete_object(); + source_clang_view->async_delete(); else delete source_view; } diff --git a/src/notebook.h b/src/notebook.h index 43254f6..8ad8480 100644 --- a/src/notebook.h +++ b/src/notebook.h @@ -8,10 +8,11 @@ #include #include #include +#include "directories.h" class Notebook : public Gtk::Notebook { public: - Notebook(); + Notebook(Directories &directories); Source::View* get_view(int page); int size(); Source::View* get_current_view(); @@ -19,11 +20,11 @@ public: void open(std::string filename); bool save(int page); bool save_current(); - std::string project_path; private: - bool make_compile_commands(); + bool make_compile_commands(const std::string &path); bool save_modified_dialog(); + Directories &directories; std::vector source_views; //Is NOT freed in destructor, this is intended for quick program exit. std::vector > scrolled_windows; std::vector > hboxes; diff --git a/src/singletons.cc b/src/singletons.cc index 08e627d..07b2a39 100644 --- a/src/singletons.cc +++ b/src/singletons.cc @@ -1,7 +1,6 @@ #include "singletons.h" std::unique_ptr Singleton::Config::source_=std::unique_ptr(new Source::Config()); -std::unique_ptr Singleton::Config::terminal_=std::unique_ptr(new Terminal::Config()); std::unique_ptr Singleton::Config::directories_=std::unique_ptr(new Directories::Config()); std::unique_ptr Singleton::terminal_=std::unique_ptr(); std::unique_ptr Singleton::Config::theme_ = std::unique_ptr(new Theme::Config()); diff --git a/src/singletons.h b/src/singletons.h index 4a92eb3..dd1e6ca 100644 --- a/src/singletons.h +++ b/src/singletons.h @@ -16,7 +16,6 @@ public: class Config { public: static Source::Config *source() {return source_.get();} - static Terminal::Config *terminal() {return terminal_.get();} static Directories::Config *directories() {return directories_.get();} static Theme::Config *theme() { return theme_.get(); } static Window::Config *window() { return window_.get(); } @@ -24,7 +23,6 @@ public: static std::unique_ptr source_; static std::unique_ptr theme_; static std::unique_ptr window_; - static std::unique_ptr terminal_; static std::unique_ptr directories_; }; static std::string config_dir() { return std::string(getenv("HOME")) + "/.juci/config/"; } diff --git a/src/source.cc b/src/source.cc index 1d1b8fd..358b43a 100644 --- a/src/source.cc +++ b/src/source.cc @@ -36,9 +36,8 @@ Glib::RefPtr Source::guess_language(const std::string &file_path) ////////////// //// View //// ////////////// -Source::View::View(const std::string& file_path, const std::string& project_path): -file_path(file_path), project_path(project_path) { - set_smart_home_end(Gsv::SMART_HOME_END_BEFORE); +Source::View::View(const std::string& file_path): file_path(file_path) { + set_smart_home_end(Gsv::SMART_HOME_END_BEFORE); get_source_buffer()->begin_not_undoable_action(); juci::filesystem::read(file_path, get_buffer()); get_source_buffer()->end_not_undoable_action(); @@ -254,7 +253,7 @@ bool Source::View::on_key_press_event(GdkEventKey* key) { ///////////////////// //// GenericView //// ///////////////////// -Source::GenericView::GenericView(const std::string& file_path, const std::string& project_path, Glib::RefPtr language) : View(file_path, project_path) { +Source::GenericView::GenericView(const std::string& file_path, Glib::RefPtr language) : View(file_path) { if(language) { get_source_buffer()->set_language(language); Singleton::terminal()->print("Language for file "+file_path+" set to "+language->get_name()+".\n"); @@ -273,8 +272,8 @@ Source::GenericView::GenericView(const std::string& file_path, const std::string //////////////////////// clang::Index Source::ClangViewParse::clang_index(0, 0); -Source::ClangViewParse::ClangViewParse(const std::string& file_path, const std::string& project_path): -Source::View(file_path, project_path) { +Source::ClangViewParse::ClangViewParse(const std::string& file_path, const std::string& project_path) : + Source::View(file_path), project_path(project_path) { INFO("Tagtable beeing filled"); for (auto &item : Singleton::Config::source()->tags) { auto scheme = get_source_buffer()->get_style_scheme(); @@ -321,6 +320,34 @@ Source::View(file_path, project_path) { //TODO: clear tag_class and param_spec? parsing_in_progress=Singleton::terminal()->print_in_progress("Parsing "+file_path); + //GTK-calls must happen in main thread, so the parse_thread + //sends signals to the main thread that it is to call the following functions: + parse_start.connect([this]{ + if(parse_thread_buffer_map_mutex.try_lock()) { + parse_thread_buffer_map=get_buffer_map(); + parse_thread_mapped=true; + parse_thread_buffer_map_mutex.unlock(); + } + parse_thread_go=true; + }); + parse_done.connect([this](){ + if(parse_thread_mapped) { + if(parsing_mutex.try_lock()) { + INFO("Updating syntax"); + update_syntax(); + update_diagnostics(); + update_types(); + clang_readable=true; + set_status(""); + parsing_mutex.unlock(); + INFO("Syntax updated"); + } + parsing_in_progress->done("done"); + } + else { + parse_thread_go=true; + } + }); init_parse(); get_buffer()->signal_changed().connect([this]() { @@ -362,37 +389,7 @@ void Source::ClangViewParse::init_parse() { start_offset, end_offset); update_syntax(); - - //GTK-calls must happen in main thread, so the parse_thread - //sends signals to the main thread that it is to call the following functions: - parse_start.connect([this]{ - if(parse_thread_buffer_map_mutex.try_lock()) { - parse_thread_buffer_map=get_buffer_map(); - parse_thread_mapped=true; - parse_thread_buffer_map_mutex.unlock(); - } - parse_thread_go=true; - }); - - parse_done.connect([this](){ - if(parse_thread_mapped) { - if(parsing_mutex.try_lock()) { - INFO("Updating syntax"); - update_syntax(); - update_diagnostics(); - update_types(); - clang_readable=true; - set_status(""); - parsing_mutex.unlock(); - INFO("Syntax updated"); - } - parsing_in_progress->done("done"); - } - else { - parse_thread_go=true; - } - }); - + set_status("parsing..."); if(parse_thread.joinable()) parse_thread.join(); @@ -560,7 +557,7 @@ void Source::ClangViewParse::update_types() { bool Source::ClangViewParse::on_motion_notify_event(GdkEventMotion* event) { delayed_tooltips_connection.disconnect(); - if(clang_readable) { + if(clang_readable && event->state==0) { Gdk::Rectangle rectangle(event->x, event->y, 1, 1); Tooltips::init(); type_tooltips.show(rectangle); @@ -1082,7 +1079,7 @@ Source::ClangView::ClangView(const std::string& file_path, const std::string& pr }); } -void Source::ClangView::delete_object() { +void Source::ClangView::async_delete() { parsing_in_progress->cancel("canceled, freeing resources in the background"); parse_thread_stop=true; delete_thread=std::thread([this](){ diff --git a/src/source.h b/src/source.h index 8c68249..40b4b37 100644 --- a/src/source.h +++ b/src/source.h @@ -46,7 +46,7 @@ namespace Source { class View : public Gsv::View { public: - View(const std::string& file_path, const std::string& project_path); + View(const std::string& file_path); ~View(); void search_highlight(const std::string &text, bool case_sensitive, bool regex); @@ -58,7 +58,6 @@ namespace Source { void replace_all(const std::string &replacement); std::string file_path; - std::string project_path; std::function()> get_declaration_location; std::function goto_method; @@ -84,12 +83,13 @@ namespace Source { class GenericView : public View { public: - GenericView(const std::string& file_path, const std::string& project_path, Glib::RefPtr language); + GenericView(const std::string& file_path, Glib::RefPtr language); }; class ClangViewParse : public View { public: ClangViewParse(const std::string& file_path, const std::string& project_path); + std::string project_path; protected: void init_parse(); void start_reparse(); @@ -125,7 +125,7 @@ namespace Source { bool on_scroll_event(GdkEventScroll* event); static clang::Index clang_index; std::vector get_compilation_commands(); - + Glib::Dispatcher parse_done; Glib::Dispatcher parse_start; std::map parse_thread_buffer_map; @@ -169,7 +169,7 @@ namespace Source { class ClangView : public ClangViewRefactor { public: ClangView(const std::string& file_path, const std::string& project_path); - void delete_object(); + void async_delete(); bool restart_parse(); private: Glib::Dispatcher do_delete_object; diff --git a/src/terminal.cc b/src/terminal.cc index de42e7c..8103af4 100644 --- a/src/terminal.cc +++ b/src/terminal.cc @@ -2,6 +2,84 @@ #include #include "logging.h" #include "singletons.h" +#include +#include +#include + +#include //TODO: remove +using namespace std; //TODO: remove + +int execute_status=-1; +std::mutex async_and_sync_execute_mutex; +std::unordered_map > async_execute_descriptors; +std::unordered_map async_execute_status; + +//TODO: Windows... +//Coppied partially from http://www.linuxprogrammingblog.com/code-examples/sigaction +void signal_execl_exit(int sig, siginfo_t *siginfo, void *context) { + async_and_sync_execute_mutex.lock(); + if(async_execute_descriptors.find(siginfo->si_pid)!=async_execute_descriptors.end()) { + close(async_execute_descriptors.at(siginfo->si_pid)[0]); + close(async_execute_descriptors.at(siginfo->si_pid)[1]); + close(async_execute_descriptors.at(siginfo->si_pid)[2]); + } + int status; + while (waitpid(siginfo->si_pid, &status, WNOHANG) > 0) {} + + if(async_execute_descriptors.find(siginfo->si_pid)!=async_execute_descriptors.end()) { + async_execute_status[siginfo->si_pid]=status; + async_execute_descriptors.erase(siginfo->si_pid); + } + else + execute_status=status; + async_and_sync_execute_mutex.unlock(); +} + +//TODO: Windows... +//TODO: Someone who knows this stuff see if I have done something stupid. +//Copied partially from http://stackoverflow.com/questions/12778672/killing-process-that-has-been-created-with-popen2 +pid_t popen3(const char *command, int *input_descriptor, int *output_descriptor, int *error_descriptor) { + pid_t pid; + int p_stdin[2], p_stdout[2], p_stderr[2]; + + if (pipe(p_stdin) != 0 || pipe(p_stdout) != 0 || pipe(p_stderr) != 0) + return -1; + + pid = fork(); + + if (pid < 0) + return pid; + else if (pid == 0) { + close(p_stdin[1]); + dup2(p_stdin[0], 0); + close(p_stdout[0]); + dup2(p_stdout[1], 1); + close(p_stderr[0]); + dup2(p_stderr[1], 2); + + setpgid(0, 0); + execl("/bin/sh", "sh", "-c", command, NULL); + perror("execl"); + exit(1); + } + + if (input_descriptor == NULL) + close(p_stdin[1]); + else + *input_descriptor = p_stdin[1]; + + if (output_descriptor == NULL) + close(p_stdout[0]); + else + *output_descriptor = p_stdout[0]; + + if (error_descriptor == NULL) + close(p_stderr[0]); + else + *error_descriptor = p_stderr[0]; + + return pid; +} Terminal::InProgress::InProgress(const std::string& start_msg): stop(false) { waiting_print.connect([this](){ @@ -48,86 +126,154 @@ Terminal::Terminal() { scrolled_window.add(text_view); add(scrolled_window); - change_folder_command = ""; text_view.signal_size_allocate().connect([this](Gtk::Allocation& allocation){ auto end=text_view.get_buffer()->create_mark(text_view.get_buffer()->end()); text_view.scroll_to(end); text_view.get_buffer()->delete_mark(end); }); + + bold_tag=text_view.get_buffer()->create_tag(); + bold_tag->property_weight()=PANGO_WEIGHT_BOLD; + async_print_dispatcher.connect([this](){ + async_print_strings_mutex.lock(); + if(async_print_strings.size()>0) { + for(auto &string_bold: async_print_strings) + print(string_bold.first, string_bold.second); + async_print_strings.clear(); + } + async_print_strings_mutex.unlock(); + }); + + //Coppied from http://www.linuxprogrammingblog.com/code-examples/sigaction + struct sigaction act; + memset (&act, '\0', sizeof(act)); + act.sa_sigaction = &signal_execl_exit; + act.sa_flags = SA_SIGINFO; + sigaction(SIGCHLD, &act, NULL); } -bool Terminal::execute(const std::string &path, const std::string &command) { +int Terminal::execute(const std::string &command, const std::string &path) { boost::filesystem::path boost_path; - if(path=="") - boost_path=boost::filesystem::current_path(); - else + std::string cd_path_and_command; + if(path!="") { boost_path=boost::filesystem::path(path); - //TODO: Windows... - auto cd_path_and_command="cd "+boost_path.string()+" 2>&1 && "+command; + //TODO: Windows... + cd_path_and_command="cd "+boost_path.string()+" 2>&1 && "+command; + } + else + cd_path_and_command=command; - FILE* p = NULL; + FILE* p; p = popen(cd_path_and_command.c_str(), "r"); if (p == NULL) { print("Error: Failed to run command" + command + "\n"); - return false; + return -1; } else { - char buffer[1028]; - while (fgets(buffer, 1028, p) != NULL) { + char buffer[1024]; + while (fgets(buffer, 1024, p) != NULL) { print(buffer); + while(gtk_events_pending()) + gtk_main_iteration(); } + async_and_sync_execute_mutex.lock(); int exit_code=pclose(p); - if(exit_code==0) - return true; - else - return false; + if(exit_code==-1) + exit_code=execute_status; + async_and_sync_execute_mutex.unlock(); + return exit_code; } } -void Terminal::async_execute(const std::string &path, const std::string &command) { - -} - -void Terminal::set_change_folder_command(boost::filesystem::path CMake_path) { - INFO("Terminal: set_change_folder_command"); - path = CMake_path.string(); - change_folder_command = "cd "+ path + "; "; +void Terminal::async_execute(const std::string &command, const std::string &path, std::function callback) { + std::thread async_execute_thread([this, command, path, callback](){ + boost::filesystem::path boost_path; + std::string cd_path_and_command; + if(path!="") { + boost_path=boost::filesystem::path(path); + + //TODO: Windows... + cd_path_and_command="cd "+boost_path.string()+" && "+command; + } + else + cd_path_and_command=command; + + int input_descriptor, output_descriptor, error_descriptor; + async_and_sync_execute_mutex.lock(); + auto pid=popen3(cd_path_and_command.c_str(), &input_descriptor, &output_descriptor, &error_descriptor); + async_execute_descriptors[pid]={input_descriptor, output_descriptor, error_descriptor}; + async_and_sync_execute_mutex.unlock(); + if (pid<=0) { + async_print("Error: Failed to run command: " + command + "\n"); + if(callback) + callback(-1); + } + else { + std::thread error_thread([this, error_descriptor](){ + char buffer[1024]; + ssize_t n; + while ((n=read(error_descriptor, buffer, 1024)) > 0) { + std::string message; + for(int c=0;c 0) { + std::string message; + for(int c=0;cset_text(""); - DEBUG("Terminal: compile: running cmake command"); - std::vector commands = Singleton::Config::terminal()->compile_commands; - for (size_t it = 0; it < commands.size(); ++it) { - execute_command(commands.at(it), "r"); +void Terminal::kill_executing() { + async_and_sync_execute_mutex.lock(); + int status; + for(auto &pid: async_execute_descriptors) { + close(async_execute_descriptors.at(pid.first)[0]); + close(async_execute_descriptors.at(pid.first)[1]); + close(async_execute_descriptors.at(pid.first)[2]); + kill(-pid.first, SIGINT); //signal_execl_exit is not always called after kill! + while(waitpid(-pid.first, &status, WNOHANG) > 0) {} + async_execute_status[pid.first]=status; } - print("\n"); - DEBUG("Terminal: compile: compile done"); -} - -void Terminal::run(std::string executable) { - INFO("Terminal: run"); - print("juCi++ execute: " + executable + "\n"); - DEBUG("Terminal: compile: running run command: "); - DEBUG_VAR(executable); - execute_command("cd "+Singleton::Config::terminal()->run_command + "; ./"+executable, "r"); - print("\n"); + async_and_sync_execute_mutex.unlock(); } -int Terminal::print(std::string message){ +int Terminal::print(const std::string &message, bool bold){ INFO("Terminal: PrintMessage"); - text_view.get_buffer()->insert(text_view.get_buffer()->end(), "> "+message); + if(bold) + text_view.get_buffer()->insert_with_tag(text_view.get_buffer()->end(), message, bold_tag); + else + text_view.get_buffer()->insert(text_view.get_buffer()->end(), message); return text_view.get_buffer()->end().get_line(); } -void Terminal::print(int line_nr, std::string message){ +void Terminal::print(int line_nr, const std::string &message, bool bold){ INFO("Terminal: PrintMessage at line " << line_nr); auto iter=text_view.get_buffer()->get_iter_at_line(line_nr); while(!iter.ends_line()) iter++; - text_view.get_buffer()->insert(iter, message); + if(bold) + text_view.get_buffer()->insert_with_tag(iter, message, bold_tag); + else + text_view.get_buffer()->insert(iter, message); } std::shared_ptr Terminal::print_in_progress(std::string start_msg) { @@ -135,21 +281,13 @@ std::shared_ptr Terminal::print_in_progress(std::string st return in_progress; } -void Terminal::execute_command(std::string command, std::string mode) { - INFO("Terminal: execute_command"); - command = change_folder_command+command; - DEBUG("Terminal: PrintMessage: running command"); - FILE* p = NULL; - std::cout << command << std::endl; - p = popen(command.c_str(), mode.c_str()); - if (p == NULL) { - print("juCi++ ERROR: Failed to run command" + command + "\n"); - } - else { - char buffer[1028]; - while (fgets(buffer, 1028, p) != NULL) { - print(buffer); - } - pclose(p); - } +void Terminal::async_print(const std::string &message, bool bold) { + async_print_strings_mutex.lock(); + bool dispatch=true; + if(async_print_strings.size()>0) + dispatch=false; + async_print_strings.emplace_back(message, bold); + async_print_strings_mutex.unlock(); + if(dispatch) + async_print_dispatcher(); } diff --git a/src/terminal.h b/src/terminal.h index d7865c5..a9b9475 100644 --- a/src/terminal.h +++ b/src/terminal.h @@ -9,13 +9,7 @@ #include class Terminal : public Gtk::HBox { -public: - class Config { - public: - std::vector compile_commands; - std::string run_command; - }; - +public: class InProgress { public: InProgress(const std::string& start_msg); @@ -31,25 +25,22 @@ public: }; Terminal(); - bool execute(const std::string &path, const std::string &command); - void async_execute(const std::string &path, const std::string &command); - void set_change_folder_command(boost::filesystem::path CMake_path); //TODO: remove - void run(std::string executable); //TODO: remove - void compile(); //TODO: remove - int print(std::string message); - void print(int line_nr, std::string message); + int execute(const std::string &command, const std::string &path=""); + void async_execute(const std::string &command, const std::string &path="", std::function callback=nullptr); + void kill_executing(); + + int print(const std::string &message, bool bold=false); + void print(int line_nr, const std::string &message, bool bold=false); std::shared_ptr print_in_progress(std::string start_msg); + void async_print(const std::string &message, bool bold=false); private: - void execute_command(std::string command, std::string mode); //TODO: remove - Gtk::TextView text_view; Gtk::ScrolledWindow scrolled_window; - - std::string change_folder_command; //TODO: remove - std::string path; //TODO: remove - const std::string cmake_sucsess = "Build files have been written to:"; //TODO: remove - const std::string make_built = "Built target"; //TODO: remove - const std::string make_executable = "Linking CXX executable"; //TODO: remove + + Glib::Dispatcher async_print_dispatcher; + std::vector > async_print_strings; + std::mutex async_print_strings_mutex; + Glib::RefPtr bold_tag; }; #endif // JUCI_TERMINAL_H_ diff --git a/src/window.cc b/src/window.cc index 11ee2d5..8044d8d 100644 --- a/src/window.cc +++ b/src/window.cc @@ -21,7 +21,7 @@ void Window::generate_keybindings() { } } -Window::Window() : box(Gtk::ORIENTATION_VERTICAL) { +Window::Window() : box(Gtk::ORIENTATION_VERTICAL), notebook(directories), compiling(false) { INFO("Create Window"); set_title("juCi++"); set_default_size(600, 400); @@ -97,6 +97,11 @@ Window::Window() : box(Gtk::ORIENTATION_VERTICAL) { notebook.signal_page_removed().connect([this](Gtk::Widget* page, guint page_num) { entry_box.hide(); }); + + compile_success.connect([this](){ + directories.open_folder(); + }); + INFO("Window created"); } // Window constructor @@ -203,41 +208,52 @@ void Window::create_menu() { }); menu.action_group->add(Gtk::Action::create("ProjectCompileAndRun", "Compile And Run"), Gtk::AccelKey(menu.key_map["compile_and_run"]), [this]() { - if(notebook.get_current_page()==-1) + if(notebook.get_current_page()==-1 || compiling) return; - notebook.save_current(); - if (running.try_lock()) { - std::thread execute([this]() { - std::string path = notebook.get_current_view()->file_path; - size_t pos = path.find_last_of("/\\"); - if(pos != std::string::npos) { - path.erase(path.begin()+pos,path.end()); - Singleton::terminal()->set_change_folder_command(path); - } - Singleton::terminal()->compile(); - std::string executable = directories.get_cmakelists_variable(path,"add_executable"); - Singleton::terminal()->run(executable); - running.unlock(); - }); - execute.detach(); + CMake cmake(notebook.get_current_view()->file_path); + directories.open_folder(); + auto executables = cmake.get_functions_parameters("add_executable"); + std::string executable; + boost::filesystem::path path; + if(executables.size()>0 && executables[0].second.size()>0) { + executable=executables[0].second[0]; + path=executables[0].first.parent_path(); + path+="/"+executables[0].second[0]; + } + if(cmake.project_path!="") { + if(path!="") { + compiling=true; + Singleton::terminal()->print("Compiling and executing "+path.string()+"\n"); + //TODO: Windows... + Singleton::terminal()->async_execute("make", cmake.project_path.string(), [this, path](int exit_code){ + compiling=false; + if(exit_code==EXIT_SUCCESS) { + compile_success(); + //TODO: Windows... + Singleton::terminal()->async_execute(path.string(), path.parent_path().string(), [this, path](int exit_code){ + Singleton::terminal()->async_print(path.string()+" returned: "+std::to_string(exit_code)+'\n'); + }); + } + }); + } + else + Singleton::terminal()->print("Could not find an executable, please use add_executable in CMakeLists.txt\n"); } }); menu.action_group->add(Gtk::Action::create("ProjectCompile", "Compile"), Gtk::AccelKey(menu.key_map["compile"]), [this]() { - if(notebook.get_current_page()==-1) + if(notebook.get_current_page()==-1 || compiling) return; - notebook.save_current(); - if (running.try_lock()) { - std::thread execute([this]() { - std::string path = notebook.get_current_view()->file_path; - size_t pos = path.find_last_of("/\\"); - if(pos != std::string::npos){ - path.erase(path.begin()+pos,path.end()); - Singleton::terminal()->set_change_folder_command(path); - } - Singleton::terminal()->compile(); - running.unlock(); + CMake cmake(notebook.get_current_view()->file_path); + directories.open_folder(); + if(cmake.project_path!="") { + compiling=true; + Singleton::terminal()->print("Compiling project "+cmake.project_path.string()+"\n"); + //TODO: Windows... + Singleton::terminal()->async_execute("make 2>&1", cmake.project_path.string(), [this](int exit_code){ + compiling=false; + if(exit_code==EXIT_SUCCESS) + compile_success(); }); - execute.detach(); } }); @@ -250,8 +266,11 @@ void Window::create_menu() { } bool Window::on_key_press_event(GdkEventKey *event) { - if(event->keyval==GDK_KEY_Escape) + if(event->keyval==GDK_KEY_Escape) { + if(entry_box.entries.size()==0) + Singleton::terminal()->kill_executing(); entry_box.hide(); + } #ifdef __APPLE__ //For Apple's Command-left, right, up, down keys else if((event->state & GDK_META_MASK)>0) { if(event->keyval==GDK_KEY_Left) { @@ -293,6 +312,7 @@ void Window::hide() { if(!notebook.close_current_page()) return; } + Singleton::terminal()->kill_executing(); Gtk::Window::hide(); } @@ -301,18 +321,18 @@ void Window::new_file_entry() { entry_box.entries.emplace_back("untitled", [this](const std::string& content){ std::string filename=content; if(filename!="") { - if(notebook.project_path!="" && !boost::filesystem::path(filename).is_absolute()) - filename=notebook.project_path+"/"+filename; + if(directories.current_path!="" && !boost::filesystem::path(filename).is_absolute()) + filename=directories.current_path.string()+"/"+filename; boost::filesystem::path p(filename); if(boost::filesystem::exists(p)) { Singleton::terminal()->print("Error: "+p.string()+" already exists.\n"); } else { if(juci::filesystem::write(p)) { - if(notebook.project_path!="") - directories.open_folder(notebook.project_path); - notebook.open(boost::filesystem::canonical(p).string()); - Singleton::terminal()->print("New file "+p.string()+" created.\n"); + if(directories.current_path!="") + directories.open_folder(); + notebook.open(boost::filesystem::canonical(p).string()); + Singleton::terminal()->print("New file "+p.string()+" created.\n"); } else Singleton::terminal()->print("Error: could not create new file "+p.string()+".\n"); @@ -329,8 +349,8 @@ void Window::new_file_entry() { void Window::open_folder_dialog() { Gtk::FileChooserDialog dialog("Please choose a folder", Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER); - if(notebook.project_path.size()>0) - gtk_file_chooser_set_current_folder((GtkFileChooser*)dialog.gobj(), notebook.project_path.c_str()); + if(directories.current_path!="") + gtk_file_chooser_set_current_folder((GtkFileChooser*)dialog.gobj(), directories.current_path.string().c_str()); else gtk_file_chooser_set_current_folder((GtkFileChooser*)dialog.gobj(), boost::filesystem::current_path().string().c_str()); dialog.set_transient_for(*this); @@ -342,17 +362,14 @@ void Window::open_folder_dialog() { if(result==Gtk::RESPONSE_OK) { std::string project_path=dialog.get_filename(); - notebook.project_path=project_path; directories.open_folder(project_path); - if(notebook.get_current_page()!=-1) - directories.select_path(notebook.get_current_view()->file_path); } } void Window::open_file_dialog() { Gtk::FileChooserDialog dialog("Please choose a file", Gtk::FILE_CHOOSER_ACTION_OPEN); - if(notebook.project_path.size()>0) - gtk_file_chooser_set_current_folder((GtkFileChooser*)dialog.gobj(), notebook.project_path.c_str()); + if(directories.current_path!="") + gtk_file_chooser_set_current_folder((GtkFileChooser*)dialog.gobj(), directories.current_path.string().c_str()); else gtk_file_chooser_set_current_folder((GtkFileChooser*)dialog.gobj(), boost::filesystem::current_path().string().c_str()); dialog.set_transient_for(*this); @@ -406,8 +423,8 @@ void Window::save_file_dialog() { if(file) { file << notebook.get_current_view()->get_buffer()->get_text(); file.close(); - if(notebook.project_path!="") - directories.open_folder(notebook.project_path); + if(directories.current_path!="") + directories.open_folder(); notebook.open(path); Singleton::terminal()->print("File saved to: " + notebook.get_current_view()->file_path+"\n"); } diff --git a/src/window.h b/src/window.h index e7602c9..ef392a4 100644 --- a/src/window.h +++ b/src/window.h @@ -7,19 +7,22 @@ #include "notebook.h" #include "menu.h" #include +#include class Window : public Gtk::Window { public: Window(); - Notebook notebook; Directories directories; + Notebook notebook; class Config { public: boost::property_tree::ptree keybindings; }; + protected: bool on_key_press_event(GdkEventKey *event); bool on_delete_event (GdkEventAny *event); + private: Gtk::Box box; Gtk::VPaned vpaned; @@ -28,8 +31,9 @@ private: Gtk::VBox terminal_vbox; Gtk::HBox status_hbox; EntryBox entry_box; - std::mutex running; Menu menu; + std::atomic compiling; + Glib::Dispatcher compile_success; void create_menu(); void hide();