https://github.com/da-viper updated 
https://github.com/llvm/llvm-project/pull/177151

>From 7479028caadbe5905883b13dc1bcefdf74aed22b Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <[email protected]>
Date: Tue, 20 Jan 2026 16:05:40 +0000
Subject: [PATCH 1/5] [lldb-dap] Fix the completion provided to the DAP client.

Previously, completion behavior was inconsistent,
sometimes including the partial token or removing existing user text.
Since LLDB completions includes the partial token by default,
we now strip it before sending to the client.

The completion heuristic:
1. Strip the commandEscapePrefix
2. Request completions from the debugger
3. Get the line at cursor position
4. Calculate the length of any partial token
5. Offset each completion by the partial token length

In all cases it completion starts from the cursor position.
then offsets by `Length` and inserts the completion.

Examples (single quotes show whitespace and are not part of the input):
```md
| Input      | Partial Token | Length | Completion    |
|------------|---------------|--------|---------------|
| m          | m             | 1      | memory        |
| `m         | m             | 1      | memory        |
| myfoo.     | myfoo.        | 6      | myfoo.method( |
| myfoo.fi   | myfoo.fi      | 7      | myfoo.field   |
| myfoo. b   | b             | 1      | myfoo. bar    |
| 'memory '  |               | 0      | memory read   |
| memory     | memory        | 6      | memory        |
| settings s | s             | 1      | settings show |
```
---
 .../completions/TestDAP_completions.py        | 339 +++++++++++-------
 .../lldb-dap/Handler/CompletionsHandler.cpp   |  21 +-
 lldb/tools/lldb-dap/Protocol/ProtocolTypes.h  |   8 +-
 3 files changed, 235 insertions(+), 133 deletions(-)

diff --git a/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py 
b/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py
index 1792ff9953efe..937904d682bb0 100644
--- a/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py
+++ b/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py
@@ -2,47 +2,91 @@
 Test lldb-dap completions request
 """
 
+# FIXME: remove when LLDB_MINIMUM_PYTHON_VERSION > 3.8
+from __future__ import annotations
+
+import json
+from typing import Optional
 import lldbdap_testcase
-import dap_server
-from lldbsuite.test import lldbutil
+from dataclasses import dataclass, replace, asdict
 from lldbsuite.test.decorators import skipIf
 from lldbsuite.test.lldbtest import line_number
 
-session_completion = {
-    "text": "session",
-    "label": "session",
-    "detail": "Commands controlling LLDB session.",
-}
-settings_completion = {
-    "text": "settings",
-    "label": "settings",
-    "detail": "Commands for managing LLDB settings.",
-}
-memory_completion = {
-    "text": "memory",
-    "label": "memory",
-    "detail": "Commands for operating on memory in the current target 
process.",
-}
-command_var_completion = {
-    "text": "var",
-    "label": "var",
-    "detail": "Show variables for the current stack frame. Defaults to all 
arguments and local variables in scope. Names of argument, local, file static 
and file global variables can be specified.",
-}
-variable_var_completion = {"text": "var", "label": "var", "detail": 
"vector<baz> &"}
-variable_var1_completion = {"text": "var1", "label": "var1", "detail": "int &"}
-variable_var2_completion = {"text": "var2", "label": "var2", "detail": "int &"}
+
+@dataclass(frozen=True)
+class CompletionItem:
+    label: str
+    text: Optional[str] = None
+    detail: Optional[str] = None
+    start: Optional[int] = None
+    length: int = 0
+
+    def __repr__(self):
+        # use json as it easier to see the diff on failure.
+        return json.dumps(asdict(self), indent=4)
+
+    def clone(self, **kwargs) -> CompletionItem:
+        """Creates a copy of this CompletionItem with specified fields 
modified."""
+        return replace(self, **kwargs)
+
+
+@dataclass
+class TestCase:
+    input: str
+    expected: set[CompletionItem]
+    not_expected: Optional[set[CompletionItem]] = None
+
+
+session_completion = CompletionItem(
+    label="session",
+    detail="Commands controlling LLDB session.",
+)
+settings_completion = CompletionItem(
+    label="settings",
+    detail="Commands for managing LLDB settings.",
+)
+memory_completion = CompletionItem(
+    label="memory",
+    detail="Commands for operating on memory in the current target process.",
+)
+command_var_completion = CompletionItem(
+    label="var",
+    detail="Show variables for the current stack frame. Defaults to all 
arguments and local variables in scope. Names of argument, local, file static 
and file global variables can be specified.",
+    length=3,
+)
+variable_var_completion = CompletionItem(label="var", detail="vector<baz> &", 
length=3)
+variable_var1_completion = CompletionItem(label="var1", detail="int &")
+variable_var2_completion = CompletionItem(label="var2", detail="int &")
+
+str1_completion = CompletionItem(
+    label="str1",
+    detail="std::string &",
+)
 
 
 # Older version of libcxx produce slightly different typename strings for
 # templates like vector.
 @skipIf(compiler="clang", compiler_version=["<", "16.0"])
 class TestDAP_completions(lldbdap_testcase.DAPTestCaseBase):
-    def verify_completions(self, actual_list, expected_list, 
not_expected_list=[]):
-        for expected_item in expected_list:
-            self.assertIn(expected_item, actual_list)
-
-        for not_expected_item in not_expected_list:
-            self.assertNotIn(not_expected_item, actual_list)
+    def verify_completions(self, case: TestCase):
+        completions = {
+            CompletionItem(**comp)
+            for comp in self.dap_server.get_completions(case.input)
+        }
+
+        # handle expected completions
+        expected_completions = case.expected
+        for exp_comp in expected_completions:
+            # with self.subTest(f"Expected completion : {exp_comp}"):
+            self.assertIn(
+                exp_comp, completions, f"\nCompletion for input: {case.input}"
+            )
+
+        # unexpected completions
+        not_expected_label = case.not_expected or set()
+        for not_exp_comp in not_expected_label:
+            with self.subTest(f"Not expected completion : {not_exp_comp}"):
+                self.assertNotIn(not_exp_comp, completions)
 
     def setup_debuggee(self):
         program = self.getBuildArtifact("a.out")
@@ -70,72 +114,96 @@ def test_command_completions(self):
 
         # Provides completion for top-level commands
         self.verify_completions(
-            self.dap_server.get_completions("se"),
-            [session_completion, settings_completion],
+            TestCase(
+                input="se",
+                expected={
+                    session_completion.clone(length=2),
+                    settings_completion.clone(length=2),
+                },
+            )
         )
-
         # Provides completions for sub-commands
         self.verify_completions(
-            self.dap_server.get_completions("memory "),
-            [
-                {
-                    "text": "read",
-                    "label": "read",
-                    "detail": "Read from the memory of the current target 
process.",
-                },
-                {
-                    "text": "region",
-                    "label": "region",
-                    "detail": "Get information on the memory region containing 
an address in the current target process.",
+            TestCase(
+                input="memory ",
+                expected={
+                    CompletionItem(
+                        label="read",
+                        detail="Read from the memory of the current target 
process.",
+                    ),
+                    CompletionItem(
+                        label="region",
+                        detail="Get information on the memory region 
containing an address in the current target process.",
+                    ),
                 },
-            ],
+            )
         )
 
         # Provides completions for parameter values of commands
         self.verify_completions(
-            self.dap_server.get_completions("`log enable  "),
-            [{"text": "gdb-remote", "label": "gdb-remote"}],
+            TestCase(
+                input="`log enable  ", 
expected={CompletionItem(label="gdb-remote")}
+            )
         )
 
         # Also works if the escape prefix is used
         self.verify_completions(
-            self.dap_server.get_completions("`mem"), [memory_completion]
+            TestCase(input="`mem", 
expected={memory_completion.clone(length=3)})
         )
 
         self.verify_completions(
-            self.dap_server.get_completions("`"),
-            [session_completion, settings_completion, memory_completion],
+            TestCase(
+                input="`",
+                expected={
+                    session_completion.clone(),
+                    settings_completion.clone(),
+                    memory_completion.clone(),
+                },
+            )
         )
 
         # Completes an incomplete quoted token
         self.verify_completions(
-            self.dap_server.get_completions('setting "se'),
-            [
-                {
-                    "text": "set",
-                    "label": "set",
-                    "detail": "Set the value of the specified debugger 
setting.",
-                }
-            ],
+            TestCase(
+                input='setting "se',
+                expected={
+                    CompletionItem(
+                        label="set",
+                        detail="Set the value of the specified debugger 
setting.",
+                        length=3,
+                    )
+                },
+            )
         )
 
         # Completes an incomplete quoted token
         self.verify_completions(
-            self.dap_server.get_completions("'mem"),
-            [memory_completion],
+            TestCase(input="'mem", 
expected={memory_completion.clone(length=4)})
         )
 
         # Completes expressions with quotes inside
         self.verify_completions(
-            self.dap_server.get_completions('expr " "; typed'),
-            [{"text": "typedef", "label": "typedef"}],
+            TestCase(
+                input='expr " "; typed',
+                expected={CompletionItem(label="typedef", length=5)},
+            )
         )
 
         # Provides completions for commands, but not variables
         self.verify_completions(
-            self.dap_server.get_completions("var"),
-            [command_var_completion],
-            [variable_var_completion],
+            TestCase(
+                input="var",
+                expected={command_var_completion.clone()},
+                not_expected={variable_var_completion.clone()},
+            )
+        )
+
+        # Completes partial completion
+        self.verify_completions(
+            TestCase(
+                input="plugin list ar",
+                expected={CompletionItem(label="architecture", length=2)},
+            )
         )
 
     def test_variable_completions(self):
