diff --git a/src/source.cc b/src/source.cc index 0bc0913..5ff6040 100644 --- a/src/source.cc +++ b/src/source.cc @@ -212,15 +212,6 @@ Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr< // } // } } - - if(language_id == "chdr" || language_id == "cpphdr" || language_id == "c" || - language_id == "cpp" || language_id == "objc" || language_id == "java" || - language_id == "js" || language_id == "ts" || language_id == "proto" || - language_id == "c-sharp" || language_id == "html" || language_id == "cuda" || - language_id == "php" || language_id == "rust" || language_id == "swift" || - language_id == "go" || language_id == "scala" || language_id == "opencl" || - language_id == "json" || language_id == "css") - is_bracket_language = true; } setup_tooltip_and_dialog_events(); @@ -1250,13 +1241,13 @@ void Source::View::hide_dialogs() { Gtk::TextIter Source::View::find_non_whitespace_code_iter_backward(Gtk::TextIter iter) { if(iter.starts_line()) return iter; - while(!iter.starts_line() && (is_comment_iter(iter) || *iter == ' ' || *iter == '\t' || iter.ends_line()) && iter.backward_char()) { + while(!iter.starts_line() && (!is_code_iter(iter) || *iter == ' ' || *iter == '\t' || iter.ends_line()) && iter.backward_char()) { } return iter; } Gtk::TextIter Source::View::get_start_of_expression(Gtk::TextIter iter) { - while(!iter.starts_line() && (*iter == ' ' || *iter == '\t' || iter.ends_line() || !is_code_iter(iter) || is_comment_iter(iter)) && iter.backward_char()) { + while(!iter.starts_line() && (*iter == ' ' || *iter == '\t' || iter.ends_line() || !is_code_iter(iter)) && iter.backward_char()) { } if(iter.starts_line()) @@ -1323,7 +1314,7 @@ Gtk::TextIter Source::View::get_start_of_expression(Gtk::TextIter iter) { // Handle ',', ':', or operators that can be used between two lines, on previous line: auto previous_iter = iter; previous_iter.backward_char(); - while(!previous_iter.starts_line() && (*previous_iter == ' ' || previous_iter.ends_line() || is_comment_iter(previous_iter)) && previous_iter.backward_char()) { + while(!previous_iter.starts_line() && (*previous_iter == ' ' || previous_iter.ends_line() || !is_code_iter(previous_iter)) && previous_iter.backward_char()) { } if(previous_iter.starts_line()) return iter; @@ -1462,9 +1453,6 @@ long Source::View::symbol_count(Gtk::TextIter iter, unsigned int positive_char, } long curly_count = 0; - bool check_if_next_iter_is_code_iter = false; - if(positive_char == '\'' || negative_char == '\'' || positive_char == '"' || negative_char == '"') - check_if_next_iter_is_code_iter = true; do { if(*iter == positive_char && is_code_iter(iter)) @@ -1478,14 +1466,6 @@ long Source::View::symbol_count(Gtk::TextIter iter, unsigned int positive_char, } else if(*iter == '}' && is_code_iter(iter)) curly_count--; - else if(check_if_next_iter_is_code_iter) { - auto next_iter = iter; - next_iter.forward_char(); - if(*iter == positive_char && is_code_iter(next_iter)) - symbol_count++; - else if(*iter == negative_char && is_code_iter(next_iter)) - symbol_count--; - } } while(iter.backward_char()); iter = iter_stored; @@ -1505,14 +1485,6 @@ long Source::View::symbol_count(Gtk::TextIter iter, unsigned int positive_char, break; curly_count--; } - else if(check_if_next_iter_is_code_iter) { - auto next_iter = iter; - next_iter.forward_char(); - if(*iter == positive_char && is_code_iter(next_iter)) - symbol_count++; - else if(*iter == negative_char && is_code_iter(next_iter)) - symbol_count--; - } } while(iter.forward_char()); return symbol_count; @@ -2536,37 +2508,6 @@ bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *key) { return false; }; - // Move right when clicking ' before a ' or when clicking " before a " - if(((key->keyval == GDK_KEY_apostrophe && *iter == '\'') || - (key->keyval == GDK_KEY_quotedbl && *iter == '\"')) && is_code_iter(next_iter)) { - bool perform_move = false; - if(*previous_iter != '\\') - perform_move = true; - else { - auto it = previous_iter; - long backslash_count = 1; - while(it.backward_char() && *it == '\\') { - ++backslash_count; - } - if(backslash_count % 2 == 0) - perform_move = true; - } - if(perform_move) { - get_buffer()->place_cursor(next_iter); - scroll_to(get_buffer()->get_insert()); - return true; - } - } - // When to delete '' or "" - else if(key->keyval == GDK_KEY_BackSpace) { - if(((*previous_iter == '\'' && *iter == '\'') || - (*previous_iter == '"' && *iter == '"')) && is_code_iter(previous_iter)) { - get_buffer()->erase(previous_iter, next_iter); - scroll_to(get_buffer()->get_insert()); - return true; - } - } - if(is_code_iter(iter)) { // Insert () if(key->keyval == GDK_KEY_parenleft && allow_insertion(iter)) { @@ -2590,7 +2531,7 @@ bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *key) { return false; } } - // Move left on ] in [] + // Move right on ] in [] else if(key->keyval == GDK_KEY_bracketright) { if(*iter == ']' && symbol_count(iter, '[', ']') <= 0) { iter.forward_char(); @@ -2604,7 +2545,7 @@ bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *key) { auto start_iter = get_start_of_expression(iter); // Do not add } if { is at end of line and next line has a higher indentation auto test_iter = iter; - while(!test_iter.ends_line() && (*test_iter == ' ' || *test_iter == '\t' || !is_code_iter(test_iter) || is_comment_iter(test_iter)) && test_iter.forward_char()) { + while(!test_iter.ends_line() && (*test_iter == ' ' || *test_iter == '\t' || !is_code_iter(test_iter)) && test_iter.forward_char()) { } if(test_iter.ends_line()) { if(iter.get_line() + 1 < get_buffer()->get_line_count() && *start_iter != '(' && *start_iter != '[' && *start_iter != '{') { @@ -2648,7 +2589,7 @@ bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *key) { } return false; } - // Move left on } in {} + // Move right on } in {} else if(key->keyval == GDK_KEY_braceright) { if(*iter == '}' && symbol_count(iter, '{', '}') <= 0) { iter.forward_char(); @@ -2675,6 +2616,12 @@ bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *key) { scroll_to(get_buffer()->get_insert()); return true; } + // Move right on last ' in '', or last " in "" + else if(((key->keyval == GDK_KEY_apostrophe && *iter == '\'') || (key->keyval == GDK_KEY_quotedbl && *iter == '\"')) && is_spellcheck_iter(iter)) { + get_buffer()->place_cursor(next_iter); + scroll_to(get_buffer()->get_insert()); + return true; + } // Insert ; at the end of line, if iter is at the last ) else if(key->keyval == GDK_KEY_semicolon) { if(*iter == ')' && symbol_count(iter, '(', ')') <= 0 && next_iter.ends_line()) { @@ -2719,6 +2666,14 @@ bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *key) { scroll_to(get_buffer()->get_insert()); return true; } + // Delete '' or "" + else if(key->keyval == GDK_KEY_BackSpace) { + if((*previous_iter == '\'' && *iter == '\'') || (*previous_iter == '"' && *iter == '"')) { + get_buffer()->erase(previous_iter, next_iter); + scroll_to(get_buffer()->get_insert()); + return true; + } + } } } @@ -3214,9 +3169,6 @@ Source::GenericView::GenericView(const boost::filesystem::path &file_path, const configure(); spellcheck_all = true; - if(language) - get_source_buffer()->set_language(language); - auto completion = get_completion(); completion->property_show_headers() = false; completion->property_show_icons() = false; diff --git a/src/source.h b/src/source.h index 6935969..ea42d0d 100644 --- a/src/source.h +++ b/src/source.h @@ -158,7 +158,6 @@ namespace Source { sigc::connection renderer_activate_connection; - bool is_bracket_language = false; bool use_fixed_continuation_indenting = true; bool is_cpp = false; guint previous_non_modifier_keyval = 0; diff --git a/src/source_base.cc b/src/source_base.cc index f84c77c..e32fd35 100644 --- a/src/source_base.cc +++ b/src/source_base.cc @@ -16,6 +16,19 @@ Source::BaseView::BaseView(const boost::filesystem::path &file_path, const Glib: }); monitor_file(); + + if(language) { + get_source_buffer()->set_language(language); + auto language_id = language->get_id(); + if(language_id == "chdr" || language_id == "cpphdr" || language_id == "c" || + language_id == "cpp" || language_id == "objc" || language_id == "java" || + language_id == "js" || language_id == "ts" || language_id == "proto" || + language_id == "c-sharp" || language_id == "html" || language_id == "cuda" || + language_id == "php" || language_id == "rust" || language_id == "swift" || + language_id == "go" || language_id == "scala" || language_id == "opencl" || + language_id == "json" || language_id == "css") + is_bracket_language = true; + } } Source::BaseView::~BaseView() { diff --git a/src/source_base.h b/src/source_base.h index 1cc4e94..fa09221 100644 --- a/src/source_base.h +++ b/src/source_base.h @@ -52,6 +52,8 @@ namespace Source { void monitor_file(); void check_last_write_time(std::time_t last_write_time_ = static_cast(-1)); + bool is_bracket_language = false; + /// 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); diff --git a/src/source_spellcheck.cc b/src/source_spellcheck.cc index 8dbac97..c23b491 100644 --- a/src/source_spellcheck.cc +++ b/src/source_spellcheck.cc @@ -46,14 +46,14 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, return; } - if(!is_code_iter(iter)) { + if(is_spellcheck_iter(iter)) { if(last_keyval == GDK_KEY_Return || last_keyval == GDK_KEY_KP_Enter) { auto previous_line_iter = iter; while(previous_line_iter.backward_char() && !previous_line_iter.ends_line()) { } if(previous_line_iter.backward_char()) { get_buffer()->remove_tag(spellcheck_error_tag, previous_line_iter, iter); - if(!is_code_iter(previous_line_iter)) { + if(is_spellcheck_iter(previous_line_iter)) { auto word = get_word(previous_line_iter); spellcheck_word(word.first, word.second); } @@ -283,7 +283,7 @@ void Source::SpellCheckView::spellcheck() { } } else { - bool spell_check = !is_code_iter(iter); + bool spell_check = is_spellcheck_iter(iter); if(spell_check) begin_spellcheck_iter = iter; while(iter != get_buffer()->end()) { @@ -334,16 +334,16 @@ void Source::SpellCheckView::goto_next_spellcheck_error() { Info::get().print("No spelling errors found in current buffer"); } -bool Source::SpellCheckView::is_code_iter(const Gtk::TextIter &iter) { +bool Source::SpellCheckView::is_spellcheck_iter(const Gtk::TextIter &iter) { if(*iter == '\'') { auto previous_iter = iter; if(!iter.starts_line() && previous_iter.backward_char() && *previous_iter == '\'') - return false; + return true; } if(spellcheck_all) { if(no_spell_check_tag) { if(iter.has_tag(no_spell_check_tag) || iter.begins_tag(no_spell_check_tag) || iter.ends_tag(no_spell_check_tag)) - return true; + return false; // workaround for gtksourceview bug if(iter.ends_line()) { auto previous_iter = iter; @@ -352,7 +352,7 @@ bool Source::SpellCheckView::is_code_iter(const Gtk::TextIter &iter) { auto next_iter = iter; next_iter.forward_char(); if(next_iter.begins_tag(no_spell_check_tag) || next_iter.is_end()) - return true; + return false; } } } @@ -360,14 +360,14 @@ bool Source::SpellCheckView::is_code_iter(const Gtk::TextIter &iter) { if(*iter == '\'' || *iter == '"') { auto previous_iter = iter; if(previous_iter.backward_char() && *previous_iter != '\'' && *previous_iter != '\"' && previous_iter.ends_tag(no_spell_check_tag)) - return true; + return false; } } - return false; + return true; } if(comment_tag) { if(iter.has_tag(comment_tag) && !iter.begins_tag(comment_tag)) - return false; + return true; //Exception at the end of /**/ else if(iter.ends_tag(comment_tag)) { auto previous_iter = iter; @@ -379,32 +379,15 @@ bool Source::SpellCheckView::is_code_iter(const Gtk::TextIter &iter) { } auto next_iter = it; if(it.begins_tag(comment_tag) && next_iter.forward_char() && *it == '/' && *next_iter == '*' && previous_iter != it) - return true; + return false; } } - return false; + return true; } } if(string_tag) { - if(iter.has_tag(string_tag)) { - // When ending an open ''-string with ', the last '-iter is not correctly marked as end iter for string_tag - // For instance 'test, when inserting ' at end, would lead to spellcheck error of test' - if(*iter == '\'') { - long backslash_count = 0; - auto it = iter; - while(it.backward_char() && *it == '\\') - ++backslash_count; - if(backslash_count % 2 == 0) { - auto it = iter; - while(!it.begins_tag(string_tag) && it.backward_to_tag_toggle(string_tag)) { - } - if(it.begins_tag(string_tag) && *it == '\'' && iter != it) - return true; - } - } - if(!iter.begins_tag(string_tag)) - return false; - } + if(iter.has_tag(string_tag) && !iter.begins_tag(string_tag)) + return true; // If iter is at the end of string_tag, with exception of after " and ' else if(iter.ends_tag(string_tag)) { auto previous_iter = iter; @@ -419,32 +402,62 @@ bool Source::SpellCheckView::is_code_iter(const Gtk::TextIter &iter) { while(!it.begins_tag(string_tag) && it.backward_to_tag_toggle(string_tag)) { } if(it.begins_tag(string_tag) && *previous_iter == *it && previous_iter != it) - return true; + return false; } } - return false; + return true; } } } - return true; + return false; } -bool Source::SpellCheckView::is_comment_iter(const Gtk::TextIter &iter) { - if(!comment_tag) - return false; - if(iter.has_tag(comment_tag)) - return true; +bool Source::SpellCheckView::is_code_iter(const Gtk::TextIter &iter) { + // Returns true, for instance for C++, if iter is at either characters of // or /* + const auto is_comment_iter = [this](const Gtk::TextIter &iter) { + if(!comment_tag) + return false; + if(iter.has_tag(comment_tag)) + return true; #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION >= 20) - if(iter.starts_tag(comment_tag)) - return true; + if(iter.starts_tag(comment_tag)) + return true; #else - if(*iter == '/') { + if(*iter == '/') { + auto next_iter = iter; + if(!next_iter.ends_line() && next_iter.forward_char() && next_iter.has_tag(comment_tag)) + return true; + } +#endif + return false; + }; + + if(is_comment_iter(iter)) + return false; + + bool is_code_iter = !is_spellcheck_iter(iter); + + // Treat closing " or ' as code iters + if(!is_code_iter && (*iter == '\'' || *iter == '"')) { + if(comment_tag && iter.ends_tag(comment_tag)) // ' or " at end of comments are not code iters + return false; auto next_iter = iter; - if(!next_iter.ends_line() && next_iter.forward_char() && next_iter.has_tag(comment_tag)) - return true; + next_iter.forward_char(); + return !is_spellcheck_iter(next_iter); } -#endif - return false; + + if(is_bracket_language) + return is_code_iter; + + // Non-bracket languages can have code iters inside (), [] and {}, while non-code iters outside of these brackets + // Do not threat these closing code brackets as code iters + if(is_code_iter && (*iter == ')' || *iter == ']' || *iter == '}')) { + auto next_iter = iter; + next_iter.forward_char(); + return !is_spellcheck_iter(next_iter); + } + + return is_code_iter; } bool Source::SpellCheckView::is_word_iter(const Gtk::TextIter &iter) { @@ -456,15 +469,8 @@ bool Source::SpellCheckView::is_word_iter(const Gtk::TextIter &iter) { return false; if(((*iter >= 'A' && *iter <= 'Z') || (*iter >= 'a' && *iter <= 'z') || *iter >= 128)) return true; - if(*iter == '\'') { - if(is_code_iter(iter)) - return false; - auto next_iter = iter; - if(next_iter.forward_char() && is_code_iter(next_iter) && - !(comment_tag && iter.ends_tag(comment_tag))) // additional check for end of line comment - return false; - return true; - } + if(*iter == '\'') + return !is_code_iter(iter); return false; } diff --git a/src/source_spellcheck.h b/src/source_spellcheck.h index 11459d6..5d0524f 100644 --- a/src/source_spellcheck.h +++ b/src/source_spellcheck.h @@ -16,9 +16,13 @@ namespace Source { void goto_next_spellcheck_error(); protected: + /// For instance, the iter before a closing " is a spellcheck iter, while an opening " is not a spellcheck iter. + /// Otherwise, strings and comments should return true. + bool is_spellcheck_iter(const Gtk::TextIter &iter); + /// For instance, both opening and closing " are code iters. + /// None of the comment characters, for instance //, are code iters. + /// Otherwise, strings and comments should return false. bool is_code_iter(const Gtk::TextIter &iter); - /// Returns true, for instance for C++, if iter is at either characters of // or /* - bool is_comment_iter(const Gtk::TextIter &iter); bool spellcheck_all = false; guint last_keyval = 0; diff --git a/tests/source_key_test.cc b/tests/source_key_test.cc index 782c176..b79d0db 100644 --- a/tests/source_key_test.cc +++ b/tests/source_key_test.cc @@ -22,7 +22,6 @@ int main() { auto language = language_manager->get_language("cpp"); Source::View view(source_file, language); view.get_source_buffer()->set_highlight_syntax(true); - view.get_source_buffer()->set_language(language); view.set_tab_char_and_size(' ', 2); auto buffer = view.get_buffer(); event.keyval = GDK_KEY_Return; @@ -2004,7 +2003,6 @@ int main() { auto language = language_manager->get_language("python"); Source::View view(source_file, language); view.get_source_buffer()->set_highlight_syntax(true); - view.get_source_buffer()->set_language(language); view.set_tab_char_and_size(' ', 2); auto buffer = view.get_buffer(); event.keyval = GDK_KEY_Return; @@ -2022,7 +2020,6 @@ int main() { auto language = language_manager->get_language("js"); Source::View view(source_file, language); view.get_source_buffer()->set_highlight_syntax(true); - view.get_source_buffer()->set_language(language); view.set_tab_char_and_size(' ', 2); auto buffer = view.get_buffer(); event.keyval = GDK_KEY_Return; @@ -2274,4 +2271,41 @@ int main() { g_assert(buffer->get_insert()->get_iter() == buffer->end()); } } + { + auto language = language_manager->get_language("markdown"); + Source::View view(source_file, language); + view.spellcheck_all = true; + view.get_source_buffer()->set_highlight_syntax(true); + view.set_tab_char_and_size(' ', 2); + auto buffer = view.get_buffer(); + event.keyval = GDK_KEY_Return; + { + buffer->set_text("\n" + " * [test](https://test.org)\n"); + while(Gtk::Main::events_pending()) + Gtk::Main::iteration(false); + auto iter = buffer->get_iter_at_line(1); + iter.forward_to_line_end(); + buffer->place_cursor(iter); + view.on_key_press_event(&event); + g_assert(buffer->get_text() == "\n" + " * [test](https://test.org)\n" + " \n"); + iter = buffer->get_iter_at_line(2); + iter.forward_to_line_end(); + g_assert(buffer->get_insert()->get_iter() == iter); + } + { + buffer->set_text("\n" + " * [test](https://test.org)"); + while(Gtk::Main::events_pending()) + Gtk::Main::iteration(false); + view.on_key_press_event(&event); + g_assert(buffer->get_text() == "\n" + " * [test](https://test.org)\n" + " "); + auto iter = buffer->end(); + g_assert(buffer->get_insert()->get_iter() == iter); + } + } }