diff --git a/README.md b/README.md index 5d2177f..c235929 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ See [language-server-protocol/specification.md](https://github.com/Microsoft/lan * Regex search and replace * Smart paste, keys and indentation * Multiple cursors -* Auto-indentation of C++ file buffers through [clang-format](http://clang.llvm.org/docs/ClangFormat.html) +* Auto-indentation through [clang-format](http://clang.llvm.org/docs/ClangFormat.html) or [Prettier](https://github.com/prettier/prettier) if installed * Source minimap * Split view * Multiple cursors diff --git a/libclangmm b/libclangmm index 997d02a..e8f8a04 160000 --- a/libclangmm +++ b/libclangmm @@ -1 +1 @@ -Subproject commit 997d02a8de78ff879577a3522e1de5ebec9802e7 +Subproject commit e8f8a04f98b2259468d50843759caac3c5922bfa diff --git a/src/notebook.cc b/src/notebook.cc index fe75efa..32c5e0b 100644 --- a/src/notebook.cc +++ b/src/notebook.cc @@ -69,13 +69,13 @@ Notebook::Notebook() : Gtk::Paned(), notebooks(2) { }); auto provider = Gtk::CssProvider::create(); - //GtkNotebook-tab-overlap got removed in gtk 3.20, but margin works in 3.20 + //GtkNotebook-tab-overlap got removed in gtk 3.20, but margin works in 3.20 #if GTK_VERSION_GE(3, 20) - provider->load_from_data("tab {border-radius: 5px 5px 0 0; padding: 0 4px; margin: 0;}"); + provider->load_from_data("tab {border-radius: 5px 5px 0 0; padding: 0 4px; margin: 0;}"); #else - provider->load_from_data(".notebook {-GtkNotebook-tab-overlap: 0px;} tab {border-radius: 5px 5px 0 0; padding: 4px 4px;}"); + provider->load_from_data(".notebook {-GtkNotebook-tab-overlap: 0px;} tab {border-radius: 5px 5px 0 0; padding: 4px 4px;}"); #endif - notebook.get_style_context()->add_provider(provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + notebook.get_style_context()->add_provider(provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); } pack1(notebooks[0], true, true); } @@ -120,6 +120,7 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i if(notebook_index==1 && !split) toggle_split(); + // Use canonical path to follow symbolic links boost::system::error_code ec; auto canonical_file_path=boost::filesystem::canonical(file_path, ec); if(ec) diff --git a/src/source.cc b/src/source.cc index fbe030a..a97cd7c 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): BaseView(file_path, language), SpellCheckView(file_path, language), DiffView(file_path, language) { +Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtr language, bool is_generic_view): BaseView(file_path, language), SpellCheckView(file_path, language), DiffView(file_path, language) { non_deleted_views.emplace(this); views.emplace(this); @@ -184,344 +184,8 @@ Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtrget_id()=="js" || language->get_id()=="json" || language->get_id()=="css")) { - format_style=[this](bool continue_without_style_file) { - auto command=prettier.string()+" --cursor-offset "+std::to_string(get_buffer()->get_insert()->get_iter().get_offset()); - command+=" --stdin-filepath "+this->file_path.string(); - - if(get_buffer()->get_has_selection()) { - Gtk::TextIter start, end; - get_buffer()->get_selection_bounds(start, end); - command+=" --range-start "+std::to_string(start.get_offset()); - command+=" --range-end "+std::to_string(end.get_offset()); - } - - if(!continue_without_style_file) { - bool has_style_file=false; - auto style_file_search_path=this->file_path.parent_path(); - - while(true) { - if(boost::filesystem::exists(style_file_search_path/".prettierrc") || - boost::filesystem::exists(style_file_search_path/"prettier.config")) { - has_style_file=true; - break; - } - if(style_file_search_path==style_file_search_path.root_directory()) - break; - style_file_search_path=style_file_search_path.parent_path(); - } - - if(!has_style_file && !continue_without_style_file) - return; - } - - std::stringstream stdin_stream(get_buffer()->get_text()), stdout_stream, stderr_stream; - - auto exit_status=Terminal::get().process(stdin_stream, stdout_stream, command, this->file_path.parent_path(), &stderr_stream); - if(exit_status==0) { - replace_text(stdout_stream.str()); - std::string line; - std::getline(stderr_stream, line); - if(line!="NaN") { - try { - auto offset=atoi(line.c_str()); - if(offsetsize()) { - get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(offset)); - hide_tooltips(); - } - } - catch(...) {} - } - } - else { - // static std::regex regex("^\\[error\\] stdin: (.*) \\(([0-9]*):([0-9]*)\\)$"); - // std::string line; - // std::getline(stderr_stream, line); - // std::smatch sm; - // if(std::regex_match(line, sm, regex)) { - // auto line=std::min(atoi(sm[1].str().c_str()), get_buffer()->get_line_count()-1); // TODO: add try - // if(line<0) - // line=0; - // auto iter=get_iter_at_line_end(line); - // auto pos=std::min(atoi(sm[2].str().c_str()), iter.get_line_offset()); // TODO: add try - // if(pos<0) - // pos=0; - // auto start=get_buffer()->get_iter_at_line_offset(line, pos); - // auto end=start; - // end.forward_char(); - // if(start==end) - // start.forward_char(); - - // std::string diagnostic_tag_name="def:error"; - // std::string severity_spelling="Error"; - - // auto spelling=sm[1].str(); - - // auto create_tooltip_buffer=[this, spelling, severity_spelling, diagnostic_tag_name]() { - // auto tooltip_buffer=Gtk::TextBuffer::create(get_buffer()->get_tag_table()); - // tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), severity_spelling, diagnostic_tag_name); - // tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), ":\n"+spelling, "def:note"); - // return tooltip_buffer; - // }; - // diagnostic_tooltips.emplace_back(create_tooltip_buffer, this, get_buffer()->create_mark(start), get_buffer()->create_mark(end)); - - // get_buffer()->apply_tag_by_name(diagnostic_tag_name+"_underline", start, end); - // } - Terminal::get().print("Prettier:\n"+stderr_stream.str()+'\n', true); // TODO: consider using the above WiP code instead - } - }; - } - else if(language && (language->get_id()=="chdr" || language->get_id()=="cpphdr" || language->get_id()=="c" || - language->get_id()=="cpp" || language->get_id()=="objc" || language->get_id()=="java" || - language->get_id()=="js" || language->get_id()=="ts" || language->get_id()=="proto" || - language->get_id()=="c-sharp" || language->get_id()=="html" || language->get_id()=="cuda" || - language->get_id()=="php" || language->get_id()=="rust" || language->get_id()=="swift" || - language->get_id()=="go" || language->get_id()=="scala" || language->get_id()=="opencl")) { - is_bracket_language=true; - - format_style=[this](bool continue_without_style_file) { - static auto clang_format_command = filesystem::get_executable("clang-format").string(); - - auto command=clang_format_command+" -output-replacements-xml -assume-filename="+filesystem::escape_argument(this->file_path.string()); - - if(get_buffer()->get_has_selection()) { - Gtk::TextIter start, end; - get_buffer()->get_selection_bounds(start, end); - command+=" -lines="+std::to_string(start.get_line()+1)+':'+std::to_string(end.get_line()+1); - } - - bool use_style_file=false; - auto style_file_search_path=this->file_path.parent_path(); - while(true) { - if(boost::filesystem::exists(style_file_search_path/".clang-format") || boost::filesystem::exists(style_file_search_path/"_clang-format")) { - use_style_file=true; - break; - } - if(style_file_search_path==style_file_search_path.root_directory()) - break; - style_file_search_path=style_file_search_path.parent_path(); - } - - if(use_style_file) - command+=" -style=file"; - else { - if(!continue_without_style_file) - return; - unsigned indent_width; - std::string tab_style; - if(tab_char=='\t') { - indent_width=tab_size*8; - tab_style="UseTab: Always"; - } - else { - indent_width=tab_size; - tab_style="UseTab: Never"; - } - command+=" -style=\"{IndentWidth: "+std::to_string(indent_width); - command+=", "+tab_style; - command+=", "+std::string("AccessModifierOffset: -")+std::to_string(indent_width); - if(Config::get().source.clang_format_style!="") - command+=", "+Config::get().source.clang_format_style; - command+="}\""; - } - - std::stringstream stdin_stream(get_buffer()->get_text()), stdout_stream; - - auto exit_status=Terminal::get().process(stdin_stream, stdout_stream, command, this->file_path.parent_path()); - if(exit_status==0) { - // The following code is complex due to clang-format returning offsets in byte offsets instead of char offsets - - // Create bytes_in_lines cache to significantly speed up the processing of finding iterators from byte offsets - std::vector bytes_in_lines; - auto line_count=get_buffer()->get_line_count(); - for(int line_nr=0;line_nrget_iter_at_line(line_nr); - bytes_in_lines.emplace_back(iter.get_bytes_in_line()); - } - - get_buffer()->begin_user_action(); - try { - boost::property_tree::ptree pt; - boost::property_tree::xml_parser::read_xml(stdout_stream, pt); - auto replacements_pt=pt.get_child("replacements"); - for(auto it=replacements_pt.rbegin();it!=replacements_pt.rend();++it) { - if(it->first=="replacement") { - auto offset=it->second.get(".offset"); - auto length=it->second.get(".length"); - auto replacement_str=it->second.get(""); - - size_t bytes=0; - for(size_t c=0;c line_index(c, offset-previous_bytes); - auto start=get_buffer()->get_iter_at_line_index(line_index.first, line_index.second); - - // Use left gravity insert to avoid moving cursor from end of line - bool left_gravity_insert=false; - if(get_buffer()->get_insert()->get_iter()==start) { - auto iter=start; - do { - if(*iter!=' ' && *iter!='\t') { - left_gravity_insert=iter.ends_line(); - break; - } - } while(iter.forward_char()); - } - - if(length>0) { - auto offset_end=offset+length; - size_t bytes=0; - for(size_t c=0;cget_iter_at_line_index(c, offset_end-previous_bytes); - get_buffer()->erase(start, end); - start=get_buffer()->get_iter_at_line_index(line_index.first, line_index.second); - break; - } - } - } - if(left_gravity_insert) { - auto mark=get_buffer()->create_mark(start); - get_buffer()->insert(start, replacement_str); - get_buffer()->place_cursor(mark->get_iter()); - get_buffer()->delete_mark(mark); - } - else - get_buffer()->insert(start, replacement_str); - break; - } - } - } - } - } - catch(const std::exception &e) { - Terminal::get().print(std::string("Error: error parsing clang-format output: ")+e.what()+'\n', true); - } - get_buffer()->end_user_action(); - } - }; - } - else if(language && language->get_id()=="markdown") { - // The style file currently has no options, but checking if it exists - format_style=[this](bool continue_without_style_file) { - bool has_style_file=false; - auto style_file_search_path=this->file_path.parent_path(); - while(true) { - if(boost::filesystem::exists(style_file_search_path/".markdown-format")) { - has_style_file=true; - break; - } - if(style_file_search_path==style_file_search_path.root_directory()) - break; - style_file_search_path=style_file_search_path.parent_path(); - } - if(!has_style_file && !continue_without_style_file) - return; - - auto special_character=[](Gtk::TextIter iter) { - if(*iter=='*' || *iter=='#' || *iter=='<' || *iter=='>' || *iter==' ' || *iter=='=' || *iter=='`' || *iter=='-') - return true; - // Tests if a line starts with for instance: 2. - if(*iter>='0' && *iter<='9' && iter.forward_char() && - *iter=='.' && iter.forward_char() && - *iter==' ') - return true; - return false; - }; - - get_buffer()->begin_user_action(); - disable_spellcheck=true; - cleanup_whitespace_characters(); - - auto iter=get_buffer()->begin(); - size_t last_space_offset=-1; - bool headline=false; - bool monospace=false; - bool script=false; - bool html_tag=false; - int square_brackets=0; - do { - if(iter.starts_line()) { - last_space_offset=-1; - auto next_line_iter=iter; - if(*iter=='#' || (next_line_iter.forward_line() && *next_line_iter=='=')) - headline=true; - else - headline=false; - auto test_iter=iter; - if(*test_iter=='`' && test_iter.forward_char() && - *test_iter=='`' && test_iter.forward_char() && - *test_iter=='`') { - script=!script; - iter.forward_chars(3); - continue; - } - } - if(!script && *iter=='`') - monospace=!monospace; - if(!script && !monospace) { - if(*iter=='<') - html_tag=true; - else if(*iter=='>') - html_tag=false; - else if(*iter=='[') - ++square_brackets; - else if(*iter==']') - --square_brackets; - } - if(!headline && !script && !monospace && !html_tag && square_brackets==0) { - if(*iter==' ' && iter.get_line_offset()<=80) - last_space_offset=iter.get_offset(); - // Insert newline on long lines - else if((*iter==' ' || iter.ends_line()) && iter.get_line_offset()>80 && last_space_offset!=static_cast(-1)) { - auto stored_iter=iter; - iter=get_buffer()->get_iter_at_offset(last_space_offset); - auto next_iter=iter; - next_iter.forward_char(); - // Do not add newline if the next iter is a special character - if(special_character(next_iter)) { - iter=stored_iter; - if(*iter==' ') - last_space_offset=iter.get_offset(); - continue; - } - iter=get_buffer()->erase(iter, next_iter); - iter=get_buffer()->insert(iter, "\n"); - iter.backward_char(); - } - // Remove newline on short lines - else if(iter.ends_line() && !iter.starts_line() && iter.get_line_offset()<=80) { - auto next_line_iter=iter; - // Do not remove newline if the next line for instance is a header - if(next_line_iter.forward_char() && !next_line_iter.ends_line() && !special_character(next_line_iter)) { - auto end_word_iter=next_line_iter; - // Do not remove newline if the word on the next line is too long - size_t diff=0; - while(*end_word_iter!=' ' && !end_word_iter.ends_line() && end_word_iter.forward_char()) - ++diff; - if(iter.get_line_offset()+diff+1<=80) { - iter=get_buffer()->erase(iter, next_line_iter); - iter=get_buffer()->insert(iter, " "); - iter.backward_char(); - if(iter.get_line_offset()<=80) - last_space_offset=iter.get_offset(); - } - } - } - } - } while(iter.forward_char()); - disable_spellcheck=false; - get_buffer()->end_user_action(); - }; - } + setup_tooltip_and_dialog_events(); + setup_format_style(is_generic_view); #ifndef __APPLE__ set_tab_width(4); //Visual size of a \t hardcoded to be equal to visual size of 4 spaces. Buggy on OS X @@ -737,6 +401,8 @@ bool Source::View::save() { last_write_time=boost::filesystem::last_write_time(file_path, ec); if(ec) last_write_time=static_cast(-1); + // Remonitor file in case it did not exist before + monitor_file(); get_buffer()->set_modified(false); Directories::get().on_save_file(file_path); return true; @@ -847,7 +513,7 @@ void Source::View::configure() { } } -void Source::View::set_tooltip_and_dialog_events() { +void Source::View::setup_tooltip_and_dialog_events() { get_buffer()->signal_changed().connect([this] { hide_tooltips(); }); @@ -865,64 +531,401 @@ void Source::View::set_tooltip_and_dialog_events() { show_type_tooltips(rectangle); show_diagnostic_tooltips(rectangle); } - return false; - }, 100); + return false; + }, 100); + } + type_tooltips.hide(); + diagnostic_tooltips.hide(); + } + on_motion_last_x=event->x; + on_motion_last_y=event->y; + return false; + }); + + get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr& mark) { + if(get_buffer()->get_has_selection() && mark->get_name()=="selection_bound") + delayed_tooltips_connection.disconnect(); + + if(mark->get_name()=="insert") { + hide_tooltips(); + delayed_tooltips_connection=Glib::signal_timeout().connect([this]() { + Tooltips::init(); + Gdk::Rectangle rectangle; + get_iter_location(get_buffer()->get_insert()->get_iter(), rectangle); + int location_window_x, location_window_y; + buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), rectangle.get_y(), location_window_x, location_window_y); + rectangle.set_x(location_window_x-2); + rectangle.set_y(location_window_y); + rectangle.set_width(5); + if(parsed) { + show_type_tooltips(rectangle); + show_diagnostic_tooltips(rectangle); + } + return false; + }, 500); + + if(SelectionDialog::get()) + SelectionDialog::get()->hide(); + if(CompletionDialog::get()) + CompletionDialog::get()->hide(); + + if(update_status_location) + update_status_location(this); + } + }); + + signal_scroll_event().connect([this](GdkEventScroll* event) { + hide_tooltips(); + hide_dialogs(); + return false; + }); + + signal_focus_out_event().connect([this](GdkEventFocus* event) { + hide_tooltips(); + return false; + }); + + signal_leave_notify_event().connect([this](GdkEventCrossing*) { + delayed_tooltips_connection.disconnect(); + return false; + }); +} + +void Source::View::setup_format_style(bool is_generic_view) { + static auto prettier = filesystem::find_executable("prettier"); + if(!prettier.empty() && language && + (language->get_id()=="js" || language->get_id()=="json" || language->get_id()=="css")) { + if(is_generic_view) { + goto_next_diagnostic=[this] { + place_cursor_at_next_diagnostic(); + }; + get_buffer()->signal_changed().connect([this] { + clear_diagnostic_tooltips(); + status_diagnostics=std::make_tuple(0, 0, 0); + if(update_status_diagnostics) + update_status_diagnostics(this); + }); + } + format_style=[this, is_generic_view](bool continue_without_style_file) { + auto command=prettier.string(); + if(!continue_without_style_file) { + std::stringstream stdin_stream, stdout_stream; + auto exit_status=Terminal::get().process(stdin_stream, stdout_stream, command+" --find-config-path "+this->file_path.string()); + if(exit_status==0) { + if(stdout_stream.tellp()==0) + return; + } + else + return; + } + + command+=" --stdin-filepath "+this->file_path.string()+" --print-width 120 --config-precedence prefer-file"; + + if(get_buffer()->get_has_selection()) { // Cannot be used together with --cursor-offset + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + command+=" --range-start "+std::to_string(start.get_offset()); + command+=" --range-end "+std::to_string(end.get_offset()); + } + else + command+=" --cursor-offset "+std::to_string(get_buffer()->get_insert()->get_iter().get_offset()); + + size_t num_warnings=0, num_errors=0, num_fix_its=0; + if(is_generic_view) + clear_diagnostic_tooltips(); + + std::stringstream stdin_stream(get_buffer()->get_text()), stdout_stream, stderr_stream; + auto exit_status=Terminal::get().process(stdin_stream, stdout_stream, command, this->file_path.parent_path(), &stderr_stream); + if(exit_status==0) { + replace_text(stdout_stream.str()); + std::string line; + std::getline(stderr_stream, line); + if(!line.empty() && line!="NaN") { + try { + auto offset=atoi(line.c_str()); + if(offsetsize()) { + get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(offset)); + hide_tooltips(); + } + } + catch(...) {} + } + } + else if(is_generic_view) { + static std::regex regex("^\\[error\\] stdin: (.*) \\(([0-9]*):([0-9]*)\\)$"); + std::string line; + std::getline(stderr_stream, line); + std::smatch sm; + if(std::regex_match(line, sm, regex)) { + try { + auto start=get_iter_at_line_offset(atoi(sm[2].str().c_str())-1, atoi(sm[3].str().c_str())-1); + ++num_errors; + if(start.ends_line()) + start.backward_char(); + auto end=start; + end.forward_char(); + if(start==end) + start.forward_char(); + + add_diagnostic_tooltip(start, end, sm[1].str(), true); + } + catch(...) {} + } + } + if(is_generic_view) { + status_diagnostics=std::make_tuple(num_warnings, num_errors, num_fix_its); + if(update_status_diagnostics) + update_status_diagnostics(this); + } + }; + } + else if(language && (language->get_id()=="chdr" || language->get_id()=="cpphdr" || language->get_id()=="c" || + language->get_id()=="cpp" || language->get_id()=="objc" || language->get_id()=="java" || + language->get_id()=="js" || language->get_id()=="ts" || language->get_id()=="proto" || + language->get_id()=="c-sharp" || language->get_id()=="html" || language->get_id()=="cuda" || + language->get_id()=="php" || language->get_id()=="rust" || language->get_id()=="swift" || + language->get_id()=="go" || language->get_id()=="scala" || language->get_id()=="opencl")) { + is_bracket_language=true; + + format_style=[this](bool continue_without_style_file) { + static auto clang_format_command = filesystem::get_executable("clang-format").string(); + + auto command=clang_format_command+" -output-replacements-xml -assume-filename="+filesystem::escape_argument(this->file_path.string()); + + if(get_buffer()->get_has_selection()) { + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + command+=" -lines="+std::to_string(start.get_line()+1)+':'+std::to_string(end.get_line()+1); + } + + bool use_style_file=false; + auto style_file_search_path=this->file_path.parent_path(); + while(true) { + if(boost::filesystem::exists(style_file_search_path/".clang-format") || boost::filesystem::exists(style_file_search_path/"_clang-format")) { + use_style_file=true; + break; + } + if(style_file_search_path==style_file_search_path.root_directory()) + break; + style_file_search_path=style_file_search_path.parent_path(); + } + + if(use_style_file) + command+=" -style=file"; + else { + if(!continue_without_style_file) + return; + unsigned indent_width; + std::string tab_style; + if(tab_char=='\t') { + indent_width=tab_size*8; + tab_style="UseTab: Always"; + } + else { + indent_width=tab_size; + tab_style="UseTab: Never"; + } + command+=" -style=\"{IndentWidth: "+std::to_string(indent_width); + command+=", "+tab_style; + command+=", "+std::string("AccessModifierOffset: -")+std::to_string(indent_width); + if(Config::get().source.clang_format_style!="") + command+=", "+Config::get().source.clang_format_style; + command+="}\""; + } + + std::stringstream stdin_stream(get_buffer()->get_text()), stdout_stream; + + auto exit_status=Terminal::get().process(stdin_stream, stdout_stream, command, this->file_path.parent_path()); + if(exit_status==0) { + // The following code is complex due to clang-format returning offsets in byte offsets instead of char offsets + + // Create bytes_in_lines cache to significantly speed up the processing of finding iterators from byte offsets + std::vector bytes_in_lines; + auto line_count=get_buffer()->get_line_count(); + for(int line_nr=0;line_nrget_iter_at_line(line_nr); + bytes_in_lines.emplace_back(iter.get_bytes_in_line()); + } + + get_buffer()->begin_user_action(); + try { + boost::property_tree::ptree pt; + boost::property_tree::xml_parser::read_xml(stdout_stream, pt); + auto replacements_pt=pt.get_child("replacements"); + for(auto it=replacements_pt.rbegin();it!=replacements_pt.rend();++it) { + if(it->first=="replacement") { + auto offset=it->second.get(".offset"); + auto length=it->second.get(".length"); + auto replacement_str=it->second.get(""); + + size_t bytes=0; + for(size_t c=0;c line_index(c, offset-previous_bytes); + auto start=get_buffer()->get_iter_at_line_index(line_index.first, line_index.second); + + // Use left gravity insert to avoid moving cursor from end of line + bool left_gravity_insert=false; + if(get_buffer()->get_insert()->get_iter()==start) { + auto iter=start; + do { + if(*iter!=' ' && *iter!='\t') { + left_gravity_insert=iter.ends_line(); + break; + } + } while(iter.forward_char()); + } + + if(length>0) { + auto offset_end=offset+length; + size_t bytes=0; + for(size_t c=0;cget_iter_at_line_index(c, offset_end-previous_bytes); + get_buffer()->erase(start, end); + start=get_buffer()->get_iter_at_line_index(line_index.first, line_index.second); + break; + } + } + } + if(left_gravity_insert) { + auto mark=get_buffer()->create_mark(start); + get_buffer()->insert(start, replacement_str); + get_buffer()->place_cursor(mark->get_iter()); + get_buffer()->delete_mark(mark); + } + else + get_buffer()->insert(start, replacement_str); + break; + } + } + } + } + } + catch(const std::exception &e) { + Terminal::get().print(std::string("Error: error parsing clang-format output: ")+e.what()+'\n', true); + } + get_buffer()->end_user_action(); } - type_tooltips.hide(); - diagnostic_tooltips.hide(); - } - on_motion_last_x=event->x; - on_motion_last_y=event->y; - return false; - }); - - get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr& mark) { - if(get_buffer()->get_has_selection() && mark->get_name()=="selection_bound") - delayed_tooltips_connection.disconnect(); - - if(mark->get_name()=="insert") { - hide_tooltips(); - delayed_tooltips_connection=Glib::signal_timeout().connect([this]() { - Tooltips::init(); - Gdk::Rectangle rectangle; - get_iter_location(get_buffer()->get_insert()->get_iter(), rectangle); - int location_window_x, location_window_y; - buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), rectangle.get_y(), location_window_x, location_window_y); - rectangle.set_x(location_window_x-2); - rectangle.set_y(location_window_y); - rectangle.set_width(5); - if(parsed) { - show_type_tooltips(rectangle); - show_diagnostic_tooltips(rectangle); + }; + } + else if(language && language->get_id()=="markdown") { + // The style file currently has no options, but checking if it exists + format_style=[this](bool continue_without_style_file) { + bool has_style_file=false; + auto style_file_search_path=this->file_path.parent_path(); + while(true) { + if(boost::filesystem::exists(style_file_search_path/".markdown-format")) { + has_style_file=true; + break; } + if(style_file_search_path==style_file_search_path.root_directory()) + break; + style_file_search_path=style_file_search_path.parent_path(); + } + if(!has_style_file && !continue_without_style_file) + return; + + auto special_character=[](Gtk::TextIter iter) { + if(*iter=='*' || *iter=='#' || *iter=='<' || *iter=='>' || *iter==' ' || *iter=='=' || *iter=='`' || *iter=='-') + return true; + // Tests if a line starts with for instance: 2. + if(*iter>='0' && *iter<='9' && iter.forward_char() && + *iter=='.' && iter.forward_char() && + *iter==' ') + return true; return false; - }, 500); + }; - if(SelectionDialog::get()) - SelectionDialog::get()->hide(); - if(CompletionDialog::get()) - CompletionDialog::get()->hide(); + get_buffer()->begin_user_action(); + disable_spellcheck=true; + cleanup_whitespace_characters(); - if(update_status_location) - update_status_location(this); - } - }); - - signal_scroll_event().connect([this](GdkEventScroll* event) { - hide_tooltips(); - hide_dialogs(); - return false; - }); - - signal_focus_out_event().connect([this](GdkEventFocus* event) { - hide_tooltips(); - return false; - }); - - signal_leave_notify_event().connect([this](GdkEventCrossing*) { - delayed_tooltips_connection.disconnect(); - return false; - }); + auto iter=get_buffer()->begin(); + size_t last_space_offset=-1; + bool headline=false; + bool monospace=false; + bool script=false; + bool html_tag=false; + int square_brackets=0; + do { + if(iter.starts_line()) { + last_space_offset=-1; + auto next_line_iter=iter; + if(*iter=='#' || (next_line_iter.forward_line() && *next_line_iter=='=')) + headline=true; + else + headline=false; + auto test_iter=iter; + if(*test_iter=='`' && test_iter.forward_char() && + *test_iter=='`' && test_iter.forward_char() && + *test_iter=='`') { + script=!script; + iter.forward_chars(3); + continue; + } + } + if(!script && *iter=='`') + monospace=!monospace; + if(!script && !monospace) { + if(*iter=='<') + html_tag=true; + else if(*iter=='>') + html_tag=false; + else if(*iter=='[') + ++square_brackets; + else if(*iter==']') + --square_brackets; + } + if(!headline && !script && !monospace && !html_tag && square_brackets==0) { + if(*iter==' ' && iter.get_line_offset()<=80) + last_space_offset=iter.get_offset(); + // Insert newline on long lines + else if((*iter==' ' || iter.ends_line()) && iter.get_line_offset()>80 && last_space_offset!=static_cast(-1)) { + auto stored_iter=iter; + iter=get_buffer()->get_iter_at_offset(last_space_offset); + auto next_iter=iter; + next_iter.forward_char(); + // Do not add newline if the next iter is a special character + if(special_character(next_iter)) { + iter=stored_iter; + if(*iter==' ') + last_space_offset=iter.get_offset(); + continue; + } + iter=get_buffer()->erase(iter, next_iter); + iter=get_buffer()->insert(iter, "\n"); + iter.backward_char(); + } + // Remove newline on short lines + else if(iter.ends_line() && !iter.starts_line() && iter.get_line_offset()<=80) { + auto next_line_iter=iter; + // Do not remove newline if the next line for instance is a header + if(next_line_iter.forward_char() && !next_line_iter.ends_line() && !special_character(next_line_iter)) { + auto end_word_iter=next_line_iter; + // Do not remove newline if the word on the next line is too long + size_t diff=0; + while(*end_word_iter!=' ' && !end_word_iter.ends_line() && end_word_iter.forward_char()) + ++diff; + if(iter.get_line_offset()+diff+1<=80) { + iter=get_buffer()->erase(iter, next_line_iter); + iter=get_buffer()->insert(iter, " "); + iter.backward_char(); + if(iter.get_line_offset()<=80) + last_space_offset=iter.get_offset(); + } + } + } + } + } while(iter.forward_char()); + disable_spellcheck=false; + get_buffer()->end_user_action(); + }; + } } void Source::View::search_occurrences_updated(GtkWidget* widget, GParamSpec* property, gpointer data) { @@ -1169,28 +1172,35 @@ void Source::View::paste() { scroll_to_cursor_delayed(this, false, false); } -Gtk::TextIter Source::View::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::View::hide_tooltips() { delayed_tooltips_connection.disconnect(); type_tooltips.hide(); diagnostic_tooltips.hide(); } +void Source::View::add_diagnostic_tooltip(const Gtk::TextIter &start, const Gtk::TextIter &end, std::string spelling, bool error) { + diagnostic_offsets.emplace(start.get_offset()); + + std::string severity_tag_name = error ? "def:error" : "def:warning"; + + auto create_tooltip_buffer=[this, spelling=std::move(spelling), error, severity_tag_name]() { + auto tooltip_buffer=Gtk::TextBuffer::create(get_buffer()->get_tag_table()); + tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), error ? "Error" : "Warning", severity_tag_name); + tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), ":\n"+spelling, "def:note"); + return tooltip_buffer; + }; + diagnostic_tooltips.emplace_back(create_tooltip_buffer, this, get_buffer()->create_mark(start), get_buffer()->create_mark(end)); + + get_buffer()->apply_tag_by_name(severity_tag_name+"_underline", start, end); +} + +void Source::View::clear_diagnostic_tooltips() { + diagnostic_offsets.clear(); + diagnostic_tooltips.clear(); + get_buffer()->remove_tag_by_name("def:warning_underline", get_buffer()->begin(), get_buffer()->end()); + get_buffer()->remove_tag_by_name("def:error_underline", get_buffer()->begin(), get_buffer()->end()); +} + void Source::View::hide_dialogs() { SpellCheckView::hide_dialogs(); if(SelectionDialog::get()) @@ -2887,7 +2897,7 @@ std::pair Source::View::find_tab_char_and_size() { ///////////////////// //// GenericView //// ///////////////////// -Source::GenericView::GenericView(const boost::filesystem::path &file_path, Glib::RefPtr language) : BaseView(file_path, 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, true) { configure(); spellcheck_all=true; diff --git a/src/source.h b/src/source.h index b694d05..3053636 100644 --- a/src/source.h +++ b/src/source.h @@ -42,7 +42,7 @@ namespace Source { static std::unordered_set non_deleted_views; static std::unordered_set views; - View(const boost::filesystem::path &file_path, Glib::RefPtr language); + View(const boost::filesystem::path &file_path, Glib::RefPtr language, bool is_generic_view=false); ~View(); bool save() override; @@ -77,8 +77,6 @@ namespace Source { std::function()> get_documentation_template; std::function toggle_breakpoint; - Gtk::TextIter get_iter_for_dialog(); - void hide_tooltips() override; void hide_dialogs() override; @@ -90,15 +88,16 @@ namespace Source { virtual void soft_reparse(bool delayed=false) {soft_reparse_needed=false;} virtual void full_reparse() {full_reparse_needed=false;} protected: - bool parsed=false; + bool parsed=true; Tooltips diagnostic_tooltips; Tooltips type_tooltips; sigc::connection delayed_tooltips_connection; virtual void show_diagnostic_tooltips(const Gdk::Rectangle &rectangle) { diagnostic_tooltips.show(rectangle); } + void add_diagnostic_tooltip(const Gtk::TextIter &start, const Gtk::TextIter &end, std::string spelling, bool error); + void clear_diagnostic_tooltips(); virtual void show_type_tooltips(const Gdk::Rectangle &rectangle) {} gdouble on_motion_last_x=0.0; gdouble on_motion_last_y=0.0; - void set_tooltip_and_dialog_events(); /// Usually returns at start of line, but not always Gtk::TextIter find_start_of_sentence(Gtk::TextIter iter); @@ -130,6 +129,9 @@ namespace Source { guint previous_non_modifier_keyval=0; private: + void setup_tooltip_and_dialog_events(); + void setup_format_style(bool is_generic_view); + void cleanup_whitespace_characters(); Gsv::DrawSpacesFlags parse_show_whitespace_characters(const std::string &text); diff --git a/src/source_base.cc b/src/source_base.cc index 9e64c01..4987891 100644 --- a/src/source_base.cc +++ b/src/source_base.cc @@ -15,39 +15,7 @@ Source::BaseView::BaseView(const boost::filesystem::path &file_path, Glib::RefPt return false; }); -#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); - } - }; - 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=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 + monitor_file(); } Source::BaseView::~BaseView() { @@ -204,6 +172,92 @@ void Source::BaseView::rename(const boost::filesystem::path &path) { 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()) { + get_buffer()->set_modified(false); + 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(); @@ -221,15 +275,20 @@ Gtk::TextIter Source::BaseView::get_iter_at_line_end(int line_nr) { } } -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); +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) { @@ -237,21 +296,11 @@ void Source::BaseView::place_cursor_at_line_pos(int line, int 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)); + get_buffer()->place_cursor(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)); + get_buffer()->place_cursor(get_iter_at_line_index(line, index)); } Gtk::TextIter Source::BaseView::get_smart_home_iter(const Gtk::TextIter &iter) { @@ -266,6 +315,7 @@ Gtk::TextIter Source::BaseView::get_smart_home_iter(const Gtk::TextIter &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; @@ -324,24 +374,20 @@ Gtk::TextIter Source::BaseView::get_tabs_end_iter() { return get_tabs_end_iter(get_buffer()->get_insert()); } -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()) { - get_buffer()->set_modified(false); - return; - } +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; } } - 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++"); + 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); } } diff --git a/src/source_base.h b/src/source_base.h index 2adf87d..73ac1b1 100644 --- a/src/source_base.h +++ b/src/source_base.h @@ -2,6 +2,7 @@ #include #include +#include #include namespace Source { @@ -29,10 +30,16 @@ namespace Source { std::function scroll_to_cursor_delayed=[](BaseView* view, bool center, bool show_tooltips) {}; + /// Safely returns iter given line and 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 returns iter given line and character offset + Gtk::TextIter get_iter_at_line_offset(int line, int offset); + /// Safely returns iter given line and byte index + Gtk::TextIter get_iter_at_line_index(int line, int index); + Gtk::TextIter get_iter_at_line_end(int line_nr); + Gtk::TextIter get_iter_for_dialog(); - /// 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 @@ -40,6 +47,11 @@ namespace Source { /// Safely places cursor at line index void place_cursor_at_line_index(int line, int index); + protected: + std::time_t last_write_time; + void monitor_file(); + void check_last_write_time(std::time_t last_write_time_=static_cast(-1)); + /// 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); @@ -60,6 +72,9 @@ namespace Source { Gtk::TextIter get_tabs_end_iter(int line_nr); Gtk::TextIter get_tabs_end_iter(); + std::set diagnostic_offsets; + void place_cursor_at_next_diagnostic(); + public: std::function update_tab_label; std::function update_status_location; std::function update_status_file_path; @@ -71,9 +86,5 @@ namespace Source { std::string status_branch; bool disable_spellcheck=false; - - protected: - std::time_t last_write_time; - void check_last_write_time(std::time_t last_write_time_=static_cast(-1)); }; } diff --git a/src/source_clang.cc b/src/source_clang.cc index d57df94..f0bfd96 100644 --- a/src/source_clang.cc +++ b/src/source_clang.cc @@ -265,11 +265,8 @@ void Source::ClangViewParse::update_syntax() { } void Source::ClangViewParse::update_diagnostics() { - diagnostic_offsets.clear(); - diagnostic_tooltips.clear(); + clear_diagnostic_tooltips(); fix_its.clear(); - get_buffer()->remove_tag_by_name("def:warning_underline", get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag_by_name("def:error_underline", get_buffer()->begin(), get_buffer()->end()); size_t num_warnings=0; size_t num_errors=0; size_t num_fix_its=0; @@ -295,20 +292,19 @@ void Source::ClangViewParse::update_diagnostics() { index=diagnostic.offsets.second.index-1; if(index>=0 && indexget_iter_at_line_index(line, index); - - std::string diagnostic_tag_name; - if(diagnostic.severity<=CXDiagnostic_Warning) { - diagnostic_tag_name="def:warning"; + + bool error=false; + std::string severity_tag_name; + if(diagnostic.severity<=clangmm::Diagnostic::Severity::Warning) { + severity_tag_name="def:warning"; num_warnings++; } else { - diagnostic_tag_name="def:error"; + severity_tag_name="def:error"; num_errors++; + error=true; } - auto spelling=diagnostic.spelling; - auto severity_spelling=diagnostic.severity_spelling; - std::string fix_its_string; unsigned fix_its_count=0; for(auto &fix_it: diagnostic.fix_its) { @@ -333,23 +329,15 @@ void Source::ClangViewParse::update_diagnostics() { else if(fix_its_count>1) fix_its_string.insert(0, "Fix-its:\n"); - auto create_tooltip_buffer=[this, spelling, severity_spelling, diagnostic_tag_name, fix_its_string]() { - auto tooltip_buffer=Gtk::TextBuffer::create(get_buffer()->get_tag_table()); - tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), severity_spelling, diagnostic_tag_name); - tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), ":\n"+spelling, "def:note"); - if(fix_its_string.size()>0) { - tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), ":\n\n"+fix_its_string, "def:note"); - } - return tooltip_buffer; - }; - diagnostic_tooltips.emplace_back(create_tooltip_buffer, this, get_buffer()->create_mark(start), get_buffer()->create_mark(end)); - - get_buffer()->apply_tag_by_name(diagnostic_tag_name+"_underline", start, end); + if(!fix_its_string.empty()) + diagnostic.spelling+="\n\n"+fix_its_string; + add_diagnostic_tooltip(start, end, diagnostic.spelling, error); + auto iter=get_buffer()->get_insert()->get_iter(); if(iter.ends_line()) { auto next_iter=iter; if(next_iter.forward_char()) - get_buffer()->remove_tag_by_name(diagnostic_tag_name+"_underline", iter, next_iter); + get_buffer()->remove_tag_by_name(severity_tag_name+"_underline", iter, next_iter); } } } @@ -1616,21 +1604,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file Info::get().print("Buffer is parsing"); return; } - 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); - } + place_cursor_at_next_diagnostic(); }; get_fix_its=[this]() { diff --git a/src/source_clang.h b/src/source_clang.h index bea1eda..59a3e0d 100644 --- a/src/source_clang.h +++ b/src/source_clang.h @@ -32,7 +32,6 @@ namespace Source { void show_type_tooltips(const Gdk::Rectangle &rectangle) override; - std::set diagnostic_offsets; std::vector fix_its; std::thread parse_thread; diff --git a/src/source_diff.h b/src/source_diff.h index a078a84..d1a7ece 100644 --- a/src/source_diff.h +++ b/src/source_diff.h @@ -39,6 +39,7 @@ namespace Source { void git_goto_next_diff(); std::string git_get_diff_details(); + /// Use canonical path to follow symbolic links boost::filesystem::path canonical_file_path; private: std::mutex canonical_file_path_mutex; diff --git a/src/source_language_protocol.cc b/src/source_language_protocol.cc index 926a41e..3fe9ee2 100644 --- a/src/source_language_protocol.cc +++ b/src/source_language_protocol.cc @@ -322,7 +322,6 @@ Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path configure(); get_source_buffer()->set_language(language); get_source_buffer()->set_highlight_syntax(true); - parsed=true; similar_symbol_tag=get_buffer()->create_tag(); similar_symbol_tag->property_weight()=Pango::WEIGHT_ULTRAHEAVY; @@ -829,21 +828,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { } goto_next_diagnostic=[this]() { - 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); - } + place_cursor_at_next_diagnostic(); }; } @@ -879,10 +864,7 @@ void Source::LanguageProtocolView::unescape_text(std::string &text) { void Source::LanguageProtocolView::update_diagnostics(std::vector &&diagnostics) { dispatcher.post([this, diagnostics=std::move(diagnostics)] { - diagnostic_offsets.clear(); - diagnostic_tooltips.clear(); - get_buffer()->remove_tag_by_name("def:warning_underline", get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag_by_name("def:error_underline", get_buffer()->begin(), get_buffer()->end()); + clear_diagnostic_tooltips(); size_t num_warnings=0; size_t num_errors=0; size_t num_fix_its=0; @@ -898,37 +880,25 @@ void Source::LanguageProtocolView::update_diagnostics(std::vector=2) { - severity_spelling="Warning"; - diagnostic_tag_name="def:warning"; + severity_tag_name="def:warning"; num_warnings++; } else { - severity_spelling="Error"; - diagnostic_tag_name="def:error"; + severity_tag_name="def:error"; num_errors++; + error=true; } - auto spelling=diagnostic.spelling; + add_diagnostic_tooltip(start, end, diagnostic.spelling, error); - auto create_tooltip_buffer=[this, spelling, severity_spelling, diagnostic_tag_name]() { - auto tooltip_buffer=Gtk::TextBuffer::create(get_buffer()->get_tag_table()); - tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), severity_spelling, diagnostic_tag_name); - tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), ":\n"+spelling, "def:note"); - return tooltip_buffer; - }; - diagnostic_tooltips.emplace_back(create_tooltip_buffer, this, get_buffer()->create_mark(start), get_buffer()->create_mark(end)); - - get_buffer()->apply_tag_by_name(diagnostic_tag_name+"_underline", start, end); auto iter=get_buffer()->get_insert()->get_iter(); if(iter.ends_line()) { auto next_iter=iter; if(next_iter.forward_char()) - get_buffer()->remove_tag_by_name(diagnostic_tag_name+"_underline", iter, next_iter); + get_buffer()->remove_tag_by_name(severity_tag_name+"_underline", iter, next_iter); } } } @@ -939,14 +909,7 @@ void Source::LanguageProtocolView::update_diagnostics(std::vectorget_line_count()-1); - if(line<0) - line=0; - auto iter=get_iter_at_line_end(line); - pos=std::min(pos, iter.get_line_offset()); - if(pos<0) - pos=0; - return get_buffer()->get_iter_at_line_offset(line, pos); + return get_iter_at_line_offset(line, pos); } void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rectangle) { diff --git a/src/source_language_protocol.h b/src/source_language_protocol.h index 0a9a172..8b092af 100644 --- a/src/source_language_protocol.h +++ b/src/source_language_protocol.h @@ -110,8 +110,6 @@ namespace Source { void escape_text(std::string &text); void unescape_text(std::string &text); - - std::set diagnostic_offsets; Glib::RefPtr similar_symbol_tag; sigc::connection delayed_tag_similar_symbols_connection;