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