7 changes: 4 additions & 3 deletions lldb/source/Commands/CommandObjectMultiword.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,10 @@ int CommandObjectMultiword::HandleCompletion(CompletionRequest &request) {

auto arg0 = request.GetParsedLine()[0].ref;
if (request.GetCursorIndex() == 0) {
StringList new_matches;
AddNamesMatchingPartialString(m_subcommand_dict, arg0, new_matches);
request.AddCompletions(new_matches);
StringList new_matches, descriptions;
AddNamesMatchingPartialString(m_subcommand_dict, arg0, new_matches,
&descriptions);
request.AddCompletions(new_matches, descriptions);

if (new_matches.GetSize() == 1 &&
new_matches.GetStringAtIndex(0) != nullptr &&
Expand Down
40 changes: 18 additions & 22 deletions lldb/source/Core/IOHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,10 @@ IOHandlerConfirm::IOHandlerConfirm(Debugger &debugger, llvm::StringRef prompt,

IOHandlerConfirm::~IOHandlerConfirm() = default;

int IOHandlerConfirm::IOHandlerComplete(IOHandler &io_handler,
const char *current_line,
const char *cursor,
const char *last_char,
int skip_first_n_matches,
int max_matches, StringList &matches) {
int IOHandlerConfirm::IOHandlerComplete(
IOHandler &io_handler, const char *current_line, const char *cursor,
const char *last_char, int skip_first_n_matches, int max_matches,
StringList &matches, StringList &descriptions) {
if (current_line == cursor) {
if (m_default_response) {
matches.AppendString("y");
Expand Down Expand Up @@ -223,27 +221,27 @@ void IOHandlerConfirm::IOHandlerInputComplete(IOHandler &io_handler,
}
}

int IOHandlerDelegate::IOHandlerComplete(IOHandler &io_handler,
const char *current_line,
const char *cursor,
const char *last_char,
int skip_first_n_matches,
int max_matches, StringList &matches) {
int IOHandlerDelegate::IOHandlerComplete(
IOHandler &io_handler, const char *current_line, const char *cursor,
const char *last_char, int skip_first_n_matches, int max_matches,
StringList &matches, StringList &descriptions) {
switch (m_completion) {
case Completion::None:
break;

case Completion::LLDBCommand:
return io_handler.GetDebugger().GetCommandInterpreter().HandleCompletion(
current_line, cursor, last_char, skip_first_n_matches, max_matches,
matches);

matches, descriptions);
case Completion::Expression: {
CompletionResult result;
CompletionRequest request(current_line, current_line - cursor,
skip_first_n_matches, max_matches, matches);
skip_first_n_matches, max_matches, result);
CommandCompletions::InvokeCommonCompletionCallbacks(
io_handler.GetDebugger().GetCommandInterpreter(),
CommandCompletions::eVariablePathCompletion, request, nullptr);
result.GetMatches(matches);
result.GetDescriptions(descriptions);

size_t num_matches = request.GetNumberOfMatches();
if (num_matches > 0) {
Expand Down Expand Up @@ -432,17 +430,15 @@ int IOHandlerEditline::FixIndentationCallback(Editline *editline,
*editline_reader, lines, cursor_position);
}

int IOHandlerEditline::AutoCompleteCallback(const char *current_line,
const char *cursor,
const char *last_char,
int skip_first_n_matches,
int max_matches,
StringList &matches, void *baton) {
int IOHandlerEditline::AutoCompleteCallback(
const char *current_line, const char *cursor, const char *last_char,
int skip_first_n_matches, int max_matches, StringList &matches,
StringList &descriptions, void *baton) {
IOHandlerEditline *editline_reader = (IOHandlerEditline *)baton;
if (editline_reader)
return editline_reader->m_delegate.IOHandlerComplete(
*editline_reader, current_line, cursor, last_char, skip_first_n_matches,
max_matches, matches);
max_matches, matches, descriptions);
return 0;
}
#endif
Expand Down
4 changes: 2 additions & 2 deletions lldb/source/Expression/REPL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ void REPL::IOHandlerInputComplete(IOHandler &io_handler, std::string &code) {
int REPL::IOHandlerComplete(IOHandler &io_handler, const char *current_line,
const char *cursor, const char *last_char,
int skip_first_n_matches, int max_matches,
StringList &matches) {
StringList &matches, StringList &descriptions) {
matches.Clear();

llvm::StringRef line(current_line, cursor - current_line);
Expand All @@ -466,7 +466,7 @@ int REPL::IOHandlerComplete(IOHandler &io_handler, const char *current_line,
const char *lldb_current_line = line.substr(1).data();
return debugger.GetCommandInterpreter().HandleCompletion(
lldb_current_line, cursor, last_char, skip_first_n_matches, max_matches,
matches);
matches, descriptions);
}

// Strip spaces from the line and see if we had only spaces
Expand Down
44 changes: 34 additions & 10 deletions lldb/source/Host/common/Editline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -853,19 +853,45 @@ unsigned char Editline::BufferEndCommand(int ch) {
return CC_NEWLINE;
}

//------------------------------------------------------------------------------
/// Prints completions and their descriptions to the given file. Only the
/// completions in the interval [start, end) are printed.
//------------------------------------------------------------------------------
static void PrintCompletion(FILE *output_file, size_t start, size_t end,
StringList &completions, StringList &descriptions) {
// This is an 'int' because of printf.
int max_len = 0;

for (size_t i = start; i < end; i++) {
const char *completion_str = completions.GetStringAtIndex(i);
max_len = std::max((int)strlen(completion_str), max_len);
}

for (size_t i = start; i < end; i++) {
const char *completion_str = completions.GetStringAtIndex(i);
const char *description_str = descriptions.GetStringAtIndex(i);

fprintf(output_file, "\n\t%-*s", max_len, completion_str);

// Print the description if we got one.
if (strlen(description_str))
fprintf(output_file, " -- %s", description_str);
}
}

unsigned char Editline::TabCommand(int ch) {
if (m_completion_callback == nullptr)
return CC_ERROR;

const LineInfo *line_info = el_line(m_editline);
StringList completions;
StringList completions, descriptions;
int page_size = 40;

const int num_completions = m_completion_callback(
line_info->buffer, line_info->cursor, line_info->lastchar,
0, // Don't skip any matches (start at match zero)
-1, // Get all the matches
completions, m_completion_callback_baton);
completions, descriptions, m_completion_callback_baton);

if (num_completions == 0)
return CC_ERROR;
Expand Down Expand Up @@ -893,10 +919,8 @@ unsigned char Editline::TabCommand(int ch) {
int num_elements = num_completions + 1;
fprintf(m_output_file, "\n" ANSI_CLEAR_BELOW "Available completions:");
if (num_completions < page_size) {
for (int i = 1; i < num_elements; i++) {
completion_str = completions.GetStringAtIndex(i);
fprintf(m_output_file, "\n\t%s", completion_str);
}
PrintCompletion(m_output_file, 1, num_elements, completions,
descriptions);
fprintf(m_output_file, "\n");
} else {
int cur_pos = 1;
Expand All @@ -906,10 +930,10 @@ unsigned char Editline::TabCommand(int ch) {
int endpoint = cur_pos + page_size;
if (endpoint > num_elements)
endpoint = num_elements;
for (; cur_pos < endpoint; cur_pos++) {
completion_str = completions.GetStringAtIndex(cur_pos);
fprintf(m_output_file, "\n\t%s", completion_str);
}

PrintCompletion(m_output_file, cur_pos, endpoint, completions,
descriptions);
cur_pos = endpoint;

if (cur_pos >= num_elements) {
fprintf(m_output_file, "\n");
Expand Down
75 changes: 46 additions & 29 deletions lldb/source/Interpreter/CommandInterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -794,20 +794,23 @@ void CommandInterpreter::LoadCommandDictionary() {
}

int CommandInterpreter::GetCommandNamesMatchingPartialString(
const char *cmd_str, bool include_aliases, StringList &matches) {
AddNamesMatchingPartialString(m_command_dict, cmd_str, matches);
const char *cmd_str, bool include_aliases, StringList &matches,
StringList &descriptions) {
AddNamesMatchingPartialString(m_command_dict, cmd_str, matches,
&descriptions);

if (include_aliases) {
AddNamesMatchingPartialString(m_alias_dict, cmd_str, matches);
AddNamesMatchingPartialString(m_alias_dict, cmd_str, matches,
&descriptions);
}

return matches.GetSize();
}

CommandObjectSP CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str,
bool include_aliases,
bool exact,
StringList *matches) const {
CommandObjectSP
CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases,
bool exact, StringList *matches,
StringList *descriptions) const {
CommandObjectSP command_sp;

std::string cmd = cmd_str;
Expand Down Expand Up @@ -848,8 +851,8 @@ CommandObjectSP CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str,
// empty CommandObjectSP and the list of matches.

if (HasCommands()) {
num_cmd_matches =
AddNamesMatchingPartialString(m_command_dict, cmd_str, *matches);
num_cmd_matches = AddNamesMatchingPartialString(m_command_dict, cmd_str,
*matches, descriptions);
}

if (num_cmd_matches == 1) {
Expand All @@ -860,8 +863,8 @@ CommandObjectSP CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str,
}

if (include_aliases && HasAliases()) {
num_alias_matches =
AddNamesMatchingPartialString(m_alias_dict, cmd_str, *matches);
num_alias_matches = AddNamesMatchingPartialString(m_alias_dict, cmd_str,
*matches, descriptions);
}

if (num_alias_matches == 1) {
Expand All @@ -872,8 +875,8 @@ CommandObjectSP CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str,
}

if (HasUserCommands()) {
num_user_matches =
AddNamesMatchingPartialString(m_user_dict, cmd_str, *matches);
num_user_matches = AddNamesMatchingPartialString(m_user_dict, cmd_str,
*matches, descriptions);
}

if (num_user_matches == 1) {
Expand All @@ -898,6 +901,8 @@ CommandObjectSP CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str,
}
} else if (matches && command_sp) {
matches->AppendString(cmd_str);
if (descriptions)
descriptions->AppendString(command_sp->GetHelp());
}

return command_sp;
Expand Down Expand Up @@ -997,18 +1002,20 @@ CommandObjectSP CommandInterpreter::GetCommandSPExact(llvm::StringRef cmd_str,
return ret_val;
}

CommandObject *CommandInterpreter::GetCommandObject(llvm::StringRef cmd_str,
StringList *matches) const {
CommandObject *
CommandInterpreter::GetCommandObject(llvm::StringRef cmd_str,
StringList *matches,
StringList *descriptions) const {
CommandObject *command_obj =
GetCommandSP(cmd_str, false, true, matches).get();
GetCommandSP(cmd_str, false, true, matches, descriptions).get();

// If we didn't find an exact match to the command string in the commands,
// look in the aliases.

if (command_obj)
return command_obj;

command_obj = GetCommandSP(cmd_str, true, true, matches).get();
command_obj = GetCommandSP(cmd_str, true, true, matches, descriptions).get();

if (command_obj)
return command_obj;
Expand All @@ -1023,10 +1030,12 @@ CommandObject *CommandInterpreter::GetCommandObject(llvm::StringRef cmd_str,
if (command_obj) {
if (matches)
matches->AppendString(command_obj->GetCommandName());
if (descriptions)
descriptions->AppendString(command_obj->GetHelp());
return command_obj;
}

return GetCommandSP(cmd_str, true, false, matches).get();
return GetCommandSP(cmd_str, true, false, matches, descriptions).get();
}

bool CommandInterpreter::CommandExists(llvm::StringRef cmd) const {
Expand Down Expand Up @@ -1712,16 +1721,17 @@ int CommandInterpreter::HandleCompletionMatches(CompletionRequest &request) {
if (request.GetCursorIndex() == -1) {
// We got nothing on the command line, so return the list of commands
bool include_aliases = true;
StringList new_matches;
num_command_matches =
GetCommandNamesMatchingPartialString("", include_aliases, new_matches);
request.AddCompletions(new_matches);
StringList new_matches, descriptions;
num_command_matches = GetCommandNamesMatchingPartialString(
"", include_aliases, new_matches, descriptions);
request.AddCompletions(new_matches, descriptions);
} else if (request.GetCursorIndex() == 0) {
// The cursor is in the first argument, so just do a lookup in the
// dictionary.
StringList new_matches;
CommandObject *cmd_obj = GetCommandObject(
request.GetParsedLine().GetArgumentAtIndex(0), &new_matches);
StringList new_matches, new_descriptions;
CommandObject *cmd_obj =
GetCommandObject(request.GetParsedLine().GetArgumentAtIndex(0),
&new_matches, &new_descriptions);

if (num_command_matches == 1 && cmd_obj && cmd_obj->IsMultiwordObject() &&
new_matches.GetStringAtIndex(0) != nullptr &&
Expand All @@ -1733,12 +1743,13 @@ int CommandInterpreter::HandleCompletionMatches(CompletionRequest &request) {
look_for_subcommand = true;
num_command_matches = 0;
new_matches.DeleteStringAtIndex(0);
new_descriptions.DeleteStringAtIndex(0);
request.GetParsedLine().AppendArgument(llvm::StringRef());
request.SetCursorIndex(request.GetCursorIndex() + 1);
request.SetCursorCharPosition(0);
}
}
request.AddCompletions(new_matches);
request.AddCompletions(new_matches, new_descriptions);
num_command_matches = request.GetNumberOfMatches();
}

Expand All @@ -1762,12 +1773,13 @@ int CommandInterpreter::HandleCompletionMatches(CompletionRequest &request) {

int CommandInterpreter::HandleCompletion(
const char *current_line, const char *cursor, const char *last_char,
int match_start_point, int max_return_elements, StringList &matches) {
int match_start_point, int max_return_elements, StringList &matches,
StringList &descriptions) {

llvm::StringRef command_line(current_line, last_char - current_line);
CompletionResult result;
CompletionRequest request(command_line, cursor - current_line,
match_start_point, max_return_elements, matches);

match_start_point, max_return_elements, result);
// Don't complete comments, and if the line we are completing is just the
// history repeat character, substitute the appropriate history line.
const char *first_arg = request.GetParsedLine().GetArgumentAtIndex(0);
Expand All @@ -1777,6 +1789,7 @@ int CommandInterpreter::HandleCompletion(
else if (first_arg[0] == CommandHistory::g_repeat_char) {
if (auto hist_str = m_command_history.FindString(first_arg)) {
matches.InsertStringAtIndex(0, *hist_str);
descriptions.InsertStringAtIndex(0, "Previous command history event");
return -2;
} else
return 0;
Expand All @@ -1787,13 +1800,16 @@ int CommandInterpreter::HandleCompletion(
lldbassert(max_return_elements == -1);

int num_command_matches = HandleCompletionMatches(request);
result.GetMatches(matches);
result.GetDescriptions(descriptions);

if (num_command_matches <= 0)
return num_command_matches;

if (request.GetParsedLine().GetArgumentCount() == 0) {
// If we got an empty string, insert nothing.
matches.InsertStringAtIndex(0, "");
descriptions.InsertStringAtIndex(0, "");
} else {
// Now figure out if there is a common substring, and if so put that in
// element 0, otherwise put an empty string in element 0.
Expand All @@ -1815,6 +1831,7 @@ int CommandInterpreter::HandleCompletion(
common_prefix.push_back(' ');
}
matches.InsertStringAtIndex(0, common_prefix.c_str());
descriptions.InsertStringAtIndex(0, "");
}
return num_command_matches;
}
Expand Down
43 changes: 38 additions & 5 deletions lldb/source/Utility/CompletionRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ CompletionRequest::CompletionRequest(llvm::StringRef command_line,
unsigned raw_cursor_pos,
int match_start_point,
int max_return_elements,
StringList &matches)
CompletionResult &result)
: m_command(command_line), m_raw_cursor_pos(raw_cursor_pos),
m_match_start_point(match_start_point),
m_max_return_elements(max_return_elements), m_matches(&matches) {
matches.Clear();
m_max_return_elements(max_return_elements), m_result(result) {

// We parse the argument up to the cursor, so the last argument in
// parsed_line is the one containing the cursor, and the cursor is after the
Expand All @@ -36,8 +35,6 @@ CompletionRequest::CompletionRequest(llvm::StringRef command_line,
m_cursor_char_position =
strlen(m_partial_parsed_line.GetArgumentAtIndex(m_cursor_index));

matches.Clear();

const char *cursor = command_line.data() + raw_cursor_pos;
if (raw_cursor_pos > 0 && cursor[-1] == ' ') {
// We are just after a space. If we are in an argument, then we will
Expand All @@ -58,3 +55,39 @@ CompletionRequest::CompletionRequest(llvm::StringRef command_line,
}
}
}

std::string CompletionResult::Completion::GetUniqueKey() const {

// We build a unique key for this pair of completion:description. We
// prefix the key with the length of the completion string. This prevents
// that we could get any collisions from completions pairs such as these:
// "foo:", "bar" would be "foo:bar", but will now be: "4foo:bar"
// "foo", ":bar" would be "foo:bar", but will now be: "3foo:bar"

std::string result;
result.append(std::to_string(m_completion.size()));
result.append(m_completion);
result.append(m_descripton);
return result;
}

void CompletionResult::AddResult(llvm::StringRef completion,
llvm::StringRef description) {
Completion r(completion, description);

// Add the completion if we haven't seen the same value before.
if (m_added_values.insert(r.GetUniqueKey()).second)
m_results.push_back(r);
}

void CompletionResult::GetMatches(StringList &matches) const {
matches.Clear();
for (const Completion &completion : m_results)
matches.AppendString(completion.m_completion);
}

void CompletionResult::GetDescriptions(StringList &descriptions) const {
descriptions.Clear();
for (const Completion &completion : m_results)
descriptions.AppendString(completion.m_descripton);
}
100 changes: 97 additions & 3 deletions lldb/unittests/Utility/CompletionRequestTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ TEST(CompletionRequest, Constructor) {
const int match_start = 2345;
const int match_max_return = 12345;
StringList matches;
CompletionResult result;

CompletionRequest request(command, cursor_pos, match_start, match_max_return,
matches);
result);
result.GetMatches(matches);

EXPECT_STREQ(request.GetRawLine().str().c_str(), command.c_str());
EXPECT_EQ(request.GetRawCursorPos(), cursor_pos);
Expand All @@ -41,63 +43,155 @@ TEST(CompletionRequest, DuplicateFiltering) {
const unsigned cursor_pos = 3;
StringList matches;

CompletionRequest request(command, cursor_pos, 0, 0, matches);
CompletionResult result;
CompletionRequest request(command, cursor_pos, 0, 0, result);
result.GetMatches(matches);

EXPECT_EQ(0U, request.GetNumberOfMatches());

// Add foo twice
request.AddCompletion("foo");
result.GetMatches(matches);

EXPECT_EQ(1U, request.GetNumberOfMatches());
EXPECT_EQ(1U, matches.GetSize());
EXPECT_STREQ("foo", matches.GetStringAtIndex(0));

request.AddCompletion("foo");
result.GetMatches(matches);

EXPECT_EQ(1U, request.GetNumberOfMatches());
EXPECT_EQ(1U, matches.GetSize());
EXPECT_STREQ("foo", matches.GetStringAtIndex(0));

// Add bar twice
request.AddCompletion("bar");
result.GetMatches(matches);

EXPECT_EQ(2U, request.GetNumberOfMatches());
EXPECT_EQ(2U, matches.GetSize());
EXPECT_STREQ("foo", matches.GetStringAtIndex(0));
EXPECT_STREQ("bar", matches.GetStringAtIndex(1));

request.AddCompletion("bar");
result.GetMatches(matches);

EXPECT_EQ(2U, request.GetNumberOfMatches());
EXPECT_EQ(2U, matches.GetSize());
EXPECT_STREQ("foo", matches.GetStringAtIndex(0));
EXPECT_STREQ("bar", matches.GetStringAtIndex(1));

// Add foo again.
request.AddCompletion("foo");
result.GetMatches(matches);

EXPECT_EQ(2U, request.GetNumberOfMatches());
EXPECT_EQ(2U, matches.GetSize());
EXPECT_STREQ("foo", matches.GetStringAtIndex(0));
EXPECT_STREQ("bar", matches.GetStringAtIndex(1));

// Add something with an existing prefix
request.AddCompletion("foobar");
result.GetMatches(matches);

EXPECT_EQ(3U, request.GetNumberOfMatches());
EXPECT_EQ(3U, matches.GetSize());
EXPECT_STREQ("foo", matches.GetStringAtIndex(0));
EXPECT_STREQ("bar", matches.GetStringAtIndex(1));
EXPECT_STREQ("foobar", matches.GetStringAtIndex(2));
}

TEST(CompletionRequest, DuplicateFilteringWithComments) {
std::string command = "a bad c";
const unsigned cursor_pos = 3;
StringList matches, descriptions;

CompletionResult result;
CompletionRequest request(command, cursor_pos, 0, 0, result);
result.GetMatches(matches);
result.GetDescriptions(descriptions);

EXPECT_EQ(0U, request.GetNumberOfMatches());

// Add foo twice with same comment
request.AddCompletion("foo", "comment");
result.GetMatches(matches);
result.GetDescriptions(descriptions);

EXPECT_EQ(1U, request.GetNumberOfMatches());
EXPECT_EQ(1U, matches.GetSize());
EXPECT_EQ(1U, descriptions.GetSize());
EXPECT_STREQ("foo", matches.GetStringAtIndex(0));
EXPECT_STREQ("comment", descriptions.GetStringAtIndex(0));

request.AddCompletion("foo", "comment");
result.GetMatches(matches);
result.GetDescriptions(descriptions);

EXPECT_EQ(1U, request.GetNumberOfMatches());
EXPECT_EQ(1U, matches.GetSize());
EXPECT_EQ(1U, descriptions.GetSize());
EXPECT_STREQ("foo", matches.GetStringAtIndex(0));
EXPECT_STREQ("comment", descriptions.GetStringAtIndex(0));

// Add bar twice with different comments
request.AddCompletion("bar", "comment");
result.GetMatches(matches);
result.GetDescriptions(descriptions);

EXPECT_EQ(2U, request.GetNumberOfMatches());
EXPECT_EQ(2U, matches.GetSize());
EXPECT_EQ(2U, descriptions.GetSize());
EXPECT_STREQ("foo", matches.GetStringAtIndex(0));
EXPECT_STREQ("bar", matches.GetStringAtIndex(1));

request.AddCompletion("bar", "another comment");
result.GetMatches(matches);
result.GetDescriptions(descriptions);

EXPECT_EQ(3U, request.GetNumberOfMatches());
EXPECT_EQ(3U, matches.GetSize());
EXPECT_EQ(3U, descriptions.GetSize());
EXPECT_STREQ("foo", matches.GetStringAtIndex(0));
EXPECT_STREQ("comment", descriptions.GetStringAtIndex(0));
EXPECT_STREQ("bar", matches.GetStringAtIndex(1));
EXPECT_STREQ("comment", descriptions.GetStringAtIndex(1));
EXPECT_STREQ("bar", matches.GetStringAtIndex(2));
EXPECT_STREQ("another comment", descriptions.GetStringAtIndex(2));

// Add foo again with no comment
request.AddCompletion("foo");
result.GetMatches(matches);
result.GetDescriptions(descriptions);

EXPECT_EQ(4U, request.GetNumberOfMatches());
EXPECT_EQ(4U, matches.GetSize());
EXPECT_EQ(4U, descriptions.GetSize());
EXPECT_STREQ("foo", matches.GetStringAtIndex(0));
EXPECT_STREQ("comment", descriptions.GetStringAtIndex(0));
EXPECT_STREQ("bar", matches.GetStringAtIndex(1));
EXPECT_STREQ("comment", descriptions.GetStringAtIndex(1));
EXPECT_STREQ("bar", matches.GetStringAtIndex(2));
EXPECT_STREQ("another comment", descriptions.GetStringAtIndex(2));
EXPECT_STREQ("foo", matches.GetStringAtIndex(3));
EXPECT_STREQ("", descriptions.GetStringAtIndex(3));
}

TEST(CompletionRequest, TestCompletionOwnership) {
std::string command = "a bad c";
const unsigned cursor_pos = 3;
StringList matches;

CompletionRequest request(command, cursor_pos, 0, 0, matches);
CompletionResult result;
CompletionRequest request(command, cursor_pos, 0, 0, result);

std::string Temporary = "bar";
request.AddCompletion(Temporary);
// Manipulate our completion. The request should have taken a copy, so that
// shouldn't influence anything.
Temporary[0] = 'f';

result.GetMatches(matches);
EXPECT_EQ(1U, request.GetNumberOfMatches());
EXPECT_STREQ("bar", matches.GetStringAtIndex(0));
}