blob: cb1d6c44029c0f351799571be071ac1e0ddf519c [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "garnet/bin/zxdb/console/line_input.h"
#include "gtest/gtest.h"
namespace zxdb {
namespace {
// Some common terminal codes.
#define TERM_UP "\x1b[A"
#define TERM_DOWN "\x1b[B"
#define TERM_LEFT "\x1b[D"
#define TERM_RIGHT "\x1b[C"
// Dummy completion functions that return two completions.
std::vector<std::string> CompletionCallback(const std::string& line) {
std::vector<std::string> result;
result.push_back("one");
result.push_back("two");
return result;
}
} // namespace
class TestLineInput : public LineInputBase {
public:
TestLineInput(const std::string& prompt) : LineInputBase(prompt) {}
void ClearOutput() { output_.clear(); }
std::string GetAndClearOutput() {
std::string ret = output_;
ClearOutput();
return ret;
}
// This input takes a string instead of one character at a time, returning
// the result of the last one.
bool OnInputStr(const std::string& input) {
bool result = false;
for (char c : input)
result = OnInput(c);
return result;
}
void SetLine(const std::string& input) {
cur_line() = input;
set_pos(input.size());
}
void SetPos(size_t pos) {
set_pos(pos);
}
protected:
void Write(const std::string& data) { output_.append(data); }
private:
std::string output_;
};
TEST(LineInput, CursorCommands) {
std::string prompt("Prompt ");
TestLineInput input(prompt);
// Basic prompt. "7C" at the end means cursor is @ 7th character.
input.BeginReadLine();
EXPECT_EQ("\rPrompt \x1b[0K\r\x1B[7C", input.GetAndClearOutput());
// Basic input with enter.
EXPECT_FALSE(input.OnInput('a'));
EXPECT_FALSE(input.OnInput('b'));
EXPECT_TRUE(input.OnInput('\r'));
EXPECT_EQ("ab", input.line());
input.BeginReadLine();
EXPECT_FALSE(input.OnInputStr("abcd"));
EXPECT_EQ(4u, input.pos());
// Basic cursor movement.
EXPECT_FALSE(input.OnInput(2)); // Control-B = left.
EXPECT_EQ(3u, input.pos());
EXPECT_FALSE(input.OnInput(6)); // Control-F = right.
EXPECT_EQ(4u, input.pos());
EXPECT_FALSE(input.OnInput(1)); // Control-A = home.
EXPECT_EQ(0u, input.pos());
EXPECT_FALSE(input.OnInput(5)); // Control-E = end.
EXPECT_EQ(4u, input.pos());
// Longer escaped sequences.
EXPECT_FALSE(input.OnInputStr("\x1b[D")); // Left.
EXPECT_EQ(3u, input.pos());
EXPECT_FALSE(input.OnInputStr("\x1b[C")); // Right.
EXPECT_EQ(4u, input.pos());
EXPECT_FALSE(input.OnInputStr("\x1b[H")); // Home.
EXPECT_EQ(0u, input.pos());
EXPECT_FALSE(input.OnInputStr("\x1b[F")); // End.
EXPECT_EQ(4u, input.pos());
// Backspace.
EXPECT_FALSE(input.OnInput(127)); // Backspace.
EXPECT_EQ(3u, input.pos());
EXPECT_EQ("abc", input.line());
// Delete. This one also tests the line refresh commands.
EXPECT_FALSE(input.OnInput(1)); // Home.
input.ClearOutput();
EXPECT_FALSE(input.OnInputStr("\x1b[3~"));
EXPECT_EQ("bc", input.line());
// "7C" at the end means cursor is at the 7th character (the "b").
EXPECT_EQ("\rPrompt bc\x1b[0K\r\x1B[7C", input.GetAndClearOutput());
EXPECT_EQ(0u, input.pos());
}
TEST(LineInput, History) {
TestLineInput input("");
// Make some history.
input.AddToHistory("one");
input.AddToHistory("two");
// Go up twice.
input.BeginReadLine();
EXPECT_FALSE(input.OnInputStr(TERM_UP TERM_UP));
// Should have selected the first line and the cursor should be at the end.
EXPECT_EQ("one", input.line());
EXPECT_EQ(3u, input.pos());
// Append a letter and accept it.
input.OnInputStr("s\r");
input.AddToHistory(input.line());
// Start editing a new line with some input.
input.BeginReadLine();
input.OnInputStr("three");
// Check history. Should be:
// ones
// two
// ones
// three
EXPECT_EQ("three", input.line());
EXPECT_FALSE(input.OnInputStr(TERM_UP));
EXPECT_EQ("ones", input.line());
EXPECT_FALSE(input.OnInputStr(TERM_UP));
EXPECT_EQ("two", input.line());
EXPECT_FALSE(input.OnInputStr(TERM_UP));
EXPECT_FALSE(input.OnInputStr(TERM_UP)); // From here, these are extra to
EXPECT_FALSE(input.OnInputStr(TERM_UP)); // test that going beyond the top
EXPECT_FALSE(input.OnInputStr(TERM_UP)); // stays stopped.
EXPECT_EQ("ones", input.line());
// Going back to the bottom (also doing one extra one to test the boundary).
EXPECT_FALSE(input.OnInputStr(TERM_DOWN TERM_DOWN TERM_DOWN TERM_DOWN));
// Should have gotten the original non-accepted input back.
EXPECT_EQ("three", input.line());
}
TEST(LineInput, Completions) {
TestLineInput input("");
input.set_completion_callback(&CompletionCallback);
input.BeginReadLine();
input.OnInput('z');
// Send one tab, should get the first suggestion.
input.OnInput(9);
EXPECT_EQ("one", input.line());
EXPECT_EQ(3u, input.pos());
// Second suggestion.
input.OnInput(9);
EXPECT_EQ("two", input.line());
EXPECT_EQ(3u, input.pos());
// Again should go back to original text.
input.OnInput(9);
EXPECT_EQ("z", input.line());
EXPECT_EQ(1u, input.pos());
// Should wrap around to the first suggestion.
input.OnInput(9);
EXPECT_EQ("one", input.line());
EXPECT_EQ(3u, input.pos());
// Typing should append.
input.OnInput('s');
EXPECT_EQ("ones", input.line());
EXPECT_EQ(4u, input.pos());
// Tab again should give the same suggestions.
input.OnInput(9);
EXPECT_EQ("one", input.line());
EXPECT_EQ(3u, input.pos());
// Send an escape sequence "left" which should accept the suggestion and
// execute the sequence.
input.OnInputStr("\x1b[D");
EXPECT_EQ("one", input.line());
EXPECT_EQ(2u, input.pos());
}
TEST(LineInput, Scroll) {
TestLineInput input("ABCDE");
input.set_max_cols(10);
input.BeginReadLine();
input.ClearOutput();
// Write up to the 9th character, which should be the last character printed
// until scrolling starts. It should have used the optimized "just write the
// characters" code path for everything after the prompt.
EXPECT_FALSE(input.OnInputStr("FGHI"));
EXPECT_EQ("FGHI", input.GetAndClearOutput());
// Add a 10th character. The whole line should scroll one to the left,
// leaving the cursor at the last column (colum offset 9 = "9C" at the end).
EXPECT_FALSE(input.OnInput('J'));
EXPECT_EQ("\rBCDEFGHIJ\x1b[0K\r\x1B[9C", input.GetAndClearOutput());
// Move left, the line should scroll back.
EXPECT_FALSE(input.OnInput(2)); // 2 = Control-B.
EXPECT_EQ("\rABCDEFGHIJ\x1b[0K\r\x1B[9C", input.GetAndClearOutput());
}
TEST(LineInput, NegAck) {
TestLineInput input("ABCDE");
input.BeginReadLine();
// Empty should remain with them prompt.
EXPECT_FALSE(input.OnInput(SpecialCharacters::kKeyControlU));
EXPECT_EQ(input.line(), "");
// Adding characters and then Control-U should clear.
input.OnInputStr("12345");
EXPECT_FALSE(input.OnInput(SpecialCharacters::kKeyControlU));
EXPECT_EQ(input.line(), "");
// In the middle of the line should clear until the cursor.
input.OnInputStr("0123456789");
EXPECT_FALSE(input.OnInputStr(TERM_LEFT));
EXPECT_FALSE(input.OnInputStr(TERM_LEFT));
EXPECT_FALSE(input.OnInputStr(TERM_LEFT));
EXPECT_FALSE(input.OnInputStr(TERM_LEFT));
EXPECT_FALSE(input.OnInput(SpecialCharacters::kKeyControlU));
EXPECT_EQ(input.line(), "6789");
EXPECT_EQ(input.pos(), 0u);
}
TEST(LineInput, EndOfTransimission) {
TestLineInput input("[zxdb] ");
input.BeginReadLine();
// v
input.SetLine("First Second Third");
input.SetPos(0);
EXPECT_FALSE(input.OnInput(SpecialCharacters::kKeyControlW));
EXPECT_EQ(input.line(), "First Second Third");
// v
input.SetLine("First Second Third");
input.SetPos(2);
EXPECT_FALSE(input.OnInput(SpecialCharacters::kKeyControlW));
EXPECT_EQ(input.line(), "rst Second Third");
// v
input.SetLine("First Second Third");
input.SetPos(5);
EXPECT_FALSE(input.OnInput(SpecialCharacters::kKeyControlW));
EXPECT_EQ(input.line(), " Second Third");
// v
input.SetLine("First Second Third");
input.SetPos(8);
EXPECT_FALSE(input.OnInput(SpecialCharacters::kKeyControlW));
EXPECT_EQ(input.line(), "First cond Third");
// v
input.SetLine("First Second Third");
input.SetPos(12);
EXPECT_FALSE(input.OnInput(SpecialCharacters::kKeyControlW));
EXPECT_EQ(input.line(), "First Third");
// v
input.SetLine("First Second Third");
input.SetPos(15);
EXPECT_FALSE(input.OnInput(SpecialCharacters::kKeyControlW));
EXPECT_EQ(input.line(), "First Second ird");
// v
input.SetLine("First Second Third");
EXPECT_FALSE(input.OnInput(SpecialCharacters::kKeyControlW));
EXPECT_EQ(input.line(), "First Second ");
}
} // namespace zxdb