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

tstack / lnav / 17589970077-2502

09 Sep 2025 05:00PM UTC coverage: 65.196% (-5.0%) from 70.225%
17589970077-2502

push

github

tstack
[format] add fields for source file/line

Knowing the source file/line context in a log
message can help find log messages when using
log2src.

56 of 70 new or added lines in 2 files covered. (80.0%)

13954 existing lines in 210 files now uncovered.

45516 of 69814 relevant lines covered (65.2%)

404154.37 hits per line

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

57.1
/src/base/intern_string.cc
1
/**
2
 * Copyright (c) 2014, Timothy Stack
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 * * Redistributions of source code must retain the above copyright notice, this
10
 * list of conditions and the following disclaimer.
11
 * * Redistributions in binary form must reproduce the above copyright notice,
12
 * this list of conditions and the following disclaimer in the documentation
13
 * and/or other materials provided with the distribution.
14
 * * Neither the name of Timothy Stack nor the names of its contributors
15
 * may be used to endorse or promote products derived from this software
16
 * without specific prior written permission.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 *
29
 * @file intern_string.cc
30
 */
31

32
#include <mutex>
33

34
#include "intern_string.hh"
35

36
#include <string.h>
37

38
#include "config.h"
39
#include "fmt/ostream.h"
40
#include "lnav_log.hh"
41
#include "pcrepp/pcre2pp.hh"
42
#include "unictype.h"
43
#include "uniwidth.h"
44
#include "ww898/cp_utf8.hpp"
45
#include "xxHash/xxhash.h"
46

47
const static int TABLE_SIZE = 4095;
48

49
struct intern_string::intern_table {
50
    ~intern_table()
1,092✔
51
    {
52
        for (auto is : this->it_table) {
4,472,832✔
53
            auto curr = is;
4,471,740✔
54

55
            while (curr != nullptr) {
7,137,689✔
56
                auto next = curr->is_next;
2,665,949✔
57

58
                delete curr;
2,665,949✔
59
                curr = next;
2,665,949✔
60
            }
61
        }
62
    }
1,092✔
63

64
    intern_string* it_table[TABLE_SIZE];
65
};
66

67
intern_table_lifetime
68
intern_string::get_table_lifetime()
14,323,220✔
69
{
70
    static intern_table_lifetime retval = std::make_shared<intern_table>();
14,323,220✔
71

72
    return retval;
14,323,220✔
73
}
74

75
unsigned long
76
hash_str(const char* str, size_t len)
15,963,203✔
77
{
78
    return XXH3_64bits(str, len);
15,963,203✔
79
}
80

81
const intern_string*
82
intern_string::lookup(const char* str, ssize_t len) noexcept
14,316,379✔
83
{
84
    unsigned long h;
85
    intern_string* curr;
86

87
    if (len == -1) {
14,316,379✔
88
        len = strlen(str);
144,004✔
89
    }
90
    h = hash_str(str, len) % TABLE_SIZE;
14,316,379✔
91

92
    {
93
        static std::mutex table_mutex;
94

95
        std::lock_guard<std::mutex> lk(table_mutex);
14,316,379✔
96
        auto tab = get_table_lifetime();
14,316,379✔
97

98
        curr = tab->it_table[h];
14,316,379✔
99
        while (curr != nullptr) {
16,824,975✔
100
            if (static_cast<ssize_t>(curr->is_str.size()) == len
14,159,026✔
101
                && strncmp(curr->is_str.c_str(), str, len) == 0)
14,159,026✔
102
            {
103
                return curr;
11,650,430✔
104
            }
105
            curr = curr->is_next;
2,508,596✔
106
        }
107

108
        curr = new intern_string(str, len);
2,665,949✔
109
        curr->is_next = tab->it_table[h];
2,665,949✔
110
        tab->it_table[h] = curr;
2,665,949✔
111

112
        return curr;
2,665,949✔
113
    }
14,316,379✔
114
}
115

116
const intern_string*
117
intern_string::lookup(const string_fragment& sf) noexcept
5,968,106✔
118
{
119
    return lookup(sf.data(), sf.length());
5,968,106✔
120
}
121

122
const intern_string*
123
intern_string::lookup(const std::string& str) noexcept
4,619,776✔
124
{
125
    return lookup(str.c_str(), str.size());
4,619,776✔
126
}
127

