1 /* radare - LGPL - Copyright 2008-2018 - pancake */
2 
3 #include <r_cons.h>
4 #include <ctype.h>
5 
6 #define I(x) r_cons_singleton ()->x
7 
8 // Display the content of a file in the hud
r_cons_hud_file(const char * f)9 R_API char *r_cons_hud_file(const char *f) {
10 	char *s = r_file_slurp (f, NULL);
11 	if (s) {
12 		char *ret = r_cons_hud_string (s);
13 		free (s);
14 		return ret;
15 	}
16 	return NULL;
17 }
18 
19 // Display a buffer in the hud (splitting it line-by-line and ignoring
20 // the lines starting with # )
r_cons_hud_string(const char * s)21 R_API char *r_cons_hud_string(const char *s) {
22 	if (!r_cons_is_interactive ()) {
23 		eprintf ("Hud mode requires scr.interactive=true.\n");
24 		return NULL;
25 	}
26 	char *os, *track, *ret, *o = strdup (s);
27 	if (!o) {
28 		return NULL;
29 	}
30 	r_str_replace_ch (o, '\r', 0, true);
31 	r_str_replace_ch (o, '\t', 0, true);
32 	RList *fl = r_list_new ();
33 	int i;
34 	if (!fl) {
35 		free (o);
36 		return NULL;
37 	}
38 	fl->free = free;
39 	for (os = o, i = 0; o[i]; i++) {
40 		if (o[i] == '\n') {
41 			o[i] = 0;
42 			if (*os && *os != '#') {
43 				track = strdup (os);
44 				if (!r_list_append (fl, track)) {
45 					free (track);
46 					break;
47 				}
48 			}
49 			os = o + i + 1;
50 		}
51 	}
52 	ret = r_cons_hud (fl, NULL);
53 	free (o);
54 	r_list_free (fl);
55 	return ret;
56 }
57 
58 /* Match a filter on a line. A filter can contain multiple words
59    separated by spaces, which are all matched *in any order* over the target
60    entry. If all words are present, the function returns true.
61    The mask is a character buffer which is filled by 'x' to mark those characters
62    that match the filter */
__matchString(char * entry,char * filter,char * mask,const int mask_size)63 static bool __matchString(char *entry, char *filter, char *mask, const int mask_size) {
64 	char *p, *current_token = filter;
65 	const char *filter_end = filter + strlen (filter);
66 	char *ansi_filtered = strdup (entry);
67 	int *cps;
68 	r_str_ansi_filter (ansi_filtered, NULL, &cps, -1);
69 	entry = ansi_filtered;
70 	// first we separate the filter in words (include the terminator char
71 	// to avoid special handling of the last token)
72 	for (p = filter; p <= filter_end; p++) {
73 		if (*p == ' ' || *p == '\0') {
74 			const char *next_match, *entry_ptr = entry;
75 			char old_char = *p;
76 			int token_len;
77 
78 			// Ignoring consecutive spaces
79 			if (p == current_token) {
80 				current_token++;
81 				continue;
82 			}
83 			*p = 0;
84 			token_len = strlen (current_token);
85 			// look for all matches of the current_token in this entry
86 			while ((next_match = r_str_casestr (entry_ptr, current_token))) {
87 				int real_pos, filtered_pos = next_match - entry;
88 				int end_pos = cps[filtered_pos + token_len];
89 				for (real_pos = cps[filtered_pos];
90 					real_pos < end_pos && real_pos < mask_size;
91 					real_pos = cps[++filtered_pos]) {
92 					mask[real_pos] = 'x';
93 				}
94 				entry_ptr += token_len;
95 			}
96 			*p = old_char;
97 			if (entry_ptr == entry) {
98 				// the word is not present in the target
99 				free (cps);
100 				free (ansi_filtered);
101 				return false;
102 			}
103 			current_token = p + 1;
104 		}
105 	}
106 	free (cps);
107 	free (ansi_filtered);
108 	return true;
109 }
110 
111 
hud_filter(RList * list,char * user_input,int top_entry_n,int * current_entry_n,char ** selected_entry)112 static RList *hud_filter(RList *list, char *user_input, int top_entry_n, int *current_entry_n, char **selected_entry) {
113 	RListIter *iter;
114 	char *current_entry;
115 	char mask[HUD_BUF_SIZE];
116 	char *p, *x;
117 	int j, rows;
118 	(void) r_cons_get_size (&rows);
119 	int counter = 0;
120 	bool first_line = true;
121 	RList *res = r_list_newf (free);
122 	r_list_foreach (list, iter, current_entry) {
123 		memset (mask, 0, HUD_BUF_SIZE);
124 		if (*user_input && !__matchString (current_entry, user_input, mask, HUD_BUF_SIZE)) {
125 			continue;
126 		}
127 		if (++counter == rows + top_entry_n) {
128 			break;
129 		}
130 		// if the user scrolled down the list, do not print the first entries
131 		if (!top_entry_n || *current_entry_n >= top_entry_n) {
132 			// remove everything after a tab (in ??, it contains the commands)
133 			x = strchr (current_entry, '\t');
134 			if (x) {
135 				*x = 0;
136 			}
137 			p = strdup (current_entry);
138 			// if the filter is empty, print the entry and move on
139 			if (!user_input[0]) {
140 				r_list_append (res, r_str_newf (" %c %s", first_line? '-': ' ', p));
141 			} else {
142 				// otherwise we need to emphasize the matching part
143 				if (I (context->color_mode)) {
144 					int last_color_change = 0;
145 					int last_mask = 0;
146 					char *str = r_str_newf (" %c ", first_line? '-': ' ');
147 					// Instead of printing one char at the time
148 					// (which would be slow), we group substrings of the same color
149 					for (j = 0; p[j] && j < HUD_BUF_SIZE; j++) {
150 						if (mask[j] != last_mask) {
151 							char tmp = p[j];
152 							p[j] = 0;
153 							if (mask[j]) {
154 								str = r_str_appendf (str, Color_RESET "%s", p + last_color_change);
155 							} else {
156 								str = r_str_appendf (str, Color_GREEN "%s", p + last_color_change);
157 							}
158 							p[j] = tmp;
159 							last_color_change = j;
160 							last_mask = mask[j];
161 						}
162 					}
163 					if (last_mask) {
164 						str = r_str_appendf (str, Color_GREEN "%s"Color_RESET, p + last_color_change);
165 					} else {
166 						str = r_str_appendf (str, Color_RESET "%s", p + last_color_change);
167 					}
168 					r_list_append (res, str);
169 				} else {
170 					// Otherwise we print the matching characters uppercase
171 					for (j = 0; p[j]; j++) {
172 						if (mask[j]) {
173 							p[j] = toupper ((unsigned char) p[j]);
174 						}
175 					}
176 					r_list_append (res, r_str_newf (" %c %s", first_line? '-': ' ', p));
177 				}
178 			}
179 			// Clean up and restore the tab character (if any)
180 			free (p);
181 			if (x) {
182 				*x = '\t';
183 			}
184 			if (first_line) {
185 				*selected_entry = current_entry;
186 			}
187 			first_line = false;
188 		}
189 		(*current_entry_n)++;
190 
191 	}
192 	return res;
193 }
194 
mht_free_kv(HtPPKv * kv)195 static void mht_free_kv(HtPPKv *kv) {
196 	free (kv->key);
197 	r_list_free (kv->value);
198 }
199 
200 // Display a list of entries in the hud, filtered and emphasized based on the user input.
201 
202 #define HUD_CACHE 0
r_cons_hud(RList * list,const char * prompt)203 R_API char *r_cons_hud(RList *list, const char *prompt) {
204 	char user_input[HUD_BUF_SIZE + 1];
205 	char *selected_entry = NULL;
206 	RListIter *iter;
207 
208 	HtPP *ht = ht_pp_new (NULL, (HtPPKvFreeFunc)mht_free_kv, (HtPPCalcSizeV)strlen);
209 	RLineHud *hud = (RLineHud*) R_NEW (RLineHud);
210 	hud->activate = 0;
211 	hud->vi = 0;
212 	I(line)->echo = false;
213 	I(line)->hud = hud;
214 	user_input [0] = 0;
215 	user_input[HUD_BUF_SIZE] = 0;
216 	hud->top_entry_n = 0;
217 	r_cons_show_cursor (false);
218 	r_cons_enable_mouse (false);
219 	r_cons_clear ();
220 
221 	// Repeat until the user exits the hud
222 	for (;;) {
223 		r_cons_gotoxy (0, 0);
224 		hud->current_entry_n = 0;
225 
226 		if (hud->top_entry_n < 0) {
227 			hud->top_entry_n = 0;
228 		}
229 		selected_entry = NULL;
230 		if (prompt && *prompt) {
231 			r_cons_printf (">> %s\n", prompt);
232 		}
233 		r_cons_printf ("%d> %s|\n", hud->top_entry_n, user_input);
234 		char *row;
235 		RList *filtered_list = NULL;
236 
237 		bool found = false;
238 		filtered_list = ht_pp_find (ht, user_input, &found);
239 		if (!found) {
240 			filtered_list = hud_filter (list, user_input,
241 				hud->top_entry_n, &(hud->current_entry_n), &selected_entry);
242 #if HUD_CACHE
243 			ht_pp_insert (ht, user_input, filtered_list);
244 #endif
245 		}
246 		r_list_foreach (filtered_list, iter, row) {
247 			r_cons_printf ("%s\n", row);
248 		}
249 		if (!filtered_list->length) {				// hack to remove garbage value when list is empty
250 			printf ("%s", R_CONS_CLEAR_LINE);
251 		}
252 #if !HUD_CACHE
253 		r_list_free (filtered_list);
254 #endif
255 		r_cons_visual_flush ();
256 		(void) r_line_readline ();
257 		r_str_ncpy (user_input, I(line)->buffer.data, HUD_BUF_SIZE);
258 
259 		if (!hud->activate) {
260 			hud->top_entry_n = 0;
261 			if (hud->current_entry_n >= 1 ) {
262 				if (selected_entry) {
263 					R_FREE (I(line)->hud);
264 					I(line)->echo = true;
265 					r_cons_enable_mouse (false);
266 					r_cons_show_cursor (true);
267 					r_cons_set_raw (false);
268 					return strdup (selected_entry);
269 				}
270 			} else {
271 				goto _beach;
272 			}
273 		}
274 	}
275 _beach:
276 	R_FREE (I(line)->hud);
277 	I(line)->echo = true;
278 	r_cons_show_cursor (true);
279 	r_cons_enable_mouse (false);
280 	r_cons_set_raw (false);
281 	ht_pp_free (ht);
282 	return NULL;
283 }
284 
285 // Display the list of files in a directory
r_cons_hud_path(const char * path,int dir)286 R_API char *r_cons_hud_path(const char *path, int dir) {
287 	char *tmp, *ret = NULL;
288 	RList *files;
289 	if (path) {
290 		path = r_str_trim_head_ro (path);
291 		tmp = strdup (*path ? path : "./");
292 	} else {
293 		tmp = strdup ("./");
294 	}
295 	files = r_sys_dir (tmp);
296 	if (files) {
297 		ret = r_cons_hud (files, tmp);
298 		if (ret) {
299 			tmp = r_str_append (tmp, "/");
300 			tmp = r_str_append (tmp, ret);
301 			free (ret);
302 			ret = r_file_abspath (tmp);
303 			free (tmp);
304 			tmp = ret;
305 			if (r_file_is_directory (tmp)) {
306 				ret = r_cons_hud_path (tmp, dir);
307 				free (tmp);
308 				tmp = ret;
309 			}
310 		}
311 		r_list_free (files);
312 	} else {
313 		eprintf ("No files found\n");
314 	}
315 	if (!ret) {
316 		free (tmp);
317 		return NULL;
318 	}
319 	return tmp;
320 }
321 
r_cons_message(const char * msg)322 R_API char *r_cons_message(const char *msg) {
323 	int len = strlen (msg);
324 	int rows, cols = r_cons_get_size (&rows);
325 	r_cons_clear ();
326 	r_cons_gotoxy ((cols - len) / 2, rows / 2);
327 	r_cons_println (msg);
328 	r_cons_flush ();
329 	r_cons_gotoxy (0, rows - 2);
330 	r_cons_any_key (NULL);
331 	return NULL;
332 }
333