#include "source_generic.hpp" #include "filesystem.hpp" #include "info.hpp" #include "selection_dialog.hpp" #include "snippets.hpp" #include "terminal.hpp" #include "utility.hpp" #include #include Source::GenericView::GenericView(const boost::filesystem::path &file_path, const Glib::RefPtr &language) : BaseView(file_path, language), View(file_path, language, true), autocomplete(this, interactive_completion, last_keyval, false) { spellcheck_all = true; if(language) { auto language_manager = LanguageManager::get_default(); auto search_paths = language_manager->get_search_path(); bool found_language_file = false; boost::filesystem::path language_file; boost::system::error_code ec; for(auto &search_path : search_paths) { boost::filesystem::path p(static_cast(search_path) + '/' + static_cast(language->get_id()) + ".lang"); if(boost::filesystem::exists(p, ec) && boost::filesystem::is_regular_file(p, ec)) { language_file = p; found_language_file = true; break; } } if(found_language_file) { boost::property_tree::ptree pt; try { boost::property_tree::xml_parser::read_xml(language_file.string(), pt); } catch(const std::exception &e) { Terminal::get().print("\e[31mError\e[m: error parsing language file " + filesystem::get_short_path(language_file).string() + ": " + e.what() + '\n', true); } bool has_context_class = false; parse_language_file(has_context_class, pt); if(!has_context_class || language->get_id() == "cmake") // TODO: no longer use the spellcheck_all flag? spellcheck_all = false; } } setup_buffer_words(); setup_autocomplete(); } Source::GenericView::~GenericView() { autocomplete.state = Autocomplete::State::idle; if(autocomplete.thread.joinable()) autocomplete.thread.join(); } void Source::GenericView::parse_language_file(bool &has_context_class, const boost::property_tree::ptree &pt) { bool case_insensitive = false; for(auto &node : pt) { if(node.first == "") { auto data = static_cast(node.second.data()); std::transform(data.begin(), data.end(), data.begin(), ::tolower); if(data.find("case insensitive") != std::string::npos) case_insensitive = true; } else if(node.first == "keyword") { auto data = static_cast(node.second.data()); keywords.emplace(data); if(case_insensitive) { std::transform(data.begin(), data.end(), data.begin(), ::tolower); keywords.emplace(data); } } else if(!has_context_class && node.first == "context") { auto class_attribute = node.second.get(".class", ""); auto class_disabled_attribute = node.second.get(".class-disabled", ""); if(class_attribute.size() > 0 || class_disabled_attribute.size() > 0) has_context_class = true; } try { parse_language_file(has_context_class, node.second); } catch(const std::exception &e) { } } } std::vector> Source::GenericView::get_words(const Gtk::TextIter &start, const Gtk::TextIter &end) { std::vector> words; auto iter = start; while(iter && iter < end) { if(is_token_char(*iter)) { auto word = get_token_iters(iter); if(!(*word.first >= '0' && *word.first <= '9') && (word.second.get_offset() - word.first.get_offset()) >= 3) // Minimum word length: 3 words.emplace_back(word.first, word.second); iter = word.second; } iter.forward_char(); } return words; } void Source::GenericView::setup_buffer_words() { { auto words = get_words(get_buffer()->begin(), get_buffer()->end()); LockGuard lock(buffer_words_mutex); for(auto &word : words) { auto result = buffer_words.emplace(get_buffer()->get_text(word.first, word.second), 1); if(!result.second) ++(result.first->second); } } // Remove changed word at insert get_buffer()->signal_insert().connect( [this](const Gtk::TextIter &iter_, const Glib::ustring &text, int bytes) { auto iter = iter_; if(!is_token_char(*iter)) iter.backward_char(); if(is_token_char(*iter)) { auto word = get_token_iters(iter); if(word.second.get_offset() - word.first.get_offset() >= 3) { LockGuard lock(buffer_words_mutex); auto it = buffer_words.find(get_buffer()->get_text(word.first, word.second)); if(it != buffer_words.end()) { if(it->second > 1) --(it->second); else buffer_words.erase(it); } } } }, false); // Add all words between start and end of insert get_buffer()->signal_insert().connect([this](const Gtk::TextIter &iter, const Glib::ustring &text, int bytes) { auto start = iter; auto end = iter; start.backward_chars(text.size()); if(!is_token_char(*start)) start.backward_char(); end.forward_char(); auto words = get_words(start, end); LockGuard lock(buffer_words_mutex); for(auto &word : words) { auto result = buffer_words.emplace(get_buffer()->get_text(word.first, word.second), 1); if(!result.second) ++(result.first->second); } }); // Remove words within text that was removed get_buffer()->signal_erase().connect( [this](const Gtk::TextIter &start_, const Gtk::TextIter &end_) { auto start = start_; auto end = end_; if(!is_token_char(*start)) start.backward_char(); end.forward_char(); auto words = get_words(start, end); LockGuard lock(buffer_words_mutex); for(auto &word : words) { auto it = buffer_words.find(get_buffer()->get_text(word.first, word.second)); if(it != buffer_words.end()) { if(it->second > 1) --(it->second); else buffer_words.erase(it); } } }, false); // Add new word resulting from erased text get_buffer()->signal_erase().connect([this](const Gtk::TextIter &start_, const Gtk::TextIter & /*end*/) { auto start = start_; if(!is_token_char(*start)) start.backward_char(); if(is_token_char(*start)) { auto word = get_token_iters(start); if(word.second.get_offset() - word.first.get_offset() >= 3) { LockGuard lock(buffer_words_mutex); auto result = buffer_words.emplace(get_buffer()->get_text(word.first, word.second), 1); if(!result.second) ++(result.first->second); } } }); } void Source::GenericView::setup_autocomplete() { non_interactive_completion = [this] { if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) return; autocomplete.run(); }; autocomplete.reparse = [this] { autocomplete_comment.clear(); autocomplete_insert.clear(); }; autocomplete.run_check = [this]() { auto prefix_start = get_buffer()->get_insert()->get_iter(); auto prefix_end = prefix_start; size_t count = 0; while(prefix_start.backward_char() && is_token_char(*prefix_start)) ++count; if(prefix_start != prefix_end && !is_token_char(*prefix_start)) prefix_start.forward_char(); if((count >= 3 && !(*prefix_start >= '0' && *prefix_start <= '9')) || !interactive_completion) { LockGuard lock1(autocomplete.prefix_mutex); LockGuard lock2(buffer_words_mutex); autocomplete.prefix = get_buffer()->get_text(prefix_start, prefix_end); if(interactive_completion) show_prefix_buffer_word = buffer_words.find(autocomplete.prefix) != buffer_words.end(); else { auto it = buffer_words.find(autocomplete.prefix); show_prefix_buffer_word = !(it == buffer_words.end() || it->second == 1); } return true; } return false; }; autocomplete.before_add_rows = [this] { status_state = "autocomplete..."; if(update_status_state) update_status_state(this); }; autocomplete.after_add_rows = [this] { status_state = ""; if(update_status_state) update_status_state(this); }; autocomplete.add_rows = [this](std::string &buffer, int /*line*/, int /*line_index*/) { if(autocomplete.state == Autocomplete::State::starting) { autocomplete_comment.clear(); autocomplete_insert.clear(); std::string prefix; { LockGuard lock(autocomplete.prefix_mutex); prefix = autocomplete.prefix; } for(auto &keyword : keywords) { if(starts_with(keyword, prefix)) { autocomplete.rows.emplace_back(keyword); autocomplete_insert.emplace_back(keyword); autocomplete_comment.emplace_back(""); } } { LockGuard lock(buffer_words_mutex); for(auto &buffer_word : buffer_words) { if((show_prefix_buffer_word || buffer_word.first.size() > prefix.size()) && starts_with(buffer_word.first, prefix) && keywords.find(buffer_word.first) == keywords.end()) { autocomplete.rows.emplace_back(buffer_word.first); auto insert = buffer_word.first; boost::replace_all(insert, "$", "\\$"); autocomplete_insert.emplace_back(insert); autocomplete_comment.emplace_back(""); } } } LockGuard lock(snippets_mutex); if(snippets) { for(auto &snippet : *snippets) { if(starts_with(snippet.prefix, prefix)) { autocomplete.rows.emplace_back(snippet.prefix); autocomplete_insert.emplace_back(snippet.body); autocomplete_comment.emplace_back(snippet.description); } } } } return true; }; autocomplete.on_show = [this] { hide_tooltips(); }; autocomplete.on_hide = [this] { autocomplete_comment.clear(); autocomplete_insert.clear(); }; autocomplete.on_select = [this](unsigned int index, const std::string &text, bool hide_window) { get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), get_buffer()->get_insert()->get_iter()); if(hide_window) insert_snippet(CompletionDialog::get()->start_mark->get_iter(), autocomplete_insert[index]); else get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), text); }; autocomplete.set_tooltip_buffer = [this](unsigned int index) -> std::function { auto tooltip_str = autocomplete_comment[index]; if(tooltip_str.empty()) return nullptr; return [tooltip_str = std::move(tooltip_str)](Tooltip &tooltip) { tooltip.insert_with_links_tagged(tooltip_str); }; }; }