Browse Source

Cleanup: added Source::BaseView class. Also fixed replace_text for older libgit2 versions.

merge-requests/382/head
eidheim 8 years ago
parent
commit
9ee77f9df1
  1. 1
      src/CMakeLists.txt
  2. 16
      src/notebook.cc
  3. 2
      src/notebook.h
  4. 239
      src/source.cc
  5. 47
      src/source.h
  6. 294
      src/source_base.cc
  7. 76
      src/source_base.h
  8. 8
      src/source_clang.cc
  9. 42
      src/source_diff.cc
  10. 19
      src/source_diff.h
  11. 2
      src/source_language_protocol.cc
  12. 2
      src/source_spellcheck.cc
  13. 13
      src/source_spellcheck.h
  14. 28
      tests/source_test.cc

1
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

16
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<std::pair<size_t, Source::View *>> 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)

2
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<void(Source::View*)> on_change_page;

239
src/source.cc

@ -121,7 +121,7 @@ std::string Source::FixIt::string(Glib::RefPtr<Gtk::TextBuffer> buffer) {
std::unordered_set<Source::View*> Source::View::non_deleted_views;
std::unordered_set<Source::View*> Source::View::views;
Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> 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<Gsv::Language> 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::RefPtr<Gsv::L
}
}
bool Source::View::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<std::time_t>(-1);
return status;
}
void Source::View::rename(const boost::filesystem::path &path) {
{
std::unique_lock<std::mutex> 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<int>::max() : iter.get_line_offset();
std::vector<std::pair<const char*, const char*>> new_lines;
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()]);
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;
}
}
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<Gtk::TextBuffer::Mark> 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<Gtk::TextBuffer::Mark> 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<Gtk::TextBuffer::Mark> 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<char, unsigned> Source::View::find_tab_char_and_size() {
/////////////////////
//// GenericView ////
/////////////////////
Source::GenericView::GenericView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) : View(file_path, language) {
Source::GenericView::GenericView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) : BaseView(file_path, language), View(file_path, language) {
configure();
spellcheck_all=true;

47
src/source.h

@ -45,12 +45,7 @@ namespace Source {
View(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> 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<Gsv::Language> language;
std::function<void()> non_interactive_completion;
std::function<void(bool)> format_style;
std::function<Offset()> get_declaration_location;
@ -86,27 +79,9 @@ namespace Source {
Gtk::TextIter get_iter_for_dialog();
std::function<void(View* view, bool center, bool show_tooltips)> 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<void(View *view)> update_tab_label;
std::function<void(View *view)> update_status_location;
std::function<void(View *view)> update_status_file_path;
std::function<void(View *view)> update_status_diagnostics;
std::function<void(View *view)> update_status_state;
std::tuple<size_t, size_t, size_t> status_diagnostics;
std::string status_state;
void set_tab_char_and_size(char tab_char, unsigned tab_size);
std::pair<char, unsigned> get_tab_char_and_size() {return {tab_char, tab_size};}
@ -125,18 +100,6 @@ namespace Source {
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<Gtk::TextBuffer::Mark> 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<Gtk::TextBuffer::Mark> mark);
std::string get_line_before();
Gtk::TextIter get_tabs_end_iter(const Gtk::TextIter &iter);
Gtk::TextIter get_tabs_end_iter(Glib::RefPtr<Gtk::TextBuffer::Mark> 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);
bool find_open_non_curly_bracket_backward(Gtk::TextIter iter, Gtk::TextIter &found_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);

294
src/source_base.cc

@ -0,0 +1,294 @@
#include "source_base.h"
#include "info.h"
#include "terminal.h"
#include "git.h"
#include <fstream>
Source::BaseView::BaseView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> 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<std::time_t>(-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<std::time_t>(-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<int>::max() : iter.get_line_offset();
std::vector<std::pair<const char*, const char*>> new_lines;
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()]);
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;
}
}
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<std::mutex> 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+1<get_buffer()->get_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<Gtk::TextBuffer::Mark> 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<Gtk::TextBuffer::Mark> 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<Gtk::TextBuffer::Mark> 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<std::time_t>(-1) && last_write_time!=this->last_write_time)
Info::get().print("Caution: " + file_path.filename().string() + " was changed outside of juCi++");
}
}

76
src/source_base.h

@ -0,0 +1,76 @@
#pragma once
#include <gtksourceviewmm.h>
#include <mutex>
#include <boost/filesystem.hpp>
namespace Source {
class BaseView : public Gsv::View {
public:
BaseView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language);
boost::filesystem::path file_path;
boost::filesystem::path canonical_file_path;
Glib::RefPtr<Gsv::Language> 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<void(BaseView* view, bool center, bool show_tooltips)> 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<Gtk::TextBuffer::Mark> 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<Gtk::TextBuffer::Mark> mark);
std::string get_line_before();
Gtk::TextIter get_tabs_end_iter(const Gtk::TextIter &iter);
Gtk::TextIter get_tabs_end_iter(Glib::RefPtr<Gtk::TextBuffer::Mark> mark);
Gtk::TextIter get_tabs_end_iter(int line_nr);
Gtk::TextIter get_tabs_end_iter();
std::function<void(BaseView *view)> update_tab_label;
std::function<void(BaseView *view)> update_status_location;
std::function<void(BaseView *view)> update_status_file_path;
std::function<void(BaseView *view)> update_status_diagnostics;
std::function<void(BaseView *view)> update_status_state;
std::tuple<size_t, size_t, size_t> status_diagnostics;
std::string status_state;
std::function<void(BaseView *view)> 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();
};
}

8
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<Gsv::Language> 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<Gsv::Language> 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<std::string, std::string> &Source::ClangViewAutocomplet
Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> 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<Gsv::Language> 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);

42
src/source_diff.cc

@ -34,27 +34,13 @@ void Source::DiffView::Renderer::draw_vfunc(const Cairo::RefPtr<Cairo::Context>
}
}
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<Gsv::Language> 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<std::time_t>(-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<std::time_t>(-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+1<get_buffer()->get_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;

19
src/source_diff.h

@ -1,5 +1,5 @@
#pragma once
#include <gtksourceviewmm.h>
#include "source_base.h"
#include <boost/filesystem.hpp>
#include "dispatcher.h"
#include <set>
@ -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<Gsv::Language> 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<void(DiffView *view)> 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();

2
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<Gsv::Language> 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);

2
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<Gsv::Language> language): BaseView(file_path, language) {
if(spellcheck_config==nullptr)
spellcheck_config=new_aspell_config();
spellcheck_checker=nullptr;

13
src/source_spellcheck.h

@ -1,23 +1,20 @@
#pragma once
#include <gtksourceviewmm.h>
#include "source_base.h"
#include <aspell.h>
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<Gsv::Language> 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;

28
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);
// 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";
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="";
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";

Loading…
Cancel
Save