What if the connection wrote its output as a utf-8 array?

This commit is contained in:
Dustin L. Howett 2025-12-05 21:13:30 -06:00
parent 224ac9de47
commit db65c42f2c
12 changed files with 87 additions and 62 deletions

View File

@ -120,17 +120,18 @@ namespace winrt::Microsoft::TerminalApp::implementation
return ConnectionState::Failed;
}
void DebugTapConnection::_OutputHandler(const std::wstring_view str)
void DebugTapConnection::_OutputHandler(const std::string_view str)
{
auto output = til::visualize_control_codes(str);
(void)str;
//auto output = til::visualize_control_codes(str);
// To make the output easier to read, we introduce a line break whenever
// an LF control is encountered. But at this point, the LF would have
// been converted to U+240A (␊), so that's what we need to search for.
for (size_t lfPos = 0; (lfPos = output.find(L'\u240A', lfPos)) != std::wstring::npos;)
{
output.insert(++lfPos, L"\r\n");
}
TerminalOutput.raise(output);
//for (size_t lfPos = 0; (lfPos = output.find(L'\x0A', lfPos)) != std::wstring::npos;)
//{
//output.insert(++lfPos, L"\r\n");
//}
//TerminalOutput.raise(output);
}
// Called by the DebugInputTapConnection to print user input
@ -138,7 +139,8 @@ namespace winrt::Microsoft::TerminalApp::implementation
{
auto clean{ til::visualize_control_codes(str) };
auto formatted{ wil::str_printf<std::wstring>(L"\x1b[91m%ls\x1b[m", clean.data()) };
TerminalOutput.raise(formatted);
(void)formatted;
//TerminalOutput.raise(formatted);
}
// Wire us up so that we can forward input through

View File

@ -31,7 +31,7 @@ namespace winrt::Microsoft::TerminalApp::implementation
private:
void _PrintInput(const std::wstring_view data);
void _OutputHandler(const std::wstring_view str);
void _OutputHandler(const std::string_view str);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection::TerminalOutput_revoker _outputRevoker;
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection::StateChanged_revoker _stateChangedRevoker;

View File

