Browse Source

Added support for snippets. Fixes #348

merge-requests/393/head
eidheim 7 years ago
parent
commit
994573b75e
  1. 2
      CMakeLists.txt
  2. 1
      README.md
  3. 2
      src/CMakeLists.txt
  4. 2
      src/autocomplete.h
  5. 1
      src/files.h
  6. 6
      src/menu.cc
  7. 1
      src/notebook.cc
  8. 8
      src/project.cc
  9. 5
      src/selection_dialog.cc
  10. 40
      src/snippets.cc
  11. 25
      src/snippets.h
  12. 206
      src/source.cc
  13. 22
      src/source.h
  14. 233
      src/source_base.cc
  15. 21
      src/source_base.h
  16. 53
      src/source_clang.cc
  17. 4
      src/source_clang.h
  18. 346
      src/source_generic.cc
  19. 31
      src/source_generic.h
  20. 118
      src/source_language_protocol.cc
  21. 3
      src/source_language_protocol.h
  22. 3
      src/window.cc
  23. 4
      tests/CMakeLists.txt
  24. 143
      tests/source_generic_test.cc

2
CMakeLists.txt

@ -1,7 +1,7 @@
cmake_minimum_required (VERSION 2.8.8)
project(juci)
set(JUCI_VERSION "1.4.6.1")
set(JUCI_VERSION "1.4.6.2")
set(CPACK_PACKAGE_NAME "jucipp")
set(CPACK_PACKAGE_CONTACT "Ole Christian Eidheim <eidheim@gmail.com>")

1
README.md

