Browse Source

Added syntax highlighting to tooltips, and some various cleanups.

pipelines/235045657
eidheim 6 years ago
parent
commit
b92da8dc12
  1. 2
      src/autocomplete.cpp
  2. 6
      src/autocomplete.hpp
  3. 4
      src/compile_commands.cpp
  4. 18
      src/debug_lldb.cpp
  5. 2
      src/dialogs_win.cpp
  6. 4
      src/filesystem.cpp
  7. 8
      src/git.cpp
  8. 7
      src/notebook.hpp
  9. 2
      src/project.cpp
  10. 7
      src/selection_dialog.cpp
  11. 5
      src/selection_dialog.hpp
  12. 55
      src/source.cpp
  13. 21
      src/source_base.cpp
  14. 28
      src/source_base.hpp
  15. 15
      src/source_clang.cpp
  16. 105
      src/source_language_protocol.cpp
  17. 17
      src/source_language_protocol.hpp
  18. 34
      src/source_spellcheck.cpp
  19. 3
      src/terminal.cpp
  20. 219
      src/tooltips.cpp
  21. 17
      src/tooltips.hpp
  22. 31
      src/window.cpp
  23. 2
      tests/stubs/selection_dialog.cpp
  24. 1
      tests/tooltips_test.cpp

2
src/autocomplete.cpp

