Browse Source

Improved replace text through libgit2 making file reload and some format style functions faster and without scrolling issues

merge-requests/382/head
eidheim 8 years ago
parent
commit
a26940e921
  1. 17
      src/git.cc
  2. 9
      src/git.h
  3. 102
      src/source.cc
  4. 2
      src/source.h
  5. 34
      src/source_language_protocol.cc
  6. 4
      src/window.cc
  7. 19
      tests/git_test.cc
  8. 118
      tests/source_test.cc

17
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::Hunk> Git::Repository::Diff::get_hunks(const std::string &old_buffer, const std::string &new_buffer) {
std::vector<Git::Repository::Diff::Hunk> 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<std::vector<Git::Repository::Diff::Hunk>*>(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<std::pair<std::string, int> *>(payload);
auto line_nr=details->second;

9
src/git.h

@ -31,6 +31,14 @@ public:
std::vector<std::pair<int, int> > modified;
std::vector<int> 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<int, int> old_lines;
/// Start and size
std::pair<int, int> 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<Hunk> get_hunks(const std::string &old_buffer, const std::string &new_buffer);
std::string get_details(const std::string &buffer, int line_nr);
};

102
src/source.cc

@ -6,6 +6,7 @@
#include "directories.h"
#include "menu.h"
#include "selection_dialog.h"
#include "git.h"
#include <gtksourceview/gtksource.h>
#include <boost/property_tree/json_parser.hpp>
#include <boost/spirit/home/qi/char.hpp>
@ -15,6 +16,7 @@
#include <numeric>
#include <set>
#include <regex>
#include <limits>
// 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::RefPtr<Gsv::L
bool Source::View::load() {
disable_spellcheck=true;
get_source_buffer()->begin_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);
@ -582,7 +583,10 @@ bool Source::View::load() {
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;c<text.size();++c) {
if(text[c]=='\n' || c==text.size()-1) {
std::string line=text.substr(start_line_index, c-start_line_index);
if(text[c]!='\n')
line+=text[c];
//Remove carriage return
for(auto it=line.begin();it!=line.end();) {
if(*it=='\r')
it=line.erase(it);
else
++it;
}
Gtk::TextIter iter;
if(line_nr<get_buffer()->get_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<int>::max() : iter.get_line_offset();
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);
}
}
else {
iter=get_buffer()->end();
get_buffer()->insert(iter, '\n'+line);
}
std::vector<std::pair<const char*, const char*>> new_lines;
++line_nr;
start_line_index=c+1;
const char* line_start=new_text.c_str();
for(size_t i=0;i<new_text.size();++i) {
if(new_text[i]=='\n') {
new_lines.emplace_back(line_start, &new_text[i]+1);
line_start = &new_text[i]+1;
}
}
if(new_text.empty() || new_text.back()!='\n')
new_lines.emplace_back(line_start, &new_text[new_text.size()]);
if(text[text.size()-1]=='\n') {
Gtk::TextIter iter;
get_buffer()->insert(get_iter_at_line_end(line_nr-1), "\n");
++line_nr;
}
try {
auto hunks = Git::Repository::Diff::get_hunks(get_buffer()->get_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_nr<end.get_line()) {
if(it->new_lines.second!=0) {
place_cursor = true;
int line_diff=cursor_line_nr-start.get_line();
cursor_line_nr+=static_cast<int>(0.5+(static_cast<float>(line_diff)/it->old_lines.second)*it->new_lines.second)-line_diff;
}
}
if(line_nr<get_buffer()->get_line_count()) {
auto iter=get_iter_at_line_end(line_nr-1);
get_buffer()->erase(iter, get_buffer()->end());
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();
place_cursor_at_line_offset(cursor_line_nr, cursor_line_offset);
}
void Source::View::configure() {

2
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;

34
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<unsigned>(end_iter.get_line()) ||
(replaces[0].end.line==static_cast<unsigned>(end_iter.get_line()) && replaces[0].end.index>=static_cast<unsigned>(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();
};
}

4
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();
}

19
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);
}
}

118
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);
}
}
}

Loading…
Cancel
Save