@@ -152,26 +220,32 @@ def test_variable_completions(self):
 
         # Provides completions for varibles, but not command
         self.verify_completions(
-            self.dap_server.get_completions("var"),
-            [variable_var_completion],
-            [command_var_completion],
+            TestCase(
+                input="var",
+                expected={variable_var_completion.clone()},
+                not_expected={command_var_completion.clone()},
+            )
         )
 
         # We stopped inside `fun`, so we shouldn't see variables from main
         self.verify_completions(
-            self.dap_server.get_completions("var"),
-            [variable_var_completion],
-            [
-                variable_var1_completion,
-                variable_var2_completion,
-            ],
+            TestCase(
+                input="var",
+                expected={variable_var_completion},
+                not_expected={
+                    variable_var1_completion.clone(length=3),
+                    variable_var2_completion.clone(length=3),
+                },
+            )
         )
 
         # We should see global keywords but not variables inside main
         self.verify_completions(
-            self.dap_server.get_completions("str"),
-            [{"text": "struct", "label": "struct"}],
-            [{"text": "str1", "label": "str1", "detail": "std::string &"}],
+            TestCase(
+                input="str",
+                expected={CompletionItem(label="struct", length=3)},
+                not_expected={str1_completion.clone(length=3)},
+            )
         )
 
         self.continue_to_next_stop()