128
bool
UNCOV
129
intern_string::startswith(const char* prefix) const
×
130
{
131
    const char* curr = this->is_str.data();
×
132

UNCOV
133
    while (*prefix != '\0' && *prefix == *curr) {
×
UNCOV
134
        prefix += 1;
×
135
        curr += 1;
×
136
    }
137

UNCOV
138
    return *prefix == '\0';
×
139
}
140

141
string_fragment
142
string_fragment::trim(const char* tokens) const
31,181✔
143
{
144
    string_fragment retval = *this;
31,181✔
145

146
    while (retval.sf_begin < retval.sf_end) {
55,389✔
147
        bool found = false;
55,229✔
148

149
        for (int lpc = 0; tokens[lpc] != '\0'; lpc++) {
175,263✔
150
            if (retval.sf_string[retval.sf_begin] == tokens[lpc]) {
144,242✔
151
                found = true;
24,208✔
152
                break;
24,208✔
153
            }
154
        }
155
        if (!found) {
55,229✔
156
            break;
31,021✔
157
        }
158

159
        retval.sf_begin += 1;
24,208✔
160
    }
161
    while (retval.sf_begin < retval.sf_end) {
32,073✔
162
        bool found = false;
31,913✔
163

164
        for (int lpc = 0; tokens[lpc] != '\0'; lpc++) {
153,827✔
165
            if (retval.sf_string[retval.sf_end - 1] == tokens[lpc]) {
122,806✔
166
                found = true;
892✔
167
                break;
892✔
168
            }
169
        }
170
        if (!found) {
31,913✔
171
            break;
31,021✔
172
        }
173

174
        retval.sf_end -= 1;
892✔
175
    }
176

177
    return retval;
31,181✔
178
}
179

180
string_fragment
181
string_fragment::trim() const
27,000✔
182
{
183
    return this->trim(" \t\r\n");
27,000✔
184
}
185

186
string_fragment
187
string_fragment::rtrim(const char* tokens) const
1,494✔
188
{
189
    string_fragment retval = *this;
1,494✔
190

191
    while (retval.sf_begin < retval.sf_end) {
2,958✔
192
        bool found = false;
2,952✔
193

194
        for (int lpc = 0; tokens[lpc] != '\0'; lpc++) {
4,440✔
195
            if (retval.sf_string[retval.sf_end - 1] == tokens[lpc]) {
2,952✔
196
                found = true;
1,464✔
197
                break;
1,464✔
198
            }
199
        }
200
        if (!found) {
2,952✔
201
            break;
1,488✔
202
        }
203

204
        retval.sf_end -= 1;
1,464✔
205
    }
206

207
    return retval;
1,494✔
208
}
209

210
std::optional<int>
211
string_fragment::rfind(char ch) const
×
212
{
UNCOV
213
    if (this->empty()) {
×
UNCOV
214
        return std::nullopt;
×
215
    }
216

UNCOV
217
    for (auto index = this->sf_end - 1; index >= this->sf_begin; index--) {
×
UNCOV
218
        if (this->sf_string[index] == ch) {
×
UNCOV
219
            return index;
×
220
        }
221
    }
222

UNCOV
223
    return std::nullopt;
×
224
}
225

226
std::optional<string_fragment>
227
string_fragment::consume_n(int amount) const
411✔
228
{
229
    if (amount > this->length()) {
411✔
UNCOV
230
        return std::nullopt;
×
231
    }
232

233
    return string_fragment{
411✔
234
        this->sf_string,
411✔
235
        this->sf_begin + amount,
411✔
236
        this->sf_end,
411✔
237
    };
411✔
238
}
239

240
string_fragment::split_result
241
string_fragment::split_n(int amount) const
247,923✔
242
{
243
    if (amount > this->length()) {
247,923✔
UNCOV
244
        return std::nullopt;
×
245
    }
246

247
    return std::make_pair(
247,923✔
UNCOV
248
        string_fragment{
×
249
            this->sf_string,
247,923✔
250
            this->sf_begin,
247,923✔
251
            this->sf_begin + amount,
247,923✔
252
        },
253
        string_fragment{
247,923✔
254
            this->sf_string,
247,923✔
255
            this->sf_begin + amount,
247,923✔
256
            this->sf_end,
247,923✔
257
        });
247,923✔
258
}
259

