#include "source_base.h" #include "info.h" #include "terminal.h" #include "git.h" #include "config.h" #include Source::BaseView::BaseView(const boost::filesystem::path &file_path, Glib::RefPtr language): Gsv::View(), file_path(file_path), language(language), status_diagnostics(0, 0, 0) { load(); get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(0)); signal_focus_in_event().connect([this](GdkEventFocus *event) { if(this->last_write_time!=static_cast(-1)) check_last_write_time(); return false; }); monitor_file(); } Source::BaseView::~BaseView() { monitor_changed_connection.disconnect(); delayed_monitor_changed_connection.disconnect(); } bool Source::BaseView::load() { boost::system::error_code ec; last_write_time=boost::filesystem::last_write_time(file_path, ec); if(ec) last_write_time=static_cast(-1); disable_spellcheck=true; get_source_buffer()->begin_not_undoable_action(); class Guard { public: Source::BaseView *view; ~Guard() { view->get_source_buffer()->end_not_undoable_action(); view->disable_spellcheck=false; } }; Guard guard{this}; 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(!valid) Terminal::get().print("Warning: "+file_path.string()+" is not a valid UTF-8 file. Saving might corrupt the file.\n"); if(get_buffer()->size()==0) get_buffer()->insert_at_cursor(ustr); else replace_text(ustr.raw()); } else return false; } else { std::ifstream input(file_path.string(), std::ofstream::binary); if(input) { std::stringstream ss; ss << input.rdbuf(); Glib::ustring ustr=ss.str(); if(ustr.validate()) { if(get_buffer()->size()==0) get_buffer()->insert_at_cursor(ustr); else replace_text(ustr.raw()); } else { Terminal::get().print("Error: "+file_path.string()+" is not a valid UTF-8 file.\n", true); return false; } } else return false; } get_buffer()->set_modified(false); return true; } 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) { file_path=path; if(update_status_file_path) update_status_file_path(this); if(update_tab_label) update_tab_label(this); } void Source::BaseView::monitor_file() { #ifdef __APPLE__ // TODO: Gio file monitor is bugged on MacOS class Recursive { public: static void f(BaseView *view, std::time_t last_write_time_) { view->delayed_monitor_changed_connection.disconnect(); view->delayed_monitor_changed_connection=Glib::signal_timeout().connect([view, last_write_time_]() { boost::system::error_code ec; auto last_write_time=boost::filesystem::last_write_time(view->file_path, ec); if(last_write_time!=last_write_time_) view->check_last_write_time(last_write_time); Recursive::f(view, last_write_time); return false; }, 1000); } }; delayed_monitor_changed_connection.disconnect(); if(this->last_write_time!=static_cast(-1)) Recursive::f(this, last_write_time); #else if(this->last_write_time!=static_cast(-1)) { monitor=Gio::File::create_for_path(file_path.string())->monitor_file(Gio::FileMonitorFlags::FILE_MONITOR_NONE); monitor_changed_connection.disconnect(); monitor_changed_connection=monitor->signal_changed().connect([this](const Glib::RefPtr &file, const Glib::RefPtr&, Gio::FileMonitorEvent monitor_event) { if(monitor_event!=Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { delayed_monitor_changed_connection.disconnect(); delayed_monitor_changed_connection=Glib::signal_timeout().connect([this]() { check_last_write_time(); return false; }, 500); } }); } #endif } void Source::BaseView::check_last_write_time(std::time_t last_write_time_) { if(this->last_write_time==static_cast(-1)) return; if(Config::get().source.auto_reload_changed_files && !get_buffer()->get_modified()) { boost::system::error_code ec; auto last_write_time=last_write_time_!=static_cast(-1) ? last_write_time_ : boost::filesystem::last_write_time(file_path, ec); if(!ec && last_write_time!=this->last_write_time) { if(load()) return; } } else if(has_focus()) { boost::system::error_code ec; auto last_write_time=last_write_time_!=static_cast(-1) ? last_write_time_ : boost::filesystem::last_write_time(file_path, ec); if(!ec && last_write_time!=this->last_write_time) Info::get().print("Caution: " + file_path.filename().string() + " was changed outside of juCi++"); } } Gtk::TextIter Source::BaseView::get_iter_at_line_pos(int line, int pos) { return get_iter_at_line_index(line, pos); } Gtk::TextIter Source::BaseView::get_iter_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()); if(offset<0) offset=0; return get_buffer()->get_iter_at_line_offset(line, offset); } Gtk::TextIter Source::BaseView::get_iter_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()); if(index<0) index=0; return get_buffer()->get_iter_at_line_index(line, index); } 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_for_dialog() { auto iter=get_buffer()->get_insert()->get_iter(); Gdk::Rectangle visible_rect; get_visible_rect(visible_rect); Gdk::Rectangle iter_rect; get_iter_location(iter, iter_rect); iter_rect.set_width(1); if(iter.get_line_offset()>=80) { get_iter_at_location(iter, visible_rect.get_x(), iter_rect.get_y()); get_iter_location(iter, iter_rect); } if(!visible_rect.intersects(iter_rect)) get_iter_at_location(iter, visible_rect.get_x(), visible_rect.get_y()+visible_rect.get_height()/3); return iter; } 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) { get_buffer()->place_cursor(get_iter_at_line_offset(line, offset)); } void Source::BaseView::place_cursor_at_line_index(int line, int index) { get_buffer()->place_cursor(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::place_cursor_at_next_diagnostic() { auto insert_offset=get_buffer()->get_insert()->get_iter().get_offset(); for(auto offset: diagnostic_offsets) { if(offset>insert_offset) { get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(offset)); scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); return; } } if(diagnostic_offsets.size()==0) Info::get().print("No diagnostics found in current buffer"); else { auto iter=get_buffer()->get_iter_at_offset(*diagnostic_offsets.begin()); get_buffer()->place_cursor(iter); scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); } }