aboutsummaryrefslogtreecommitdiff
path: root/ttwhy/terminal/echo.cppm
blob: 660cf429a4cd06caa3d4db356fc7836d1d527fab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
module;

#include <asio.hpp>

#include <algorithm>

#include <cstddef>
#include <format>
#include <string_view>

export module ttwhy.terminal:echo;

import :events;
import ttwhy.core;

namespace ttwhy::terminal
{

  using namespace std::string_view_literals;

  constexpr auto vte_backspace_sequence = "\b \b"sv;
  constexpr auto vte_newline_sequence = "\r\n"sv;
  constexpr auto vte_horizontal_tab_sequence = "\t"sv;
  constexpr auto vte_cursor_left_sequence = "\x1b[D"sv;
  constexpr auto vte_cursor_right_sequence = "\x1b[C"sv;
  constexpr auto vte_delete_character_sequence = "\x1b[P"sv;
  constexpr auto vte_cursor_left_sequence_format = "\x1b[{}D"sv;
  constexpr auto vte_cursor_right_sequence_format = "\x1b[{}C"sv;

  export template<typename StreamType>
  struct echo
  {
    explicit echo(StreamType & stream)
        : m_output_stream{stream}
    {}

    [[nodiscard]] auto cursor_position() const noexcept -> std::size_t
    {
      return m_cursor_position;
    }

    [[nodiscard]] auto max_position() const noexcept -> std::size_t
    {
      return m_max_position;
    }

    auto process(input_event event) -> asio::awaitable<void>
    {
      co_await std::visit(
          [&](auto && event) -> asio::awaitable<void> {
            using event_type = std::decay_t<decltype(event)>;

            if constexpr (std::same_as<character_event, event_type>)
            {
              co_await process_character(event.value);
            }
            else if constexpr (std::same_as<control_event, event_type>)
            {
              co_await process_control_key(event.key);
            }
            else if constexpr (std::same_as<navigation_event, event_type>)
            {
              co_await process_navigation_key(event);
            }
          },
          event);
    }

  private:
    auto process_character(char character) -> asio::awaitable<void>
    {
      auto [error, bytes_written] =
          co_await asio::async_write(m_output_stream, asio::buffer(&character, 1), asio::as_tuple(asio::use_awaitable));
      if (!error)
      {
        m_cursor_position += bytes_written;
        m_max_position = std::max(m_max_position, m_cursor_position);
      }
    }

    auto process_control_key(control_key key) -> asio::awaitable<void>
    {
      switch (key)
      {
        case control_key::backspace:
          if (m_cursor_position > 0)
          {
            auto [error, bytes_written] = co_await asio::async_write(
                m_output_stream, asio::buffer(vte_backspace_sequence), asio::as_tuple(asio::use_awaitable));
            if (!error)
            {
              if (m_cursor_position == m_max_position)
              {
                --m_max_position;
              }
              --m_cursor_position;
            }
          }
          break;
        case control_key::enter:
        {
          auto [error, bytes_written] = co_await asio::async_write(m_output_stream, asio::buffer(vte_newline_sequence),
                                                                   asio::as_tuple(asio::use_awaitable));
          if (!error)
          {
            m_cursor_position = 0;
            m_max_position = 0;
          }
          break;
        }
        case control_key::tab:
        {
          auto [error, bytes_written] = co_await asio::async_write(
              m_output_stream, asio::buffer(vte_horizontal_tab_sequence), asio::as_tuple(asio::use_awaitable));
          if (!error)
          {
            auto const tab_stop = 8uz - (m_cursor_position % 8uz);
            m_cursor_position += tab_stop;
            m_max_position = std::max(m_max_position, m_cursor_position);
          }
          break;
        }
        default:
          co_await asio::async_write(m_output_stream, asio::buffer(std::format("{{CTRL:{}}}", std::to_underlying(key))),
                                     asio::as_tuple(asio::use_awaitable));
          break;
      };
    }

    auto process_navigation_key(navigation_event event) -> asio::awaitable<void>
    {
      switch (event.key)
      {
        case navigation_key::left:
          if (m_cursor_position > 0)
          {
            auto [error, bytes_written] = co_await asio::async_write(
                m_output_stream, asio::buffer(vte_cursor_left_sequence), asio::as_tuple(asio::use_awaitable));
            if (!error)
            {
              --m_cursor_position;
            }
          }
          break;

        case navigation_key::right:
          if (m_cursor_position < m_max_position)
          {
            auto [error, bytes_written] = co_await asio::async_write(
                m_output_stream, asio::buffer(vte_cursor_right_sequence), asio::as_tuple(asio::use_awaitable));
            if (!error)
            {
              ++m_cursor_position;
            }
          }
          break;

        case navigation_key::home:
          if (m_cursor_position > 0)
          {
            auto sequence = std::format(vte_cursor_left_sequence_format, m_cursor_position);
            auto [error, bytes_written] = co_await asio::async_write(m_output_stream, asio::buffer(sequence),
                                                                     asio::as_tuple(asio::use_awaitable));
            if (!error)
            {
              m_cursor_position = 0;
            }
          }
          break;

        case navigation_key::end:
          if (m_cursor_position < m_max_position)
          {
            auto const distance = m_max_position - m_cursor_position;
            auto sequence = std::format(vte_cursor_right_sequence_format, distance);
            auto [error, bytes_written] = co_await asio::async_write(m_output_stream, asio::buffer(sequence),
                                                                     asio::as_tuple(asio::use_awaitable));
            if (!error)
            {
              m_cursor_position = m_max_position;
            }
          }
          break;

        case navigation_key::delete_key:
          if (m_cursor_position < m_max_position)
          {
            auto [error, bytes_written] = co_await asio::async_write(
                m_output_stream, asio::buffer(vte_delete_character_sequence), asio::as_tuple(asio::use_awaitable));
            if (!error)
            {
              --m_max_position;
            }
          }
          break;

        default:
          break;
      }
    }

    StreamType & m_output_stream;
    std::size_t m_cursor_position{};
    std::size_t m_max_position{};
  };

}  // namespace ttwhy::terminal