@@ -179,65 +253,86 @@ def test_variable_completions(self):
         # We stopped in `main`, so we should see variables from main but
         # not from the other function
         self.verify_completions(
-            self.dap_server.get_completions("var"),
-            [
-                variable_var1_completion,
-                variable_var2_completion,
-            ],
-            [
-                variable_var_completion,
-            ],
+            TestCase(
+                input="var",
+                expected={
+                    variable_var1_completion.clone(length=3),
+                    variable_var2_completion.clone(length=3),
+                },
+                not_expected={
+                    variable_var_completion.clone(length=3),
+                },
+            )
         )
 
         self.verify_completions(
-            self.dap_server.get_completions("str"),
-            [
-                {"text": "struct", "label": "struct"},
-                {"text": "str1", "label": "str1", "detail": "std::string &"},
-            ],
+            TestCase(
+                input="str",
+                expected={
+                    CompletionItem(label="struct", length=3),
+                    str1_completion.clone(length=3),
+                },
+            )
         )
 
         self.assertIsNotNone(self.dap_server.get_completions("ƒ"))
         # Test utf8 after ascii.
+        # TODO
         self.dap_server.get_completions("mƒ")
 
         # Completion also works for more complex expressions
         self.verify_completions(
-            self.dap_server.get_completions("foo1.v"),
-            [{"text": "var1", "label": "foo1.var1", "detail": "int"}],
+            TestCase(
+                input="foo1.v",
+                expected={CompletionItem(label="foo1.var1", detail="int", 
length=6)},
+            )
         )
 
         self.verify_completions(
-            self.dap_server.get_completions("foo1.my_bar_object.v"),
-            [{"text": "var1", "label": "foo1.my_bar_object.var1", "detail": 
"int"}],
+            TestCase(
+                input="foo1.my_bar_object.v",
+                expected={
+                    CompletionItem(
+                        label="foo1.my_bar_object.var1", detail="int", 
length=20
+                    )
+                },
+            )
         )
 
         self.verify_completions(
-            self.dap_server.get_completions("foo1.var1 + foo1.v"),
-            [{"text": "var1", "label": "foo1.var1", "detail": "int"}],
+            TestCase(
+                input="foo1.var1 + foo1.v",
+                expected={CompletionItem(label="foo1.var1", detail="int", 
length=6)},
+            )
         )
 
         self.verify_completions(
-            self.dap_server.get_completions("foo1.var1 + v"),
-            [{"text": "var1", "label": "var1", "detail": "int &"}],
+            TestCase(
+                input="foo1.var1 + v",
+                expected={CompletionItem(label="var1", detail="int &", 
length=1)},
+            )
         )
 
         # should correctly handle spaces between objects and member operators
         self.verify_completions(
-            self.dap_server.get_completions("foo1 .v"),
-            [{"text": "var1", "label": ".var1", "detail": "int"}],
-            [{"text": "var2", "label": ".var2", "detail": "int"}],
+            TestCase(
+                input="foo1 .v",
+                expected={CompletionItem(label=".var1", detail="int", 
length=2)},
+                not_expected={CompletionItem(label=".var2", detail="int", 
length=2)},
+            )
         )
 
         self.verify_completions(
-            self.dap_server.get_completions("foo1 . v"),
-            [{"text": "var1", "label": "var1", "detail": "int"}],
-            [{"text": "var2", "label": "var2", "detail": "int"}],
+            TestCase(
+                input="foo1 . v",
+                expected={CompletionItem(label="var1", detail="int", 
length=1)},
+                not_expected={CompletionItem(label="var2", detail="int", 
length=1)},
+            )
         )
 
         # Even in variable mode, we can still use the escape prefix
         self.verify_completions(
-            self.dap_server.get_completions("`mem"), [memory_completion]
+            TestCase(input="`mem", 
expected={memory_completion.clone(length=3)})
         )
 
     def test_auto_completions(self):
