Repository: microsoft/terminal (language: cpp)

Issue:
COOKED_READ: cmd.exe tab fails to erase trailing characters
### Windows build number

10.0.26052.0

### Steps to reproduce

* Launch cmd.exe
* Type `foo bar`
* Move the cursor the space after `foo`
* Press tab

### Expected Behavior

The prompt reads `foo`.

### Actual Behavior

The prompt reads `foo bar` and trailing `bar` is inaccessible.


Discussion / hints:
No way... This reproduces all the way back to conhost v1 lol. At this point we've found `ReadConsole`'s `dwCtrlWakeupMask` API:
* overwrote the character under the cursor with the control character even though it wasn't ever intended to do that
* allowed you read beyond the end of the initialized buffer
* assumed that 1 char = 1 column despite being added after the W APIs got added
* ✨NEW✨ doesn't even erase the end of the prompt

At this point I finally got to say: Kudos to the people who designed, implemented and reviewed the `CONSOLE_READCONSOLE_CONTROL` API. Whoever you all are, all of you did a. fantastic. job.

Fix patch:
diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp
index a05bed8a46a..4aa458b1cdf 100644
--- a/src/host/readDataCooked.cpp
+++ b/src/host/readDataCooked.cpp
@@ -442,10 +442,17 @@ void COOKED_READ_DATA::_handleChar(wchar_t wch, const DWORD modifiers)
         // It's unclear whether the original intention was to write at the end of the buffer at all times or to implement an insert mode.
         // I went with insert mode.
         //
+        // The old implementation also failed to clear the end of the prompt if you pressed tab in the middle of it.
+        // You can reproduce this issue by launching cmd in an old conhost build and writing "<command that doesn't exist> foo",
+        // moving your cursor to the space past the <command> and pressing tab. Nothing will happen but the "foo" will be inaccessible.
+        // I've now fixed this behavior by adding an additional Replace() before the _flushBuffer() call that removes the tail end.
+        //
         // It is important that we don't actually print that character out though, as it's only for the calling application to see.
         // That's why we flush the contents before the insertion and then ensure that the _flushBuffer() call in Read() exits early.
+        const auto cursor = _buffer.GetCursorPosition();
+        _buffer.Replace(cursor, npos, nullptr, 0);
         _flushBuffer();
-        _buffer.Replace(_buffer.GetCursorPosition(), 0, &wch, 1);
+        _buffer.Replace(cursor, 0, &wch, 1);
         _buffer.MarkAsClean();
 
         _controlKeyState = modifiers;


Test patch: