diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2539e96..3113903 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -58,6 +58,7 @@ set(project_shared_files project_build.cc source.cc source_clang.cc + source_spellcheck.cc ../libclangmm/src/CodeCompleteResults.cc ../libclangmm/src/CompilationDatabase.cc diff --git a/src/source.cc b/src/source.cc index 0e2b1ca..ae9f1ef 100644 --- a/src/source.cc +++ b/src/source.cc @@ -88,9 +88,7 @@ const REGEX_NS::regex Source::View::bracket_regex("^([ \\t]*).*\\{ *$"); const REGEX_NS::regex Source::View::no_bracket_statement_regex("^([ \\t]*)(if|for|else if|while) *\\(.*[^;}] *$"); const REGEX_NS::regex Source::View::no_bracket_no_para_statement_regex("^([ \\t]*)(else) *$"); -AspellConfig* Source::View::spellcheck_config=NULL; - -Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtr language): file_path(file_path), language(language) { +Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtr language): SpellCheckView(), file_path(file_path), language(language) { get_source_buffer()->begin_not_undoable_action(); last_read_time=std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); if(language) { @@ -125,11 +123,6 @@ Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtrcreate_tag("def:error_underline"); get_buffer()->create_tag("def:note_background"); get_buffer()->create_tag("def:note"); - if(spellcheck_config==NULL) - spellcheck_config=new_aspell_config(); - spellcheck_checker=NULL; - auto tag=get_buffer()->create_tag("spellcheck_error"); - tag->property_underline()=Pango::Underline::UNDERLINE_ERROR; auto mark_attr_debug_breakpoint=Gsv::MarkAttributes::create(); Gdk::RGBA rgba; @@ -146,135 +139,6 @@ Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtrset_background(rgba); set_mark_attributes("debug_stop", mark_attr_debug_stop, 101); - get_buffer()->signal_changed().connect([this](){ - if(spellcheck_checker==NULL) - return; - - delayed_spellcheck_suggestions_connection.disconnect(); - - auto iter=get_buffer()->get_insert()->get_iter(); - bool ends_line=iter.ends_line(); - if(iter.backward_char()) { - auto context_iter=iter; - bool backward_success=true; - if(ends_line || *iter=='/' || *iter=='*') //iter_has_context_class is sadly bugged - backward_success=context_iter.backward_char(); - if(backward_success) { - if(last_keyval==GDK_KEY_BackSpace && !is_word_iter(iter) && iter.forward_char()) {} //backspace fix - if((spellcheck_all && !get_source_buffer()->iter_has_context_class(context_iter, "no-spell-check")) || get_source_buffer()->iter_has_context_class(context_iter, "comment") || get_source_buffer()->iter_has_context_class(context_iter, "string")) { - if(!is_word_iter(iter) || last_keyval==GDK_KEY_Return) { //Might have used space or - to split two words - auto first=iter; - auto second=iter; - if(last_keyval==GDK_KEY_Return) { - while(first && !first.ends_line() && first.backward_char()) {} - if(first.backward_char() && second.forward_char()) { - get_buffer()->remove_tag_by_name("spellcheck_error", first, second); - auto word=spellcheck_get_word(first); - spellcheck_word(word.first, word.second); - word=spellcheck_get_word(second); - spellcheck_word(word.first, word.second); - } - } - else if(first.backward_char() && second.forward_char() && !second.starts_line()) { - get_buffer()->remove_tag_by_name("spellcheck_error", first, second); - auto word=spellcheck_get_word(first); - spellcheck_word(word.first, word.second); - word=spellcheck_get_word(second); - spellcheck_word(word.first, word.second); - } - } - else { - auto word=spellcheck_get_word(iter); - spellcheck_word(word.first, word.second); - } - } - } - } - delayed_spellcheck_error_clear.disconnect(); - delayed_spellcheck_error_clear=Glib::signal_timeout().connect([this]() { - auto iter=get_buffer()->begin(); - Gtk::TextIter begin_no_spellcheck_iter; - if(spellcheck_all) { - bool spell_check=!get_source_buffer()->iter_has_context_class(iter, "no-spell-check"); - if(!spell_check) - begin_no_spellcheck_iter=iter; - while(iter!=get_buffer()->end()) { - if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter, "no-spell-check")) - iter=get_buffer()->end(); - - spell_check=!spell_check; - if(!spell_check) - begin_no_spellcheck_iter=iter; - else - get_buffer()->remove_tag_by_name("spellcheck_error", begin_no_spellcheck_iter, iter); - } - return false; - } - - bool spell_check=get_source_buffer()->iter_has_context_class(iter, "string") || get_source_buffer()->iter_has_context_class(iter, "comment"); - if(!spell_check) - begin_no_spellcheck_iter=iter; - while(iter!=get_buffer()->end()) { - auto iter1=iter; - auto iter2=iter; - if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter1, "string")) - iter1=get_buffer()->end(); - if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter2, "comment")) - iter2=get_buffer()->end(); - - if(iter2remove_tag_by_name("spellcheck_error", begin_no_spellcheck_iter, iter); - } - return false; - }, 1000); - }); - - get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iter, const Glib::RefPtr& mark) { - if(spellcheck_checker==NULL) - return; - - if(mark->get_name()=="insert") { - if(spellcheck_suggestions_dialog) - spellcheck_suggestions_dialog->hide(); - delayed_spellcheck_suggestions_connection.disconnect(); - delayed_spellcheck_suggestions_connection=Glib::signal_timeout().connect([this]() { - auto tags=get_buffer()->get_insert()->get_iter().get_tags(); - bool need_suggestions=false; - for(auto &tag: tags) { - if(tag->property_name()=="spellcheck_error") { - need_suggestions=true; - break; - } - } - if(need_suggestions) { - spellcheck_suggestions_dialog=std::unique_ptr(new SelectionDialog(*this, get_buffer()->create_mark(get_buffer()->get_insert()->get_iter()), false)); - auto word=spellcheck_get_word(get_buffer()->get_insert()->get_iter()); - auto suggestions=spellcheck_get_suggestions(word.first, word.second); - if(suggestions.size()==0) - return false; - for(auto &suggestion: suggestions) - spellcheck_suggestions_dialog->add_row(suggestion); - spellcheck_suggestions_dialog->on_select=[this, word](const std::string& selected, bool hide_window) { - get_buffer()->begin_user_action(); - get_buffer()->erase(word.first, word.second); - get_buffer()->insert(get_buffer()->get_insert()->get_iter(), selected); - get_buffer()->end_user_action(); - delayed_tooltips_connection.disconnect(); - }; - spellcheck_suggestions_dialog->show(); - } - return false; - }, 500); - } - }); - get_buffer()->signal_changed().connect([this](){ set_info(info); }); @@ -438,6 +302,8 @@ bool Source::View::save(const std::vector &views) { } void Source::View::configure() { + SpellCheckView::configure(); + //TODO: Move this to notebook? Might take up too much memory doing this for every tab. auto style_scheme_manager=Gsv::StyleSchemeManager::get_default(); style_scheme_manager->prepend_search_path((Config::get().juci_home_path()/"styles").string()); @@ -516,20 +382,6 @@ void Source::View::configure() { if(style->property_foreground_set()) { note_tag->property_foreground()=style->property_foreground(); } - - if(Config::get().source.spellcheck_language.size()>0) { - aspell_config_replace(spellcheck_config, "lang", Config::get().source.spellcheck_language.c_str()); - aspell_config_replace(spellcheck_config, "encoding", "utf-8"); - } - spellcheck_possible_err=new_aspell_speller(spellcheck_config); - if(spellcheck_checker!=NULL) - delete_aspell_speller(spellcheck_checker); - spellcheck_checker=NULL; - if (aspell_error_number(spellcheck_possible_err) != 0) - std::cerr << "Spell check error: " << aspell_error_message(spellcheck_possible_err) << std::endl; - else - spellcheck_checker = to_aspell_speller(spellcheck_possible_err); - get_buffer()->remove_tag_by_name("spellcheck_error", get_buffer()->begin(), get_buffer()->end()); } void Source::View::set_tooltip_and_dialog_events() { @@ -585,8 +437,6 @@ void Source::View::set_tooltip_and_dialog_events() { type_tooltips.hide(); diagnostic_tooltips.hide(); - if(spellcheck_suggestions_dialog) - spellcheck_suggestions_dialog->hide(); if(autocomplete_dialog) autocomplete_dialog->hide(); if(selection_dialog) @@ -604,13 +454,11 @@ void Source::View::set_tooltip_and_dialog_events() { signal_focus_out_event().connect([this](GdkEventFocus* event) { hide_tooltips(); - delayed_spellcheck_suggestions_connection.disconnect(); return false; }); signal_leave_notify_event().connect([this](GdkEventCrossing*) { delayed_tooltips_connection.disconnect(); - delayed_spellcheck_suggestions_connection.disconnect(); return false; }); } @@ -626,11 +474,6 @@ Source::View::~View() { g_clear_object(&search_settings); delayed_tooltips_connection.disconnect(); - delayed_spellcheck_suggestions_connection.disconnect(); - delayed_spellcheck_error_clear.disconnect(); - - if(spellcheck_checker!=NULL) - delete_aspell_speller(spellcheck_checker); } void Source::View::search_highlight(const std::string &text, bool case_sensitive, bool regex) { @@ -854,9 +697,7 @@ void Source::View::hide_tooltips() { } void Source::View::hide_dialogs() { - delayed_spellcheck_suggestions_connection.disconnect(); - if(spellcheck_suggestions_dialog) - spellcheck_suggestions_dialog->hide(); + SpellCheckView::hide_dialogs(); if(selection_dialog) selection_dialog->hide(); if(autocomplete_dialog) @@ -877,89 +718,6 @@ void Source::View::set_info(const std::string &info) { on_update_info(this, positions+" "+info); } -void Source::View::spellcheck(const Gtk::TextIter& start, const Gtk::TextIter& end) { - if(spellcheck_checker==NULL) - return; - auto iter=start; - while(iter && iterbegin(); - Gtk::TextIter begin_spellcheck_iter; - if(spellcheck_all) { - bool spell_check=!get_source_buffer()->iter_has_context_class(iter, "no-spell-check"); - if(spell_check) - begin_spellcheck_iter=iter; - while(iter!=get_buffer()->end()) { - if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter, "no-spell-check")) - iter=get_buffer()->end(); - - spell_check=!spell_check; - if(spell_check) - begin_spellcheck_iter=iter; - else - spellcheck(begin_spellcheck_iter, iter); - } - } - else { - bool spell_check=get_source_buffer()->iter_has_context_class(iter, "string") || get_source_buffer()->iter_has_context_class(iter, "comment"); - if(spell_check) - begin_spellcheck_iter=iter; - while(iter!=get_buffer()->end()) { - auto iter1=iter; - auto iter2=iter; - if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter1, "string")) - iter1=get_buffer()->end(); - if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter2, "comment")) - iter2=get_buffer()->end(); - - if(iter2remove_tag_by_name("spellcheck_error", get_buffer()->begin(), get_buffer()->end()); -} - -void Source::View::goto_next_spellcheck_error() { - auto iter=get_buffer()->get_insert()->get_iter(); - auto insert_iter=iter; - bool wrapped=false; - iter.forward_char(); - while(iter && (!wrapped || iterproperty_name()=="spellcheck_error") { - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); - return; - } - } - iter.forward_char(); - if(!wrapped && iter==get_buffer()->end()) { - iter=get_buffer()->begin(); - wrapped=true; - } - } -} - std::string Source::View::get_line(const Gtk::TextIter &iter) { auto line_start_it = get_buffer()->get_iter_at_line(iter.get_line()); auto line_end_it = iter; @@ -1157,10 +915,6 @@ std::string Source::View::get_token(Gtk::TextIter iter) { } bool Source::View::on_key_press_event(GdkEventKey* key) { - if(spellcheck_suggestions_dialog && spellcheck_suggestions_dialog->shown) { - if(spellcheck_suggestions_dialog->on_key_press(key)) - return true; - } if(selection_dialog && selection_dialog->shown) { if(selection_dialog->on_key_press(key)) return true; @@ -1822,65 +1576,6 @@ std::pair Source::View::find_tab_char_and_size() { return {found_tab_char, found_tab_size}; } -bool Source::View::is_word_iter(const Gtk::TextIter& iter) { - return ((*iter>='A' && *iter<='Z') || (*iter>='a' && *iter<='z') || *iter=='\'' || *iter>=128); -} - -std::pair Source::View::spellcheck_get_word(Gtk::TextIter iter) { - auto start=iter; - auto end=iter; - - while(is_word_iter(iter)) { - start=iter; - if(!iter.backward_char()) - break; - } - while(is_word_iter(end)) { - if(!end.forward_char()) - break; - } - - return {start, end}; -} - -void Source::View::spellcheck_word(const Gtk::TextIter& start, const Gtk::TextIter& end) { - auto spellcheck_start=start; - auto spellcheck_end=end; - if((spellcheck_end.get_offset()-spellcheck_start.get_offset())>=2) { - auto last_char=spellcheck_end; - last_char.backward_char(); - if(*spellcheck_start=='\'' && *last_char=='\'') { - spellcheck_start.forward_char(); - spellcheck_end.backward_char(); - } - } - - auto word=get_buffer()->get_text(spellcheck_start, spellcheck_end); - if(word.size()>0) { - auto correct = aspell_speller_check(spellcheck_checker, word.data(), word.bytes()); - if(correct==0) - get_buffer()->apply_tag_by_name("spellcheck_error", start, end); - else - get_buffer()->remove_tag_by_name("spellcheck_error", start, end); - } -} - -std::vector Source::View::spellcheck_get_suggestions(const Gtk::TextIter& start, const Gtk::TextIter& end) { - auto word_with_error=get_buffer()->get_text(start, end); - - const AspellWordList *suggestions = aspell_speller_suggest(spellcheck_checker, word_with_error.data(), word_with_error.bytes()); - AspellStringEnumeration *elements = aspell_word_list_elements(suggestions); - - std::vector words; - const char *word; - while ((word = aspell_string_enumeration_next(elements))!= NULL) { - words.emplace_back(word); - } - delete_aspell_string_enumeration(elements); - - return words; -} - ///////////////////// //// GenericView //// diff --git a/src/source.h b/src/source.h index 1941220..53f00a5 100644 --- a/src/source.h +++ b/src/source.h @@ -1,9 +1,8 @@ #ifndef JUCI_SOURCE_H_ #define JUCI_SOURCE_H_ -#include +#include "source_spellcheck.h" #include #include -#include #include #include #include @@ -20,7 +19,6 @@ #define REGEX_NS boost #endif -#include "selectiondialog.h" #include "tooltips.h" namespace Source { @@ -51,13 +49,13 @@ namespace Source { std::pair offsets; }; - class View : public Gsv::View { + class View : public SpellCheckView { public: View(const boost::filesystem::path &file_path, Glib::RefPtr language); ~View(); virtual bool save(const std::vector &views); - virtual void configure(); + void configure() override; void search_highlight(const std::string &text, bool case_sensitive, bool regex); std::function update_search_occurrences; @@ -92,7 +90,7 @@ namespace Source { void place_cursor_at_line_index(int line, int index); void hide_tooltips(); - void hide_dialogs(); + void hide_dialogs() override; std::function on_update_status; std::function on_update_info; @@ -101,10 +99,6 @@ namespace Source { std::string status; std::string info; - void spellcheck(); - void remove_spellcheck_errors(); - void goto_next_spellcheck_error(); - void set_tab_char_and_size(char tab_char, unsigned tab_size); std::pair get_tab_char_and_size() {return {tab_char, tab_size};} @@ -159,10 +153,7 @@ namespace Source { char tab_char; std::string tab; - bool spellcheck_all=false; - std::unique_ptr spellcheck_suggestions_dialog; guint previous_non_modifier_keyval=0; - guint last_keyval=0; private: void cleanup_whitespace_characters(); Gsv::DrawSpacesFlags parse_show_whitespace_characters(const std::string &text); @@ -170,18 +161,6 @@ namespace Source { GtkSourceSearchContext *search_context; GtkSourceSearchSettings *search_settings; static void search_occurrences_updated(GtkWidget* widget, GParamSpec* property, gpointer data); - - static AspellConfig* spellcheck_config; - AspellCanHaveError *spellcheck_possible_err; - AspellSpeller *spellcheck_checker; - bool is_word_iter(const Gtk::TextIter& iter); - std::pair spellcheck_get_word(Gtk::TextIter iter); - void spellcheck_word(const Gtk::TextIter& start, const Gtk::TextIter& end); - std::vector spellcheck_get_suggestions(const Gtk::TextIter& start, const Gtk::TextIter& end); - sigc::connection delayed_spellcheck_suggestions_connection; - sigc::connection delayed_spellcheck_error_clear; - - void spellcheck(const Gtk::TextIter& start, const Gtk::TextIter& end); }; class GenericView : public View { diff --git a/src/source_spellcheck.cc b/src/source_spellcheck.cc new file mode 100644 index 0000000..64d4bba --- /dev/null +++ b/src/source_spellcheck.cc @@ -0,0 +1,345 @@ +#include "source_spellcheck.h" +#include "config.h" +#include + +AspellConfig* Source::SpellCheckView::spellcheck_config=NULL; + +Source::SpellCheckView::SpellCheckView() : Gsv::View() { + if(spellcheck_config==NULL) + spellcheck_config=new_aspell_config(); + spellcheck_checker=NULL; + auto tag=get_buffer()->create_tag("spellcheck_error"); + tag->property_underline()=Pango::Underline::UNDERLINE_ERROR; + + signal_key_press_event().connect([this](GdkEventKey *event) { + if(spellcheck_suggestions_dialog && spellcheck_suggestions_dialog->shown) { + if(spellcheck_suggestions_dialog->on_key_press(event)) + return true; + } + + return false; + }, false); + + //The following signal is added in case SpellCheckView is not subclassed + signal_key_press_event().connect([this](GdkEventKey *event) { + last_keyval=event->keyval; + return false; + }); + + get_buffer()->signal_changed().connect([this](){ + if(spellcheck_checker==NULL) + return; + + delayed_spellcheck_suggestions_connection.disconnect(); + + auto iter=get_buffer()->get_insert()->get_iter(); + bool ends_line=iter.ends_line(); + if(iter.backward_char()) { + auto context_iter=iter; + bool backward_success=true; + if(ends_line || *iter=='/' || *iter=='*') //iter_has_context_class is sadly bugged + backward_success=context_iter.backward_char(); + if(backward_success) { + if(last_keyval==GDK_KEY_BackSpace && !is_word_iter(iter) && iter.forward_char()) {} //backspace fix + if((spellcheck_all && !get_source_buffer()->iter_has_context_class(context_iter, "no-spell-check")) || get_source_buffer()->iter_has_context_class(context_iter, "comment") || get_source_buffer()->iter_has_context_class(context_iter, "string")) { + if(!is_word_iter(iter) || last_keyval==GDK_KEY_Return) { //Might have used space or - to split two words + auto first=iter; + auto second=iter; + if(last_keyval==GDK_KEY_Return) { + while(first && !first.ends_line() && first.backward_char()) {} + if(first.backward_char() && second.forward_char()) { + get_buffer()->remove_tag_by_name("spellcheck_error", first, second); + auto word=spellcheck_get_word(first); + spellcheck_word(word.first, word.second); + word=spellcheck_get_word(second); + spellcheck_word(word.first, word.second); + } + } + else if(first.backward_char() && second.forward_char() && !second.starts_line()) { + get_buffer()->remove_tag_by_name("spellcheck_error", first, second); + auto word=spellcheck_get_word(first); + spellcheck_word(word.first, word.second); + word=spellcheck_get_word(second); + spellcheck_word(word.first, word.second); + } + } + else { + auto word=spellcheck_get_word(iter); + spellcheck_word(word.first, word.second); + } + } + } + } + delayed_spellcheck_error_clear.disconnect(); + delayed_spellcheck_error_clear=Glib::signal_timeout().connect([this]() { + auto iter=get_buffer()->begin(); + Gtk::TextIter begin_no_spellcheck_iter; + if(spellcheck_all) { + bool spell_check=!get_source_buffer()->iter_has_context_class(iter, "no-spell-check"); + if(!spell_check) + begin_no_spellcheck_iter=iter; + while(iter!=get_buffer()->end()) { + if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter, "no-spell-check")) + iter=get_buffer()->end(); + + spell_check=!spell_check; + if(!spell_check) + begin_no_spellcheck_iter=iter; + else + get_buffer()->remove_tag_by_name("spellcheck_error", begin_no_spellcheck_iter, iter); + } + return false; + } + + bool spell_check=get_source_buffer()->iter_has_context_class(iter, "string") || get_source_buffer()->iter_has_context_class(iter, "comment"); + if(!spell_check) + begin_no_spellcheck_iter=iter; + while(iter!=get_buffer()->end()) { + auto iter1=iter; + auto iter2=iter; + if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter1, "string")) + iter1=get_buffer()->end(); + if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter2, "comment")) + iter2=get_buffer()->end(); + + if(iter2remove_tag_by_name("spellcheck_error", begin_no_spellcheck_iter, iter); + } + return false; + }, 1000); + }); + + get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iter, const Glib::RefPtr& mark) { + if(spellcheck_checker==NULL) + return; + + if(mark->get_name()=="insert") { + if(spellcheck_suggestions_dialog) + spellcheck_suggestions_dialog->hide(); + delayed_spellcheck_suggestions_connection.disconnect(); + delayed_spellcheck_suggestions_connection=Glib::signal_timeout().connect([this]() { + auto tags=get_buffer()->get_insert()->get_iter().get_tags(); + bool need_suggestions=false; + for(auto &tag: tags) { + if(tag->property_name()=="spellcheck_error") { + need_suggestions=true; + break; + } + } + if(need_suggestions) { + spellcheck_suggestions_dialog=std::unique_ptr(new SelectionDialog(*this, get_buffer()->create_mark(get_buffer()->get_insert()->get_iter()), false)); + auto word=spellcheck_get_word(get_buffer()->get_insert()->get_iter()); + auto suggestions=spellcheck_get_suggestions(word.first, word.second); + if(suggestions.size()==0) + return false; + for(auto &suggestion: suggestions) + spellcheck_suggestions_dialog->add_row(suggestion); + spellcheck_suggestions_dialog->on_select=[this, word](const std::string& selected, bool hide_window) { + get_buffer()->begin_user_action(); + get_buffer()->erase(word.first, word.second); + get_buffer()->insert(get_buffer()->get_insert()->get_iter(), selected); + get_buffer()->end_user_action(); + }; + spellcheck_suggestions_dialog->show(); + } + return false; + }, 500); + } + }); + + get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr& mark) { + if(mark->get_name()=="insert") { + if(spellcheck_suggestions_dialog) + spellcheck_suggestions_dialog->hide(); + } + }); + + signal_focus_out_event().connect([this](GdkEventFocus* event) { + delayed_spellcheck_suggestions_connection.disconnect(); + return false; + }); + + signal_leave_notify_event().connect([this](GdkEventCrossing*) { + delayed_spellcheck_suggestions_connection.disconnect(); + return false; + }); +} + +Source::SpellCheckView::~SpellCheckView() { + delayed_spellcheck_suggestions_connection.disconnect(); + delayed_spellcheck_error_clear.disconnect(); + + if(spellcheck_checker!=NULL) + delete_aspell_speller(spellcheck_checker);//asd +} + +void Source::SpellCheckView::configure() { + if(Config::get().source.spellcheck_language.size()>0) { + aspell_config_replace(spellcheck_config, "lang", Config::get().source.spellcheck_language.c_str()); + aspell_config_replace(spellcheck_config, "encoding", "utf-8"); + } + spellcheck_possible_err=new_aspell_speller(spellcheck_config); + if(spellcheck_checker!=NULL) + delete_aspell_speller(spellcheck_checker); + spellcheck_checker=NULL; + if (aspell_error_number(spellcheck_possible_err) != 0) + std::cerr << "Spell check error: " << aspell_error_message(spellcheck_possible_err) << std::endl; + else + spellcheck_checker = to_aspell_speller(spellcheck_possible_err); + get_buffer()->remove_tag_by_name("spellcheck_error", get_buffer()->begin(), get_buffer()->end()); +} + +void Source::SpellCheckView::hide_dialogs() { + delayed_spellcheck_suggestions_connection.disconnect(); + if(spellcheck_suggestions_dialog) + spellcheck_suggestions_dialog->hide(); +} + +void Source::SpellCheckView::spellcheck(const Gtk::TextIter& start, const Gtk::TextIter& end) { + if(spellcheck_checker==NULL) + return; + auto iter=start; + while(iter && iterbegin(); + Gtk::TextIter begin_spellcheck_iter; + if(spellcheck_all) { + bool spell_check=!get_source_buffer()->iter_has_context_class(iter, "no-spell-check"); + if(spell_check) + begin_spellcheck_iter=iter; + while(iter!=get_buffer()->end()) { + if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter, "no-spell-check")) + iter=get_buffer()->end(); + + spell_check=!spell_check; + if(spell_check) + begin_spellcheck_iter=iter; + else + spellcheck(begin_spellcheck_iter, iter); + } + } + else { + bool spell_check=get_source_buffer()->iter_has_context_class(iter, "string") || get_source_buffer()->iter_has_context_class(iter, "comment"); + if(spell_check) + begin_spellcheck_iter=iter; + while(iter!=get_buffer()->end()) { + auto iter1=iter; + auto iter2=iter; + if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter1, "string")) + iter1=get_buffer()->end(); + if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter2, "comment")) + iter2=get_buffer()->end(); + + if(iter2remove_tag_by_name("spellcheck_error", get_buffer()->begin(), get_buffer()->end()); +} + +void Source::SpellCheckView::goto_next_spellcheck_error() { + auto iter=get_buffer()->get_insert()->get_iter(); + auto insert_iter=iter; + bool wrapped=false; + iter.forward_char(); + while(iter && (!wrapped || iterproperty_name()=="spellcheck_error") { + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); + return; + } + } + iter.forward_char(); + if(!wrapped && iter==get_buffer()->end()) { + iter=get_buffer()->begin(); + wrapped=true; + } + } +} + +bool Source::SpellCheckView::is_word_iter(const Gtk::TextIter& iter) { + return ((*iter>='A' && *iter<='Z') || (*iter>='a' && *iter<='z') || *iter=='\'' || *iter>=128); +} + +std::pair Source::SpellCheckView::spellcheck_get_word(Gtk::TextIter iter) { + auto start=iter; + auto end=iter; + + while(is_word_iter(iter)) { + start=iter; + if(!iter.backward_char()) + break; + } + while(is_word_iter(end)) { + if(!end.forward_char()) + break; + } + + return {start, end}; +} + +void Source::SpellCheckView::spellcheck_word(const Gtk::TextIter& start, const Gtk::TextIter& end) { + auto spellcheck_start=start; + auto spellcheck_end=end; + if((spellcheck_end.get_offset()-spellcheck_start.get_offset())>=2) { + auto last_char=spellcheck_end; + last_char.backward_char(); + if(*spellcheck_start=='\'' && *last_char=='\'') { + spellcheck_start.forward_char(); + spellcheck_end.backward_char(); + } + } + + auto word=get_buffer()->get_text(spellcheck_start, spellcheck_end); + if(word.size()>0) { + auto correct = aspell_speller_check(spellcheck_checker, word.data(), word.bytes()); + if(correct==0) + get_buffer()->apply_tag_by_name("spellcheck_error", start, end); + else + get_buffer()->remove_tag_by_name("spellcheck_error", start, end); + } +} + +std::vector Source::SpellCheckView::spellcheck_get_suggestions(const Gtk::TextIter& start, const Gtk::TextIter& end) { + auto word_with_error=get_buffer()->get_text(start, end); + + const AspellWordList *suggestions = aspell_speller_suggest(spellcheck_checker, word_with_error.data(), word_with_error.bytes()); + AspellStringEnumeration *elements = aspell_word_list_elements(suggestions); + + std::vector words; + const char *word; + while ((word = aspell_string_enumeration_next(elements))!= NULL) { + words.emplace_back(word); + } + delete_aspell_string_enumeration(elements); + + return words; +} diff --git a/src/source_spellcheck.h b/src/source_spellcheck.h new file mode 100644 index 0000000..1ab6d9f --- /dev/null +++ b/src/source_spellcheck.h @@ -0,0 +1,41 @@ +#ifndef JUCI_SOURCE_SPELLCHECK_H_ +#define JUCI_SOURCE_SPELLCHECK_H_ +#include +#include +#include "selectiondialog.h" + +namespace Source { + class SpellCheckView : public Gsv::View { + public: + SpellCheckView(); + ~SpellCheckView(); + + virtual void configure(); + + virtual void hide_dialogs(); + + void spellcheck(); + void remove_spellcheck_errors(); + void goto_next_spellcheck_error(); + + protected: + bool spellcheck_all=false; + guint last_keyval=0; + private: + std::unique_ptr spellcheck_suggestions_dialog; + + static AspellConfig* spellcheck_config; + AspellCanHaveError *spellcheck_possible_err; + AspellSpeller *spellcheck_checker; + bool is_word_iter(const Gtk::TextIter& iter); + std::pair spellcheck_get_word(Gtk::TextIter iter); + void spellcheck_word(const Gtk::TextIter& start, const Gtk::TextIter& end); + std::vector spellcheck_get_suggestions(const Gtk::TextIter& start, const Gtk::TextIter& end); + sigc::connection delayed_spellcheck_suggestions_connection; + sigc::connection delayed_spellcheck_error_clear; + + void spellcheck(const Gtk::TextIter& start, const Gtk::TextIter& end); + }; +} + +#endif //JUCI_SOURCE_SPELLCHECK_H_