1 //: A debugging helper that lets you zoom in/out on a trace.
  2 
  3 //: browse the trace we just created
  4 :(before "End Primitive Recipe Declarations")
  5 _BROWSE_TRACE,
  6 :(before "End Primitive Recipe Numbers")
  7 put(Recipe_ordinal, "$browse-trace", _BROWSE_TRACE);
  8 :(before "End Primitive Recipe Checks")
  9 case _BROWSE_TRACE: {
 10   break;
 11 }
 12 :(before "End Primitive Recipe Implementations")
 13 case _BROWSE_TRACE: {
 14   start_trace_browser();
 15   break;
 16 }
 17 
 18 //: browse a trace loaded from a file
 19 :(after "Commandline Parsing")
 20 if (argc == 3 && is_equal(argv[1], "browse-trace")) {
 21   load_trace(argv[2]);
 22   start_trace_browser();
 23   return 0;
 24 }
 25 
 26 :(before "End Globals")
 27 set<int> Visible;
 28 int Top_of_screen = 0;
 29 int Last_printed_row = 0;
 30 map<int, int> Trace_index;  // screen row -> trace index
 31 
 32 :(code)
 33 void start_trace_browser() {
 34   if (!Trace_stream) return;
 35   cerr << "computing min depth to display\n";
 36   int min_depth = 9999;
 37   for (int i = 0; i < SIZE(Trace_stream->past_lines); ++i) {
 38     trace_line& curr_line = Trace_stream->past_lines.at(i);
 39     if (curr_line.depth < min_depth) min_depth = curr_line.depth;
 40   }
 41   cerr << "min depth is " << min_depth << '\n';
 42   cerr << "computing lines to display\n";
 43   for (int i = 0; i < SIZE(Trace_stream->past_lines); ++i) {
 44     if (Trace_stream->past_lines.at(i).depth == min_depth)
 45       Visible.insert(i);
 46   }
 47   tb_init();
 48   Display_row = Display_column = 0;
 49   tb_event event;
 50   Top_of_screen = 0;
 51   refresh_screen_rows();
 52   while (true) {
 53     render();
 54     do {
 55       tb_poll_event(&event);
 56     } while (event.type != TB_EVENT_KEY);
 57     int key = event.key ? event.key : event.ch;
 58     if (key == 'q' || key == 'Q') break;
 59     if (key == 'j' || key == TB_KEY_ARROW_DOWN) {
 60       // move cursor one line down
 61       if (Display_row < Last_printed_row) ++Display_row;
 62     }
 63     if (key == 'k' || key == TB_KEY_ARROW_UP) {
 64       // move cursor one line up
 65       if (Display_row > 0) --Display_row;
 66     }
 67     if (key == 'H') {
 68       // move cursor to top of screen
 69       Display_row = 0;
 70     }
 71     if (key == 'M') {
 72       // move cursor to center of screen
 73       Display_row = tb_height()/2;
 74     }
 75     if (key == 'L') {
 76       // move cursor to bottom of screen
 77       Display_row = tb_height()-1;
 78     }
 79     if (key == 'J' || key == TB_KEY_PGDN) {
 80       // page-down
 81       if (Trace_index.find(tb_height()-1) != Trace_index.end()) {
 82         Top_of_screen = get(Trace_index, tb_height()-1) + 1;
 83         refresh_screen_rows();
 84       }
 85     }
 86     if (key == 'K' || key == TB_KEY_PGUP) {
 87       // page-up is more convoluted
 88       for (int screen_row = tb_height(); screen_row > 0 && Top_of_screen > 0; --screen_row) {
 89         --Top_of_screen;
 90         if (Top_of_screen <= 0) break;
 91         while (Top_of_screen > 0 && !contains_key(Visible, Top_of_screen))
 92           --Top_of_screen;
 93       }
 94       if (Top_of_screen >= 0)
 95         refresh_screen_rows();
 96     }
 97     if (key == 'G') {
 98       // go to bottom of screen; largely like page-up, interestingly
 99       Top_of_screen = SIZE(Trace_stream->past_lines)-1;
100       for (int screen_row = tb_height(); screen_row > 0 && Top_of_screen > 0; --screen_row) {
101         --Top_of_screen;
102         if (Top_of_screen <= 0) break;
103         while (Top_of_screen > 0 && !contains_key(Visible, Top_of_screen))
104           --Top_of_screen;
105       }
106       refresh_screen_rows();
107       // move cursor to bottom
108       Display_row = Last_printed_row;
109       refresh_screen_rows();
110     }
111     if (key == TB_KEY_CARRIAGE_RETURN) {
112       // expand lines under current by one level
113       assert(contains_key(Trace_index, Display_row));
114       int start_index = get(Trace_index, Display_row);
115       int index = 0;
116       // simultaneously compute end_index and min_depth
117       int min_depth = 9999;
118       for (index = start_index+1; index < SIZE(Trace_stream->past_lines); ++index) {
119         if (contains_key(Visible, index)) break;
120         trace_line& curr_line = Trace_stream->past_lines.at(index);
121         assert(curr_line.depth > Trace_stream->past_lines.at(start_index).depth);
122         if (curr_line.depth < min_depth) min_depth = curr_line.depth;
123       }
124       int end_index = index;
125       // mark as visible all intervening indices at min_depth
126       for (index = start_index; index < end_index; ++index) {
127         trace_line& curr_line = Trace_stream->past_lines.at(index);
128         if (curr_line.depth == min_depth) {
129           Visible.insert(index);
130         }
131       }
132       refresh_screen_rows();
133     }
134     if (key == TB_KEY_BACKSPACE || key == TB_KEY_BACKSPACE2) {
135       // collapse all lines under current
136       assert(contains_key(Trace_index, Display_row));
137       int start_index = get(Trace_index, Display_row);
138       int index = 0;
139       // end_index is the next line at a depth same as or lower than start_index
140       int initial_depth = Trace_stream->past_lines.at(start_index).depth;
141       for (index = start_index+1; index < SIZE(Trace_stream->past_lines); ++index) {
142         if (!contains_key(Visible, index)) continue;
143         trace_line& curr_line = Trace_stream->past_lines.at(index);
144         if (curr_line.depth <= initial_depth) break;
145       }
146       int end_index = index;
147       // mark as visible all intervening indices at min_depth
148       for (index = start_index+1; index < end_index; ++index) {
149         Visible.erase(index);
150       }
151       refresh_screen_rows();
152     }
153   }
154   tb_shutdown();
155 }
156 
157 // update Trace_indices for each screen_row on the basis of Top_of_screen and Visible
158 void refresh_screen_rows() {
159   int screen_row = 0, index = 0;
160   Trace_index.clear();
161   for (screen_row = 0, index = Top_of_screen; screen_row < tb_height() && index < SIZE(Trace_stream->past_lines); ++screen_row, ++index) {
162     // skip lines without depth for now
163     while (!contains_key(Visible, index)) {
164       ++index;
165       if (index >= SIZE(Trace_stream->past_lines)) goto done;
166     }
167     assert(index < SIZE(Trace_stream->past_lines));
168     put(Trace_index, screen_row, index);
169   }
170 done:;
171 }
172 
173 void render() {
174   int screen_row = 0;
175   for (screen_row = 0; screen_row < tb_height(); ++screen_row) {
176     if (!contains_key(Trace_index, screen_row)) break;
177     trace_line& curr_line = Trace_stream->past_lines.at(get(Trace_index, screen_row));
178     ostringstream out;
179     out << std::setw(4) << curr_line.depth << ' ' << curr_line.label << ": " << curr_line.contents;
180     if (screen_row < tb_height()-1) {
181       int delta = lines_hidden(screen_row);
182       // home-brew escape sequence for red
183       if (delta > 999) out << static_cast<char>(1);
184       out << " (" << delta << ")";
185       if (delta > 999) out << static_cast<char>(2);
186     }
187     render_line(screen_row, out.str());
188   }
189   // clear rest of screen
190   Last_printed_row = screen_row-1;
191   for (; screen_row < tb_height(); ++screen_row) {
192     render_line(screen_row, "~");
193   }
194   // move cursor back to display row at the end
195   tb_set_cursor(0, Display_row);
196   tb_present();
197 }
198 
199 int lines_hidden(int screen_row) {
200   assert(contains_key(Trace_index, screen_row));
201   if (!contains_key(Trace_index, screen_row+1))
202     return SIZE(Trace_stream->past_lines) - get(Trace_index, screen_row);
203   else
204     return get(Trace_index, screen_row+1) - get(Trace_index, screen_row);
205 }
206 
207 void render_line(int screen_row, const string& s) {
208   int col = 0;
209   int color = TB_WHITE;
210   for (col = 0; col < tb_width() && col < SIZE(s); ++col) {
211     char c = s.at(col);  // todo: unicode
212     if (c == '\n') c = ';';  // replace newlines with semi-colons
213     // escapes. hack: can't start a line with them.
214     if (c == '\1') { color = /*red*/1; c = ' '; }
215     if (c == '\2') { color = TB_WHITE; c = ' '; }
216     tb_change_cell(col, screen_row, c, color, TB_BLACK);
217   }
218   for (; col < tb_width(); ++col) {
219     tb_change_cell(col, screen_row, ' ', TB_WHITE, TB_BLACK);
220   }
221 }
222 
223 void load_trace(const char* filename) {
224   ifstream tin(filename);
225   if (!tin) {
226     cerr << "no such file: " << filename << '\n';
227     exit(1);
228   }
229   Trace_stream = new trace_stream;
230   while (has_data(tin)) {
231     tin >> std::noskipws;
232       skip_whitespace_but_not_newline(tin);
233       if (!isdigit(tin.peek())) {
234         string dummy;
235         getline(tin, dummy);
236         continue;
237       }
238     tin >> std::skipws;
239     int depth;
240     tin >> depth;
241     string label;
242     tin >> label;
243     if (*--label.end() == ':') label.erase(--label.end());
244     string line;
245     getline(tin, line);
246     Trace_stream->past_lines.push_back(trace_line(depth, label, line));
247   }
248   cerr << "lines read: " << Trace_stream->past_lines.size() << '\n';
249 }