Implement watch view for evaluating and displaying expressions dynamically

Author Mitja Felicijan <mitja.felicijan@gmail.com> 2026-01-17 02:09:19 +0100
Committer Mitja Felicijan <mitja.felicijan@gmail.com> 2026-01-17 02:09:19 +0100
Commit 7288e081d7ca39d6e5501d74f912fd92f9aee58d (patch)
-rw-r--r-- tdbg.cpp 128
1 files changed, 116 insertions, 12 deletions
diff --git a/tdbg.cpp b/tdbg.cpp
...
20
struct LayoutConfig {
20
struct LayoutConfig {
21
	int log_height = 10;
21
	int log_height = 10;
22
	int status_height = 1;
22
	int status_height = 1;
23
	int breakpoints_height = 10;
23
	int watch_height = 15;
24
	int sidebar_width = 50;
24
	int sidebar_width = 50;
25
} layout_config;
25
} layout_config;
26
  
26
  
...
32
enum InputMode {
32
enum InputMode {
33
	INPUT_MODE_NORMAL,
33
	INPUT_MODE_NORMAL,
34
	INPUT_MODE_BREAKPOINT,
34
	INPUT_MODE_BREAKPOINT,
35
	INPUT_MODE_VARIABLE
35
	INPUT_MODE_VARIABLE,
  
36
	INPUT_MODE_WATCH
36
};
37
};
37
  
38
  
38
struct LLDBGuard {
39
struct LLDBGuard {
...
160
	return '?';
161
	return '?';
161
}
162
}
162
  
163
  
163
void collect_variables_recursive(SBValue val, int indent, std::vector<VarLine>& lines, int width) {
164
void collect_variables_recursive(SBValue val, int indent, std::vector<VarLine>& lines, int width, const std::string& name_override = "") {
164
	if (indent > 3) return;
165
	if (indent > 3) return;
165
  
166
  
166
	std::string original_name = val.GetName() ? val.GetName() : "";
167
	std::string original_name = name_override.empty() ? (val.GetName() ? val.GetName() : "") : name_override;
167
	char type_char = get_type_char(val.GetType());
168
	char type_char = get_type_char(val.GetType());
168
	std::string prefix = std::string("(") + type_char + ") ";
169
	std::string prefix = std::string("(") + type_char + ") ";
169
  
170
  
...
503
	}
504
	}
504
}
505
}
505
  
506
  
  
507
void draw_watch_view(SBFrame& frame, int x, int y, int w, int h, const std::vector<std::string>& expressions, int scroll_offset) {
  
508
	draw_box(x, y, w, h, "Watch");
  
509
	int cx = x + 1;
  
510
	int cy = y + 1;
  
511
	int ch = h - 2;
  
512
	int cw = w - 2;
  
513
  
  
514
	if (expressions.empty()) {
  
515
		draw_text(cx, cy, TB_DEFAULT, TB_DEFAULT, "No watch expressions.");
  
516
		return;
  
517
	}
  
518
  
  
519
	std::vector<VarLine> lines;
  
520
	for (const auto& expr : expressions) {
  
521
		SBValue val = frame.EvaluateExpression(expr.c_str());
  
522
		if (val.IsValid() && !val.GetError().Fail()) {
  
523
			collect_variables_recursive(val, 0, lines, cw, expr);
  
524
		} else {
  
525
			VarLine vl;
  
526
			vl.text = expr + " = (error)";
  
527
			if (val.GetError().GetCString()) {
  
528
				vl.text += ": " + std::string(val.GetError().GetCString());
  
529
			}
  
530
			if ((int)vl.text.length() > cw) vl.text = vl.text.substr(0, cw - 3) + "...";
  
531
			vl.indent = 0;
  
532
			vl.prefix_start = 0;
  
533
			vl.prefix_end = 0;
  
534
			lines.push_back(vl);
  
535
		}
  
536
	}
  
537
  
  
538
	int total_lines = (int)lines.size();
  
539
	int display_count = std::min(total_lines, ch);
  
540
  
  
541
	for (int i = 0; i < display_count; ++i) {
  
542
		int line_idx = scroll_offset + i;
  
543
		if (line_idx < 0 || line_idx >= total_lines) continue;
  
544
  
  
545
		const VarLine& vl = lines[line_idx];
  
546
		for (int j = 0; j < (int)vl.text.length() && j < cw; ++j) {
  
547
			uint16_t fg = TB_DEFAULT;
  
548
			if (j >= vl.prefix_start && j < vl.prefix_end) {
  
549
				fg = TB_BLACK | TB_BOLD;
  
550
			}
  
551
			tb_set_cell(cx + j, cy + i, vl.text[j], fg, TB_DEFAULT);
  
552
		}
  
553
	}
  
554
  
  
555
	// Draw scrollbar
  
556
	if (total_lines > ch) {
  
557
		int thumb_height = std::max(1, (ch * ch) / total_lines);
  
558
		int max_scroll = total_lines - ch;
  
559
		double scroll_percent = (double)scroll_offset / (double)max_scroll;
  
560
		int thumb_pos = (ch - thumb_height) * scroll_percent;
  
561
  
  
562
		for (int i = 0; i < ch; ++i) {
  
563
			uint32_t cell_char = SCROLLBAR_LINE;
  
564
			uint16_t fg = TB_DEFAULT;
  
565
			if (i >= thumb_pos && i < thumb_pos + thumb_height) {
  
566
				cell_char = SCROLLBAR_THUMB;
  
567
				fg = TB_WHITE;
  
568
			}
  
569
			tb_set_cell(x + w - 1, cy + i, cell_char, fg, TB_DEFAULT);
  
570
		}
  
571
	}
  
572
}
  
