diff --git a/src/git.cc b/src/git.cc index a6a9502..03f3ab6 100644 --- a/src/git.cc +++ b/src/git.cc @@ -52,6 +52,23 @@ Git::Repository::Diff::Lines Git::Repository::Diff::get_lines(const std::string return lines; } +std::vector Git::Repository::Diff::get_hunks(const std::string &old_buffer, const std::string &new_buffer) { + std::vector hunks; + Error error; + git_diff_options options; + git_diff_init_options(&options, GIT_DIFF_OPTIONS_VERSION); + options.context_lines=0; + error.code=git_diff_buffers(old_buffer.c_str(), old_buffer.size(), nullptr, new_buffer.c_str(), new_buffer.size(), nullptr, &options, nullptr, nullptr, + [](const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) { + auto hunks=static_cast*>(payload); + hunks->emplace_back(hunk->old_start, hunk->old_lines, hunk->new_start, hunk->new_lines); + return 0; + }, nullptr, &hunks); + if(error) + throw std::runtime_error(error.message()); + return hunks; +} + int Git::Repository::Diff::line_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, void *payload) noexcept { auto details=static_cast *>(payload); auto line_nr=details->second; diff --git a/src/git.h b/src/git.h index 6b44928..eaf9526 100644 --- a/src/git.h +++ b/src/git.h @@ -31,6 +31,14 @@ public: std::vector > modified; std::vector removed; }; + class Hunk { + public: + Hunk(int old_start, int old_size, int new_start, int new_size): old_lines(old_start, old_size), new_lines(new_start, new_size) {} + /// Start and size + std::pair old_lines; + /// Start and size + std::pair new_lines; + }; private: friend class Repository; Diff(const boost::filesystem::path &path, git_repository *repository); @@ -42,6 +50,7 @@ public: public: Diff() : repository(nullptr), blob(nullptr) {} Lines get_lines(const std::string &buffer); + static std::vector get_hunks(const std::string &old_buffer, const std::string &new_buffer); std::string get_details(const std::string &buffer, int line_nr); }; diff --git a/src/source.cc b/src/source.cc index 5ced1c1..44bc993 100644 --- a/src/source.cc +++ b/src/source.cc @@ -6,6 +6,7 @@ #include "directories.h" #include "menu.h" #include "selection_dialog.h" +#include "git.h" #include #include #include @@ -15,6 +16,7 @@ #include #include #include +#include // TODO 2019: Remove workarounds when Debian stable and FreeBSD has newer glibmm packages #if GLIBMM_MAJOR_VERSION>2 || (GLIBMM_MAJOR_VERSION==2 && (GLIBMM_MINOR_VERSION>51 || (GLIBMM_MINOR_VERSION==51 && GLIBMM_MICRO_VERSION>=2))) @@ -565,7 +567,6 @@ Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtrbegin_not_undoable_action(); - get_buffer()->erase(get_buffer()->begin(), get_buffer()->end()); bool status=true; if(language) { std::ifstream input(file_path.string(), std::ofstream::binary); @@ -581,8 +582,11 @@ bool Source::View::load() { next_char_iter++; ustr.replace(iter, next_char_iter, "?"); valid=false; - } - get_buffer()->insert_at_cursor(ustr); + } + 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"); @@ -596,8 +600,12 @@ bool Source::View::load() { Glib::ustring ustr=ss.str(); bool valid=true; - if(ustr.validate()) - get_buffer()->insert_at_cursor(ustr); + if(ustr.validate()) { + if(get_buffer()->size()==0) + get_buffer()->insert_at_cursor(ustr); + else + replace_text(ustr.raw()); + } else valid=false; @@ -731,61 +739,59 @@ bool Source::View::save() { } } -void Source::View::replace_text(const std::string &text) { +void Source::View::replace_text(const std::string &new_text) { get_buffer()->begin_user_action(); auto iter=get_buffer()->get_insert()->get_iter(); - auto cursor_line_nr=iter.get_line(); - auto cursor_line_offset=iter.get_line_offset(); - - size_t start_line_index=0; - int line_nr=0; - for(size_t c=0;cget_line_count()) { - auto start_iter=get_buffer()->get_iter_at_line(line_nr); - auto end_iter=get_iter_at_line_end(line_nr); + 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(get_buffer()->get_text(start_iter, end_iter)!=line) { - get_buffer()->erase(start_iter, end_iter); - iter=get_buffer()->get_iter_at_line(line_nr); - get_buffer()->insert(iter, line); + 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 { - iter=get_buffer()->end(); - get_buffer()->insert(iter, '\n'+line); + 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); } - - ++line_nr; - start_line_index=c+1; } } - - if(text[text.size()-1]=='\n') { - Gtk::TextIter iter; - get_buffer()->insert(get_iter_at_line_end(line_nr-1), "\n"); - ++line_nr; - } - - if(line_nrget_line_count()) { - auto iter=get_iter_at_line_end(line_nr-1); - get_buffer()->erase(iter, get_buffer()->end()); + catch(...) { + Terminal::get().print("Error: Could not replace text in buffer\n", true); } get_buffer()->end_user_action(); - - place_cursor_at_line_offset(cursor_line_nr, cursor_line_offset); } void Source::View::configure() { diff --git a/src/source.h b/src/source.h index e8e5dd0..b335e65 100644 --- a/src/source.h +++ b/src/source.h @@ -50,7 +50,7 @@ namespace Source { virtual bool save(); ///Set new text without moving scrolled window - void replace_text(const std::string &text); + void replace_text(const std::string &new_text); void configure() override; diff --git a/src/source_language_protocol.cc b/src/source_language_protocol.cc index 5f0aed2..b48e68a 100644 --- a/src/source_language_protocol.cc +++ b/src/source_language_protocol.cc @@ -477,24 +477,26 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { }); result_processed.get_future().get(); - get_buffer()->begin_user_action(); - auto iter=get_buffer()->get_insert()->get_iter(); - auto line=iter.get_line(); - auto offset=iter.get_line_offset(); - for(auto it=replaces.rbegin();it!=replaces.rend();++it) { - auto start=get_iter_at_line_pos(it->start.line, it->start.index); - auto end=get_iter_at_line_pos(it->end.line, it->end.index); - get_buffer()->erase(start, end); - start=get_iter_at_line_pos(it->start.line, it->start.index); - unescape_text(it->text); - get_buffer()->insert(start, it->text); + auto end_iter=get_buffer()->end(); + if(replaces.size()==1 && + replaces[0].start.line==0 && replaces[0].start.index==0 && + (replaces[0].end.line>static_cast(end_iter.get_line()) || + (replaces[0].end.line==static_cast(end_iter.get_line()) && replaces[0].end.index>=static_cast(end_iter.get_line_offset())))) { + unescape_text(replaces[0].text); + replace_text(replaces[0].text); } - if(get_buffer()->get_insert()->get_iter().is_end()) { - place_cursor_at_line_offset(line, offset); - hide_tooltips(); - scroll_to_cursor_delayed(this, true, false); + else { + get_buffer()->begin_user_action(); + for(auto it=replaces.rbegin();it!=replaces.rend();++it) { + auto start=get_iter_at_line_pos(it->start.line, it->start.index); + auto end=get_iter_at_line_pos(it->end.line, it->end.index); + get_buffer()->erase(start, end); + start=get_iter_at_line_pos(it->start.line, it->start.index); + unescape_text(it->text); + get_buffer()->insert(start, it->text); + } + get_buffer()->end_user_action(); } - get_buffer()->end_user_action(); }; } diff --git a/src/window.cc b/src/window.cc index 0848498..00a9deb 100644 --- a/src/window.cc +++ b/src/window.cc @@ -285,14 +285,10 @@ void Window::set_menu_actions() { return; } - int line = view->get_buffer()->get_insert()->get_iter().get_line(); - int offset = view->get_buffer()->get_insert()->get_iter().get_line_offset(); view->load(); while(Gtk::Main::events_pending()) Gtk::Main::iteration(false); Notebook::get().delete_cursor_locations(view); - view->place_cursor_at_line_offset(line, offset); - view->scroll_to_cursor_delayed(view, true, false); view->get_buffer()->set_modified(false); view->full_reparse(); } diff --git a/tests/git_test.cc b/tests/git_test.cc index 159f027..a9a3f52 100644 --- a/tests/git_test.cc +++ b/tests/git_test.cc @@ -37,5 +37,22 @@ int main() { return 1; } - return 0; + { + std::string old_text("line 1\nline2\n\nline4\n\n"); + std::string new_text("line2\n\nline41\nline5\n\nline 5\nline 6\n"); + auto hunks=Git::Repository::Diff::get_hunks(old_text, new_text); + assert(hunks.size()==3); + assert(hunks[0].old_lines.first==1); + assert(hunks[0].old_lines.second==1); + assert(hunks[0].new_lines.first==0); + assert(hunks[0].new_lines.second==0); + assert(hunks[1].old_lines.first==4); + assert(hunks[1].old_lines.second==1); + assert(hunks[1].new_lines.first==3); + assert(hunks[1].new_lines.second==2); + assert(hunks[2].old_lines.first==5); + assert(hunks[2].old_lines.second==0); + assert(hunks[2].new_lines.first==6); + assert(hunks[2].new_lines.second==2); + } } diff --git a/tests/source_test.cc b/tests/source_test.cc index d698334..9b669a1 100644 --- a/tests/source_test.cc +++ b/tests/source_test.cc @@ -40,4 +40,122 @@ int main() { g_assert(boost::filesystem::remove(source_file)); g_assert(!boost::filesystem::exists(source_file)); + + // replace_text tests + { + auto buffer=source_view.get_buffer(); + { + auto text="line 1\nline 2\nline3"; + buffer->set_text(text); + buffer->place_cursor(buffer->begin()); + source_view.replace_text(text); + assert(buffer->get_text()==text); + assert(buffer->get_insert()->get_iter()==buffer->begin()); + + buffer->place_cursor(buffer->end()); + source_view.replace_text(text); + assert(buffer->get_text()==text); + assert(buffer->get_insert()->get_iter()==buffer->end()); + + source_view.place_cursor_at_line_offset(1, 0); + source_view.replace_text(text); + assert(buffer->get_text()==text); + assert(buffer->get_insert()->get_iter().get_line()==1); + assert(buffer->get_insert()->get_iter().get_line_offset()==0); + } + { + auto old_text="line 1\nline3"; + auto new_text="line 1\nline 2\nline3"; + buffer->set_text(old_text); + source_view.place_cursor_at_line_offset(1, 0); + source_view.replace_text(new_text); + assert(buffer->get_text()==new_text); + assert(buffer->get_insert()->get_iter().get_line()==2); + assert(buffer->get_insert()->get_iter().get_line_offset()==0); + + 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()==0); + + source_view.place_cursor_at_line_offset(0, 0); + source_view.replace_text(new_text); + assert(buffer->get_text()==new_text); + assert(buffer->get_insert()->get_iter().get_line()==0); + assert(buffer->get_insert()->get_iter().get_line_offset()==0); + + source_view.replace_text(old_text); + assert(buffer->get_text()==old_text); + assert(buffer->get_insert()->get_iter().get_line()==0); + assert(buffer->get_insert()->get_iter().get_line_offset()==0); + + source_view.replace_text(new_text); + assert(buffer->get_text()==new_text); + + source_view.place_cursor_at_line_offset(2, 0); + 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()==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); + + // 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="line 1\nline 3\n"; + 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()==2); + assert(buffer->get_insert()->get_iter().get_line_offset()==0); + } + { + auto old_text="line 1\n\nline 3\nline 4\n\nline 5\n"; + auto new_text="line 1\n\nline 33\nline 44\n\nline 5\n"; + buffer->set_text(old_text); + source_view.place_cursor_at_line_offset(2, 0); + source_view.replace_text(new_text); + assert(buffer->get_text()==new_text); + assert(buffer->get_insert()->get_iter().get_line()==2); + assert(buffer->get_insert()->get_iter().get_line_offset()==0); + + buffer->set_text(old_text); + source_view.place_cursor_at_line_offset(3, 0); + source_view.replace_text(new_text); + assert(buffer->get_text()==new_text); + assert(buffer->get_insert()->get_iter().get_line()==3); + assert(buffer->get_insert()->get_iter().get_line_offset()==0); + } + { + auto old_text="line 1\n\nline 3\nline 4\n\nline 5\n"; + auto new_text="line 1\n\nline 33\nline 44\nline 45\nline 46\n\nline 5\n"; + buffer->set_text(old_text); + source_view.place_cursor_at_line_offset(2, 0); + source_view.replace_text(new_text); + assert(buffer->get_text()==new_text); + assert(buffer->get_insert()->get_iter().get_line()==2); + assert(buffer->get_insert()->get_iter().get_line_offset()==0); + + buffer->set_text(old_text); + source_view.place_cursor_at_line_offset(3, 0); + source_view.replace_text(new_text); + assert(buffer->get_text()==new_text); + assert(buffer->get_insert()->get_iter().get_line()==4); + assert(buffer->get_insert()->get_iter().get_line_offset()==0); + } + } }