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

Reply via email to