@@ -261,24 +356,28 @@ def test_auto_completions(self):
         # We are stopped inside `main`. Variables `var1` and `var2` are in 
scope.
         # Make sure, we offer all completions
         self.verify_completions(
-            self.dap_server.get_completions("va"),
-            [
-                command_var_completion,
-                variable_var1_completion,
-                variable_var2_completion,
-            ],
+            TestCase(
+                input="va",
+                expected={
+                    command_var_completion.clone(length=2),
+                    variable_var1_completion.clone(length=2),
+                    variable_var2_completion.clone(length=2),
+                },
+            )
         )
 
         # If we are using the escape prefix, only commands are suggested, but 
no variables
         self.verify_completions(
-            self.dap_server.get_completions("`va"),
-            [
-                command_var_completion,
-            ],
-            [
-                variable_var1_completion,
-                variable_var2_completion,
-            ],
+            TestCase(
+                input="`va",
+                expected={
+                    command_var_completion.clone(length=2),
+                },
+                not_expected={
+                    variable_var1_completion.clone(length=2),
+                    variable_var2_completion.clone(length=2),
+                },
+            )
         )
 
         # TODO: Note we are not checking the result because the `expression 
--` command adds an extra character
diff --git a/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp 
b/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp
index 9a3973190f7e7..671d894611db8 100644
--- a/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp
@@ -72,6 +72,14 @@ static std::optional<size_t> GetCursorPos(StringRef text, 
uint32_t line,
   return cursor_pos;
 }
 
+static size_t GetTokenLengthAtCursor(StringRef line, size_t cursor_pos) {
+  line = line.substr(0, cursor_pos);
+  const size_t idx = line.rfind(' ');
+  if (idx == StringRef::npos)
+    return line.size();
+  return line.size() - (idx + 1);
+}
+
 /// Returns a list of possible completions for a given caret position and text.
 ///
 /// Clients should only call this request if the corresponding capability
@@ -109,6 +117,7 @@ CompletionsRequestHandler::Run(const CompletionsArguments 
&args) const {
     cursor_pos -= escape_prefix.size();
   }
 
+  const size_t partial_token_len = GetTokenLengthAtCursor(text, cursor_pos);
   // While the user is typing then we likely have an incomplete input and 
cannot
   // reliably determine the precise intent (command vs variable), try 
completing
   // the text as both a command and variable expression, if applicable.
@@ -138,19 +147,13 @@ CompletionsRequestHandler::Run(const CompletionsArguments 
&args) const {
       const StringRef match = matches.GetStringAtIndex(i);
       const StringRef description = descriptions.GetStringAtIndex(i);
 
-      StringRef match_ref = match;
-      for (const StringRef commit_point : {".", "->"}) {
-        if (const size_t pos = match_ref.rfind(commit_point);
-            pos != StringRef::npos) {
-          match_ref = match_ref.substr(pos + commit_point.size());
-        }
-      }
-
       CompletionItem item;
-      item.text = match_ref;
       item.label = match;
       if (!description.empty())
         item.detail = description;
+      // lldb returned completions includes the partial typed token
+      // overwrite it.
+      item.length = partial_token_len;
 
       targets.emplace_back(std::move(item));
     }
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h 
b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
index 3433dc74d5b31..71046d24c9787 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
@@ -168,24 +168,24 @@ struct CompletionItem {
   /// whether it is 0- or 1-based. If the start position is omitted the text
   /// is added at the location specified by the `column` attribute of the
   /// `completions` request.
-  int64_t start = 0;
+  uint64_t start = 0;
 
   /// Length determines how many characters are overwritten by the completion
   /// text and it is measured in UTF-16 code units. If missing the value 0 is
   /// assumed which results in the completion text being inserted.
-  int64_t length = 0;
+  uint64_t length = 0;
 
   /// Determines the start of the new selection after the text has been
   /// inserted (or replaced). `selectionStart` is measured in UTF-16 code
   /// units and must be in the range 0 and length of the completion text. If
   /// omitted the selection starts at the end of the completion text.
-  int64_t selectionStart = 0;
+  uint64_t selectionStart = 0;
 
   /// Determines the length of the new selection after the text has been
   /// inserted (or replaced) and it is measured in UTF-16 code units. The
   /// selection can not extend beyond the bounds of the completion text. If
   /// omitted the length is assumed to be 0.
-  int64_t selectionLength = 0;
+  uint64_t selectionLength = 0;
 };
 bool fromJSON(const llvm::json::Value &, CompletionItem &, llvm::json::Path);
 llvm::json::Value toJSON(const CompletionItem &);

>From 3a9ab9400af8363727412e9b35eb0118f858119d Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <[email protected]>
Date: Fri, 23 Jan 2026 15:39:42 +0000
Subject: [PATCH 2/5] [lldb-dap] add review changes

---
 .../completions/TestDAP_completions.py        | 39 +++++++++++++++++++
 .../lldb-dap/Handler/CompletionsHandler.cpp   | 18 ++++++---
 2 files changed, 51 insertions(+), 6 deletions(-)

diff --git a/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py 
b/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py
index 937904d682bb0..8f6da7ca836ba 100644
--- a/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py
+++ b/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py
@@ -100,6 +100,38 @@ def setup_debuggee(self):
             ],
         )
 
+    def check_non_ascii_completion(self, alias_cmd: str):
+        """Creates an command alias for the `next` command and
+        verify if it has completion for the command and its help.
+
+        It assumes we are in command mode in the repl.
+        """
+        res = self.dap_server.request_evaluate(
+            f"command alias {alias_cmd} next", context="repl"
+        )
+        self.assertTrue(res["success"])
+
+        part = alias_cmd[:2]  # first two characters
+        part_codeunits = len(part.encode("utf-16-le")) // 2
+
+        next_detail = "Source level single step, stepping over calls.  
Defaults to current thread unless specified."
+        expected_item = CompletionItem(
+            label=alias_cmd, detail=next_detail, length=part_codeunits
+        )
+
+        # complete the command
+        self.verify_completions(TestCase(input=part, 
expected={expected_item.clone()}))
+        # complete the help
+        self.verify_completions(
+            TestCase(input=f"help {part}", expected={expected_item.clone()})
+        )
+
+        # remove the alias
+        res = self.dap_server.request_evaluate(
+            f"command unalias {alias_cmd}", context="repl"
+        )
+        self.assertTrue(res["success"])
+
     def test_command_completions(self):
         """
         Tests completion requests for lldb commands, within "repl-mode=command"
@@ -206,6 +238,13 @@ def test_command_completions(self):
             )
         )
 
+        # Complete custom command with non ascii character.
+        self.check_non_ascii_completion("n€xt")  # 2 bytes £
+        self.check_non_ascii_completion("n£xt")  # 3 bytes €
+        self.check_non_ascii_completion("n💩xt")  # 4 bytes 💩
+        self.check_non_ascii_completion("öxt")  # start with non ascii
+        self.check_non_ascii_completion("one_seç")  # ends with non ascii
+
     def test_variable_completions(self):
         """
         Tests completion requests in "repl-mode=variable"
diff --git a/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp 
b/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp
index 671d894611db8..4704abd4b60c8 100644
--- a/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp
@@ -12,6 +12,7 @@
 #include "Protocol/ProtocolTypes.h"
 #include "RequestHandler.h"
 #include "lldb/API/SBStringList.h"
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/Support/ConvertUTF.h"
 
 using namespace llvm;
@@ -72,12 +73,17 @@ static std::optional<size_t> GetCursorPos(StringRef text, 
uint32_t line,
   return cursor_pos;
 }
 
-static size_t GetTokenLengthAtCursor(StringRef line, size_t cursor_pos) {
+static size_t GetPartialTokenCodeUnits(StringRef line, size_t cursor_pos) {
   line = line.substr(0, cursor_pos);
   const size_t idx = line.rfind(' ');
-  if (idx == StringRef::npos)
-    return line.size();
-  return line.size() - (idx + 1);
+
+  const size_t byte_offset = (idx == StringRef::npos) ? 0 : idx + 1;
+  const StringRef byte_token = line.substr(byte_offset);
+  llvm::SmallVector<UTF16, 20> utf16_token;
+
+  if (llvm::convertUTF8ToUTF16String(byte_token, utf16_token))
+    return utf16_token.size();
+  return byte_token.size(); // fallback to back to byte offset.
 }
 
 /// Returns a list of possible completions for a given caret position and text.
@@ -117,7 +123,7 @@ CompletionsRequestHandler::Run(const CompletionsArguments 
&args) const {
     cursor_pos -= escape_prefix.size();
   }
 
-  const size_t partial_token_len = GetTokenLengthAtCursor(text, cursor_pos);
+  const size_t partial_token_cu = GetPartialTokenCodeUnits(text, cursor_pos);
   // While the user is typing then we likely have an incomplete input and 
cannot
   // reliably determine the precise intent (command vs variable), try 
completing
   // the text as both a command and variable expression, if applicable.
@@ -153,7 +159,7 @@ CompletionsRequestHandler::Run(const CompletionsArguments 
&args) const {
         item.detail = description;
       // lldb returned completions includes the partial typed token
       // overwrite it.
-      item.length = partial_token_len;
+      item.length = partial_token_cu;
 
       targets.emplace_back(std::move(item));
     }

>From be9ea448ada80e01af69bd4f9ecf022d48f8b26c Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <[email protected]>
Date: Fri, 23 Jan 2026 15:42:39 +0000
Subject: [PATCH 3/5] add review changes

---
 lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py 
b/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py
index 8f6da7ca836ba..099c2271383d3 100644
--- a/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py
+++ b/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py
@@ -30,7 +30,7 @@ def clone(self, **kwargs) -> CompletionItem:
         return replace(self, **kwargs)
 
 
-@dataclass
+@dataclass(frozen=True)
 class TestCase:
     input: str
     expected: set[CompletionItem]

>From 28e8cebcedb082cfef5b5d639782de74a20e3c46 Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <[email protected]>
Date: Fri, 23 Jan 2026 15:48:04 +0000
Subject: [PATCH 4/5] remove unnecessary clones

---
 .../completions/TestDAP_completions.py         | 18 +++++++-----------
 1 file changed, 7 insertions(+), 11 deletions(-)

diff --git a/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py 
b/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py
index 099c2271383d3..517f9d43ed8da 100644
--- a/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py
+++ b/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py
@@ -120,10 +120,10 @@ def check_non_ascii_completion(self, alias_cmd: str):
         )
 
         # complete the command
-        self.verify_completions(TestCase(input=part, 
expected={expected_item.clone()}))
+        self.verify_completions(TestCase(input=part, expected={expected_item}))
         # complete the help
         self.verify_completions(
-            TestCase(input=f"help {part}", expected={expected_item.clone()})
+            TestCase(input=f"help {part}", expected={expected_item})
         )
 
         # remove the alias
@@ -186,11 +186,7 @@ def test_command_completions(self):
         self.verify_completions(
             TestCase(
                 input="`",
