Repository: microsoft/terminal
File: src/cascadia/TerminalApp/DebugTapConnection.cpp (C++)

Code before:
    void DebugTapConnection::Close()
    {
        _outputRevoker.revoke();
        _stateChangedRevoker.revoke();
        _wrappedConnection = nullptr;
    }

    ConnectionState DebugTapConnection::State() const noexcept
    {
        if (auto strongConnection{ _wrappedConnection.get() })
        {
            return strongConnection.State();
        }
        return ConnectionState::Failed;
    }

    void DebugTapConnection::_OutputHandler(const hstring str)
    {
        _TerminalOutputHandlers(til::visualize_control_codes(str));
    }

    // Called by the DebugInputTapConnection to print user input
    void DebugTapConnection::_PrintInput(const hstring& str)
    {
        auto clean{ til::visualize_control_codes(str) };
        auto formatted{ wil::str_printf<std::wstring>(L"\x1b[91m%ls\x1b[m", clean.data()) };
        _TerminalOutputHandlers(formatted);
    }

    // Wire us up so that we can forward input through
    void DebugTapConnection::SetInputTap(const Microsoft::Terminal::TerminalConnection::ITerminalConnection& inputTap)
    {
        _inputSide = inputTap;
    }
}

// Function Description
// - Takes one connection and returns two connections:
//   1. One that can be used in place of the original connection (wrapped)
//   2. One that will print raw VT sequences sent into and received _from_ the original connection.
std::tuple<ITerminalConnection, ITerminalConnection> OpenDebugTapConnection(ITerminalConnection baseConnection)
{
    using namespace winrt::Microsoft::TerminalApp::implementation;
    auto debugSide{ winrt::make_self<DebugTapConnection>(baseConnection) };
    auto inputSide{ winrt::make_self<DebugInputTapConnection>(debugSide, baseConnection) };
    debugSide->SetInputTap(*inputSide);
    std::tuple<ITerminalConnection, ITerminalConnection> p{ *inputSide, *debugSide };
    return p;
}


Review comment (performance):
I'm fine with this.

Although I'd generally prefer it if `til::visualize_control_codes` was decomposed, so that we can "recompose" it back into the two behaviors we need (namely newline preserving and not). Something like that:
```cpp
namespace til
{
    constexpr wchar_t visualize_control_code(wchar_t ch) noexcept
    {
        if (ch < 0x20)
        {
            ch += 0x2400;
        }
        else if (ch == 0x20)
        {
            ch = 0x2423; // replace space with ␣
        }
        else if (ch == 0x7f)
        {
            ch = 0x2421; // replace del with ␡
        }
        return ch;
    }

    _TIL_INLINEPREFIX std::wstring visualize_control_codes(std::wstring str) noexcept
    {
        for (auto& ch : str)
        {
            ch = visualize_control_code(ch);
        }
        return str;
    }

    _TIL_INLINEPREFIX std::wstring visualize_control_codes_with_newlines(std::wstring_view str) noexcept
    {
        std::wstring out;
        out.reserve(str.size());

        wchar_t buffer[3]{ L'\0', L'\r', L'\n' };

        for (const auto& ch : str)
        {
            buffer[0] = visualize_control_code(ch);
            const auto length = buffer[0] != 0x240A ? 1 : 3;
            out.append(&buffer[0], length);
        }

        return out;
    }
}
```

(I haven't tested, nor commented it, but I think it should work. The main benefit is that it doesn't need to reshuffle the string so that it's only a single `O(n)` loop.)

Code after:
    void DebugTapConnection::Close()
    {
        _outputRevoker.revoke();
        _stateChangedRevoker.revoke();
        _wrappedConnection = nullptr;
    }

    ConnectionState DebugTapConnection::State() const noexcept
    {
        if (auto strongConnection{ _wrappedConnection.get() })
        {
            return strongConnection.State();
        }
        return ConnectionState::Failed;
    }

    void DebugTapConnection::_OutputHandler(const hstring 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");
        }
        _TerminalOutputHandlers(output);
    }

    // Called by the DebugInputTapConnection to print user input
    void DebugTapConnection::_PrintInput(const hstring& str)
    {
        auto clean{ til::visualize_control_codes(str) };
        auto formatted{ wil::str_printf<std::wstring>(L"\x1b[91m%ls\x1b[m", clean.data()) };
        _TerminalOutputHandlers(formatted);
    }

    // Wire us up so that we can forward input through
    void DebugTapConnection::SetInputTap(const Microsoft::Terminal::TerminalConnection::ITerminalConnection& inputTap)
    {
        _inputSide = inputTap;
    }
}

// Function Description
// - Takes one connection and returns two connections:
//   1. One that can be used in place of the original connection (wrapped)
//   2. One that will print raw VT sequences sent into and received _from_ the original connection.
std::tuple<ITerminalConnection, ITerminalConnection> OpenDebugTapConnection(ITerminalConnection baseConnection)
{
    using namespace winrt::Microsoft::TerminalApp::implementation;