573
  
506
SBBreakpoint create_breakpoint(SBTarget& target, const std::string& input) {
574
SBBreakpoint create_breakpoint(SBTarget& target, const std::string& input) {
507
	SBBreakpoint bp;
575
	SBBreakpoint bp;
508
	size_t colon_pos = input.rfind(':');
576
	size_t colon_pos = input.rfind(':');
...
523
}
591
}
524
  
592
  
525
void draw_log_view(int x, int y, int w, int h, const std::vector<std::string>& log_buffer, InputMode mode, const std::string& input_buffer, int scroll_offset) {
593
void draw_log_view(int x, int y, int w, int h, const std::vector<std::string>& log_buffer, InputMode mode, const std::string& input_buffer, int scroll_offset) {
526
	bool input_mode = (mode == INPUT_MODE_BREAKPOINT || mode == INPUT_MODE_VARIABLE);
594
	bool input_mode = (mode == INPUT_MODE_BREAKPOINT || mode == INPUT_MODE_VARIABLE || mode == INPUT_MODE_WATCH);
527
	std::string title = input_mode ? "Input (Esc to Cancel)" : "Logs";
595
	std::string title = input_mode ? "Input (Esc to Cancel)" : "Logs";
528
	if (!input_mode && scroll_offset > 0) {
596
	if (!input_mode && scroll_offset > 0) {
529
		title += " (Scrolled up: " + std::to_string(scroll_offset) + ")";
597
		title += " (Scrolled up: " + std::to_string(scroll_offset) + ")";
...
539
		std::string prompt;
607
		std::string prompt;
540
		if (mode == INPUT_MODE_BREAKPOINT) prompt = "Add Breakpoint: ";
608
		if (mode == INPUT_MODE_BREAKPOINT) prompt = "Add Breakpoint: ";
541
		else if (mode == INPUT_MODE_VARIABLE) prompt = "Print Variable: ";
609
		else if (mode == INPUT_MODE_VARIABLE) prompt = "Print Variable: ";
  
610
		else if (mode == INPUT_MODE_WATCH) prompt = "Watch Variable: ";
542
  
611
  
543
		prompt += input_buffer;
612
		prompt += input_buffer;
544
		if ((int)prompt.length() > cw) prompt = prompt.substr(prompt.length() - cw);
613
		if ((int)prompt.length() > cw) prompt = prompt.substr(prompt.length() - cw);
...
591
	}
660
	}
592
  
661
  
593
	state_str += (mode == INPUT_MODE_NORMAL)
662
	state_str += (mode == INPUT_MODE_NORMAL)
594
		? " | r=Run, b=Add breakpoint, p=Print, n=Step Over, s=Step Into, o=Step Out, c=Continue, q=Quit"
663
		? " | r=Run, b=Add breakpoint, p=Print, w=Watch, n=Step Over, s=Step Into, o=Step Out, c=Continue, q=Quit"
595
		: " | Enter=Confirm, Esc=Cancel";
664
		: " | Enter=Confirm, Esc=Cancel";
596
  
665
  
597
	for (int x = 0; x < width; ++x) {
666
	for (int x = 0; x < width; ++x) {
...
653
	std::string input_buffer;
722
	std::string input_buffer;
654
	std::string current_source_filename;
723
	std::string current_source_filename;
655
	std::vector<std::string> log_buffer;
724
	std::vector<std::string> log_buffer;
  
725
	std::vector<std::string> watch_expressions;
656
	int log_scroll_offset = 0;
726
	int log_scroll_offset = 0;
657
	int locals_scroll_offset = 0;
727
	int locals_scroll_offset = 0;
  
728
	int watch_scroll_offset = 0;
658
	int source_scroll_offset = 0;
729
	int source_scroll_offset = 0;
659
	uint64_t last_pc = 0;
730
	uint64_t last_pc = 0;
660
	SourceCache source_cache;
731
	SourceCache source_cache;
...
669
		int height = tb_height();
740
		int height = tb_height();
670
		int main_window_height = height - layout_config.log_height - layout_config.status_height;
741
		int main_window_height = height - layout_config.log_height - layout_config.status_height;
671
		int split_x = width - layout_config.sidebar_width;
742
		int split_x = width - layout_config.sidebar_width;
672
		int locals_window_height = main_window_height - layout_config.breakpoints_height;
743
		int locals_window_height = main_window_height - layout_config.watch_height;
673
  
744
  
674
		SBFrame frame;
745
		SBFrame frame;
675
		if (process.IsValid() && process.GetState() != eStateExited) {
746
		if (process.IsValid() && process.GetState() != eStateExited) {
...
705
  
776
  
706
		draw_source_view(frame, 0, 0, split_x, main_window_height, source_cache, source_scroll_offset);
777
		draw_source_view(frame, 0, 0, split_x, main_window_height, source_cache, source_scroll_offset);
707
		draw_variables_view(frame, split_x, 0, layout_config.sidebar_width, locals_window_height, locals_scroll_offset);
778
		draw_variables_view(frame, split_x, 0, layout_config.sidebar_width, locals_window_height, locals_scroll_offset);
708
		draw_breakpoints_view(target, split_x, locals_window_height, layout_config.sidebar_width, layout_config.breakpoints_height);
779
		draw_watch_view(frame, split_x, locals_window_height, layout_config.sidebar_width, layout_config.watch_height, watch_expressions, watch_scroll_offset);
709
		draw_log_view(0, main_window_height, width, layout_config.log_height, log_buffer, mode, input_buffer, log_scroll_offset);
780
		draw_log_view(0, main_window_height, split_x, layout_config.log_height, log_buffer, mode, input_buffer, log_scroll_offset);
  
781
		draw_breakpoints_view(target, split_x, main_window_height, layout_config.sidebar_width, layout_config.log_height);
710
		draw_status_bar(process, mode, width, height);
782
		draw_status_bar(process, mode, width, height);
711
  
783
  
712
		tb_present();
784
		tb_present();
...
772
					} else if (ev.ch == 'p') {
844
					} else if (ev.ch == 'p') {
773
						mode = INPUT_MODE_VARIABLE;
845
						mode = INPUT_MODE_VARIABLE;
774
						input_buffer.clear();
846
						input_buffer.clear();
  
847
					} else if (ev.ch == 'w') {
  
848
						mode = INPUT_MODE_WATCH;
  
849
						input_buffer.clear();
775
					} else {
850
					} else {
776
						if (process.IsValid() && process.GetState() == eStateStopped) {
851
						if (process.IsValid() && process.GetState() == eStateStopped) {
777
							switch (ev.ch) {
852
							switch (ev.ch) {
...
789
							}
864
							}
790
						}
865
						}
791
					}
866
					}
792
				} else if (mode == INPUT_MODE_BREAKPOINT || mode == INPUT_MODE_VARIABLE) {
867
				} else if (mode == INPUT_MODE_BREAKPOINT || mode == INPUT_MODE_VARIABLE || mode == INPUT_MODE_WATCH) {
793
					if (ev.key == TB_KEY_ESC) {
868
					if (ev.key == TB_KEY_ESC) {
794
						mode = INPUT_MODE_NORMAL;
869
						mode = INPUT_MODE_NORMAL;
795
						input_buffer.clear();
870
						input_buffer.clear();
...
818
										log_msg(log_buffer, err);
893
										log_msg(log_buffer, err);
819
									}
894
									}
820
								}
895
								}
  
896
							} else if (mode == INPUT_MODE_WATCH) {
  
897
								watch_expressions.push_back(input_buffer);
  
898
								log_msg(log_buffer, "Added to watch: " + input_buffer);
821
							}
899
							}
