Highlight shell commands in history pager

This solution is quite hacky. I added a comment that suggests a better
solution, which shouldn't be hard to implement.
This commit is contained in:
Johannes Altmanninger 2022-08-10 01:31:57 +02:00
parent b64cec1d7e
commit c031e6f193
3 changed files with 43 additions and 6 deletions

View file

@ -88,6 +88,9 @@ class completion_t {
/// \return whether this replaces its token. /// \return whether this replaces its token.
bool replaces_token() const { return flags & COMPLETE_REPLACES_TOKEN; } bool replaces_token() const { return flags & COMPLETE_REPLACES_TOKEN; }
/// \return whether this replaces the entire commandline.
bool replaces_commandline() const { return flags & COMPLETE_REPLACES_COMMANDLINE; }
/// \return the completion's match rank. Lower ranks are better completions. /// \return the completion's match rank. Lower ranks are better completions.
uint32_t rank() const { return match.rank(); } uint32_t rank() const { return match.rank(); }

View file

@ -16,6 +16,7 @@
#include "fallback.h" #include "fallback.h"
#include "flog.h" #include "flog.h"
#include "highlight.h" #include "highlight.h"
#include "operation_context.h"
#include "pager.h" #include "pager.h"
#include "reader.h" #include "reader.h"
#include "screen.h" #include "screen.h"
@ -75,8 +76,10 @@ static size_t divide_round_up(size_t numer, size_t denom) {
/// \param max the maximum space that may be used for printing /// \param max the maximum space that may be used for printing
/// \param has_more if this flag is true, this is not the entire string, and the string should be /// \param has_more if this flag is true, this is not the entire string, and the string should be
/// ellipsized even if the string fits but takes up the whole space. /// ellipsized even if the string fits but takes up the whole space.
static size_t print_max(const wcstring &str, highlight_spec_t color, size_t max, bool has_more, template <typename Func>
line_t *line) { static typename std::enable_if<
std::is_convertible<Func, std::function<highlight_spec_t(size_t)>>::value, size_t>::type
print_max(const wcstring &str, Func color, size_t max, bool has_more, line_t *line) {
size_t remaining = max; size_t remaining = max;
for (size_t i = 0; i < str.size(); i++) { for (size_t i = 0; i < str.size(); i++) {
wchar_t c = str.at(i); wchar_t c = str.at(i);
@ -91,13 +94,13 @@ static size_t print_max(const wcstring &str, highlight_spec_t color, size_t max,
wchar_t ellipsis = get_ellipsis_char(); wchar_t ellipsis = get_ellipsis_char();
if ((width_c == remaining) && (has_more || i + 1 < str.size())) { if ((width_c == remaining) && (has_more || i + 1 < str.size())) {
line->append(ellipsis, color); line->append(ellipsis, color(i));
int ellipsis_width = fish_wcwidth(ellipsis); int ellipsis_width = fish_wcwidth(ellipsis);
remaining -= std::min(remaining, size_t(ellipsis_width)); remaining -= std::min(remaining, size_t(ellipsis_width));
break; break;
} }
line->append(c, color); line->append(c, color(i));
assert(remaining >= width_c); assert(remaining >= width_c);
remaining -= width_c; remaining -= width_c;
} }
@ -107,6 +110,12 @@ static size_t print_max(const wcstring &str, highlight_spec_t color, size_t max,
return max - remaining; return max - remaining;
} }
static size_t print_max(const wcstring &str, highlight_spec_t color, size_t max, bool has_more,
line_t *line) {
return print_max(
str, [=](size_t) -> highlight_spec_t { return color; }, max, has_more, line);
}
/// Print the specified item using at the specified amount of space. /// Print the specified item using at the specified amount of space.
line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, size_t row, line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, size_t row,
size_t column, size_t width, bool secondary, bool selected, size_t column, size_t width, bool secondary, bool selected,
@ -171,8 +180,14 @@ line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, s
} }
comp_remaining -= print_max(prefix, prefix_col, comp_remaining, !comp.empty(), &line_data); comp_remaining -= print_max(prefix, prefix_col, comp_remaining, !comp.empty(), &line_data);
comp_remaining -= comp_remaining -= print_max(
print_max(comp, comp_col, comp_remaining, i + 1 < c->comp.size(), &line_data); comp,
[&](size_t i) -> highlight_spec_t {
if (c->colors.empty()) return comp_col; // Not a shell command.
if (selected) return comp_col; // Rendered in reverse video, so avoid highlighting.
return i < c->colors.size() ? c->colors[i] : c->colors.back();
},
comp_remaining, i + 1 < c->comp.size(), &line_data);
} }
size_t desc_remaining = width - comp_width + comp_remaining; size_t desc_remaining = width - comp_width + comp_remaining;
@ -315,6 +330,21 @@ static comp_info_list_t process_completions_into_infos(const completion_list_t &
// Append the single completion string. We may later merge these into multiple. // Append the single completion string. We may later merge these into multiple.
comp_info->comp.push_back(escape_string( comp_info->comp.push_back(escape_string(
comp.completion, ESCAPE_NO_PRINTABLES | ESCAPE_NO_QUOTED | ESCAPE_SYMBOLIC)); comp.completion, ESCAPE_NO_PRINTABLES | ESCAPE_NO_QUOTED | ESCAPE_SYMBOLIC));
if (comp.replaces_commandline()
// HACK We want to render a full shell command, with syntax highlighting. Above we
// escape nonprintables, which might make the rendered command longer than the original
// completion. In that case we get wrong colors. However this should only happen in
// contrived cases, since our symbolic escaping uses a single character to represent
// newline and tab characters; other nonprintables are extremely rare in a command
// line. It will only be common for single-byte locales where we don't
// use Unicode characters for escaping, so just disable those here.
// We should probably fix this by first highlighting the original completion, and
// then writing a variant of escape_string() that adjusts highlighting according
// so it matches the escaped string.
&& MB_CUR_MAX > 1) {
highlight_shell(comp.completion, comp_info->colors, operation_context_t::empty());
assert(comp_info->comp.back().size() >= comp_info->colors.size());
}
// Append the mangled description. // Append the mangled description.
comp_info->desc = comp.description; comp_info->desc = comp.description;

View file

@ -61,6 +61,8 @@ enum class selection_motion_t {
// How many rows we will show in the "initial" pager. // How many rows we will show in the "initial" pager.
#define PAGER_UNDISCLOSED_MAX_ROWS 4 #define PAGER_UNDISCLOSED_MAX_ROWS 4
struct highlight_spec_t;
class pager_t { class pager_t {
size_t available_term_width{0}; size_t available_term_width{0};
size_t available_term_height{0}; size_t available_term_height{0};
@ -87,6 +89,8 @@ class pager_t {
wcstring desc{}; wcstring desc{};
/// The representative completion. /// The representative completion.
completion_t representative{L""}; completion_t representative{L""};
/// The per-character highlighting, used when this is a full shell command.
std::vector<highlight_spec_t> colors{};
/// On-screen width of the completion string. /// On-screen width of the completion string.
size_t comp_width{0}; size_t comp_width{0};
/// On-screen width of the description information. /// On-screen width of the description information.