Browse Source

Source::View cleanup: moved spellchecking to source_spellcheck.*

merge-requests/365/head
eidheim 10 years ago
parent
commit
0cbc14d7e8
  1. 1
      src/CMakeLists.txt
  2. 313
      src/source.cc
  3. 29
      src/source.h
  4. 345
      src/source_spellcheck.cc
  5. 41
      src/source_spellcheck.h

1
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

313
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<Gsv::Language> language): file_path(file_path), language(language) {
Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> 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::RefPtr<Gsv::L
get_buffer()->create_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::RefPtr<Gsv::L
mark_attr_debug_stop->set_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(iter2<iter1)
iter=iter2;
else
iter=iter1;
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;
}, 1000);
});
get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iter, const Glib::RefPtr<Gtk::TextBuffer::Mark>& 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<SelectionDialog>(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<Source::View*> &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 && iter<end) {
if(is_word_iter(iter)) {
auto word=spellcheck_get_word(iter);
spellcheck_word(word.first, word.second);
iter=word.second;
}
iter.forward_char();
}
}
void Source::View::spellcheck() {
auto iter=get_buffer()->begin();
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(iter2<iter1)
iter=iter2;
else
iter=iter1;
spell_check=!spell_check;
if(spell_check)
begin_spellcheck_iter=iter;
else
spellcheck(begin_spellcheck_iter, iter);
}
}
}
void Source::View::remove_spellcheck_errors() {
get_buffer()->remove_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 || iter<insert_iter)) {
auto toggled_tags=iter.get_toggled_tags();
for(auto &toggled_tag: toggled_tags) {
if(toggled_tag->property_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<char, unsigned> 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<Gtk::TextIter, Gtk::TextIter> 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<std::string> 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<std::string> words;
const char *word;
while ((word = aspell_string_enumeration_next(elements))!= NULL) {
words.emplace_back(word);
}
delete_aspell_string_enumeration(elements);
return words;
}
/////////////////////
//// GenericView ////

29
src/source.h

@ -1,9 +1,8 @@
#ifndef JUCI_SOURCE_H_
#define JUCI_SOURCE_H_
#include <aspell.h>
#include "source_spellcheck.h"
#include <boost/property_tree/xml_parser.hpp>
#include <boost/filesystem.hpp>
#include <gtksourceviewmm.h>
#include <string>
#include <unordered_map>
#include <vector>
@ -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<Offset, Offset> offsets;
};
class View : public Gsv::View {
class View : public SpellCheckView {
public:
View(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language);
~View();
virtual bool save(const std::vector<Source::View*> &views);
virtual void configure();
void configure() override;
void search_highlight(const std::string &text, bool case_sensitive, bool regex);
std::function<void(int number)> 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<void(View* view, const std::string &status_text)> on_update_status;
std::function<void(View* view, const std::string &info_text)> 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<char, unsigned> 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<SelectionDialog> 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<Gtk::TextIter, Gtk::TextIter> spellcheck_get_word(Gtk::TextIter iter);
void spellcheck_word(const Gtk::TextIter& start, const Gtk::TextIter& end);
std::vector<std::string> 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 {

345
src/source_spellcheck.cc

@ -0,0 +1,345 @@
#include "source_spellcheck.h"
#include "config.h"
#include <iostream>
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(iter2<iter1)
iter=iter2;
else
iter=iter1;
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;
}, 1000);
});
get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iter, const Glib::RefPtr<Gtk::TextBuffer::Mark>& 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<SelectionDialog>(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<Gtk::TextBuffer::Mark>& 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 && iter<end) {
if(is_word_iter(iter)) {
auto word=spellcheck_get_word(iter);
spellcheck_word(word.first, word.second);
iter=word.second;
}
iter.forward_char();
}
}
void Source::SpellCheckView::spellcheck() {
auto iter=get_buffer()->begin();
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(iter2<iter1)
iter=iter2;
else
iter=iter1;
spell_check=!spell_check;
if(spell_check)
begin_spellcheck_iter=iter;
else
spellcheck(begin_spellcheck_iter, iter);
}
}
}
void Source::SpellCheckView::remove_spellcheck_errors() {
get_buffer()->remove_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 || iter<insert_iter)) {
auto toggled_tags=iter.get_toggled_tags();
for(auto &toggled_tag: toggled_tags) {
if(toggled_tag->property_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<Gtk::TextIter, Gtk::TextIter> 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<std::string> 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<std::string> words;
const char *word;
while ((word = aspell_string_enumeration_next(elements))!= NULL) {
words.emplace_back(word);
}
delete_aspell_string_enumeration(elements);
return words;
}

41
src/source_spellcheck.h

@ -0,0 +1,41 @@
#ifndef JUCI_SOURCE_SPELLCHECK_H_
#define JUCI_SOURCE_SPELLCHECK_H_
#include <gtksourceviewmm.h>
#include <aspell.h>
#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<SelectionDialog> spellcheck_suggestions_dialog;
static AspellConfig* spellcheck_config;
AspellCanHaveError *spellcheck_possible_err;
AspellSpeller *spellcheck_checker;
bool is_word_iter(const Gtk::TextIter& iter);
std::pair<Gtk::TextIter, Gtk::TextIter> spellcheck_get_word(Gtk::TextIter iter);
void spellcheck_word(const Gtk::TextIter& start, const Gtk::TextIter& end);
std::vector<std::string> 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_
Loading…
Cancel
Save