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 "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->get_buffer()->signal_changed().connect([this, &last_keyval] {
if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) {

6
src/autocomplete.hpp

@ -7,9 +7,9 @@
#include <thread>
class Autocomplete {
Gtk::TextView *view;
Gsv::View *view;
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.
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; };
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 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)) {
auto clang_version = sm[1].str();
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());
}
#endif
@ -202,7 +202,7 @@ std::vector<std::string> CompileCommands::get_arguments(const boost::filesystem:
#endif
#ifdef _WIN32
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());
#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)
environment.emplace_back(e.c_str());
size_t environ_size = 0;
while(environ[environ_size] != nullptr)
while(environ[environ_size])
++environ_size;
for(size_t c = 0; c < environ_size; ++c)
environment.emplace_back(environ[c]);
@ -340,13 +340,11 @@ std::vector<Debug::LLDB::Frame> Debug::LLDB::get_backtrace() {
backtrace_frame.index = c_f;
if(frame.GetFunctionName() != nullptr)
backtrace_frame.function_name = frame.GetFunctionName();
if(auto function_name = frame.GetFunctionName())
backtrace_frame.function_name = function_name;
auto module_filename = frame.GetModule().GetFileSpec().GetFilename();
if(module_filename != nullptr) {
if(auto module_filename = frame.GetModule().GetFileSpec().GetFilename())
backtrace_frame.module_filename = module_filename;
}
auto line_entry = frame.GetLineEntry();
if(line_entry.IsValid()) {
@ -381,8 +379,8 @@ std::vector<Debug::LLDB::Variable> Debug::LLDB::get_variables() {
Debug::LLDB::Variable variable;
variable.thread_index_id = thread.GetIndexID();
variable.frame_index = c_f;
if(value.GetName() != nullptr)
variable.name = value.GetName();
if(auto name = value.GetName())
variable.name = name;
value.GetDescription(stream);
variable.value = stream.GetData();
@ -442,7 +440,8 @@ std::string Debug::LLDB::get_value(const std::string &variable, const boost::fil
lldb::SBStream stream;
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();
if(declaration.IsValid()) {
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 {};
}

2
src/dialogs_win.cpp

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

4
src/filesystem.cpp

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

8
src/git.cpp

@ -8,11 +8,11 @@ Git::Error Git::error;
std::string Git::Error::message() noexcept {
#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
const git_error *last_error = giterr_last();
auto last_error = giterr_last();
#endif
if(last_error == nullptr)
if(!last_error)
return std::string();
else
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 {
if(cpath == nullptr)
if(!cpath)
return boost::filesystem::path();
auto cpath_length = cpath_length_.value_or(strlen(cpath));
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 {
public:
CursorLocation(Source::View *view, const Gtk::TextIter &iter) : view(view), mark(iter.get_buffer()->create_mark(iter, false)) {}
~CursorLocation() {
mark->get_buffer()->delete_mark(mark);
}
CursorLocation(Source::View *view, const Gtk::TextIter &iter) : view(view), mark(iter, false) {}
Source::View *view;
Glib::RefPtr<Gtk::TextBuffer::Mark> mark;
Source::Mark mark;
};
private:

2
src/project.cpp

@ -629,7 +629,7 @@ void Project::LLDB::debug_show_variables() {
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) {

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)
: 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 gio_application = Glib::wrap(g_application, true);
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() {
if(!is_visible())
return;

5
src/selection_dialog.hpp

@ -1,4 +1,5 @@
#pragma once
#include "source_base.hpp"
#include <boost/optional.hpp>
#include <functional>
#include <gtkmm.h>
@ -38,7 +39,7 @@ class SelectionDialogBase {
public:
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 erase_rows();
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(unsigned int index, const std::string &text, bool hide_window)> on_select;
std::function<void(const std::string &text)> on_search_entry_changed;
Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark;
Source::Mark start_mark;
protected:
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_set() = true;
get_buffer()->create_tag("def:warning");
get_buffer()->create_tag("def:warning_underline");
get_buffer()->create_tag("def:error");
get_buffer()->create_tag("def:error_underline");
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 tag_table = get_buffer()->get_tag_table();
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");
if(style && (style->property_foreground_set() || style->property_background_set())) {
Glib::ustring warning_property;
if(style->property_foreground_set()) {
if(style->property_foreground_set())
warning_property = style->property_foreground().get_value();
diagnostic_tag->property_foreground() = warning_property;
}
else if(style->property_background_set())
warning_property = style->property_background().get_value();
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 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));
}
}
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");
if(style && (style->property_foreground_set() || style->property_background_set())) {
Glib::ustring error_property;
if(style->property_foreground_set()) {
if(style->property_foreground_set())
error_property = style->property_foreground().get_value();
diagnostic_tag->property_foreground() = error_property;
}
else if(style->property_background_set())
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) {
auto mark_name = mark->get_name();
if(get_buffer()->get_has_selection() && mark_name == "selection_bound")
if(mark_name == "selection_bound") {
if(get_buffer()->get_has_selection())
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();
delayed_tooltips_connection.disconnect();
@ -672,10 +669,6 @@ void Source::View::setup_signals() {
if(!keep_previous_extended_selections)
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) {
@ -929,10 +922,9 @@ void Source::View::setup_format_style(bool is_generic_view) {
}
}
if(left_gravity_insert) {
auto mark = get_buffer()->create_mark(start);
Mark mark(start);
get_buffer()->insert(start, replacement_str);
get_buffer()->place_cursor(mark->get_iter());
get_buffer()->delete_mark(mark);
}
else
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.
Gtk::TextIter 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_end = selection_end.get_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())
get_buffer()->insert(line_it, tab);
}
get_buffer()->delete_mark(selection_end_mark);
return true;
}
// 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 != '}') {
get_buffer()->insert_at_cursor('\n' + tabs + tab);
auto iter = get_buffer()->get_insert()->get_iter();
auto mark = get_buffer()->create_mark(iter);
Mark mark(iter);
iter.forward_to_line_end();
get_buffer()->insert(iter, '\n' + tabs + static_cast<char>(close_symbol));
scroll_to(get_buffer()->get_insert());
get_buffer()->place_cursor(mark->get_iter());
get_buffer()->delete_mark(mark);
return true;
}
else {
@ -2603,12 +2593,11 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey *key) {
if(!iter.ends_line() && *iter != ')' && *iter != ']') {
get_buffer()->insert_at_cursor('\n' + tabs + tab);
auto iter = get_buffer()->get_insert()->get_iter();
auto mark = get_buffer()->create_mark(iter);
Mark mark(iter);
iter.forward_to_line_end();
get_buffer()->insert(iter, '\n' + tabs + '}');
scroll_to(get_buffer()->get_insert());
get_buffer()->place_cursor(mark->get_iter());
get_buffer()->delete_mark(mark);
return true;
}
else {
@ -2886,16 +2875,14 @@ bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *key) {
auto after_end = end;
if(before_start.backward_char() && *before_start == '*' && before_start.backward_char() && *before_start == '/' &&
*after_end == '*' && after_end.forward_char() && *after_end == '/') {
auto start_mark = get_buffer()->create_mark(start);
auto end_mark = get_buffer()->create_mark(end);
Mark start_mark(start);
Mark end_mark(end);
get_buffer()->erase(before_start, start);
after_end = end_mark->get_iter();
after_end.forward_chars(2);
get_buffer()->erase(end_mark->get_iter(), after_end);
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;
}
}
@ -2960,16 +2947,14 @@ bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *key) {
if(!left.empty() && !right.empty()) {
Gtk::TextIter start, end;
get_buffer()->get_selection_bounds(start, end);
auto start_mark = get_buffer()->create_mark(start);
auto end_mark = get_buffer()->create_mark(end);
Mark start_mark(start);
Mark end_mark(end);
get_buffer()->insert(start, left);
get_buffer()->insert(end_mark->get_iter(), right);
auto start_mark_next_iter = start_mark->get_iter();
start_mark_next_iter.forward_chars(left.size());
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 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);
auto last_insert = get_buffer()->create_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_insert = std::make_shared<Mark>(get_buffer()->get_insert()->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 {
if(mark->get_name() == "insert") {
if(enable_multiple_cursors || enable_multiple_cursors_placements) {
auto set_enable_multiple_cursors = enable_multiple_cursors;
if(set_enable_multiple_cursors)
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) {
for(auto &extra_cursor : extra_cursors) {
auto iter = extra_cursor.insert->get_iter();
@ -1258,7 +1258,7 @@ void Source::BaseView::setup_extra_cursor_signals() {
if(set_enable_multiple_cursors)
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") {
@ -1266,7 +1266,7 @@ void Source::BaseView::setup_extra_cursor_signals() {
auto set_enable_multiple_cursors = enable_multiple_cursors;
if(set_enable_multiple_cursors)
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) {
for(auto &extra_cursor : extra_cursors) {
auto iter = extra_cursor.selection_bound->get_iter();
@ -1278,7 +1278,7 @@ void Source::BaseView::setup_extra_cursor_signals() {
if(set_enable_multiple_cursors)
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);
}
auto mark = get_buffer()->create_mark(iter);
Mark mark(iter);
get_source_buffer()->begin_user_action();
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();
iter = mark->get_iter();
get_buffer()->delete_mark(mark);
for(auto &parameter_offsets_and_sized : parameter_offsets_and_sizes_map) {
snippet_parameters_list.emplace_back();
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)
: extra_cursor_selection(extra_cursor_selection),
insert(start_iter.get_buffer()->create_mark(start_iter, false)),
selection_bound(end_iter.get_buffer()->create_mark(end_iter, false)),
insert(start_iter, false),
selection_bound(end_iter, false),
line_offset(line_offset), snippet(snippet) {
insert->set_visible(true);
if(start_iter != end_iter)
@ -1671,8 +1670,6 @@ Source::BaseView::ExtraCursor::ExtraCursor(const Glib::RefPtr<Gtk::TextTag> &ext
Source::BaseView::ExtraCursor::~ExtraCursor() {
insert->get_buffer()->remove_tag(extra_cursor_selection, insert->get_iter(), selection_bound->get_iter());
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) {

28
src/source_base.hpp

@ -11,6 +11,22 @@
#include <vector>
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 {
public:
SearchView();
@ -162,8 +178,8 @@ namespace Source {
void move(const Gtk::TextIter &iter, bool selection_activated);
void move_selection_bound(const Gtk::TextIter &iter);
Glib::RefPtr<Gtk::TextBuffer::Mark> insert;
Glib::RefPtr<Gtk::TextBuffer::Mark> selection_bound;
Mark insert;
Mark selection_bound;
/// Used when moving cursor up/down lines
int line_offset;
/// Set to true when the extra cursor corresponds to a snippet parameter
@ -181,12 +197,8 @@ namespace Source {
class SnippetParameter {
public:
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()) {}
~SnippetParameter() {
start->get_buffer()->delete_mark(start);
end->get_buffer()->delete_mark(end);
}
Glib::RefPtr<Gtk::TextBuffer::Mark> start, end;
: start(start_iter, false), end(end_iter, false), size(end_iter.get_offset() - start_iter.get_offset()) {}
Mark start, end;
/// Used to check if the parameter has been deleted, and should be passed on next tab
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) {
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();
if(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++;
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"))
clangmm::remove_include_guard(buffer);
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;
parse_state.compare_exchange_strong(expected, ParseState::restarting);
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) {
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);
});
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
struct Content {
std::string value;
bool markdown;
std::string kind;
};
std::vector<Content> contents;
auto contents_pt = result.get_child_optional("contents");
@ -1139,20 +1139,28 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect
return;
auto value = contents_pt->get_value<std::string>("");
if(!value.empty())
contents.emplace_back(Content{value, true});
contents.emplace_back(Content{value, "markdown"});
else {
auto value_pt = contents_pt->get_optional<std::string>("value");
if(value_pt)
contents.emplace_back(Content{*value_pt, contents_pt->get<std::string>("kind", "") == "markdown"});
if(value_pt) {
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 {
for(auto it = contents_pt->begin(); it != contents_pt->end(); ++it) {
auto value = it->second.get<std::string>("value", "");
if(!value.empty())
contents.emplace_back(Content{value, contents_pt->get<std::string>("kind", "") == "markdown"});
if(!value.empty()) {
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 {
value = it->second.get_value<std::string>("");
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++) {
if(i > 0)
tooltip.buffer->insert_at_cursor("\n\n");
if(contents[i].markdown && language_id != "python") // TODO: python-language-server might support markdown in the future
tooltip.insert_markdown(contents[i].value);
else {
if(contents[i].kind == "plaintext" || contents[i].kind.empty())
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();
}
}
#ifdef JUCI_ENABLE_DEBUG
if(language_id == "rust" && capabilities.definition) {
@ -1224,7 +1233,8 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect
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) {
auto label = parameter_it->second.get<std::string>("label", "");
auto insert = label;
auto plaintext = parameter_it->second.get<std::string>("documentation", "");
std::string markdown;
if(plaintext.empty()) {
auto documentation = parameter_it->second.get_child_optional("documentation");
if(documentation) {
auto kind = documentation->get<std::string>("kind", "");
if(kind == "markdown")
markdown = documentation->get<std::string>("value", "");
else
plaintext = documentation->get<std::string>("value", "");
auto documentation = parameter_it->second.get<std::string>("documentation", "");
std::string kind;
if(documentation.empty()) {
auto documentation_pt = parameter_it->second.get_child_optional("documentation");
if(documentation_pt) {
documentation = documentation_pt->get<std::string>("value", "");
kind = documentation_pt->get<std::string>("kind", "");
}
}
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) {
auto label = it->second.get<std::string>("label", "");
auto detail = it->second.get<std::string>("detail", "");
auto plaintext = it->second.get<std::string>("documentation", "");
std::string markdown;
if(plaintext.empty()) {
auto documentation = it->second.get_child_optional("documentation");
if(documentation) {
auto kind = documentation->get<std::string>("kind", "");
if(kind == "markdown")
markdown = documentation->get<std::string>("value", "");
else
plaintext = documentation->get<std::string>("value", "");
auto documentation = it->second.get<std::string>("documentation", "");
std::string kind;
if(documentation.empty()) {
auto documentation_pt = it->second.get_child_optional("documentation");
if(documentation_pt) {
documentation = documentation_pt->get<std::string>("value", "");
kind = documentation_pt->get<std::string>("kind", "");
}
}
auto insert = it->second.get<std::string>("insertText", "");
@ -1600,12 +1604,7 @@ void Source::LanguageProtocolView::setup_autocomplete() {
}
if(starts_with(label, prefix)) {
autocomplete->rows.emplace_back(std::move(label));
if(!plaintext.empty() && detail != plaintext) {
if(!detail.empty())
detail += "\n\n";
detail += plaintext;
}
autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(detail), std::move(markdown)});
autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(detail), std::move(documentation), std::move(kind)});
}
}
}
@ -1621,7 +1620,7 @@ void Source::LanguageProtocolView::setup_autocomplete() {
for(auto &snippet : *snippets) {
if(starts_with(snippet.prefix, 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)> {
auto plaintext = autocomplete_rows[index].plaintext;
auto markdown = autocomplete_rows[index].markdown;
if(plaintext.empty() && markdown.empty())
auto autocomplete = autocomplete_rows[index];
if(autocomplete.detail.empty() && autocomplete.documentation.empty())
return nullptr;
return [plaintext = std::move(plaintext), markdown = std::move(markdown)](Tooltip &tooltip) {
if(!plaintext.empty())
tooltip.insert_with_links_tagged(plaintext);
if(!markdown.empty()) {
if(!plaintext.empty())
return [this, autocomplete = std::move(autocomplete)](Tooltip &tooltip) {
if(!autocomplete.detail.empty()) {
tooltip.insert_code(autocomplete.detail, language ? language->get_id().raw() : std::string{});
tooltip.remove_trailing_newlines();
}
if(!autocomplete.documentation.empty()) {
if(tooltip.buffer->size() > 0)
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 {
std::string insert;
std::string plaintext;
std::string markdown;
std::string detail;
std::string documentation;
std::string kind;
};
std::vector<AutocompleteRow> autocomplete_rows;
@ -214,17 +215,7 @@ namespace Source {
std::vector<LanguageProtocol::Diagnostic> last_diagnostics;
sigc::connection update_type_coverage_connection;
class TypeCoverageMark {
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;
std::list<std::pair<Mark, Mark>> type_coverage_marks;
size_t num_warnings = 0, num_errors = 0, num_fix_its = 0;
void update_type_coverage();
std::atomic<int> update_type_coverage_retries = {60};

34
src/source_spellcheck.cpp

@ -7,7 +7,7 @@
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) {
if(spellcheck_config == nullptr)
if(!spellcheck_config)
spellcheck_config = new_aspell_config();
spellcheck_checker = nullptr;
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]() {
if(spellcheck_checker == nullptr)
if(!spellcheck_checker)
return;
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
get_buffer()->signal_insert().connect([this](const Gtk::TextIter &start_iter, const Glib::ustring &inserted_string, int) {
if(spellcheck_checker == nullptr)
return;
if(!disable_spellcheck)
if(!spellcheck_checker)
return;
if(disable_spellcheck) {
auto iter = start_iter;
if(!is_word_iter(iter) && !iter.starts_line())
iter.backward_char();
@ -142,16 +140,18 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path,
auto word = get_word(iter);
get_buffer()->remove_tag(spellcheck_error_tag, word.first, word.second);
}
}
}, false);
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(SelectionDialog::get())
SelectionDialog::get()->hide();
if(!spellcheck_checker)
return;
delayed_spellcheck_suggestions_connection.disconnect();
if(get_buffer()->get_has_selection())
return;
delayed_spellcheck_suggestions_connection = Glib::signal_timeout().connect([this]() {
if(get_buffer()->get_insert()->get_iter().has_tag(spellcheck_error_tag)) {
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) {
delayed_spellcheck_suggestions_connection.disconnect();
return false;
@ -221,7 +214,7 @@ Source::SpellCheckView::~SpellCheckView() {
delayed_spellcheck_suggestions_connection.disconnect();
delayed_spellcheck_error_clear.disconnect();
if(spellcheck_checker != nullptr)
if(spellcheck_checker)
delete_aspell_speller(spellcheck_checker);
signal_tag_added_connection.disconnect();
@ -234,7 +227,7 @@ void Source::SpellCheckView::configure() {
aspell_config_replace(spellcheck_config, "encoding", "utf-8");
}
spellcheck_possible_err = new_aspell_speller(spellcheck_config);
if(spellcheck_checker != nullptr)
if(spellcheck_checker)
delete_aspell_speller(spellcheck_checker);
spellcheck_checker = nullptr;
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) {
if(spellcheck_checker == nullptr)
if(!spellcheck_checker)
return;
auto iter = start;
while(iter && iter < end) {
@ -550,9 +543,8 @@ std::vector<std::string> Source::SpellCheckView::get_spellcheck_suggestions(cons
std::vector<std::string> words;
const char *word;
while((word = aspell_string_enumeration_next(elements)) != nullptr) {
while((word = aspell_string_enumeration_next(elements)))
words.emplace_back(word);
}
delete_aspell_string_enumeration(elements);
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, "?");
}
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)
get_buffer()->insert_with_tag(get_buffer()->end(), umessage, bold_tag);
else
get_buffer()->insert(get_buffer()->end(), umessage);
auto start_iter = start_mark->get_iter();
get_buffer()->delete_mark(start_mark);
auto end_iter = get_buffer()->get_insert()->get_iter();
apply_link_tags(start_iter, end_iter);

219
src/tooltips.cpp

@ -11,34 +11,30 @@
std::set<Tooltip *> Tooltips::shown_tooltips;
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_)
: 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_)) {}
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), end_mark(end_iter), view(view), set_buffer(std::move(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() {
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() {
if(text_view) {
if(view) {
auto iter = start_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()) {
while(iter.forward_char() && iter != end_iter) {
Gdk::Rectangle rectangle;
text_view->get_iter_location(iter, rectangle);
view->get_iter_location(iter, rectangle);
activation_rectangle.join(rectangle);
}
}
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_y(location_window_y);
}
@ -82,20 +78,18 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion)
buffer = Gtk::TextBuffer::create();
if(text_view) {
auto tag = text_view->get_buffer()->get_tag_table()->lookup("def:warning");
auto new_tag = buffer->create_tag("def:warning");
if(tag->property_foreground_set())
new_tag->property_foreground_rgba() = tag->property_foreground_rgba().get_value();
if(tag->property_background_set())
new_tag->property_background_rgba() = tag->property_background_rgba().get_value();
tag = text_view->get_buffer()->get_tag_table()->lookup("def:error");
new_tag = buffer->create_tag("def:error");
if(tag->property_foreground_set())
new_tag->property_foreground_rgba() = tag->property_foreground_rgba().get_value();
if(tag->property_background_set())
new_tag->property_background_rgba() = tag->property_background_rgba().get_value();
if(view) {
auto create_tag_from_style = [this](const std::string &style_name) {
if(auto style = view->get_source_buffer()->get_style_scheme()->get_style(style_name)) {
auto tag = buffer->create_tag(style_name);
if(style->property_foreground_set())
tag->property_foreground() = style->property_foreground();
if(style->property_background_set())
tag->property_background() = style->property_background();
}
};
create_tag_from_style("def:warning");
create_tag_from_style("def:error");
}
link_tag = buffer->create_tag("link");
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;
if(std::regex_match(link, sm, regex)) {
auto path = boost::filesystem::path(sm[1].str());
if(auto view = dynamic_cast<Source::View *>(text_view))
path = filesystem::get_normal_path(view->file_path.parent_path() / path);
if(auto source_view = dynamic_cast<Source::View *>(view))
path = filesystem::get_normal_path(source_view->file_path.parent_path() / path);
boost::system::error_code 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);
if(auto view = dynamic_cast<Source::View *>(text_view))
path = filesystem::get_normal_path(view->file_path.parent_path() / path);
if(auto source_view = dynamic_cast<Source::View *>(view))
path = filesystem::get_normal_path(source_view->file_path.parent_path() / path);
boost::system::error_code ec;
if(boost::filesystem::is_regular_file(path, ec) && Notebook::get().open(path))
return true;
@ -206,13 +200,14 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion)
// Add ScrolledWindow if needed
Gtk::Widget *widget = tooltip_text_view;
auto screen_width = Gdk::Screen::get_default()->get_width();
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());
scrolled_window->property_hscrollbar_policy() = Gtk::PolicyType::POLICY_NEVER;
scrolled_window->property_vscrollbar_policy() = Gtk::PolicyType::POLICY_AUTOMATIC;
scrolled_window->property_hscrollbar_policy() = size.first > screen_width - 6 ? Gtk::PolicyType::POLICY_AUTOMATIC : Gtk::PolicyType::POLICY_NEVER;
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->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;
}
@ -225,7 +220,7 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion)
#endif
window->signal_realize().connect([this] {
if(!text_view) {
if(!view) {
auto &dialog = SelectionDialog::get();
if(dialog && dialog->is_visible()) {
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
int root_x = 0, root_y = 0;
if(text_view) {
if(view) {
Gdk::Rectangle visible_rect;
text_view->get_visible_rect(visible_rect);
view->get_visible_rect(visible_rect);
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();
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
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
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);
@ -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) {
// Keep tooltip if mouse is moving towards it
// 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;
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_y = mouse_pos->second - last_mouse_pos->second;
class Corner {
@ -326,7 +321,7 @@ void Tooltip::wrap_lines() {
while(true) {
auto last_space = buffer->end();
bool long_line = true;
for(unsigned c = 0; c <= 80; c++) {
for(int c = 0; c <= max_columns; c++) {
if(*iter == ' ')
last_space = iter;
if(iter.ends_line()) {
@ -337,7 +332,7 @@ void Tooltip::wrap_lines() {
return;
}
if(long_line) {
if(!last_space) { // If word is longer than 80
if(!last_space) { // If word is longer than max_columns
while(true) {
if(iter.ends_line())
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) {
h1_tag = buffer->create_tag();
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->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;
@ -705,6 +705,7 @@ void Tooltip::insert_markdown(const std::string &input) {
if(starts_with(input, i, "```")) {
auto i_saved = i;
if(forward_to({'\n'})) {
auto language = input.substr(i_saved + 3, i - (i_saved + 3));
i++;
if(i < input.size()) {
auto start = i;
@ -717,7 +718,7 @@ void Tooltip::insert_markdown(const std::string &input) {
auto end = buffer->end();
if(end.backward_char() && !end.starts_line())
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");
i += 3;
return true;
@ -781,6 +782,106 @@ void Tooltip::insert_markdown(const std::string &input) {
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() {
auto end = buffer->end();
while(end.starts_line() && end.backward_char()) {

17
src/tooltips.hpp

@ -1,7 +1,8 @@
#pragma once
#include "source_base.hpp"
#include <boost/optional.hpp>
#include <functional>
#include <gtkmm.h>
#include <gtksourceviewmm.h>
#include <list>
#include <set>
#include <string>
@ -9,7 +10,7 @@
class Tooltip {
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();
@ -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);
Gdk::Rectangle activation_rectangle;
Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark;
Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark;
Source::Mark start_mark, end_mark;
Glib::RefPtr<Gtk::TextBuffer> buffer;
void insert_with_links_tagged(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
void remove_trailing_newlines();
private:
std::unique_ptr<Gtk::Window> window;
void wrap_lines();
Gtk::TextView *text_view;
Gsv::View *view;
std::function<void(Tooltip &)> set_buffer;
/// Initial maximum number of characters on each line
int max_columns = 80;
std::pair<int, int> size;
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> reference_links;
std::unordered_map<std::string, std::string> references;
void wrap_lines();
void create_tags();
};
class Tooltips {

31
src/window.cpp

@ -1266,7 +1266,7 @@ void Window::set_menu_actions() {
std::set<Glib::RefPtr<Gtk::TextBuffer>> buffers;
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) {
auto view = current_view;
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 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());
}
for(auto &buffer : buffers)
buffer->begin_user_action();
for(size_t i = 0; i < fix_its.size(); ++i) {
auto buffer = fix_it_marks[i].first->get_buffer();
if(fix_its[i].type == Source::FixIt::Type::insert)
buffer->insert(fix_it_marks[i].first->get_iter(), fix_its[i].source);
else if(fix_its[i].type == Source::FixIt::Type::replace) {
buffer->erase(fix_it_marks[i].first->get_iter(), fix_it_marks[i].second->get_iter());
buffer->insert(fix_it_marks[i].first->get_iter(), fix_its[i].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());
}
for(auto &mark_pair : fix_it_marks) {
auto buffer = mark_pair.first->get_buffer();
buffer->delete_mark(mark_pair.first);
buffer->delete_mark(mark_pair.second);
auto fix_it_mark = fix_it_marks.begin();
for(auto &fix_it : fix_its) {
auto buffer = fix_it_mark->first->get_buffer();
if(fix_it.type == Source::FixIt::Type::insert)
buffer->insert(fix_it_mark->first->get_iter(), fix_it.source);
else if(fix_it.type == Source::FixIt::Type::replace) {
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_it.type == Source::FixIt::Type::erase)
buffer->erase(fix_it_mark->first->get_iter(), fix_it_mark->second->get_iter());
++fix_it_mark;
}
for(auto &buffer : buffers)
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)
: SelectionDialogBase(text_view, start_iter, show_search_entry, use_markup) {}
SelectionDialogBase::~SelectionDialogBase() {}
bool SelectionDialog::on_key_press(GdkEventKey *key) { return true; }
std::unique_ptr<CompletionDialog> CompletionDialog::instance;

1
tests/tooltips_test.cpp

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

Loading…
Cancel
Save