diff --git a/src/config.cc b/src/config.cc index 7a433fb..f41feb4 100644 --- a/src/config.cc +++ b/src/config.cc @@ -45,10 +45,9 @@ void MainConfig::GenerateSource() { auto source_cfg = Singleton::Config::source(); auto source_json = cfg.get_child("source"); - source_cfg->tab_size = source_json.get("tab_size"); - source_cfg->tab_char = source_json.get("tab_char"); - for(unsigned c=0;ctab_size;c++) - source_cfg->tab+=source_cfg->tab_char; + source_cfg->default_tab_char = source_json.get("default_tab_char"); + source_cfg->default_tab_size = source_json.get("default_tab_size"); + source_cfg->auto_tab_char_and_size = source_json.get("auto_tab_char_and_size"); source_cfg->highlight_current_line = source_json.get_value("highlight_current_line"); source_cfg->show_line_numbers = source_json.get_value("show_line_numbers"); diff --git a/src/directories.cc b/src/directories.cc index 82d9dba..edf3ea7 100644 --- a/src/directories.cc +++ b/src/directories.cc @@ -3,7 +3,7 @@ #include "logging.h" #include "singletons.h" #include -#include "boost/algorithm/string.hpp" +#include #include //TODO: remove using namespace std; //TODO: remove @@ -12,78 +12,169 @@ namespace sigc { SIGC_FUNCTORS_DEDUCE_RESULT_TYPE_WITH_DECLTYPE } -Directories::Directories() { +Directories::Directories() : stop_update_thread(false) { DEBUG("adding treeview to scrolledwindow"); add(tree_view); set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); tree_store = Gtk::TreeStore::create(column_record); tree_view.set_model(tree_store); tree_view.append_column("", column_record.name); - tree_store->set_sort_column(0, Gtk::SortType::SORT_ASCENDING); + tree_store->set_sort_column(column_record.id, Gtk::SortType::SORT_ASCENDING); + tree_view.set_enable_search(true); //TODO: why does this not work in OS X? + tree_view.set_search_column(column_record.name); tree_view.signal_row_activated().connect([this](const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column){ INFO("Directory navigation"); auto iter = tree_store->get_iter(path); if (iter) { - Gtk::TreeModel::Row row = *iter; - std::string upath = Glib::ustring(row[column_record.path]); - boost::filesystem::path fs_path(upath); - if (boost::filesystem::is_directory(fs_path)) { - tree_view.row_expanded(path) ? tree_view.collapse_row(path) : tree_view.expand_row(path, false); - } else { - std::stringstream sstm; - sstm << row[column_record.path]; - if(on_row_activated) - on_row_activated(sstm.str()); + auto path_str=iter->get_value(column_record.path); + if(path_str!="") { + if (boost::filesystem::is_directory(boost::filesystem::path(path_str))) { + tree_view.row_expanded(path) ? tree_view.collapse_row(path) : tree_view.expand_row(path, false); + } else { + if(on_row_activated) + on_row_activated(path_str); + } } } }); + + tree_view.signal_test_expand_row().connect([this](const Gtk::TreeModel::iterator& iter, const Gtk::TreeModel::Path& path){ + if(iter->children().begin()->get_value(column_record.path)=="") { + update_mutex.lock(); + add_path(iter->get_value(column_record.path), *iter); + update_mutex.unlock(); + } + return false; + }); + tree_view.signal_row_collapsed().connect([this](const Gtk::TreeModel::iterator& iter, const Gtk::TreeModel::Path& path){ + update_mutex.lock(); + last_write_times.erase(iter->get_value(column_record.path)); + update_mutex.unlock(); + auto children=iter->children(); + if(children) { + while(children) { + tree_store->erase(children.begin()); + } + auto child=tree_store->append(iter->children()); + child->set_value(column_record.name, std::string("(empty)")); + } + }); + + update_dispatcher.connect([this](){ + update_mutex.lock(); + for(auto &path: update_paths) { + if(last_write_times.count(path)>0) + add_path(path, last_write_times.at(path).first); + } + update_paths.clear(); + update_mutex.unlock(); + }); + + update_thread=std::thread([this](){ + while(!stop_update_thread) { + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + update_mutex.lock(); + if(update_paths.size()==0) { + for(auto it=last_write_times.begin();it!=last_write_times.end();) { + try { + if(boost::filesystem::exists(it->first)) { //Added for older boost versions (no exception thrown) + if(it->second.secondfirst)) { + update_paths.emplace_back(it->first); + } + it++; + } + else + it=last_write_times.erase(it); + } + catch(const std::exception &e) { + it=last_write_times.erase(it); + } + } + if(update_paths.size()>0) + update_dispatcher(); + } + update_mutex.unlock(); + } + }); } -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; - } +Directories::~Directories() { + stop_update_thread=true; + update_thread.join(); +} + +void Directories::open(const boost::filesystem::path& dir_path) { + if(dir_path=="") + return; - std::vector expanded_paths; - 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); - }); - } + INFO("Open folder"); tree_store->clear(); - - if(dir_path!="") - cmake=std::unique_ptr(new CMake(new_path)); + update_mutex.lock(); + last_write_times.clear(); + update_paths.clear(); + update_mutex.unlock(); + + cmake=std::unique_ptr(new CMake(dir_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); + update_mutex.lock(); + add_path(dir_path, Gtk::TreeModel::Row()); + update_mutex.unlock(); - current_path=new_path; + current_path=dir_path; - if(selected_path!="") - select_path(selected_path); DEBUG("Folder opened"); } -void Directories::select_path(const boost::filesystem::path &path) { +void Directories::update() { + update_mutex.lock(); + for(auto &last_write_time: last_write_times) { + add_path(last_write_time.first, last_write_time.second.first); + } + update_mutex.unlock(); +} + +void Directories::select(const boost::filesystem::path &path) { + if(current_path=="") + return; + + if(path.string().substr(0, current_path.string().size())!=current_path.string()) + return; + + std::list paths; + boost::filesystem::path parent_path; + if(boost::filesystem::is_directory(path)) + parent_path=path; + else + parent_path=path.parent_path(); + paths.emplace_front(parent_path); + while(parent_path!=current_path) { + parent_path=parent_path.parent_path(); + paths.emplace_front(parent_path); + } + + for(auto &a_path: paths) { + tree_store->foreach_iter([this, &a_path](const Gtk::TreeModel::iterator& iter){ + if(iter->get_value(column_record.path)==a_path.string()) { + update_mutex.lock(); + add_path(a_path, *iter); + update_mutex.unlock(); + return true; + } + return false; + }); + } + tree_store->foreach_iter([this, &path](const Gtk::TreeModel::iterator& iter){ if(iter->get_value(column_record.path)==path.string()) { 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; @@ -106,37 +197,58 @@ bool Directories::ignored(std::string path) { return false; } -void Directories::add_paths(const boost::filesystem::path& dir_path, const Gtk::TreeModel::Row &parent, unsigned row_id) { - boost::filesystem::directory_iterator end_itr; - Gtk::TreeModel::Row child; - Gtk::TreeModel::Row row; - DEBUG(""); - // Fill the treeview - for(boost::filesystem::directory_iterator itr(dir_path);itr != end_itr;++itr) { - if (!ignored(itr->path().filename().string())) { - if (boost::filesystem::is_directory(itr->status())) { - if (boost::filesystem::canonical(itr->path()) > boost::filesystem::canonical(dir_path)) { // is child - child = *(tree_store->append(parent.children())); - std::string col_id("a"+itr->path().filename().string()); - child[column_record.id] = col_id; - child[column_record.name] = itr->path().filename().string(); - child[column_record.path] = itr->path().string(); - add_paths(itr->path(), child, row_id); - } else { - row = *(tree_store->append()); - std::string col_id("a"+itr->path().filename().string()); - row[column_record.path] = itr->path().string(); - row[column_record.id] = col_id; - row[column_record.name] = itr->path().filename().string(); - add_paths(itr->path(), parent, row_id); +void Directories::add_path(const boost::filesystem::path& dir_path, const Gtk::TreeModel::Row &parent) { + last_write_times[dir_path.string()]={parent, boost::filesystem::last_write_time(dir_path)}; + std::unique_ptr children; //Gtk::TreeNodeChildren is missing default constructor... + if(parent) + children=std::unique_ptr(new Gtk::TreeNodeChildren(parent.children())); + else + children=std::unique_ptr(new Gtk::TreeNodeChildren(tree_store->children())); + if(*children) { + if(children->begin()->get_value(column_record.path)=="") + tree_store->erase(children->begin()); + } + std::unordered_set not_deleted; + boost::filesystem::directory_iterator end_it; + for(boost::filesystem::directory_iterator it(dir_path);it!=end_it;it++) { + auto filename=it->path().filename().string(); + if (!ignored(filename)) { + bool already_added=false; + if(*children) { + for(auto &child: *children) { + if(child.get_value(column_record.name)==filename) { + not_deleted.emplace(filename); + already_added=true; + break; + } } - } else { // is a file - child = *(tree_store->append(parent.children())); - std::string col_id("b"+itr->path().filename().string()); - child[column_record.id] = col_id; - child[column_record.name] = itr->path().filename().string(); - child[column_record.path] = itr->path().string(); } + if(!already_added) { + auto child = tree_store->append(*children); + not_deleted.emplace(filename); + child->set_value(column_record.name, filename); + child->set_value(column_record.path, it->path().string()); + if (boost::filesystem::is_directory(*it)) { + child->set_value(column_record.id, "a"+filename); + auto grandchild=tree_store->append(child->children()); + grandchild->set_value(column_record.name, std::string("(empty)")); + } + else + child->set_value(column_record.id, "b"+filename); + } + } + } + if(*children) { + for(auto it=children->begin();it!=children->end();) { + if(not_deleted.count(it->get_value(column_record.name))==0) { + it=tree_store->erase(it); + } + else + it++; } } + if(!*children) { + auto child=tree_store->append(*children); + child->set_value(column_record.name, std::string("(empty)")); + } } diff --git a/src/directories.h b/src/directories.h index d8dcb77..c34e319 100644 --- a/src/directories.h +++ b/src/directories.h @@ -6,6 +6,9 @@ #include #include "boost/filesystem.hpp" #include "cmake.h" +#include +#include +#include class Directories : public Gtk::ScrolledWindow { public: @@ -22,26 +25,33 @@ public: add(name); add(path); } - Gtk::TreeModelColumn id; - Gtk::TreeModelColumn name; - Gtk::TreeModelColumn path; + Gtk::TreeModelColumn id; + Gtk::TreeModelColumn name; + Gtk::TreeModelColumn path; }; Directories(); - void open_folder(const boost::filesystem::path& dir_path=""); - void select_path(const boost::filesystem::path &path); + ~Directories(); + void open(const boost::filesystem::path& dir_path=""); + void update(); + void select(const boost::filesystem::path &path); 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); + void add_path(const boost::filesystem::path& dir_path, const Gtk::TreeModel::Row &row); bool ignored(std::string path); Gtk::TreeView tree_view; Glib::RefPtr tree_store; ColumnRecord column_record; - boost::filesystem::path selected_path; + std::unordered_map > last_write_times; + std::mutex update_mutex; + std::thread update_thread; + std::atomic stop_update_thread; + Glib::Dispatcher update_dispatcher; + std::vector update_paths; }; #endif // JUCI_DIRECTORIES_H_ diff --git a/src/files.h b/src/files.h index 4312334..9112019 100644 --- a/src/files.h +++ b/src/files.h @@ -7,7 +7,12 @@ const std::string configjson = " },\n" " \"source\": {\n" " \"style\": \"juci-light\", //Use \"\" for default style, and for instance juci-dark together with dark gtk_theme variant. Styles from normal gtksourceview install: classic, cobalt, kate, oblivion, solarized-dark, solarized-light, tango\n" -" \"font\": \"Monospace\", //Use \"\" for default font, and for instance \"Monospace 12\" to also set size.\n" +#ifdef __APPLE__ +" \"font\": \"Menlo 11\", " +#else +" \"font\": \"Monospace\", " +#endif +"//Use \"\" for default font, and for instance \"Monospace 12\" to also set size.\n" " \"clang_types\": {\n" " \"8\": \"def:function\",\n" " \"21\": \"def:function\",\n" @@ -23,14 +28,16 @@ const std::string configjson = " \"702\": \"def:statement\",\n" " \"705\": \"def:comment\"\n" " },\n" -" \"tab_size\": 2,\n" -" \"tab_char\": \" \", //Use \"\\t\" for regular tab\n" +" \"auto_tab_char_and_size\": true, //Use false to always use default tab char and size\n" +" \"default_tab_char\": \" \", //Use \"\\t\" for regular tab\n" +" \"default_tab_size\": 2,\n" " \"highlight_current_line\": true,\n" " \"show_line_numbers\": true\n" " },\n" " \"keybindings\": {\n" " \"new_file\": \"n\",\n" -" \"open_folder\": \"o\",\n" +" \"new_folder\": \"n\",\n" +" \"open_folder\": \"o\",\n" " \"open_file\": \"o\",\n" " \"save\": \"s\",\n" " \"save_as\": \"s\",\n" @@ -76,6 +83,7 @@ const std::string menuxml = " \n" " \n" " \n" +" \n" " \n" " \n" " \n" diff --git a/src/juci.cc b/src/juci.cc index 68623ea..6390d3d 100644 --- a/src/juci.cc +++ b/src/juci.cc @@ -45,7 +45,7 @@ void app::on_activate() { bool first_directory=true; for(auto &directory: directories) { if(first_directory) { - window->directories.open_folder(directory); + window->directories.open(directory); first_directory=false; } else { diff --git a/src/notebook.cc b/src/notebook.cc index 3bc8f0e..7bfd4f9 100644 --- a/src/notebook.cc +++ b/src/notebook.cc @@ -120,7 +120,6 @@ bool Notebook::save(int page) { //TODO: recreate cmake even without directories open? if(view->file_path.filename()=="CMakeLists.txt") { if(directories.cmake && directories.cmake->project_path!="" && view->file_path.string().substr(0, directories.cmake->project_path.string().size())==directories.cmake->project_path.string() && CMake::create_compile_commands(directories.cmake->project_path)) { - directories.open_folder(); for(auto source_view: source_views) { if(auto source_clang_view=dynamic_cast(source_view)) { if(directories.cmake->project_path.string()==source_clang_view->project_path) { diff --git a/src/source.cc b/src/source.cc index 47f36d6..06672c8 100644 --- a/src/source.cc +++ b/src/source.cc @@ -4,7 +4,6 @@ #include #include "logging.h" #include -#include #include "singletons.h" #include #include @@ -75,6 +74,29 @@ Source::View::View(const boost::filesystem::path &file_path): file_path(file_pat property_show_line_numbers() = Singleton::Config::source()->show_line_numbers; if(Singleton::Config::source()->font.size()>0) override_font(Pango::FontDescription(Singleton::Config::source()->font)); + + tab_char=Singleton::Config::source()->default_tab_char; + tab_size=Singleton::Config::source()->default_tab_size; + if(Singleton::Config::source()->auto_tab_char_and_size) { + auto tab_char_and_size=find_tab_char_and_size(); + if(tab_char_and_size.first!=0) { + if(tab_char!=tab_char_and_size.first || tab_size!=tab_char_and_size.second) { + std::string tab_str; + if(tab_char_and_size.first==' ') + tab_str=""; + else + tab_str=""; + Singleton::terminal()->print("Tab char and size for file "+file_path.string()+" set to: "+tab_str+", "+boost::lexical_cast(tab_char_and_size.second)+".\n"); + } + + tab_char=tab_char_and_size.first; + tab_size=tab_char_and_size.second; + } + } + for(unsigned c=0;crequest_text([this](const Glib::ustring& text){ - const std::regex spaces_regex(std::string("^(")+Singleton::Config::source()->tab_char+"*)(.*)$"); auto line=get_line_before_insert(); std::smatch sm; std::string prefix_tabs; - if(!get_buffer()->get_has_selection() && std::regex_match(line, sm, spaces_regex) && sm[2].str().size()==0) { + if(!get_buffer()->get_has_selection() && std::regex_match(line, sm, tabs_regex) && sm[2].str().size()==0) { prefix_tabs=sm[1].str(); Glib::ustring::size_type start_line=0; @@ -177,7 +198,7 @@ void Source::View::paste() { std::string line=text.substr(start_line, end_line-start_line); size_t tabs=0; for(auto chr: line) { - if(chr==Singleton::Config::source()->tab_char) + if(chr==tab_char) tabs++; else break; @@ -260,21 +281,19 @@ string Source::View::get_line_before_insert() { //Basic indentation bool Source::View::on_key_press_event(GdkEventKey* key) { get_source_buffer()->begin_user_action(); - auto config=Singleton::Config::source(); - const std::regex spaces_regex(std::string("^(")+config->tab_char+"*).*$"); //Indent as in next or previous line if(key->keyval==GDK_KEY_Return && key->state==0 && !get_buffer()->get_has_selection()) { auto insert_it=get_buffer()->get_insert()->get_iter(); int line_nr=insert_it.get_line(); auto line=get_line_before_insert(); std::smatch sm; - if(std::regex_match(line, sm, spaces_regex)) { + if(std::regex_match(line, sm, tabs_regex)) { if((line_nr+1)get_line_count()) { string next_line=get_line(line_nr+1); auto line_end_iter=get_buffer()->get_iter_at_line(line_nr+1); line_end_iter--; std::smatch sm2; - if(insert_it==line_end_iter && std::regex_match(next_line, sm2, spaces_regex)) { + if(insert_it==line_end_iter && std::regex_match(next_line, sm2, tabs_regex)) { if(sm2[1].str().size()>sm[1].str().size()) { get_source_buffer()->insert_at_cursor("\n"+sm2[1].str()); scroll_to(get_source_buffer()->get_insert()); @@ -297,7 +316,7 @@ bool Source::View::on_key_press_event(GdkEventKey* key) { int line_end=selection_end.get_line(); for(int line=line_start;line<=line_end;line++) { Gtk::TextIter line_it = get_source_buffer()->get_iter_at_line(line); - get_source_buffer()->insert(line_it, config->tab); + get_source_buffer()->insert(line_it, tab); } get_source_buffer()->end_user_action(); return true; @@ -309,11 +328,11 @@ bool Source::View::on_key_press_event(GdkEventKey* key) { int line_start=selection_start.get_line(); int line_end=selection_end.get_line(); - unsigned indent_left_steps=config->tab_size; + unsigned indent_left_steps=tab_size; for(int line_nr=line_start;line_nr<=line_end;line_nr++) { string line=get_line(line_nr); std::smatch sm; - if(std::regex_match(line, sm, spaces_regex) && sm[1].str().size()>0) { + if(std::regex_match(line, sm, tabs_regex) && sm[1].str().size()>0) { indent_left_steps=std::min(indent_left_steps, (unsigned)sm[1].str().size()); } else { @@ -339,12 +358,12 @@ bool Source::View::on_key_press_event(GdkEventKey* key) { int line_nr=insert_it.get_line(); auto line=get_line_before_insert(); std::smatch sm; - if(std::regex_match(line, sm, spaces_regex) && sm[1].str().size()==line.size()) { + if(std::regex_match(line, sm, tabs_regex) && sm[1].str().size()==line.size()) { if((line_nr-1)>=0) { string previous_line=get_line(line_nr-1); std::smatch sm2; - if(std::regex_match(previous_line, sm2, spaces_regex)) { - if(line.size()==sm2[1].str().size() || line.size()==sm2[1].str().size()+config->tab_size || line.size()==sm2[1].str().size()-config->tab_size) { + if(std::regex_match(previous_line, sm2, tabs_regex)) { + if(line.size()==sm2[1].str().size() || line.size()==sm2[1].str().size()+tab_size || line.size()==sm2[1].str().size()-tab_size) { auto previous_line_end_it=insert_it; for(unsigned c=0;c=config->tab_size) { + if(line.size()>=tab_size) { auto insert_minus_tab_it=insert_it; - for(unsigned c=0;ctab_size;c++) + for(unsigned c=0;cerase(insert_minus_tab_it, insert_it); get_source_buffer()->end_user_action(); @@ -371,6 +390,58 @@ bool Source::View::on_key_press_event(GdkEventKey* key) { return stop; } +std::pair Source::View::find_tab_char_and_size() { + const std::regex indent_regex("^([ \t]+).*$"); + auto size=get_buffer()->get_line_count(); + std::unordered_map tab_chars; + std::unordered_map tab_sizes; + unsigned last_tab_size=0; + for(int c=0;c(str.size()-last_tab_size)); + if(tab_diff>0) { + unsigned tab_diff_unsigned=static_cast(tab_diff); + auto it_size=tab_sizes.find(tab_diff_unsigned); + if(it_size!=tab_sizes.end()) + it_size->second++; + else + tab_sizes[tab_diff_unsigned]=1; + } + + last_tab_size=str.size(); + + if(str.size()>0) { + auto it_char=tab_chars.find(str[0]); + if(it_char!=tab_chars.end()) + it_char->second++; + else + tab_chars[str[0]]=1; + } + } + } + char found_tab_char=0; + size_t occurences=0; + for(auto &tab_char: tab_chars) { + if(tab_char.second>occurences) { + found_tab_char=tab_char.first; + occurences=tab_char.second; + } + } + unsigned found_tab_size=0; + occurences=0; + for(auto &tab_size: tab_sizes) { + if(tab_size.second>occurences) { + found_tab_size=tab_size.first; + occurences=tab_size.second; + } + } + return {found_tab_char, found_tab_size}; +} + ///////////////////// //// GenericView //// ///////////////////// @@ -509,6 +580,10 @@ Source::View(file_path), project_path(project_path) { }); get_buffer()->signal_mark_set().connect(sigc::mem_fun(*this, &Source::ClangViewParse::on_mark_set), false); + + bracket_regex=std::regex(std::string("^(")+tab_char+"*).*\\{ *$"); + no_bracket_statement_regex=std::regex(std::string("^(")+tab_char+"*)(if|for|else if|catch|while) *\\(.*[^;}] *$"); + no_bracket_no_para_statement_regex=std::regex(std::string("^(")+tab_char+"*)(else|try|do) *$"); } void Source::ClangViewParse::init_parse() { @@ -658,8 +733,24 @@ void Source::ClangViewParse::update_diagnostics() { auto diagnostics=clang_tu->get_diagnostics(); for(auto &diagnostic: diagnostics) { if(diagnostic.path==file_path.string()) { - auto start=get_buffer()->get_iter_at_line_index(diagnostic.offsets.first.line-1, diagnostic.offsets.first.index-1); - auto end=get_buffer()->get_iter_at_line_index(diagnostic.offsets.second.line-1, diagnostic.offsets.second.index-1); + auto start_line=get_line(diagnostic.offsets.first.line-1); //index is sometimes off the line + auto start_line_index=diagnostic.offsets.first.index-1; + if(start_line_index>=start_line.size()) { + if(start_line.size()==0) + start_line_index=0; + else + start_line_index=start_line.size()-1; + } + auto end_line=get_line(diagnostic.offsets.second.line-1); //index is sometimes off the line + auto end_line_index=diagnostic.offsets.second.index-1; + if(end_line_index>=end_line.size()) { + if(end_line.size()==0) + end_line_index=0; + else + end_line_index=end_line.size()-1; + } + auto start=get_buffer()->get_iter_at_line_index(diagnostic.offsets.first.line-1, start_line_index); + auto end=get_buffer()->get_iter_at_line_index(diagnostic.offsets.second.line-1, end_line_index); std::string diagnostic_tag_name; if(diagnostic.severity<=CXDiagnostic_Warning) diagnostic_tag_name="def:warning"; @@ -765,11 +856,6 @@ bool Source::ClangViewParse::on_key_press_event(GdkEventKey* key) { return Source::View::on_key_press_event(key); } get_source_buffer()->begin_user_action(); - auto config=Singleton::Config::source(); - const std::regex bracket_regex(std::string("^(")+config->tab_char+"*).*\\{ *$"); - const std::regex no_bracket_statement_regex(std::string("^(")+config->tab_char+"*)(if|for|else if|catch|while) *\\(.*[^;}] *$"); - const std::regex no_bracket_no_para_statement_regex(std::string("^(")+config->tab_char+"*)(else|try|do) *$"); - const std::regex spaces_regex(std::string("^(")+config->tab_char+"*).*$"); //Indent depending on if/else/etc and brackets if(key->keyval==GDK_KEY_Return && key->state==0) { @@ -780,18 +866,18 @@ bool Source::ClangViewParse::on_key_press_event(GdkEventKey* key) { if((line_nr+1)get_line_count()) { string next_line=get_line(line_nr+1); std::smatch sm2; - if(std::regex_match(next_line, sm2, spaces_regex)) { - if(sm2[1].str()==sm[1].str()+config->tab) { - get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+config->tab); + if(std::regex_match(next_line, sm2, tabs_regex)) { + if(sm2[1].str()==sm[1].str()+tab) { + get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+tab); scroll_to(get_source_buffer()->get_insert()); get_source_buffer()->end_user_action(); return true; } } if(next_line!=sm[1].str()+"}") { - get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+config->tab+"\n"+sm[1].str()+"}"); + get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+tab+"\n"+sm[1].str()+"}"); auto insert_it = get_source_buffer()->get_insert()->get_iter(); - for(size_t c=0;ctab_size+sm[1].str().size();c++) + for(size_t c=0;cget_insert()); get_source_buffer()->place_cursor(insert_it); @@ -799,7 +885,7 @@ bool Source::ClangViewParse::on_key_press_event(GdkEventKey* key) { return true; } else { - get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+config->tab); + get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+tab); scroll_to(get_source_buffer()->get_insert()); get_source_buffer()->end_user_action(); return true; @@ -807,21 +893,21 @@ bool Source::ClangViewParse::on_key_press_event(GdkEventKey* key) { } } else if(std::regex_match(line, sm, no_bracket_statement_regex)) { - get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+config->tab); + get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+tab); scroll_to(get_source_buffer()->get_insert()); get_source_buffer()->end_user_action(); return true; } else if(std::regex_match(line, sm, no_bracket_no_para_statement_regex)) { - get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+config->tab); + get_source_buffer()->insert_at_cursor("\n"+sm[1].str()+tab); scroll_to(get_source_buffer()->get_insert()); get_source_buffer()->end_user_action(); return true; } - else if(std::regex_match(line, sm, spaces_regex)) { + else if(std::regex_match(line, sm, tabs_regex)) { std::smatch sm2; size_t line_nr=get_source_buffer()->get_insert()->get_iter().get_line(); - if(line_nr>0 && sm[1].str().size()>=config->tab_size) { + if(line_nr>0 && sm[1].str().size()>=tab_size) { string previous_line=get_line(line_nr-1); if(!std::regex_match(previous_line, sm2, bracket_regex)) { if(std::regex_match(previous_line, sm2, no_bracket_statement_regex)) { @@ -843,23 +929,25 @@ bool Source::ClangViewParse::on_key_press_event(GdkEventKey* key) { //Indent left when writing } on a new line else if(key->keyval==GDK_KEY_braceright) { string line=get_line_before_insert(); - if(line.size()>=config->tab_size) { + if(line.size()>=tab_size) { for(auto c: line) { - if(c!=config->tab_char) { + if(c!=tab_char) { + get_source_buffer()->insert_at_cursor("}"); get_source_buffer()->end_user_action(); - return Source::View::on_key_press_event(key); + return true; } } Gtk::TextIter insert_it = get_source_buffer()->get_insert()->get_iter(); Gtk::TextIter line_it = get_source_buffer()->get_iter_at_line(insert_it.get_line()); Gtk::TextIter line_plus_it=line_it; - for(unsigned c=0;ctab_size;c++) + for(unsigned c=0;cerase(line_it, line_plus_it); } + get_source_buffer()->insert_at_cursor("}"); get_source_buffer()->end_user_action(); - return Source::View::on_key_press_event(key); + return true; } get_source_buffer()->end_user_action(); diff --git a/src/source.h b/src/source.h index e76fb1e..ee4a4ec 100644 --- a/src/source.h +++ b/src/source.h @@ -14,6 +14,7 @@ #include "tooltips.h" #include "selectiondialog.h" #include +#include namespace Source { Glib::RefPtr guess_language(const boost::filesystem::path &file_path); @@ -22,9 +23,9 @@ namespace Source { public: std::string style; std::string font; - unsigned tab_size; - char tab_char; - std::string tab; + bool auto_tab_char_and_size; + char default_tab_char; + unsigned default_tab_size; bool highlight_current_line; bool show_line_numbers; std::unordered_map clang_types; @@ -79,6 +80,12 @@ namespace Source { std::string get_line_before_insert(); bool on_key_press_event(GdkEventKey* key); + + std::pair find_tab_char_and_size(); + unsigned tab_size; + char tab_char; + std::string tab; + std::regex tabs_regex; private: GtkSourceSearchContext *search_context; GtkSourceSearchSettings *search_settings; @@ -109,6 +116,10 @@ namespace Source { std::shared_ptr parsing_in_progress; std::thread parse_thread; std::atomic parse_thread_stop; + + std::regex bracket_regex; + std::regex no_bracket_statement_regex; + std::regex no_bracket_no_para_statement_regex; private: std::map get_buffer_map() const; // inits the syntax highligthing on file open diff --git a/src/window.cc b/src/window.cc index 11e4722..2aa0100 100644 --- a/src/window.cc +++ b/src/window.cc @@ -91,7 +91,7 @@ Window::Window() : box(Gtk::ORIENTATION_VERTICAL), notebook(directories), compil if(auto menu_item=dynamic_cast(menu.ui_manager->get_widget("/MenuBar/SourceMenu/SourceRename"))) menu_item->set_sensitive((bool)notebook.get_current_view()->rename_similar_tokens); - directories.select_path(notebook.get_current_view()->file_path); + directories.select(notebook.get_current_view()->file_path); Singleton::status()->set_text(notebook.get_current_view()->status); } @@ -99,10 +99,6 @@ Window::Window() : box(Gtk::ORIENTATION_VERTICAL), notebook(directories), compil 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 @@ -112,20 +108,23 @@ void Window::create_menu() { menu.action_group->add(Gtk::Action::create("FileQuit", "Quit juCi++"), Gtk::AccelKey(menu.key_map["quit"]), [this]() { hide(); }); - menu.action_group->add(Gtk::Action::create("FileNewFile", "New file"), Gtk::AccelKey(menu.key_map["new_file"]), [this]() { - new_file_entry(); + menu.action_group->add(Gtk::Action::create("FileNewFile", "New File"), Gtk::AccelKey(menu.key_map["new_file"]), [this]() { + new_file_dialog(); + }); + menu.action_group->add(Gtk::Action::create("FileNewFolder", "New Folder"), Gtk::AccelKey(menu.key_map["new_folder"]), [this]() { + new_folder_dialog(); }); menu.action_group->add(Gtk::Action::create("FileNewProject", "New Project")); menu.action_group->add(Gtk::Action::create("FileNewProjectCpp", "C++"), [this]() { new_cpp_project_dialog(); }); - menu.action_group->add(Gtk::Action::create("FileOpenFile", "Open file"), Gtk::AccelKey(menu.key_map["open_file"]), [this]() { + menu.action_group->add(Gtk::Action::create("FileOpenFile", "Open File"), Gtk::AccelKey(menu.key_map["open_file"]), [this]() { open_file_dialog(); }); - menu.action_group->add(Gtk::Action::create("FileOpenFolder", "Open folder"), Gtk::AccelKey(menu.key_map["open_folder"]), [this]() { + menu.action_group->add(Gtk::Action::create("FileOpenFolder", "Open Folder"), Gtk::AccelKey(menu.key_map["open_folder"]), [this]() { open_folder_dialog(); }); - menu.action_group->add(Gtk::Action::create("FileSaveAs", "Save as"), Gtk::AccelKey(menu.key_map["save_as"]), [this]() { + menu.action_group->add(Gtk::Action::create("FileSaveAs", "Save As"), Gtk::AccelKey(menu.key_map["save_as"]), [this]() { save_file_dialog(); }); @@ -178,17 +177,17 @@ void Window::create_menu() { INFO("Done Redo"); }); - menu.action_group->add(Gtk::Action::create("SourceGotoLine", "Go to line"), Gtk::AccelKey(menu.key_map["source_goto_line"]), [this]() { + menu.action_group->add(Gtk::Action::create("SourceGotoLine", "Go to Line"), Gtk::AccelKey(menu.key_map["source_goto_line"]), [this]() { goto_line_entry(); }); - menu.action_group->add(Gtk::Action::create("SourceCenterCursor", "Center cursor"), Gtk::AccelKey(menu.key_map["source_center_cursor"]), [this]() { + menu.action_group->add(Gtk::Action::create("SourceCenterCursor", "Center Cursor"), Gtk::AccelKey(menu.key_map["source_center_cursor"]), [this]() { if(notebook.get_current_page()!=-1) { while(gtk_events_pending()) gtk_main_iteration(); notebook.get_current_view()->scroll_to(notebook.get_current_view()->get_buffer()->get_insert(), 0.0, 1.0, 0.5); } }); - menu.action_group->add(Gtk::Action::create("SourceGotoDeclaration", "Go to declaration"), Gtk::AccelKey(menu.key_map["source_goto_declaration"]), [this]() { + menu.action_group->add(Gtk::Action::create("SourceGotoDeclaration", "Go to Declaration"), Gtk::AccelKey(menu.key_map["source_goto_declaration"]), [this]() { if(notebook.get_current_page()!=-1) { if(notebook.get_current_view()->get_declaration_location) { auto location=notebook.get_current_view()->get_declaration_location(); @@ -202,7 +201,7 @@ void Window::create_menu() { } } }); - menu.action_group->add(Gtk::Action::create("SourceGotoMethod", "Go to method"), Gtk::AccelKey(menu.key_map["source_goto_method"]), [this]() { + menu.action_group->add(Gtk::Action::create("SourceGotoMethod", "Go to Method"), Gtk::AccelKey(menu.key_map["source_goto_method"]), [this]() { if(notebook.get_current_page()!=-1) { if(notebook.get_current_view()->goto_method) { notebook.get_current_view()->goto_method(); @@ -213,11 +212,10 @@ void Window::create_menu() { rename_token_entry(); }); - menu.action_group->add(Gtk::Action::create("ProjectCompileAndRun", "Compile And Run"), Gtk::AccelKey(menu.key_map["compile_and_run"]), [this]() { + 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 || compiling) return; CMake cmake(notebook.get_current_view()->file_path); - directories.open_folder(); auto executables = cmake.get_functions_parameters("add_executable"); boost::filesystem::path executable_path; if(executables.size()>0 && executables[0].second.size()>0) { @@ -233,7 +231,6 @@ void Window::create_menu() { Singleton::terminal()->async_execute(Singleton::Config::terminal()->make_command, cmake.project_path, [this, executable_path, project_path](int exit_code){ compiling=false; if(exit_code==EXIT_SUCCESS) { - compile_success(); //TODO: Windows... auto executable_path_spaces_fixed=executable_path.string(); char last_char=0; @@ -261,15 +258,12 @@ void Window::create_menu() { if(notebook.get_current_page()==-1 || compiling) return; 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(Singleton::Config::terminal()->make_command, cmake.project_path, [this](int exit_code){ compiling=false; - if(exit_code==EXIT_SUCCESS) - compile_success(); }); } }); @@ -298,7 +292,7 @@ void Window::create_menu() { Singleton::terminal()->kill_last_async_execute(true); }); - menu.action_group->add(Gtk::Action::create("WindowCloseTab", "Close tab"), Gtk::AccelKey(menu.key_map["close_tab"]), [this]() { + menu.action_group->add(Gtk::Action::create("WindowCloseTab", "Close Tab"), Gtk::AccelKey(menu.key_map["close_tab"]), [this]() { notebook.close_current_page(); }); add_accel_group(menu.ui_manager->get_accel_group()); @@ -355,35 +349,62 @@ void Window::hide() { Gtk::Window::hide(); } -void Window::new_file_entry() { - entry_box.clear(); - entry_box.entries.emplace_back("untitled", [this](const std::string& content){ - std::string filename=content; - if(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"); +void Window::new_file_dialog() { + Gtk::FileChooserDialog dialog("Please create a new file", Gtk::FILE_CHOOSER_ACTION_SAVE); + 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); + dialog.set_position(Gtk::WindowPosition::WIN_POS_CENTER_ALWAYS); + dialog.add_button("Cancel", Gtk::RESPONSE_CANCEL); + dialog.add_button("Save", Gtk::RESPONSE_OK); + + int result = dialog.run(); + if(result==Gtk::RESPONSE_OK) { + boost::filesystem::path path = dialog.get_filename(); + if(path!="") { + if(boost::filesystem::exists(path)) { + Singleton::terminal()->print("Error: "+path.string()+" already exists.\n"); } else { - if(juci::filesystem::write(p)) { + if(juci::filesystem::write(path)) { if(directories.current_path!="") - directories.open_folder(); - notebook.open(boost::filesystem::canonical(p).string()); - Singleton::terminal()->print("New file "+p.string()+" created.\n"); + directories.update(); + notebook.open(path.string()); + Singleton::terminal()->print("New file "+path.string()+" created.\n"); } else - Singleton::terminal()->print("Error: could not create new file "+p.string()+".\n"); + Singleton::terminal()->print("Error: could not create new file "+path.string()+".\n"); } } - entry_box.hide(); - }); - auto entry_it=entry_box.entries.begin(); - entry_box.buttons.emplace_back("Create file", [this, entry_it](){ - entry_it->activate(); - }); - entry_box.show(); + } +} + +void Window::new_folder_dialog() { + auto time_now=std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + Gtk::FileChooserDialog dialog("Please create a new folder", Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER); + 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); + dialog.set_position(Gtk::WindowPosition::WIN_POS_CENTER_ALWAYS); + dialog.add_button("Cancel", Gtk::RESPONSE_CANCEL); + dialog.add_button("Create", Gtk::RESPONSE_OK); + + int result = dialog.run(); + if(result==Gtk::RESPONSE_OK) { + boost::filesystem::path path=dialog.get_filename(); + if(boost::filesystem::last_write_time(path)>=time_now) { + if(directories.current_path!="") + directories.update(); + Singleton::terminal()->print("New folder "+path.string()+" created.\n"); + } + else + Singleton::terminal()->print("Error: "+path.string()+" already exists.\n"); + directories.select(path); + } } void Window::new_cpp_project_dialog() { @@ -393,9 +414,9 @@ void Window::new_cpp_project_dialog() { else gtk_file_chooser_set_current_folder((GtkFileChooser*)dialog.gobj(), boost::filesystem::current_path().string().c_str()); dialog.set_transient_for(*this); - - dialog.add_button("_Cancel", Gtk::RESPONSE_CANCEL); - dialog.add_button("Select", Gtk::RESPONSE_OK); + dialog.set_position(Gtk::WindowPosition::WIN_POS_CENTER_ALWAYS); + dialog.add_button("Cancel", Gtk::RESPONSE_CANCEL); + dialog.add_button("Create", Gtk::RESPONSE_OK); int result = dialog.run(); if(result==Gtk::RESPONSE_OK) { @@ -420,7 +441,7 @@ void Window::new_cpp_project_dialog() { std::string cmakelists="cmake_minimum_required(VERSION 2.8)\n\nproject("+project_name+")\n\nset(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -std=c++1y -Wall\")\n\nadd_executable("+project_name+" main.cpp)\n"; std::string cpp_main="#include \n\nusing namespace std;\n\nint main() {\n cout << \"Hello World!\" << endl;\n\n return 0;\n}\n"; if(juci::filesystem::write(cmakelists_path, cmakelists) && juci::filesystem::write(cpp_main_path, cpp_main)) { - directories.open_folder(project_path); + directories.open(project_path); notebook.open(cpp_main_path); Singleton::terminal()->print("C++ project "+project_name+" created.\n"); } @@ -436,15 +457,15 @@ void Window::open_folder_dialog() { else gtk_file_chooser_set_current_folder((GtkFileChooser*)dialog.gobj(), boost::filesystem::current_path().string().c_str()); dialog.set_transient_for(*this); - //Add response buttons the the dialog: - dialog.add_button("_Cancel", Gtk::RESPONSE_CANCEL); - dialog.add_button("Select", Gtk::RESPONSE_OK); + dialog.set_position(Gtk::WindowPosition::WIN_POS_CENTER_ALWAYS); + dialog.add_button("Cancel", Gtk::RESPONSE_CANCEL); + dialog.add_button("Open", Gtk::RESPONSE_OK); int result = dialog.run(); if(result==Gtk::RESPONSE_OK) { std::string project_path=dialog.get_filename(); - directories.open_folder(project_path); + directories.open(project_path); } } @@ -456,28 +477,8 @@ void Window::open_file_dialog() { gtk_file_chooser_set_current_folder((GtkFileChooser*)dialog.gobj(), boost::filesystem::current_path().string().c_str()); dialog.set_transient_for(*this); dialog.set_position(Gtk::WindowPosition::WIN_POS_CENTER_ALWAYS); - - //Add response buttons the the dialog: - dialog.add_button("_Cancel", Gtk::RESPONSE_CANCEL); - dialog.add_button("_Open", Gtk::RESPONSE_OK); - - //Add filters, so that only certain file types can be selected: - Glib::RefPtr filter_text = Gtk::FileFilter::create(); - filter_text->set_name("Text files"); - filter_text->add_mime_type("text/plain"); - dialog.add_filter(filter_text); - - Glib::RefPtr filter_cpp = Gtk::FileFilter::create(); - filter_cpp->set_name("C/C++ files"); - filter_cpp->add_mime_type("text/x-c"); - filter_cpp->add_mime_type("text/x-c++"); - filter_cpp->add_mime_type("text/x-c-header"); - dialog.add_filter(filter_cpp); - - Glib::RefPtr filter_any = Gtk::FileFilter::create(); - filter_any->set_name("Any files"); - filter_any->add_pattern("*"); - dialog.add_filter(filter_any); + dialog.add_button("Cancel", Gtk::RESPONSE_CANCEL); + dialog.add_button("Open", Gtk::RESPONSE_OK); int result = dialog.run(); @@ -494,8 +495,8 @@ void Window::save_file_dialog() { Gtk::FileChooserDialog dialog(*this, "Please choose a file", Gtk::FILE_CHOOSER_ACTION_SAVE); gtk_file_chooser_set_filename((GtkFileChooser*)dialog.gobj(), notebook.get_current_view()->file_path.c_str()); dialog.set_position(Gtk::WindowPosition::WIN_POS_CENTER_ALWAYS); - dialog.add_button("_Cancel", Gtk::RESPONSE_CANCEL); - dialog.add_button("_Save", Gtk::RESPONSE_OK); + dialog.add_button("Cancel", Gtk::RESPONSE_CANCEL); + dialog.add_button("Save", Gtk::RESPONSE_OK); int result = dialog.run(); if(result==Gtk::RESPONSE_OK) { @@ -506,7 +507,7 @@ void Window::save_file_dialog() { file << notebook.get_current_view()->get_buffer()->get_text(); file.close(); if(directories.current_path!="") - directories.open_folder(); + directories.update(); notebook.open(path); Singleton::terminal()->print("File saved to: " + notebook.get_current_view()->file_path.string()+"\n"); } diff --git a/src/window.h b/src/window.h index dbb08eb..b18ae0f 100644 --- a/src/window.h +++ b/src/window.h @@ -36,11 +36,11 @@ private: EntryBox entry_box; Menu menu; std::atomic compiling; - Glib::Dispatcher compile_success; void create_menu(); void hide(); - void new_file_entry(); + void new_file_dialog(); + void new_folder_dialog(); void new_cpp_project_dialog(); void open_folder_dialog(); void open_file_dialog();