From c04bbe81c672fa7201282fa5701fc6b7ba859813 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 21 Jun 2019 11:13:41 +0200 Subject: [PATCH] Debugging in C/C++: tooltips now show values of evaluated expressions that does not change program state, for instance return values from const member functions, and results from expressions like: arr[a + b]. Additionally, now closes completion dialog on paste. --- src/debug_lldb.cc | 26 ++++-- src/debug_lldb.h | 5 +- src/source_base.cc | 4 + src/source_clang.cc | 154 +++++++++++++++++++++++++------- src/source_language_protocol.cc | 3 +- src/tooltips.cc | 3 + 6 files changed, 156 insertions(+), 39 deletions(-) diff --git a/src/debug_lldb.cc b/src/debug_lldb.cc index 0ff35cc..7fdbaf0 100644 --- a/src/debug_lldb.cc +++ b/src/debug_lldb.cc @@ -414,7 +414,6 @@ void Debug::LLDB::cancel() { } std::string Debug::LLDB::get_value(const std::string &variable, const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index) { - std::string variable_value; std::lock_guard lock(mutex); if(state == lldb::StateType::eStateStopped) { auto frame = process->GetSelectedThread().GetSelectedFrame(); @@ -435,23 +434,40 @@ std::string Debug::LLDB::get_value(const std::string &variable, const boost::fil value_decl_path /= file_spec.GetFilename(); if(value_decl_path == file_path) { value.GetDescription(stream); - variable_value = stream.GetData(); - break; + return stream.GetData(); } } } } } } + } + return {}; +} + +std::string Debug::LLDB::get_value(const std::string &expression) { + std::string variable_value; + std::lock_guard lock(mutex); + if(state == lldb::StateType::eStateStopped) { + auto frame = process->GetSelectedThread().GetSelectedFrame(); if(variable_value.empty()) { - //In case a variable is missing file and line number, only do check on name - auto value = frame.GetValueForVariablePath(variable.c_str()); + // Attempt to get variable from variable expression, using the faster GetValueForVariablePath + auto value = frame.GetValueForVariablePath(expression.c_str()); if(value.IsValid()) { lldb::SBStream stream; value.GetDescription(stream); variable_value = stream.GetData(); } } + if(variable_value.empty()) { + // Attempt to get variable from variable expression, using the slower EvaluateExpression + auto value = frame.EvaluateExpression(expression.c_str()); + if(value.IsValid() && !value.GetError().Fail()) { + lldb::SBStream stream; + value.GetDescription(stream); + variable_value = stream.GetData(); + } + } } return variable_value; } diff --git a/src/debug_lldb.h b/src/debug_lldb.h index 6f36b25..32947d8 100644 --- a/src/debug_lldb.h +++ b/src/debug_lldb.h @@ -63,7 +63,10 @@ namespace Debug { void cancel(); - std::string get_value(const std::string &variable, const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index); + /// Get value using variable name and location + std::string get_value(const std::string &variable_name, const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index); + /// Get value from expression + std::string get_value(const std::string &expression); std::string get_return_value(const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index); bool is_invalid(); diff --git a/src/source_base.cc b/src/source_base.cc index f4b113e..9a2a86d 100644 --- a/src/source_base.cc +++ b/src/source_base.cc @@ -2,6 +2,7 @@ #include "config.h" #include "git.h" #include "info.h" +#include "selection_dialog.h" #include "terminal.h" #include "utility.h" #include @@ -678,6 +679,9 @@ void Source::BaseView::cleanup_whitespace_characters(const Gtk::TextIter &iter) } void Source::BaseView::paste() { + if(CompletionDialog::get()) + CompletionDialog::get()->hide(); + enable_multiple_cursors = true; ScopeGuard guard{[this] { enable_multiple_cursors = false; diff --git a/src/source_clang.cc b/src/source_clang.cc index 7a690f2..d25c2e9 100644 --- a/src/source_clang.cc +++ b/src/source_clang.cc @@ -375,49 +375,139 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle) for(size_t c = clang_tokens->size() - 1; c != static_cast(-1); --c) { auto &token = (*clang_tokens)[c]; auto &token_offsets = clang_tokens_offsets[c]; - if(token.is_identifier() || token.get_spelling() == "auto") { + auto token_spelling = token.get_spelling(); + if(token.is_identifier() || token_spelling == "auto" || token_spelling == "[" || token_spelling == "]" || token_spelling == "*" || token_spelling == "&") { if(line == token_offsets.first.line - 1 && index >= token_offsets.first.index - 1 && index <= token_offsets.second.index - 1) { auto cursor = token.get_cursor(); auto referenced = cursor.get_referenced(); - if(referenced) { + if(referenced || token_spelling == "[" || token_spelling == "]" || token_spelling == "*" || token_spelling == "&") { auto start = get_buffer()->get_iter_at_line_index(token_offsets.first.line - 1, token_offsets.first.index - 1); auto end = get_buffer()->get_iter_at_line_index(token_offsets.second.line - 1, token_offsets.second.index - 1); - type_tooltips.emplace_back(this, get_buffer()->create_mark(start), get_buffer()->create_mark(end), [this, token, start, end](const Glib::RefPtr &buffer) { - buffer->insert(buffer->get_insert()->get_iter(), "Type: " + token.get_cursor().get_type_description()); - auto brief_comment = token.get_cursor().get_brief_comments(); - if(brief_comment != "") - insert_with_links_tagged(buffer, "\n\n" + brief_comment); + type_tooltips.emplace_back(this, get_buffer()->create_mark(start), get_buffer()->create_mark(end), [this, token](const Glib::RefPtr &buffer) { + auto cursor = token.get_cursor(); + if(cursor.is_valid_kind()) { + buffer->insert(buffer->get_insert()->get_iter(), "Type: " + cursor.get_type_description()); + auto brief_comment = cursor.get_brief_comments(); + if(brief_comment != "") + insert_with_links_tagged(buffer, "\n\n" + brief_comment); + } #ifdef JUCI_ENABLE_DEBUG if(Debug::LLDB::get().is_stopped()) { - auto referenced = token.get_cursor().get_referenced(); - auto location = referenced.get_source_location(); Glib::ustring value_type = "Value"; - - auto iter = start; - auto corrected_start = start; - while((*iter >= 'a' && *iter <= 'z') || (*iter >= 'A' && *iter <= 'Z') || (*iter >= '0' && *iter <= '9') || *iter == '_' || *iter == '.') { - corrected_start = iter; - if(!iter.backward_char()) - break; - if(*iter == '>') { - if(!(iter.backward_char() && *iter == '-' && iter.backward_char())) - break; - } - else if(*iter == ':') { - if(!(iter.backward_char() && *iter == ':' && iter.backward_char())) - break; - } + Glib::ustring debug_value; + auto referenced = cursor.get_referenced(); + auto kind = clangmm::Cursor::Kind::UnexposedDecl; + if(referenced) { + kind = referenced.get_kind(); + auto location = referenced.get_source_location(); + debug_value = Debug::LLDB::get().get_value(token.get_spelling(), location.get_path(), location.get_offset().line, location.get_offset().index); } - auto spelling = get_buffer()->get_text(corrected_start, end).raw(); + if(debug_value.empty()) { + // Attempt to get value from expression (for instance: (*a).b.c, or: (*d)[1 + 1]) + auto is_safe = [](const clangmm::Cursor &cursor) { + auto referenced = cursor.get_referenced(); + if(referenced) { + auto kind = referenced.get_kind(); + // operator[] is passed even without being const for convenience purposes + if(!clang_CXXMethod_isConst(referenced.cx_cursor) && referenced.get_spelling() != "operator[]" && + (kind == clangmm::Cursor::Kind::CXXMethod || kind == clangmm::Cursor::Kind::FunctionDecl || + kind == clangmm::Cursor::Kind::Constructor || kind == clangmm::Cursor::Kind::Destructor || + kind == clangmm::Cursor::Kind::FunctionTemplate || kind == clangmm::Cursor::Kind::ConversionFunction)) { + return false; + } + } + return true; + }; + + // Do not call state altering expressions: + if(is_safe(cursor)) { + auto offsets = cursor.get_source_range().get_offsets(); + auto start = get_buffer()->get_iter_at_line_index(offsets.first.line - 1, offsets.first.index - 1); + auto end = get_buffer()->get_iter_at_line_index(offsets.second.line - 1, offsets.second.index - 1); + + std::string expression; + // Get full expression from cursor parent: + if(*start == '[' || (kind == clangmm::Cursor::Kind::CXXMethod && (*start == '<' || *start == '>' || *start == '=' || *start == '!' || + *start == '+' || *start == '-' || *start == '*' || *start == '/' || + *start == '%' || *start == '&' || *start == '|' || *start == '^' || + *end == '('))) { + struct VisitorData { + std::pair cursor_offsets; + clangmm::Cursor parent; + }; + VisitorData visitor_data{cursor.get_source_range().get_offsets(), {}}; + auto start_cursor = cursor; + for(auto parent = cursor.get_semantic_parent(); parent.get_kind() != clangmm::Cursor::Kind::TranslationUnit; parent = parent.get_semantic_parent()) + start_cursor = parent; + clang_visitChildren(start_cursor.cx_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data_) { + auto data = static_cast(data_); + auto offsets = clangmm::Cursor(cx_cursor).get_source_range().get_offsets(); + if(offsets == data->cursor_offsets) { + data->parent = clangmm::Cursor(cx_parent); + return CXChildVisit_Break; + } + return CXChildVisit_Recurse; + }, &visitor_data); + if(visitor_data.parent) + cursor = visitor_data.parent; + } - Glib::ustring debug_value; - auto cursor_kind = referenced.get_kind(); - if(cursor_kind != clangmm::Cursor::Kind::FunctionDecl && cursor_kind != clangmm::Cursor::Kind::CXXMethod && - cursor_kind != clangmm::Cursor::Kind::Constructor && cursor_kind != clangmm::Cursor::Kind::Destructor && - cursor_kind != clangmm::Cursor::Kind::FunctionTemplate && cursor_kind != clangmm::Cursor::Kind::ConversionFunction) { - debug_value = Debug::LLDB::get().get_value(spelling, location.get_path(), location.get_offset().line, location.get_offset().index); + // Check children + std::vector children; + clang_visitChildren(cursor.cx_cursor, [](CXCursor cx_cursor, CXCursor /*parent*/, CXClientData data) { + static_cast *>(data)->emplace_back(cx_cursor); + return CXChildVisit_Continue; + }, &children); + + // Check if expression can be called without altering state + bool call_expression = true; + for(auto &child : children) { + if(!is_safe(child)) { + call_expression = false; + break; + } + } + + if(call_expression) { + offsets = cursor.get_source_range().get_offsets(); + start = get_iter_at_line_index(offsets.first.line - 1, offsets.first.index - 1); + end = get_iter_at_line_index(offsets.second.line - 1, offsets.second.index - 1); + + expression = get_buffer()->get_text(start, end).raw(); + + if(!expression.empty()) { + // Check for C-like assignment/increment/decrement (non-const) operators + char last_last_chr = 0; + char last_chr = expression[0]; + for(size_t i = 1; i < expression.size(); ++i) { + auto &chr = expression[i]; + if((last_chr == '+' && (chr == '+' || chr == '=')) || + (last_chr == '-' && (chr == '-' || chr == '=')) || + (last_chr == '*' && chr == '=') || + (last_chr == '/' && chr == '=') || + (last_chr == '%' && chr == '=') || + (last_chr == '&' && chr == '=') || + (last_chr == '|' && chr == '=') || + (last_chr == '^' && chr == '=') || + // <<= >>= + (chr == '=' && ((last_last_chr == '<' && last_chr == '<') || (last_last_chr == '>' && last_chr == '>'))) || + // Checks for = (not ==. .== !=. <=. >=. <=>) + (last_chr == '=' && last_last_chr != '=' && chr != '=' && last_last_chr != '!' && last_last_chr != '<' && last_last_chr != '>' && + !(last_last_chr == '<' && chr == '>'))) { + call_expression = false; + break; + } + last_last_chr = last_chr; + last_chr = chr; + } + + if(call_expression) + debug_value = Debug::LLDB::get().get_value(expression); + } + } + } } if(debug_value.empty()) { value_type = "Return value"; @@ -433,7 +523,7 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle) next_char_iter++; debug_value.replace(iter, next_char_iter, "?"); } - buffer->insert(buffer->get_insert()->get_iter(), "\n\n" + value_type + ": " + debug_value.substr(pos + 3, debug_value.size() - (pos + 3) - 1)); + buffer->insert(buffer->get_insert()->get_iter(), (buffer->size() > 0 ? "\n\n" : "") + value_type + ": " + debug_value.substr(pos + 3, debug_value.size() - (pos + 3) - 1)); } } } diff --git a/src/source_language_protocol.cc b/src/source_language_protocol.cc index f84ccdb..0798df1 100644 --- a/src/source_language_protocol.cc +++ b/src/source_language_protocol.cc @@ -1065,9 +1065,10 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect } while(((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || (*end >= '0' && *end <= '9') || *end == '_') && end.forward_char()) { } + auto variable = get_buffer()->get_text(start, end); auto offset = get_declaration(start); - Glib::ustring debug_value = Debug::LLDB::get().get_value(get_buffer()->get_text(start, end), offset.file_path, offset.line + 1, offset.index + 1); + Glib::ustring debug_value = Debug::LLDB::get().get_value(variable, offset.file_path, offset.line + 1, offset.index + 1); if(debug_value.empty()) { value_type = "Return value"; debug_value = Debug::LLDB::get().get_return_value(file_path, start.get_line() + 1, start.get_line_index() + 1); diff --git a/src/tooltips.cc b/src/tooltips.cc index 9e7ae54..9754215 100644 --- a/src/tooltips.cc +++ b/src/tooltips.cc @@ -180,6 +180,9 @@ void Tooltip::show(bool disregard_drawn, const std::function &on_motion) }); } + if(text_buffer->size() == 0) + return; // Do not show empty tooltips + int root_x = 0, root_y = 0; if(text_view) { //Adjust if tooltip is left of text_view