-                expected={
-                    session_completion.clone(),
-                    settings_completion.clone(),
-                    memory_completion.clone(),
-                },
+                expected={session_completion, settings_completion, 
memory_completion},
             )
         )
 
@@ -225,8 +221,8 @@ def test_command_completions(self):
         self.verify_completions(
             TestCase(
                 input="var",
-                expected={command_var_completion.clone()},
-                not_expected={variable_var_completion.clone()},
+                expected={command_var_completion},
+                not_expected={variable_var_completion},
             )
         )
 
@@ -261,8 +257,8 @@ def test_variable_completions(self):
         self.verify_completions(
             TestCase(
                 input="var",
-                expected={variable_var_completion.clone()},
-                not_expected={command_var_completion.clone()},
+                expected={variable_var_completion},
+                not_expected={command_var_completion},
             )
         )
 

>From 0435cc35e8b0dd5f9fd6efe96b92258e98a02616 Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <[email protected]>
Date: Fri, 23 Jan 2026 18:37:05 +0000
Subject: [PATCH 5/5] add review changes

---
 .../completions/TestDAP_completions.py        | 75 +++++++++----------
 .../lldb-dap/Handler/CompletionsHandler.cpp   |  4 +-
 2 files changed, 38 insertions(+), 41 deletions(-)

