summaryrefslogtreecommitdiffstats
path: root/src/textview_curses.hh
blob: 51bb4fe6254d940b7c7ddaf74ef732a477699e4b (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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
/**
 * Copyright (c) 2007-2012, Timothy Stack
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * * Neither the name of Timothy Stack nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @file textview_curses.hh
 */

#ifndef textview_curses_hh
#define textview_curses_hh

#include <utility>
#include <vector>

#include "base/func_util.hh"
#include "base/lnav_log.hh"
#include "bookmarks.hh"
#include "breadcrumb.hh"
#include "grep_proc.hh"
#include "highlighter.hh"
#include "listview_curses.hh"
#include "lnav_config_fwd.hh"
#include "logfile_fwd.hh"
#include "ring_span.hh"
#include "text_format.hh"
#include "textview_curses_fwd.hh"

class textview_curses;

using vis_bookmarks = bookmarks<vis_line_t>::type;

class logfile_filter_state {
public:
    logfile_filter_state(std::shared_ptr<logfile> lf = nullptr);

    void clear();

    void clear_filter_state(size_t index);

    void clear_deleted_filter_state(uint32_t used_mask);

    void resize(size_t newsize);

    nonstd::optional<size_t> content_line_to_vis_line(uint32_t line);

    const static int MAX_FILTERS = 32;

    std::shared_ptr<logfile> tfs_logfile;
    size_t tfs_filter_count[MAX_FILTERS];
    int tfs_filter_hits[MAX_FILTERS];
    bool tfs_message_matched[MAX_FILTERS];
    size_t tfs_lines_for_message[MAX_FILTERS];
    bool tfs_last_message_matched[MAX_FILTERS];
    size_t tfs_last_lines_for_message[MAX_FILTERS];
    std::vector<uint32_t> tfs_mask;
    std::vector<uint32_t> tfs_index;
};

enum class filter_lang_t : int {
    NONE,
    REGEX,
    SQL,
};

class text_filter {
public:
    typedef enum {
        MAYBE,
        INCLUDE,
        EXCLUDE,

        LFT__MAX,

        LFT__MASK = (MAYBE | INCLUDE | EXCLUDE)
    } type_t;

    text_filter(type_t type, filter_lang_t lang, std::string id, size_t index)
        : lf_type(type), lf_lang(lang), lf_id(std::move(id)), lf_index(index)
    {
    }
    virtual ~text_filter() = default;

    type_t get_type() const { return this->lf_type; }
    filter_lang_t get_lang() const { return this->lf_lang; }
    void set_type(type_t t) { this->lf_type = t; }
    std::string get_id() const { return this->lf_id; }
    void set_id(std::string id) { this->lf_id = std::move(id); }
    size_t get_index() const { return this->lf_index; }

    bool is_enabled() const { return this->lf_enabled; }
    void enable() { this->lf_enabled = true; }
    void disable() { this->lf_enabled = false; }
    void set_enabled(bool value) { this->lf_enabled = value; }

    void revert_to_last(logfile_filter_state& lfs, size_t rollback_size);

    void add_line(logfile_filter_state& lfs,
                  logfile_const_iterator ll,
                  shared_buffer_ref& line);

    void end_of_message(logfile_filter_state& lfs);

    virtual bool matches(const logfile& lf,
                         logfile_const_iterator ll,
                         shared_buffer_ref& line)
        = 0;

    virtual std::string to_command() const = 0;

    bool operator==(const std::string& rhs) const { return this->lf_id == rhs; }

    bool lf_deleted{false};

protected:
    bool lf_enabled{true};
    type_t lf_type;
    filter_lang_t lf_lang;
    std::string lf_id;
    size_t lf_index;
};

class empty_filter : public text_filter {
public:
    empty_filter(type_t type, size_t index)
        : text_filter(type, filter_lang_t::REGEX, "", index)
    {
    }

    bool matches(const logfile& lf,
                 logfile_const_iterator ll,
                 shared_buffer_ref& line) override;

    std::string to_command() const override;
};

class filter_stack {
public:
    using iterator = std::vector<std::shared_ptr<text_filter>>::iterator;
    using const_iterator
        = std::vector<std::shared_ptr<text_filter>>::const_iterator;
    using value_type = std::shared_ptr<text_filter>;

    explicit filter_stack(size_t reserved = 0) : fs_reserved(reserved) {}

    iterator begin() { return this->fs_filters.begin(); }

    iterator end() { return this->fs_filters.end(); }

    const_iterator begin() const { return this->fs_filters.begin(); }

    const_iterator end() const { return this->fs_filters.end(); }

    size_t size() const { return this->fs_filters.size(); }

    bool empty() const { return this->fs_filters.empty(); };

    bool full() const
    {
        return (this->fs_reserved + this->fs_filters.size())
            == logfile_filter_state::MAX_FILTERS;
    }

    nonstd::optional<size_t> next_index();

    void add_filter(const std::shared_ptr<text_filter>& filter);

    void clear_filters()
    {
        while (!this->fs_filters.empty()) {
            this->fs_filters.pop_back();
        }
    }

    void set_filter_enabled(const std::shared_ptr<text_filter>& filter,
                            bool enabled)
    {
        if (enabled) {
            filter->enable();
        } else {
            filter->disable();
        }
    }

    std::shared_ptr<text_filter> get_filter(const std::string& id);

    bool delete_filter(const std::string& id);

    void get_mask(uint32_t& filter_mask);

    void get_enabled_mask(uint32_t& filter_in_mask, uint32_t& filter_out_mask);

private:
    const size_t fs_reserved;
    std::vector<std::shared_ptr<text_filter>> fs_filters;
};

class text_time_translator {
public:
    virtual ~text_time_translator() = default;

    virtual nonstd::optional<vis_line_t> row_for_time(
        struct timeval time_bucket)
        = 0;

    virtual nonstd::optional<struct timeval> time_for_row(vis_line_t row) = 0;

    void scroll_invoked(textview_curses* tc);

    void data_reloaded(textview_curses* tc);

protected:
    struct timeval ttt_top_time {
        0, 0
    };
};

class text_anchors {
public:
    virtual ~text_anchors() = default;

    static std::string to_anchor_string(const std::string& raw);

    virtual nonstd::optional<vis_line_t> row_for_anchor(const std::string& id)
        = 0;

    virtual nonstd::optional<std::string> anchor_for_row(vis_line_t vl) = 0;

    virtual std::unordered_set<std::string> get_anchors() = 0;
};

class location_history {
public:
    virtual ~location_history() = default;

    virtual void loc_history_append(vis_line_t top) = 0;

    virtual nonstd::optional<vis_line_t> loc_history_back(
        vis_line_t current_top)
        = 0;

    virtual nonstd::optional<vis_line_t> loc_history_forward(
        vis_line_t current_top)
        = 0;

    const static int MAX_SIZE = 100;

protected:
    size_t lh_history_position{0};
};

/**
 * Source for the text to be shown in a textview_curses view.
 */
class text_sub_source {
public:
    virtual ~text_sub_source() = default;

    enum {
        RB_RAW,
        RB_FULL,
        RB_REWRITE,
    };

    enum {
        RF_RAW = (1UL << RB_RAW),
        RF_FULL = (1UL << RB_FULL),
        RF_REWRITE = (1UL << RB_REWRITE),
    };

    typedef long line_flags_t;

    text_sub_source(size_t reserved_filters = 0) : tss_filters(reserved_filters)
    {
    }

    void register_view(textview_curses* tc) { this->tss_view = tc; }

    /**
     * @return The total number of lines available from the source.
     */
    virtual size_t text_line_count() = 0;

    virtual size_t text_line_width(textview_curses& curses) { return INT_MAX; }

    virtual bool text_is_row_selectable(textview_curses& tc, vis_line_t row)
    {
        return true;
    }

    virtual void text_selection_changed(textview_curses& tc) {}

    /**
     * Get the value for a line.
     *
     * @param tc The textview_curses object that is delegating control.
     * @param line The line number to retrieve.
     * @param value_out The string object that should be set to the line
     *   contents.
     * @param raw Indicates that the raw contents of the line should be returned
     *   without any post processing.
     */
    virtual void text_value_for_line(textview_curses& tc,
                                     int line,
                                     std::string& value_out,
                                     line_flags_t flags = 0)
        = 0;

    virtual size_t text_size_for_line(textview_curses& tc,
                                      int line,
                                      line_flags_t raw = 0)
        = 0;

    /**
     * Inform the source that the given line has been marked/unmarked.  This
     * callback function can be used to translate between between visible line
     * numbers and content line numbers.  For example, when viewing a log file
     * with filters being applied, we want the bookmarked lines to be stable
     * across changes in the filters.
     *
     * @param bm    The type of bookmark.
     * @param line  The line that has been marked/unmarked.
     * @param added True if the line was bookmarked and false if it was
     *   unmarked.
     */
    virtual void text_mark(const bookmark_type_t* bm,
                           vis_line_t line,
                           bool added)
    {
    }

    /**
     * Clear the bookmarks for a particular type in the text source.
     *
     * @param bm The type of bookmarks to clear.
     */
    virtual void text_clear_marks(const bookmark_type_t* bm) {}

    /**
     * Get the attributes for a line of text.
     *
     * @param tc The textview_curses object that is delegating control.
     * @param line The line number to retrieve.
     * @param value_out A string_attrs_t object that should be updated with the
     *   attributes for the line.
     */
    virtual void text_attrs_for_line(textview_curses& tc,
                                     int line,
                                     string_attrs_t& value_out)
    {
    }

    /**
     * Update the bookmarks used by the text view based on the bookmarks
     * maintained by the text source.
     *
     * @param bm The bookmarks data structure used by the text view.
     */
    virtual void text_update_marks(vis_bookmarks& bm) {}

    virtual std::string text_source_name(const textview_curses& tv)
    {
        return "";
    }

    filter_stack& get_filters() { return this->tss_filters; }

    virtual void text_filters_changed() {}

    virtual int get_filtered_count() const { return 0; }

    virtual int get_filtered_count_for(size_t filter_index) const { return 0; }

    virtual text_format_t get_text_format() const
    {
        return text_format_t::TF_UNKNOWN;
    }

    virtual nonstd::optional<
        std::pair<grep_proc_source<vis_line_t>*, grep_proc_sink<vis_line_t>*>>
    get_grepper()
    {
        return nonstd::nullopt;
    }

    virtual nonstd::optional<location_history*> get_location_history()
    {
        return nonstd::nullopt;
    }

    void toggle_apply_filters();

    virtual void text_crumbs_for_line(int line,
                                      std::vector<breadcrumb::crumb>& crumbs);

    virtual void quiesce() {}

    bool tss_supports_filtering{false};
    bool tss_apply_filters{true};

protected:
    textview_curses* tss_view{nullptr};
    filter_stack tss_filters;
};

class vis_location_history : public location_history {
public:
    vis_location_history()
        : vlh_history(std::begin(this->vlh_backing),
                      std::end(this->vlh_backing))
    {
    }

    void loc_history_append(vis_line_t top) override;

    nonstd::optional<vis_line_t> loc_history_back(
        vis_line_t current_top) override;

    nonstd::optional<vis_line_t> loc_history_forward(
        vis_line_t current_top) override;

    nonstd::ring_span<vis_line_t> vlh_history;

private:
    vis_line_t current_position()
    {
        auto iter = this->vlh_history.rbegin();

        iter += this->lh_history_position;

        return *iter;
    }

    vis_line_t vlh_backing[MAX_SIZE];
};

class text_delegate {
public:
    virtual ~text_delegate() = default;

    virtual void text_overlay(textview_curses& tc) {}

    virtual bool text_handle_mouse(textview_curses& tc, mouse_event& me)
    {
        return false;
    }
};

/**
 * The textview_curses class adds user bookmarks and searching to the standard
 * list view interface.
 */
class textview_curses
    : public listview_curses
    , public list_data_source
    , public grep_proc_source<vis_line_t>
    , public grep_proc_sink<vis_line_t>
    , public lnav_config_listener {
public:
    using action = std::function<void(textview_curses*)>;

    const static bookmark_type_t BM_USER;
    const static bookmark_type_t BM_USER_EXPR;
    const static bookmark_type_t BM_SEARCH;
    const static bookmark_type_t BM_META;

    textview_curses();

    ~textview_curses();

    void reload_config(error_reporter& reporter);

    void set_paused(bool paused)
    {
        this->tc_paused = paused;
        if (this->tc_state_event_handler) {
            this->tc_state_event_handler(*this);
        }
    }

    bool is_paused() const { return this->tc_paused; }

    vis_bookmarks& get_bookmarks() { return this->tc_bookmarks; }

    const vis_bookmarks& get_bookmarks() const { return this->tc_bookmarks; }

    void toggle_user_mark(const bookmark_type_t* bm,
                          vis_line_t start_line,
                          vis_line_t end_line = vis_line_t(-1));

    void set_user_mark(const bookmark_type_t* bm, vis_line_t vl, bool marked);

    textview_curses& set_sub_source(text_sub_source* src);

    text_sub_source* get_sub_source() const { return this->tc_sub_source; }

    textview_curses& set_delegate(std::shared_ptr<text_delegate> del)
    {
        this->tc_delegate = del;

        return *this;
    }

    std::shared_ptr<text_delegate> get_delegate() const
    {
        return this->tc_delegate;
    }

    nonstd::optional<std::pair<int, int>> horiz_shift(vis_line_t start,
                                                      vis_line_t end,
                                                      int off_start);

    void set_search_action(action sa)
    {
        this->tc_search_action = std::move(sa);
    }

    void grep_end_batch(grep_proc<vis_line_t>& gp);
    void grep_end(grep_proc<vis_line_t>& gp);

    size_t listview_rows(const listview_curses& lv)
    {
        return this->tc_sub_source == nullptr
            ? 0
            : this->tc_sub_source->text_line_count();
    }

    size_t listview_width(const listview_curses& lv)
    {
        return this->tc_sub_source == nullptr
            ? 0
            : this->tc_sub_source->text_line_width(*this);
    }

    void listview_value_for_rows(const listview_curses& lv,
                                 vis_line_t line,
                                 std::vector<attr_line_t>& rows_out);

    void textview_value_for_row(vis_line_t line, attr_line_t& value_out);

    bool listview_is_row_selectable(const listview_curses& lv, vis_line_t row);

    void listview_selection_changed(const listview_curses& lv);

    size_t listview_size_for_row(const listview_curses& lv, vis_line_t row)
    {
        return this->tc_sub_source->text_size_for_line(*this, row);
    }

    std::string listview_source_name(const listview_curses& lv)
    {
        return this->tc_sub_source == nullptr
            ? ""
            : this->tc_sub_source->text_source_name(*this);
    }

    bool grep_value_for_line(vis_line_t line, std::string& value_out);

    void grep_quiesce()
    {
        if (this->tc_sub_source != nullptr) {
            this->tc_sub_source->quiesce();
        }
    }

    void grep_begin(grep_proc<vis_line_t>& gp,
                    vis_line_t start,
                    vis_line_t stop);
    void grep_match(grep_proc<vis_line_t>& gp,
                    vis_line_t line,
                    int start,
                    int end);

    bool is_searching() const { return this->tc_searching > 0; }

    void set_follow_search_for(int64_t ms_to_deadline,
                               std::function<bool()> func)
    {
        struct timeval now, tv;

        tv.tv_sec = ms_to_deadline / 1000;
        tv.tv_usec = (ms_to_deadline % 1000) * 1000;
        gettimeofday(&now, nullptr);
        timeradd(&now, &tv, &this->tc_follow_deadline);
        this->tc_follow_top = this->get_top();
        this->tc_follow_func = func;
    }

    size_t get_match_count() { return this->tc_bookmarks[&BM_SEARCH].size(); }

    void match_reset()
    {
        this->tc_bookmarks[&BM_SEARCH].clear();
        if (this->tc_sub_source != nullptr) {
            this->tc_sub_source->text_clear_marks(&BM_SEARCH);
        }
    }

    highlight_map_t& get_highlights() { return this->tc_highlights; }

    const highlight_map_t& get_highlights() const
    {
        return this->tc_highlights;
    }

    std::set<highlight_source_t>& get_disabled_highlights()
    {
        return this->tc_disabled_highlights;
    }

    bool handle_mouse(mouse_event& me);

    void reload_data();

    void do_update()
    {
        this->listview_curses::do_update();
        if (this->tc_delegate != nullptr) {
            this->tc_delegate->text_overlay(*this);
        }
    }

    bool toggle_hide_fields()
    {
        bool retval = this->tc_hide_fields;

        this->tc_hide_fields = !this->tc_hide_fields;

        return retval;
    }

    bool get_hide_fields() const { return this->tc_hide_fields; }

    void execute_search(const std::string& regex_orig);

    void redo_search();

    void search_range(vis_line_t start, vis_line_t stop = -1_vl)
    {
        if (this->tc_search_child) {
            this->tc_search_child->get_grep_proc()->queue_request(start, stop);
        }
        if (this->tc_source_search_child) {
            this->tc_source_search_child->queue_request(start, stop);
        }
    }

    void search_new_data(vis_line_t start = -1_vl)
    {
        this->search_range(start);
        if (this->tc_search_child) {
            this->tc_search_child->get_grep_proc()->start();
        }
        if (this->tc_source_search_child) {
            this->tc_source_search_child->start();
        }
    }

    std::string get_current_search() const { return this->tc_current_search; }

    void save_current_search()
    {
        this->tc_previous_search = this->tc_current_search;
    }

    void revert_search() { this->execute_search(this->tc_previous_search); }

    void invoke_scroll()
    {
        if (this->tc_sub_source != nullptr) {
            auto ttt = dynamic_cast<text_time_translator*>(this->tc_sub_source);

            if (ttt != nullptr) {
                ttt->scroll_invoked(this);
            }
        }

        listview_curses::invoke_scroll();
    }

    std::function<void(textview_curses&)> tc_state_event_handler;

protected:
    class grep_highlighter {
    public:
        grep_highlighter(std::shared_ptr<grep_proc<vis_line_t>>& gp,
                         highlight_source_t source,
                         std::string hl_name,
                         highlight_map_t& hl_map)
            : gh_grep_proc(std::move(gp)), gh_hl_source(source),
              gh_hl_name(std::move(hl_name)), gh_hl_map(hl_map)
        {
        }

        ~grep_highlighter()
        {
            this->gh_hl_map.erase(
                this->gh_hl_map.find({this->gh_hl_source, this->gh_hl_name}));
        }

        grep_proc<vis_line_t>* get_grep_proc()
        {
            return this->gh_grep_proc.get();
        }

    private:
        std::shared_ptr<grep_proc<vis_line_t>> gh_grep_proc;
        highlight_source_t gh_hl_source;
        std::string gh_hl_name;
        highlight_map_t& gh_hl_map;
    };

    text_sub_source* tc_sub_source{nullptr};
    std::shared_ptr<text_delegate> tc_delegate;

    vis_bookmarks tc_bookmarks;

    int tc_searching{0};
    struct timeval tc_follow_deadline {
        0, 0
    };
    vis_line_t tc_follow_top{-1_vl};
    std::function<bool()> tc_follow_func;
    action tc_search_action;

    highlight_map_t tc_highlights;
    std::set<highlight_source_t> tc_disabled_highlights;

    vis_line_t tc_selection_start{-1_vl};
    vis_line_t tc_selection_last{-1_vl};
    bool tc_selection_cleared{false};
    bool tc_hide_fields{true};
    bool tc_paused{false};

    std::string tc_current_search;
    std::string tc_previous_search;
    std::shared_ptr<grep_highlighter> tc_search_child;
    std::shared_ptr<grep_proc<vis_line_t>> tc_source_search_child;
};

#endif