radare2/libr/cons/hud.c
2021-02-06 03:30:53 +01:00

333 lines
8.4 KiB
C

/* radare - LGPL - Copyright 2008-2018 - pancake */
#include <r_cons.h>
#include <ctype.h>
#define I(x) r_cons_singleton ()->x
// Display the content of a file in the hud
R_API char *r_cons_hud_file(const char *f) {
char *s = r_file_slurp (f, NULL);
if (s) {
char *ret = r_cons_hud_string (s);
free (s);
return ret;
}
return NULL;
}
// Display a buffer in the hud (splitting it line-by-line and ignoring
// the lines starting with # )
R_API char *r_cons_hud_string(const char *s) {
if (!r_cons_is_interactive ()) {
eprintf ("Hud mode requires scr.interactive=true.\n");
return NULL;
}
char *os, *track, *ret, *o = strdup (s);
if (!o) {
return NULL;
}
r_str_replace_ch (o, '\r', 0, true);
r_str_replace_ch (o, '\t', 0, true);
RList *fl = r_list_new ();
int i;
if (!fl) {
free (o);
return NULL;
}
fl->free = free;
for (os = o, i = 0; o[i]; i++) {
if (o[i] == '\n') {
o[i] = 0;
if (*os && *os != '#') {
track = strdup (os);
if (!r_list_append (fl, track)) {
free (track);
break;
}
}
os = o + i + 1;
}
}
ret = r_cons_hud (fl, NULL);
free (o);
r_list_free (fl);
return ret;
}
/* Match a filter on a line. A filter can contain multiple words
separated by spaces, which are all matched *in any order* over the target
entry. If all words are present, the function returns true.
The mask is a character buffer which is filled by 'x' to mark those characters
that match the filter */
static bool __matchString(char *entry, char *filter, char *mask, const int mask_size) {
char *p, *current_token = filter;
const char *filter_end = filter + strlen (filter);
char *ansi_filtered = strdup (entry);
int *cps;
r_str_ansi_filter (ansi_filtered, NULL, &cps, -1);
entry = ansi_filtered;
// first we separate the filter in words (include the terminator char
// to avoid special handling of the last token)
for (p = filter; p <= filter_end; p++) {
if (*p == ' ' || *p == '\0') {
const char *next_match, *entry_ptr = entry;
char old_char = *p;
int token_len;
// Ignoring consecutive spaces
if (p == current_token) {
current_token++;
continue;
}
*p = 0;
token_len = strlen (current_token);
// look for all matches of the current_token in this entry
while ((next_match = r_str_casestr (entry_ptr, current_token))) {
int real_pos, filtered_pos = next_match - entry;
int end_pos = cps[filtered_pos + token_len];
for (real_pos = cps[filtered_pos];
real_pos < end_pos && real_pos < mask_size;
real_pos = cps[++filtered_pos]) {
mask[real_pos] = 'x';
}
entry_ptr += token_len;
}
*p = old_char;
if (entry_ptr == entry) {
// the word is not present in the target
free (cps);
free (ansi_filtered);
return false;
}
current_token = p + 1;
}
}
free (cps);
free (ansi_filtered);
return true;
}
static RList *hud_filter(RList *list, char *user_input, int top_entry_n, int *current_entry_n, char **selected_entry) {
RListIter *iter;
char *current_entry;
char mask[HUD_BUF_SIZE];
char *p, *x;
int j, rows;
(void) r_cons_get_size (&rows);
int counter = 0;
bool first_line = true;
RList *res = r_list_newf (free);
r_list_foreach (list, iter, current_entry) {
memset (mask, 0, HUD_BUF_SIZE);
if (*user_input && !__matchString (current_entry, user_input, mask, HUD_BUF_SIZE)) {
continue;
}
if (++counter == rows + top_entry_n) {
break;
}
// if the user scrolled down the list, do not print the first entries
if (!top_entry_n || *current_entry_n >= top_entry_n) {
// remove everything after a tab (in ??, it contains the commands)
x = strchr (current_entry, '\t');
if (x) {
*x = 0;
}
p = strdup (current_entry);
// if the filter is empty, print the entry and move on
if (!user_input[0]) {
r_list_append (res, r_str_newf (" %c %s", first_line? '-': ' ', p));
} else {
// otherwise we need to emphasize the matching part
if (I (context->color_mode)) {
int last_color_change = 0;
int last_mask = 0;
char *str = r_str_newf (" %c ", first_line? '-': ' ');
// Instead of printing one char at the time
// (which would be slow), we group substrings of the same color
for (j = 0; p[j] && j < HUD_BUF_SIZE; j++) {
if (mask[j] != last_mask) {
char tmp = p[j];
p[j] = 0;
if (mask[j]) {
str = r_str_appendf (str, Color_RESET "%s", p + last_color_change);
} else {
str = r_str_appendf (str, Color_GREEN "%s", p + last_color_change);
}
p[j] = tmp;
last_color_change = j;
last_mask = mask[j];
}
}
if (last_mask) {
str = r_str_appendf (str, Color_GREEN "%s"Color_RESET, p + last_color_change);
} else {
str = r_str_appendf (str, Color_RESET "%s", p + last_color_change);
}
r_list_append (res, str);
} else {
// Otherwise we print the matching characters uppercase
for (j = 0; p[j]; j++) {
if (mask[j]) {
p[j] = toupper ((unsigned char) p[j]);
}
}
r_list_append (res, r_str_newf (" %c %s", first_line? '-': ' ', p));
}
}
// Clean up and restore the tab character (if any)
free (p);
if (x) {
*x = '\t';
}
if (first_line) {
*selected_entry = current_entry;
}
first_line = false;
}
(*current_entry_n)++;
}
return res;
}
static void mht_free_kv(HtPPKv *kv) {
free (kv->key);
r_list_free (kv->value);
}
// Display a list of entries in the hud, filtered and emphasized based on the user input.
#define HUD_CACHE 0
R_API char *r_cons_hud(RList *list, const char *prompt) {
char user_input[HUD_BUF_SIZE + 1];
char *selected_entry = NULL;
RListIter *iter;
HtPP *ht = ht_pp_new (NULL, (HtPPKvFreeFunc)mht_free_kv, (HtPPCalcSizeV)strlen);
RLineHud *hud = (RLineHud*) R_NEW (RLineHud);
hud->activate = 0;
hud->vi = 0;
I(line)->echo = false;
I(line)->hud = hud;
user_input [0] = 0;
user_input[HUD_BUF_SIZE] = 0;
hud->top_entry_n = 0;
r_cons_show_cursor (false);
r_cons_enable_mouse (false);
r_cons_clear ();
// Repeat until the user exits the hud
for (;;) {
r_cons_gotoxy (0, 0);
hud->current_entry_n = 0;
if (hud->top_entry_n < 0) {
hud->top_entry_n = 0;
}
selected_entry = NULL;
if (prompt && *prompt) {
r_cons_printf (">> %s\n", prompt);
}
r_cons_printf ("%d> %s|\n", hud->top_entry_n, user_input);
char *row;
RList *filtered_list = NULL;
bool found = false;
filtered_list = ht_pp_find (ht, user_input, &found);
if (!found) {
filtered_list = hud_filter (list, user_input,
hud->top_entry_n, &(hud->current_entry_n), &selected_entry);
#if HUD_CACHE
ht_pp_insert (ht, user_input, filtered_list);
#endif
}
r_list_foreach (filtered_list, iter, row) {
r_cons_printf ("%s\n", row);
}
if (!filtered_list->length) { // hack to remove garbage value when list is empty
printf ("%s", R_CONS_CLEAR_LINE);
}
#if !HUD_CACHE
r_list_free (filtered_list);
#endif
r_cons_visual_flush ();
(void) r_line_readline ();
r_str_ncpy (user_input, I(line)->buffer.data, HUD_BUF_SIZE);
if (!hud->activate) {
hud->top_entry_n = 0;
if (hud->current_entry_n >= 1 ) {
if (selected_entry) {
R_FREE (I(line)->hud);
I(line)->echo = true;
r_cons_enable_mouse (false);
r_cons_show_cursor (true);
r_cons_set_raw (false);
return strdup (selected_entry);
}
} else {
goto _beach;
}
}
}
_beach:
R_FREE (I(line)->hud);
I(line)->echo = true;
r_cons_show_cursor (true);
r_cons_enable_mouse (false);
r_cons_set_raw (false);
ht_pp_free (ht);
return NULL;
}
// Display the list of files in a directory
R_API char *r_cons_hud_path(const char *path, int dir) {
char *tmp, *ret = NULL;
RList *files;
if (path) {
path = r_str_trim_head_ro (path);
tmp = strdup (*path ? path : "./");
} else {
tmp = strdup ("./");
}
files = r_sys_dir (tmp);
if (files) {
ret = r_cons_hud (files, tmp);
if (ret) {
tmp = r_str_append (tmp, "/");
tmp = r_str_append (tmp, ret);
free (ret);
ret = r_file_abspath (tmp);
free (tmp);
tmp = ret;
if (r_file_is_directory (tmp)) {
ret = r_cons_hud_path (tmp, dir);
free (tmp);
tmp = ret;
}
}
r_list_free (files);
} else {
eprintf ("No files found\n");
}
if (!ret) {
free (tmp);
return NULL;
}
return tmp;
}
R_API char *r_cons_message(const char *msg) {
int len = strlen (msg);
int rows, cols = r_cons_get_size (&rows);
r_cons_clear ();
r_cons_gotoxy ((cols - len) / 2, rows / 2);
r_cons_println (msg);
r_cons_flush ();
r_cons_gotoxy (0, rows - 2);
r_cons_any_key (NULL);
return NULL;
}