diff --git a/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py 
b/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py
index 517f9d43ed8da..3996bdf77242d 100644
--- a/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py
+++ b/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py
@@ -31,7 +31,7 @@ def clone(self, **kwargs) -> CompletionItem:
 
 
 @dataclass(frozen=True)
-class TestCase:
+class Scenario:
     input: str
     expected: set[CompletionItem]
     not_expected: Optional[set[CompletionItem]] = None
@@ -68,23 +68,20 @@ class TestCase:
 # templates like vector.
 @skipIf(compiler="clang", compiler_version=["<", "16.0"])
 class TestDAP_completions(lldbdap_testcase.DAPTestCaseBase):
-    def verify_completions(self, case: TestCase):
+    def verify_completions(self, case: Scenario):
         completions = {
             CompletionItem(**comp)
             for comp in self.dap_server.get_completions(case.input)
         }
 
         # handle expected completions
-        expected_completions = case.expected
-        for exp_comp in expected_completions:
-            # with self.subTest(f"Expected completion : {exp_comp}"):
+        for exp_comp in case.expected:
             self.assertIn(
                 exp_comp, completions, f"\nCompletion for input: {case.input}"
             )
 
         # unexpected completions
-        not_expected_label = case.not_expected or set()
-        for not_exp_comp in not_expected_label:
+        for not_exp_comp in case.not_expected or set():
             with self.subTest(f"Not expected completion : {not_exp_comp}"):
                 self.assertNotIn(not_exp_comp, completions)
 