260
std::vector<string_fragment>
261
string_fragment::split_lines() const
330,079✔
262
{
263
    std::vector<string_fragment> retval;
330,079✔
264
    int start = this->sf_begin;
330,079✔
265

266
    for (auto index = start; index < this->sf_end; index++) {
51,931,448✔
267
        if (this->sf_string[index] == '\n') {
51,601,369✔
268
            retval.emplace_back(this->sf_string, start, index + 1);
21,654✔
269
            start = index + 1;
21,654✔
270
        }
271
    }
272
    if (retval.empty() || start < this->sf_end) {
330,079✔
273
        retval.emplace_back(this->sf_string, start, this->sf_end);
329,658✔
274
    }
275

276
    return retval;
660,158✔
UNCOV
277
}
×
278

279
Result<ssize_t, const char*>
UNCOV
280
string_fragment::utf8_length() const
×
281
{
UNCOV
282
    ssize_t retval = 0;
×
283

UNCOV
284
    for (ssize_t byte_index = this->sf_begin; byte_index < this->sf_end;) {
×
UNCOV
285
        auto ch_size = TRY(ww898::utf::utf8::char_size([this, byte_index]() {
×
286
            return std::make_pair(this->sf_string[byte_index],
287
                                  this->sf_end - byte_index);
288
        }));
UNCOV
289
        byte_index += ch_size;
×
UNCOV
290
        retval += 1;
×
291
    }
292

UNCOV
293
    return Ok(retval);
×
294
}
295

296
string_fragment::case_style
297
string_fragment::detect_text_case_style() const
72✔
298
{
299
    static const auto LOWER_RE
300
        = lnav::pcre2pp::code::from_const(R"(^[^A-Z]+$)");
72✔
301
    static const auto UPPER_RE
302
        = lnav::pcre2pp::code::from_const(R"(^[^a-z]+$)");
72✔
303
    static const auto CAMEL_RE
304
        = lnav::pcre2pp::code::from_const(R"(^(?:[A-Z][a-z0-9]+)+$)");
72✔
305

306
    if (LOWER_RE.find_in(*this).ignore_error().has_value()) {
72✔
307
        return case_style::lower;
41✔
308
    }
309
    if (UPPER_RE.find_in(*this).ignore_error().has_value()) {
31✔
310
        return case_style::upper;
3✔
311
    }
312
    if (CAMEL_RE.find_in(*this).ignore_error().has_value()) {
28✔
313
        return case_style::camel;
15✔
314
    }
315

316
    return case_style::mixed;
13✔
317
}
318

319
std::string
320
string_fragment::to_string_with_case_style(case_style style) const
241✔
321
{
322
    std::string retval;
241✔
323

324
    switch (style) {
241✔
325
        case case_style::lower: {
41✔
326
            for (auto ch : *this) {
344✔
327
                retval.append(1, std::tolower(ch));
303✔
328
            }
329
            break;
41✔
330
        }
331
        case case_style::upper: {
172✔
332
            for (auto ch : *this) {
969✔
333
                retval.append(1, std::toupper(ch));
797✔
334
            }
335
            break;
172✔
336
        }
337
        case case_style::camel: {
15✔
338
            retval = this->to_string();
15✔
339
            if (!this->empty()) {
15✔
340
                retval[0] = toupper(retval[0]);
15✔
341
            }
342
            break;
15✔
343
        }
344
        case case_style::mixed: {
13✔
345
            return this->to_string();
13✔
346
        }
347
    }
348

349
    return retval;
228✔
350
}
241✔
351

352
std::string
353
string_fragment::to_unquoted_string() const
1,063✔
354
{
355
    auto sub_sf = *this;
1,063✔
356

357
    if (sub_sf.startswith("r") || sub_sf.startswith("u")) {
1,063✔
358
        sub_sf = sub_sf.consume_n(1).value();
22✔
359
    }
360
    if (sub_sf.length() >= 2
1,063✔
361
        && ((sub_sf.startswith("\"") && sub_sf.endswith("\""))
1,848✔
362
            || (sub_sf.startswith("'") && sub_sf.endswith("'"))))
785✔
363
    {
364
        std::string retval;
294✔
365

366
        sub_sf.sf_begin += 1;
294✔
367
        sub_sf.sf_end -= 1;
294✔
368
        retval.reserve(this->length());
294✔
369

370
        auto in_escape = false;
294✔
371
        for (auto ch : sub_sf) {
2,136✔
372
            if (in_escape) {
1,842✔
UNCOV
373
                switch (ch) {
×
UNCOV
374
                    case 'n':
×
UNCOV
375
                        retval.push_back('\n');
×
UNCOV
376
                        break;
×
UNCOV
377
                    case 't':
×
UNCOV
378
                        retval.push_back('\t');
×
UNCOV
379
                        break;
×
UNCOV
380
                    case 'r':
×
UNCOV
381
                        retval.push_back('\r');
×
UNCOV
382
                        break;
×
UNCOV
383
                    default:
×
UNCOV
384
                        retval.push_back(ch);
×
UNCOV
385
                        break;
×
386
                }
UNCOV
387
                in_escape = false;
×
388
            } else if (ch == '\\') {
1,842✔
389
                in_escape = true;
×
390
            } else {
391
                retval.push_back(ch);
1,842✔
392
            }
393
        }
394

395
        return retval;
294✔
396
    }
294✔
397

398
    return this->to_string();
769✔
399
}
400