@ -1,7 +1,7 @@
#include "autocomplete.hpp" #include "autocomplete.hpp"
#include "selection_dialog.hpp" #include "selection_dialog.hpp"
Autocomplete::Autocomplete(Gtk::TextView *view, bool &interactive_completion, guint &last_keyval, bool pass_buffer_and_strip_word) Autocomplete::Autocomplete(Gsv::View *view, bool &interactive_completion, guint &last_keyval, bool pass_buffer_and_strip_word)
: view(view), interactive_completion(interactive_completion), pass_buffer_and_strip_word(pass_buffer_and_strip_word) { : view(view), interactive_completion(interactive_completion), pass_buffer_and_strip_word(pass_buffer_and_strip_word) {
view->get_buffer()->signal_changed().connect([this, &last_keyval] { view->get_buffer()->signal_changed().connect([this, &last_keyval] {
if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) { if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) {

6
src/autocomplete.hpp

@ -7,9 +7,9 @@
#include <thread> #include <thread>
class Autocomplete { class Autocomplete {
Gtk::TextView *view; Gsv::View *view;
bool &interactive_completion; bool &interactive_completion;
/// If text_view buffer should be passed to add_rows. Empty buffer is passed if not. /// If view buffer should be passed to add_rows. Empty buffer is passed if not.
/// Also, some utilities, like libclang, require that autocomplete is started at the beginning of a word. /// Also, some utilities, like libclang, require that autocomplete is started at the beginning of a word.
bool pass_buffer_and_strip_word; bool pass_buffer_and_strip_word;
@ -52,7 +52,7 @@ public:
std::function<std::function<void(Tooltip &tooltip)>(unsigned int)> set_tooltip_buffer = [](unsigned int index) { return nullptr; }; std::function<std::function<void(Tooltip &tooltip)>(unsigned int)> set_tooltip_buffer = [](unsigned int index) { return nullptr; };
Autocomplete(Gtk::TextView *view, bool &interactive_completion, guint &last_keyval, bool pass_buffer_and_strip_word); Autocomplete(Gsv::View *view, bool &interactive_completion, guint &last_keyval, bool pass_buffer_and_strip_word);
void run(); void run();
void stop(); void stop();

4
src/compile_commands.cpp

@ -180,7 +180,7 @@ std::vector<std::string> CompileCommands::get_arguments(const boost::filesystem:
if(std::regex_match(clang_version_string, sm, clang_version_regex)) { if(std::regex_match(clang_version_string, sm, clang_version_regex)) {
auto clang_version = sm[1].str(); auto clang_version = sm[1].str();
auto env_msystem_prefix = std::getenv("MSYSTEM_PREFIX"); auto env_msystem_prefix = std::getenv("MSYSTEM_PREFIX");
if(env_msystem_prefix != nullptr) if(env_msystem_prefix)
arguments.emplace_back("-I" + (boost::filesystem::path(env_msystem_prefix) / "lib/clang" / clang_version / "include").string()); arguments.emplace_back("-I" + (boost::filesystem::path(env_msystem_prefix) / "lib/clang" / clang_version / "include").string());
} }
#endif #endif
@ -202,7 +202,7 @@ std::vector<std::string> CompileCommands::get_arguments(const boost::filesystem:
#endif #endif
#ifdef _WIN32 #ifdef _WIN32
auto env_msystem_prefix = std::getenv("MSYSTEM_PREFIX"); auto env_msystem_prefix = std::getenv("MSYSTEM_PREFIX");
if(env_msystem_prefix != nullptr) if(env_msystem_prefix)
arguments.emplace_back("-I" + (boost::filesystem::path(env_msystem_prefix) / "lib/clang" / clang_version / "include").string()); arguments.emplace_back("-I" + (boost::filesystem::path(env_msystem_prefix) / "lib/clang" / clang_version / "include").string());
#endif #endif
} }

18
src/debug_lldb.cpp

@ -187,7 +187,7 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat
for(auto &e : environment_from_arguments) for(auto &e : environment_from_arguments)
environment.emplace_back(e.c_str()); environment.emplace_back(e.c_str());
size_t environ_size = 0; size_t environ_size = 0;
while(environ[environ_size] != nullptr) while(environ[environ_size])
++environ_size; ++environ_size;
for(size_t c = 0; c < environ_size; ++c) for(size_t c = 0; c < environ_size; ++c)
environment.emplace_back(environ[c]); environment.emplace_back(environ[c]);
@ -340,13 +340,11 @@ std::vector<Debug::LLDB::Frame> Debug::LLDB::get_backtrace() {
backtrace_frame.index = c_f; backtrace_frame.index = c_f;
if(frame.GetFunctionName() != nullptr) if(auto function_name = frame.GetFunctionName())
backtrace_frame.function_name = frame.GetFunctionName(); backtrace_frame.function_name = function_name;
auto module_filename = frame.GetModule().GetFileSpec().GetFilename(); if(auto module_filename = frame.GetModule().GetFileSpec().GetFilename())
if(module_filename != nullptr) {
backtrace_frame.module_filename = module_filename; backtrace_frame.module_filename = module_filename;
}
auto line_entry = frame.GetLineEntry(); auto line_entry = frame.GetLineEntry();
if(line_entry.IsValid()) { if(line_entry.IsValid()) {
@ -381,8 +379,8 @@ std::vector<Debug::LLDB::Variable> Debug::LLDB::get_variables() {
Debug::LLDB::Variable variable; Debug::LLDB::Variable variable;
variable.thread_index_id = thread.GetIndexID(); variable.thread_index_id = thread.GetIndexID();
variable.frame_index = c_f; variable.frame_index = c_f;
if(value.GetName() != nullptr) if(auto name = value.GetName())
variable.name = value.GetName(); variable.name = name;
value.GetDescription(stream); value.GetDescription(stream);
variable.value = stream.GetData(); variable.value = stream.GetData();
@ -442,7 +440,8 @@ std::string Debug::LLDB::get_value(const std::string &variable, const boost::fil
lldb::SBStream stream; lldb::SBStream stream;
auto value = values.GetValueAtIndex(value_index); auto value = values.GetValueAtIndex(value_index);
if(value.GetName() != nullptr && value.GetName() == variable) { if(auto name = value.GetName()) {
if(name == variable) {
auto declaration = value.GetDeclaration(); auto declaration = value.GetDeclaration();
if(declaration.IsValid()) { if(declaration.IsValid()) {
if(declaration.GetLine() == line_nr && (declaration.GetColumn() == 0 || declaration.GetColumn() == line_index)) { if(declaration.GetLine() == line_nr && (declaration.GetColumn() == 0 || declaration.GetColumn() == line_index)) {
@ -462,6 +461,7 @@ std::string Debug::LLDB::get_value(const std::string &variable, const boost::fil
} }
} }
} }
}
return {}; return {};
} }

2
src/dialogs_win.cpp

@ -19,7 +19,7 @@ public:
Win32Dialog(){}; Win32Dialog(){};
~Win32Dialog() { ~Win32Dialog() {
if(dialog != nullptr) if(dialog)
dialog->Release(); dialog->Release();
} }

4
src/filesystem.cpp

@ -86,10 +86,8 @@ boost::filesystem::path filesystem::get_home_path() noexcept {
if(!home_path.empty()) if(!home_path.empty())
return home_path; return home_path;
std::vector<std::string> environment_variables = {"HOME", "AppData"}; std::vector<std::string> environment_variables = {"HOME", "AppData"};
char *ptr = nullptr;
for(auto &variable : environment_variables) { for(auto &variable : environment_variables) {
ptr = std::getenv(variable.c_str()); if(auto ptr = std::getenv(variable.c_str())) {
if(ptr != nullptr) {
boost::system::error_code ec; boost::system::error_code ec;
boost::filesystem::path path(ptr); boost::filesystem::path path(ptr);
if(boost::filesystem::exists(path, ec)) { if(boost::filesystem::exists(path, ec)) {

8
src/git.cpp

@ -8,11 +8,11 @@ Git::Error Git::error;
std::string Git::Error::message() noexcept { std::string Git::Error::message() noexcept {
#if LIBGIT2_VER_MAJOR > 0 || (LIBGIT2_VER_MAJOR == 0 && LIBGIT2_VER_MINOR >= 28) #if LIBGIT2_VER_MAJOR > 0 || (LIBGIT2_VER_MAJOR == 0 && LIBGIT2_VER_MINOR >= 28)
const git_error *last_error = git_error_last(); auto last_error = git_error_last();
#else #else
const git_error *last_error = giterr_last(); auto last_error = giterr_last();
#endif #endif
if(last_error == nullptr) if(!last_error)
return std::string(); return std::string();
else else
return last_error->message; return last_error->message;
@ -253,7 +253,7 @@ std::shared_ptr<Git::Repository> Git::get_repository(const boost::filesystem::pa
} }
boost::filesystem::path Git::path(const char *cpath, boost::optional<size_t> cpath_length_) noexcept { boost::filesystem::path Git::path(const char *cpath, boost::optional<size_t> cpath_length_) noexcept {
if(cpath == nullptr) if(!cpath)
return boost::filesystem::path(); return boost::filesystem::path();
auto cpath_length = cpath_length_.value_or(strlen(cpath)); auto cpath_length = cpath_length_.value_or(strlen(cpath));
if(cpath_length > 0 && (cpath[cpath_length - 1] == '/' || cpath[cpath_length - 1] == '\\')) if(cpath_length > 0 && (cpath[cpath_length - 1] == '/' || cpath[cpath_length - 1] == '\\'))

7
src/notebook.hpp

@ -18,12 +18,9 @@ class Notebook : public Gtk::Paned {
class CursorLocation { class CursorLocation {
public: public:
CursorLocation(Source::View *view, const Gtk::TextIter &iter) : view(view), mark(iter.get_buffer()->create_mark(iter, false)) {} CursorLocation(Source::View *view, const Gtk::TextIter &iter) : view(view), mark(iter, false) {}
~CursorLocation() {
mark->get_buffer()->delete_mark(mark);
}
Source::View *view; Source::View *view;
Glib::RefPtr<Gtk::TextBuffer::Mark> mark; Source::Mark mark;
}; };
private: private:

2
src/project.cpp

@ -629,7 +629,7 @@ void Project::LLDB::debug_show_variables() {
next_char_iter++; next_char_iter++;
value.replace(iter, next_char_iter, "?"); value.replace(iter, next_char_iter, "?");
} }
tooltip.buffer->insert(tooltip.buffer->get_insert()->get_iter(), value.substr(0, value.size() - 1)); tooltip.insert_code(value.substr(0, value.size() - 1), {});
} }
}; };
if(view) { if(view) {

7
src/selection_dialog.cpp

@ -36,7 +36,7 @@ void SelectionDialogBase::ListViewText::clear() {
} }
SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, const boost::optional<Gtk::TextIter> &start_iter, bool show_search_entry, bool use_markup) SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, const boost::optional<Gtk::TextIter> &start_iter, bool show_search_entry, bool use_markup)
: start_mark(start_iter ? start_iter->get_buffer()->create_mark(*start_iter) : Glib::RefPtr<Gtk::TextBuffer::Mark>()), text_view(text_view), window(Gtk::WindowType::WINDOW_POPUP), vbox(Gtk::Orientation::ORIENTATION_VERTICAL), list_view_text(use_markup), show_search_entry(show_search_entry) { : start_mark(start_iter ? Source::Mark(*start_iter) : Source::Mark()), text_view(text_view), window(Gtk::WindowType::WINDOW_POPUP), vbox(Gtk::Orientation::ORIENTATION_VERTICAL), list_view_text(use_markup), show_search_entry(show_search_entry) {
auto g_application = g_application_get_default(); auto g_application = g_application_get_default();
auto gio_application = Glib::wrap(g_application, true); auto gio_application = Glib::wrap(g_application, true);
auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application);
@ -141,11 +141,6 @@ SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, const boost::
}); });
} }
SelectionDialogBase::~SelectionDialogBase() {
if(text_view)
text_view->get_buffer()->delete_mark(start_mark);
}
void SelectionDialogBase::cursor_changed() { void SelectionDialogBase::cursor_changed() {
if(!is_visible()) if(!is_visible())
return; return;

5
src/selection_dialog.hpp

@ -1,4 +1,5 @@
#pragma once #pragma once
#include "source_base.hpp"
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <functional> #include <functional>
#include <gtkmm.h> #include <gtkmm.h>
@ -38,7 +39,7 @@ class SelectionDialogBase {
public: public:
SelectionDialogBase(Gtk::TextView *text_view, const boost::optional<Gtk::TextIter> &start_iter, bool show_search_entry, bool use_markup); SelectionDialogBase(Gtk::TextView *text_view, const boost::optional<Gtk::TextIter> &start_iter, bool show_search_entry, bool use_markup);
virtual ~SelectionDialogBase(); virtual ~SelectionDialogBase() {}
void add_row(const std::string &row); void add_row(const std::string &row);
void erase_rows(); void erase_rows();
void set_cursor_at_last_row(); void set_cursor_at_last_row();
@ -53,7 +54,7 @@ public:
std::function<void(boost::optional<unsigned int> index, const std::string &text)> on_change; std::function<void(boost::optional<unsigned int> index, const std::string &text)> on_change;
std::function<void(unsigned int index, const std::string &text, bool hide_window)> on_select; std::function<void(unsigned int index, const std::string &text, bool hide_window)> on_select;
std::function<void(const std::string &text)> on_search_entry_changed; std::function<void(const std::string &text)> on_search_entry_changed;
Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark; Source::Mark start_mark;
protected: protected:
void cursor_changed(); void cursor_changed();

55
src/source.cpp

@ -133,9 +133,7 @@ Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr<
clickable_tag->property_underline() = Pango::Underline::UNDERLINE_SINGLE; clickable_tag->property_underline() = Pango::Underline::UNDERLINE_SINGLE;
clickable_tag->property_underline_set() = true; clickable_tag->property_underline_set() = true;
get_buffer()->create_tag("def:warning");
get_buffer()->create_tag("def:warning_underline"); get_buffer()->create_tag("def:warning_underline");
get_buffer()->create_tag("def:error");
get_buffer()->create_tag("def:error_underline"); get_buffer()->create_tag("def:error_underline");
auto mark_attr_debug_breakpoint = Gsv::MarkAttributes::create(); auto mark_attr_debug_breakpoint = Gsv::MarkAttributes::create();
@ -464,33 +462,26 @@ void Source::View::configure() {
auto scheme = get_source_buffer()->get_style_scheme(); auto scheme = get_source_buffer()->get_style_scheme();
auto tag_table = get_buffer()->get_tag_table(); auto tag_table = get_buffer()->get_tag_table();
auto style = scheme->get_style("def:warning"); auto style = scheme->get_style("def:warning");
auto diagnostic_tag = get_buffer()->get_tag_table()->lookup("def:warning");
auto diagnostic_tag_underline = get_buffer()->get_tag_table()->lookup("def:warning_underline"); auto diagnostic_tag_underline = get_buffer()->get_tag_table()->lookup("def:warning_underline");
if(style && (style->property_foreground_set() || style->property_background_set())) { if(style && (style->property_foreground_set() || style->property_background_set())) {
Glib::ustring warning_property; Glib::ustring warning_property;
if(style->property_foreground_set()) { if(style->property_foreground_set())
warning_property = style->property_foreground().get_value(); warning_property = style->property_foreground().get_value();
diagnostic_tag->property_foreground() = warning_property;
}
else if(style->property_background_set()) else if(style->property_background_set())
warning_property = style->property_background().get_value(); warning_property = style->property_background().get_value();
diagnostic_tag_underline->property_underline() = Pango::Underline::UNDERLINE_ERROR; diagnostic_tag_underline->property_underline() = Pango::Underline::UNDERLINE_ERROR;
auto tag_class = G_OBJECT_GET_CLASS(diagnostic_tag_underline->gobj()); //For older GTK+ 3 versions: auto tag_class = G_OBJECT_GET_CLASS(diagnostic_tag_underline->gobj()); //For older GTK+ 3 versions:
auto param_spec = g_object_class_find_property(tag_class, "underline-rgba"); auto param_spec = g_object_class_find_property(tag_class, "underline-rgba");
if(param_spec != nullptr) { if(param_spec)
diagnostic_tag_underline->set_property("underline-rgba", Gdk::RGBA(warning_property)); diagnostic_tag_underline->set_property("underline-rgba", Gdk::RGBA(warning_property));
} }
}
style = scheme->get_style("def:error"); style = scheme->get_style("def:error");
diagnostic_tag = get_buffer()->get_tag_table()->lookup("def:error");
diagnostic_tag_underline = get_buffer()->get_tag_table()->lookup("def:error_underline"); diagnostic_tag_underline = get_buffer()->get_tag_table()->lookup("def:error_underline");
if(style && (style->property_foreground_set() || style->property_background_set())) { if(style && (style->property_foreground_set() || style->property_background_set())) {
Glib::ustring error_property; Glib::ustring error_property;
if(style->property_foreground_set()) { if(style->property_foreground_set())
error_property = style->property_foreground().get_value(); error_property = style->property_foreground().get_value();
diagnostic_tag->property_foreground() = error_property;
}
else if(style->property_background_set()) else if(style->property_background_set())
error_property = style->property_background().get_value(); error_property = style->property_background().get_value();
@ -630,11 +621,17 @@ void Source::View::setup_signals() {
get_buffer()->signal_mark_set().connect([this](const Gtk::TextIter &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { get_buffer()->signal_mark_set().connect([this](const Gtk::TextIter &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) {
auto mark_name = mark->get_name(); auto mark_name = mark->get_name();
if(mark_name == "selection_bound") {
if(get_buffer()->get_has_selection() && mark_name == "selection_bound") if(get_buffer()->get_has_selection())
delayed_tooltips_connection.disconnect(); delayed_tooltips_connection.disconnect();
if(mark_name == "insert") { if(update_status_location)
update_status_location(this);
if(!keep_previous_extended_selections)
previous_extended_selections.clear();
}
else if(mark_name == "insert") {
hide_tooltips(); hide_tooltips();
delayed_tooltips_connection.disconnect(); delayed_tooltips_connection.disconnect();
@ -672,10 +669,6 @@ void Source::View::setup_signals() {
if(!keep_previous_extended_selections) if(!keep_previous_extended_selections)
previous_extended_selections.clear(); previous_extended_selections.clear();
} }
if(!keep_previous_extended_selections && (mark_name == "insert" || mark_name == "selection_bound"))
if(!keep_previous_extended_selections)
previous_extended_selections.clear();
}); });
signal_key_release_event().connect([this](GdkEventKey *event) { signal_key_release_event().connect([this](GdkEventKey *event) {
@ -929,10 +922,9 @@ void Source::View::setup_format_style(bool is_generic_view) {
} }
} }
if(left_gravity_insert) { if(left_gravity_insert) {
auto mark = get_buffer()->create_mark(start); Mark mark(start);
get_buffer()->insert(start, replacement_str); get_buffer()->insert(start, replacement_str);
get_buffer()->place_cursor(mark->get_iter()); get_buffer()->place_cursor(mark->get_iter());
get_buffer()->delete_mark(mark);
} }
else else
get_buffer()->insert(start, replacement_str); get_buffer()->insert(start, replacement_str);
@ -2145,7 +2137,7 @@ bool Source::View::on_key_press_event_basic(GdkEventKey *key) {
// Indent right when clicking tab, no matter where in the line the cursor is. Also works on selected text. // Indent right when clicking tab, no matter where in the line the cursor is. Also works on selected text.
Gtk::TextIter selection_start, selection_end; Gtk::TextIter selection_start, selection_end;
get_buffer()->get_selection_bounds(selection_start, selection_end); get_buffer()->get_selection_bounds(selection_start, selection_end);
auto selection_end_mark = get_buffer()->create_mark(selection_end); Mark selection_end_mark(selection_end);
int line_start = selection_start.get_line(); int line_start = selection_start.get_line();
int line_end = selection_end.get_line(); int line_end = selection_end.get_line();
for(int line = line_start; line <= line_end; line++) { for(int line = line_start; line <= line_end; line++) {
@ -2153,7 +2145,6 @@ bool Source::View::on_key_press_event_basic(GdkEventKey *key) {
if(!get_buffer()->get_has_selection() || line_it != selection_end_mark->get_iter()) if(!get_buffer()->get_has_selection() || line_it != selection_end_mark->get_iter())
get_buffer()->insert(line_it, tab); get_buffer()->insert(line_it, tab);
} }
get_buffer()->delete_mark(selection_end_mark);
return true; return true;
} }
// Indent left when clicking shift-tab, no matter where in the line the cursor is. Also works on selected text. // Indent left when clicking shift-tab, no matter where in the line the cursor is. Also works on selected text.
@ -2472,12 +2463,11 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey *key) {
if(!iter.ends_line() && *iter != ')' && *iter != ']' && *iter != '}') { if(!iter.ends_line() && *iter != ')' && *iter != ']' && *iter != '}') {
get_buffer()->insert_at_cursor('\n' + tabs + tab); get_buffer()->insert_at_cursor('\n' + tabs + tab);
auto iter = get_buffer()->get_insert()->get_iter(); auto iter = get_buffer()->get_insert()->get_iter();
auto mark = get_buffer()->create_mark(iter); Mark mark(iter);
iter.forward_to_line_end(); iter.forward_to_line_end();
get_buffer()->insert(iter, '\n' + tabs + static_cast<char>(close_symbol)); get_buffer()->insert(iter, '\n' + tabs + static_cast<char>(close_symbol));
scroll_to(get_buffer()->get_insert()); scroll_to(get_buffer()->get_insert());
get_buffer()->place_cursor(mark->get_iter()); get_buffer()->place_cursor(mark->get_iter());
get_buffer()->delete_mark(mark);
return true; return true;
} }
else { else {
@ -2603,12 +2593,11 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey *key) {
if(!iter.ends_line() && *iter != ')' && *iter != ']') { if(!iter.ends_line() && *iter != ')' && *iter != ']') {
get_buffer()->insert_at_cursor('\n' + tabs + tab); get_buffer()->insert_at_cursor('\n' + tabs + tab);
auto iter = get_buffer()->get_insert()->get_iter(); auto iter = get_buffer()->get_insert()->get_iter();
auto mark = get_buffer()->create_mark(iter); Mark mark(iter);
iter.forward_to_line_end(); iter.forward_to_line_end();
get_buffer()->insert(iter, '\n' + tabs + '}'); get_buffer()->insert(iter, '\n' + tabs + '}');
scroll_to(get_buffer()->get_insert()); scroll_to(get_buffer()->get_insert());
get_buffer()->place_cursor(mark->get_iter()); get_buffer()->place_cursor(mark->get_iter());
get_buffer()->delete_mark(mark);
return true; return true;
} }
else { else {
@ -2886,16 +2875,14 @@ bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *key) {
auto after_end = end; auto after_end = end;
if(before_start.backward_char() && *before_start == '*' && before_start.backward_char() && *before_start == '/' && if(before_start.backward_char() && *before_start == '*' && before_start.backward_char() && *before_start == '/' &&
*after_end == '*' && after_end.forward_char() && *after_end == '/') { *after_end == '*' && after_end.forward_char() && *after_end == '/') {
auto start_mark = get_buffer()->create_mark(start); Mark start_mark(start);
auto end_mark = get_buffer()->create_mark(end); Mark end_mark(end);
get_buffer()->erase(before_start, start); get_buffer()->erase(before_start, start);
after_end = end_mark->get_iter(); after_end = end_mark->get_iter();
after_end.forward_chars(2); after_end.forward_chars(2);
get_buffer()->erase(end_mark->get_iter(), after_end); get_buffer()->erase(end_mark->get_iter(), after_end);
get_buffer()->select_range(start_mark->get_iter(), end_mark->get_iter()); get_buffer()->select_range(start_mark->get_iter(), end_mark->get_iter());
get_buffer()->delete_mark(start_mark);
get_buffer()->delete_mark(end_mark);
return true; return true;
} }
} }
@ -2960,16 +2947,14 @@ bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *key) {
if(!left.empty() && !right.empty()) { if(!left.empty() && !right.empty()) {
Gtk::TextIter start, end; Gtk::TextIter start, end;
get_buffer()->get_selection_bounds(start, end); get_buffer()->get_selection_bounds(start, end);
auto start_mark = get_buffer()->create_mark(start); Mark start_mark(start);
auto end_mark = get_buffer()->create_mark(end); Mark end_mark(end);
get_buffer()->insert(start, left); get_buffer()->insert(start, left);
get_buffer()->insert(end_mark->get_iter(), right); get_buffer()->insert(end_mark->get_iter(), right);
auto start_mark_next_iter = start_mark->get_iter(); auto start_mark_next_iter = start_mark->get_iter();
start_mark_next_iter.forward_chars(left.size()); start_mark_next_iter.forward_chars(left.size());
get_buffer()->select_range(start_mark_next_iter, end_mark->get_iter()); get_buffer()->select_range(start_mark_next_iter, end_mark->get_iter());
get_buffer()->delete_mark(start_mark);
get_buffer()->delete_mark(end_mark);
return true; return true;
} }
return false; return false;

21
src/source_base.cpp

@ -1238,15 +1238,15 @@ void Source::BaseView::setup_extra_cursor_signals() {
extra_cursor_selection->set_priority(get_buffer()->get_tag_table()->get_size() - 1); extra_cursor_selection->set_priority(get_buffer()->get_tag_table()->get_size() - 1);
auto last_insert = get_buffer()->create_mark(get_buffer()->get_insert()->get_iter(), false); auto last_insert = std::make_shared<Mark>(get_buffer()->get_insert()->get_iter(), false);
auto last_selection_bound = get_buffer()->create_mark(get_buffer()->get_selection_bound()->get_iter(), false); auto last_selection_bound = std::make_shared<Mark>(get_buffer()->get_selection_bound()->get_iter(), false);
get_buffer()->signal_mark_set().connect([this, last_insert, last_selection_bound](const Gtk::TextIter &iter, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) mutable { get_buffer()->signal_mark_set().connect([this, last_insert, last_selection_bound](const Gtk::TextIter &iter, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) mutable {
if(mark->get_name() == "insert") { if(mark->get_name() == "insert") {
if(enable_multiple_cursors || enable_multiple_cursors_placements) { if(enable_multiple_cursors || enable_multiple_cursors_placements) {
auto set_enable_multiple_cursors = enable_multiple_cursors; auto set_enable_multiple_cursors = enable_multiple_cursors;
if(set_enable_multiple_cursors) if(set_enable_multiple_cursors)
enable_multiple_cursors = false; enable_multiple_cursors = false;
auto offset_diff = mark->get_iter().get_offset() - last_insert->get_iter().get_offset(); auto offset_diff = mark->get_iter().get_offset() - (*last_insert)->get_iter().get_offset();
if(offset_diff != 0) { if(offset_diff != 0) {
for(auto &extra_cursor : extra_cursors) { for(auto &extra_cursor : extra_cursors) {
auto iter = extra_cursor.insert->get_iter(); auto iter = extra_cursor.insert->get_iter();
@ -1258,7 +1258,7 @@ void Source::BaseView::setup_extra_cursor_signals() {
if(set_enable_multiple_cursors) if(set_enable_multiple_cursors)
enable_multiple_cursors = true; enable_multiple_cursors = true;
} }
get_buffer()->move_mark(last_insert, mark->get_iter()); get_buffer()->move_mark(*last_insert, mark->get_iter());
} }
if(mark->get_name() == "selection_bound") { if(mark->get_name() == "selection_bound") {
@ -1266,7 +1266,7 @@ void Source::BaseView::setup_extra_cursor_signals() {
auto set_enable_multiple_cursors = enable_multiple_cursors; auto set_enable_multiple_cursors = enable_multiple_cursors;
if(set_enable_multiple_cursors) if(set_enable_multiple_cursors)
enable_multiple_cursors = false; enable_multiple_cursors = false;
auto offset_diff = mark->get_iter().get_offset() - last_selection_bound->get_iter().get_offset(); auto offset_diff = mark->get_iter().get_offset() - (*last_selection_bound)->get_iter().get_offset();
if(offset_diff != 0) { if(offset_diff != 0) {
for(auto &extra_cursor : extra_cursors) { for(auto &extra_cursor : extra_cursors) {
auto iter = extra_cursor.selection_bound->get_iter(); auto iter = extra_cursor.selection_bound->get_iter();
@ -1278,7 +1278,7 @@ void Source::BaseView::setup_extra_cursor_signals() {
if(set_enable_multiple_cursors) if(set_enable_multiple_cursors)
enable_multiple_cursors = true; enable_multiple_cursors = true;
} }
get_buffer()->move_mark(last_selection_bound, mark->get_iter()); get_buffer()->move_mark(*last_selection_bound, mark->get_iter());
} }
}); });
@ -1540,7 +1540,7 @@ void Source::BaseView::insert_snippet(Gtk::TextIter iter, const std::string &sni
parameter_offsets_and_sizes_map.erase(it); parameter_offsets_and_sizes_map.erase(it);
} }
auto mark = get_buffer()->create_mark(iter); Mark mark(iter);
get_source_buffer()->begin_user_action(); get_source_buffer()->begin_user_action();
Gtk::TextIter start, end; Gtk::TextIter start, end;
@ -1563,7 +1563,6 @@ void Source::BaseView::insert_snippet(Gtk::TextIter iter, const std::string &sni
get_source_buffer()->end_user_action(); get_source_buffer()->end_user_action();
iter = mark->get_iter(); iter = mark->get_iter();
get_buffer()->delete_mark(mark);
for(auto &parameter_offsets_and_sized : parameter_offsets_and_sizes_map) { for(auto &parameter_offsets_and_sized : parameter_offsets_and_sizes_map) {
snippet_parameters_list.emplace_back(); snippet_parameters_list.emplace_back();
for(auto &offsets : parameter_offsets_and_sized.second) { for(auto &offsets : parameter_offsets_and_sized.second) {
@ -1660,8 +1659,8 @@ bool Source::BaseView::clear_snippet_marks() {
Source::BaseView::ExtraCursor::ExtraCursor(const Glib::RefPtr<Gtk::TextTag> &extra_cursor_selection, const Gtk::TextIter &start_iter, const Gtk::TextIter &end_iter, bool snippet, int line_offset) Source::BaseView::ExtraCursor::ExtraCursor(const Glib::RefPtr<Gtk::TextTag> &extra_cursor_selection, const Gtk::TextIter &start_iter, const Gtk::TextIter &end_iter, bool snippet, int line_offset)
: extra_cursor_selection(extra_cursor_selection), : extra_cursor_selection(extra_cursor_selection),
insert(start_iter.get_buffer()->create_mark(start_iter, false)), insert(start_iter, false),
selection_bound(end_iter.get_buffer()->create_mark(end_iter, false)), selection_bound(end_iter, false),
line_offset(line_offset), snippet(snippet) { line_offset(line_offset), snippet(snippet) {
insert->set_visible(true); insert->set_visible(true);
if(start_iter != end_iter) if(start_iter != end_iter)
@ -1671,8 +1670,6 @@ Source::BaseView::ExtraCursor::ExtraCursor(const Glib::RefPtr<Gtk::TextTag> &ext
Source::BaseView::ExtraCursor::~ExtraCursor() { Source::BaseView::ExtraCursor::~ExtraCursor() {
insert->get_buffer()->remove_tag(extra_cursor_selection, insert->get_iter(), selection_bound->get_iter()); insert->get_buffer()->remove_tag(extra_cursor_selection, insert->get_iter(), selection_bound->get_iter());
insert->set_visible(false); insert->set_visible(false);
insert->get_buffer()->delete_mark(insert);
selection_bound->get_buffer()->delete_mark(selection_bound);
} }
void Source::BaseView::ExtraCursor::move(const Gtk::TextIter &iter, bool selection_activated) { void Source::BaseView::ExtraCursor::move(const Gtk::TextIter &iter, bool selection_activated) {

28
src/source_base.hpp

@ -11,6 +11,22 @@
#include <vector> #include <vector>
namespace Source { namespace Source {
/// RAII-style text mark. Use instead of Gtk::TextBuffer::create_mark and Gtk::TextBuffer::delete_mark,
/// since Gtk::TextBuffer::delete_mark is not called upon Glib::RefPtr<Gtk::TextMark> deletion
class Mark : public Glib::RefPtr<Gtk::TextMark> {
public:
Mark(const Gtk::TextIter &iter, bool left_gravity = true) : Glib::RefPtr<Gtk::TextMark>(iter.get_buffer()->create_mark(iter, left_gravity)) {}
Mark() = default;
Mark(Mark &&) = default;
Mark &operator=(Mark &&) = default;
Mark(const Mark &) = delete;
Mark &operator=(const Mark &) = delete;
~Mark() {
if(*this)
(*this)->get_buffer()->delete_mark(*this);
}
};
class SearchView : public Gsv::View { class SearchView : public Gsv::View {
public: public:
SearchView(); SearchView();
@ -162,8 +178,8 @@ namespace Source {
void move(const Gtk::TextIter &iter, bool selection_activated); void move(const Gtk::TextIter &iter, bool selection_activated);
void move_selection_bound(const Gtk::TextIter &iter); void move_selection_bound(const Gtk::TextIter &iter);
Glib::RefPtr<Gtk::TextBuffer::Mark> insert; Mark insert;
Glib::RefPtr<Gtk::TextBuffer::Mark> selection_bound; Mark selection_bound;
/// Used when moving cursor up/down lines /// Used when moving cursor up/down lines
int line_offset; int line_offset;
/// Set to true when the extra cursor corresponds to a snippet parameter /// Set to true when the extra cursor corresponds to a snippet parameter
@ -181,12 +197,8 @@ namespace Source {
class SnippetParameter { class SnippetParameter {
public: public:
SnippetParameter(const Gtk::TextIter &start_iter, const Gtk::TextIter &end_iter) SnippetParameter(const Gtk::TextIter &start_iter, const Gtk::TextIter &end_iter)
: start(start_iter.get_buffer()->create_mark(start_iter, false)), end(end_iter.get_buffer()->create_mark(end_iter, false)), size(end_iter.get_offset() - start_iter.get_offset()) {} : start(start_iter, false), end(end_iter, false), size(end_iter.get_offset() - start_iter.get_offset()) {}
~SnippetParameter() { Mark start, end;
start->get_buffer()->delete_mark(start);
end->get_buffer()->delete_mark(end);
}
Glib::RefPtr<Gtk::TextBuffer::Mark> start, end;
/// Used to check if the parameter has been deleted, and should be passed on next tab /// Used to check if the parameter has been deleted, and should be passed on next tab
int size; int size;
}; };

15
src/source_clang.cpp

@ -511,7 +511,11 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle)
type_tooltips.emplace_back(this, start, end, [this, token](Tooltip &tooltip) { type_tooltips.emplace_back(this, start, end, [this, token](Tooltip &tooltip) {
auto cursor = token.get_cursor(); auto cursor = token.get_cursor();
tooltip.buffer->insert(tooltip.buffer->get_insert()->get_iter(), "Type: " + cursor.get_type_description()); auto type_description = cursor.get_type_description();
size_t pos = 0;
while((pos = type_description.find("::__1", pos)) != std::string::npos)
type_description.erase(pos, 5);
tooltip.insert_code(type_description, std::string{});
auto brief_comment = cursor.get_brief_comments(); auto brief_comment = cursor.get_brief_comments();
if(brief_comment != "") if(brief_comment != "")
tooltip.insert_with_links_tagged("\n\n" + brief_comment); tooltip.insert_with_links_tagged("\n\n" + brief_comment);
@ -647,7 +651,12 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle)
next_char_iter++; next_char_iter++;
debug_value.replace(iter, next_char_iter, "?"); debug_value.replace(iter, next_char_iter, "?");
} }
tooltip.buffer->insert(tooltip.buffer->get_insert()->get_iter(), (tooltip.buffer->size() > 0 ? "\n\n" : "") + value_type + ": " + debug_value.substr(pos + 3, debug_value.size() - (pos + 3) - 1)); tooltip.buffer->insert(tooltip.buffer->get_insert()->get_iter(), (tooltip.buffer->size() > 0 ? "\n\n" : "") + value_type + ":\n");
auto value = debug_value.substr(pos + 3, debug_value.size() - (pos + 3) - 1);
size_t pos = 0;
while((pos = value.find("::__1", pos)) != std::string::npos)
value.erase(pos, 5);
tooltip.insert_code(value, {});
} }
} }
} }
@ -820,7 +829,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa
if(this->language && (this->language->get_id() == "chdr" || this->language->get_id() == "cpphdr")) if(this->language && (this->language->get_id() == "chdr" || this->language->get_id() == "cpphdr"))
clangmm::remove_include_guard(buffer); clangmm::remove_include_guard(buffer);
code_complete_results = std::make_unique<clangmm::CodeCompleteResults>(clang_tu->get_code_completions(buffer, line_number, column)); code_complete_results = std::make_unique<clangmm::CodeCompleteResults>(clang_tu->get_code_completions(buffer, line_number, column));
if(code_complete_results->cx_results == nullptr) { if(!code_complete_results->cx_results) {
auto expected = ParseState::processing; auto expected = ParseState::processing;
parse_state.compare_exchange_strong(expected, ParseState::restarting); parse_state.compare_exchange_strong(expected, ParseState::restarting);
return; return;

105
src/source_language_protocol.cpp

@ -1092,7 +1092,7 @@ void Source::LanguageProtocolView::update_diagnostics(std::vector<LanguageProtoc
} }
for(auto &mark : type_coverage_marks) { for(auto &mark : type_coverage_marks) {
add_diagnostic_tooltip(mark.start->get_iter(), mark.end->get_iter(), false, [](Tooltip &tooltip) { add_diagnostic_tooltip(mark.first->get_iter(), mark.second->get_iter(), false, [](Tooltip &tooltip) {
tooltip.buffer->insert_at_cursor(type_coverage_message); tooltip.buffer->insert_at_cursor(type_coverage_message);
}); });
num_warnings++; num_warnings++;
@ -1131,7 +1131,7 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect
// hover result structure vary significantly from the different language servers // hover result structure vary significantly from the different language servers
struct Content { struct Content {
std::string value; std::string value;
bool markdown; std::string kind;
}; };
std::vector<Content> contents; std::vector<Content> contents;
auto contents_pt = result.get_child_optional("contents"); auto contents_pt = result.get_child_optional("contents");
@ -1139,20 +1139,28 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect
return; return;
auto value = contents_pt->get_value<std::string>(""); auto value = contents_pt->get_value<std::string>("");
if(!value.empty()) if(!value.empty())
contents.emplace_back(Content{value, true}); contents.emplace_back(Content{value, "markdown"});
else { else {
auto value_pt = contents_pt->get_optional<std::string>("value"); auto value_pt = contents_pt->get_optional<std::string>("value");
if(value_pt) if(value_pt) {
contents.emplace_back(Content{*value_pt, contents_pt->get<std::string>("kind", "") == "markdown"}); auto kind = contents_pt->get<std::string>("kind", "");
if(kind.empty())
kind = contents_pt->get<std::string>("language", "");
contents.emplace_back(Content{*value_pt, kind});
}
else { else {
for(auto it = contents_pt->begin(); it != contents_pt->end(); ++it) { for(auto it = contents_pt->begin(); it != contents_pt->end(); ++it) {
auto value = it->second.get<std::string>("value", ""); auto value = it->second.get<std::string>("value", "");
if(!value.empty()) if(!value.empty()) {
contents.emplace_back(Content{value, contents_pt->get<std::string>("kind", "") == "markdown"}); auto kind = it->second.get<std::string>("kind", "");
if(kind.empty())
kind = it->second.get<std::string>("language", "");
contents.emplace_back(Content{value, kind});
}
else { else {
value = it->second.get_value<std::string>(""); value = it->second.get_value<std::string>("");
if(!value.empty()) if(!value.empty())
contents.emplace_back(Content{value, true}); contents.emplace_back(Content{value, "markdown"});
} }
} }
} }
@ -1172,13 +1180,14 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect
for(size_t i = 0; i < contents.size(); i++) { for(size_t i = 0; i < contents.size(); i++) {
if(i > 0) if(i > 0)
tooltip.buffer->insert_at_cursor("\n\n"); tooltip.buffer->insert_at_cursor("\n\n");
if(contents[i].markdown && language_id != "python") // TODO: python-language-server might support markdown in the future if(contents[i].kind == "plaintext" || contents[i].kind.empty())
tooltip.insert_markdown(contents[i].value);
else {
tooltip.insert_with_links_tagged(contents[i].value); tooltip.insert_with_links_tagged(contents[i].value);
else if(contents[i].kind == "markdown")
tooltip.insert_markdown(contents[i].value);
else
tooltip.insert_code(contents[i].value, contents[i].kind);
tooltip.remove_trailing_newlines(); tooltip.remove_trailing_newlines();
} }
}
#ifdef JUCI_ENABLE_DEBUG #ifdef JUCI_ENABLE_DEBUG
if(language_id == "rust" && capabilities.definition) { if(language_id == "rust" && capabilities.definition) {
@ -1224,7 +1233,8 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect
next_char_iter++; next_char_iter++;
debug_value.replace(iter, next_char_iter, "?"); debug_value.replace(iter, next_char_iter, "?");
} }
tooltip.buffer->insert_at_cursor("\n\n" + value_type + ": " + debug_value.substr(pos + 3, debug_value.size() - (pos + 3) - 1)); tooltip.buffer->insert_at_cursor("\n\n" + value_type + ":\n");
tooltip.insert_code(debug_value.substr(pos + 3, debug_value.size() - (pos + 3) - 1), {});
} }
} }
} }
@ -1514,20 +1524,17 @@ void Source::LanguageProtocolView::setup_autocomplete() {
for(auto parameter_it = parameters.begin(); parameter_it != parameters.end(); ++parameter_it) { for(auto parameter_it = parameters.begin(); parameter_it != parameters.end(); ++parameter_it) {
auto label = parameter_it->second.get<std::string>("label", ""); auto label = parameter_it->second.get<std::string>("label", "");
auto insert = label; auto insert = label;
auto plaintext = parameter_it->second.get<std::string>("documentation", ""); auto documentation = parameter_it->second.get<std::string>("documentation", "");
std::string markdown; std::string kind;
if(plaintext.empty()) { if(documentation.empty()) {
auto documentation = parameter_it->second.get_child_optional("documentation"); auto documentation_pt = parameter_it->second.get_child_optional("documentation");
if(documentation) { if(documentation_pt) {
auto kind = documentation->get<std::string>("kind", ""); documentation = documentation_pt->get<std::string>("value", "");
if(kind == "markdown") kind = documentation_pt->get<std::string>("kind", "");
markdown = documentation->get<std::string>("value", "");
else
plaintext = documentation->get<std::string>("value", "");
} }
} }
autocomplete->rows.emplace_back(std::move(label)); autocomplete->rows.emplace_back(std::move(label));
autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(plaintext), std::move(markdown)}); autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), {}, std::move(documentation), std::move(kind)});
} }
} }
} }
@ -1550,16 +1557,13 @@ void Source::LanguageProtocolView::setup_autocomplete() {
for(auto it = begin; it != end; ++it) { for(auto it = begin; it != end; ++it) {
auto label = it->second.get<std::string>("label", ""); auto label = it->second.get<std::string>("label", "");
auto detail = it->second.get<std::string>("detail", ""); auto detail = it->second.get<std::string>("detail", "");
auto plaintext = it->second.get<std::string>("documentation", ""); auto documentation = it->second.get<std::string>("documentation", "");
std::string markdown; std::string kind;
if(plaintext.empty()) { if(documentation.empty()) {
auto documentation = it->second.get_child_optional("documentation"); auto documentation_pt = it->second.get_child_optional("documentation");
if(documentation) { if(documentation_pt) {
auto kind = documentation->get<std::string>("kind", ""); documentation = documentation_pt->get<std::string>("value", "");
if(kind == "markdown") kind = documentation_pt->get<std::string>("kind", "");
markdown = documentation->get<std::string>("value", "");
else
plaintext = documentation->get<std::string>("value", "");
} }
} }
auto insert = it->second.get<std::string>("insertText", ""); auto insert = it->second.get<std::string>("insertText", "");
@ -1600,12 +1604,7 @@ void Source::LanguageProtocolView::setup_autocomplete() {
} }
if(starts_with(label, prefix)) { if(starts_with(label, prefix)) {
autocomplete->rows.emplace_back(std::move(label)); autocomplete->rows.emplace_back(std::move(label));
if(!plaintext.empty() && detail != plaintext) { autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(detail), std::move(documentation), std::move(kind)});
if(!detail.empty())
detail += "\n\n";
detail += plaintext;
}
autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(detail), std::move(markdown)});
} }
} }
} }
@ -1621,7 +1620,7 @@ void Source::LanguageProtocolView::setup_autocomplete() {
for(auto &snippet : *snippets) { for(auto &snippet : *snippets) {
if(starts_with(snippet.prefix, prefix)) { if(starts_with(snippet.prefix, prefix)) {
autocomplete->rows.emplace_back(snippet.prefix); autocomplete->rows.emplace_back(snippet.prefix);
autocomplete_rows.emplace_back(AutocompleteRow{snippet.body, snippet.description, {}}); autocomplete_rows.emplace_back(AutocompleteRow{snippet.body, {}, snippet.description, {}});
} }
} }
} }
@ -1691,17 +1690,23 @@ void Source::LanguageProtocolView::setup_autocomplete() {
}; };
autocomplete->set_tooltip_buffer = [this](unsigned int index) -> std::function<void(Tooltip & tooltip)> { autocomplete->set_tooltip_buffer = [this](unsigned int index) -> std::function<void(Tooltip & tooltip)> {
auto plaintext = autocomplete_rows[index].plaintext; auto autocomplete = autocomplete_rows[index];
auto markdown = autocomplete_rows[index].markdown; if(autocomplete.detail.empty() && autocomplete.documentation.empty())
if(plaintext.empty() && markdown.empty())
return nullptr; return nullptr;
return [plaintext = std::move(plaintext), markdown = std::move(markdown)](Tooltip &tooltip) { return [this, autocomplete = std::move(autocomplete)](Tooltip &tooltip) {
if(!plaintext.empty()) if(!autocomplete.detail.empty()) {
tooltip.insert_with_links_tagged(plaintext); tooltip.insert_code(autocomplete.detail, language ? language->get_id().raw() : std::string{});
if(!markdown.empty()) { tooltip.remove_trailing_newlines();
if(!plaintext.empty()) }
if(!autocomplete.documentation.empty()) {
if(tooltip.buffer->size() > 0)
tooltip.buffer->insert_at_cursor("\n\n"); tooltip.buffer->insert_at_cursor("\n\n");
tooltip.insert_markdown(markdown); if(autocomplete.kind == "plaintext" || autocomplete.kind.empty())
tooltip.insert_with_links_tagged(autocomplete.documentation);
else if(autocomplete.kind == "markdown")
tooltip.insert_markdown(autocomplete.documentation);
else
tooltip.insert_code(autocomplete.documentation, autocomplete.kind);
} }
}; };
}; };

17
src/source_language_protocol.hpp

@ -200,8 +200,9 @@ namespace Source {
struct AutocompleteRow { struct AutocompleteRow {
std::string insert; std::string insert;
std::string plaintext; std::string detail;
std::string markdown; std::string documentation;
std::string kind;
}; };
std::vector<AutocompleteRow> autocomplete_rows; std::vector<AutocompleteRow> autocomplete_rows;
@ -214,17 +215,7 @@ namespace Source {
std::vector<LanguageProtocol::Diagnostic> last_diagnostics; std::vector<LanguageProtocol::Diagnostic> last_diagnostics;
sigc::connection update_type_coverage_connection; sigc::connection update_type_coverage_connection;
class TypeCoverageMark { std::list<std::pair<Mark, Mark>> type_coverage_marks;
public:
TypeCoverageMark(const Gtk::TextIter &start_iter, const Gtk::TextIter &end_iter)
: start(start_iter.get_buffer()->create_mark(start_iter)), end(end_iter.get_buffer()->create_mark(end_iter)) {}
~TypeCoverageMark() {
start->get_buffer()->delete_mark(start);
end->get_buffer()->delete_mark(end);
}
Glib::RefPtr<Gtk::TextMark> start, end;
};
std::list<TypeCoverageMark> type_coverage_marks;
size_t num_warnings = 0, num_errors = 0, num_fix_its = 0; size_t num_warnings = 0, num_errors = 0, num_fix_its = 0;
void update_type_coverage(); void update_type_coverage();
std::atomic<int> update_type_coverage_retries = {60}; std::atomic<int> update_type_coverage_retries = {60};

34
src/source_spellcheck.cpp

@ -7,7 +7,7 @@
AspellConfig *Source::SpellCheckView::spellcheck_config = nullptr; AspellConfig *Source::SpellCheckView::spellcheck_config = nullptr;
Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language) : BaseView(file_path, language) { Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language) : BaseView(file_path, language) {
if(spellcheck_config == nullptr) if(!spellcheck_config)
spellcheck_config = new_aspell_config(); spellcheck_config = new_aspell_config();
spellcheck_checker = nullptr; spellcheck_checker = nullptr;
spellcheck_error_tag = get_buffer()->create_tag("spellcheck_error"); spellcheck_error_tag = get_buffer()->create_tag("spellcheck_error");
@ -29,7 +29,7 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path,
}); });
get_buffer()->signal_changed().connect([this]() { get_buffer()->signal_changed().connect([this]() {
if(spellcheck_checker == nullptr) if(!spellcheck_checker)
return; return;
delayed_spellcheck_suggestions_connection.disconnect(); delayed_spellcheck_suggestions_connection.disconnect();
@ -129,12 +129,10 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path,
// In case of for instance text paste or undo/redo // In case of for instance text paste or undo/redo
get_buffer()->signal_insert().connect([this](const Gtk::TextIter &start_iter, const Glib::ustring &inserted_string, int) { get_buffer()->signal_insert().connect([this](const Gtk::TextIter &start_iter, const Glib::ustring &inserted_string, int) {
if(spellcheck_checker == nullptr) if(!spellcheck_checker)
return;
if(!disable_spellcheck)
return; return;
if(disable_spellcheck) {
auto iter = start_iter; auto iter = start_iter;
if(!is_word_iter(iter) && !iter.starts_line()) if(!is_word_iter(iter) && !iter.starts_line())
iter.backward_char(); iter.backward_char();
@ -142,16 +140,18 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path,
auto word = get_word(iter); auto word = get_word(iter);
get_buffer()->remove_tag(spellcheck_error_tag, word.first, word.second); get_buffer()->remove_tag(spellcheck_error_tag, word.first, word.second);
} }
}
}, false); }, false);
get_buffer()->signal_mark_set().connect([this](const Gtk::TextIter &iter, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { get_buffer()->signal_mark_set().connect([this](const Gtk::TextIter &iter, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) {
if(spellcheck_checker == nullptr)
return;
if(mark->get_name() == "insert") { if(mark->get_name() == "insert") {
if(SelectionDialog::get()) if(SelectionDialog::get())
SelectionDialog::get()->hide(); SelectionDialog::get()->hide();
if(!spellcheck_checker)
return;
delayed_spellcheck_suggestions_connection.disconnect(); delayed_spellcheck_suggestions_connection.disconnect();
if(get_buffer()->get_has_selection())
return;
delayed_spellcheck_suggestions_connection = Glib::signal_timeout().connect([this]() { delayed_spellcheck_suggestions_connection = Glib::signal_timeout().connect([this]() {
if(get_buffer()->get_insert()->get_iter().has_tag(spellcheck_error_tag)) { if(get_buffer()->get_insert()->get_iter().has_tag(spellcheck_error_tag)) {
SelectionDialog::create(this, false); SelectionDialog::create(this, false);
@ -182,13 +182,6 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path,
} }
}); });
get_buffer()->signal_mark_set().connect([](const Gtk::TextIter &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) {
if(mark->get_name() == "insert") {
if(SelectionDialog::get())
SelectionDialog::get()->hide();
}
});
signal_focus_out_event().connect([this](GdkEventFocus *event) { signal_focus_out_event().connect([this](GdkEventFocus *event) {
delayed_spellcheck_suggestions_connection.disconnect(); delayed_spellcheck_suggestions_connection.disconnect();
return false; return false;
@ -221,7 +214,7 @@ Source::SpellCheckView::~SpellCheckView() {
delayed_spellcheck_suggestions_connection.disconnect(); delayed_spellcheck_suggestions_connection.disconnect();
delayed_spellcheck_error_clear.disconnect(); delayed_spellcheck_error_clear.disconnect();
if(spellcheck_checker != nullptr) if(spellcheck_checker)
delete_aspell_speller(spellcheck_checker); delete_aspell_speller(spellcheck_checker);
signal_tag_added_connection.disconnect(); signal_tag_added_connection.disconnect();
@ -234,7 +227,7 @@ void Source::SpellCheckView::configure() {
aspell_config_replace(spellcheck_config, "encoding", "utf-8"); aspell_config_replace(spellcheck_config, "encoding", "utf-8");
} }
spellcheck_possible_err = new_aspell_speller(spellcheck_config); spellcheck_possible_err = new_aspell_speller(spellcheck_config);
if(spellcheck_checker != nullptr) if(spellcheck_checker)
delete_aspell_speller(spellcheck_checker); delete_aspell_speller(spellcheck_checker);
spellcheck_checker = nullptr; spellcheck_checker = nullptr;
if(aspell_error_number(spellcheck_possible_err) != 0) if(aspell_error_number(spellcheck_possible_err) != 0)
@ -251,7 +244,7 @@ void Source::SpellCheckView::hide_dialogs() {
} }
void Source::SpellCheckView::spellcheck(const Gtk::TextIter &start, const Gtk::TextIter &end) { void Source::SpellCheckView::spellcheck(const Gtk::TextIter &start, const Gtk::TextIter &end) {
if(spellcheck_checker == nullptr) if(!spellcheck_checker)
return; return;
auto iter = start; auto iter = start;
while(iter && iter < end) { while(iter && iter < end) {
@ -550,9 +543,8 @@ std::vector<std::string> Source::SpellCheckView::get_spellcheck_suggestions(cons
std::vector<std::string> words; std::vector<std::string> words;
const char *word; const char *word;
while((word = aspell_string_enumeration_next(elements)) != nullptr) { while((word = aspell_string_enumeration_next(elements)))
words.emplace_back(word); words.emplace_back(word);
}
delete_aspell_string_enumeration(elements); delete_aspell_string_enumeration(elements);
return words; return words;

3
src/terminal.cpp

@ -277,13 +277,12 @@ size_t Terminal::print(const std::string &message, bool bold) {
umessage.replace(iter, next_char_iter, "?"); umessage.replace(iter, next_char_iter, "?");
} }
auto start_mark = get_buffer()->create_mark(get_buffer()->get_iter_at_line(get_buffer()->end().get_line())); Source::Mark start_mark(get_buffer()->get_iter_at_line(get_buffer()->end().get_line()));
if(bold) if(bold)
get_buffer()->insert_with_tag(get_buffer()->end(), umessage, bold_tag); get_buffer()->insert_with_tag(get_buffer()->end(), umessage, bold_tag);
else else
get_buffer()->insert(get_buffer()->end(), umessage); get_buffer()->insert(get_buffer()->end(), umessage);
auto start_iter = start_mark->get_iter(); auto start_iter = start_mark->get_iter();
get_buffer()->delete_mark(start_mark);
auto end_iter = get_buffer()->get_insert()->get_iter(); auto end_iter = get_buffer()->get_insert()->get_iter();
apply_link_tags(start_iter, end_iter); apply_link_tags(start_iter, end_iter);

219
src/tooltips.cpp

@ -11,34 +11,30 @@
std::set<Tooltip *> Tooltips::shown_tooltips; std::set<Tooltip *> Tooltips::shown_tooltips;
Gdk::Rectangle Tooltips::drawn_tooltips_rectangle = Gdk::Rectangle(); Gdk::Rectangle Tooltips::drawn_tooltips_rectangle = Gdk::Rectangle();
Tooltip::Tooltip(Gtk::TextView *text_view, const Gtk::TextIter &start_iter, const Gtk::TextIter &end_iter, std::function<void(Tooltip &toolip)> set_buffer_) Tooltip::Tooltip(Gsv::View *view, const Gtk::TextIter &start_iter, const Gtk::TextIter &end_iter, std::function<void(Tooltip &toolip)> set_buffer_)
: start_mark(start_iter.get_buffer()->create_mark(start_iter)), end_mark(end_iter.get_buffer()->create_mark(end_iter)), text_view(text_view), set_buffer(std::move(set_buffer_)) {} : start_mark(start_iter), end_mark(end_iter), view(view), set_buffer(std::move(set_buffer_)) {}
Tooltip::Tooltip(std::function<void(Tooltip &)> set_buffer_) Tooltip::Tooltip(std::function<void(Tooltip &)> set_buffer_)
: text_view(nullptr), set_buffer(std::move(set_buffer_)) {} : view(nullptr), set_buffer(std::move(set_buffer_)) {}
Tooltip::~Tooltip() { Tooltip::~Tooltip() {
Tooltips::shown_tooltips.erase(this); Tooltips::shown_tooltips.erase(this);
if(text_view) {
text_view->get_buffer()->delete_mark(start_mark);
text_view->get_buffer()->delete_mark(end_mark);
}
} }
void Tooltip::update() { void Tooltip::update() {
if(text_view) { if(view) {
auto iter = start_mark->get_iter(); auto iter = start_mark->get_iter();
auto end_iter = end_mark->get_iter(); auto end_iter = end_mark->get_iter();
text_view->get_iter_location(iter, activation_rectangle); view->get_iter_location(iter, activation_rectangle);
if(iter.get_offset() < end_iter.get_offset()) { if(iter.get_offset() < end_iter.get_offset()) {
while(iter.forward_char() && iter != end_iter) { while(iter.forward_char() && iter != end_iter) {
Gdk::Rectangle rectangle; Gdk::Rectangle rectangle;
text_view->get_iter_location(iter, rectangle); view->get_iter_location(iter, rectangle);
activation_rectangle.join(rectangle); activation_rectangle.join(rectangle);
} }
} }
int location_window_x, location_window_y; int location_window_x, location_window_y;
text_view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, activation_rectangle.get_x(), activation_rectangle.get_y(), location_window_x, location_window_y); view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, activation_rectangle.get_x(), activation_rectangle.get_y(), location_window_x, location_window_y);
activation_rectangle.set_x(location_window_x); activation_rectangle.set_x(location_window_x);
activation_rectangle.set_y(location_window_y); activation_rectangle.set_y(location_window_y);
} }
@ -82,20 +78,18 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion)
buffer = Gtk::TextBuffer::create(); buffer = Gtk::TextBuffer::create();
if(text_view) { if(view) {
auto tag = text_view->get_buffer()->get_tag_table()->lookup("def:warning"); auto create_tag_from_style = [this](const std::string &style_name) {
auto new_tag = buffer->create_tag("def:warning"); if(auto style = view->get_source_buffer()->get_style_scheme()->get_style(style_name)) {
if(tag->property_foreground_set()) auto tag = buffer->create_tag(style_name);
new_tag->property_foreground_rgba() = tag->property_foreground_rgba().get_value(); if(style->property_foreground_set())
if(tag->property_background_set()) tag->property_foreground() = style->property_foreground();
new_tag->property_background_rgba() = tag->property_background_rgba().get_value(); if(style->property_background_set())
tag->property_background() = style->property_background();
tag = text_view->get_buffer()->get_tag_table()->lookup("def:error"); }
new_tag = buffer->create_tag("def:error"); };
if(tag->property_foreground_set()) create_tag_from_style("def:warning");
new_tag->property_foreground_rgba() = tag->property_foreground_rgba().get_value(); create_tag_from_style("def:error");
if(tag->property_background_set())
new_tag->property_background_rgba() = tag->property_background_rgba().get_value();
} }
link_tag = buffer->create_tag("link"); link_tag = buffer->create_tag("link");
link_tag->property_underline() = Pango::Underline::UNDERLINE_SINGLE; link_tag->property_underline() = Pango::Underline::UNDERLINE_SINGLE;
@ -164,8 +158,8 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion)
std::smatch sm; std::smatch sm;
if(std::regex_match(link, sm, regex)) { if(std::regex_match(link, sm, regex)) {
auto path = boost::filesystem::path(sm[1].str()); auto path = boost::filesystem::path(sm[1].str());
if(auto view = dynamic_cast<Source::View *>(text_view)) if(auto source_view = dynamic_cast<Source::View *>(view))
path = filesystem::get_normal_path(view->file_path.parent_path() / path); path = filesystem::get_normal_path(source_view->file_path.parent_path() / path);
boost::system::error_code ec; boost::system::error_code ec;
if(boost::filesystem::is_regular_file(path, ec)) { if(boost::filesystem::is_regular_file(path, ec)) {
@ -185,8 +179,8 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion)
} }
auto path = boost::filesystem::path(link); auto path = boost::filesystem::path(link);
if(auto view = dynamic_cast<Source::View *>(text_view)) if(auto source_view = dynamic_cast<Source::View *>(view))
path = filesystem::get_normal_path(view->file_path.parent_path() / path); path = filesystem::get_normal_path(source_view->file_path.parent_path() / path);
boost::system::error_code ec; boost::system::error_code ec;
if(boost::filesystem::is_regular_file(path, ec) && Notebook::get().open(path)) if(boost::filesystem::is_regular_file(path, ec) && Notebook::get().open(path))
return true; return true;
@ -206,13 +200,14 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion)
// Add ScrolledWindow if needed // Add ScrolledWindow if needed
Gtk::Widget *widget = tooltip_text_view; Gtk::Widget *widget = tooltip_text_view;
auto screen_width = Gdk::Screen::get_default()->get_width();
auto screen_height = Gdk::Screen::get_default()->get_height(); auto screen_height = Gdk::Screen::get_default()->get_height();
if(size.second > screen_height - 6 /* 2xpadding */) { if(size.first > screen_width - 6 /* 2xpadding */ || size.second > screen_height - 6 /* 2xpadding */) {
auto scrolled_window = Gtk::manage(new Gtk::ScrolledWindow()); auto scrolled_window = Gtk::manage(new Gtk::ScrolledWindow());
scrolled_window->property_hscrollbar_policy() = Gtk::PolicyType::POLICY_NEVER; scrolled_window->property_hscrollbar_policy() = size.first > screen_width - 6 ? Gtk::PolicyType::POLICY_AUTOMATIC : Gtk::PolicyType::POLICY_NEVER;
scrolled_window->property_vscrollbar_policy() = Gtk::PolicyType::POLICY_AUTOMATIC; scrolled_window->property_vscrollbar_policy() = size.second > screen_height - 6 ? Gtk::PolicyType::POLICY_AUTOMATIC : Gtk::PolicyType::POLICY_NEVER;
scrolled_window->add(*tooltip_text_view); scrolled_window->add(*tooltip_text_view);
scrolled_window->set_size_request(-1, screen_height - 6 /* 2xpadding */); scrolled_window->set_size_request(size.first > screen_width - 6 ? screen_width - 6 : -1, size.second > screen_height - 6 ? screen_height - 6 : -1);
widget = scrolled_window; widget = scrolled_window;
} }
@ -225,7 +220,7 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion)
#endif #endif
window->signal_realize().connect([this] { window->signal_realize().connect([this] {
if(!text_view) { if(!view) {
auto &dialog = SelectionDialog::get(); auto &dialog = SelectionDialog::get();
if(dialog && dialog->is_visible()) { if(dialog && dialog->is_visible()) {
int root_x, root_y; int root_x, root_y;
@ -245,15 +240,15 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion)
return; // Do not show empty tooltips return; // Do not show empty tooltips
int root_x = 0, root_y = 0; int root_x = 0, root_y = 0;
if(text_view) { if(view) {
Gdk::Rectangle visible_rect; Gdk::Rectangle visible_rect;
text_view->get_visible_rect(visible_rect); view->get_visible_rect(visible_rect);
int visible_window_x, visible_window_y; int visible_window_x, visible_window_y;
text_view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, visible_rect.get_x(), visible_rect.get_y(), visible_window_x, visible_window_y); view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, visible_rect.get_x(), visible_rect.get_y(), visible_window_x, visible_window_y);
auto window_x = std::max(activation_rectangle.get_x(), visible_window_x); // Adjust tooltip right if it is left of text_view auto window_x = std::max(activation_rectangle.get_x(), visible_window_x); // Adjust tooltip right if it is left of view
auto window_y = activation_rectangle.get_y(); auto window_y = activation_rectangle.get_y();
text_view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(window_x, window_y, root_x, root_y); view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(window_x, window_y, root_x, root_y);
root_x -= 3; // -1xpadding root_x -= 3; // -1xpadding
if(root_y < size.second) if(root_y < size.second)
@ -262,7 +257,7 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion)
// Move tooltip left if it is right of screen // Move tooltip left if it is right of screen
auto screen_width = Gdk::Screen::get_default()->get_width(); auto screen_width = Gdk::Screen::get_default()->get_width();
rectangle.set_x(root_x + size.first > screen_width ? screen_width - size.first : root_x); rectangle.set_x(root_x + size.first > screen_width ? std::max(0, screen_width - size.first) /* Move tooptip right if it is left of screen */ : root_x);
} }
rectangle.set_width(size.first); rectangle.set_width(size.first);
@ -292,9 +287,9 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion)
void Tooltip::hide(const boost::optional<std::pair<int, int>> &last_mouse_pos, const boost::optional<std::pair<int, int>> &mouse_pos) { void Tooltip::hide(const boost::optional<std::pair<int, int>> &last_mouse_pos, const boost::optional<std::pair<int, int>> &mouse_pos) {
// Keep tooltip if mouse is moving towards it // Keep tooltip if mouse is moving towards it
// Calculated using dot product between the mouse_pos vector and the corners of the tooltip window // Calculated using dot product between the mouse_pos vector and the corners of the tooltip window
if(text_view && window && shown && last_mouse_pos && mouse_pos) { if(view && window && shown && last_mouse_pos && mouse_pos) {
static int root_x, root_y; static int root_x, root_y;
text_view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(last_mouse_pos->first, last_mouse_pos->second, root_x, root_y); view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(last_mouse_pos->first, last_mouse_pos->second, root_x, root_y);
int diff_x = mouse_pos->first - last_mouse_pos->first; int diff_x = mouse_pos->first - last_mouse_pos->first;
int diff_y = mouse_pos->second - last_mouse_pos->second; int diff_y = mouse_pos->second - last_mouse_pos->second;
class Corner { class Corner {
@ -326,7 +321,7 @@ void Tooltip::wrap_lines() {
while(true) { while(true) {
auto last_space = buffer->end(); auto last_space = buffer->end();
bool long_line = true; bool long_line = true;
for(unsigned c = 0; c <= 80; c++) { for(int c = 0; c <= max_columns; c++) {
if(*iter == ' ') if(*iter == ' ')
last_space = iter; last_space = iter;
if(iter.ends_line()) { if(iter.ends_line()) {
@ -337,7 +332,7 @@ void Tooltip::wrap_lines() {
return; return;
} }
if(long_line) { if(long_line) {
if(!last_space) { // If word is longer than 80 if(!last_space) { // If word is longer than max_columns
while(true) { while(true) {
if(iter.ends_line()) if(iter.ends_line())
break; break;
@ -366,21 +361,8 @@ void Tooltip::wrap_lines() {
} }
} }
void Tooltip::insert_with_links_tagged(const std::string &text) {
static std::regex http_regex("(https?://[a-zA-Z0-9\\-._~:/?#\\[\\]@!$&'()*+,;=]+[a-zA-Z0-9\\-_~/@$*+;=])");
std::smatch sm;
std::sregex_iterator it(text.begin(), text.end(), http_regex);
std::sregex_iterator end;
size_t start_pos = 0;
for(; it != end; ++it) {
buffer->insert(buffer->get_insert()->get_iter(), &text[start_pos], &text[it->position()]);
buffer->insert_with_tag(buffer->get_insert()->get_iter(), &text[it->position()], &text[it->position() + it->length()], link_tag);
start_pos = it->position() + it->length();
}
buffer->insert(buffer->get_insert()->get_iter(), &text[start_pos], &text[text.size()]);
}
void Tooltip::insert_markdown(const std::string &input) { void Tooltip::create_tags() {
if(!h1_tag) { if(!h1_tag) {
h1_tag = buffer->create_tag(); h1_tag = buffer->create_tag();
h1_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY; h1_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY;
@ -419,6 +401,24 @@ void Tooltip::insert_markdown(const std::string &input) {
strikethrough_tag = buffer->create_tag(); strikethrough_tag = buffer->create_tag();
strikethrough_tag->property_strikethrough() = true; strikethrough_tag->property_strikethrough() = true;
} }
}
void Tooltip::insert_with_links_tagged(const std::string &text) {
static std::regex http_regex("(https?://[a-zA-Z0-9\\-._~:/?#\\[\\]@!$&'()*+,;=]+[a-zA-Z0-9\\-_~/@$*+;=])");
std::smatch sm;
std::sregex_iterator it(text.begin(), text.end(), http_regex);
std::sregex_iterator end;
size_t start_pos = 0;
for(; it != end; ++it) {
buffer->insert(buffer->get_insert()->get_iter(), &text[start_pos], &text[it->position()]);
buffer->insert_with_tag(buffer->get_insert()->get_iter(), &text[it->position()], &text[it->position() + it->length()], link_tag);
start_pos = it->position() + it->length();
}
buffer->insert(buffer->get_insert()->get_iter(), &text[start_pos], &text[text.size()]);
}
void Tooltip::insert_markdown(const std::string &input) {
create_tags();
size_t i = 0; size_t i = 0;
@ -705,6 +705,7 @@ void Tooltip::insert_markdown(const std::string &input) {
if(starts_with(input, i, "```")) { if(starts_with(input, i, "```")) {
auto i_saved = i; auto i_saved = i;
if(forward_to({'\n'})) { if(forward_to({'\n'})) {
auto language = input.substr(i_saved + 3, i - (i_saved + 3));
i++; i++;
if(i < input.size()) { if(i < input.size()) {
auto start = i; auto start = i;
@ -717,7 +718,7 @@ void Tooltip::insert_markdown(const std::string &input) {
auto end = buffer->end(); auto end = buffer->end();
if(end.backward_char() && !end.starts_line()) if(end.backward_char() && !end.starts_line())
buffer->insert_at_cursor("\n"); buffer->insert_at_cursor("\n");
buffer->insert_with_tag(buffer->get_insert()->get_iter(), input.substr(start, i - start), code_block_tag); insert_code(input.substr(start, i - start), language, true);
buffer->insert_at_cursor("\n"); buffer->insert_at_cursor("\n");
i += 3; i += 3;
return true; return true;
@ -781,6 +782,106 @@ void Tooltip::insert_markdown(const std::string &input) {
remove_trailing_newlines(); remove_trailing_newlines();
} }
void Tooltip::insert_code(const std::string &code, const boost::optional<std::string> &language_identifier, bool block) {
create_tags();
auto insert_iter = buffer->get_insert()->get_iter();
Source::Mark start_mark(insert_iter);
bool style_format_type_description = false;
if(!block) {
auto pos = code.find("\n");
if(pos != std::string::npos)
block = true;
else if(insert_iter == buffer->begin() && !block && utf8_character_count(code) > static_cast<size_t>(max_columns)) {
block = true;
style_format_type_description = true;
}
}
buffer->insert_with_tag(insert_iter, code, block ? code_block_tag : code_tag);
if(view && language_identifier) {
auto tmp_view = Gsv::View();
tmp_view.get_buffer()->set_text(code);
auto scheme = view->get_source_buffer()->get_style_scheme();
tmp_view.get_source_buffer()->set_style_scheme(scheme);
Glib::RefPtr<Gsv::Language> language;
if(!language_identifier->empty())
language = Source::LanguageManager::get_default()->get_language(*language_identifier);
if(!language) {
if(auto source_view = dynamic_cast<Source::View *>(view))
language = source_view->language;
}
if(language) {
tmp_view.get_source_buffer()->set_language(language);
tmp_view.get_source_buffer()->set_highlight_syntax(true);
tmp_view.get_source_buffer()->ensure_highlight(tmp_view.get_buffer()->begin(), tmp_view.get_buffer()->end());
auto start_iter = start_mark->get_iter();
tmp_view.get_buffer()->get_tag_table()->foreach([this, &tmp_view, &start_iter](const Glib::RefPtr<Gtk::TextTag> &tmp_tag) {
if(tmp_tag->property_foreground_set()) {
auto tag = buffer->create_tag();
tag->property_foreground_rgba() = tmp_tag->property_foreground_rgba().get_value();
auto tmp_iter = tmp_view.get_source_buffer()->begin();
Gtk::TextIter tmp_start;
if(tmp_iter.starts_tag(tmp_tag))
tmp_start = tmp_iter;
while(tmp_iter.forward_to_tag_toggle(tmp_tag)) {
if(tmp_iter.ends_tag(tmp_tag)) {
auto start = start_iter;
start.forward_chars(tmp_start.get_offset());
auto end = start_iter;
end.forward_chars(tmp_iter.get_offset());
buffer->apply_tag(tag, start, end);
}
else
tmp_start = tmp_iter;
}
}
});
}
}
// Make long type descriptions readable
if(style_format_type_description) {
int initial_max_columns = max_columns;
std::list<Source::Mark> open_brackets;
for(auto iter = start_mark->get_iter(); iter; iter.forward_char()) {
if(*iter == '(' || *iter == '[' || *iter == '{' || *iter == '<')
open_brackets.emplace_back(iter);
else if(*iter == ')' || *iter == ']' || *iter == '}' || *iter == '>') {
if(!open_brackets.empty())
open_brackets.pop_back();
}
else if(*iter == ',' && !open_brackets.empty()) {
auto line_offset = open_brackets.back()->get_iter().get_line_offset();
if(iter.forward_char()) {
Source::Mark mark(iter);
auto previous = iter;
if(*iter == ' ' && iter.forward_char()) {
buffer->erase(previous, iter);
iter = mark->get_iter();
}
buffer->insert(mark->get_iter(), '\n' + std::string(line_offset + 1, ' '));
iter = mark->get_iter();
}
}
else if(*iter == ' ' && open_brackets.empty() && iter.get_line_offset() + (buffer->end().get_offset() - iter.get_offset()) > initial_max_columns) { // Add new line between return value, parameters and specifiers
auto previous = iter;
if(iter.forward_char()) {
Source::Mark mark(iter);
buffer->erase(previous, iter);
buffer->insert(mark->get_iter(), "\n");
iter = mark->get_iter();
}
}
max_columns = std::max(max_columns, iter.get_line_offset() + 1);
}
}
}
void Tooltip::remove_trailing_newlines() { void Tooltip::remove_trailing_newlines() {
auto end = buffer->end(); auto end = buffer->end();
while(end.starts_line() && end.backward_char()) { while(end.starts_line() && end.backward_char()) {

17
src/tooltips.hpp

@ -1,7 +1,8 @@
#pragma once #pragma once
#include "source_base.hpp"
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <functional> #include <functional>
#include <gtkmm.h> #include <gtksourceviewmm.h>
#include <list> #include <list>
#include <set> #include <set>
#include <string> #include <string>
@ -9,7 +10,7 @@
class Tooltip { class Tooltip {
public: public:
Tooltip(Gtk::TextView *text_view, const Gtk::TextIter &start_iter, const Gtk::TextIter &end_iter, std::function<void(Tooltip &)> set_buffer_); Tooltip(Gsv::View *view, const Gtk::TextIter &start_iter, const Gtk::TextIter &end_iter, std::function<void(Tooltip &)> set_buffer_);
Tooltip(std::function<void(Tooltip &tooltip)> set_buffer_); Tooltip(std::function<void(Tooltip &tooltip)> set_buffer_);
~Tooltip(); ~Tooltip();
@ -18,22 +19,23 @@ public:
void hide(const boost::optional<std::pair<int, int>> &last_mouse_pos, const boost::optional<std::pair<int, int>> &mouse_pos); void hide(const boost::optional<std::pair<int, int>> &last_mouse_pos, const boost::optional<std::pair<int, int>> &mouse_pos);
Gdk::Rectangle activation_rectangle; Gdk::Rectangle activation_rectangle;
Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark; Source::Mark start_mark, end_mark;
Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark;
Glib::RefPtr<Gtk::TextBuffer> buffer; Glib::RefPtr<Gtk::TextBuffer> buffer;
void insert_with_links_tagged(const std::string &text); void insert_with_links_tagged(const std::string &text);
void insert_markdown(const std::string &text); void insert_markdown(const std::string &text);
void insert_code(const std::string &code, const boost::optional<std::string> &language_identifier, bool block = false);
// Remove empty lines at end of buffer // Remove empty lines at end of buffer
void remove_trailing_newlines(); void remove_trailing_newlines();
private: private:
std::unique_ptr<Gtk::Window> window; std::unique_ptr<Gtk::Window> window;
void wrap_lines();
Gtk::TextView *text_view; Gsv::View *view;
std::function<void(Tooltip &)> set_buffer; std::function<void(Tooltip &)> set_buffer;
/// Initial maximum number of characters on each line
int max_columns = 80;
std::pair<int, int> size; std::pair<int, int> size;
Gdk::Rectangle rectangle; Gdk::Rectangle rectangle;
@ -52,6 +54,9 @@ private:
std::map<Glib::RefPtr<Gtk::TextTag>, std::string> links; std::map<Glib::RefPtr<Gtk::TextTag>, std::string> links;
std::map<Glib::RefPtr<Gtk::TextTag>, std::string> reference_links; std::map<Glib::RefPtr<Gtk::TextTag>, std::string> reference_links;
std::unordered_map<std::string, std::string> references; std::unordered_map<std::string, std::string> references;
void wrap_lines();
void create_tags();
}; };
class Tooltips { class Tooltips {

31
src/window.cpp

@ -1266,7 +1266,7 @@ void Window::set_menu_actions() {
std::set<Glib::RefPtr<Gtk::TextBuffer>> buffers; std::set<Glib::RefPtr<Gtk::TextBuffer>> buffers;
std::set<Source::View *> header_views; std::set<Source::View *> header_views;
std::vector<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark>>> fix_it_marks; std::list<std::pair<Source::Mark, Source::Mark>> fix_it_marks;
for(auto &fix_it : fix_its) { for(auto &fix_it : fix_its) {
auto view = current_view; auto view = current_view;
if(fix_it.path != current_view->file_path) { if(fix_it.path != current_view->file_path) {
@ -1278,27 +1278,24 @@ void Window::set_menu_actions() {
} }
auto start_iter = view->get_iter_at_line_pos(fix_it.offsets.first.line, fix_it.offsets.first.index); auto start_iter = view->get_iter_at_line_pos(fix_it.offsets.first.line, fix_it.offsets.first.index);
auto end_iter = view->get_iter_at_line_pos(fix_it.offsets.second.line, fix_it.offsets.second.index); auto end_iter = view->get_iter_at_line_pos(fix_it.offsets.second.line, fix_it.offsets.second.index);
fix_it_marks.emplace_back(view->get_buffer()->create_mark(start_iter), view->get_buffer()->create_mark(end_iter)); fix_it_marks.emplace_back(start_iter, end_iter);
buffers.emplace(view->get_buffer()); buffers.emplace(view->get_buffer());
} }
for(auto &buffer : buffers) for(auto &buffer : buffers)
buffer->begin_user_action(); buffer->begin_user_action();
for(size_t i = 0; i < fix_its.size(); ++i) { auto fix_it_mark = fix_it_marks.begin();
auto buffer = fix_it_marks[i].first->get_buffer(); for(auto &fix_it : fix_its) {
if(fix_its[i].type == Source::FixIt::Type::insert) auto buffer = fix_it_mark->first->get_buffer();
buffer->insert(fix_it_marks[i].first->get_iter(), fix_its[i].source); if(fix_it.type == Source::FixIt::Type::insert)
else if(fix_its[i].type == Source::FixIt::Type::replace) { buffer->insert(fix_it_mark->first->get_iter(), fix_it.source);
buffer->erase(fix_it_marks[i].first->get_iter(), fix_it_marks[i].second->get_iter()); else if(fix_it.type == Source::FixIt::Type::replace) {
buffer->insert(fix_it_marks[i].first->get_iter(), fix_its[i].source); buffer->erase(fix_it_mark->first->get_iter(), fix_it_mark->second->get_iter());
} buffer->insert(fix_it_mark->first->get_iter(), fix_it.source);
else if(fix_its[i].type == Source::FixIt::Type::erase) }
buffer->erase(fix_it_marks[i].first->get_iter(), fix_it_marks[i].second->get_iter()); else if(fix_it.type == Source::FixIt::Type::erase)
} buffer->erase(fix_it_mark->first->get_iter(), fix_it_mark->second->get_iter());
for(auto &mark_pair : fix_it_marks) { ++fix_it_mark;
auto buffer = mark_pair.first->get_buffer();
buffer->delete_mark(mark_pair.first);
buffer->delete_mark(mark_pair.second);
} }
for(auto &buffer : buffers) for(auto &buffer : buffers)
buffer->end_user_action(); buffer->end_user_action();

2
tests/stubs/selection_dialog.cpp

@ -16,8 +16,6 @@ std::unique_ptr<SelectionDialog> SelectionDialog::instance;
SelectionDialog::SelectionDialog(Gtk::TextView *text_view, const boost::optional<Gtk::TextIter> &start_iter, bool show_search_entry, bool use_markup) SelectionDialog::SelectionDialog(Gtk::TextView *text_view, const boost::optional<Gtk::TextIter> &start_iter, bool show_search_entry, bool use_markup)
: SelectionDialogBase(text_view, start_iter, show_search_entry, use_markup) {} : SelectionDialogBase(text_view, start_iter, show_search_entry, use_markup) {}
SelectionDialogBase::~SelectionDialogBase() {}
bool SelectionDialog::on_key_press(GdkEventKey *key) { return true; } bool SelectionDialog::on_key_press(GdkEventKey *key) { return true; }
std::unique_ptr<CompletionDialog> CompletionDialog::instance; std::unique_ptr<CompletionDialog> CompletionDialog::instance;

1
tests/tooltips_test.cpp

@ -4,6 +4,7 @@
int main() { int main() {
auto app = Gtk::Application::create(); auto app = Gtk::Application::create();
Gsv::init();
auto get_markdown_tooltip = [](const std::string &input) { auto get_markdown_tooltip = [](const std::string &input) {
auto tooltip = std::make_unique<Tooltip>([&](Tooltip &tooltip) { auto tooltip = std::make_unique<Tooltip>([&](Tooltip &tooltip) {

Loading…
Cancel
Save