[debugger] Added Ctrl-w

Ctrl-w removes characters from the beginning of the current word until
the cursor.

Reorganized some character constants so that they're usable from the
tests.

DX-879  # Done.

TEST=unit

Change-Id: If64846d03286a26493840777ffabd50ff9a21739
diff --git a/bin/zxdb/console/line_input.cc b/bin/zxdb/console/line_input.cc
index 344e462..3b0444e 100644
--- a/bin/zxdb/console/line_input.cc
+++ b/bin/zxdb/console/line_input.cc
@@ -15,32 +15,17 @@
 #include <termios.h>
 #endif
 
-#include "garnet/public/lib/fxl/logging.h"
+#include "lib/fxl/logging.h"
+#include "lib/fxl/strings/split_string.h"
 
 namespace zxdb {
 
+const char* SpecialCharacters::kTermBeginningOfLine = "\r";
+const char* SpecialCharacters::kTermClearToEnd = "\x1b[0K";
+const char* SpecialCharacters::kTermCursorToColFormat = "\r\x1b[%dC";
+
 namespace {
 
-constexpr char kKeyControlA = 1;
-constexpr char kKeyControlB = 2;
-constexpr char kKeyControlE = 5;
-constexpr char kKeyControlF = 6;
-constexpr char kKeyControlH = 8;
-constexpr char kKeyTab = 9;
-constexpr char kKeyNewline = 10;
-constexpr char kKeyFormFeed = 12;
-constexpr char kKeyEnter = 13;
-constexpr char kKeyControlN = 14;
-constexpr char kKeyControlP = 16;
-constexpr char kKeyControlU = 21;
-constexpr char kKeyEsc = 27;
-constexpr char kKeyBackspace = 127;
-
-// Escape sequences for terminal output.
-const char kTermBeginningOfLine[] = "\r";
-const char kTermClearToEnd[] = "\x1b[0K";
-const char kTermCursorToColFormat[] = "\r\x1b[%dC";  // printf format.
-
 size_t GetTerminalMaxCols(int fileno) {
 #ifdef __Fuchsia__
   pty_window_size_t wsz;
@@ -82,7 +67,7 @@
 
   if (completion_mode_) {
     // Special keys for completion mode.
-    if (c == kKeyTab) {
+    if (c == SpecialCharacters::kKeyTab) {
       HandleTab();
       return false;
     }
@@ -93,42 +78,45 @@
   }
 
   switch (c) {
-    case kKeyControlA:
+    case SpecialCharacters::kKeyControlA:
       MoveHome();
       break;
-    case kKeyControlB:
+    case SpecialCharacters::kKeyControlB:
       MoveLeft();
       break;
-    case kKeyControlE:
+    case SpecialCharacters::kKeyControlE:
       MoveEnd();
       break;
-    case kKeyControlF:
+    case SpecialCharacters::kKeyControlF:
       MoveRight();
       break;
-    case kKeyFormFeed:
+    case SpecialCharacters::kKeyFormFeed:
       HandleFormFeed();
       break;
-    case kKeyTab:
+    case SpecialCharacters::kKeyTab:
       HandleTab();
       break;
-    case kKeyNewline:
-    case kKeyEnter:
+    case SpecialCharacters::kKeyNewline:
+    case SpecialCharacters::kKeyEnter:
       HandleEnter();
       return true;
-    case kKeyControlN:
+    case SpecialCharacters::kKeyControlN:
       MoveDown();
       break;
-    case kKeyControlP:
+    case SpecialCharacters::kKeyControlP:
       MoveUp();
       break;
-    case kKeyControlU:
+    case SpecialCharacters::kKeyControlU:
       HandleNegAck();
       break;
-    case kKeyEsc:
+    case SpecialCharacters::kKeyControlW:
+      HandleEndOfTransimission();
+      break;
+    case SpecialCharacters::kKeyEsc:
       reading_escaped_input_ = true;
       break;
-    case kKeyControlH:
-    case kKeyBackspace:
+    case SpecialCharacters::kKeyControlH:
+    case SpecialCharacters::kKeyBackspace:
       HandleBackspace();
       break;
     default:
@@ -156,8 +144,8 @@
     return;
 
   std::string cmd;
-  cmd += kTermBeginningOfLine;
-  cmd += kTermClearToEnd;
+  cmd += SpecialCharacters::kTermBeginningOfLine;
+  cmd += SpecialCharacters::kTermClearToEnd;
 
   Write(cmd);
   EnsureNoRawMode();
@@ -286,6 +274,33 @@
   RepaintLine();
 }
 
+void LineInputBase::HandleEndOfTransimission() {
+  const auto& line = cur_line();
+  if (line.empty())
+    return;
+
+  // We search for the last space that's before the cursor.
+  size_t latest_space = 0;
+  for (size_t i = 0; i < line.size(); i++) {
+    if (i >= pos_)
+      break;
+
+    if (line[i] == ' ')
+      latest_space = i;
+  }
+
+  // Ctrl-w removes from the latest space until the cursor.
+  std::string new_line;
+  if (latest_space > 0)
+    new_line.append(line.substr(0, latest_space + 1));
+  new_line.append(line.substr(pos_));
+
+  size_t diff = line.size() - new_line.size();
+  pos_ -= diff;
+  cur_line() = std::move(new_line);
+  RepaintLine();
+}
+
 void LineInputBase::HandleFormFeed() {
   Write("\033c");  // Form feed.
   RepaintLine();
@@ -367,7 +382,7 @@
   std::string buf;
   buf.reserve(64);
 
-  buf += kTermBeginningOfLine;
+  buf += SpecialCharacters::kTermBeginningOfLine;
 
   // Only print up to max_cols_ - 1 to leave room for the cursor at the end.
   std::string line_data = prompt_ + cur_line();
@@ -388,10 +403,11 @@
     buf += line_data;
   }
 
-  buf += kTermClearToEnd;
+  buf += SpecialCharacters::kTermClearToEnd;
 
   char forward_buf[32];
-  snprintf(forward_buf, sizeof(forward_buf), kTermCursorToColFormat,
+  snprintf(forward_buf, sizeof(forward_buf),
+           SpecialCharacters::kTermCursorToColFormat,
            static_cast<int>(pos_in_cols));
   buf += forward_buf;
 
diff --git a/bin/zxdb/console/line_input.h b/bin/zxdb/console/line_input.h
index e3ec949..882e66b 100644
--- a/bin/zxdb/console/line_input.h
+++ b/bin/zxdb/console/line_input.h
@@ -14,6 +14,29 @@
 
 namespace zxdb {
 
+struct SpecialCharacters {
+  static constexpr char kKeyControlA = 1;
+  static constexpr char kKeyControlB = 2;
+  static constexpr char kKeyControlE = 5;
+  static constexpr char kKeyControlF = 6;
+  static constexpr char kKeyControlH = 8;
+  static constexpr char kKeyTab = 9;
+  static constexpr char kKeyNewline = 10;
+  static constexpr char kKeyFormFeed = 12;
+  static constexpr char kKeyEnter = 13;
+  static constexpr char kKeyControlN = 14;
+  static constexpr char kKeyControlP = 16;
+  static constexpr char kKeyControlU = 21;
+  static constexpr char kKeyControlW = 23;
+  static constexpr char kKeyEsc = 27;
+  static constexpr char kKeyBackspace = 127;
+
+  // Escape sequences for terminal output.
+  static const char* kTermBeginningOfLine;
+  static const char* kTermClearToEnd;
+  static const char* kTermCursorToColFormat;  // printf format.
+};
+
 // This class implements a push model for input of characters, allowing it to
 // be used in asynchronous contexts.
 //
@@ -84,6 +107,12 @@
   virtual void EnsureRawMode() {}
   virtual void EnsureNoRawMode() {}
 
+  // Helper to return the current line of text.
+  std::string& cur_line() { return history_[history_index_]; }
+
+  // Useful for testing.
+  void set_pos(size_t pos) { pos_ = pos; }
+
  private:
   void HandleEscapedInput(char c);
 
@@ -94,6 +123,8 @@
   void HandleTab();
   // NegAck is the name of Ctrl-U in ASCII world.
   void HandleNegAck();
+  // EndOfTransimission is the name for Ctrl-W in ASCII world.
+  void HandleEndOfTransimission();
 
   void Insert(char c);
   void MoveLeft();
@@ -109,9 +140,6 @@
   void RepaintLine();
   void ResetLineState();
 
-  // Helper to return the current line of text.
-  std::string& cur_line() { return history_[history_index_]; }
-
   const std::string prompt_;
   size_t max_cols_ = 0;
   CompletionCallback completion_callback_ = nullptr;
diff --git a/bin/zxdb/console/line_input_unittest.cc b/bin/zxdb/console/line_input_unittest.cc
index dbc9001..cb1d6c4 100644
--- a/bin/zxdb/console/line_input_unittest.cc
+++ b/bin/zxdb/console/line_input_unittest.cc
@@ -45,6 +45,15 @@
     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); }
 
@@ -223,12 +232,12 @@
   input.BeginReadLine();
 
   // Empty should remain with them prompt.
-  EXPECT_FALSE(input.OnInput(21));  // 21 = Control-U
+  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(21));  // 21 = Control-U
+  EXPECT_FALSE(input.OnInput(SpecialCharacters::kKeyControlU));
   EXPECT_EQ(input.line(), "");
 
   // In the middle of the line should clear until the cursor.
@@ -237,9 +246,55 @@
   EXPECT_FALSE(input.OnInputStr(TERM_LEFT));
   EXPECT_FALSE(input.OnInputStr(TERM_LEFT));
   EXPECT_FALSE(input.OnInputStr(TERM_LEFT));
-  EXPECT_FALSE(input.OnInput(21));  // 21 = Control-U
+  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