@@ -100,7 +97,7 @@ def setup_debuggee(self):
             ],
         )
 
-    def check_non_ascii_completion(self, alias_cmd: str):
+    def verify_non_ascii_completion(self, alias_cmd: str):
         """Creates an command alias for the `next` command and
         verify if it has completion for the command and its help.
 
@@ -120,10 +117,10 @@ def check_non_ascii_completion(self, alias_cmd: str):
         )
 
         # complete the command
-        self.verify_completions(TestCase(input=part, expected={expected_item}))
+        self.verify_completions(Scenario(input=part, expected={expected_item}))
         # complete the help
         self.verify_completions(
-            TestCase(input=f"help {part}", expected={expected_item})
+            Scenario(input=f"help {part}", expected={expected_item})
         )
 
         # remove the alias
@@ -146,7 +143,7 @@ def test_command_completions(self):
 
         # Provides completion for top-level commands
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="se",
                 expected={
                     session_completion.clone(length=2),
@@ -156,7 +153,7 @@ def test_command_completions(self):
         )
         # Provides completions for sub-commands
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="memory ",
                 expected={
                     CompletionItem(
@@ -173,18 +170,18 @@ def test_command_completions(self):
 
         # Provides completions for parameter values of commands
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="`log enable  ", 
expected={CompletionItem(label="gdb-remote")}
             )
         )
 
         # Also works if the escape prefix is used
         self.verify_completions(
-            TestCase(input="`mem", 
expected={memory_completion.clone(length=3)})
+            Scenario(input="`mem", 
expected={memory_completion.clone(length=3)})
         )
 
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="`",
                 expected={session_completion, settings_completion, 
memory_completion},
             )
@@ -192,7 +189,7 @@ def test_command_completions(self):
 
         # Completes an incomplete quoted token
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input='setting "se',
                 expected={
                     CompletionItem(
@@ -206,12 +203,12 @@ def test_command_completions(self):
 
         # Completes an incomplete quoted token
         self.verify_completions(
-            TestCase(input="'mem", 
expected={memory_completion.clone(length=4)})
+            Scenario(input="'mem", 
expected={memory_completion.clone(length=4)})
         )
 
         # Completes expressions with quotes inside
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input='expr " "; typed',
                 expected={CompletionItem(label="typedef", length=5)},
             )
@@ -219,7 +216,7 @@ def test_command_completions(self):
 
         # Provides completions for commands, but not variables
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="var",
                 expected={command_var_completion},
                 not_expected={variable_var_completion},
@@ -228,18 +225,18 @@ def test_command_completions(self):
 
         # Completes partial completion
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="plugin list ar",
                 expected={CompletionItem(label="architecture", length=2)},
             )
         )
 
         # Complete custom command with non ascii character.
-        self.check_non_ascii_completion("n€xt")  # 2 bytes £
-        self.check_non_ascii_completion("n£xt")  # 3 bytes €
-        self.check_non_ascii_completion("n💩xt")  # 4 bytes 💩
-        self.check_non_ascii_completion("öxt")  # start with non ascii
-        self.check_non_ascii_completion("one_seç")  # ends with non ascii
+        self.verify_non_ascii_completion("n€xt")  # 2 bytes £
+        self.verify_non_ascii_completion("n£xt")  # 3 bytes €
+        self.verify_non_ascii_completion("n💩xt")  # 4 bytes 💩
+        self.verify_non_ascii_completion("öxt")  # start with non ascii
+        self.verify_non_ascii_completion("one_seç")  # ends with non ascii
 
     def test_variable_completions(self):
         """
