diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a0a934b..9c32003 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,6 +12,7 @@ set(JUCI_SHARED_FILES meson.cc project_build.cc source.cc + source_base.cc source_clang.cc source_diff.cc source_language_protocol.cc diff --git a/src/notebook.cc b/src/notebook.cc index f6e4e76..fe75efa 100644 --- a/src/notebook.cc +++ b/src/notebook.cc @@ -164,7 +164,7 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i else source_views.emplace_back(new Source::GenericView(file_path, language)); - source_views.back()->scroll_to_cursor_delayed=[this](Source::View* view, bool center, bool show_tooltips) { + source_views.back()->scroll_to_cursor_delayed=[this](Source::BaseView* view, bool center, bool show_tooltips) { while(Gtk::Main::events_pending()) Gtk::Main::iteration(false); if(get_current_view()==view) { @@ -176,17 +176,17 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i view->hide_tooltips(); } }; - source_views.back()->update_status_location=[this](Source::View* view) { + source_views.back()->update_status_location=[this](Source::BaseView* view) { if(get_current_view()==view) { auto iter=view->get_buffer()->get_insert()->get_iter(); status_location.set_text(" "+std::to_string(iter.get_line()+1)+":"+std::to_string(iter.get_line_offset()+1)); } }; - source_views.back()->update_status_file_path=[this](Source::View* view) { + source_views.back()->update_status_file_path=[this](Source::BaseView* view) { if(get_current_view()==view) status_file_path.set_text(' '+filesystem::get_short_path(view->file_path).string()); }; - source_views.back()->update_status_branch=[this](Source::DiffView* view) { + source_views.back()->update_status_branch=[this](Source::BaseView* view) { if(get_current_view()==view) { if(!view->status_branch.empty()) status_branch.set_text(" ("+view->status_branch+")"); @@ -194,7 +194,7 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i status_branch.set_text(""); } }; - source_views.back()->update_tab_label=[this](Source::View *view) { + source_views.back()->update_tab_label=[this](Source::BaseView *view) { std::string title=view->file_path.filename().string(); if(view->get_buffer()->get_modified()) title+='*'; @@ -210,7 +210,7 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i } } }; - source_views.back()->update_status_diagnostics=[this](Source::View* view) { + source_views.back()->update_status_diagnostics=[this](Source::BaseView* view) { if(get_current_view()==view) { std::string diagnostic_info; @@ -271,7 +271,7 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i status_diagnostics.set_markup(diagnostic_info); } }; - source_views.back()->update_status_state=[this](Source::View* view) { + source_views.back()->update_status_state=[this](Source::BaseView* view) { if(get_current_view()==view) status_state.set_text(view->status_state+" "); }; @@ -601,7 +601,7 @@ std::vector> Notebook::get_notebook_views() { return notebook_views; } -void Notebook::update_status(Source::View *view) { +void Notebook::update_status(Source::BaseView *view) { if(view->update_status_location) view->update_status_location(view); if(view->update_status_file_path) diff --git a/src/notebook.h b/src/notebook.h index b9c36b8..8563404 100644 --- a/src/notebook.h +++ b/src/notebook.h @@ -52,7 +52,7 @@ public: Gtk::Label status_branch; Gtk::Label status_diagnostics; Gtk::Label status_state; - void update_status(Source::View *view); + void update_status(Source::BaseView *view); void clear_status(); std::function on_change_page; diff --git a/src/source.cc b/src/source.cc index 44bc993..8a4b6df 100644 --- a/src/source.cc +++ b/src/source.cc @@ -121,7 +121,7 @@ std::string Source::FixIt::string(Glib::RefPtr buffer) { std::unordered_set Source::View::non_deleted_views; std::unordered_set Source::View::views; -Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtr language): Gsv::View(), SpellCheckView(), DiffView(file_path), language(language), status_diagnostics(0, 0, 0) { +Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtr language): BaseView(file_path, language), SpellCheckView(file_path, language), DiffView(file_path, language) { non_deleted_views.emplace(this); views.emplace(this); @@ -564,83 +564,6 @@ Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtrbegin_not_undoable_action(); - bool status=true; - if(language) { - std::ifstream input(file_path.string(), std::ofstream::binary); - if(input) { - std::stringstream ss; - ss << input.rdbuf(); - Glib::ustring ustr=ss.str(); - - bool valid=true; - Glib::ustring::iterator iter; - while(!ustr.validate(iter)) { - auto next_char_iter=iter; - next_char_iter++; - ustr.replace(iter, next_char_iter, "?"); - valid=false; - } - if(get_buffer()->size()==0) - get_buffer()->insert_at_cursor(ustr); - else - replace_text(ustr.raw()); - - if(!valid) - Terminal::get().print("Warning: "+file_path.string()+" is not a valid UTF-8 file. Saving might corrupt the file.\n"); - } - } - else { - std::ifstream input(file_path.string(), std::ofstream::binary); - if(input) { - std::stringstream ss; - ss << input.rdbuf(); - Glib::ustring ustr=ss.str(); - - bool valid=true; - if(ustr.validate()) { - if(get_buffer()->size()==0) - get_buffer()->insert_at_cursor(ustr); - else - replace_text(ustr.raw()); - } - else - valid=false; - - if(!valid) - Terminal::get().print("Error: "+file_path.string()+" is not a valid UTF-8 file.\n", true); - status=false; - } - } - get_source_buffer()->end_not_undoable_action(); - disable_spellcheck=false; - - boost::system::error_code ec; - last_write_time=boost::filesystem::last_write_time(file_path, ec); - if(ec) - last_write_time=static_cast(-1); - - return status; -} - -void Source::View::rename(const boost::filesystem::path &path) { - { - std::unique_lock lock(file_path_mutex); - file_path=path; - - boost::system::error_code ec; - canonical_file_path=boost::filesystem::canonical(file_path, ec); - if(ec) - canonical_file_path=file_path; - } - if(update_status_file_path) - update_status_file_path(this); - if(update_tab_label) - update_tab_label(this); -} - void Source::View::set_tab_char_and_size(char tab_char, unsigned tab_size) { this->tab_char=tab_char; this->tab_size=tab_size; @@ -739,61 +662,6 @@ bool Source::View::save() { } } -void Source::View::replace_text(const std::string &new_text) { - get_buffer()->begin_user_action(); - auto iter=get_buffer()->get_insert()->get_iter(); - int cursor_line_nr=iter.get_line(); - int cursor_line_offset=iter.ends_line() ? std::numeric_limits::max() : iter.get_line_offset(); - - std::vector> new_lines; - - const char* line_start=new_text.c_str(); - for(size_t i=0;iget_text().raw(), new_text); - - for(auto it=hunks.rbegin();it!=hunks.rend();++it) { - bool place_cursor=false; - Gtk::TextIter start; - if(it->old_lines.second!=0) { - start=get_buffer()->get_iter_at_line(it->old_lines.first-1); - auto end=get_buffer()->get_iter_at_line(it->old_lines.first-1+it->old_lines.second); - - if(cursor_line_nr>=start.get_line() && cursor_line_nrnew_lines.second!=0) { - place_cursor = true; - int line_diff=cursor_line_nr-start.get_line(); - cursor_line_nr+=static_cast(0.5+(static_cast(line_diff)/it->old_lines.second)*it->new_lines.second)-line_diff; - } - } - - get_buffer()->erase(start, end); - start=get_buffer()->get_iter_at_line(it->old_lines.first-1); - } - else - start=get_buffer()->get_iter_at_line(it->old_lines.first); - if(it->new_lines.second!=0) { - get_buffer()->insert(start, new_lines[it->new_lines.first-1].first, new_lines[it->new_lines.first-1+it->new_lines.second-1].second); - if(place_cursor) - place_cursor_at_line_offset(cursor_line_nr, cursor_line_offset); - } - } - } - catch(...) { - Terminal::get().print("Error: Could not replace text in buffer\n", true); - } - - get_buffer()->end_user_action(); -} - void Source::View::configure() { SpellCheckView::configure(); DiffView::configure(); @@ -1232,39 +1100,6 @@ Gtk::TextIter Source::View::get_iter_for_dialog() { return iter; } -Gtk::TextIter Source::View::get_iter_at_line_pos(int line, int pos) { - line=std::min(line, get_buffer()->get_line_count()-1); - if(line<0) - line=0; - auto iter=get_iter_at_line_end(line); - pos=std::min(pos, iter.get_line_index()); - if(pos<0) - pos=0; - return get_buffer()->get_iter_at_line_index(line, pos); -} - -void Source::View::place_cursor_at_line_pos(int line, int pos) { - get_buffer()->place_cursor(get_iter_at_line_pos(line, pos)); -} - -void Source::View::place_cursor_at_line_offset(int line, int offset) { - line=std::min(line, get_buffer()->get_line_count()-1); - if(line<0) - line=0; - auto iter=get_iter_at_line_end(line); - offset=std::min(offset, iter.get_line_offset()); - get_buffer()->place_cursor(get_buffer()->get_iter_at_line_offset(line, offset)); -} - -void Source::View::place_cursor_at_line_index(int line, int index) { - line=std::min(line, get_buffer()->get_line_count()-1); - if(line<0) - line=0; - auto iter=get_iter_at_line_end(line); - index=std::min(index, iter.get_line_index()); - get_buffer()->place_cursor(get_buffer()->get_iter_at_line_index(line, index)); -} - void Source::View::hide_tooltips() { delayed_tooltips_connection.disconnect(); type_tooltips.hide(); @@ -1279,49 +1114,6 @@ void Source::View::hide_dialogs() { CompletionDialog::get()->hide(); } -std::string Source::View::get_line(const Gtk::TextIter &iter) { - auto line_start_it = get_buffer()->get_iter_at_line(iter.get_line()); - auto line_end_it = get_iter_at_line_end(iter.get_line()); - std::string line(get_buffer()->get_text(line_start_it, line_end_it)); - return line; -} -std::string Source::View::get_line(Glib::RefPtr mark) { - return get_line(mark->get_iter()); -} -std::string Source::View::get_line(int line_nr) { - return get_line(get_buffer()->get_iter_at_line(line_nr)); -} -std::string Source::View::get_line() { - return get_line(get_buffer()->get_insert()); -} - -std::string Source::View::get_line_before(const Gtk::TextIter &iter) { - auto line_it = get_buffer()->get_iter_at_line(iter.get_line()); - std::string line(get_buffer()->get_text(line_it, iter)); - return line; -} -std::string Source::View::get_line_before(Glib::RefPtr mark) { - return get_line_before(mark->get_iter()); -} -std::string Source::View::get_line_before() { - return get_line_before(get_buffer()->get_insert()); -} - -Gtk::TextIter Source::View::get_tabs_end_iter(const Gtk::TextIter &iter) { - return get_tabs_end_iter(iter.get_line()); -} -Gtk::TextIter Source::View::get_tabs_end_iter(Glib::RefPtr mark) { - return get_tabs_end_iter(mark->get_iter()); -} -Gtk::TextIter Source::View::get_tabs_end_iter(int line_nr) { - auto sentence_iter = get_buffer()->get_iter_at_line(line_nr); - while((*sentence_iter==' ' || *sentence_iter=='\t') && !sentence_iter.ends_line() && sentence_iter.forward_char()) {} - return sentence_iter; -} -Gtk::TextIter Source::View::get_tabs_end_iter() { - return get_tabs_end_iter(get_buffer()->get_insert()); -} - bool Source::View::find_open_non_curly_bracket_backward(Gtk::TextIter iter, Gtk::TextIter &found_iter) { long para_count=0; long square_count=0; @@ -1741,33 +1533,6 @@ bool Source::View::on_key_press_event(GdkEventKey* key) { } } -Gtk::TextIter Source::View::get_smart_home_iter(const Gtk::TextIter &iter) { - auto start_line_iter=get_buffer()->get_iter_at_line(iter.get_line()); - auto start_sentence_iter=start_line_iter; - while(!start_sentence_iter.ends_line() && - (*start_sentence_iter==' ' || *start_sentence_iter=='\t') && - start_sentence_iter.forward_char()) {} - - if(iter>start_sentence_iter || iter==start_line_iter) - return start_sentence_iter; - else - return start_line_iter; -} -Gtk::TextIter Source::View::get_smart_end_iter(const Gtk::TextIter &iter) { - auto end_line_iter=get_iter_at_line_end(iter.get_line()); - auto end_sentence_iter=end_line_iter; - while(!end_sentence_iter.starts_line() && - (*end_sentence_iter==' ' || *end_sentence_iter=='\t' || end_sentence_iter.ends_line()) && - end_sentence_iter.backward_char()) {} - if(!end_sentence_iter.ends_line() && *end_sentence_iter!=' ' && *end_sentence_iter!='\t') - end_sentence_iter.forward_char(); - - if(iter==end_line_iter) - return end_sentence_iter; - else - return end_line_iter; -} - //Basic indentation bool Source::View::on_key_press_event_basic(GdkEventKey* key) { auto iter=get_buffer()->get_insert()->get_iter(); @@ -3037,7 +2802,7 @@ std::pair Source::View::find_tab_char_and_size() { ///////////////////// //// GenericView //// ///////////////////// -Source::GenericView::GenericView(const boost::filesystem::path &file_path, Glib::RefPtr language) : View(file_path, language) { +Source::GenericView::GenericView(const boost::filesystem::path &file_path, Glib::RefPtr language) : BaseView(file_path, language), View(file_path, language) { configure(); spellcheck_all=true; diff --git a/src/source.h b/src/source.h index b335e65..b4228b6 100644 --- a/src/source.h +++ b/src/source.h @@ -45,12 +45,7 @@ namespace Source { View(const boost::filesystem::path &file_path, Glib::RefPtr language); ~View(); - bool load(); - void rename(const boost::filesystem::path &path); - - virtual bool save(); - ///Set new text without moving scrolled window - void replace_text(const std::string &new_text); + bool save() override; void configure() override; @@ -64,8 +59,6 @@ namespace Source { void paste(); - Glib::RefPtr language; - std::function non_interactive_completion; std::function format_style; std::function get_declaration_location; @@ -86,27 +79,9 @@ namespace Source { Gtk::TextIter get_iter_for_dialog(); - std::function scroll_to_cursor_delayed=[](View* view, bool center, bool show_tooltips) {}; - /// Safely returns iter at a line at an offset using either byte index or character offset. Defaults to using byte index. - virtual Gtk::TextIter get_iter_at_line_pos(int line, int pos); - /// Safely places cursor at line using get_iter_at_line_pos. - void place_cursor_at_line_pos(int line, int pos); - /// Safely places cursor at line offset - void place_cursor_at_line_offset(int line, int offset); - /// Safely places cursor at line index - void place_cursor_at_line_index(int line, int index); - void hide_tooltips() override; void hide_dialogs() override; - std::function update_tab_label; - std::function update_status_location; - std::function update_status_file_path; - std::function update_status_diagnostics; - std::function update_status_state; - std::tuple status_diagnostics; - std::string status_state; - void set_tab_char_and_size(char tab_char, unsigned tab_size); std::pair get_tab_char_and_size() {return {tab_char, tab_size};} @@ -124,18 +99,6 @@ namespace Source { gdouble on_motion_last_x=0.0; gdouble on_motion_last_y=0.0; void set_tooltip_and_dialog_events(); - - std::string get_line(const Gtk::TextIter &iter); - std::string get_line(Glib::RefPtr mark); - std::string get_line(int line_nr); - std::string get_line(); - std::string get_line_before(const Gtk::TextIter &iter); - std::string get_line_before(Glib::RefPtr mark); - std::string get_line_before(); - Gtk::TextIter get_tabs_end_iter(const Gtk::TextIter &iter); - Gtk::TextIter get_tabs_end_iter(Glib::RefPtr mark); - Gtk::TextIter get_tabs_end_iter(int line_nr); - Gtk::TextIter get_tabs_end_iter(); /// Usually returns at start of line, but not always Gtk::TextIter find_start_of_sentence(Gtk::TextIter iter); @@ -149,14 +112,6 @@ namespace Source { void cleanup_whitespace_characters_on_return(const Gtk::TextIter &iter); - /// Move iter to line start. Depending on iter position, before or after indentation. - /// Works with wrapped lines. - Gtk::TextIter get_smart_home_iter(const Gtk::TextIter &iter); - /// Move iter to line end. Depending on iter position, before or after indentation. - /// Works with wrapped lines. - /// Note that smart end goes FIRST to end of line to avoid hiding empty chars after expressions. - Gtk::TextIter get_smart_end_iter(const Gtk::TextIter &iter); - bool is_bracket_language=false; bool on_key_press_event(GdkEventKey* key) override; bool on_key_press_event_basic(GdkEventKey* key); diff --git a/src/source_base.cc b/src/source_base.cc new file mode 100644 index 0000000..8b4a083 --- /dev/null +++ b/src/source_base.cc @@ -0,0 +1,294 @@ +#include "source_base.h" +#include "info.h" +#include "terminal.h" +#include "git.h" +#include + +Source::BaseView::BaseView(const boost::filesystem::path &file_path, Glib::RefPtr language): file_path(file_path), language(language), status_diagnostics(0, 0, 0) { + boost::system::error_code ec; + canonical_file_path=boost::filesystem::canonical(file_path, ec); + if(ec) + canonical_file_path=file_path; + + last_write_time=boost::filesystem::last_write_time(file_path, ec); + if(ec) + last_write_time=static_cast(-1); + + signal_focus_in_event().connect([this](GdkEventFocus *event) { + check_last_write_time(); + return false; + }); +} + +bool Source::BaseView::load() { + disable_spellcheck=true; + get_source_buffer()->begin_not_undoable_action(); + bool status=true; + if(language) { + std::ifstream input(file_path.string(), std::ofstream::binary); + if(input) { + std::stringstream ss; + ss << input.rdbuf(); + Glib::ustring ustr=ss.str(); + + bool valid=true; + Glib::ustring::iterator iter; + while(!ustr.validate(iter)) { + auto next_char_iter=iter; + next_char_iter++; + ustr.replace(iter, next_char_iter, "?"); + valid=false; + } + if(get_buffer()->size()==0) + get_buffer()->insert_at_cursor(ustr); + else + replace_text(ustr.raw()); + + if(!valid) + Terminal::get().print("Warning: "+file_path.string()+" is not a valid UTF-8 file. Saving might corrupt the file.\n"); + } + } + else { + std::ifstream input(file_path.string(), std::ofstream::binary); + if(input) { + std::stringstream ss; + ss << input.rdbuf(); + Glib::ustring ustr=ss.str(); + + bool valid=true; + if(ustr.validate()) { + if(get_buffer()->size()==0) + get_buffer()->insert_at_cursor(ustr); + else + replace_text(ustr.raw()); + } + else + valid=false; + + if(!valid) + Terminal::get().print("Error: "+file_path.string()+" is not a valid UTF-8 file.\n", true); + status=false; + } + } + get_source_buffer()->end_not_undoable_action(); + disable_spellcheck=false; + + boost::system::error_code ec; + last_write_time=boost::filesystem::last_write_time(file_path, ec); + if(ec) + last_write_time=static_cast(-1); + + return status; +} + +void Source::BaseView::replace_text(const std::string &new_text) { + get_buffer()->begin_user_action(); + + if(get_buffer()->size()==0) { + get_buffer()->insert_at_cursor(new_text); + get_buffer()->end_user_action(); + return; + } + else if(new_text.empty()) { + get_buffer()->set_text(new_text); + get_buffer()->end_user_action(); + return; + } + + auto iter=get_buffer()->get_insert()->get_iter(); + int cursor_line_nr=iter.get_line(); + int cursor_line_offset=iter.ends_line() ? std::numeric_limits::max() : iter.get_line_offset(); + + std::vector> new_lines; + + const char* line_start=new_text.c_str(); + for(size_t i=0;iget_text().raw(), new_text); + + for(auto it=hunks.rbegin();it!=hunks.rend();++it) { + bool place_cursor=false; + Gtk::TextIter start; + if(it->old_lines.second!=0) { + start=get_buffer()->get_iter_at_line(it->old_lines.first-1); + auto end=get_buffer()->get_iter_at_line(it->old_lines.first-1+it->old_lines.second); + + if(cursor_line_nr>=start.get_line() && cursor_line_nrnew_lines.second!=0) { + place_cursor = true; + int line_diff=cursor_line_nr-start.get_line(); + cursor_line_nr+=static_cast(0.5+(static_cast(line_diff)/it->old_lines.second)*it->new_lines.second)-line_diff; + } + } + + get_buffer()->erase(start, end); + start=get_buffer()->get_iter_at_line(it->old_lines.first-1); + } + else + start=get_buffer()->get_iter_at_line(it->old_lines.first); + if(it->new_lines.second!=0) { + get_buffer()->insert(start, new_lines[it->new_lines.first-1].first, new_lines[it->new_lines.first-1+it->new_lines.second-1].second); + if(place_cursor) + place_cursor_at_line_offset(cursor_line_nr, cursor_line_offset); + } + } + } + catch(...) { + Terminal::get().print("Error: Could not replace text in buffer\n", true); + } + + get_buffer()->end_user_action(); +} + +void Source::BaseView::rename(const boost::filesystem::path &path) { + { + std::unique_lock lock(file_path_mutex); + file_path=path; + + boost::system::error_code ec; + canonical_file_path=boost::filesystem::canonical(file_path, ec); + if(ec) + canonical_file_path=file_path; + } + if(update_status_file_path) + update_status_file_path(this); + if(update_tab_label) + update_tab_label(this); +} + +Gtk::TextIter Source::BaseView::get_iter_at_line_end(int line_nr) { + if(line_nr>=get_buffer()->get_line_count()) + return get_buffer()->end(); + else if(line_nr+1get_line_count()) { + auto iter=get_buffer()->get_iter_at_line(line_nr+1); + iter.backward_char(); + if(!iter.ends_line()) // for CR+LF + iter.backward_char(); + return iter; + } + else { + auto iter=get_buffer()->get_iter_at_line(line_nr); + while(!iter.ends_line() && iter.forward_char()) {} + return iter; + } +} + +Gtk::TextIter Source::BaseView::get_iter_at_line_pos(int line, int pos) { + line=std::min(line, get_buffer()->get_line_count()-1); + if(line<0) + line=0; + auto iter=get_iter_at_line_end(line); + pos=std::min(pos, iter.get_line_index()); + if(pos<0) + pos=0; + return get_buffer()->get_iter_at_line_index(line, pos); +} + +void Source::BaseView::place_cursor_at_line_pos(int line, int pos) { + get_buffer()->place_cursor(get_iter_at_line_pos(line, pos)); +} + +void Source::BaseView::place_cursor_at_line_offset(int line, int offset) { + line=std::min(line, get_buffer()->get_line_count()-1); + if(line<0) + line=0; + auto iter=get_iter_at_line_end(line); + offset=std::min(offset, iter.get_line_offset()); + get_buffer()->place_cursor(get_buffer()->get_iter_at_line_offset(line, offset)); +} + +void Source::BaseView::place_cursor_at_line_index(int line, int index) { + line=std::min(line, get_buffer()->get_line_count()-1); + if(line<0) + line=0; + auto iter=get_iter_at_line_end(line); + index=std::min(index, iter.get_line_index()); + get_buffer()->place_cursor(get_buffer()->get_iter_at_line_index(line, index)); +} + +Gtk::TextIter Source::BaseView::get_smart_home_iter(const Gtk::TextIter &iter) { + auto start_line_iter=get_buffer()->get_iter_at_line(iter.get_line()); + auto start_sentence_iter=start_line_iter; + while(!start_sentence_iter.ends_line() && + (*start_sentence_iter==' ' || *start_sentence_iter=='\t') && + start_sentence_iter.forward_char()) {} + + if(iter>start_sentence_iter || iter==start_line_iter) + return start_sentence_iter; + else + return start_line_iter; +} +Gtk::TextIter Source::BaseView::get_smart_end_iter(const Gtk::TextIter &iter) { + auto end_line_iter=get_iter_at_line_end(iter.get_line()); + auto end_sentence_iter=end_line_iter; + while(!end_sentence_iter.starts_line() && + (*end_sentence_iter==' ' || *end_sentence_iter=='\t' || end_sentence_iter.ends_line()) && + end_sentence_iter.backward_char()) {} + if(!end_sentence_iter.ends_line() && *end_sentence_iter!=' ' && *end_sentence_iter!='\t') + end_sentence_iter.forward_char(); + + if(iter==end_line_iter) + return end_sentence_iter; + else + return end_line_iter; +} + +std::string Source::BaseView::get_line(const Gtk::TextIter &iter) { + auto line_start_it = get_buffer()->get_iter_at_line(iter.get_line()); + auto line_end_it = get_iter_at_line_end(iter.get_line()); + std::string line(get_buffer()->get_text(line_start_it, line_end_it)); + return line; +} +std::string Source::BaseView::get_line(Glib::RefPtr mark) { + return get_line(mark->get_iter()); +} +std::string Source::BaseView::get_line(int line_nr) { + return get_line(get_buffer()->get_iter_at_line(line_nr)); +} +std::string Source::BaseView::get_line() { + return get_line(get_buffer()->get_insert()); +} + +std::string Source::BaseView::get_line_before(const Gtk::TextIter &iter) { + auto line_it = get_buffer()->get_iter_at_line(iter.get_line()); + std::string line(get_buffer()->get_text(line_it, iter)); + return line; +} +std::string Source::BaseView::get_line_before(Glib::RefPtr mark) { + return get_line_before(mark->get_iter()); +} +std::string Source::BaseView::get_line_before() { + return get_line_before(get_buffer()->get_insert()); +} + +Gtk::TextIter Source::BaseView::get_tabs_end_iter(const Gtk::TextIter &iter) { + return get_tabs_end_iter(iter.get_line()); +} +Gtk::TextIter Source::BaseView::get_tabs_end_iter(Glib::RefPtr mark) { + return get_tabs_end_iter(mark->get_iter()); +} +Gtk::TextIter Source::BaseView::get_tabs_end_iter(int line_nr) { + auto sentence_iter = get_buffer()->get_iter_at_line(line_nr); + while((*sentence_iter==' ' || *sentence_iter=='\t') && !sentence_iter.ends_line() && sentence_iter.forward_char()) {} + return sentence_iter; +} +Gtk::TextIter Source::BaseView::get_tabs_end_iter() { + return get_tabs_end_iter(get_buffer()->get_insert()); +} + +void Source::BaseView::check_last_write_time() { + if(has_focus()) { + boost::system::error_code ec; + auto last_write_time=boost::filesystem::last_write_time(file_path, ec); + if(!ec && this->last_write_time!=static_cast(-1) && last_write_time!=this->last_write_time) + Info::get().print("Caution: " + file_path.filename().string() + " was changed outside of juCi++"); + } +} diff --git a/src/source_base.h b/src/source_base.h new file mode 100644 index 0000000..e0227fa --- /dev/null +++ b/src/source_base.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include + +namespace Source { + class BaseView : public Gsv::View { + public: + BaseView(const boost::filesystem::path &file_path, Glib::RefPtr language); + boost::filesystem::path file_path; + boost::filesystem::path canonical_file_path; + + Glib::RefPtr language; + + bool load(); + /// Set new text more optimally and without unnecessary scrolling + void replace_text(const std::string &new_text); + void rename(const boost::filesystem::path &path); + virtual bool save() = 0; + + virtual void configure() = 0; + virtual void hide_tooltips() = 0; + virtual void hide_dialogs() = 0; + + std::function scroll_to_cursor_delayed=[](BaseView* view, bool center, bool show_tooltips) {}; + + Gtk::TextIter get_iter_at_line_end(int line_nr); + + /// Safely returns iter at a line at an offset using either byte index or character offset. Defaults to using byte index. + virtual Gtk::TextIter get_iter_at_line_pos(int line, int pos); + /// Safely places cursor at line using get_iter_at_line_pos. + void place_cursor_at_line_pos(int line, int pos); + /// Safely places cursor at line offset + void place_cursor_at_line_offset(int line, int offset); + /// Safely places cursor at line index + void place_cursor_at_line_index(int line, int index); + + /// Move iter to line start. Depending on iter position, before or after indentation. + /// Works with wrapped lines. + Gtk::TextIter get_smart_home_iter(const Gtk::TextIter &iter); + /// Move iter to line end. Depending on iter position, before or after indentation. + /// Works with wrapped lines. + /// Note that smart end goes FIRST to end of line to avoid hiding empty chars after expressions. + Gtk::TextIter get_smart_end_iter(const Gtk::TextIter &iter); + + std::string get_line(const Gtk::TextIter &iter); + std::string get_line(Glib::RefPtr mark); + std::string get_line(int line_nr); + std::string get_line(); + std::string get_line_before(const Gtk::TextIter &iter); + std::string get_line_before(Glib::RefPtr mark); + std::string get_line_before(); + Gtk::TextIter get_tabs_end_iter(const Gtk::TextIter &iter); + Gtk::TextIter get_tabs_end_iter(Glib::RefPtr mark); + Gtk::TextIter get_tabs_end_iter(int line_nr); + Gtk::TextIter get_tabs_end_iter(); + + std::function update_tab_label; + std::function update_status_location; + std::function update_status_file_path; + std::function update_status_diagnostics; + std::function update_status_state; + std::tuple status_diagnostics; + std::string status_state; + std::function update_status_branch; + std::string status_branch; + + bool disable_spellcheck=false; + + protected: + std::mutex file_path_mutex; + std::time_t last_write_time; + void check_last_write_time(); + }; +} diff --git a/src/source_clang.cc b/src/source_clang.cc index f73b00f..9baa38f 100644 --- a/src/source_clang.cc +++ b/src/source_clang.cc @@ -17,7 +17,7 @@ clangmm::Index Source::ClangViewParse::clang_index(0, 0); Source::ClangViewParse::ClangViewParse(const boost::filesystem::path &file_path, Glib::RefPtr language): - Source::View(file_path, language) { + BaseView(file_path, language), Source::View(file_path, language) { Usages::Clang::erase_cache(file_path); auto tag_table=get_buffer()->get_tag_table(); @@ -454,7 +454,7 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle) Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::path &file_path, Glib::RefPtr language): - Source::ClangViewParse(file_path, language), autocomplete(this, interactive_completion, last_keyval, true) { + BaseView(file_path, language), Source::ClangViewParse(file_path, language), autocomplete(this, interactive_completion, last_keyval, true) { non_interactive_completion=[this] { if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) return; @@ -829,7 +829,7 @@ const std::unordered_map &Source::ClangViewAutocomplet Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file_path, Glib::RefPtr language) : - Source::ClangViewParse(file_path, language) { + BaseView(file_path, language), Source::ClangViewParse(file_path, language) { similar_identifiers_tag=get_buffer()->create_tag(); similar_identifiers_tag->property_weight()=Pango::WEIGHT_ULTRAHEAVY; @@ -1771,7 +1771,7 @@ void Source::ClangViewRefactor::tag_similar_identifiers(const Identifier &identi Source::ClangView::ClangView(const boost::filesystem::path &file_path, Glib::RefPtr language): - ClangViewParse(file_path, language), ClangViewAutocomplete(file_path, language), ClangViewRefactor(file_path, language) { + BaseView(file_path, language), ClangViewParse(file_path, language), ClangViewAutocomplete(file_path, language), ClangViewRefactor(file_path, language) { if(language) { get_source_buffer()->set_highlight_syntax(true); get_source_buffer()->set_language(language); diff --git a/src/source_diff.cc b/src/source_diff.cc index b3507b2..9c70a1d 100644 --- a/src/source_diff.cc +++ b/src/source_diff.cc @@ -34,27 +34,13 @@ void Source::DiffView::Renderer::draw_vfunc(const Cairo::RefPtr } } -Source::DiffView::DiffView(const boost::filesystem::path &file_path) : Gsv::View(), file_path(file_path), renderer(new Renderer()) { - boost::system::error_code ec; - canonical_file_path=boost::filesystem::canonical(file_path, ec); - if(ec) - canonical_file_path=file_path; - +Source::DiffView::DiffView(const boost::filesystem::path &file_path, Glib::RefPtr language) : BaseView(file_path, language), renderer(new Renderer()) { renderer->tag_added=get_buffer()->create_tag("git_added"); renderer->tag_modified=get_buffer()->create_tag("git_modified"); renderer->tag_removed=get_buffer()->create_tag("git_removed"); renderer->tag_removed_below=get_buffer()->create_tag(); renderer->tag_removed_above=get_buffer()->create_tag(); - last_write_time=boost::filesystem::last_write_time(file_path, ec); - if(ec) - last_write_time=static_cast(-1); - - signal_focus_in_event().connect([this](GdkEventFocus *event) { - check_last_write_time(); - return false; - }); - configure(); } @@ -74,15 +60,6 @@ Source::DiffView::~DiffView() { } } -void Source::DiffView::check_last_write_time() { - if(has_focus()) { - boost::system::error_code ec; - auto last_write_time=boost::filesystem::last_write_time(file_path, ec); - if(!ec && this->last_write_time!=static_cast(-1) && last_write_time!=this->last_write_time) - Info::get().print("Caution: " + file_path.filename().string() + " was changed outside of juCi++"); - } -} - void Source::DiffView::configure() { if(Config::get().source.show_git_diff) { if(repository) @@ -274,23 +251,6 @@ void Source::DiffView::configure() { }); } -Gtk::TextIter Source::DiffView::get_iter_at_line_end(int line_nr) { - if(line_nr>=get_buffer()->get_line_count()) - return get_buffer()->end(); - else if(line_nr+1get_line_count()) { - auto iter=get_buffer()->get_iter_at_line(line_nr+1); - iter.backward_char(); - if(!iter.ends_line()) // for CR+LF - iter.backward_char(); - return iter; - } - else { - auto iter=get_buffer()->get_iter_at_line(line_nr); - while(!iter.ends_line() && iter.forward_char()) {} - return iter; - } -} - void Source::DiffView::git_goto_next_diff() { auto iter=get_buffer()->get_insert()->get_iter(); auto insert_iter=iter; diff --git a/src/source_diff.h b/src/source_diff.h index 61cbfc0..5e7c9e4 100644 --- a/src/source_diff.h +++ b/src/source_diff.h @@ -1,5 +1,5 @@ #pragma once -#include +#include "source_base.h" #include #include "dispatcher.h" #include @@ -10,7 +10,7 @@ #include "git.h" namespace Source { - class DiffView : virtual public Gsv::View { + class DiffView : virtual public Source::BaseView { enum class ParseState {IDLE, STARTING, PREPROCESSING, PROCESSING, POSTPROCESSING}; class Renderer : public Gsv::GutterRenderer { @@ -29,22 +29,11 @@ namespace Source { Gsv::GutterRendererState p6) override; }; public: - DiffView(const boost::filesystem::path &file_path); + DiffView(const boost::filesystem::path &file_path, Glib::RefPtr language); ~DiffView(); - boost::filesystem::path file_path; - boost::filesystem::path canonical_file_path; - protected: - std::mutex file_path_mutex; - std::time_t last_write_time; - void check_last_write_time(); public: - virtual void configure(); - - std::function update_status_branch; - std::string status_branch; - - Gtk::TextIter get_iter_at_line_end(int line_nr); + void configure() override; void git_goto_next_diff(); std::string git_get_diff_details(); diff --git a/src/source_language_protocol.cc b/src/source_language_protocol.cc index b48e68a..a2477a5 100644 --- a/src/source_language_protocol.cc +++ b/src/source_language_protocol.cc @@ -318,7 +318,7 @@ void LanguageProtocol::Client::handle_server_request(const std::string &method, } Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path &file_path, Glib::RefPtr language, std::string language_id_) - : Source::View(file_path, language), uri("file://"+file_path.string()), language_id(std::move(language_id_)), client(LanguageProtocol::Client::get(file_path, language_id)), autocomplete(this, interactive_completion, last_keyval, false) { + : Source::BaseView(file_path, language), Source::View(file_path, language), uri("file://"+file_path.string()), language_id(std::move(language_id_)), client(LanguageProtocol::Client::get(file_path, language_id)), autocomplete(this, interactive_completion, last_keyval, false) { configure(); get_source_buffer()->set_language(language); get_source_buffer()->set_highlight_syntax(true); diff --git a/src/source_spellcheck.cc b/src/source_spellcheck.cc index a74cdc9..f9247e4 100644 --- a/src/source_spellcheck.cc +++ b/src/source_spellcheck.cc @@ -6,7 +6,7 @@ AspellConfig* Source::SpellCheckView::spellcheck_config=nullptr; -Source::SpellCheckView::SpellCheckView() : Gsv::View() { +Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, Glib::RefPtr language): BaseView(file_path, language) { if(spellcheck_config==nullptr) spellcheck_config=new_aspell_config(); spellcheck_checker=nullptr; diff --git a/src/source_spellcheck.h b/src/source_spellcheck.h index c1381db..a320760 100644 --- a/src/source_spellcheck.h +++ b/src/source_spellcheck.h @@ -1,23 +1,20 @@ #pragma once -#include +#include "source_base.h" #include namespace Source { - class SpellCheckView : virtual public Gsv::View { + class SpellCheckView : virtual public Source::BaseView { public: - SpellCheckView(); + SpellCheckView(const boost::filesystem::path &file_path, Glib::RefPtr language); ~SpellCheckView(); - virtual void configure(); - - virtual void hide_tooltips() {} - virtual void hide_dialogs(); + void configure() override; + void hide_dialogs() override; void spellcheck(); void remove_spellcheck_errors(); void goto_next_spellcheck_error(); - bool disable_spellcheck=false; protected: bool is_code_iter(const Gtk::TextIter &iter); bool spellcheck_all=false; diff --git a/tests/source_test.cc b/tests/source_test.cc index 9b669a1..c15c5a8 100644 --- a/tests/source_test.cc +++ b/tests/source_test.cc @@ -99,17 +99,23 @@ int main() { assert(buffer->get_insert()->get_iter().get_line_offset()==0); } { - // Fails on libgit2 shipped with Ubuntu 16 - // auto old_text="line 1\nline 3"; - // auto new_text=""; - // buffer->set_text(old_text); - // source_view.replace_text(new_text); - // assert(buffer->get_text()==new_text); + auto old_text="line 1\nline 3"; + auto new_text=""; + buffer->set_text(old_text); + source_view.replace_text(new_text); + assert(buffer->get_text()==new_text); - // source_view.replace_text(old_text); - // assert(buffer->get_text()==old_text); - // assert(buffer->get_insert()->get_iter().get_line()==1); - // assert(buffer->get_insert()->get_iter().get_line_offset()==6); + source_view.replace_text(old_text); + assert(buffer->get_text()==old_text); + assert(buffer->get_insert()->get_iter().get_line()==1); + assert(buffer->get_insert()->get_iter().get_line_offset()==6); + } + { + auto old_text=""; + auto new_text=""; + buffer->set_text(old_text); + source_view.replace_text(new_text); + assert(buffer->get_text()==new_text); } { auto old_text="line 1\nline 3\n";