@ -96,7 +96,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
// - str: the string to write.
void AzureConnection::_WriteStringWithNewline(const std::wstring_view str)
{
TerminalOutput.raise(str + L"\r\n");
_dhSend16(str + L"\r\n");
}
// Method description:
@ -112,7 +112,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
catch (const std::exception& runtimeException)
{
// This also catches the AzureException, which has a .what()
TerminalOutput.raise(_colorize(91, til::u8u16(std::string{ runtimeException.what() })));
_dhSend16(_colorize(91, til::u8u16(std::string{ runtimeException.what() })));
}
catch (...)
{
@ -162,13 +162,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
_currentInputMode = mode;
TerminalOutput.raise(L"> \x1b[92m"); // Make prompted user input green
_dhSend16(L"> \x1b[92m"); // Make prompted user input green
_inputEvent.wait(inputLock, [this, mode]() {
return _currentInputMode != mode || _isStateAtOrBeyond(ConnectionState::Closing);
});
TerminalOutput.raise(L"\x1b[m");
_dhSend16(L"\x1b[m");
if (_isStateAtOrBeyond(ConnectionState::Closing))
{
@ -211,19 +211,19 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
if (_userInput.size() > 0)
{
_userInput.pop_back();
TerminalOutput.raise(L"\x08 \x08"); // overstrike the character with a space
_dhSend16(L"\x08 \x08"); // overstrike the character with a space
}
}
else
{
TerminalOutput.raise(data); // echo back
_dhSend16(data); // echo back
switch (_currentInputMode)
{
case InputMode::Line:
if (data.size() > 0 && gsl::at(data, 0) == UNICODE_CARRIAGERETURN)
{
TerminalOutput.raise(L"\r\n"); // we probably got a \r, so we need to advance to the next line.
_dhSend16(L"\r\n"); // we probably got a \r, so we need to advance to the next line.
_currentInputMode = InputMode::None; // toggling the mode indicates completion
_inputEvent.notify_one();
break;
@ -415,21 +415,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
case WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE:
case WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE:
{
const auto result{ til::u8u16(std::string_view{ _buffer.data(), read }, _u16Str, _u8State) };
if (FAILED(result))
{
// EXIT POINT
_transitionToState(ConnectionState::Failed);
return gsl::narrow<DWORD>(result);
}
if (_u16Str.empty())
if (read == 0)
{
continue;
}
// Pass the output to our registered event handlers
TerminalOutput.raise(_u16Str);
TerminalOutput.raise(winrt::array_view{ reinterpret_cast<const uint8_t*>(_buffer.data()), read });
break;
}
case WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE:
@ -772,7 +764,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
const auto shellType = _ParsePreferredShellType(settingsResponse);
_WriteStringWithNewline(RS_(L"AzureRequestingTerminal"));
const auto socketUri = _GetTerminal(shellType);
TerminalOutput.raise(L"\r\n");
_dhSend16(L"\r\n");
//// Step 8: connecting to said terminal
{

View File

@ -103,6 +103,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
std::array<char, 4096> _buffer{};
static winrt::hstring _ParsePreferredShellType(const winrt::Windows::Data::Json::JsonObject& settingsResponse);
template<typename B>
void _dhSend16(B&& b)
{
auto eight = til::u16u8(b);
TerminalOutput.raise(winrt::array_view{ reinterpret_cast<const uint8_t*>(eight.c_str()), static_cast<uint32_t>(eight.size()) });
}
};
}

View File

@ -478,28 +478,28 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
// GH#11556 - make sure to format the error code to this string as an UNSIGNED int
const auto failureText = RS_fmt(L"ProcessFailedToLaunch", _formatStatus(hr), _commandline);
TerminalOutput.raise(failureText);
_dhSend16(failureText);
// If the path was invalid, let's present an informative message to the user
if (hr == HRESULT_FROM_WIN32(ERROR_DIRECTORY))
{
const auto badPathText = RS_fmt(L"BadPathText", _startingDirectory);
TerminalOutput.raise(L"\r\n");
TerminalOutput.raise(badPathText);
_dhSend16(L"\r\n");
_dhSend16(badPathText);
}
// If the requested action requires elevation, display appropriate message
else if (hr == HRESULT_FROM_WIN32(ERROR_ELEVATION_REQUIRED))
{
const auto elevationText = RS_(L"ElevationRequired");
TerminalOutput.raise(L"\r\n");
TerminalOutput.raise(elevationText);
_dhSend16(L"\r\n");
_dhSend16(elevationText);
}
// If the requested executable was not found, display appropriate message
else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
{
const auto fileNotFoundText = RS_(L"FileNotFound");
TerminalOutput.raise(L"\r\n");
TerminalOutput.raise(fileNotFoundText);
_dhSend16(L"\r\n");
_dhSend16(fileNotFoundText);
}
_transitionToState(ConnectionState::Failed);
@ -520,7 +520,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
const auto msg1 = RS_fmt(L"ProcessExited", _formatStatus(status));
const auto msg2 = RS_(L"CtrlDToClose");
const auto msg = fmt::format(FMT_COMPILE(L"\r\n{}\r\n{}\r\n"), msg1, msg2);
TerminalOutput.raise(msg);
_dhSend16(msg);
}
CATCH_LOG();
}
@ -745,11 +745,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
const wil::unique_event overlappedEvent{ CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS) };
OVERLAPPED overlapped{ .hEvent = overlappedEvent.get() };
bool overlappedPending = false;
char buffer[128 * 1024];
DWORD read = 0;
til::u8state u8State;
std::wstring wstr;
char buffer[128 * 1024], buffer2[128*1024];
char* thisBuffer = buffer;
DWORD read = 0;
char* lastBuffer = buffer2;
DWORD lastRead = 0;
// If we use overlapped IO We want to queue ReadFile() calls before processing the
// string, because TerminalOutput.raise() may take a while (relatively speaking).
@ -760,7 +761,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
// When we have a `wstr` that's ready for processing we must do so without blocking.
// Otherwise, whatever the user typed will be delayed until the next IO operation.
// With overlapped IO that's not a problem because the ReadFile() calls won't block.
if (!ReadFile(_pipe.get(), &buffer[0], sizeof(buffer), &read, &overlapped))
if (!ReadFile(_pipe.get(), thisBuffer, sizeof(buffer), &read, &overlapped))
{
if (GetLastError() != ERROR_IO_PENDING)
{
@ -772,7 +773,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
// wstr can be empty in two situations:
// * The previous call to til::u8u16 failed.
// * We're using overlapped IO, and it's the first iteration.
if (!wstr.empty())
if (lastBuffer && lastRead)
{
if (!_receivedFirstByte)
{
@ -792,7 +793,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
try
{
TerminalOutput.raise(wstr);
TerminalOutput.raise(winrt::array_view{ reinterpret_cast<const uint8_t*>(lastBuffer), lastRead });
}
CATCH_LOG();
}
@ -832,8 +833,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
// If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it.
FAILED_LOG(til::u8u16({ &buffer[0], gsl::narrow_cast<size_t>(read) }, wstr, u8State));
std::swap(thisBuffer, lastBuffer);
}
return 0;

View File

@ -103,6 +103,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
} _startupInfo{};
DWORD _OutputThread();
template<typename B>
void _dhSend16(B&& b)
{
auto eight = til::u16u8(b);
TerminalOutput.raise(winrt::array_view{ reinterpret_cast<const uint8_t*>(eight.c_str()), static_cast<uint32_t>(eight.size()) });
}
};
}