401
uint32_t
402
string_fragment::front_codepoint() const
3,643,906✔
403
{
404
    size_t index = 0;
3,643,906✔
405
    auto read_res = ww898::utf::utf8::read(
406
        [this, &index]() { return this->data()[index++]; });
7,287,824✔
407
    if (read_res.isErr()) {
3,643,906✔
UNCOV
408
        return this->data()[0];
×
409
    }
410
    return read_res.unwrap();
3,643,906✔
411
}
3,643,906✔
412

413
Result<ssize_t, const char*>
414
string_fragment::codepoint_to_byte_index(ssize_t cp_index) const
3,652,352✔
415
{
416
    ssize_t retval = 0;
3,652,352✔
417

418
    while (cp_index > 0) {
6,918,135✔
419
        if (retval >= this->length()) {
3,652,352✔
420
            return Err("index is beyond the end of the string");
386,568✔
421
        }
422
        auto ch_len = TRY(ww898::utf::utf8::char_size([this, retval]() {
6,531,568✔
423
            return std::make_pair(this->data()[retval],
424
                                  this->length() - retval - 1);
425
        }));
426

427
        retval += ch_len;
3,265,783✔
428
        cp_index -= 1;
3,265,783✔
429
    }
430

431
    return Ok(retval);
3,265,783✔
432
}
433

434
string_fragment
UNCOV
435
string_fragment::sub_cell_range(int cell_start, int cell_end) const
×
436
{
UNCOV
437
    int byte_index = this->sf_begin;
×
438
    std::optional<int> byte_start;
×
439
    std::optional<int> byte_end;
×
UNCOV
440
    int cell_index = 0;
×
441

442
    while (byte_index < this->sf_end) {
×
443
        if (cell_start == cell_index) {
×
444
            byte_start = byte_index;
×
445
        }
446
        if (!byte_end && cell_index >= cell_end) {
×
447
            byte_end = byte_index;
×
UNCOV
448
            break;
×
449
        }
450
        auto read_res = ww898::utf::utf8::read(
UNCOV
451
            [this, &byte_index]() { return this->sf_string[byte_index++]; });
×
UNCOV
452
        if (read_res.isErr()) {
×
UNCOV
453
            byte_index += 1;
×
454
        } else {
455
            auto ch = read_res.unwrap();
×
456

UNCOV
457
            switch (ch) {
×
458
                case '\t':
×
459
                    do {
UNCOV
460
                        cell_index += 1;
×
UNCOV
461
                    } while (cell_index % 8);
×
462
                    break;
×
463
                default: {
×
UNCOV
464
                    auto wcw_res = uc_width(read_res.unwrap(), "UTF-8");
×
UNCOV
465
                    if (wcw_res < 0) {
×
466
                        wcw_res = 1;
×
467
                    }
UNCOV
468
                    cell_index += wcw_res;
×
UNCOV
469
                    break;
×
470
                }
471
            }
472
        }
473
    }
UNCOV
474
    if (cell_start == cell_index) {
×
UNCOV
475
        byte_start = byte_index;
×
476
    }
UNCOV
477
    if (!byte_end) {
×
UNCOV
478
        byte_end = byte_index;
×
479
    }
480

UNCOV
481
    if (byte_start && byte_end) {
×
UNCOV
482
        return this->sub_range(byte_start.value(), byte_end.value());
×
483
    }
484

UNCOV
485
    return string_fragment{};
×
486
}
487