@ -41,6 +41,7 @@ towards libclang with speed, stability, and ease of use in mind.
* Regex search and replace
* Smart paste, keys and indentation
* Multiple cursors
* Snippets can be added in ~/.juci/snippets.json. The language ids used in the regexes can be found here: https://gitlab.gnome.org/GNOME/gtksourceview/tree/master/data/language-specs.
* Auto-indentation through [clang-format](http://clang.llvm.org/docs/ClangFormat.html) or [Prettier](https://github.com/prettier/prettier) if installed
* Source minimap
* Split view

2
src/CMakeLists.txt

@ -11,10 +11,12 @@ set(JUCI_SHARED_FILES
menu.cc
meson.cc
project_build.cc
snippets.cc
source.cc
source_base.cc
source_clang.cc
source_diff.cc
source_generic.cc
source_language_protocol.cc
source_spellcheck.cc
terminal.cc

2
src/autocomplete.h

@ -16,7 +16,7 @@ class Autocomplete {
public:
enum class State { IDLE, STARTING, RESTARTING, CANCELED };
std::string prefix;
Glib::ustring prefix;
std::mutex prefix_mutex;
std::vector<std::string> rows;
Tooltips tooltips;

1
src/files.h

@ -72,6 +72,7 @@ const std::string default_config_file = R"RAW({
},
"keybindings": {
"preferences": "<primary>comma",
"snippets": "",
"quit": "<primary>q",
"file_new_file": "<primary>n",
"file_new_folder": "<primary><shift>n",

6
src/menu.cc

@ -99,6 +99,12 @@ const Glib::ustring menu_xml = R"RAW(<interface>
<attribute name='action'>app.preferences</attribute>
</item>
</section>
<section>
<item>
<attribute name='label' translatable='yes'>_Snippets</attribute>
<attribute name='action'>app.snippets</attribute>
</item>
</section>
<section>
<item>
<attribute name='label' translatable='yes'>_Quit</attribute>

1
src/notebook.cc

@ -6,6 +6,7 @@
#include "project.h"
#include "selection_dialog.h"
#include "source_clang.h"
#include "source_generic.h"
#include "source_language_protocol.h"
#include <fstream>
#include <regex>

8
src/project.cc

@ -12,6 +12,7 @@
#endif
#include "ctags.h"
#include "info.h"
#include "snippets.h"
#include "source_clang.h"
#include "source_language_protocol.h"
#include "usages_clang.h"
@ -46,6 +47,13 @@ void Project::on_save(size_t index) {
auto view = Notebook::get().get_view(index);
if(!view)
return;
if(view->file_path == Config::get().home_juci_path / "snippets.json") {
Snippets::get().load();
for(auto view : Notebook::get().get_views())
view->set_snippets();
}
boost::filesystem::path build_path;
if(view->language && view->language->get_id() == "cmake") {
if(view->file_path.filename() == "CMakeLists.txt")

5
src/selection_dialog.cc

@ -403,10 +403,7 @@ bool CompletionDialog::on_key_release(GdkEventKey *key) {
}
bool CompletionDialog::on_key_press(GdkEventKey *key) {
if((key->keyval >= GDK_KEY_0 && key->keyval <= GDK_KEY_9) ||
(key->keyval >= GDK_KEY_A && key->keyval <= GDK_KEY_Z) ||
(key->keyval >= GDK_KEY_a && key->keyval <= GDK_KEY_z) ||
key->keyval == GDK_KEY_underscore || key->keyval == GDK_KEY_BackSpace) {
if((key->keyval >= '0' && key->keyval <= '9') || (key->keyval >= 'a' && key->keyval <= 'z') || (key->keyval >= 'A' && key->keyval <= 'Z') || key->keyval == '_' || gdk_keyval_to_unicode(key->keyval) >= 0x00C0 || key->keyval == GDK_KEY_BackSpace) {
if(row_in_entry) {
text_view->get_buffer()->erase(start_mark->get_iter(), text_view->get_buffer()->get_insert()->get_iter());
row_in_entry = false;

40
src/snippets.cc

@ -0,0 +1,40 @@
#include "snippets.h"
#include "config.h"
#include "filesystem.h"
#include "terminal.h"
#include <boost/property_tree/json_parser.hpp>
Snippets::Snippets() {
load();
}
void Snippets::load() {
auto snippets_file = Config::get().home_juci_path / "snippets.json";
boost::system::error_code ec;
if(!boost::filesystem::exists(snippets_file, ec))
filesystem::write(snippets_file, R"({
"^markdown$": [
{
"prefix": "code_block",
"body": "```${1:language}\n${2:code}\n```\n",
"description": "Insert code block"
}
]
}
)");
snippets.clear();
try {
boost::property_tree::ptree pt;
boost::property_tree::json_parser::read_json(snippets_file.string(), pt);
for(auto language_it = pt.begin(); language_it != pt.end(); ++language_it) {
snippets.emplace_back(std::regex(language_it->first), std::vector<Snippet>());
for(auto snippet_it = language_it->second.begin(); snippet_it != language_it->second.end(); ++snippet_it)
snippets.back().second.emplace_back(Snippet{snippet_it->second.get<std::string>("prefix"), snippet_it->second.get<std::string>("body"), snippet_it->second.get<std::string>("description", "")});
}
}
catch(const std::exception &e) {
Terminal::get().async_print(std::string("Error: ") + e.what() + "\n", true);
}
}

25
src/snippets.h

@ -0,0 +1,25 @@
#pragma once
#include <regex>
#include <string>
#include <vector>
class Snippets {
Snippets();
public:
class Snippet {
public:
std::string prefix;
std::string body;
std::string description;
};
static Snippets &get() {
static Snippets singleton;
return singleton;
}
std::vector<std::pair<std::regex, std::vector<Snippet>>> snippets;
void load();
};

206
src/source.cc

@ -1350,7 +1350,11 @@ bool Source::View::on_key_press_event(GdkEventKey *key) {
previous_non_modifier_keyval = last_keyval;
last_keyval = key->keyval;
if(Config::get().source.enable_multiple_cursors && on_key_press_event_multiple_cursors(key))
if((key->keyval == GDK_KEY_Tab || key->keyval == GDK_KEY_ISO_Left_Tab) && (key->state & GDK_SHIFT_MASK) == 0 && select_snippet_argument())
return true;
else if(key->keyval == GDK_KEY_Escape && clear_snippet_marks())
return true;
else if(Config::get().source.enable_multiple_cursors && on_key_press_event_extra_cursors(key))
return true;
//Move cursor one paragraph down
@ -1740,7 +1744,7 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey *key) {
auto end_of_line_iter = start_iter;
end_of_line_iter.forward_to_line_end();
auto line = get_buffer()->get_text(tabs_end_iter, end_of_line_iter);
if(!line.empty() && line.compare(0, 2, "*/") == 0) {
if(!line.empty() && line.raw().compare(0, 2, "*/") == 0) {
tabs.pop_back();
get_buffer()->insert_at_cursor('\n' + tabs);
scroll_to(get_buffer()->get_insert());
@ -2185,9 +2189,9 @@ bool Source::View::on_key_press_event_smart_brackets(GdkEventKey *key) {
}
bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *key) {
keep_argument_marks = true;
keep_snippet_marks = true;
ScopeGuard guard{[this] {
keep_argument_marks = false;
keep_snippet_marks = false;
}};
if(get_buffer()->get_has_selection()) {
@ -2453,80 +2457,15 @@ bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *key) {
return false;
}
bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) {
if(!multiple_cursors_signals_set) {
multiple_cursors_signals_set = true;
multiple_cursors_last_insert = get_buffer()->create_mark(get_buffer()->get_insert()->get_iter(), false);
get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator &iter, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) {
for(auto &extra_cursor : multiple_cursors_extra_cursors) {
if(extra_cursor.first == mark && (!iter.ends_line() || iter.get_line_offset() > extra_cursor.second)) {
extra_cursor.second = iter.get_line_offset();
break;
}
}
bool Source::View::on_key_press_event_extra_cursors(GdkEventKey *key) {
setup_extra_cursor_signals();
if(mark->get_name() == "insert") {
if(enable_multiple_cursors) {
enable_multiple_cursors = false;
auto offset_diff = mark->get_iter().get_offset() - multiple_cursors_last_insert->get_iter().get_offset();
if(offset_diff != 0) {
for(auto &extra_cursor : multiple_cursors_extra_cursors) {
auto iter = extra_cursor.first->get_iter();
iter.forward_chars(offset_diff);
get_buffer()->move_mark(extra_cursor.first, iter);
}
}
enable_multiple_cursors = true;
}
get_buffer()->delete_mark(multiple_cursors_last_insert);
multiple_cursors_last_insert = get_buffer()->create_mark(mark->get_iter(), false);
}
});
// TODO: this handler should run after signal_insert
get_buffer()->signal_insert().connect([this](const Gtk::TextBuffer::iterator &iter, const Glib::ustring &text, int bytes) {
if(enable_multiple_cursors) {
enable_multiple_cursors = false;
auto offset = iter.get_offset() - get_buffer()->get_insert()->get_iter().get_offset();
for(auto &extra_cursor : multiple_cursors_extra_cursors) {
auto iter = extra_cursor.first->get_iter();
iter.forward_chars(offset);
get_buffer()->insert(iter, text);
}
enable_multiple_cursors = true;
}
});
get_buffer()->signal_erase().connect([this](const Gtk::TextBuffer::iterator &iter_start, const Gtk::TextBuffer::iterator &iter_end) {
if(enable_multiple_cursors) {
auto insert_offset = get_buffer()->get_insert()->get_iter().get_offset();
multiple_cursors_erase_backward_length = insert_offset - iter_start.get_offset();
multiple_cursors_erase_forward_length = iter_end.get_offset() - insert_offset;
}
}, false);
get_buffer()->signal_erase().connect([this](const Gtk::TextBuffer::iterator &iter_start, const Gtk::TextBuffer::iterator &iter_end) {
if(enable_multiple_cursors) {
enable_multiple_cursors = false;
for(auto &extra_cursor : multiple_cursors_extra_cursors) {
auto start_iter = extra_cursor.first->get_iter();
auto end_iter = start_iter;
start_iter.backward_chars(multiple_cursors_erase_backward_length);
end_iter.forward_chars(multiple_cursors_erase_forward_length);
get_buffer()->erase(start_iter, end_iter);
}
enable_multiple_cursors = true;
}
});
}
if(key->keyval == GDK_KEY_Escape && !multiple_cursors_extra_cursors.empty()) {
for(auto &extra_cursor : multiple_cursors_extra_cursors) {
if(key->keyval == GDK_KEY_Escape && !extra_cursors.empty()) {
for(auto &extra_cursor : extra_cursors) {
extra_cursor.first->set_visible(false);
get_buffer()->delete_mark(extra_cursor.first);
}
multiple_cursors_extra_cursors.clear();
extra_cursors.clear();
return true;
}
@ -2535,27 +2474,27 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) {
// Move last created cursor
if((key->keyval == GDK_KEY_Left || key->keyval == GDK_KEY_KP_Left) && (key->state & move_last_created_cursor_mask) == move_last_created_cursor_mask) {
if(multiple_cursors_extra_cursors.empty())
if(extra_cursors.empty())
return false;
auto &cursor = multiple_cursors_extra_cursors.back().first;
auto &cursor = extra_cursors.back().first;
auto iter = cursor->get_iter();
iter.backward_char();
get_buffer()->move_mark(cursor, iter);
return true;
}
if((key->keyval == GDK_KEY_Right || key->keyval == GDK_KEY_KP_Right) && (key->state & move_last_created_cursor_mask) == move_last_created_cursor_mask) {
if(multiple_cursors_extra_cursors.empty())
if(extra_cursors.empty())
return false;
auto &cursor = multiple_cursors_extra_cursors.back().first;
auto &cursor = extra_cursors.back().first;
auto iter = cursor->get_iter();
iter.forward_char();
get_buffer()->move_mark(cursor, iter);
return true;
}
if((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) && (key->state & move_last_created_cursor_mask) == move_last_created_cursor_mask) {
if(multiple_cursors_extra_cursors.empty())
if(extra_cursors.empty())
return false;
auto &extra_cursor = multiple_cursors_extra_cursors.back();
auto &extra_cursor = extra_cursors.back();
auto iter = extra_cursor.first->get_iter();
auto line_offset = extra_cursor.second;
if(iter.backward_line()) {
@ -2568,9 +2507,9 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) {
return true;
}
if((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) && (key->state & move_last_created_cursor_mask) == move_last_created_cursor_mask) {
if(multiple_cursors_extra_cursors.empty())
if(extra_cursors.empty())
return false;
auto &extra_cursor = multiple_cursors_extra_cursors.back();
auto &extra_cursor = extra_cursors.back();
auto iter = extra_cursor.first->get_iter();
auto line_offset = extra_cursor.second;
if(iter.forward_line()) {
@ -2588,7 +2527,7 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) {
auto insert_iter = get_buffer()->get_insert()->get_iter();
auto insert_line_offset = insert_iter.get_line_offset();
auto offset = insert_iter.get_offset();
for(auto &extra_cursor : multiple_cursors_extra_cursors)
for(auto &extra_cursor : extra_cursors)
offset = std::min(offset, extra_cursor.first->get_iter().get_offset());
auto iter = get_buffer()->get_iter_at_offset(offset);
if(iter.backward_line()) {
@ -2596,8 +2535,8 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) {
if(!end_line_iter.ends_line())
end_line_iter.forward_to_line_end();
iter.forward_chars(std::min(insert_line_offset, end_line_iter.get_line_offset()));
multiple_cursors_extra_cursors.emplace_back(get_buffer()->create_mark(iter, false), insert_line_offset);
multiple_cursors_extra_cursors.back().first->set_visible(true);
extra_cursors.emplace_back(get_buffer()->create_mark(iter, false), insert_line_offset);
extra_cursors.back().first->set_visible(true);
}
return true;
}
@ -2605,7 +2544,7 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) {
auto insert_iter = get_buffer()->get_insert()->get_iter();
auto insert_line_offset = insert_iter.get_line_offset();
auto offset = insert_iter.get_offset();
for(auto &extra_cursor : multiple_cursors_extra_cursors)
for(auto &extra_cursor : extra_cursors)
offset = std::max(offset, extra_cursor.first->get_iter().get_offset());
auto iter = get_buffer()->get_iter_at_offset(offset);
if(iter.forward_line()) {
@ -2613,8 +2552,8 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) {
if(!end_line_iter.ends_line())
end_line_iter.forward_to_line_end();
iter.forward_chars(std::min(insert_line_offset, end_line_iter.get_line_offset()));
multiple_cursors_extra_cursors.emplace_back(get_buffer()->create_mark(iter, false), insert_line_offset);
multiple_cursors_extra_cursors.back().first->set_visible(true);
extra_cursors.emplace_back(get_buffer()->create_mark(iter, false), insert_line_offset);
extra_cursors.back().first->set_visible(true);
}
return true;
}
@ -2622,7 +2561,7 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) {
// Move cursors left/right
if((key->keyval == GDK_KEY_Left || key->keyval == GDK_KEY_KP_Left) && (key->state & GDK_CONTROL_MASK) > 0) {
enable_multiple_cursors = false;
for(auto &extra_cursor : multiple_cursors_extra_cursors) {
for(auto &extra_cursor : extra_cursors) {
auto iter = extra_cursor.first->get_iter();
iter.backward_word_start();
extra_cursor.second = iter.get_line_offset();
@ -2638,7 +2577,7 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) {
}
if((key->keyval == GDK_KEY_Right || key->keyval == GDK_KEY_KP_Right) && (key->state & GDK_CONTROL_MASK) > 0) {
enable_multiple_cursors = false;
for(auto &extra_cursor : multiple_cursors_extra_cursors) {
for(auto &extra_cursor : extra_cursors) {
auto iter = extra_cursor.first->get_iter();
iter.forward_visible_word_end();
extra_cursor.second = iter.get_line_offset();
@ -2656,7 +2595,7 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) {
// Move cursors up/down
if((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up)) {
enable_multiple_cursors = false;
for(auto &extra_cursor : multiple_cursors_extra_cursors) {
for(auto &extra_cursor : extra_cursors) {
auto iter = extra_cursor.first->get_iter();
auto line_offset = extra_cursor.second;
if(iter.backward_line()) {
@ -2671,7 +2610,7 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) {
}
if((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down)) {
enable_multiple_cursors = false;
for(auto &extra_cursor : multiple_cursors_extra_cursors) {
for(auto &extra_cursor : extra_cursors) {
auto iter = extra_cursor.first->get_iter();
auto line_offset = extra_cursor.second;
if(iter.forward_line()) {
@ -2687,14 +2626,14 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) {
// Smart Home-key, start of line
if((key->keyval == GDK_KEY_Home || key->keyval == GDK_KEY_KP_Home) && (key->state & GDK_CONTROL_MASK) == 0) {
for(auto &extra_cursor : multiple_cursors_extra_cursors)
for(auto &extra_cursor : extra_cursors)
get_buffer()->move_mark(extra_cursor.first, get_smart_home_iter(extra_cursor.first->get_iter()));
enable_multiple_cursors = false;
return false;
}
// Smart End-key, end of line
if((key->keyval == GDK_KEY_End || key->keyval == GDK_KEY_KP_End) && (key->state & GDK_CONTROL_MASK) == 0) {
for(auto &extra_cursor : multiple_cursors_extra_cursors)
for(auto &extra_cursor : extra_cursors)
get_buffer()->move_mark(extra_cursor.first, get_smart_end_iter(extra_cursor.first->get_iter()));
enable_multiple_cursors = false;
return false;
@ -2775,82 +2714,3 @@ bool Source::View::on_motion_notify_event(GdkEventMotion *event) {
return Gsv::View::on_motion_notify_event(event);
#endif
}
/////////////////////
//// GenericView ////
/////////////////////
Source::GenericView::GenericView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language) : BaseView(file_path, language), View(file_path, language, true) {
configure();
spellcheck_all = true;
auto completion = get_completion();
completion->property_show_headers() = false;
completion->property_show_icons() = false;
completion->property_accelerators() = 0;
auto completion_words = Gsv::CompletionWords::create("", Glib::RefPtr<Gdk::Pixbuf>());
completion_words->register_provider(get_buffer());
completion->add_provider(completion_words);
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;
for(auto &search_path : search_paths) {
boost::filesystem::path p(static_cast<std::string>(search_path) + '/' + static_cast<std::string>(language->get_id()) + ".lang");
if(boost::filesystem::exists(p) && boost::filesystem::is_regular_file(p)) {
language_file = p;
found_language_file = true;
break;
}
}
if(found_language_file) {
auto completion_buffer_keywords = CompletionBuffer::create();
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("Error: error parsing language file " + language_file.string() + ": " + e.what() + '\n', true);
}
bool has_context_class = false;
parse_language_file(completion_buffer_keywords, has_context_class, pt);
if(!has_context_class || language->get_id() == "cmake") // TODO: no longer use the spellcheck_all flag
spellcheck_all = false;
completion_words->register_provider(completion_buffer_keywords);
}
}
}
void Source::GenericView::parse_language_file(Glib::RefPtr<CompletionBuffer> &completion_buffer, bool &has_context_class, 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());
completion_buffer->insert_at_cursor(data + '\n');
if(case_insensitive) {
std::transform(data.begin(), data.end(), data.begin(), ::tolower);
completion_buffer->insert_at_cursor(data + '\n');
}
}
else if(!has_context_class && node.first == "context") {
auto class_attribute = node.second.get<std::string>("<xmlattr>.class", "");
auto class_disabled_attribute = node.second.get<std::string>("<xmlattr>.class-disabled", "");
if(class_attribute.size() > 0 || class_disabled_attribute.size() > 0)
has_context_class = true;
}
try {
parse_language_file(completion_buffer, has_context_class, node.second);
}
catch(const std::exception &e) {
}
}
}

22
src/source.h

@ -133,8 +133,6 @@ namespace Source {
bool on_button_press_event(GdkEventButton *event) override;
bool on_motion_notify_event(GdkEventMotion *motion_event) override;
/// After autocomplete, arguments could be marked so that one can use tab to select the next argument
bool keep_argument_marks = false;
bool interactive_completion = true;
private:
@ -149,24 +147,6 @@ namespace Source {
bool is_cpp = false;
guint previous_non_modifier_keyval = 0;
bool multiple_cursors_signals_set = false;
std::vector<std::pair<Glib::RefPtr<Gtk::TextBuffer::Mark>, int>> multiple_cursors_extra_cursors;
Glib::RefPtr<Gtk::TextBuffer::Mark> multiple_cursors_last_insert;
int multiple_cursors_erase_backward_length;
int multiple_cursors_erase_forward_length;
bool on_key_press_event_multiple_cursors(GdkEventKey *key);
};
class GenericView : public View {
private:
class CompletionBuffer : public Gtk::TextBuffer {
public:
static Glib::RefPtr<CompletionBuffer> create() { return Glib::RefPtr<CompletionBuffer>(new CompletionBuffer()); }
};
public:
GenericView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language);
void parse_language_file(Glib::RefPtr<CompletionBuffer> &completion_buffer, bool &has_context_class, const boost::property_tree::ptree &pt);
bool on_key_press_event_extra_cursors(GdkEventKey *key);
};
} // namespace Source

233
src/source_base.cc

@ -6,6 +6,7 @@
#include "utility.h"
#include <fstream>
#include <gtksourceview/gtksource.h>
#include <regex>
Source::BaseView::BaseView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language) : Gsv::View(), file_path(file_path), language(language), status_diagnostics(0, 0, 0) {
load(true);
@ -58,6 +59,15 @@ Source::BaseView::BaseView(const boost::filesystem::path &file_path, const Glib:
search_context = gtk_source_search_context_new(get_source_buffer()->gobj(), search_settings);
gtk_source_search_context_set_highlight(search_context, true);
g_signal_connect(search_context, "notify::occurrences-count", G_CALLBACK(search_occurrences_updated), this);
set_snippets();
snippet_argument_tag = get_buffer()->create_tag();
Gdk::RGBA rgba;
rgba.set_rgba(0.5, 0.5, 0.5, 0.4);
snippet_argument_tag->property_background_rgba() = rgba;
snippet_argument_tag->property_background_set() = true;
}
Source::BaseView::~BaseView() {
@ -801,7 +811,6 @@ void Source::BaseView::paste() {
// add final newline if present in text
if(text.size() > 0 && text.back() == '\n')
get_buffer()->insert_at_cursor('\n' + prefix_tabs);
get_buffer()->place_cursor(get_buffer()->get_insert()->get_iter());
get_buffer()->end_user_action();
scroll_to_cursor_delayed(this, false, false);
}
@ -916,3 +925,225 @@ void Source::BaseView::search_occurrences_updated(GtkWidget *widget, GParamSpec
if(view->update_search_occurrences)
view->update_search_occurrences(gtk_source_search_context_get_occurrences_count(view->search_context));
}
void Source::BaseView::set_snippets() {
std::lock_guard<std::mutex> lock(snippets_mutex);
snippets = nullptr;
if(language) {
for(auto &pair : Snippets::get().snippets) {
std::smatch sm;
if(std::regex_match(language->get_id().raw(), sm, pair.first)) {
snippets = &pair.second;
break;
}
}
}
}
void Source::BaseView::setup_extra_cursor_signals() {
if(extra_cursors_signals_set)
return;
extra_cursors_signals_set = true;
auto last_insert = get_buffer()->create_mark(get_buffer()->get_insert()->get_iter(), false);
get_buffer()->signal_mark_set().connect([this, last_insert](const Gtk::TextBuffer::iterator &iter, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) mutable {
for(auto &extra_cursor : extra_cursors) {
if(extra_cursor.first == mark && !iter.ends_line()) {
extra_cursor.second = std::max(extra_cursor.second, iter.get_line_offset());
break;
}
}
if(mark->get_name() == "insert") {
if(!keep_snippet_marks)
clear_snippet_marks();
if(enable_multiple_cursors) {
enable_multiple_cursors = false;
auto offset_diff = mark->get_iter().get_offset() - last_insert->get_iter().get_offset();
if(offset_diff != 0) {
for(auto &extra_cursor : extra_cursors) {
auto iter = extra_cursor.first->get_iter();
iter.forward_chars(offset_diff);
get_buffer()->move_mark(extra_cursor.first, iter);
}
for(auto &extra_cursor : extra_snippet_cursors) {
auto iter = extra_cursor->get_iter();
iter.forward_chars(offset_diff);
get_buffer()->move_mark(extra_cursor, iter);
}
}
enable_multiple_cursors = true;
}
get_buffer()->move_mark(last_insert, mark->get_iter());
}
});
get_buffer()->signal_insert().connect([this](const Gtk::TextBuffer::iterator &iter, const Glib::ustring &text, int bytes) {
if(enable_multiple_cursors && (!extra_cursors.empty() || !extra_snippet_cursors.empty())) {
enable_multiple_cursors = false;
auto offset = iter.get_offset() - get_buffer()->get_insert()->get_iter().get_offset();
if(offset > 0)
offset -= text.size();
for(auto &extra_cursor : extra_cursors) {
auto iter = extra_cursor.first->get_iter();
iter.forward_chars(offset);
get_buffer()->insert(iter, text);
auto extra_cursor_iter = extra_cursor.first->get_iter();
if(!extra_cursor_iter.ends_line())
extra_cursor.second = extra_cursor_iter.get_line_offset();
}
for(auto &extra_cursor : extra_snippet_cursors) {
auto iter = extra_cursor->get_iter();
iter.forward_chars(offset);
get_buffer()->insert(iter, text);
}
enable_multiple_cursors = true;
}
});
auto erase_backward_length = std::make_shared<int>(0);
auto erase_forward_length = std::make_shared<int>(0);
get_buffer()->signal_erase().connect([this, erase_backward_length, erase_forward_length](const Gtk::TextBuffer::iterator &iter_start, const Gtk::TextBuffer::iterator &iter_end) {
if(enable_multiple_cursors && (!extra_cursors.empty() || !extra_snippet_cursors.empty())) {
auto insert_offset = get_buffer()->get_insert()->get_iter().get_offset();
*erase_backward_length = insert_offset - iter_start.get_offset();
*erase_forward_length = iter_end.get_offset() - insert_offset;
}
}, false);
get_buffer()->signal_erase().connect([this, erase_backward_length, erase_forward_length](const Gtk::TextBuffer::iterator &iter_start, const Gtk::TextBuffer::iterator &iter_end) {
if(enable_multiple_cursors && (*erase_backward_length != 0 || *erase_forward_length != 0)) {
enable_multiple_cursors = false;
for(auto &extra_cursor : extra_cursors) {
auto start_iter = extra_cursor.first->get_iter();
auto end_iter = start_iter;
start_iter.backward_chars(*erase_backward_length);
end_iter.forward_chars(*erase_forward_length);
get_buffer()->erase(start_iter, end_iter);
auto extra_cursor_iter = extra_cursor.first->get_iter();
if(!extra_cursor_iter.ends_line())
extra_cursor.second = extra_cursor_iter.get_line_offset();
}
for(auto &extra_cursor : extra_snippet_cursors) {
auto start_iter = extra_cursor->get_iter();
auto end_iter = start_iter;
start_iter.backward_chars(*erase_backward_length);
end_iter.forward_chars(*erase_forward_length);
get_buffer()->erase(start_iter, end_iter);
}
enable_multiple_cursors = true;
*erase_backward_length = 0;
*erase_forward_length = 0;
}
});
}
void Source::BaseView::insert_snippet(Gtk::TextIter iter, Glib::ustring snippet) {
std::map<size_t, std::vector<std::pair<size_t, size_t>>> arguments_offsets;
size_t pos1 = 0;
while((pos1 = snippet.find("${", pos1)) != Glib::ustring::npos) {
size_t pos2 = snippet.find(":", pos1 + 2);
if(pos2 != Glib::ustring::npos) {
size_t pos3 = snippet.find("}", pos2 + 1);
if(pos3 != Glib::ustring::npos) {
try {
auto number = std::stoul(snippet.substr(pos1 + 2, pos2 - pos1 - 2));
size_t length = pos3 - pos2 - 1;
snippet.erase(pos3, 1);
snippet.erase(pos1, pos2 - pos1 + 1);
arguments_offsets[number].emplace_back(pos1, pos1 + length);
pos1 += length;
continue;
}
catch(...) {
}
}
}
break;
}
auto mark = get_buffer()->create_mark(iter);
get_buffer()->insert(iter, snippet);
iter = mark->get_iter();
get_buffer()->delete_mark(mark);
for(auto arguments_offsets_it = arguments_offsets.rbegin(); arguments_offsets_it != arguments_offsets.rend(); ++arguments_offsets_it) {
snippets_marks.emplace_front();
for(auto &offsets : arguments_offsets_it->second) {
auto start = iter;
auto end = start;
start.forward_chars(offsets.first);
end.forward_chars(offsets.second);
snippets_marks.front().emplace_back(get_buffer()->create_mark(start), get_buffer()->create_mark(end));
get_buffer()->apply_tag(snippet_argument_tag, start, end);
}
}
if(!arguments_offsets.empty())
select_snippet_argument();
}
bool Source::BaseView::select_snippet_argument() {
if(!extra_snippet_cursors.empty()) {
for(auto &extra_cursor : extra_snippet_cursors) {
extra_cursor->set_visible(false);
get_buffer()->delete_mark(extra_cursor);
}
extra_snippet_cursors.clear();
}
if(!snippets_marks.empty()) {
auto snippets_marks_it = snippets_marks.begin();
bool first = true;
for(auto &marks : *snippets_marks_it) {
auto start = marks.first->get_iter();
auto end = marks.second->get_iter();
if(first) {
keep_snippet_marks = true;
get_buffer()->select_range(start, end);
keep_snippet_marks = false;
first = false;
}
else {
extra_snippet_cursors.emplace_back(get_buffer()->create_mark(start, false));
extra_snippet_cursors.back()->set_visible(true);
setup_extra_cursor_signals();
}
get_buffer()->delete_mark(marks.first);
get_buffer()->delete_mark(marks.second);
}
snippets_marks.erase(snippets_marks_it);
return true;
}
return false;
}
bool Source::BaseView::clear_snippet_marks() {
bool cleared = false;
if(!snippets_marks.empty()) {
for(auto &snippet_marks : snippets_marks) {
for(auto &pair : snippet_marks) {
get_buffer()->delete_mark(pair.first);
get_buffer()->delete_mark(pair.second);
}
}
snippets_marks.clear();
cleared = true;
}
if(!extra_snippet_cursors.empty()) {
for(auto &extra_cursor : extra_snippet_cursors) {
extra_cursor->set_visible(false);
get_buffer()->delete_mark(extra_cursor);
}
extra_snippet_cursors.clear();
cleared = true;
}
get_buffer()->remove_tag(snippet_argument_tag, get_buffer()->begin(), get_buffer()->end());
return cleared;
}

21
src/source_base.h

@ -1,9 +1,13 @@
#pragma once
#include "snippets.h"
#include <boost/filesystem.hpp>
#include <gtksourceviewmm.h>
#include <list>
#include <mutex>
#include <regex>
#include <set>
#include <vector>
namespace Source {
class BaseView : public Gsv::View {
@ -75,6 +79,8 @@ namespace Source {
bool disable_spellcheck = false;
void set_snippets();
private:
GtkSourceSearchContext *search_context;
GtkSourceSearchSettings *search_settings;
@ -121,5 +127,20 @@ namespace Source {
void cleanup_whitespace_characters(const Gtk::TextIter &iter);
bool enable_multiple_cursors = false;
std::vector<std::pair<Glib::RefPtr<Gtk::TextBuffer::Mark>, int>> extra_cursors;
std::vector<Glib::RefPtr<Gtk::TextBuffer::Mark>> extra_snippet_cursors;
void setup_extra_cursor_signals();
bool extra_cursors_signals_set = false;
/// After inserting a snippet, one can use tab to select the next argument
bool keep_snippet_marks = false;
std::vector<Snippets::Snippet> *snippets = nullptr;
std::mutex snippets_mutex;
std::list<std::vector<std::pair<Glib::RefPtr<Gtk::TextBuffer::Mark>, Glib::RefPtr<Gtk::TextBuffer::Mark>>>> snippets_marks;
Glib::RefPtr<Gtk::TextTag> snippet_argument_tag;
void insert_snippet(Gtk::TextIter iter, Glib::ustring snippet);
bool select_snippet_argument();
bool clear_snippet_marks();
};
} // namespace Source

53
src/source_clang.cc

@ -540,6 +540,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa
if(!is_code_iter(iter))
return false;
enable_snippets = false;
show_parameters = false;
auto line = ' ' + get_line_before();
@ -551,6 +552,8 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa
{
std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex);
autocomplete.prefix = sm.length(2) ? sm[3].str() : sm.length(4) ? sm[5].str() : sm[6].str();
if(!sm.length(2) && !sm.length(4))
enable_snippets = true;
}
return true;
}
@ -567,8 +570,20 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa
}
if(iter != end_iter)
iter.forward_char();
std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex);
autocomplete.prefix = get_buffer()->get_text(iter, end_iter);
{
std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex);
autocomplete.prefix = get_buffer()->get_text(iter, end_iter);
}
auto prev1 = iter;
if(prev1.backward_char() && *prev1 != '.') {
auto prev2 = prev1;
if(!prev2.backward_char())
enable_snippets = true;
else if(!(*prev2 == ':' && *prev1 == ':') && !(*prev2 == '-' && *prev1 == '>'))
enable_snippets = true;
}
return true;
}
@ -605,13 +620,15 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa
}
if(autocomplete.state == Autocomplete::State::STARTING) {
std::string prefix_copy;
std::string prefix;
{
std::lock_guard<std::mutex> lock(autocomplete.prefix_mutex);
prefix_copy = autocomplete.prefix;
prefix = autocomplete.prefix;
}
completion_strings.clear();
snippet_inserts.clear();
snippet_comments.clear();
for(unsigned i = 0; i < code_complete_results->size(); ++i) {
auto result = code_complete_results->get(i);
if(result.available()) {
@ -654,7 +671,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa
if(kind != clangmm::CompletionChunk_Informative) {
auto chunk_cstr = clangmm::String(clang_getCompletionChunkText(result.cx_completion_string, i));
if(kind == clangmm::CompletionChunk_TypedText) {
if(strlen(chunk_cstr.c_str) >= prefix_copy.size() && prefix_copy.compare(0, prefix_copy.size(), chunk_cstr.c_str, prefix_copy.size()) == 0)
if(strlen(chunk_cstr.c_str) >= prefix.size() && prefix.compare(0, prefix.size(), chunk_cstr.c_str, prefix.size()) == 0)
match = true;
else
break;
@ -674,6 +691,19 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa
}
}
}
if(!show_parameters && enable_snippets) {
std::lock_guard<std::mutex> lock(snippets_mutex);
if(snippets) {
for(auto &snippet : *snippets) {
if(prefix.compare(0, prefix.size(), snippet.prefix, 0, prefix.size()) == 0) {
autocomplete.rows.emplace_back(snippet.prefix);
completion_strings.emplace_back(nullptr);
snippet_inserts.emplace(autocomplete.rows.size() - 1, snippet.body);
snippet_comments.emplace(autocomplete.rows.size() - 1, snippet.description);
}
}
}
}
}
};
@ -691,6 +721,17 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa
};
autocomplete.on_select = [this](unsigned int index, const std::string &text, bool hide_window) {
if(!completion_strings[index]) { // Insert snippet instead
get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), get_buffer()->get_insert()->get_iter());
if(!hide_window) {
get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), text);
}
else
insert_snippet(CompletionDialog::get()->start_mark->get_iter(), snippet_inserts[index]);
return;
}
std::string row;
auto pos = text.find("");
if(pos != std::string::npos)
@ -779,7 +820,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa
};
autocomplete.get_tooltip = [this](unsigned int index) {
return clangmm::to_string(clang_getCompletionBriefComment(completion_strings[index]));
return completion_strings[index] ? clangmm::to_string(clang_getCompletionBriefComment(completion_strings[index])) : snippet_comments[index];
};
}

4
src/source_clang.h

@ -65,6 +65,10 @@ namespace Source {
std::vector<CXCompletionString> completion_strings;
sigc::connection delayed_show_arguments_connection;
std::atomic<bool> enable_snippets = {false};
std::map<size_t, std::string> snippet_inserts;
std::map<size_t, std::string> snippet_comments;
private:
std::atomic<bool> show_parameters = {false};

346
src/source_generic.cc

@ -0,0 +1,346 @@
#include "source_generic.h"
#include "selection_dialog.h"
#include "snippets.h"
#include "terminal.h"
Source::GenericView::GenericView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language) : BaseView(file_path, language), View(file_path, language, true), autocomplete(this, interactive_completion, last_keyval, false) {
configure();
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;
for(auto &search_path : search_paths) {
boost::filesystem::path p(static_cast<std::string>(search_path) + '/' + static_cast<std::string>(language->get_id()) + ".lang");
if(boost::filesystem::exists(p) && boost::filesystem::is_regular_file(p)) {
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("Error: error parsing language file " + 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 == "<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);
}
}
else if(!has_context_class && node.first == "context") {
auto class_attribute = node.second.get<std::string>("<xmlattr>.class", "");
auto class_disabled_attribute = node.second.get<std::string>("<xmlattr>.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) {
}
}
}
bool Source::GenericView::is_word_iter(const Gtk::TextIter &iter) {
if(((*iter >= '0' && *iter <= '9') || (*iter >= 'A' && *iter <= 'Z') || (*iter >= 'a' && *iter <= 'z') || *iter >= 128))
return true;
return false;
}
std::pair<Gtk::TextIter, Gtk::TextIter> Source::GenericView::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};
}
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_word_iter(iter)) {
auto word = get_word(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());
std::lock_guard<std::mutex> 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::TextBuffer::iterator &iter_, const Glib::ustring &text, int bytes) {
auto iter = iter_;
if(!is_word_iter(iter))
iter.backward_char();
if(is_word_iter(iter)) {
auto word = get_word(iter);
if(word.second.get_offset() - word.first.get_offset() >= 3) {
std::lock_guard<std::mutex> 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::TextBuffer::iterator &iter, const Glib::ustring &text, int bytes) {
auto start = iter;
auto end = iter;
start.backward_chars(text.size());
if(!is_word_iter(start))
start.backward_char();
end.forward_char();
auto words = get_words(start, end);
std::lock_guard<std::mutex> 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::TextBuffer::iterator &start_, const Gtk::TextBuffer::iterator &end_) {
auto start = start_;
auto end = end_;
if(!is_word_iter(start))
start.backward_char();
end.forward_char();
auto words = get_words(start, end);
std::lock_guard<std::mutex> 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::TextBuffer::iterator &start_, const Gtk::TextBuffer::iterator & /*end*/) {
auto start = start_;
if(!is_word_iter(start))
start.backward_char();
if(is_word_iter(start)) {
auto word = get_word(start);
if(word.second.get_offset() - word.first.get_offset() >= 3) {
std::lock_guard<std::mutex> 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.is_continue_key = [](guint keyval) {
if((keyval >= '0' && keyval <= '9') || (keyval >= 'a' && keyval <= 'z') || (keyval >= 'A' && keyval <= 'Z') || keyval == '_' || gdk_keyval_to_unicode(keyval) >= 0x00C0)
return true;
return false;
};
autocomplete.is_restart_key = [](guint keyval) {
return false;
};
autocomplete.run_check = [this]() {
auto start = get_buffer()->get_insert()->get_iter();
auto end = start;
size_t count = 0;
while(start.backward_char() && ((*start >= '0' && *start <= '9') || (*start >= 'a' && *start <= 'z') || (*start >= 'A' && *start <= 'Z') || *start == '_' || *start >= 0x00C0))
++count;
if((start.is_start() || start.forward_char()) && count >= 3 && !(*start >= '0' && *start <= '9')) {
std::lock(autocomplete.prefix_mutex, buffer_words_mutex);
std::lock_guard<std::mutex> lock1(autocomplete.prefix_mutex, std::adopt_lock);
std::lock_guard<std::mutex> lock2(buffer_words_mutex, std::adopt_lock);
autocomplete.prefix = get_buffer()->get_text(start, end);
show_prefix_buffer_word = buffer_words.find(autocomplete.prefix) != buffer_words.end();
return true;
}
else if(!interactive_completion) {
auto end_iter = get_buffer()->get_insert()->get_iter();
auto iter = end_iter;
while(iter.backward_char() && autocomplete.is_continue_key(*iter)) {
}
if(iter != end_iter)
iter.forward_char();
std::lock(autocomplete.prefix_mutex, buffer_words_mutex);
std::lock_guard<std::mutex> lock1(autocomplete.prefix_mutex, std::adopt_lock);
std::lock_guard<std::mutex> lock2(buffer_words_mutex, std::adopt_lock);
autocomplete.prefix = get_buffer()->get_text(iter, end_iter);
show_prefix_buffer_word = buffer_words.find(autocomplete.prefix) != buffer_words.end();
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.on_add_rows_error = [this] {
autocomplete_comment.clear();
autocomplete_insert.clear();
};
autocomplete.add_rows = [this](std::string &buffer, int line_number, int column) {
if(autocomplete.state == Autocomplete::State::STARTING) {
autocomplete_comment.clear();
autocomplete_insert.clear();
std::string prefix;
{
std::lock_guard<std::mutex> lock(autocomplete.prefix_mutex);
prefix = autocomplete.prefix;
}
for(auto &keyword : keywords) {
if(prefix.compare(0, prefix.size(), keyword, 0, prefix.size()) == 0) {
autocomplete.rows.emplace_back(keyword);
autocomplete_insert.emplace_back(keyword);
autocomplete_comment.emplace_back("");
}
}
bool show_prefix_buffer_word = this->show_prefix_buffer_word;
{
std::lock_guard<std::mutex> lock(buffer_words_mutex);
for(auto &buffer_word : buffer_words) {
if((show_prefix_buffer_word || buffer_word.first.size() > prefix.size()) && prefix.compare(0, prefix.size(), buffer_word.first, 0, prefix.size()) == 0 &&
keywords.find(buffer_word.first) == keywords.end()) {
autocomplete.rows.emplace_back(buffer_word.first);
autocomplete_insert.emplace_back(buffer_word.first);
autocomplete_comment.emplace_back("");
}
}
}
std::lock_guard<std::mutex> lock(snippets_mutex);
if(snippets) {
for(auto &snippet : *snippets) {
if(prefix.compare(0, prefix.size(), snippet.prefix, 0, prefix.size()) == 0) {
autocomplete.rows.emplace_back(snippet.prefix);
autocomplete_insert.emplace_back(snippet.body);
autocomplete_comment.emplace_back(snippet.description);
}
}
}
}
};
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) {
Glib::ustring insert = hide_window ? autocomplete_insert[index] : text;
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(), insert);
else
get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert);
};
autocomplete.get_tooltip = [this](unsigned int index) {
return autocomplete_comment[index];
};
}

31
src/source_generic.h

@ -0,0 +1,31 @@
#pragma once
#include "autocomplete.h"
#include "source.h"
#include <atomic>
namespace Source {
class GenericView : public View {
public:
GenericView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language);
~GenericView();
private:
void parse_language_file(bool &has_context_class, const boost::property_tree::ptree &pt);
std::set<std::string> keywords;
bool is_word_iter(const Gtk::TextIter &iter);
std::pair<Gtk::TextIter, Gtk::TextIter> get_word(Gtk::TextIter iter);
std::vector<std::pair<Gtk::TextIter, Gtk::TextIter>> get_words(const Gtk::TextIter &start, const Gtk::TextIter &end);
std::map<std::string, size_t> buffer_words;
void setup_buffer_words();
std::atomic<bool> show_prefix_buffer_word = {false}; /// To avoid showing the current word if it is unique in document
Autocomplete autocomplete;
std::vector<std::string> autocomplete_comment;
std::vector<std::string> autocomplete_insert;
void setup_autocomplete();
};
} // namespace Source

118
src/source_language_protocol.cc

@ -1251,6 +1251,7 @@ void Source::LanguageProtocolView::setup_autocomplete() {
if(!is_code_iter(iter))
return false;
autocomplete_enable_snippets = false;
autocomplete_show_parameters = false;
auto line = ' ' + get_line_before();
@ -1262,6 +1263,8 @@ void Source::LanguageProtocolView::setup_autocomplete() {
{
std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex);
autocomplete.prefix = sm.length(2) ? sm[3].str() : sm.length(4) ? sm[5].str() : sm[6].str();
if(!sm.length(2) && !sm.length(4))
autocomplete_enable_snippets = true;
}
return true;
}
@ -1278,8 +1281,20 @@ void Source::LanguageProtocolView::setup_autocomplete() {
}
if(iter != end_iter)
iter.forward_char();
std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex);
autocomplete.prefix = get_buffer()->get_text(iter, end_iter);
{
std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex);
autocomplete.prefix = get_buffer()->get_text(iter, end_iter);
}
auto prev1 = iter;
if(prev1.backward_char() && *prev1 != '.') {
auto prev2 = prev1;
if(!prev2.backward_char())
autocomplete_enable_snippets = true;
else if(!(*prev2 == ':' && *prev1 == ':'))
autocomplete_enable_snippets = true;
}
return true;
}
@ -1382,6 +1397,24 @@ void Source::LanguageProtocolView::setup_autocomplete() {
autocomplete_insert.emplace_back(std::move(insert));
}
}
if(autocomplete_enable_snippets) {
std::string prefix;
{
std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex);
prefix = autocomplete.prefix;
}
std::lock_guard<std::mutex> lock(snippets_mutex);
if(snippets) {
for(auto &snippet : *snippets) {
if(prefix.compare(0, prefix.size(), snippet.prefix, 0, prefix.size()) == 0) {
autocomplete.rows.emplace_back(snippet.prefix);
autocomplete_insert.emplace_back(snippet.body);
autocomplete_comment.emplace_back(snippet.description);
}
}
}
}
}
result_processed.set_value();
});
@ -1390,44 +1423,7 @@ void Source::LanguageProtocolView::setup_autocomplete() {
}
};
signal_key_press_event().connect([this](GdkEventKey *event) {
if((event->keyval == GDK_KEY_Tab || event->keyval == GDK_KEY_ISO_Left_Tab) && (event->state & GDK_SHIFT_MASK) == 0) {
if(!argument_marks.empty()) {
auto it = argument_marks.begin();
auto start = it->first->get_iter();
auto end = it->second->get_iter();
if(start == end)
return false;
keep_argument_marks = true;
get_buffer()->select_range(it->first->get_iter(), it->second->get_iter());
keep_argument_marks = false;
get_buffer()->delete_mark(it->first);
get_buffer()->delete_mark(it->second);
argument_marks.erase(it);
return true;
}
}
return false;
}, false);
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(!keep_argument_marks) {
for(auto &pair : argument_marks) {
get_buffer()->delete_mark(pair.first);
get_buffer()->delete_mark(pair.second);
}
argument_marks.clear();
}
}
});
autocomplete.on_show = [this] {
for(auto &pair : argument_marks) {
get_buffer()->delete_mark(pair.first);
get_buffer()->delete_mark(pair.second);
}
argument_marks.clear();
hide_tooltips();
};
@ -1452,56 +1448,18 @@ void Source::LanguageProtocolView::setup_autocomplete() {
if(hide_window) {
if(autocomplete_show_parameters) {
if(has_named_parameters()) { // Do not select named parameters in for instance Python
if(has_named_parameters()) // Do not select named parameters in for instance Python
get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert);
return;
}
else {
get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert);
int start_offset = CompletionDialog::get()->start_mark->get_iter().get_offset();
int end_offset = CompletionDialog::get()->start_mark->get_iter().get_offset() + insert.size();
get_buffer()->select_range(get_buffer()->get_iter_at_offset(start_offset), get_buffer()->get_iter_at_offset(end_offset));
return;
}
return;
}
// Find and add position marks that one can move to using tab-key
size_t pos1 = 0;
std::vector<std::pair<size_t, size_t>> mark_offsets;
while((pos1 = insert.find("${"), pos1) != Glib::ustring::npos) {
size_t pos2 = insert.find(":", pos1 + 2);
if(pos2 != Glib::ustring::npos) {
size_t pos3 = insert.find("}", pos2 + 1);
if(pos3 != Glib::ustring::npos) {
size_t length = pos3 - pos2 - 1;
insert.erase(pos3, 1);
insert.erase(pos1, pos2 - pos1 + 1);
mark_offsets.emplace_back(pos1, pos1 + length);
pos1 += length;
}
else
break;
}
else
break;
}
get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert);
for(auto &offset : mark_offsets) {
auto start = CompletionDialog::get()->start_mark->get_iter();
auto end = start;
start.forward_chars(offset.first);
end.forward_chars(offset.second);
argument_marks.emplace_back(get_buffer()->create_mark(start), get_buffer()->create_mark(end));
}
if(!argument_marks.empty()) {
auto it = argument_marks.begin();
keep_argument_marks = true;
get_buffer()->select_range(it->first->get_iter(), it->second->get_iter());
keep_argument_marks = false;
get_buffer()->delete_mark(it->first);
get_buffer()->delete_mark(it->second);
argument_marks.erase(it);
}
insert_snippet(CompletionDialog::get()->start_mark->get_iter(), insert);
}
else
get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert);