View File

@ -34,7 +34,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
prettyPrint << wch;
}
}
TerminalOutput.raise(prettyPrint.str());
(void)prettyPrint;
//TerminalOutput.raise(prettyPrint.str());
}
void EchoConnection::Resize(uint32_t /*rows*/, uint32_t /*columns*/) noexcept

View File

@ -13,7 +13,7 @@ namespace Microsoft.Terminal.TerminalConnection
Failed
};
delegate void TerminalOutputHandler(String output);
delegate void TerminalOutputHandler(UInt8[] output);
interface ITerminalConnection
{

View File

@ -2221,13 +2221,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto noticeArgs = winrt::make<NoticeEventArgs>(NoticeLevel::Info, RS_(L"TermControlReadOnly"));
RaiseNotice.raise(*this, std::move(noticeArgs));
}
void ControlCore::_connectionOutputHandler(const hstring& hstr)
void ControlCore::_connectionOutputHandler(const winrt::array_view<const uint8_t>& data)
{
try
{
std::wstring u16out;
(void)til::u8u16(std::string_view{reinterpret_cast<const char*>(data.data()), data.size()}, u16out, _u8State);
{
const auto lock = _terminal->LockForWriting();
_terminal->Write(hstr);
_terminal->Write(u16out);
}
if (!_pendingResponses.empty())

View File

@ -348,7 +348,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _raiseReadOnlyWarning();
void _updateAntiAliasingMode();
void _connectionOutputHandler(const hstring& hstr);
void _connectionOutputHandler(const winrt::array_view<const uint8_t>& data);
void _connectionStateChangedHandler(const TerminalConnection::ITerminalConnection&, const Windows::Foundation::IInspectable&);
void _updateHoveredCell(const std::optional<til::point> terminalPosition);
void _setOpacity(const float opacity, const bool focused = true);
@ -457,6 +457,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TerminalConnection::ITerminalConnection::StateChanged_revoker _connectionStateChangedRevoker;
TerminalConnection::ITerminalConnection _connection{ nullptr };
til::u8state _u8State;
friend class ControlUnitTests::ControlCoreTests;
friend class ControlUnitTests::ControlInteractivityTests;
bool _inUnitTests{ false };

View File

@ -7,20 +7,20 @@
using namespace ::winrt::Microsoft::Terminal::TerminalConnection;
using namespace ::winrt::Windows::Foundation;
static constexpr std::wstring_view PromptTextPlain{ L"C:\\> " };
static constexpr std::wstring_view PromptTextPowerline{ L"\x1b[49;34m\xe0b6\x1b[1;97;44m C:\\ \x1b[m\x1b[46;34m\xe0b8\x1b[49;36m\xe0b8\x1b[m " };
static constexpr std::u8string_view PromptTextPlain{ u8"C:\\> " };
static constexpr std::u8string_view PromptTextPowerline{ u8"\x1b[49;34m\xe0b6\x1b[1;97;44m C:\\ \x1b[m\x1b[46;34m\xe0b8\x1b[49;36m\xe0b8\x1b[m " };
// clang-format off
static constexpr std::wstring_view PreviewText{
L"\x001b"
L"c" // Hard Reset (RIS); on separate lines to avoid becoming 0x01BC
L"Windows Terminal\r\n"
L"{0}\x1b[93m" L"git\x1b[m diff \x1b[90m-w\x1b[m\r\n"
L"\x1b[1m" L"diff --git a/win b/win\x1b[m\r\n"
L"\x1b[36m@@ -1 +1 @@\x1b[m\r\n"
L"\x1b[31m- Windows Console\x1b[m\r\n"
L"\x1b[32m+ Windows Terminal!\x1b[m\r\n"
L"{0}\x1b[93mWrite-Host \x1b[36m\"\xd83c\xdf2f!\"\x1b[1D\x1b[m"
static constexpr std::u8string_view PreviewText{
u8"\x001b"
u8"c" // Hard Reset (RIS); on separate lines to avoid becoming 0x01BC
u8"Windows Terminal\r\n"
u8"{0}\x1b[93m" u8"git\x1b[m diff \x1b[90m-w\x1b[m\r\n"
u8"\x1b[1m" u8"diff --git a/win b/win\x1b[m\r\n"
u8"\x1b[36m@@ -1 +1 @@\x1b[m\r\n"
u8"\x1b[31m- Windows Console\x1b[m\r\n"
u8"\x1b[32m+ Windows Terminal!\x1b[m\r\n"
u8"{0}\x1b[93mWrite-Host \x1b[36m\"\xd83c\xdf2f!\"\x1b[1D\x1b[m"
};
// clang-format on
@ -31,7 +31,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void PreviewConnection::Start() noexcept
{
// Send the preview text
TerminalOutput.raise(fmt::format(PreviewText, _displayPowerlineGlyphs ? PromptTextPowerline : PromptTextPlain));
auto e = fmt::format(PreviewText, _displayPowerlineGlyphs ? PromptTextPowerline : PromptTextPlain);
//auto e = til::u16u8(s);
TerminalOutput.raise(winrt::array_view{ reinterpret_cast<const uint8_t*>(e.c_str()), static_cast<uint32_t>(e.size()) });
}
void PreviewConnection::Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/) noexcept

View File

@ -81,3 +81,13 @@ std::wstring RS_fmt_impl(std::wstring_view key, Args&&... args)
const auto format = GetLibraryResourceString(key);
return fmt::format(fmt::runtime(std::wstring_view{ format }), std::forward<Args>(args)...);
}
#define RS_A_fmt(x, ...) RS_A_fmt_impl(USES_RESOURCE(x), __VA_ARGS__)
template<typename... Args>
std::string RS_A_fmt_impl(std::wstring_view key, Args&&... args)
{
const auto format = GetLibraryResourceString(key);
auto format8 = til::u16u8(std::wstring_view{ format });
return fmt::format(fmt::runtime(std::string_view{ format8 }), std::forward<Args>(args)...);
}