mirror of https://gitlab.com/cppit/jucipp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
282 lines
9.5 KiB
282 lines
9.5 KiB
#include "source_generic.hpp" |
|
#include "filesystem.hpp" |
|
#include "info.hpp" |
|
#include "selection_dialog.hpp" |
|
#include "snippets.hpp" |
|
#include "terminal.hpp" |
|
#include "utility.hpp" |
|
#include <algorithm> |
|
#include <boost/algorithm/string.hpp> |
|
|
|
Source::GenericView::GenericView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language, bool is_generic_view) : BaseView(file_path, language), View(file_path, language, is_generic_view) { |
|
if(is_generic_view) |
|
setup_autocomplete(); |
|
} |
|
|
|
void Source::GenericView::parse_language_file(const boost::property_tree::ptree &pt) { |
|
bool case_insensitive = false; |
|
for(auto &node : pt) { |
|
if(node.first == "<xmlcomment>") { |
|
auto data = static_cast<std::string>(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<std::string>(node.second.data()); |
|
keywords.emplace(data); |
|
|
|
if(case_insensitive) { |
|
std::transform(data.begin(), data.end(), data.begin(), ::tolower); |
|
keywords.emplace(data); |
|
} |
|
} |
|
try { |
|
parse_language_file(node.second); |
|
} |
|
catch(const std::exception &e) { |
|
} |
|
} |
|
} |
|
|
|
std::vector<std::pair<Gtk::TextIter, Gtk::TextIter>> Source::GenericView::get_words(const Gtk::TextIter &start, const Gtk::TextIter &end) { |
|
std::vector<std::pair<Gtk::TextIter, Gtk::TextIter>> 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()); |
|
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) { |
|
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); |
|
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); |
|
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) { |
|
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() { |
|
autocomplete = std::make_unique<Autocomplete>(this, interactive_completion, last_keyval, false, false); |
|
|
|
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 path(static_cast<std::string>(search_path) + '/' + static_cast<std::string>(language->get_id()) + ".lang"); |
|
if(boost::filesystem::exists(path, ec) && boost::filesystem::is_regular_file(path, ec)) { |
|
language_file = path; |
|
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); |
|
parse_language_file(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); |
|
} |
|
} |
|
} |
|
|
|
setup_buffer_words(); |
|
|
|
non_interactive_completion = [this] { |
|
if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) |
|
return; |
|
autocomplete->run(); |
|
}; |
|
|
|
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 lock(autocomplete->prefix_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->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(""); |
|
} |
|
} |
|
{ |
|
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(""); |
|
} |
|
} |
|
} |
|
if(snippets_mutex.try_lock()) { // To avoid deadlock when inserting snippets |
|
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); |
|
} |
|
} |
|
} |
|
snippets_mutex.unlock(); |
|
} |
|
} |
|
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<void(Tooltip & tooltip)> { |
|
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); |
|
}; |
|
}; |
|
}
|
|
|