488
size_t
489
string_fragment::column_to_byte_index(const size_t col) const
270✔
490
{
491
    auto index = this->sf_begin;
270✔
492
    size_t curr_col = 0;
270✔
493

494
    while (curr_col < col && index < this->sf_end) {
540✔
495
        auto read_res = ww898::utf::utf8::read(
496
            [this, &index]() { return this->sf_string[index++]; });
1,318✔
497
        if (read_res.isErr()) {
270✔
UNCOV
498
            curr_col += 1;
×
499
        } else {
500
            auto ch = read_res.unwrap();
270✔
501

502
            switch (ch) {
270✔
UNCOV
503
                case '\t':
×
504
                    do {
UNCOV
505
                        curr_col += 1;
×
UNCOV
506
                    } while (curr_col % 8);
×
UNCOV
507
                    break;
×
508
                default: {
270✔
509
                    auto wcw_res = uc_width(read_res.unwrap(), "UTF-8");
270✔
510
                    if (wcw_res < 0) {
270✔
UNCOV
511
                        wcw_res = 1;
×
512
                    }
513

514
                    curr_col += wcw_res;
270✔
515
                    break;
270✔
516
                }
517
            }
518
        }
519
    }
270✔
520

521
    return index - this->sf_begin;
270✔
522
}
523

524
size_t
UNCOV
525
string_fragment::byte_to_column_index(const size_t byte_index) const
×
526
{
UNCOV
527
    auto index = this->sf_begin;
×
UNCOV
528
    size_t curr_col = 0;
×
529

UNCOV
530
    while (index < this->sf_end && index < (ssize_t) byte_index) {
×
531
        auto read_res = ww898::utf::utf8::read(
UNCOV
532
            [this, &index]() { return this->sf_string[index++]; });
×
UNCOV
533
        if (read_res.isErr()) {
×
UNCOV
534
            curr_col += 1;
×
535
        } else {
UNCOV
536
            auto ch = read_res.unwrap();
×
537

UNCOV
538
            switch (ch) {
×
UNCOV
539
                case '\t':
×
540
                    do {
UNCOV
541
                        curr_col += 1;
×
UNCOV
542
                    } while (curr_col % 8);
×
UNCOV
543
                break;
×
UNCOV
544
                default: {
×
UNCOV
545
                    auto wcw_res = uc_width(read_res.unwrap(), "UTF-8");
×
UNCOV
546
                    if (wcw_res < 0) {
×
UNCOV
547
                        wcw_res = 1;
×
548
                    }
549

UNCOV
550
                    curr_col += wcw_res;
×
UNCOV
551
                    break;
×
552
                }
553
            }
554
        }
555
    }
556

UNCOV
557
    return curr_col;
×
558
}
559

560
static bool
UNCOV
561
iswordbreak(wchar_t wchar)
×
562
{
563
    static constexpr uint32_t mask
564
        = UC_CATEGORY_MASK_L | UC_CATEGORY_MASK_N | UC_CATEGORY_MASK_Pc;
UNCOV
565
    return !uc_is_general_category_withtable(wchar, mask);
×
566
}
567

568
std::optional<int>
UNCOV
569
string_fragment::next_word(const int start_col) const
×
570
{
UNCOV
571
    auto index = this->sf_begin;
×
UNCOV
572
    int curr_col = 0;
×
UNCOV
573
    auto in_word = false;
×
574

UNCOV
575
    while (index < this->sf_end) {
×
576
        auto read_res = ww898::utf::utf8::read(
UNCOV
577
            [this, &index]() { return this->sf_string[index++]; });
×
UNCOV
578
        if (read_res.isErr()) {
×
UNCOV
579
            curr_col += 1;
×
580
        } else {
UNCOV
581
            auto ch = read_res.unwrap();
×
582

UNCOV
583
            switch (ch) {
×
UNCOV
584
                case '\t':
×
585
                    do {
UNCOV
586
                        curr_col += 1;
×
UNCOV
587
                    } while (curr_col % 8);
×
UNCOV
588
                    break;
×
UNCOV
589
                default: {
×
UNCOV
590
                    auto wcw_res = uc_width(read_res.unwrap(), "UTF-8");
×
UNCOV
591
                    if (wcw_res < 0) {
×
UNCOV
592
                        wcw_res = 1;
×
593
                    }
594

UNCOV
595
                    if (curr_col == start_col) {
×
UNCOV
596
                        in_word = !iswordbreak(ch);
×
UNCOV
597
                    } else if (curr_col > start_col) {
×
UNCOV
598
                        if (in_word) {
×
UNCOV
599
                            if (iswordbreak(ch)) {
×
UNCOV
600
                                in_word = false;
×
601
                            }
UNCOV
602
                        } else if (!iswordbreak(ch)) {
×
UNCOV
603
                            return curr_col;
×
604
                        }
605
                    }
UNCOV
606
                    curr_col += wcw_res;
×
UNCOV
607
                    break;
×
608
                }
609
            }
610
        }
611
    }
612

UNCOV
613
    return std::nullopt;
×
614
}
615

