1 /**
2  * Copyright (c) 2018, 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 
30 #include "config.h"
31 
32 #include "lnav.hh"
33 #include "sql_util.hh"
34 #include "pretty_printer.hh"
35 #include "environ_vtab.hh"
36 #include "vtab_module.hh"
37 #include "shlex.hh"
38 #include "help-txt.h"
39 #include "view_helpers.hh"
40 
41 using namespace std;
42 
43 const char *lnav_view_strings[LNV__MAX + 1] = {
44     "log",
45     "text",
46     "help",
47     "histogram",
48     "db",
49     "schema",
50     "pretty",
51     "spectro",
52 
53     nullptr
54 };
55 
view_from_string(const char * name)56 nonstd::optional<lnav_view_t> view_from_string(const char *name)
57 {
58     if (name == nullptr) {
59         return nonstd::nullopt;
60     }
61 
62     auto view_name_iter = find_if(
63         ::begin(lnav_view_strings), ::end(lnav_view_strings),
64         [&](const char *v) {
65             return v != nullptr && strcasecmp(v, name) == 0;
66         });
67 
68     if (view_name_iter == ::end(lnav_view_strings)) {
69         return nonstd::nullopt;
70     }
71 
72     return lnav_view_t(view_name_iter - lnav_view_strings);
73 }
74 
open_schema_view()75 static void open_schema_view()
76 {
77     textview_curses *schema_tc = &lnav_data.ld_views[LNV_SCHEMA];
78     string schema;
79 
80     dump_sqlite_schema(lnav_data.ld_db, schema);
81 
82     schema += "\n\n-- Virtual Table Definitions --\n\n";
83     schema += ENVIRON_CREATE_STMT;
84     schema += vtab_module_schemas;
85     for (const auto &vtab_iter : *lnav_data.ld_vtab_manager) {
86         schema += "\n" + vtab_iter.second->get_table_statement();
87     }
88 
89     delete schema_tc->get_sub_source();
90 
91     auto *pts = new plain_text_source(schema);
92     pts->set_text_format(text_format_t::TF_SQL);
93 
94     schema_tc->set_sub_source(pts);
95     schema_tc->redo_search();
96 }
97 
open_pretty_view()98 static void open_pretty_view()
99 {
100     static const char *NOTHING_MSG =
101         "Nothing to pretty-print";
102 
103     textview_curses *top_tc = *lnav_data.ld_view_stack.top();
104     textview_curses *pretty_tc = &lnav_data.ld_views[LNV_PRETTY];
105     textview_curses *log_tc = &lnav_data.ld_views[LNV_LOG];
106     textview_curses *text_tc = &lnav_data.ld_views[LNV_TEXT];
107     attr_line_t full_text;
108 
109     delete pretty_tc->get_sub_source();
110     pretty_tc->set_sub_source(nullptr);
111     if (top_tc->get_inner_height() == 0) {
112         pretty_tc->set_sub_source(new plain_text_source(NOTHING_MSG));
113         return;
114     }
115 
116     if (top_tc == log_tc) {
117         logfile_sub_source &lss = lnav_data.ld_log_source;
118         bool first_line = true;
119 
120         for (vis_line_t vl = log_tc->get_top(); vl <= log_tc->get_bottom(); ++vl) {
121             content_line_t cl = lss.at(vl);
122             shared_ptr<logfile> lf = lss.find(cl);
123             auto ll = lf->begin() + cl;
124             shared_buffer_ref sbr;
125 
126             if (!first_line && !ll->is_message()) {
127                 continue;
128             }
129             auto ll_start = lf->message_start(ll);
130             attr_line_t al;
131 
132             vl -= vis_line_t(distance(ll_start, ll));
133             lss.text_value_for_line(*log_tc, vl, al.get_string(),
134                                     text_sub_source::RF_FULL|
135                                     text_sub_source::RF_REWRITE);
136             lss.text_attrs_for_line(*log_tc, vl, al.get_attrs());
137             if (log_tc->get_hide_fields()) {
138                 al.apply_hide();
139             }
140 
141             line_range orig_lr = find_string_attr_range(
142                 al.get_attrs(), &SA_ORIGINAL_LINE);
143             attr_line_t orig_al = al.subline(orig_lr.lr_start, orig_lr.length());
144             attr_line_t prefix_al = al.subline(0, orig_lr.lr_start);
145 
146             data_scanner ds(orig_al.get_string());
147             pretty_printer pp(&ds, orig_al.get_attrs());
148             attr_line_t pretty_al;
149             vector<attr_line_t> pretty_lines;
150 
151             // TODO: dump more details of the line in the output.
152             pp.append_to(pretty_al);
153             pretty_al.split_lines(pretty_lines);
154 
155             for (auto &pretty_line : pretty_lines) {
156                 if (pretty_line.empty() && &pretty_line == &pretty_lines.back()) {
157                     break;
158                 }
159                 pretty_line.insert(0, prefix_al);
160                 pretty_line.append("\n");
161                 full_text.append(pretty_line);
162             }
163 
164             first_line = false;
165         }
166 
167         if (!full_text.empty()) {
168             full_text.erase(full_text.length() - 1, 1);
169         }
170     }
171     else if (top_tc == text_tc) {
172         shared_ptr<logfile> lf = lnav_data.ld_text_source.current_file();
173 
174         for (vis_line_t vl = text_tc->get_top(); vl <= text_tc->get_bottom(); ++vl) {
175             auto ll = lf->begin() + vl;
176             shared_buffer_ref sbr;
177 
178             lf->read_full_message(ll, sbr);
179             data_scanner ds(sbr);
180             string_attrs_t sa;
181             pretty_printer pp(&ds, sa);
182 
183             pp.append_to(full_text);
184         }
185     }
186     auto *pts = new plain_text_source();
187     pts->replace_with(full_text);
188     pretty_tc->set_sub_source(pts);
189     if (lnav_data.ld_last_pretty_print_top != log_tc->get_top()) {
190         pretty_tc->set_top(vis_line_t(0));
191     }
192     lnav_data.ld_last_pretty_print_top = log_tc->get_top();
193     pretty_tc->redo_search();
194 }
195 
build_all_help_text()196 static void build_all_help_text()
197 {
198     if (!lnav_data.ld_help_source.empty()) {
199         return;
200     }
201 
202     attr_line_t all_help_text;
203     shlex lexer(help_txt.to_string_fragment());
204     string sub_help_text;
205 
206     lexer.with_ignore_quotes(true)
207         .eval(sub_help_text, lnav_data.ld_exec_context.ec_global_vars);
208     all_help_text.with_ansi_string(sub_help_text);
209 
210     map<string, help_text *> sql_funcs;
211     map<string, help_text *> sql_keywords;
212 
213     for (const auto &iter : sqlite_function_help) {
214         switch (iter.second->ht_context) {
215             case help_context_t::HC_SQL_FUNCTION:
216             case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION:
217                 sql_funcs[iter.second->ht_name] = iter.second;
218                 break;
219             case help_context_t::HC_SQL_KEYWORD:
220                 sql_keywords[iter.second->ht_name] = iter.second;
221                 break;
222             default:
223                 break;
224         }
225     }
226 
227     for (const auto &iter : sql_funcs) {
228         all_help_text.append(2, '\n');
229         format_help_text_for_term(*iter.second, 79, all_help_text);
230         if (!iter.second->ht_example.empty()) {
231             all_help_text.append(1, '\n');
232             format_example_text_for_term(*iter.second, eval_example, 90, all_help_text);
233         }
234     }
235 
236     for (const auto &iter : sql_keywords) {
237         all_help_text.append(2, '\n');
238         format_help_text_for_term(*iter.second, 79, all_help_text);
239         if (!iter.second->ht_example.empty()) {
240             all_help_text.append(1, '\n');
241             format_example_text_for_term(*iter.second, eval_example, 79, all_help_text);
242         }
243     }
244 
245     lnav_data.ld_help_source.replace_with(all_help_text);
246     lnav_data.ld_views[LNV_HELP].redo_search();
247 }
248 
layout_views()249 void layout_views()
250 {
251     unsigned long width, height;
252 
253     getmaxyx(lnav_data.ld_window, height, width);
254     int doc_height;
255     bool doc_side_by_side = width > (90 + 60);
256     bool preview_status_open = !lnav_data.ld_preview_status_source
257                                          .get_description().empty();
258     bool filter_status_open = false;
259 
260     lnav_data.ld_view_stack.top() | [&] (auto tc) {
261         text_sub_source *tss = tc->get_sub_source();
262 
263         if (tss == nullptr) {
264             return;
265         }
266 
267         if (tss->tss_supports_filtering) {
268             filter_status_open = true;
269         }
270     };
271 
272     if (doc_side_by_side) {
273         doc_height = std::max(
274             lnav_data.ld_doc_source.text_line_count(),
275             lnav_data.ld_example_source.text_line_count());
276     } else {
277         doc_height =
278             lnav_data.ld_doc_source.text_line_count() +
279             lnav_data.ld_example_source.text_line_count();
280     }
281 
282     int preview_height = lnav_data.ld_preview_hidden ? 0 :
283                          lnav_data.ld_preview_source.text_line_count();
284     int match_rows = lnav_data.ld_match_source.text_line_count();
285     int match_height = min((unsigned long)match_rows, (height - 4) / 2);
286 
287     lnav_data.ld_match_view.set_height(vis_line_t(match_height));
288 
289     if (doc_height + 14 > ((int) height - match_height - preview_height - 2)) {
290         preview_height = 0;
291         preview_status_open = false;
292     }
293 
294     if (doc_height + 14 > ((int) height - match_height - 2)) {
295         doc_height = lnav_data.ld_doc_source.text_line_count();
296         if (doc_height + 14 > ((int) height - match_height - 2)) {
297             doc_height = 0;
298         }
299     }
300 
301     bool doc_open = doc_height > 0;
302     bool filters_open = (lnav_data.ld_mode == LNM_FILTER ||
303                          lnav_data.ld_mode == LNM_FILES ||
304                          lnav_data.ld_mode == LNM_SEARCH_FILTERS ||
305                          lnav_data.ld_mode == LNM_SEARCH_FILES) &&
306                         !preview_status_open &&
307                         !doc_open;
308     int filter_height = filters_open ? 5 : 0;
309 
310     int bottom_height =
311         (doc_open ? 1 : 0)
312         + doc_height
313         + (preview_status_open ? 1 : 0)
314         + preview_height
315         + 1 // bottom status
316         + match_height
317         + lnav_data.ld_rl_view->get_height();
318 
319     for (auto &tc : lnav_data.ld_views) {
320         tc.set_height(vis_line_t(-(bottom_height
321                                    + (filter_status_open ? 1 : 0)
322                                    + (filters_open ? 1 : 0)
323                                    + filter_height)));
324     }
325     lnav_data.ld_status[LNS_TOP].set_enabled(!filters_open);
326     lnav_data.ld_status[LNS_FILTER].set_visible(filter_status_open);
327     lnav_data.ld_status[LNS_FILTER].set_enabled(filters_open);
328     lnav_data.ld_status[LNS_FILTER].set_top(
329         -(bottom_height + filter_height + 1 + (filters_open ? 1 : 0)));
330     lnav_data.ld_status[LNS_FILTER_HELP].set_visible(filters_open);
331     lnav_data.ld_status[LNS_FILTER_HELP].set_top(-(bottom_height + filter_height + 1));
332     lnav_data.ld_status[LNS_BOTTOM].set_top(-(match_height + 2));
333     lnav_data.ld_status[LNS_DOC].set_top(height - bottom_height);
334     lnav_data.ld_status[LNS_DOC].set_visible(doc_open);
335     lnav_data.ld_status[LNS_PREVIEW].set_top(height
336                                              - bottom_height
337                                              + (doc_open ? 1 : 0)
338                                              + doc_height);
339     lnav_data.ld_status[LNS_PREVIEW].set_visible(preview_status_open);
340 
341     if (!doc_open || doc_side_by_side) {
342         lnav_data.ld_doc_view.set_height(vis_line_t(doc_height));
343     } else {
344         lnav_data.ld_doc_view.set_height(vis_line_t(lnav_data.ld_doc_source.text_line_count()));
345     }
346     lnav_data.ld_doc_view.set_y(height - bottom_height + 1);
347 
348     if (!doc_open || doc_side_by_side) {
349         lnav_data.ld_example_view.set_height(vis_line_t(doc_height));
350         lnav_data.ld_example_view.set_x(doc_open ? 90 : 0);
351         lnav_data.ld_example_view.set_y(height - bottom_height + 1);
352     } else {
353         lnav_data.ld_example_view.set_height(vis_line_t(lnav_data.ld_example_source.text_line_count()));
354         lnav_data.ld_example_view.set_x(0);
355         lnav_data.ld_example_view.set_y(height - bottom_height + lnav_data.ld_doc_view.get_height() + 1);
356     }
357 
358     lnav_data.ld_filter_view.set_height(vis_line_t(filter_height));
359     lnav_data.ld_filter_view.set_y(height - bottom_height - filter_height);
360     lnav_data.ld_filter_view.set_width(width);
361 
362     lnav_data.ld_files_view.set_height(vis_line_t(filter_height));
363     lnav_data.ld_files_view.set_y(height - bottom_height - filter_height);
364     lnav_data.ld_files_view.set_width(width);
365 
366     lnav_data.ld_preview_view.set_height(vis_line_t(preview_height));
367     lnav_data.ld_preview_view.set_y(height
368                                     - bottom_height
369                                     + 1
370                                     + (doc_open ? 1 : 0)
371                                     + doc_height);
372     lnav_data.ld_match_view.set_y(
373         height
374         - lnav_data.ld_rl_view->get_height()
375         - match_height);
376     lnav_data.ld_rl_view->set_width(width);
377 }
378 
379 static unordered_map<string, attr_line_t> EXAMPLE_RESULTS;
380 
execute_examples()381 void execute_examples()
382 {
383     db_label_source &dls = lnav_data.ld_db_row_source;
384     db_overlay_source &dos = lnav_data.ld_db_overlay;
385     textview_curses &db_tc = lnav_data.ld_views[LNV_DB];
386 
387     for (auto &help_iter : sqlite_function_help) {
388         struct help_text &ht = *(help_iter.second);
389 
390         for (auto &ex : ht.ht_example) {
391             string alt_msg;
392             attr_line_t result;
393 
394             if (!ex.he_cmd) {
395                 continue;
396             }
397 
398             switch (ht.ht_context) {
399                 case help_context_t::HC_SQL_KEYWORD:
400                 case help_context_t::HC_SQL_INFIX:
401                 case help_context_t::HC_SQL_FUNCTION:
402                 case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION: {
403                     exec_context ec;
404 
405                     execute_sql(ec, ex.he_cmd, alt_msg);
406 
407                     if (dls.dls_rows.size() == 1 &&
408                         dls.dls_rows[0].size() == 1) {
409                         result.append(dls.dls_rows[0][0]);
410                     } else {
411                         attr_line_t al;
412                         dos.list_value_for_overlay(db_tc,
413                                                    0, 1,
414                                                    0_vl,
415                                                    al);
416                         result.append(al);
417                         for (int lpc = 0;
418                              lpc < (int)dls.text_line_count(); lpc++) {
419                             al.clear();
420                             dls.text_value_for_line(db_tc, lpc,
421                                                     al.get_string(),
422                                                     false);
423                             dls.text_attrs_for_line(db_tc, lpc,
424                                                     al.get_attrs());
425                             std::replace(al.get_string().begin(),
426                                          al.get_string().end(),
427                                          '\n',
428                                          ' ');
429                             result.append("\n")
430                                 .append(al);
431                         }
432                     }
433 
434                     EXAMPLE_RESULTS[ex.he_cmd] = result;
435 
436                     log_debug("example: %s", ex.he_cmd);
437                     log_debug("example result: %s",
438                               result.get_string().c_str());
439                     break;
440                 }
441                 default:
442                     log_warning("Not executing example: %s", ex.he_cmd);
443                     break;
444             }
445         }
446     }
447 
448     dls.clear();
449 }
450 
eval_example(const help_text & ht,const help_example & ex)451 attr_line_t eval_example(const help_text &ht, const help_example &ex)
452 {
453     auto iter = EXAMPLE_RESULTS.find(ex.he_cmd);
454 
455     if (iter != EXAMPLE_RESULTS.end()) {
456         return iter->second;
457     }
458 
459     return "";
460 }
461 
toggle_view(textview_curses * toggle_tc)462 bool toggle_view(textview_curses *toggle_tc)
463 {
464     textview_curses *tc = lnav_data.ld_view_stack.top().value_or(nullptr);
465     bool             retval = false;
466 
467     require(toggle_tc != NULL);
468     require(toggle_tc >= &lnav_data.ld_views[0]);
469     require(toggle_tc < &lnav_data.ld_views[LNV__MAX]);
470 
471     if (tc == toggle_tc) {
472         if (lnav_data.ld_view_stack.size() == 1) {
473             return false;
474         }
475         lnav_data.ld_last_view = tc;
476         lnav_data.ld_view_stack.pop_back();
477     }
478     else {
479         if (toggle_tc == &lnav_data.ld_views[LNV_SCHEMA]) {
480             open_schema_view();
481         }
482         else if (toggle_tc == &lnav_data.ld_views[LNV_PRETTY]) {
483             open_pretty_view();
484         }
485         else if (toggle_tc == &lnav_data.ld_views[LNV_HISTOGRAM]) {
486             // Rebuild to reflect changes in marks.
487             rebuild_hist();
488         }
489         else if (toggle_tc == &lnav_data.ld_views[LNV_HELP]) {
490             build_all_help_text();
491         }
492         lnav_data.ld_last_view = nullptr;
493         lnav_data.ld_view_stack.push_back(toggle_tc);
494         retval = true;
495     }
496 
497     return retval;
498 }
499 
500 /**
501  * Ensure that the view is on the top of the view stack.
502  *
503  * @param expected_tc The text view that should be on top.
504  * @return True if the view was already on the top of the stack.
505  */
ensure_view(textview_curses * expected_tc)506 bool ensure_view(textview_curses *expected_tc)
507 {
508     textview_curses *tc = lnav_data.ld_view_stack.top().value_or(nullptr);
509     bool retval = true;
510 
511     if (tc != expected_tc) {
512         toggle_view(expected_tc);
513         retval = false;
514     }
515     return retval;
516 }
517 
next_cluster(vis_line_t (bookmark_vector<vis_line_t>::* f)(vis_line_t)const,bookmark_type_t * bt,const vis_line_t top)518 nonstd::optional<vis_line_t> next_cluster(
519     vis_line_t(bookmark_vector<vis_line_t>::*f) (vis_line_t) const,
520     bookmark_type_t *bt,
521     const vis_line_t top)
522 {
523     textview_curses *tc = get_textview_for_mode(lnav_data.ld_mode);
524     vis_bookmarks &bm = tc->get_bookmarks();
525     bookmark_vector<vis_line_t> &bv = bm[bt];
526     bool top_is_marked = binary_search(bv.begin(), bv.end(), top);
527     vis_line_t last_top(top), new_top(top), tc_height;
528     unsigned long tc_width;
529     int hit_count = 0;
530 
531     tc->get_dimensions(tc_height, tc_width);
532 
533     while ((new_top = (bv.*f)(new_top)) != -1) {
534         int diff = new_top - last_top;
535 
536         hit_count += 1;
537         if (!top_is_marked || diff > 1) {
538             return new_top;
539         }
540         else if (hit_count > 1 && std::abs(new_top - top) >= tc_height) {
541             return vis_line_t(new_top - diff);
542         }
543         else if (diff < -1) {
544             last_top = new_top;
545             while ((new_top = (bv.*f)(new_top)) != -1) {
546                 if ((std::abs(last_top - new_top) > 1) ||
547                     (hit_count > 1 && (std::abs(top - new_top) >= tc_height))) {
548                     break;
549                 }
550                 last_top = new_top;
551             }
552             return last_top;
553         }
554         last_top = new_top;
555     }
556 
557     if (last_top != top) {
558         return last_top;
559     }
560 
561     return nonstd::nullopt;
562 }
563 
moveto_cluster(vis_line_t (bookmark_vector<vis_line_t>::* f)(vis_line_t)const,bookmark_type_t * bt,vis_line_t top)564 bool moveto_cluster(vis_line_t(bookmark_vector<vis_line_t>::*f) (vis_line_t) const,
565                     bookmark_type_t *bt,
566                     vis_line_t top)
567 {
568     textview_curses *tc = get_textview_for_mode(lnav_data.ld_mode);
569     auto new_top = next_cluster(f, bt, top);
570 
571     if (!new_top) {
572         new_top = next_cluster(f, bt,
573                                tc->is_selectable() ?
574                                tc->get_selection() :
575                                tc->get_top());
576     }
577     if (new_top != -1) {
578         tc->get_sub_source()->get_location_history() | [new_top] (auto lh) {
579             lh->loc_history_append(new_top.value());
580         };
581 
582         if (tc->is_selectable()) {
583             tc->set_selection(new_top.value());
584         } else {
585             tc->set_top(new_top.value());
586         }
587         return true;
588     }
589 
590     alerter::singleton().chime();
591 
592     return false;
593 }
594 
previous_cluster(bookmark_type_t * bt,textview_curses * tc)595 void previous_cluster(bookmark_type_t *bt, textview_curses *tc)
596 {
597     key_repeat_history &krh = lnav_data.ld_key_repeat_history;
598     vis_line_t height, initial_top;
599     unsigned long width;
600 
601     if (tc->is_selectable()) {
602         initial_top = tc->get_selection();
603     } else {
604         initial_top = tc->get_top();
605     }
606     auto new_top = next_cluster(&bookmark_vector<vis_line_t>::prev,
607                                 bt,
608                                 initial_top);
609 
610     tc->get_dimensions(height, width);
611     if (krh.krh_count > 1 &&
612         initial_top < (krh.krh_start_line - (1.5 * height)) &&
613         (!new_top || ((initial_top - new_top.value()) < height))) {
614         bookmark_vector<vis_line_t> &bv = tc->get_bookmarks()[bt];
615         new_top = bv.next(std::max(0_vl, initial_top - height));
616     }
617 
618     if (new_top) {
619         tc->get_sub_source()->get_location_history() | [new_top] (auto lh) {
620             lh->loc_history_append(new_top.value());
621         };
622 
623         if (tc->is_selectable()) {
624             tc->set_selection(new_top.value());
625         } else {
626             tc->set_top(new_top.value());
627         }
628     }
629     else {
630         alerter::singleton().chime();
631     }
632 }
633 
search_forward_from(textview_curses * tc)634 vis_line_t search_forward_from(textview_curses *tc)
635 {
636     vis_line_t height, retval =
637         tc->is_selectable() ? tc->get_selection() : tc->get_top();
638     key_repeat_history &krh = lnav_data.ld_key_repeat_history;
639     unsigned long width;
640 
641     tc->get_dimensions(height, width);
642 
643     if (krh.krh_count > 1 &&
644         retval > (krh.krh_start_line + (1.5 * height))) {
645         retval += vis_line_t(0.90 * height);
646     }
647 
648     return retval;
649 }
650