822
						}
900
						}
823
						mode = INPUT_MODE_NORMAL;
901
						mode = INPUT_MODE_NORMAL;
...
834
				// Log window scrolling
912
				// Log window scrolling
835
				int log_start_y = main_window_height;
913
				int log_start_y = main_window_height;
836
				int log_end_y = tb_height() - layout_config.status_height;
914
				int log_end_y = tb_height() - layout_config.status_height;
837
				if (ev.y >= log_start_y && ev.y < log_end_y) {
915
				if (ev.x < split_x && ev.y >= log_start_y && ev.y < log_end_y) {
838
					if (ev.key == TB_KEY_MOUSE_WHEEL_UP) {
916
					if (ev.key == TB_KEY_MOUSE_WHEEL_UP) {
839
						int max_scroll = std::max(0, (int)log_buffer.size() - (layout_config.log_height - 2));
917
						int max_scroll = std::max(0, (int)log_buffer.size() - (layout_config.log_height - 2));
840
						if (log_scroll_offset < max_scroll) {
918
						if (log_scroll_offset < max_scroll) {
...
876
  
954
  
877
				// Locals window scrolling
955
				// Locals window scrolling
878
				int split_x = tb_width() - layout_config.sidebar_width;
956
				int split_x = tb_width() - layout_config.sidebar_width;
879
				int locals_window_height = main_window_height - layout_config.breakpoints_height;
957
				int locals_window_height = main_window_height - layout_config.watch_height;
880
				if (ev.x >= split_x && ev.y < locals_window_height) {
958
				if (ev.x >= split_x && ev.y < locals_window_height) {
881
					std::vector<VarLine> lines;
959
					std::vector<VarLine> lines;
882
					if (frame.IsValid()) {
960
					if (frame.IsValid()) {
...
894
					} else if (ev.key == TB_KEY_MOUSE_WHEEL_DOWN) {
972
					} else if (ev.key == TB_KEY_MOUSE_WHEEL_DOWN) {
895
						if (locals_scroll_offset < max_scroll) {
973
						if (locals_scroll_offset < max_scroll) {
896
							locals_scroll_offset++;
974
							locals_scroll_offset++;
  
975
						}
  
976
					}
  
977
				}
  
978
  
  
979
				// Watch window scrolling
  
980
				if (ev.x >= split_x && ev.y >= locals_window_height && ev.y < main_window_height) {
  
981
					std::vector<VarLine> lines;
  
982
					for (const auto& expr : watch_expressions) {
  
983
						SBValue val = frame.EvaluateExpression(expr.c_str());
  
984
						if (val.IsValid() && !val.GetError().Fail()) {
  
985
							collect_variables_recursive(val, 0, lines, layout_config.sidebar_width - 2, expr);
  
986
						} else {
  
987
							VarLine vl;
  
988
							vl.text = expr + " = (error)";
  
989
							lines.push_back(vl);
  
990
						}
  
991
					}
  
992
					int max_scroll = std::max(0, (int)lines.size() - (layout_config.watch_height - 2));
  
993
  
  
994
					if (ev.key == TB_KEY_MOUSE_WHEEL_UP) {
  
995
						if (watch_scroll_offset > 0) {
  
996
							watch_scroll_offset--;
  
997
						}
  
998
					} else if (ev.key == TB_KEY_MOUSE_WHEEL_DOWN) {
  
999
						if (watch_scroll_offset < max_scroll) {
  
1000
							watch_scroll_offset++;
897
						}
1001
						}
898
					}
1002
					}
899
				}
1003
				}
...