@@ -255,7 +252,7 @@ def test_variable_completions(self):
 
         # Provides completions for varibles, but not command
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="var",
                 expected={variable_var_completion},
                 not_expected={command_var_completion},
@@ -264,7 +261,7 @@ def test_variable_completions(self):
 
         # We stopped inside `fun`, so we shouldn't see variables from main
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="var",
                 expected={variable_var_completion},
                 not_expected={
@@ -276,7 +273,7 @@ def test_variable_completions(self):
 
         # We should see global keywords but not variables inside main
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="str",
                 expected={CompletionItem(label="struct", length=3)},
                 not_expected={str1_completion.clone(length=3)},
@@ -288,7 +285,7 @@ def test_variable_completions(self):
         # We stopped in `main`, so we should see variables from main but
         # not from the other function
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="var",
                 expected={
                     variable_var1_completion.clone(length=3),
@@ -301,7 +298,7 @@ def test_variable_completions(self):
         )
 
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="str",
                 expected={
                     CompletionItem(label="struct", length=3),
@@ -317,14 +314,14 @@ def test_variable_completions(self):
 
         # Completion also works for more complex expressions
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="foo1.v",
                 expected={CompletionItem(label="foo1.var1", detail="int", 
length=6)},
             )
         )
 
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="foo1.my_bar_object.v",
                 expected={
                     CompletionItem(
@@ -335,14 +332,14 @@ def test_variable_completions(self):
         )
 
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="foo1.var1 + foo1.v",
                 expected={CompletionItem(label="foo1.var1", detail="int", 
length=6)},
             )
         )
 
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="foo1.var1 + v",
                 expected={CompletionItem(label="var1", detail="int &", 
length=1)},
             )
@@ -350,7 +347,7 @@ def test_variable_completions(self):
 
         # should correctly handle spaces between objects and member operators
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="foo1 .v",
                 expected={CompletionItem(label=".var1", detail="int", 
length=2)},
                 not_expected={CompletionItem(label=".var2", detail="int", 
length=2)},
@@ -358,7 +355,7 @@ def test_variable_completions(self):
         )
 
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="foo1 . v",
                 expected={CompletionItem(label="var1", detail="int", 
length=1)},
                 not_expected={CompletionItem(label="var2", detail="int", 
length=1)},
@@ -367,7 +364,7 @@ def test_variable_completions(self):
 
         # Even in variable mode, we can still use the escape prefix
         self.verify_completions(
-            TestCase(input="`mem", 
expected={memory_completion.clone(length=3)})
+            Scenario(input="`mem", 
expected={memory_completion.clone(length=3)})
         )
 
     def test_auto_completions(self):
@@ -391,7 +388,7 @@ def test_auto_completions(self):
         # We are stopped inside `main`. Variables `var1` and `var2` are in 
scope.
         # Make sure, we offer all completions
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="va",
                 expected={
                     command_var_completion.clone(length=2),
@@ -403,7 +400,7 @@ def test_auto_completions(self):
 
         # If we are using the escape prefix, only commands are suggested, but 
no variables
         self.verify_completions(
-            TestCase(
+            Scenario(
                 input="`va",
                 expected={
                     command_var_completion.clone(length=2),
diff --git a/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp 
b/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp
index 4704abd4b60c8..47e0f1a35ebac 100644
--- a/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp
@@ -79,9 +79,9 @@ static size_t GetPartialTokenCodeUnits(StringRef line, size_t 
cursor_pos) {
 
   const size_t byte_offset = (idx == StringRef::npos) ? 0 : idx + 1;
   const StringRef byte_token = line.substr(byte_offset);
-  llvm::SmallVector<UTF16, 20> utf16_token;
+  SmallVector<UTF16, 20> utf16_token;
 
-  if (llvm::convertUTF8ToUTF16String(byte_token, utf16_token))
+  if (convertUTF8ToUTF16String(byte_token, utf16_token))
     return utf16_token.size();
   return byte_token.size(); // fallback to back to byte offset.
 }

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to