mirror of https://github.com/tstack/lnav
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
370 lines
14 KiB
C++
370 lines
14 KiB
C++
/**
|
|
* Copyright (c) 2017, 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.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <numeric>
|
|
#include <algorithm>
|
|
|
|
#include "ansi_scrubber.hh"
|
|
#include "help_text_formatter.hh"
|
|
#include "readline_highlighters.hh"
|
|
|
|
using namespace std;
|
|
|
|
|
|
std::multimap<std::string, help_text *> help_text::TAGGED;
|
|
|
|
void format_help_text_for_term(const help_text &ht, int width, attr_line_t &out, bool synopsis_only)
|
|
{
|
|
static size_t body_indent = 2;
|
|
|
|
view_colors &vc = view_colors::singleton();
|
|
text_wrap_settings tws;
|
|
size_t start_index = out.get_string().length();
|
|
|
|
tws.with_width(width);
|
|
|
|
switch (ht.ht_context) {
|
|
case HC_COMMAND: {
|
|
out.append("Synopsis", &view_curses::VC_STYLE, A_UNDERLINE)
|
|
.append("\n")
|
|
.append(body_indent, ' ')
|
|
.append(":")
|
|
.append(ht.ht_name, &view_curses::VC_STYLE, A_BOLD);
|
|
for (auto ¶m : ht.ht_parameters) {
|
|
out.append(" ");
|
|
if (param.ht_nargs == HN_OPTIONAL) {
|
|
out.append("[");
|
|
}
|
|
out.append(param.ht_name, &view_curses::VC_STYLE, A_UNDERLINE);
|
|
if (param.ht_nargs == HN_OPTIONAL) {
|
|
out.append("]");
|
|
}
|
|
if (param.ht_nargs == HN_ONE_OR_MORE) {
|
|
out.append("1", &view_curses::VC_STYLE, A_UNDERLINE);
|
|
out.append(" [");
|
|
out.append("...", &view_curses::VC_STYLE, A_UNDERLINE);
|
|
out.append(" ");
|
|
out.append(param.ht_name, &view_curses::VC_STYLE,
|
|
A_UNDERLINE);
|
|
out.append("N", &view_curses::VC_STYLE, A_UNDERLINE);
|
|
out.append("]");
|
|
}
|
|
}
|
|
out.append(" - ")
|
|
.append(attr_line_t::from_ansi_str(ht.ht_summary),
|
|
&tws.with_indent(body_indent + 2))
|
|
.append("\n");
|
|
break;
|
|
}
|
|
case HC_SQL_FUNCTION: {
|
|
bool needs_comma = false;
|
|
|
|
out.append("Synopsis", &view_curses::VC_STYLE, A_UNDERLINE)
|
|
.append("\n")
|
|
.append(body_indent, ' ')
|
|
.append(ht.ht_name, &view_curses::VC_STYLE, A_BOLD)
|
|
.append("(");
|
|
for (auto ¶m : ht.ht_parameters) {
|
|
if (param.ht_flag_name) {
|
|
out.append(" ")
|
|
.append(param.ht_flag_name, &view_curses::VC_STYLE, A_BOLD)
|
|
.append(" ");
|
|
} else if (needs_comma) {
|
|
out.append(", ");
|
|
}
|
|
out.append(param.ht_name, &view_curses::VC_STYLE, A_UNDERLINE);
|
|
if (param.ht_nargs == HN_ZERO_OR_MORE ||
|
|
param.ht_nargs == HN_ONE_OR_MORE) {
|
|
out.append(", ...");
|
|
}
|
|
needs_comma = true;
|
|
}
|
|
out.append(") -- ")
|
|
.append(attr_line_t::from_ansi_str(ht.ht_summary),
|
|
&tws.with_indent(body_indent + 2))
|
|
.append("\n");
|
|
break;
|
|
}
|
|
case HC_SQL_KEYWORD: {
|
|
size_t line_start = body_indent;
|
|
bool break_all = false;
|
|
|
|
if (!synopsis_only) {
|
|
out.append("Synopsis", &view_curses::VC_STYLE, A_UNDERLINE)
|
|
.append("\n");
|
|
}
|
|
out.append(body_indent, ' ')
|
|
.append(ht.ht_name, &view_curses::VC_STYLE, A_BOLD);
|
|
for (auto ¶m : ht.ht_parameters) {
|
|
if (break_all ||
|
|
(int)(out.get_string().length() - start_index - line_start + 10) >=
|
|
tws.tws_width) {
|
|
out.append("\n");
|
|
line_start = out.get_string().length();
|
|
out.append(body_indent + strlen(ht.ht_name) + 1, ' ');
|
|
break_all = true;
|
|
}
|
|
if (param.ht_nargs == HN_ZERO_OR_MORE ||
|
|
param.ht_nargs == HN_OPTIONAL) {
|
|
if (!break_all) {
|
|
out.append(" ");
|
|
}
|
|
out.append("[");
|
|
}
|
|
if (param.ht_flag_name) {
|
|
out.ensure_space()
|
|
.append(param.ht_flag_name, &view_curses::VC_STYLE,
|
|
A_BOLD);
|
|
}
|
|
if (param.ht_name[0]) {
|
|
out.ensure_space()
|
|
.append(param.ht_name, &view_curses::VC_STYLE,
|
|
A_UNDERLINE);
|
|
if (!param.ht_parameters.empty()) {
|
|
if (param.ht_nargs == HN_ZERO_OR_MORE ||
|
|
param.ht_nargs == HN_ONE_OR_MORE) {
|
|
out.append("1", &view_curses::VC_STYLE, A_UNDERLINE);
|
|
}
|
|
if (param.ht_parameters[0].ht_flag_name) {
|
|
out.append(" ")
|
|
.append(param.ht_parameters[0].ht_flag_name,
|
|
&view_curses::VC_STYLE,
|
|
A_BOLD)
|
|
.append(" ");
|
|
}
|
|
out.append(param.ht_parameters[0].ht_name,
|
|
&view_curses::VC_STYLE,
|
|
A_UNDERLINE);
|
|
}
|
|
}
|
|
if (param.ht_nargs == HN_ZERO_OR_MORE ||
|
|
param.ht_nargs == HN_ONE_OR_MORE) {
|
|
bool needs_comma = param.ht_parameters.empty() ||
|
|
!param.ht_flag_name;
|
|
|
|
out.append("1", &view_curses::VC_STYLE, A_UNDERLINE)
|
|
.append(" [")
|
|
.append(needs_comma ? ", " : "")
|
|
.append("...")
|
|
.append(needs_comma ? "" : " ")
|
|
.append((needs_comma || !param.ht_flag_name) ? "" : param.ht_flag_name,
|
|
&view_curses::VC_STYLE,
|
|
A_BOLD)
|
|
.append(" ")
|
|
.append(param.ht_name, &view_curses::VC_STYLE,
|
|
A_UNDERLINE)
|
|
.append("N", &view_curses::VC_STYLE, A_UNDERLINE);
|
|
if (!param.ht_parameters.empty()) {
|
|
if (param.ht_parameters[0].ht_flag_name) {
|
|
out.append(" ")
|
|
.append(param.ht_parameters[0].ht_flag_name,
|
|
&view_curses::VC_STYLE,
|
|
A_BOLD)
|
|
.append(" ");
|
|
}
|
|
|
|
out.append(param.ht_parameters[0].ht_name,
|
|
&view_curses::VC_STYLE,
|
|
A_UNDERLINE)
|
|
.append("N", &view_curses::VC_STYLE, A_UNDERLINE);
|
|
}
|
|
out.append("]");
|
|
}
|
|
if (param.ht_nargs == HN_ZERO_OR_MORE ||
|
|
param.ht_nargs == HN_OPTIONAL) {
|
|
out.append("]");
|
|
}
|
|
}
|
|
if (!synopsis_only) {
|
|
out.append("\n\n")
|
|
.append(body_indent, ' ')
|
|
.append(ht.ht_summary, &tws)
|
|
.append("\n");
|
|
} else {
|
|
out.append("\n");
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!synopsis_only && !ht.ht_parameters.empty()) {
|
|
size_t max_param_name_width = 0;
|
|
|
|
for (auto ¶m : ht.ht_parameters) {
|
|
max_param_name_width = std::max(strlen(param.ht_name),
|
|
max_param_name_width);
|
|
}
|
|
|
|
out.append(ht.ht_parameters.size() == 1 ? "Parameter" : "Parameters",
|
|
&view_curses::VC_STYLE,
|
|
A_UNDERLINE)
|
|
.append("\n");
|
|
|
|
for (auto ¶m : ht.ht_parameters) {
|
|
if (!param.ht_summary) {
|
|
continue;
|
|
}
|
|
|
|
out.append(body_indent, ' ')
|
|
.append(param.ht_name,
|
|
&view_curses::VC_STYLE,
|
|
vc.attrs_for_role(view_colors::VCR_VARIABLE) | A_BOLD)
|
|
.append(max_param_name_width - strlen(param.ht_name), ' ')
|
|
.append(" ")
|
|
.append(attr_line_t::from_ansi_str(param.ht_summary),
|
|
&(tws.with_indent(2 + max_param_name_width + 3)))
|
|
.append("\n");
|
|
}
|
|
}
|
|
if (!synopsis_only && !ht.ht_tags.empty()) {
|
|
vector<string> tags;
|
|
|
|
for (const auto &tag : ht.ht_tags) {
|
|
auto tagged = help_text::TAGGED.equal_range(tag);
|
|
|
|
for (auto tag_iter = tagged.first;
|
|
tag_iter != tagged.second;
|
|
++tag_iter) {
|
|
if (tag_iter->second == &ht) {
|
|
continue;
|
|
}
|
|
|
|
help_text &related = *tag_iter->second;
|
|
|
|
if (!related.ht_opposites.empty() &&
|
|
find_if(related.ht_opposites.begin(),
|
|
related.ht_opposites.end(),
|
|
[&ht](const char *x) {
|
|
return strcmp(x, ht.ht_name) == 0;
|
|
}) == related.ht_opposites.end()) {
|
|
continue;
|
|
}
|
|
|
|
string name = related.ht_name;
|
|
switch (related.ht_context) {
|
|
case HC_COMMAND:
|
|
name = ":" + name;
|
|
break;
|
|
case HC_SQL_FUNCTION:
|
|
name = name + "()";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
tags.push_back(name);
|
|
}
|
|
}
|
|
|
|
stable_sort(tags.begin(), tags.end());
|
|
|
|
out.append("See Also", &view_curses::VC_STYLE, A_UNDERLINE)
|
|
.append("\n")
|
|
.append(body_indent, ' ');
|
|
|
|
bool first = true;
|
|
size_t line_start = out.length();
|
|
for (const auto &tag : tags) {
|
|
if (!first) {
|
|
out.append(", ");
|
|
}
|
|
if ((out.length() - line_start + tag.length()) > width) {
|
|
out.append("\n")
|
|
.append(body_indent, ' ');
|
|
line_start = out.length();
|
|
}
|
|
out.append(tag, &view_curses::VC_STYLE, A_BOLD);
|
|
first = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void format_example_text_for_term(const help_text &ht, int width, attr_line_t &out)
|
|
{
|
|
text_wrap_settings tws;
|
|
|
|
tws.with_width(width);
|
|
|
|
if (!ht.ht_example.empty()) {
|
|
map<string, string> vars;
|
|
int count = 1;
|
|
|
|
vars["name"] = ht.ht_name;
|
|
add_ansi_vars(vars);
|
|
|
|
out.append(ht.ht_example.size() == 1 ? "Example" : "Examples",
|
|
&view_curses::VC_STYLE,
|
|
A_UNDERLINE)
|
|
.append("\n");
|
|
for (auto &ex : ht.ht_example) {
|
|
attr_line_t ex_line(ex.he_cmd);
|
|
size_t keyword_offset = 0;
|
|
const char *space = strchr(ex.he_cmd, ' ');
|
|
const char *prompt = "";
|
|
|
|
if (space) {
|
|
keyword_offset = space - ex.he_cmd;
|
|
}
|
|
if (count > 1) {
|
|
out.append("\n");
|
|
}
|
|
switch (ht.ht_context) {
|
|
case HC_COMMAND:
|
|
ex_line.insert(0, 1, ' ');
|
|
ex_line.insert(0, 1, ':');
|
|
ex_line.insert(1, ht.ht_name);
|
|
readline_command_highlighter(ex_line, 0);
|
|
break;
|
|
case HC_SQL_KEYWORD:
|
|
case HC_SQL_FUNCTION:
|
|
readline_sqlite_highlighter(ex_line, 0);
|
|
prompt = ";";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
out.append("#")
|
|
.append(to_string(count))
|
|
.append(" ")
|
|
.append(prompt)
|
|
.append(ex_line, &tws.with_indent(3 + keyword_offset + 1))
|
|
.append("\n")
|
|
.append(3, ' ')
|
|
.append(ex.he_result, &tws.with_indent(3))
|
|
.append("\n");
|
|
|
|
count += 1;
|
|
}
|
|
}
|
|
}
|