3
src/source_language_protocol.h

@ -2,6 +2,7 @@
#include "autocomplete.h"
#include "process.hpp"
#include "source.h"
#include <atomic>
#include <boost/property_tree/json_parser.hpp>
#include <list>
#include <map>
@ -162,7 +163,7 @@ namespace Source {
void setup_autocomplete();
std::vector<std::string> autocomplete_comment;
std::vector<std::string> autocomplete_insert;
std::list<std::pair<Glib::RefPtr<Gtk::TextBuffer::Mark>, Glib::RefPtr<Gtk::TextBuffer::Mark>>> argument_marks;
std::atomic<bool> autocomplete_enable_snippets = {false};
bool autocomplete_show_parameters = false;
sigc::connection autocomplete_delayed_show_arguments_connection;

3
src/window.cc

@ -191,6 +191,9 @@ void Window::set_menu_actions() {
menu.add_action("preferences", []() {
Notebook::get().open(Config::get().home_juci_path / "config" / "config.json");
});
menu.add_action("snippets", []() {
Notebook::get().open(Config::get().home_juci_path / "snippets.json");
});
menu.add_action("quit", [this]() {
close();
});

4
tests/CMakeLists.txt

@ -46,6 +46,10 @@ add_executable(source_clang_test source_clang_test.cc $<TARGET_OBJECTS:test_stub
target_link_libraries(source_clang_test juci_shared)
add_test(source_clang_test source_clang_test)
add_executable(source_generic_test source_generic_test.cc $<TARGET_OBJECTS:test_stubs>)
target_link_libraries(source_generic_test juci_shared)
add_test(source_generic_test source_generic_test)
add_executable(source_key_test source_key_test.cc $<TARGET_OBJECTS:test_stubs>)
target_link_libraries(source_key_test juci_shared)
add_test(source_key_test source_key_test)

143
tests/source_generic_test.cc

@ -0,0 +1,143 @@
#include "source_generic.h"
#include <glib.h>
int main() {
auto app = Gtk::Application::create();
Gsv::init();
auto tests_path = boost::filesystem::canonical(JUCI_TESTS_PATH);
auto source_file = tests_path / "tmp" / "source_file.md";
auto language_manager = Gsv::LanguageManager::get_default();
auto language = language_manager->get_language("markdown");
Source::GenericView view(source_file, language);
// Test buffer words
{
std::map<std::string, size_t> buffer_words = {};
assert(view.buffer_words == buffer_words);
view.get_buffer()->set_text("testing 1 2 3");
buffer_words = {{"testing", 1}};
assert(view.buffer_words == buffer_words);
view.get_buffer()->set_text("");
assert(view.buffer_words.empty());
view.get_buffer()->set_text("\ntest ing te\n");
buffer_words = {{"test", 1}, {"ing", 1}};
assert(view.buffer_words == buffer_words);
view.get_buffer()->set_text("test ing\ntest ing te\n");
buffer_words = {{"test", 2}, {"ing", 2}};
assert(view.buffer_words == buffer_words);
auto start = view.get_buffer()->begin();
start.forward_chars(4);
auto end = start;
end.forward_char();
view.get_buffer()->erase(start, end);
buffer_words = {{"testing", 1}, {"test", 1}, {"ing", 1}};
assert(view.buffer_words == buffer_words);
start = view.get_buffer()->begin();
start.forward_chars(4);
view.get_buffer()->insert(start, " ");
buffer_words = {{"test", 2}, {"ing", 2}};
assert(view.buffer_words == buffer_words);
view.get_buffer()->erase(view.get_buffer()->begin(), view.get_buffer()->end());
assert(view.buffer_words.empty());
view.get_buffer()->set_text("this is this");
buffer_words = {{"this", 2}};
assert(view.buffer_words == buffer_words);
start = view.get_buffer()->begin();
start.forward_chars(4);
end = start;
end.forward_char();
view.get_buffer()->erase(start, end);
buffer_words = {{"thisis", 1}, {"this", 1}};
assert(view.buffer_words == buffer_words);
start = view.get_buffer()->begin();
start.forward_chars(4);
view.get_buffer()->insert(start, "\n\n");
buffer_words = {{"this", 2}};
assert(view.buffer_words == buffer_words);
view.get_buffer()->set_text("test");
buffer_words = {{"test", 1}};
assert(view.buffer_words == buffer_words);
start = view.get_buffer()->begin();
end = start;
end.forward_chars(2);
view.get_buffer()->erase(start, end);
assert(view.buffer_words.empty());
start = view.get_buffer()->begin();
view.get_buffer()->insert(start, "te");
buffer_words = {{"test", 1}};
assert(view.buffer_words == buffer_words);
start = view.get_buffer()->begin();
start.forward_chars(2);
end = start;
end.forward_chars(2);
view.get_buffer()->erase(start, end);
assert(view.buffer_words.empty());
start = view.get_buffer()->begin();
start.forward_chars(2);
view.get_buffer()->insert(start, "st");
buffer_words = {{"test", 1}};
assert(view.buffer_words == buffer_words);
view.get_buffer()->set_text("\ntest\n");
buffer_words = {{"test", 1}};
assert(view.buffer_words == buffer_words);
start = view.get_buffer()->begin();
start.forward_char();
end = start;
end.forward_chars(2);
view.get_buffer()->erase(start, end);
assert(view.buffer_words.empty());
start = view.get_buffer()->begin();
start.forward_char();
view.get_buffer()->insert(start, "te");
buffer_words = {{"test", 1}};
assert(view.buffer_words == buffer_words);
start = view.get_buffer()->begin();
start.forward_chars(3);
end = start;
end.forward_chars(2);
view.get_buffer()->erase(start, end);
assert(view.buffer_words.empty());
start = view.get_buffer()->begin();
start.forward_chars(3);
view.get_buffer()->insert(start, "st");
buffer_words = {{"test", 1}};
assert(view.buffer_words == buffer_words);
start = view.get_buffer()->begin();
start.forward_chars(2);
view.get_buffer()->insert(start, "ee");
buffer_words = {{"teeest", 1}};
assert(view.buffer_words == buffer_words);
start = view.get_buffer()->begin();
start.forward_chars(2);
end = start;
end.forward_chars(2);
view.get_buffer()->erase(start, end);
buffer_words = {{"test", 1}};
assert(view.buffer_words == buffer_words);
}
}
Loading…
Cancel
Save