diff --git a/src/files.h b/src/files.h index 5c9b436..bdf6a42 100644 --- a/src/files.h +++ b/src/files.h @@ -1,6 +1,6 @@ #include -#define JUCI_VERSION "0.9.2" +#define JUCI_VERSION "0.9.3" const std::string configjson = "{\n" @@ -74,6 +74,7 @@ const std::string configjson = " \"source_goto_method\": \"m\",\n" " \"source_rename\": \"r\",\n" " \"source_goto_next_diagnostic\": \"e\",\n" +" \"source_apply_fix_its\": \"space\",\n" " \"compile_and_run\": \"Return\",\n" " \"compile\": \"Return\",\n" " \"run_command\": \"Return\",\n" diff --git a/src/menu.cc b/src/menu.cc index 5ae64be..f6590a5 100644 --- a/src/menu.cc +++ b/src/menu.cc @@ -59,6 +59,7 @@ Menu::Menu() { " \n" " \n" " \n" + " \n" " \n" " \n" " \n" diff --git a/src/source.cc b/src/source.cc index 8e5f7c7..484f00a 100644 --- a/src/source.cc +++ b/src/source.cc @@ -42,6 +42,38 @@ Glib::RefPtr Source::guess_language(const boost::filesystem::path return language; } +Source::FixIt::FixIt(const std::string &source, const std::pair &offsets) : source(source), offsets(offsets) { + if(source.size()==0) + type=Type::ERASE; + else { + if(this->offsets.first==this->offsets.second) + type=Type::INSERT; + else + type=Type::REPLACE; + } +} + +std::string Source::FixIt::string() { + std::string text; + if(type==Type::INSERT) { + text+="Insert "+source+" at "; + text+=std::to_string(offsets.first.line)+":"+std::to_string(offsets.first.offset); + } + else if(type==Type::REPLACE) { + text+="Replace "; + text+=std::to_string(offsets.first.line)+":"+std::to_string(offsets.first.offset)+" - "; + text+=std::to_string(offsets.second.line)+":"+std::to_string(offsets.second.offset); + text+=" with "+source; + } + else { + text+="Erase "; + text+=std::to_string(offsets.first.line)+":"+std::to_string(offsets.first.offset)+" - "; + text+=std::to_string(offsets.second.line)+":"+std::to_string(offsets.second.offset); + } + + return text; +} + ////////////// //// View //// ////////////// @@ -1504,7 +1536,7 @@ std::vector Source::ClangViewParse::get_compilation_commands() { } void Source::ClangViewParse::update_syntax() { - std::vector ranges; + std::vector ranges; for (auto &token : *clang_tokens) { //if(token.get_kind()==0) // PunctuationToken //ranges.emplace_back(token.offsets, (int) token.get_cursor().get_kind()); @@ -1544,11 +1576,13 @@ void Source::ClangViewParse::update_syntax() { void Source::ClangViewParse::update_diagnostics() { diagnostic_offsets.clear(); diagnostic_tooltips.clear(); + 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()); auto diagnostics=clang_tu->get_diagnostics(); - size_t warnings=0; - size_t errors=0; + size_t num_warnings=0; + size_t num_errors=0; + size_t num_fix_its=0; for(auto &diagnostic: diagnostics) { if(diagnostic.path==file_path.string()) { auto start_line=get_line(diagnostic.offsets.first.line-1); //index is sometimes off the line @@ -1573,19 +1607,50 @@ void Source::ClangViewParse::update_diagnostics() { std::string diagnostic_tag_name; if(diagnostic.severity<=CXDiagnostic_Warning) { diagnostic_tag_name="def:warning"; - warnings++; + num_warnings++; } else { diagnostic_tag_name="def:error"; - errors++; + num_errors++; } auto spelling=diagnostic.spelling; auto severity_spelling=diagnostic.severity_spelling; - auto create_tooltip_buffer=[this, spelling, severity_spelling, diagnostic_tag_name]() { + + std::string fix_its_string; + unsigned fix_its_count=0; + for(auto &fix_it: diagnostic.fix_its) { + //Convert line index to line offset for correct output: + auto clang_offsets=fix_it.offsets; + std::pair offsets; + offsets.first.line=clang_offsets.first.line; + offsets.second.line=clang_offsets.second.line; + auto iter=get_buffer()->get_iter_at_line_index(clang_offsets.first.line-1, clang_offsets.first.index-1); + offsets.first.offset=iter.get_line_offset()+1; + iter=get_buffer()->get_iter_at_line_index(clang_offsets.second.line-1, clang_offsets.second.index-1); + offsets.second.offset=iter.get_line_offset()+1; + + fix_its.emplace_back(fix_it.source, offsets); + + if(fix_its_string.size()>0) + fix_its_string+='\n'; + fix_its_string+=fix_its.back().string(); + fix_its_count++; + num_fix_its++; + } + + if(fix_its_count==1) + fix_its_string.insert(0, "Fix-it:\n"); + 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)); @@ -1600,16 +1665,23 @@ void Source::ClangViewParse::update_diagnostics() { } } std::string diagnostic_info; - if(warnings>0) { - diagnostic_info+=std::to_string(warnings)+" warning"; - if(warnings>1) + if(num_warnings>0) { + diagnostic_info+=std::to_string(num_warnings)+" warning"; + if(num_warnings>1) + diagnostic_info+='s'; + } + if(num_errors>0) { + if(num_warnings>0) + diagnostic_info+=", "; + diagnostic_info+=std::to_string(num_errors)+" error"; + if(num_errors>1) diagnostic_info+='s'; } - if(errors>0) { - if(warnings>0) + if(num_fix_its>0) { + if(num_warnings>0 || num_errors>0) diagnostic_info+=", "; - diagnostic_info+=std::to_string(errors)+" error"; - if(errors>1) + diagnostic_info+=std::to_string(num_fix_its)+" fix it"; + if(num_fix_its>1) diagnostic_info+='s'; } set_info(" "+diagnostic_info); @@ -1966,7 +2038,7 @@ void Source::ClangViewAutocomplete::autocomplete() { if(!autocomplete_starting) { autocomplete_starting=true; autocomplete_cancel_starting=false; - std::shared_ptr > ac_data=std::make_shared >(); + std::shared_ptr > ac_data=std::make_shared >(); autocomplete_done_connection.disconnect(); autocomplete_done_connection=autocomplete_done.connect([this, ac_data](){ autocomplete_starting=false; @@ -2076,8 +2148,8 @@ void Source::ClangViewAutocomplete::autocomplete() { } } -std::vector Source::ClangViewAutocomplete::get_autocomplete_suggestions(int line_number, int column, std::map& buffer_map) { - std::vector suggestions; +std::vector Source::ClangViewAutocomplete::get_autocomplete_suggestions(int line_number, int column, std::map& buffer_map) { + std::vector suggestions; auto results=clang_tu->get_code_completions(buffer_map, line_number, column); if(results.cx_results==NULL) { parse_thread_stop=true; @@ -2290,6 +2362,37 @@ Source::ClangViewAutocomplete(file_path, project_path, language) { } } }; + + apply_fix_its=[this]() { + std::vector, Glib::RefPtr > > fix_it_marks; + if(source_readable) { + for(auto &fix_it: fix_its) { + auto start_iter=get_buffer()->get_iter_at_line_offset(fix_it.offsets.first.line-1, fix_it.offsets.first.offset-1); + auto end_iter=get_buffer()->get_iter_at_line_offset(fix_it.offsets.second.line-1, fix_it.offsets.second.offset-1); + fix_it_marks.emplace_back(get_buffer()->create_mark(start_iter), get_buffer()->create_mark(end_iter)); + } + size_t c=0; + get_source_buffer()->begin_user_action(); + for(auto &fix_it: fix_its) { + if(fix_it.type==FixIt::Type::INSERT) { + get_buffer()->insert(fix_it_marks[c].first->get_iter(), fix_it.source); + } + if(fix_it.type==FixIt::Type::REPLACE) { + get_buffer()->erase(fix_it_marks[c].first->get_iter(), fix_it_marks[c].second->get_iter()); + get_buffer()->insert(fix_it_marks[c].first->get_iter(), fix_it.source); + } + if(fix_it.type==FixIt::Type::ERASE) { + get_buffer()->erase(fix_it_marks[c].first->get_iter(), fix_it_marks[c].second->get_iter()); + } + c++; + } + for(auto &mark_pair: fix_it_marks) { + get_buffer()->delete_mark(mark_pair.first); + get_buffer()->delete_mark(mark_pair.second); + } + get_source_buffer()->end_user_action(); + } + }; } void Source::ClangViewRefactor::tag_similar_tokens(const Token &token) { diff --git a/src/source.h b/src/source.h index c69f178..47a8adb 100644 --- a/src/source.h +++ b/src/source.h @@ -38,22 +38,6 @@ namespace Source { bool show_line_numbers; std::unordered_map clang_types; }; - - class Range { - public: - Range(std::pair offsets, int kind): - offsets(offsets), kind(kind) {} - std::pair offsets; - int kind; - }; - - class AutoCompleteData { - public: - explicit AutoCompleteData(const std::vector &chunks) : - chunks(chunks) { } - std::vector chunks; - std::string brief_comments; - }; class Token { public: @@ -70,6 +54,31 @@ namespace Source { std::string spelling; std::string usr; }; + + class FixIt { + public: + class Offset { + public: + Offset() {} + Offset(unsigned line, unsigned offset): line(line), offset(offset) {} + bool operator==(const Offset &o) {return (line==o.line && offset==o.offset);} + + unsigned line; + unsigned offset; + }; + + enum class Type {INSERT, REPLACE, ERASE}; + + FixIt(Type type, const std::string &source, const std::pair &offsets): + type(type), source(source), offsets(offsets) {} + FixIt(const std::string &source, const std::pair &offsets); + + std::string string(); + + Type type; + std::string source; + std::pair offsets; + }; class View : public Gsv::View { public: @@ -95,6 +104,7 @@ namespace Source { std::function get_token; std::function rename_similar_tokens; std::function goto_next_diagnostic; + std::function apply_fix_its; std::function on_update_status; std::function on_update_info; @@ -176,6 +186,14 @@ namespace Source { class ClangViewParse : public View { public: + class TokenRange { + public: + TokenRange(std::pair offsets, int kind): + offsets(offsets), kind(kind) {} + std::pair offsets; + int kind; + }; + ClangViewParse(const boost::filesystem::path &file_path, const boost::filesystem::path& project_path, Glib::RefPtr language); ~ClangViewParse(); void configure(); @@ -204,6 +222,7 @@ namespace Source { std::regex no_bracket_no_para_statement_regex; std::set diagnostic_offsets; + std::vector fix_its; private: std::map get_buffer_map() const; void update_syntax(); @@ -224,6 +243,14 @@ namespace Source { class ClangViewAutocomplete : public ClangViewParse { public: + class AutoCompleteData { + public: + explicit AutoCompleteData(const std::vector &chunks) : + chunks(chunks) { } + std::vector chunks; + std::string brief_comments; + }; + ClangViewAutocomplete(const boost::filesystem::path &file_path, const boost::filesystem::path& project_path, Glib::RefPtr language); void async_delete(); bool restart_parse(); @@ -235,7 +262,7 @@ namespace Source { void autocomplete(); std::unique_ptr completion_dialog; bool completion_dialog_shown=false; - std::vector get_autocomplete_suggestions(int line_number, int column, std::map& buffer_map); + std::vector get_autocomplete_suggestions(int line_number, int column, std::map& buffer_map); Glib::Dispatcher autocomplete_done; sigc::connection autocomplete_done_connection; Glib::Dispatcher autocomplete_fail; diff --git a/src/window.cc b/src/window.cc index 364abb4..ce37895 100644 --- a/src/window.cc +++ b/src/window.cc @@ -115,6 +115,9 @@ Window::Window() : box(Gtk::ORIENTATION_VERTICAL), notebook(directories), compil if(auto menu_item=dynamic_cast(menu.ui_manager->get_widget("/MenuBar/SourceMenu/SourceGotoNextDiagnostic"))) menu_item->set_sensitive((bool)notebook.get_current_view()->goto_next_diagnostic); + + if(auto menu_item=dynamic_cast(menu.ui_manager->get_widget("/MenuBar/SourceMenu/SourceApplyFixIts"))) + menu_item->set_sensitive((bool)notebook.get_current_view()->apply_fix_its); directories.select(notebook.get_current_view()->file_path); @@ -300,6 +303,13 @@ void Window::create_menu() { } } }); + menu.action_group->add(Gtk::Action::create("SourceApplyFixIts", "Apply Fix Its"), Gtk::AccelKey(menu.key_map["source_apply_fix_its"]), [this]() { + if(notebook.get_current_page()!=-1) { + if(notebook.get_current_view()->apply_fix_its) { + notebook.get_current_view()->apply_fix_its(); + } + } + }); menu.action_group->add(Gtk::Action::create("ProjectCompileAndRun", "Compile and Run"), Gtk::AccelKey(menu.key_map["compile_and_run"]), [this]() { if(notebook.get_current_page()==-1 || compiling)