Author: Sergei Druzhkov Date: 2026-01-10T16:45:49+03:00 New Revision: c9f13b5404476cfe426886849e3b5e13243af06b
URL: https://github.com/llvm/llvm-project/commit/c9f13b5404476cfe426886849e3b5e13243af06b DIFF: https://github.com/llvm/llvm-project/commit/c9f13b5404476cfe426886849e3b5e13243af06b.diff LOG: [lldb-dap] Add clipboard context support (#170644) This patch introduces support for `clipboard` context from [DAP](https://microsoft.github.io/debug-adapter-protocol/specification#Types_Capabilities). This feature is very useful when you want to copy all nested values from a structure or a container instead of a summary (e.g. `size = 3` for vector). I added new short mode for description generation to reduce output verbosity, which is particularly useful for primitive types. Added: Modified: lldb/include/lldb/API/SBValue.h lldb/source/API/SBValue.cpp lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py lldb/test/API/tools/lldb-dap/evaluate/main.cpp lldb/tools/lldb-dap/Handler/RequestHandler.h lldb/tools/lldb-dap/JSONUtils.cpp Removed: ################################################################################ diff --git a/lldb/include/lldb/API/SBValue.h b/lldb/include/lldb/API/SBValue.h index 0f788ff602b70..dead11fba19fe 100644 --- a/lldb/include/lldb/API/SBValue.h +++ b/lldb/include/lldb/API/SBValue.h @@ -320,6 +320,9 @@ class LLDB_API SBValue { bool GetDescription(lldb::SBStream &description); + bool GetDescription(lldb::SBStream &description, + lldb::DescriptionLevel description_level); + bool GetExpressionPath(lldb::SBStream &description); bool GetExpressionPath(lldb::SBStream &description, diff --git a/lldb/source/API/SBValue.cpp b/lldb/source/API/SBValue.cpp index e300ecee3f8ac..5b67270859da2 100644 --- a/lldb/source/API/SBValue.cpp +++ b/lldb/source/API/SBValue.cpp @@ -45,6 +45,7 @@ #include "lldb/API/SBProcess.h" #include "lldb/API/SBTarget.h" #include "lldb/API/SBThread.h" +#include "lldb/lldb-enumerations.h" #include <memory> @@ -1262,14 +1263,45 @@ lldb::SBValue SBValue::EvaluateExpression(const char *expr, bool SBValue::GetDescription(SBStream &description) { LLDB_INSTRUMENT_VA(this, description); + return GetDescription(description, eDescriptionLevelFull); +} + +static DumpValueObjectOptions +GetDumpOptions(lldb::DescriptionLevel description_level, + lldb::DynamicValueType dyn, bool use_synthetic) { + DumpValueObjectOptions options; + switch (description_level) { + case eDescriptionLevelInitial: + return options; + case eDescriptionLevelBrief: + options.SetAllowOnelinerMode(true); + options.SetHideRootName(true); + options.SetHideRootType(true); + break; + case eDescriptionLevelVerbose: + options.SetShowTypes(true); + options.SetShowLocation(true); + break; + default: + break; + } + options.SetUseDynamicType(dyn); + options.SetUseSyntheticValue(use_synthetic); + return options; +} + +bool SBValue::GetDescription(SBStream &description, + lldb::DescriptionLevel description_level) { + LLDB_INSTRUMENT_VA(this, description, description_level); + Stream &strm = description.ref(); ValueLocker locker; lldb::ValueObjectSP value_sp(GetSP(locker)); if (value_sp) { - DumpValueObjectOptions options; - options.SetUseDynamicType(m_opaque_sp->GetUseDynamic()); - options.SetUseSyntheticValue(m_opaque_sp->GetUseSynthetic()); + const DumpValueObjectOptions options = + GetDumpOptions(description_level, m_opaque_sp->GetUseDynamic(), + m_opaque_sp->GetUseSynthetic()); if (llvm::Error error = value_sp->Dump(strm, options)) { strm << "error: " << toString(std::move(error)); return false; diff --git a/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py b/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py index 95ad0f06d9a06..bc08462cfcba9 100644 --- a/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py +++ b/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py @@ -81,6 +81,9 @@ def assertEvaluateFailure(self, expression): def isResultExpandedDescription(self): return self.context == "repl" + def isResultShortDescription(self): + return self.context == "clipboard" + def isExpressionParsedExpected(self): return self.context != "hover" @@ -165,6 +168,25 @@ def run_test_evaluate_expressions( want_type="my_struct *", want_varref=True, ) + elif self.isResultShortDescription(): + self.assertEvaluate( + "struct1", + "(foo = 15)", + want_type="my_struct", + want_varref=True, + ) + self.assertEvaluate( + "struct2", + r"0x.*", + want_type="my_struct *", + want_varref=True, + ) + self.assertEvaluate( + "struct3", + "nullptr", + want_type="my_struct *", + want_varref=True, + ) else: self.assertEvaluate( "struct1", @@ -236,6 +258,13 @@ def run_test_evaluate_expressions( want_type="my_struct", want_varref=True, ) + elif self.isResultShortDescription(): + self.assertEvaluate( + "struct1", + "(foo = 15)", + want_type="my_struct", + want_varref=True, + ) else: self.assertEvaluate( "struct1", @@ -304,17 +333,78 @@ def run_test_evaluate_expressions( # Now we check that values are updated after stepping self.continue_to_breakpoint(breakpoint_4) - self.assertEvaluate("my_vec", "size=2", want_varref=True) + if self.isResultExpandedDescription(): + self.assertEvaluate( + "my_vec", + r"\(std::vector<int>\) \$\d+ = size=2 {\n \[0\] = 1\n \[1\] = 2\n}", + want_varref=True, + ) + elif self.isResultShortDescription(): + self.assertEvaluate( + "my_vec", r"size=2 {\n \[0\] = 1\n \[1\] = 2\n}", want_varref=True + ) + else: + self.assertEvaluate("my_vec", "size=2", want_varref=True) self.continue_to_breakpoint(breakpoint_5) - self.assertEvaluate("my_vec", "size=3", want_varref=True) + if self.isResultExpandedDescription(): + self.assertEvaluate( + "my_vec", + r"\(std::vector<int>\) \$\d+ = size=3 {\n \[0\] = 1\n \[1\] = 2\n \[2\] = 3\n}", + want_varref=True, + ) + elif self.isResultShortDescription(): + self.assertEvaluate( + "my_vec", + r"size=3 {\n \[0\] = 1\n \[1\] = 2\n \[2\] = 3\n}", + want_varref=True, + ) + else: + self.assertEvaluate("my_vec", "size=3", want_varref=True) - self.assertEvaluate("my_map", "size=2", want_varref=True) + if self.isResultExpandedDescription(): + self.assertEvaluate( + "my_map", + r"\(std::map<int, int>\) \$\d+ = size=2 {\n \[0\] = \(first = 1, second = 2\)\n \[1\] = \(first = 2, second = 3\)\n}", + want_varref=True, + ) + elif self.isResultShortDescription(): + self.assertEvaluate( + "my_map", + r"size=2 {\n \[0\] = \(first = 1, second = 2\)\n \[1\] = \(first = 2, second = 3\)\n}", + want_varref=True, + ) + else: + self.assertEvaluate("my_map", "size=2", want_varref=True) self.continue_to_breakpoint(breakpoint_6) self.assertEvaluate("my_map", "size=3", want_varref=True) - self.assertEvaluate("my_bool_vec", "size=1", want_varref=True) + if self.isResultExpandedDescription(): + self.assertEvaluate( + "my_bool_vec", + r"\(std::vector<bool>\) \$\d+ = size=1 {\n \[0\] = true\n}", + want_varref=True, + ) + elif self.isResultShortDescription(): + self.assertEvaluate( + "my_bool_vec", r"size=1 {\n \[0\] = true\n}", want_varref=True + ) + else: + self.assertEvaluate("my_bool_vec", "size=1", want_varref=True) self.continue_to_breakpoint(breakpoint_7) - self.assertEvaluate("my_bool_vec", "size=2", want_varref=True) + if self.isResultExpandedDescription(): + self.assertEvaluate( + "my_bool_vec", + r"\(std::vector<bool>\) \$\d+ = size=2 {\n \[0\] = true\n \[1\] = false\n}", + want_varref=True, + ) + elif self.isResultShortDescription(): + self.assertEvaluate( + "my_bool_vec", + r"size=2 {\n \[0\] = true\n \[1\] = false\n}", + want_varref=True, + ) + else: + self.assertEvaluate("my_bool_vec", "size=2", want_varref=True) self.continue_to_breakpoint(breakpoint_8) # Test memory read, especially with 'empty' repeat commands. @@ -327,6 +417,25 @@ def run_test_evaluate_expressions( self.assertEvaluate("", ".* 14 .*\n", want_memref=False) self.assertEvaluate("", ".* 19 .*\n", want_memref=False) + if self.isResultExpandedDescription(): + self.assertEvaluate( + "my_longs", + r"\(long\[3\]\) \$\d+ = \(\[0\] = 5, \[1\] = 6, \[2\] = 7\)", + want_varref=True, + ) + elif self.isResultShortDescription(): + self.assertEvaluate( + "my_longs", + r"\(\[0\] = 5, \[1\] = 6, \[2\] = 7\)", + want_varref=True, + ) + else: + self.assertEvaluate( + "my_longs", + "{5, 6, 7}" if enableAutoVariableSummaries else r"long\[3\]", + want_varref=True, + ) + self.continue_to_exit() @skipIfWindows @@ -355,3 +464,10 @@ def test_variable_evaluate_expressions(self): self.run_test_evaluate_expressions( "variables", enableAutoVariableSummaries=True ) + + @skipIfWindows + def test_clipboard_evaluate_expressions(self): + # Tests expression evaluations that are triggered when value copied in editor + self.run_test_evaluate_expressions( + "clipboard", enableAutoVariableSummaries=False + ) diff --git a/lldb/test/API/tools/lldb-dap/evaluate/main.cpp b/lldb/test/API/tools/lldb-dap/evaluate/main.cpp index 1c3d258114b1f..112726677637c 100644 --- a/lldb/test/API/tools/lldb-dap/evaluate/main.cpp +++ b/lldb/test/API/tools/lldb-dap/evaluate/main.cpp @@ -47,5 +47,6 @@ int main(int argc, char const *argv[]) { my_bool_vec.push_back(true); // breakpoint 7 uint8_t my_ints[] = {5, 10, 15, 20, 25, 30}; + long my_longs[] = {5, 6, 7}; return 0; // breakpoint 8 } diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h index 9eb956c546b50..f435257d4dcce 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.h +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h @@ -297,7 +297,8 @@ class EvaluateRequestHandler llvm::Expected<protocol::EvaluateResponseBody> Run(const protocol::EvaluateArguments &) const override; FeatureSet GetSupportedFeatures() const override { - return {protocol::eAdapterFeatureEvaluateForHovers}; + return {protocol::eAdapterFeatureEvaluateForHovers, + protocol::eAdapterFeatureClipboardContext}; } }; diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 9a2142cd847ab..8851af65d1c4f 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -662,9 +662,10 @@ VariableDescription::VariableDescription( } std::string VariableDescription::GetResult(protocol::EvaluateContext context) { - // In repl context, the results can be displayed as multiple lines so more - // detailed descriptions can be returned. - if (context != protocol::eEvaluateContextRepl) + // In repl and clipboard contexts, the results can be displayed as multiple + // lines so more detailed descriptions can be returned. + if (context != protocol::eEvaluateContextRepl && + context != protocol::eEvaluateContextClipboard) return display_value; if (!val.IsValid()) @@ -673,7 +674,10 @@ std::string VariableDescription::GetResult(protocol::EvaluateContext context) { // Try the SBValue::GetDescription(), which may call into language runtime // specific formatters (see ValueObjectPrinter). lldb::SBStream stream; - val.GetDescription(stream); + if (context == protocol::eEvaluateContextRepl) + val.GetDescription(stream, lldb::eDescriptionLevelFull); + else + val.GetDescription(stream, lldb::eDescriptionLevelBrief); llvm::StringRef description = stream.GetData(); return description.trim().str(); } _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
