• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

KazDragon / munin / 30995401802

05 Aug 2026 09:57AM UTC coverage: 98.216% (-0.1%) from 98.322%
30995401802

Pull #284

github

web-flow
Merge 7b6af72b3 into 3b2fdd11f
Pull Request #284: Expose more automation with a component ID field and more complete JSON representations

147 of 151 new or added lines in 14 files covered. (97.35%)

1 existing line in 1 file now uncovered.

2643 of 2691 relevant lines covered (98.22%)

496.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.38
/src/edit.cpp
1
#include "munin/edit.hpp"
2

3
#include "munin/render_surface.hpp"
4

5
#include <boost/range/adaptor/filtered.hpp>
6
#include <terminalpp/algorithm/for_each_in_region.hpp>
7
#include <terminalpp/mouse.hpp>
8
#include <terminalpp/virtual_key.hpp>
9

10
#include <algorithm>
11
#include <memory>
12

13
namespace munin {
14

15
// ==========================================================================
16
// EDIT::IMPLEMENTATION STRUCTURE
17
// ==========================================================================
18
struct edit::impl
19
{
20
    // ======================================================================
21
    // CONSTRUCTOR
22
    // ======================================================================
23
    explicit impl(edit &self) : self_(self)
108✔
24
    {
25
    }
108✔
26

27
    // ======================================================================
28
    // GET_CONTENT
29
    // ======================================================================
30
    [[nodiscard]] terminalpp::string const &get_content() const
260✔
31
    {
32
        return content_;
260✔
33
    }
34

35
    // ======================================================================
36
    // GET_LENGTH
37
    // ======================================================================
38
    [[nodiscard]] edit::text_index get_length() const
922✔
39
    {
40
        return static_cast<edit::text_index>(content_.size());
922✔
41
    }
42

43
    // ======================================================================
44
    // GET_CARET_POSITION
45
    // ======================================================================
46
    [[nodiscard]] edit::text_index get_caret_position() const
119✔
47
    {
48
        return caret_position_;
119✔
49
    }
50

51
    // ======================================================================
52
    // SET_CARET_POSITION
53
    // ======================================================================
54
    void set_caret_position(edit::text_index position)
199✔
55
    {
56
        caret_position_ = std::clamp(position, 0, get_length());
199✔
57
        update_cursor_position();
199✔
58
    }
199✔
59

60
    // ======================================================================
61
    // GET_CURSOR_POSITION
62
    // ======================================================================
63
    [[nodiscard]] terminalpp::point get_cursor_position() const
177✔
64
    {
65
        return cursor_position_;
177✔
66
    }
67

68
    // ======================================================================
69
    // UPDATE_CURSOR_POSITION
70
    // ======================================================================
71
    void update_cursor_position()
302✔
72
    {
73
        cursor_position_ = {
604✔
74
            std::clamp(
302✔
75
                caret_position_, 0, std::max(0, self_.get_size().width_ - 1)),
302✔
76
            0};
77
        self_.on_cursor_position_changed();
302✔
78
    }
302✔
79

80
    // ======================================================================
81
    // SET_TEXT
82
    // ======================================================================
83
    void set_text(terminalpp::string const &text)
4✔
84
    {
85
        auto const old_caret_position = caret_position_;
4✔
86

87
        content_ = text;
4✔
88

89
        auto const new_caret_position = std::min(
4✔
90
            old_caret_position, static_cast<text_index>(content_.size()));
4✔
91

92
        set_caret_position(new_caret_position);
4✔
93

94
        self_.on_preferred_size_changed();
4✔
95

96
        self_.on_redraw({
12✔
97
            {{0, 0}, self_.get_size()}
4✔
98
        });
99
    }
4✔
100

101
    // ======================================================================
102
    // INSERT_TEXT
103
    // ======================================================================
104
    void insert_text(terminalpp::string const &text)
89✔
105
    {
106
        using std::begin;
107
        using std::end;
108

109
        auto const &is_visible_in_edits = [&](auto const &element) {
436✔
110
            auto const &is_control_element = [](auto const &control_element) {
687✔
111
                return control_element.glyph_.character_
112
                    <= terminalpp::detail::ascii::esc;
340✔
113
            };
114

115
            return is_printable(element.glyph_) && !is_control_element(element);
347✔
116
        };
117

118
        auto const old_content_length = get_length();
89✔
119

120
        auto const insertable_text =
121
            text | boost::adaptors::filtered(is_visible_in_edits);
89✔
122

123
        auto const old_caret_position = caret_position_;
89✔
124

125
        content_.insert(
178✔
126
            content_.begin() + caret_position_,
89✔
127
            begin(insertable_text),
89✔
128
            end(insertable_text));
89✔
129

130
        auto const new_content_length = get_length();
89✔
131
        auto const added_content_length =
89✔
132
            new_content_length - old_content_length;
133

134
        set_caret_position(caret_position_ + added_content_length);
89✔
135

136
        // Adding new text causes not only the space under the old cursor to
137
        // be redrawn, but also everything to the right of that as it is shifted
138
        // along one character.
139

140
        // This must then be trimmed to the extents of the space currently taken
141
        // by the edit.
142
        auto const remaining_space =
143
            std::max(self_.get_size().width_ - old_caret_position, 0);
89✔
144

145
        auto const changed_text_length =
146
            std::min(new_content_length - old_caret_position, remaining_space);
89✔
147

148
        self_.on_preferred_size_changed();
89✔
149

150
        self_.on_redraw({
178✔
151
            {{old_caret_position, 0}, {changed_text_length, 1}}
152
        });
153
    }
89✔
154

155
    // ======================================================================
156
    // EVENT
157
    // ======================================================================
158
    void key_event(terminalpp::virtual_key const &vk)
61✔
159
    {
160
        switch (vk.key)
61✔
161
        {
162
            case terminalpp::vk::cursor_left:
5✔
163
                handle_cursor_left();
5✔
164
                break;
5✔
165

166
            case terminalpp::vk::cursor_right:
3✔
167
                handle_cursor_right();
3✔
168
                break;
3✔
169

170
            case terminalpp::vk::home:
8✔
171
                handle_home();
8✔
172
                break;
8✔
173

174
            case terminalpp::vk::end:
8✔
175
                handle_end();
8✔
176
                break;
8✔
177

178
            case terminalpp::vk::bs:
13✔
179
                // Fall-through
180
            case terminalpp::vk::del:
181
                handle_backspace();
13✔
182
                break;
13✔
183

184
            default:
24✔
185
                handle_text(static_cast<char>(vk.key));
24✔
186
                break;
24✔
187
        }
188
    }
61✔
189

190
    // ======================================================================
191
    // MOUSE_EVENT
192
    // ======================================================================
193
    void mouse_event(terminalpp::mouse::event const &mouse)
15✔
194
    {
195
        if (mouse.action_ == terminalpp::mouse::event_type::left_button_down)
15✔
196
        {
197
            set_caret_position(
13✔
198
                static_cast<edit::text_index>(mouse.position_.x_));
13✔
199
            self_.set_focus();
13✔
200
        }
201
    }
15✔
202

203
private:
204
    // ======================================================================
205
    // HANDLE_CURSOR_LEFT
206
    // ======================================================================
207
    void handle_cursor_left()
5✔
208
    {
209
        set_caret_position(caret_position_ - 1);
5✔
210
    }
5✔
211

212
    // ======================================================================
213
    // HANDLE_CURSOR_RIGHT
214
    // ======================================================================
215
    void handle_cursor_right()
3✔
216
    {
217
        set_caret_position(caret_position_ + 1);
3✔
218
    }
3✔
219

220
    // ======================================================================
221
    // HANDLE_HOME
222
    // ======================================================================
223
    void handle_home()
8✔
224
    {
225
        set_caret_position(0);
8✔
226
    }
8✔
227

228
    // ======================================================================
229
    // HANDLE_HOME
230
    // ======================================================================
231
    void handle_end()
8✔
232
    {
233
        set_caret_position(get_length());
8✔
234
    }
8✔
235

236
    // ======================================================================
237
    // HANDLE_BACKSPACE
238
    // ======================================================================
239
    void handle_backspace()
13✔
240
    {
241
        if (caret_position_ != 0)
13✔
242
        {
243
            auto const redraw_amount = (get_length() - caret_position_) + 1;
9✔
244

245
            auto const erased_content =
246
                content_.begin() + (caret_position_ - 1);
9✔
247
            content_.erase(erased_content, erased_content + 1);
9✔
248

249
            set_caret_position(caret_position_ - 1);
9✔
250
            self_.on_preferred_size_changed();
9✔
251

252
            self_.on_redraw({
27✔
253
                {{caret_position_, 0}, {redraw_amount, 1}}
254
            });
255
        }
256
    }
13✔
257

258
    // ======================================================================
259
    // HANDLE_TEXT
260
    // ======================================================================
261
    void handle_text(char ch)
24✔
262
    {
263
        terminalpp::string text;
24✔
264
        text += static_cast<terminalpp::byte>(ch);
24✔
265

266
        insert_text(text);
24✔
267
    }
24✔
268

269
    edit &self_;
270
    terminalpp::string content_;
271
    edit::text_index caret_position_ = 0;
272
    terminalpp::point cursor_position_{0, 0};
273
};
274

275
// ==========================================================================
276
// CONSTRUCTOR
277
// ==========================================================================
278
edit::edit() : pimpl_(std::make_unique<impl>(*this))
108✔
279
{
280
}
108✔
281

282
// ==========================================================================
283
// DESTRUCTOR
284
// ==========================================================================
285
edit::~edit() = default;
108✔
286

287
// ==========================================================================
288
// SET_CARET_POSITION
289
// ==========================================================================
290
void edit::set_caret_position(edit::text_index position)
7✔
291
{
292
    pimpl_->set_caret_position(position);
7✔
293
}
7✔
294

295
// ==========================================================================
296
// GET_CARET_POSITION
297
// ==========================================================================
298
edit::text_index edit::get_caret_position() const
119✔
299
{
300
    return pimpl_->get_caret_position();
119✔
301
}
302

303
// ==========================================================================
304
// GET_LENGTH
305
// ==============s============================================================
306
edit::text_index edit::get_length() const
528✔
307
{
308
    return pimpl_->get_length();
528✔
309
}
310

311
// ==========================================================================
312
// GET_TEXT
313
// ==========================================================================
314
terminalpp::string edit::get_text() const
50✔
315
{
316
    return pimpl_->get_content();
50✔
317
}
318

319
// ==========================================================================
320
// INSERT_TEXT
321
// ==========================================================================
322
void edit::insert_text(terminalpp::string const &text)
65✔
323
{
324
    pimpl_->insert_text(text);
65✔
325
    on_cursor_position_changed();
65✔
326
}
65✔
327

328
// ==========================================================================
329
// SET_TEXT
330
// ==========================================================================
331
void edit::set_text(terminalpp::string const &content)
4✔
332
{
333
    pimpl_->set_text(content);
4✔
334
}
4✔
335

336
// ==========================================================================
337
// DO_SET_SIZE
338
// ==========================================================================
339
void edit::do_set_size(terminalpp::extent const &size)
103✔
340
{
341
    basic_component::do_set_size(size);
103✔
342
    pimpl_->update_cursor_position();
103✔
343
}
103✔
344

345
// ==========================================================================
346
// DO_GET_PREFERRED_SIZE
347
// ==========================================================================
348
terminalpp::extent edit::do_get_preferred_size() const
38✔
349
{
350
    // An edit would prefer to have a width that is the length of the text
351
    // content, plus one space for a cursor to sit at the end.
352
    return terminalpp::extent{get_length() + 1, 1};
38✔
353
}
354

355
// ==========================================================================
356
// DO_GET_CURSOR_STATE
357
// ==========================================================================
358
bool edit::do_get_cursor_state() const
16✔
359
{
360
    return true;
16✔
361
}
362

363
// ==========================================================================
364
// DO_GET_CURSOR_POSITION
365
// ==========================================================================
366
terminalpp::point edit::do_get_cursor_position() const
177✔
367
{
368
    return pimpl_->get_cursor_position();
177✔
369
}
370

371
// ==========================================================================
372
// DO_GET_CURSOR_POSITION
373
// ==========================================================================
374
void edit::do_set_cursor_position(terminalpp::point const &position)
53✔
375
{
376
    // For an edit, setting the cursor position is identical to setting
377
    // the caret to the same location.
378
    pimpl_->set_caret_position(position.x_);
53✔
379
}
53✔
380

381
// ==========================================================================
382
// DO_DRAW
383
// ==========================================================================
384
void edit::do_draw(
168✔
385
    render_surface &surface, terminalpp::rectangle const &region) const
386
{
387
    terminalpp::for_each_in_region(
168✔
388
        surface,
389
        region,
390
        [this, size = get_size()](
168✔
391
            terminalpp::element &elem,
392
            terminalpp::coordinate_type column,  // NOLINT
393
            terminalpp::coordinate_type row) {
394
            if (column < get_length())
487✔
395
            {
396
                elem = pimpl_->get_content()[column];
210✔
397
            }
398
            else if (column < size.width_)
277✔
399
            {
400
                elem = ' ';
277✔
401
            }
402
        });
487✔
403
}
168✔
404

405
// ==========================================================================
406
// DO_EVENT
407
// ==========================================================================
408
void edit::do_event(std::any const &ev)
76✔
409
{
410
    if (auto const *vk = std::any_cast<terminalpp::virtual_key>(&ev);
76✔
411
        vk != nullptr)
412
    {
413
        pimpl_->key_event(*vk);
61✔
414
        return;
61✔
415
    }
416
    else if (auto const *mouse = std::any_cast<terminalpp::mouse::event>(&ev);
15✔
417
             mouse != nullptr)
418
    {
419
        pimpl_->mouse_event(*mouse);
15✔
420
        return;
15✔
421
    }
422
}
423

424
// ==========================================================================
425
// DO_TO_JSON
426
// ==========================================================================
427
nlohmann::json edit::do_to_json() const
11✔
428
{
429
    auto json = basic_component::do_to_json();
11✔
430
    json["type"] = "edit";
11✔
431
    json["text"] = terminalpp::to_string(get_text());
11✔
432
    json["caret_position"] = get_caret_position();
11✔
433
    return json;
11✔
NEW
434
}
×
435

436
// ==========================================================================
437
// MAKE_EDIT
438
// ==========================================================================
439
std::shared_ptr<edit> make_edit()
35✔
440
{
441
    return std::make_shared<edit>();
35✔
442
}
443

444
}  // namespace munin
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc