1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 :(before "End Primitive Recipe Declarations")
67 _BROWSE_TRACE,
68 :(before "End Primitive Recipe Numbers")
69 put(Recipe_ordinal, "$browse-trace", _BROWSE_TRACE);
70 :(before "End Primitive Recipe Checks")
71 case _BROWSE_TRACE: {
72 break;
73 }
74 :(before "End Primitive Recipe Implementations")
75 case _BROWSE_TRACE: {
76 start_trace_browser();
77 break;
78 }
79
80
81 :(after "Commandline Parsing")
82 if (argc == 3 && is_equal(argv[1], "browse-trace")) {
83 load_trace(argv[2]);
84 start_trace_browser();
85 return 0;
86 }
87
88 :(before "End Globals")
89 set<int> Visible;
90 int Top_of_screen = 0;
91 int Left_of_screen = 0;
92 int Last_printed_row = 0;
93 map<int, int> Trace_index;
94 string Current_search_pattern = "";
95 :(before "End Types")
96 enum search_direction { FORWARD, BACKWARD };
97 :(before "End Globals")
98 search_direction Current_search_direction = FORWARD;
99
100 :(code)
101 void start_trace_browser() {
102 if (!Trace_stream) return;
103 cerr << "computing min depth to display\n";
104 int min_depth = 9999;
105 for (int i = 0; i < SIZE(Trace_stream->past_lines); ++i) {
106 ¦ trace_line& curr_line = Trace_stream->past_lines.at(i);
107 ¦ if (curr_line.depth < min_depth) min_depth = curr_line.depth;
108 }
109 cerr << "min depth is " << min_depth << '\n';
110 cerr << "computing lines to display\n";
111 for (int i = 0; i < SIZE(Trace_stream->past_lines); ++i) {
112 ¦ if (Trace_stream->past_lines.at(i).depth == min_depth)
113 ¦ ¦ Visible.insert(i);
114 }
115 tb_init();
116 tb_clear();
117 Display_row = Display_column = 0;
118 Top_of_screen = 0;
119 refresh_screen_rows();
120 while (true) {
121 ¦ render();
122 ¦ int key = read_key();
123 ¦ if (key == 'q' || key == 'Q' || key == TB_KEY_CTRL_C) break;
124 ¦ if (key == 'j' || key == TB_KEY_ARROW_DOWN) {
125 ¦ ¦
126 ¦ ¦ if (Display_row < Last_printed_row) ++Display_row;
127 ¦ }
128 ¦ else if (key == 'k' || key == TB_KEY_ARROW_UP) {
129 ¦ ¦
130 ¦ ¦ if (Display_row > 0) --Display_row;
131 ¦ }
132 ¦ else if (key == 't') {
133 ¦ ¦
134 ¦ ¦ Display_row = 0;
135 ¦ }
136 ¦ else if (key == 'c') {
137 ¦ ¦
138 ¦ ¦ Display_row = tb_height()/2;
139 ¦ ¦ while (!contains_key(Trace_index, Display_row))
140 ¦ ¦ ¦ --Display_row;
141 ¦ }
142 ¦ else if (key == 'b') {
143 ¦ ¦
144 ¦ ¦ Display_row = tb_height()-1;
145 ¦ ¦ while (!contains_key(Trace_index, Display_row))
146 ¦ ¦ ¦ --Display_row;
147 ¦ }
148 ¦ else if (key == 'T') {
149 ¦ ¦
150 ¦ ¦ Top_of_screen = get(Trace_index, Display_row);
151 ¦ ¦ Display_row = 0;
152 ¦ ¦ refresh_screen_rows();
153 ¦ }
154 ¦ else if (key == 'h' || key == TB_KEY_ARROW_LEFT) {
155 ¦ ¦
156 ¦ ¦ if (Left_of_screen > 0) --Left_of_screen;
157 ¦ }
158 ¦ else if (key == 'l' || key == TB_KEY_ARROW_RIGHT) {
159 ¦ ¦
160 ¦ ¦ ++Left_of_screen;
161 ¦ }
162 ¦ else if (key == 'H') {
163 ¦ ¦
164 ¦ ¦ Left_of_screen -= (tb_width() - 5);
165 ¦ ¦ if (Left_of_screen < 0) Left_of_screen = 0;
166 ¦ }
167 ¦ else if (key == 'L') {
168 ¦ ¦
169 ¦ ¦ Left_of_screen += (tb_width() - 5);
170 ¦ }
171 ¦ else if (key == 'J' || key == TB_KEY_PGDN || key == TB_KEY_CTRL_F) {
172 ¦ ¦
173 ¦ ¦ if (Trace_index.find(tb_height()-1) != Trace_index.end()) {
174 ¦ ¦ ¦ Top_of_screen = get(Trace_index, tb_height()-1) + 1;
175 ¦ ¦ ¦ refresh_screen_rows();
176 ¦ ¦ }
177 ¦ }
178 ¦ else if (key == 'K' || key == TB_KEY_PGUP || key == TB_KEY_CTRL_B) {
179 ¦ ¦
180 ¦ ¦ for (int screen_row = tb_height(); screen_row > 0 && Top_of_screen > 0; --screen_row) {
181 ¦ ¦ ¦ --Top_of_screen;
182 ¦ ¦ ¦ if (Top_of_screen <= 0) break;
183 ¦ ¦ ¦ while (Top_of_screen > 0 && !contains_key(Visible, Top_of_screen))
184 ¦ ¦ ¦ ¦ --Top_of_screen;
185 ¦ ¦ }
186 ¦ ¦ if (Top_of_screen >= 0)
187 ¦ ¦ ¦ refresh_screen_rows();
188 ¦ }
189 ¦ else if (key == 'g' || key == TB_KEY_HOME) {
190 ¦ ¦ ¦ Top_of_screen = 0;
191 ¦ ¦ ¦ Last_printed_row = 0;
192 ¦ ¦ ¦ Display_row = 0;
193 ¦ ¦ ¦ refresh_screen_rows();
194 ¦ }
195 ¦ else if (key == 'G' || key == TB_KEY_END) {
196 ¦ ¦
197 ¦ ¦ Top_of_screen = SIZE(Trace_stream->past_lines)-1;
198 ¦ ¦ for (int screen_row = tb_height(); screen_row > 0 && Top_of_screen > 0; --screen_row) {
199 ¦ ¦ ¦ --Top_of_screen;
200 ¦ ¦ ¦ if (Top_of_screen <= 0) break;
201 ¦ ¦ ¦ while (Top_of_screen > 0 && !contains_key(Visible, Top_of_screen))
202 ¦ ¦ ¦ ¦ --Top_of_screen;
203 ¦ ¦ }
204 ¦ ¦ refresh_screen_rows();
205 ¦ ¦
206 ¦ ¦ Display_row = Last_printed_row;
207 ¦ ¦ refresh_screen_rows();
208 ¦ }
209 ¦ else if (key == TB_KEY_CARRIAGE_RETURN) {
210 ¦ ¦
211 ¦ ¦ assert(contains_key(Trace_index, Display_row));
212 ¦ ¦ int start_index = get(Trace_index, Display_row);
213 ¦ ¦ int index = 0;
214 ¦ ¦
215 ¦ ¦ int min_depth = 9999;
216 ¦ ¦ for (index = start_index+1; index < SIZE(Trace_stream->past_lines); ++index) {
217 ¦ ¦ ¦ if (contains_key(Visible, index)) break;
218 ¦ ¦ ¦ trace_line& curr_line = Trace_stream->past_lines.at(index);
219 ¦ ¦ ¦ assert(curr_line.depth > Trace_stream->past_lines.at(start_index).depth);
220 ¦ ¦ ¦ if (curr_line.depth < min_depth) min_depth = curr_line.depth;
221 ¦ ¦ }
222 ¦ ¦ int end_index = index;
223 ¦ ¦
224 ¦ ¦ for (index = start_index; index < end_index; ++index) {
225 ¦ ¦ ¦ trace_line& curr_line = Trace_stream->past_lines.at(index);
226 ¦ ¦ ¦ if (curr_line.depth == min_depth) {
227 ¦ ¦ ¦ ¦ Visible.insert(index);
228 ¦ ¦ ¦ }
229 ¦ ¦ }
230 ¦ ¦ refresh_screen_rows();
231 ¦ }
232 ¦ else if (key == TB_KEY_BACKSPACE || key == TB_KEY_BACKSPACE2) {
233 ¦ ¦
234 ¦ ¦ assert(contains_key(Trace_index, Display_row));
235 ¦ ¦ int start_index = get(Trace_index, Display_row);
236 ¦ ¦ int index = 0;
237 ¦ ¦
238 ¦ ¦ int initial_depth = Trace_stream->past_lines.at(start_index).depth;
239 ¦ ¦ for (index = start_index+1; index < SIZE(Trace_stream->past_lines); ++index) {
240 ¦ ¦ ¦ if (!contains_key(Visible, index)) continue;
241 ¦ ¦ ¦ trace_line& curr_line = Trace_stream->past_lines.at(index);
242 ¦ ¦ ¦ if (curr_line.depth <= initial_depth) break;
243 ¦ ¦ }
244 ¦ ¦ int end_index = index;
245 ¦ ¦
246 ¦ ¦ for (index = start_index+1; index < end_index; ++index) {
247 ¦ ¦ ¦ Visible.erase(index);
248 ¦ ¦ }
249 ¦ ¦ refresh_screen_rows();
250 ¦ }
251 ¦ else if (key == '/') {
252 ¦ ¦ if (start_search_editor(FORWARD))
253 ¦ ¦ ¦ search(Current_search_pattern, Current_search_direction);
254 ¦ }
255 ¦ else if (key == '?') {
256 ¦ ¦ if (start_search_editor(BACKWARD))
257 ¦ ¦ ¦ search(Current_search_pattern, Current_search_direction);
258 ¦ }
259 ¦ else if (key == 'n') {
260 ¦ ¦ if (!Current_search_pattern.empty())
261 ¦ ¦ ¦ search(Current_search_pattern, Current_search_direction);
262 ¦ }
263 ¦ else if (key == 'N') {
264 ¦ ¦ if (!Current_search_pattern.empty())
265 ¦ ¦ ¦ search(Current_search_pattern, opposite(Current_search_direction));
266 ¦ }
267 }
268 tb_shutdown();
269 }
270
271 bool start_search_editor(search_direction dir) {
272 const int bottom_screen_line = tb_height()-1;
273
274 clear_line(bottom_screen_line);
275 int col = 0;
276 tb_set_cursor(col, bottom_screen_line);
277 tb_print('/', TB_WHITE, TB_BLACK);
278 ++col;
279 string pattern;
280 while (true) {
281 ¦ int key = read_key();
282 ¦ if (key == TB_KEY_ENTER) {
283 ¦ ¦ if (!pattern.empty()) {
284 ¦ ¦ ¦ Current_search_pattern = pattern;
285 ¦ ¦ ¦ Current_search_direction = dir;
286 ¦ ¦ }
287 ¦ ¦ return true;
288 ¦ }
289 ¦ else if (key == TB_KEY_ESC || key == TB_KEY_CTRL_C) {
290 ¦ ¦ return false;
291 ¦ }
292 ¦ else if (key == TB_KEY_ARROW_LEFT) {
293 ¦ ¦ if (col > 1) {
294 ¦ ¦ ¦ --col;
295 ¦ ¦ ¦ tb_set_cursor(col, bottom_screen_line);
296 ¦ ¦ }
297 ¦ }
298 ¦ else if (key == TB_KEY_ARROW_RIGHT) {
299 ¦ ¦ if (col-1 < SIZE(pattern)) {
300 ¦ ¦ ¦ ++col;
301 ¦ ¦ ¦ tb_set_cursor(col, bottom_screen_line);
302 ¦ ¦ }
303 ¦ }
304 ¦ else if (key == TB_KEY_HOME || key == TB_KEY_CTRL_A) {
305 ¦ ¦ col = 1;
306 ¦ ¦ tb_set_cursor(col, bottom_screen_line);
307 ¦ }
308 ¦ else if (key == TB_KEY_END || key == TB_KEY_CTRL_E) {
309 ¦ ¦ col = SIZE(pattern)+1;
310 ¦ ¦ tb_set_cursor(col, bottom_screen_line);
311 ¦ }
312 ¦ else if (key == TB_KEY_BACKSPACE || key == TB_KEY_BACKSPACE2) {
313 ¦ ¦ if (col > 1) {
314 ¦ ¦ ¦ assert(col <= SIZE(pattern)+1);
315 ¦ ¦ ¦ --col;
316 ¦ ¦ ¦
317 ¦ ¦ ¦ pattern.erase(col-1, 1);
318 ¦ ¦ ¦
319 ¦ ¦ ¦ tb_set_cursor(col, bottom_screen_line);
320 ¦ ¦ ¦ for (int x = col; x < SIZE(pattern)+1; ++x)
321 ¦ ¦ ¦ ¦ tb_print(pattern.at(x-1), TB_WHITE, TB_BLACK);
322 ¦ ¦ ¦ tb_print(' ', TB_WHITE, TB_BLACK);
323 ¦ ¦ ¦ tb_set_cursor(col, bottom_screen_line);
324 ¦ ¦ }
325 ¦ }
326 ¦ else if (key == TB_KEY_CTRL_K) {
327 ¦ ¦ int old_pattern_size = SIZE(pattern);
328 ¦ ¦ pattern.erase(col-1, SIZE(pattern) - (col-1));
329 ¦ ¦ tb_set_cursor(col, bottom_screen_line);
330 ¦ ¦ for (int x = col; x < old_pattern_size+1; ++x)
331 ¦ ¦ ¦ tb_print(' ', TB_WHITE, TB_BLACK);
332 ¦ ¦ tb_set_cursor(col, bottom_screen_line);
333 ¦ }
334 ¦ else if (key == TB_KEY_CTRL_U) {
335 ¦ ¦ int old_pattern_size = SIZE(pattern);
336 ¦ ¦ pattern.erase(0, col-1);
337 ¦ ¦ col = 1;
338 ¦ ¦ tb_set_cursor(col, bottom_screen_line);
339 ¦ ¦ for (int x = 1; x < SIZE(pattern)+1; ++x)
340 ¦ ¦ ¦ tb_print(pattern.at(x-1), TB_WHITE, TB_BLACK);
341 ¦ ¦ for (int x = SIZE(pattern)+1; x < old_pattern_size+1; ++x)
342 ¦ ¦ ¦ tb_print(' ', TB_WHITE, TB_BLACK);
343 ¦ ¦ tb_set_cursor(col, bottom_screen_line);
344 ¦ }
345 ¦ else if (key < 128) {
346 ¦ ¦
347 ¦ ¦ char c = static_cast<char>(key);
348 ¦ ¦ assert(col-1 >= 0);
349 ¦ ¦ assert(col-1 <= SIZE(pattern));
350 ¦ ¦ pattern.insert(col-1, 1, c);
351 ¦ ¦
352 ¦ ¦ for (int x = col; x < SIZE(pattern)+1; ++x)
353 ¦ ¦ ¦ tb_print(pattern.at(x-1), TB_WHITE, TB_BLACK);
354 ¦ ¦ ++col;
355 ¦ ¦ tb_set_cursor(col, bottom_screen_line);
356 ¦ }
357 }
358 }
359
360 void search(const string& pat, search_direction dir) {
361 if (dir == FORWARD) search_next(pat);
362 else search_previous(pat);
363 }
364
365 search_direction opposite(search_direction dir) {
366 if (dir == FORWARD) return BACKWARD;
367 else return FORWARD;
368 }
369
370 void search_next(const string& pat) {
371 for (int trace_index = get(Trace_index, Display_row)+1; trace_index < SIZE(Trace_stream->past_lines); ++trace_index) {
372 ¦ if (!contains_key(Visible, trace_index)) continue;
373 ¦ const trace_line& line = Trace_stream->past_lines.at(trace_index);
374 ¦ if (line.label.find(pat) == string::npos && line.contents.find(pat) == string::npos) continue;
375 ¦ Top_of_screen = trace_index;
376 ¦ Display_row = 0;
377 ¦ refresh_screen_rows();
378 ¦ return;
379 }
380 }
381
382 void search_previous(const string& pat) {
383 for (int trace_index = get(Trace_index, Display_row)-1; trace_index >= 0; --trace_index) {
384 ¦ if (!contains_key(Visible, trace_index)) continue;
385 ¦ const trace_line& line = Trace_stream->past_lines.at(trace_index);
386 ¦ if (line.label.find(pat) == string::npos && line.contents.find(pat) == string::npos) continue;
387 ¦ Top_of_screen = trace_index;
388 ¦ Display_row = 0;
389 ¦ refresh_screen_rows();
390 ¦ return;
391 }
392 }
393
394 void clear_line(int screen_row) {
395 tb_set_cursor(0, screen_row);
396 for (int col = 0; col < tb_width(); ++col)
397 ¦ tb_print(' ', TB_WHITE, TB_BLACK);
398 tb_set_cursor(0, screen_row);
399 }
400
401
402 void refresh_screen_rows() {
403 int screen_row = 0, index = 0;
404 Trace_index.clear();
405 for (screen_row = 0, index = Top_of_screen; screen_row < tb_height() && index < SIZE(Trace_stream->past_lines); ++screen_row, ++index) {
406 ¦
407 ¦ while (!contains_key(Visible, index)) {
408 ¦ ¦ ++index;
409 ¦ ¦ if (index >= SIZE(Trace_stream->past_lines)) goto done;
410 ¦ }
411 ¦ assert(index < SIZE(Trace_stream->past_lines));
412 ¦ put(Trace_index, screen_row, index);
413 }
414 done:;
415 }
416
417 void render() {
418 int screen_row = 0;
419 for (screen_row = 0; screen_row < tb_height(); ++screen_row) {
420 ¦ if (!contains_key(Trace_index, screen_row)) break;
421 ¦ trace_line& curr_line = Trace_stream->past_lines.at(get(Trace_index, screen_row));
422 ¦ ostringstream out;
423 ¦ out << std::setw(4) << curr_line.depth << ' ' << curr_line.label << ": " << curr_line.contents;
424 ¦ if (screen_row < tb_height()-1) {
425 ¦ ¦ int delta = lines_hidden(screen_row);
426 ¦ ¦
427 ¦ ¦ if (delta > 1) {
428 ¦ ¦ ¦ if (delta > 999) out << static_cast<char>(1);
429 ¦ ¦ ¦ out << " (" << delta << ")";
430 ¦ ¦ ¦ if (delta > 999) out << static_cast<char>(2);
431 ¦ ¦ }
432 ¦ }
433 ¦ render_line(screen_row, out.str(), screen_row == Display_row);
434 }
435
436 Last_printed_row = screen_row-1;
437 for (; screen_row < tb_height(); ++screen_row)
438 ¦ render_line(screen_row, "~", false);
439
440 tb_set_cursor(0, Display_row);
441 }
442
443 int lines_hidden(int screen_row) {
444 assert(contains_key(Trace_index, screen_row));
445 if (!contains_key(Trace_index, screen_row+1))
446 ¦ return SIZE(Trace_stream->past_lines) - get(Trace_index, screen_row);
447 else
448 ¦ return get(Trace_index, screen_row+1) - get(Trace_index, screen_row);
449 }
450
451 void render_line(int screen_row, const string& s, bool cursor_line) {
452 int col = 0;
453 int color = TB_WHITE;
454 int background_color = cursor_line ? 240 : TB_BLACK;
455 vector<pair<size_t, size_t> > highlight_ranges = find_all_occurrences(s, Current_search_pattern);
456 tb_set_cursor(0, screen_row);
457 for (col = 0; col < tb_width() && col+Left_of_screen < SIZE(s); ++col) {
458 ¦ char c = s.at(col+Left_of_screen);
459 ¦ if (c == '\n') c = ';';
460 ¦
461 ¦ if (c == '\1') { color = 1; c = ' '; }
462 ¦ if (c == '\2') { color = TB_WHITE; c = ' '; }
463 ¦ if (in_range(highlight_ranges, col+Left_of_screen))
464 ¦ ¦ tb_print(c, TB_BLACK, 11);
465 ¦ else
466 ¦ ¦ tb_print(c, color, background_color);
467 }
468 for (; col < tb_width(); ++col)
469 ¦ tb_print(' ', TB_WHITE, background_color);
470 }
471
472 vector<pair<size_t, size_t> > find_all_occurrences(const string& s, const string& pat) {
473 vector<pair<size_t, size_t> > result;
474 if (pat.empty()) return result;
475 size_t idx = 0;
476 while (true) {
477 ¦ size_t next_idx = s.find(pat, idx);
478 ¦ if (next_idx == string::npos) break;
479 ¦ result.push_back(pair<size_t, size_t>(next_idx, next_idx+SIZE(pat)));
480 ¦ idx = next_idx+SIZE(pat);
481 }
482 return result;
483 }
484
485 bool in_range(const vector<pair<size_t, size_t> >& highlight_ranges, size_t idx) {
486 for (int i = 0; i < SIZE(highlight_ranges); ++i) {
487 ¦ if (idx >= highlight_ranges.at(i).first && idx < highlight_ranges.at(i).second)
488 ¦ ¦ return true;
489 ¦ if (idx < highlight_ranges.at(i).second) break;
490 }
491 return false;
492 }
493
494 void load_trace(const char* filename) {
495 ifstream tin(filename);
496 if (!tin) {
497 ¦ cerr << "no such file: " << filename << '\n';
498 ¦ exit(1);
499 }
500 Trace_stream = new trace_stream;
501 while (has_data(tin)) {
502 ¦ tin >> std::noskipws;
503 ¦ ¦ skip_whitespace_but_not_newline(tin);
504 ¦ ¦ if (!isdigit(tin.peek())) {
505 ¦ ¦ ¦ string dummy;
506 ¦ ¦ ¦ getline(tin, dummy);
507 ¦ ¦ ¦ continue;
508 ¦ ¦ }
509 ¦ tin >> std::skipws;
510 ¦ int depth;
511 ¦ tin >> depth;
512 ¦ string label;
513 ¦ tin >> label;
514 ¦ if (*--label.end() == ':') label.erase(--label.end());
515 ¦ string line;
516 ¦ getline(tin, line);
517 ¦ Trace_stream->past_lines.push_back(trace_line(depth, label, line));
518 }
519 cerr << "lines read: " << Trace_stream->past_lines.size() << '\n';
520 }
521
522 int read_key() {
523 tb_event event;
524 do {
525 ¦ tb_poll_event(&event);
526 } while (event.type != TB_EVENT_KEY);
527 return event.key ? event.key : event.ch;
528 }