616
std::optional<int>
UNCOV
617
string_fragment::prev_word(const int start_col) const
×
618
{
UNCOV
619
    auto index = this->sf_begin;
×
UNCOV
620
    int curr_col = 0;
×
UNCOV
621
    auto in_word = false;
×
UNCOV
622
    std::optional<int> last_word_col;
×
623

UNCOV
624
    while (index < this->sf_end) {
×
625
        auto read_res = ww898::utf::utf8::read(
UNCOV
626
            [this, &index]() { return this->sf_string[index++]; });
×
UNCOV
627
        if (read_res.isErr()) {
×
UNCOV
628
            curr_col += 1;
×
629
        } else {
UNCOV
630
            auto ch = read_res.unwrap();
×
631

UNCOV
632
            switch (ch) {
×
UNCOV
633
                case '\t':
×
634
                    do {
UNCOV
635
                        curr_col += 1;
×
UNCOV
636
                    } while (curr_col % 8);
×
UNCOV
637
                    break;
×
UNCOV
638
                default: {
×
UNCOV
639
                    auto wcw_res = uc_width(read_res.unwrap(), "UTF-8");
×
UNCOV
640
                    if (wcw_res < 0) {
×
UNCOV
641
                        wcw_res = 1;
×
642
                    }
643

UNCOV
644
                    if (curr_col == start_col) {
×
UNCOV
645
                        return last_word_col;
×
646
                    }
UNCOV
647
                    if (iswordbreak(ch)) {
×
UNCOV
648
                        in_word = false;
×
649
                    } else {
UNCOV
650
                        if (!in_word) {
×
UNCOV
651
                            last_word_col = curr_col;
×
652
                        }
UNCOV
653
                        in_word = true;
×
654
                    }
UNCOV
655
                    curr_col += wcw_res;
×
UNCOV
656
                    break;
×
657
                }
658
            }
659
        }
660
    }
661

UNCOV
662
    return last_word_col;
×
663
}
664

665
size_t
666
string_fragment::column_width() const
262,320✔
667
{
668
    auto index = this->sf_begin;
262,320✔
669
    size_t retval = 0;
262,320✔
670

671
    while (index < this->sf_end) {
1,972,542✔
672
        auto read_res = ww898::utf::utf8::read(
673
            [this, &index]() { return this->sf_string[index++]; });
3,473,724✔
674
        if (read_res.isErr()) {
1,710,222✔
675
            retval += 1;
1✔
676
        } else {
677
            auto ch = read_res.unwrap();
1,710,221✔
678

679
            switch (ch) {
1,710,221✔
680
                case '\t':
3,829✔
681
                    do {
682
                        retval += 1;
3,829✔
683
                    } while (retval % 8);
3,829✔
684
                    break;
651✔
685
                default: {
1,709,570✔
686
                    auto wcw_res = uc_width(read_res.unwrap(), "UTF-8");
1,709,570✔
687
                    if (wcw_res < 0) {
1,709,570✔
688
                        wcw_res = 1;
2,466✔
689
                    }
690
                    retval += wcw_res;
1,709,570✔
691
                    break;
1,709,570✔
692
                }
693
            }
694
        }
695
    }
1,710,222✔
696

697
    return retval;
262,320✔
698
}
699

700
struct single_producer : string_fragment_producer {
701
    explicit single_producer(const string_fragment& sf) : sp_frag(sf) {}
1,499✔
702

703
    next_result next() override
958✔
704
    {
705
        auto retval = std::exchange(this->sp_frag, std::nullopt);
958✔
706
        if (retval) {
958✔
707
            return retval.value();
479✔
708
        }
709

710
        return eof{};
479✔
711
    }
712

713
    std::optional<string_fragment> sp_frag;
714
};
715

716
std::unique_ptr<string_fragment_producer>
717
string_fragment_producer::from(string_fragment sf)
1,499✔
718
{
719
    return std::make_unique<single_producer>(sf);
1,499✔
720
}
721

722
std::string
723
string_fragment_producer::to_string()
11,061✔
724
{
725
    auto retval = std::string{};
11,061✔
726

727
    auto for_res = this->for_each(
UNCOV
728
        [&retval](string_fragment sf) -> Result<void, std::string> {
×
729
            retval.append(sf.data(), sf.length());
15,534✔
730
            return Ok();
15,534✔
731
        });
11,061✔
732

733
    return retval;
22,122✔
734
}
11,061✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc