1 /**
2  * Copyright (c) 2017, 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 ON
25  * 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 attr_line.hh
30  */
31 
32 #ifndef attr_line_hh
33 #define attr_line_hh
34 
35 #include <limits.h>
36 
37 #include <string>
38 #include <vector>
39 
40 #include "base/lnav_log.hh"
41 #include "base/string_util.hh"
42 #include "base/intern_string.hh"
43 #include "string_attr_type.hh"
44 
45 /**
46  * Encapsulates a range in a string.
47  */
48 struct line_range {
49     int lr_start;
50     int lr_end;
51 
line_rangeline_range52     explicit line_range(int start = -1, int end = -1) : lr_start(start), lr_end(end) { };
53 
is_validline_range54     bool is_valid() const {
55         return this->lr_start != -1;
56     }
57 
lengthline_range58     int length() const
59     {
60         return this->lr_end == -1 ? INT_MAX : this->lr_end - this->lr_start;
61     };
62 
containsline_range63     bool contains(int pos) const {
64         return this->lr_start <= pos && pos < this->lr_end;
65     };
66 
containsline_range67     bool contains(const struct line_range &other) const {
68         return this->contains(other.lr_start) && other.lr_end <= this->lr_end;
69     };
70 
intersectsline_range71     bool intersects(const struct line_range &other) const {
72         return this->contains(other.lr_start) || this->contains(other.lr_end);
73     };
74 
intersectionline_range75     line_range intersection(const struct line_range &other) const {
76         int actual_end;
77 
78         if (this->lr_end == -1) {
79             actual_end = other.lr_end;
80         } else if (other.lr_end == -1) {
81             actual_end = this->lr_end;
82         } else {
83             actual_end = std::min(this->lr_end, other.lr_end);
84         }
85         return line_range{std::max(this->lr_start, other.lr_start), actual_end};
86     };
87 
shiftline_range88     line_range &shift(int32_t start, int32_t amount) {
89         if (this->lr_start >= start) {
90             this->lr_start = std::max(0, this->lr_start + amount);
91         }
92         if (this->lr_end != -1 && start <= this->lr_end) {
93             this->lr_end += amount;
94             if (this->lr_end < this->lr_start) {
95                 this->lr_end = this->lr_start;
96             }
97         }
98 
99         return *this;
100     };
101 
ltrimline_range102     void ltrim(const char *str) {
103         while (this->lr_start < this->lr_end && isspace(str[this->lr_start])) {
104             this->lr_start += 1;
105         }
106     };
107 
operator <line_range108     bool operator<(const struct line_range &rhs) const
109     {
110         if (this->lr_start < rhs.lr_start) { return true; }
111         else if (this->lr_start > rhs.lr_start) { return false; }
112 
113         if (this->lr_end == rhs.lr_end) { return false; }
114 
115         if (this->lr_end < rhs.lr_end) { return true; }
116         return false;
117     };
118 
operator ==line_range119     bool operator==(const struct line_range &rhs) const {
120         return (this->lr_start == rhs.lr_start && this->lr_end == rhs.lr_end);
121     };
122 
substrline_range123     const char *substr(const std::string &str) const {
124         if (this->lr_start == -1) {
125             return str.c_str();
126         }
127         return &(str.c_str()[this->lr_start]);
128     }
129 
sublenline_range130     size_t sublen(const std::string &str) const {
131         if (this->lr_start == -1) {
132             return str.length();
133         }
134         if (this->lr_end == -1) {
135             return str.length() - this->lr_start;
136         }
137         return this->length();
138     }
139 };
140 
141 /**
142  * Container for attribute values for a substring.
143  */
144 typedef union {
145     const void *sav_ptr;
146     int64_t sav_int;
147 } string_attr_value_t;
148 
149 struct string_attr {
string_attrstring_attr150     string_attr(const struct line_range &lr, string_attr_type_t type, void *val)
151         : sa_range(lr), sa_type(type) {
152         require(lr.is_valid());
153         require(type);
154         this->sa_value.sav_ptr = val;
155     };
156 
string_attrstring_attr157     string_attr(const struct line_range &lr, string_attr_type_t type, std::string val)
158         : sa_range(lr), sa_type(type), sa_str_value(std::move(val)) {
159         require(lr.is_valid());
160         require(type);
161     };
162 
string_attrstring_attr163     string_attr(const struct line_range &lr, string_attr_type_t type, intern_string_t val)
164         : sa_range(lr), sa_type(type) {
165         require(lr.is_valid());
166         require(type);
167         this->sa_value.sav_ptr = val.unwrap();
168     };
169 
string_attrstring_attr170     string_attr(const struct line_range &lr, string_attr_type_t type, int64_t val = 0)
171         : sa_range(lr), sa_type(type) {
172         require(lr.is_valid());
173         require(type);
174         this->sa_value.sav_int = val;
175     };
176 
string_attrstring_attr177     string_attr(const struct line_range &lr, string_attr_type_t type, string_attr_value_t val)
178         : sa_range(lr), sa_type(type), sa_value(val) {
179         require(lr.is_valid());
180         require(type);
181     };
182 
string_attrstring_attr183     string_attr() : sa_type(nullptr) { };
184 
operator <string_attr185     bool operator<(const struct string_attr &rhs) const
186     {
187         return this->sa_range < rhs.sa_range;
188     };
189 
to_stringstring_attr190     intern_string_t to_string() const {
191         return intern_string_t((const intern_string *) this->sa_value.sav_ptr);
192     };
193 
194     struct line_range sa_range;
195     string_attr_type_t sa_type;
196     string_attr_value_t sa_value;
197     std::string sa_str_value;
198 };
199 
200 /** A map of line ranges to attributes for that range. */
201 typedef std::vector<string_attr> string_attrs_t;
202 
203 inline string_attrs_t::const_iterator
find_string_attr(const string_attrs_t & sa,string_attr_type_t type,int start=0)204 find_string_attr(const string_attrs_t &sa, string_attr_type_t type, int start = 0)
205 {
206     string_attrs_t::const_iterator iter;
207 
208     for (iter = sa.begin(); iter != sa.end(); ++iter) {
209         if (iter->sa_type == type && iter->sa_range.lr_start >= start) {
210             break;
211         }
212     }
213 
214     return iter;
215 }
216 
217 inline nonstd::optional<const string_attr*>
get_string_attr(const string_attrs_t & sa,string_attr_type_t type,int start=0)218 get_string_attr(const string_attrs_t &sa, string_attr_type_t type, int start = 0)
219 {
220     auto iter = find_string_attr(sa, type, start);
221 
222     if (iter == sa.end()) {
223         return nonstd::nullopt;
224     }
225 
226     return nonstd::make_optional(&(*iter));
227 }
228 
229 template<typename T>
230 inline string_attrs_t::const_iterator
find_string_attr_containing(const string_attrs_t & sa,string_attr_type_t type,T x)231 find_string_attr_containing(const string_attrs_t &sa, string_attr_type_t type, T x)
232 {
233     string_attrs_t::const_iterator iter;
234 
235     for (iter = sa.begin(); iter != sa.end(); ++iter) {
236         if (iter->sa_type == type && iter->sa_range.contains(x)) {
237             break;
238         }
239     }
240 
241     return iter;
242 }
243 
244 inline string_attrs_t::iterator
find_string_attr(string_attrs_t & sa,const struct line_range & lr)245 find_string_attr(string_attrs_t &sa, const struct line_range &lr)
246 {
247     string_attrs_t::iterator iter;
248 
249     for (iter = sa.begin(); iter != sa.end(); ++iter) {
250         if (lr.contains(iter->sa_range)) {
251             break;
252         }
253     }
254 
255     return iter;
256 }
257 
258 inline string_attrs_t::const_iterator
find_string_attr(const string_attrs_t & sa,size_t near)259 find_string_attr(const string_attrs_t &sa, size_t near)
260 {
261     string_attrs_t::const_iterator iter, nearest = sa.end();
262     ssize_t last_diff = INT_MAX;
263 
264     for (iter = sa.begin(); iter != sa.end(); ++iter) {
265         auto &lr = iter->sa_range;
266 
267         if (!lr.is_valid() || !lr.contains(near)) {
268             continue;
269         }
270 
271         ssize_t diff = near - lr.lr_start;
272         if (diff < last_diff) {
273             last_diff = diff;
274             nearest = iter;
275         }
276     }
277 
278     return nearest;
279 }
280 
281 template<typename T>
282 inline string_attrs_t::const_iterator
rfind_string_attr_if(const string_attrs_t & sa,ssize_t near,T predicate)283 rfind_string_attr_if(const string_attrs_t &sa, ssize_t near, T predicate)
284 {
285     string_attrs_t::const_iterator iter, nearest = sa.end();
286     ssize_t last_diff = INT_MAX;
287 
288     for (iter = sa.begin(); iter != sa.end(); ++iter) {
289         auto &lr = iter->sa_range;
290 
291         if (lr.lr_start > near) {
292             continue;
293         }
294 
295         if (!predicate(*iter)) {
296             continue;
297         }
298 
299         ssize_t diff = near - lr.lr_start;
300         if (diff < last_diff) {
301             last_diff = diff;
302             nearest = iter;
303         }
304     }
305 
306     return nearest;
307 }
308 
309 inline struct line_range
find_string_attr_range(const string_attrs_t & sa,string_attr_type_t type)310 find_string_attr_range(const string_attrs_t &sa, string_attr_type_t type)
311 {
312     auto iter = find_string_attr(sa, type);
313 
314     if (iter != sa.end()) {
315         return iter->sa_range;
316     }
317 
318     return line_range();
319 }
320 
remove_string_attr(string_attrs_t & sa,const struct line_range & lr)321 inline void remove_string_attr(string_attrs_t &sa, const struct line_range &lr)
322 {
323     string_attrs_t::iterator iter;
324 
325     while ((iter = find_string_attr(sa, lr)) != sa.end()) {
326         sa.erase(iter);
327     }
328 }
329 
remove_string_attr(string_attrs_t & sa,string_attr_type_t type)330 inline void remove_string_attr(string_attrs_t &sa, string_attr_type_t type)
331 {
332     for (auto iter = sa.begin(); iter != sa.end();) {
333         if (iter->sa_type == type) {
334             iter = sa.erase(iter);
335         } else {
336             ++iter;
337         }
338     }
339 }
340 
shift_string_attrs(string_attrs_t & sa,int32_t start,int32_t amount)341 inline void shift_string_attrs(string_attrs_t &sa, int32_t start, int32_t amount)
342 {
343     for (auto &iter : sa) {
344         iter.sa_range.shift(start, amount);
345     }
346 }
347 
348 struct text_wrap_settings {
text_wrap_settingstext_wrap_settings349     text_wrap_settings() : tws_indent(2), tws_width(80) {};
350 
with_indenttext_wrap_settings351     text_wrap_settings &with_indent(int indent) {
352         this->tws_indent = indent;
353         return *this;
354     };
355 
with_widthtext_wrap_settings356     text_wrap_settings &with_width(int width) {
357         this->tws_width = width;
358         return *this;
359     };
360 
361     int tws_indent;
362     int tws_width;
363 };
364 
365 /**
366  * A line that has attributes.
367  */
368 class attr_line_t {
369 public:
attr_line_t()370     attr_line_t() {
371         this->al_attrs.reserve(RESERVE_SIZE);
372     };
373 
attr_line_t(std::string str)374     attr_line_t(std::string str) : al_string(std::move(str)) {
375         this->al_attrs.reserve(RESERVE_SIZE);
376     };
377 
attr_line_t(const char * str)378     attr_line_t(const char *str) : al_string(str) {
379         this->al_attrs.reserve(RESERVE_SIZE);
380     };
381 
from_ansi_str(const char * str)382     static inline attr_line_t from_ansi_str(const char *str) {
383         attr_line_t retval;
384 
385         return retval.with_ansi_string("%s", str);
386     };
387 
388     /** @return The string itself. */
get_string()389     std::string &get_string() { return this->al_string; };
390 
get_string() const391     const std::string &get_string() const { return this->al_string; };
392 
393     /** @return The attributes for the string. */
get_attrs()394     string_attrs_t &get_attrs() { return this->al_attrs; };
395 
get_attrs() const396     const string_attrs_t &get_attrs() const { return this->al_attrs; };
397 
with_string(const std::string & str)398     attr_line_t &with_string(const std::string &str) {
399         this->al_string = str;
400         return *this;
401     }
402 
403     attr_line_t &with_ansi_string(const char *str, ...);
404 
405     attr_line_t &with_ansi_string(const std::string &str);
406 
with_attr(const string_attr & sa)407     attr_line_t &with_attr(const string_attr &sa) {
408         this->al_attrs.push_back(sa);
409         return *this;
410     };
411 
ensure_space()412     attr_line_t &ensure_space() {
413         if (!this->al_string.empty() &&
414             this->al_string.back() != ' ' &&
415             this->al_string.back() != '[') {
416             this->append(1, ' ');
417         }
418 
419         return *this;
420     };
421 
422     template<typename S, typename T = void *>
append(S str,string_attr_type_t type=nullptr,T val=T ())423     attr_line_t &append(S str,
424                         string_attr_type_t type = nullptr,
425                         T val = T()) {
426         size_t start_len = this->al_string.length();
427 
428         this->al_string.append(str);
429         if (type != nullptr) {
430             line_range lr{(int) start_len, (int) this->al_string.length()};
431 
432             this->al_attrs.emplace_back(lr, type, val);
433         }
434         return *this;
435     };
436 
append(const char * str,size_t len)437     attr_line_t &append(const char *str, size_t len) {
438         this->al_string.append(str, len);
439 
440         return *this;
441     };
442 
443     attr_line_t &insert(size_t index, const attr_line_t &al, text_wrap_settings *tws = nullptr);
444 
append(const attr_line_t & al,text_wrap_settings * tws=nullptr)445     attr_line_t &append(const attr_line_t &al, text_wrap_settings *tws = nullptr) {
446         return this->insert(this->al_string.length(), al, tws);
447     };
448 
append(size_t len,char c)449     attr_line_t &append(size_t len, char c) {
450         this->al_string.append(len, c);
451         return *this;
452     };
453 
insert(size_t index,size_t len,char c)454     attr_line_t &insert(size_t index, size_t len, char c) {
455         this->al_string.insert(index, len, c);
456 
457         shift_string_attrs(this->al_attrs, index, len);
458 
459         return *this;
460     }
461 
insert(size_t index,const char * str)462     attr_line_t &insert(size_t index, const char *str) {
463         this->al_string.insert(index, str);
464 
465         shift_string_attrs(this->al_attrs, index, strlen(str));
466 
467         return *this;
468     }
469 
erase(size_t pos,size_t len=std::string::npos)470     attr_line_t &erase(size_t pos, size_t len = std::string::npos) {
471         this->al_string.erase(pos, len);
472 
473         shift_string_attrs(this->al_attrs, pos, -((int32_t) len));
474 
475         return *this;
476     };
477 
erase_utf8_chars(size_t start)478     attr_line_t &erase_utf8_chars(size_t start) {
479         auto byte_index = utf8_char_to_byte_index(this->al_string, start);
480         this->erase(byte_index);
481 
482         return *this;
483     };
484 
485     attr_line_t &right_justify(unsigned long width);
486 
length() const487     ssize_t length() const {
488         size_t retval = this->al_string.length();
489 
490         for (const auto &al_attr : this->al_attrs) {
491             retval = std::max(retval, (size_t) al_attr.sa_range.lr_start);
492             if (al_attr.sa_range.lr_end != -1) {
493                 retval = std::max(retval, (size_t) al_attr.sa_range.lr_end);
494             }
495         }
496 
497         return retval;
498     };
499 
get_substring(const line_range & lr) const500     std::string get_substring(const line_range &lr) const {
501         if (!lr.is_valid()) {
502             return "";
503         }
504         return this->al_string.substr(lr.lr_start, lr.length());
505     };
506 
find_attr(size_t near) const507     string_attrs_t::const_iterator find_attr(size_t near) const {
508         near = std::min(near, this->al_string.length() - 1);
509 
510         while (near > 0 && isspace(this->al_string[near])) {
511             near -= 1;
512         }
513 
514         return find_string_attr(this->al_attrs, near);
515     };
516 
empty() const517     bool empty() const {
518         return this->length() == 0;
519     };
520 
521     /** Clear the string and the attributes for the string. */
clear()522     attr_line_t &clear()
523     {
524         this->al_string.clear();
525         this->al_attrs.clear();
526 
527         return *this;
528     };
529 
530     attr_line_t subline(size_t start, size_t len = std::string::npos) const;
531 
532     void split_lines(std::vector<attr_line_t> &lines) const;
533 
534     size_t nearest_text(size_t x) const;
535 
536     void apply_hide();
537 
538 private:
539     const static size_t RESERVE_SIZE = 128;
540 
541     std::string    al_string;
542     string_attrs_t al_attrs;
543 };
544 
545 #endif
546