Added scrollable areas

Author Mitja Felicijan <mitja.felicijan@gmail.com> 2026-01-16 17:14:08 +0100
Committer Mitja Felicijan <mitja.felicijan@gmail.com> 2026-01-16 17:14:12 +0100
Commit a7711f8d0acccbce7d5e862112342940f8fe8b38 (patch)
-rw-r--r-- example.c 80
-rw-r--r-- tdbg.cpp 251
2 files changed, 277 insertions, 54 deletions
diff --git a/example.c b/example.c
1
#include <stdio.h>
1
#include <stdio.h>
2
#include <stdlib.h>
2
#include <stdlib.h>
3
  
3
  
  
4
typedef enum {
  
5
    STATUS_IDLE,
  
6
    STATUS_RUNNING,
  
7
    STATUS_STOPPED
  
8
} Status;
  
9
  
4
typedef struct {
10
typedef struct {
5
	int q;
11
    int x;
6
	int w;
12
    int y;
  
13
} Point;
  
14
  
  
15
typedef struct {
  
16
    Point top_left;
  
17
    Point bottom_right;
  
18
} Rectangle;
  
19
  
  
20
typedef struct {
  
21
    int q;
  
22
    int w;
7
} Bar;
23
} Bar;
8
  
24
  
9
int main(void) {
25
int add_numbers(int a, int b) {
10
	const char *myenv = getenv("MYENV");
26
    int result = a + b;
  
27
    return result;
  
28
}
11
  
29
  
12
	int a = 100;
30
int fibonacci(int n) {
13
	int b = 123;
31
    if (n <= 1) return n;
14
	int c = a + b;
32
    return fibonacci(n - 1) + fibonacci(n - 2);
  
33
}
15
  
34
  
16
	Bar bar = { .q = 565, .w = 949 };
35
void print_rectangle(Rectangle rect) {
  
36
    printf("> Rect: (%d, %d) to (%d, %d)\n",
  
37
           rect.top_left.x, rect.top_left.y,
  
38
           rect.bottom_right.x, rect.bottom_right.y);
  
39
}
17
  
40
  
18
	printf("> MYENV: %s\n", myenv);
41
int main(int argc, char **argv) {
19
	printf("> c: %d\n", c);
42
    const char *myenv = getenv("MYENV");
20
	printf("> bar.q: %d\n", bar.q);
  
21
  
43
  
22
	for (int i=0; i<3; i++) {
44
    int a = 100;
23
		printf("> loop %d\n", i);
45
    int b = 123;
24
	}
46
    int c = add_numbers(a, b);
25
  
47
  
26
	return 0;
48
    Bar bar = { .q = 565, .w = 949 };
  
49
    Status status = STATUS_RUNNING;
  
50
  
  
51
    Rectangle rect = {
  
52
        .top_left = { .x = 10, .y = 20 },
  
53
        .bottom_right = { .x = 50, .y = 80 }
  
54
    };
  
55
  
  
56
    printf("> MYENV: %s\n", myenv ? myenv : "NULL");
  
57
    printf("> c: %d (via add_numbers)\n", c);
  
58
    printf("> bar.q: %d\n", bar.q);
  
59
    printf("> status: %d\n", status);
  
60
  
  
61
    print_rectangle(rect);
  
62
  
  
63
    int fib5 = fibonacci(5);
  
64
    printf("> fib(5): %d\n", fib5);
  
65
  
  
66
    int arr[] = {10, 20, 30, 40, 50};
  
67
    int *ptr = arr;
  
68
    for (int i = 0; i < 5; i++) {
  
69
        printf("> arr[%d] = %d (via ptr: %d)\n", i, arr[i], *(ptr + i));
  
70
    }
  
71
  
  
72
    for (int i = 0; i < 3; i++) {
  
73
        printf("> loop %d\n", i);
  
74
    }
  
75
  
  
76
    return 0;
27
}
77
}
diff --git a/tdbg.cpp b/tdbg.cpp
...
22
const int BREAKPOINTS_WINDOW_HEIGHT = 10;
22
const int BREAKPOINTS_WINDOW_HEIGHT = 10;
23
const int SIDEBAR_WIDTH = 40;
23
const int SIDEBAR_WIDTH = 40;
24
  
24
  
  
25
// https://unicodeplus.com/U+2593
  
26
const uint32_t SCROLLBAR_THUMB = 0x2593; // Dark shade
  
27
const uint32_t SCROLLBAR_LINE = 0x2502;  // Vertical line
  
28
  
25
struct LLDBGuard {
29
struct LLDBGuard {
26
	LLDBGuard() { SBDebugger::Initialize(); }
30
	LLDBGuard() { SBDebugger::Initialize(); }
27
	~LLDBGuard() { SBDebugger::Terminate(); }
31
	~LLDBGuard() { SBDebugger::Terminate(); }
...
54
	MODE_NORMAL,
58
	MODE_NORMAL,
55
	MODE_INPUT_BREAKPOINT,
59
	MODE_INPUT_BREAKPOINT,
56
	MODE_INPUT_VARIABLE
60
	MODE_INPUT_VARIABLE
  
61
};
  
62
  
  
63
struct VarLine {
  
64
	std::string text;
  
65
	int indent;
  
66
	int prefix_start;
  
67
	int prefix_end;
57
};
68
};
58
  
69
  
59
std::string get_timestamp() {
70
std::string get_timestamp() {
...
145
	return '?';
156
	return '?';
146
}
157
}
147
  
158
  
148
void draw_variable_recursive(SBValue val, int indent, int x, int start_y, int &current_offset, int max_height, int width) {
159
void collect_variables_recursive(SBValue val, int indent, std::vector<VarLine>& lines, int width) {
149
	if (current_offset >= max_height || indent > 3) return;
160
	if (indent > 3) return;
150
  
161
  
151
	std::string original_name = val.GetName() ? val.GetName() : "";
162
	std::string original_name = val.GetName() ? val.GetName() : "";
152
	char type_char = get_type_char(val.GetType());
163
	char type_char = get_type_char(val.GetType());
...
161
	std::string content = original_name;
172
	std::string content = original_name;
162
	if (!value.empty()) content += " = " + value;
173
	if (!value.empty()) content += " = " + value;
163
  
174
  
164
	std::string line = indent_str + prefix + content;
175
	std::string line_text = indent_str + prefix + content;
165
	if ((int)line.length() > width) line = line.substr(0, width - 3) + "...";
176
	if ((int)line_text.length() > width) line_text = line_text.substr(0, width - 3) + "...";
166
  
177
  
167
	int prefix_start = indent * 2;
178
	VarLine vl;
168
	int prefix_end = prefix_start + 4; // length of "(x) "
179
	vl.text = line_text;
169
  
180
	vl.indent = indent;
170
	for (int i = 0; i < (int)line.length(); ++i) {
181
	vl.prefix_start = indent * 2;
171
		uint16_t fg = TB_DEFAULT;
182
	vl.prefix_end = vl.prefix_start + 4; // length of "(x) "
172
		if (i >= prefix_start && i < prefix_end) {
183
	lines.push_back(vl);
173
			fg = TB_BLACK | TB_BOLD;
  
174
		}
  
175
		tb_set_cell(x + i, start_y + current_offset, line[i], fg, TB_DEFAULT);
  
176
	}
  
177
  
  
178
	current_offset++;
  
179
  
184
  
180
	if (val.GetNumChildren() > 0) {
185
	if (val.GetNumChildren() > 0) {
181
		uint32_t n = val.GetNumChildren();
186
		uint32_t n = val.GetNumChildren();
182
		for (uint32_t i = 0; i < n; ++i) {
187
		for (uint32_t i = 0; i < n; ++i) {
183
			draw_variable_recursive(val.GetChildAtIndex(i), indent + 1, x, start_y, current_offset, max_height, width);
188
			collect_variables_recursive(val.GetChildAtIndex(i), indent + 1, lines, width);
184
		}
189
		}
185
	}
190
	}
186
}
191
}
...
210
	}
215
	}
211
}
216
}
212
  
217
  
213
void draw_variables_view(SBFrame &frame, int x, int y, int w, int h) {
218
void draw_variables_view(SBFrame &frame, int x, int y, int w, int h, int scroll_offset) {
214
	draw_box(x, y, w, h, "Locals");
219
	draw_box(x, y, w, h, "Locals");
215
  
220
  
216
	// Content area
221
	// Content area
...
224
		return;
229
		return;
225
	}
230
	}
226
  
231
  
  
232
	std::vector<VarLine> lines;
227
	SBValueList vars = frame.GetVariables(true, true, false, true);
233
	SBValueList vars = frame.GetVariables(true, true, false, true);
228
	int current_offset = 0;
234
	for (uint32_t i = 0; i < vars.GetSize(); ++i) {
  
235
		collect_variables_recursive(vars.GetValueAtIndex(i), 0, lines, cw);
  
236
	}
  
237
  
  
238
	int total_lines = (int)lines.size();
  
239
	int display_count = std::min(total_lines, ch);
229
  
240
  
230
	for (uint32_t i = 0; i < vars.GetSize(); ++i) {
241
	for (int i = 0; i < display_count; ++i) {
231
		draw_variable_recursive(vars.GetValueAtIndex(i), 0, cx, cy, current_offset, ch, cw);
242
		int line_idx = scroll_offset + i;
  
243
		if (line_idx < 0 || line_idx >= total_lines) continue;
  
244
  
  
245
		const VarLine& vl = lines[line_idx];
  
246
		for (int j = 0; j < (int)vl.text.length() && j < cw; ++j) {
  
247
			uint16_t fg = TB_DEFAULT;
  
248
			if (j >= vl.prefix_start && j < vl.prefix_end) {
  
249
				fg = TB_BLACK | TB_BOLD;
  
250
			}
  
251
			tb_set_cell(cx + j, cy + i, vl.text[j], fg, TB_DEFAULT);
  
252
		}
  
253
	}
  
254
  
  
255
	// Draw scrollbar
  
256
	if (total_lines > ch) {
  
257
		int thumb_height = std::max(1, (ch * ch) / total_lines);
  
258
		int max_scroll = total_lines - ch;
  
259
		double scroll_percent = (double)scroll_offset / (double)max_scroll;
  
260
		int thumb_pos = (ch - thumb_height) * scroll_percent;
  
261
  
  
262
		for (int i = 0; i < ch; ++i) {
  
263
			uint32_t cell_char = SCROLLBAR_LINE;
  
264
			uint16_t fg = TB_DEFAULT;
  
265
			if (i >= thumb_pos && i < thumb_pos + thumb_height) {
  
266
				cell_char = SCROLLBAR_THUMB;
  
267
				fg = TB_WHITE;
  
268
			}
  
269
			tb_set_cell(x + w - 1, cy + i, cell_char, fg, TB_DEFAULT);
  
270
		}
232
	}
271
	}
233
}
272
}
234
  
273
  
...
268
	return name;
307
	return name;
269
}
308
}
270
  
309
  
271
void draw_source_view(SBFrame &frame, int x, int y, int w, int h, SourceCache& cache) {
310
void draw_source_view(SBFrame &frame, int x, int y, int w, int h, SourceCache& cache, int scroll_offset) {
272
	draw_box(x, y, w, h, "Source");
311
	draw_box(x, y, w, h, "Source");
273
  
312
  
274
	int cx = x + 1;
313
	int cx = x + 1;
...
336
		return;
375
		return;
337
	}
376
	}
338
  
377
  
  
378
	int total_lines = (int)lines.size();
339
	int current_line = line_entry.GetLine();
379
	int current_line = line_entry.GetLine();
340
	int half_height = ch / 2;
  
341
	int start_line = std::max(1, current_line - half_height);
  
342
	int end_line = std::min((int)lines.size(), start_line + ch - 1);
  
343
	if (end_line - start_line + 1 < ch) {
  
344
		start_line = std::max(1, end_line - ch + 1);
  
345
	}
  
346
  
  
347
	for (int i = 0; i < ch; ++i) {
380
	for (int i = 0; i < ch; ++i) {
348
		int line_idx = start_line + i;
381
		int line_idx = scroll_offset + i + 1;
349
		if (line_idx > end_line) break;
382
		if (line_idx > total_lines) break;
350
  
383
  
351
		std::string src = lines[line_idx - 1];
384
		std::string src = lines[line_idx - 1];
352
		// Handle basic tab expansion (simple version)
385
		// Handle basic tab expansion (simple version)
...
380
			}
413
			}
381
		}
414
		}
382
	}
415
	}
  
416
  
  
417
	// Draw scrollbar
  
418
	if (total_lines > ch) {
  
419
		int thumb_height = std::max(1, (ch * ch) / total_lines);
  
420
		int max_scroll = total_lines - ch;
  
421
		double scroll_percent = (double)scroll_offset / (double)max_scroll;
  
422
		int thumb_pos = (ch - thumb_height) * scroll_percent;
  
423
  
  
424
		for (int i = 0; i < ch; ++i) {
  
425
			uint32_t cell_char = SCROLLBAR_LINE;
  
426
			uint16_t fg = TB_DEFAULT;
  
427
			if (i >= thumb_pos && i < thumb_pos + thumb_height) {
  
428
				cell_char = SCROLLBAR_THUMB;
  
429
				fg = TB_WHITE;
  
430
			}
  
431
			tb_set_cell(x + w - 1, cy + i, cell_char, fg, TB_DEFAULT);
  
432
		}
  
433
	}
383
}
434
}
384
  
435
  
385
void draw_breakpoints_view(SBTarget& target, int x, int y, int w, int h) {
436
void draw_breakpoints_view(SBTarget& target, int x, int y, int w, int h) {
...
421
	return target.BreakpointCreateByName(input.c_str());
472
	return target.BreakpointCreateByName(input.c_str());
422
}
473
}
423
  
474
  
424
void draw_log_view(int x, int y, int w, int h, const std::vector<std::string>& log_buffer, AppMode mode, const std::string& input_buffer) {
475
void draw_log_view(int x, int y, int w, int h, const std::vector<std::string>& log_buffer, AppMode mode, const std::string& input_buffer, int scroll_offset) {
425
	bool input_mode = (mode == MODE_INPUT_BREAKPOINT || mode == MODE_INPUT_VARIABLE);
476
	bool input_mode = (mode == MODE_INPUT_BREAKPOINT || mode == MODE_INPUT_VARIABLE);
426
	std::string title = input_mode ? "Input (Esc to Cancel)" : "Command & Log";
477
	std::string title = input_mode ? "Input (Esc to Cancel)" : "Logs";
  
478
	if (!input_mode && scroll_offset > 0) {
  
479
		title += " (Scrolled up: " + std::to_string(scroll_offset) + ")";
  
480
	}
427
	draw_box(x, y, w, h, title);
481
	draw_box(x, y, w, h, title);
428
  
482
  
429
	int cx = x + 1;
483
	int cx = x + 1;
...
441
		draw_text(cx, cy, TB_WHITE | TB_BOLD, TB_DEFAULT, prompt);
495
		draw_text(cx, cy, TB_WHITE | TB_BOLD, TB_DEFAULT, prompt);
442
		tb_set_cell(cx + prompt.length(), cy, '_', TB_WHITE | TB_BOLD | TB_REVERSE, TB_DEFAULT);
496
		tb_set_cell(cx + prompt.length(), cy, '_', TB_WHITE | TB_BOLD | TB_REVERSE, TB_DEFAULT);
443
	} else {
497
	} else {
444
		int log_lines_count = std::min((int)log_buffer.size(), ch);
498
		int total_logs = log_buffer.size();
445
		for (int i = 0; i < log_lines_count; ++i) {
499
		int display_count = std::min(total_logs, ch);
446
			const std::string& msg = log_buffer[log_buffer.size() - log_lines_count + i];
500
  
  
501
		for (int i = 0; i < display_count; ++i) {
  
502
			int log_idx = total_logs - display_count - scroll_offset + i;
  
503
			if (log_idx < 0 || log_idx >= total_logs) continue;
  
504
  
  
505
			const std::string& msg = log_buffer[log_idx];
447
			std::string disp = msg;
506
			std::string disp = msg;
448
			if ((int)disp.length() > cw) disp = disp.substr(0, cw);
507
			if ((int)disp.length() > cw) disp = disp.substr(0, cw);
449
			draw_text(cx, cy + i, TB_DEFAULT, TB_DEFAULT, disp);
508
			draw_text(cx, cy + i, TB_DEFAULT, TB_DEFAULT, disp);
  
509
		}
  
510
  
  
511
		// Draw scrollbar
  
512
		if (total_logs > ch) {
  
513
			int thumb_height = std::max(1, (ch * ch) / total_logs);
  
514
			int max_scroll = total_logs - ch;
  
515
			double scroll_percent = (double)scroll_offset / (double)max_scroll;
  
516
			int thumb_pos = (ch - thumb_height) * (1.0 - scroll_percent);
  
517
  
  
518
			for (int i = 0; i < ch; ++i) {
  
519
				uint32_t cell_char = SCROLLBAR_LINE;
  
520
				uint16_t fg = TB_DEFAULT;
  
521
				if (i >= thumb_pos && i < thumb_pos + thumb_height) {
  
522
					cell_char = SCROLLBAR_THUMB;
  
523
					fg = TB_WHITE;
  
524
				}
  
525
				tb_set_cell(x + w - 1, cy + i, cell_char, fg, TB_DEFAULT);
  
526
			}
450
		}
527
		}
451
	}
528
	}
452
}
529
}
...
507
	AppMode mode = MODE_NORMAL;
584
	AppMode mode = MODE_NORMAL;
508
	std::string input_buffer;
585
	std::string input_buffer;
509
	std::vector<std::string> log_buffer;
586
	std::vector<std::string> log_buffer;
  
587
	int log_scroll_offset = 0;
  
588
	int locals_scroll_offset = 0;
  
589
	int source_scroll_offset = 0;
  
590
	uint64_t last_pc = 0;
510
	SourceCache source_cache;
591
	SourceCache source_cache;
511
	log_buffer.push_back("Debugger started. Press 'b' to add breakpoint, 'r' to run.");
592
	log_buffer.push_back("Debugger started. Press 'b' to add breakpoint, 'r' to run.");
  
593
  
  
594
	tb_set_input_mode(TB_INPUT_ESC | TB_INPUT_MOUSE);
512
  
595
  
513
	while (running) {
596
	while (running) {
514
		tb_clear();
597
		tb_clear();
...
524
			thread = process.GetSelectedThread();
607
			thread = process.GetSelectedThread();
525
			if (thread.IsValid()) {
608
			if (thread.IsValid()) {
526
				frame = thread.GetSelectedFrame();
609
				frame = thread.GetSelectedFrame();
  
610
				if (frame.IsValid()) {
  
611
					uint64_t current_pc = frame.GetPC();
  
612
					if (current_pc != last_pc) {
  
613
						last_pc = current_pc;
  
614
						SBLineEntry le = frame.GetLineEntry();
  
615
						if (le.IsValid()) {
  
616
							std::string fullpath;
  
617
							if (le.GetFileSpec().GetDirectory()) {
  
618
								fullpath = std::string(le.GetFileSpec().GetDirectory()) + "/" + le.GetFileSpec().GetFilename();
  
619
							} else {
  
620
								fullpath = le.GetFileSpec().GetFilename();
  
621
							}
  
622
							const std::vector<std::string>& lines = source_cache.get_lines(fullpath);
  
623
							int total_lines = (int)lines.size();
  
624
							int ch = main_window_height - 2;
  
625
							source_scroll_offset = std::max(0, (int)le.GetLine() - ch / 2 - 1);
  
626
							if (source_scroll_offset + ch > total_lines) {
  
627
								source_scroll_offset = std::max(0, total_lines - ch);
  
628
							}
  
629
						}
  
630
					}
  
631
				}
527
			}
632
			}
528
		}
633
		}
529
  
634
  
530
		draw_source_view(frame, 0, 0, split_x, main_window_height, source_cache);
635
		draw_source_view(frame, 0, 0, split_x, main_window_height, source_cache, source_scroll_offset);
531
		draw_variables_view(frame, split_x, 0, SIDEBAR_WIDTH, locals_window_height);
636
		draw_variables_view(frame, split_x, 0, SIDEBAR_WIDTH, locals_window_height, locals_scroll_offset);
532
		draw_breakpoints_view(target, split_x, locals_window_height, SIDEBAR_WIDTH, BREAKPOINTS_WINDOW_HEIGHT);
637
		draw_breakpoints_view(target, split_x, locals_window_height, SIDEBAR_WIDTH, BREAKPOINTS_WINDOW_HEIGHT);
533
		draw_log_view(0, main_window_height, width, LOG_WINDOW_HEIGHT, log_buffer, mode, input_buffer);
638
		draw_log_view(0, main_window_height, width, LOG_WINDOW_HEIGHT, log_buffer, mode, input_buffer, log_scroll_offset);
534
		draw_status_bar(process, mode, width, height);
639
		draw_status_bar(process, mode, width, height);
535
  
640
  
536
		tb_present();
641
		tb_present();
...
611
						if (!input_buffer.empty()) input_buffer.pop_back();
716
						if (!input_buffer.empty()) input_buffer.pop_back();
612
					} else if (ev.ch != 0) {
717
					} else if (ev.ch != 0) {
613
						input_buffer += (char)ev.ch;
718
						input_buffer += (char)ev.ch;
  
719
					}
  
720
				}
  
721
			} else if (ev.type == TB_EVENT_MOUSE) {
  
722
				int main_window_height = tb_height() - LOG_WINDOW_HEIGHT - STATUS_WINDOW_HEIGHT;
  
723
				int log_start_y = main_window_height;
  
724
				int log_end_y = tb_height() - STATUS_WINDOW_HEIGHT;
  
725
  
  
726
				if (ev.y >= log_start_y && ev.y < log_end_y) {
  
727
					if (ev.key == TB_KEY_MOUSE_WHEEL_UP) {
  
728
						int max_scroll = std::max(0, (int)log_buffer.size() - (LOG_WINDOW_HEIGHT - 2));
  
729
						if (log_scroll_offset < max_scroll) {
  
730
							log_scroll_offset++;
  
731
						}
  
732
					} else if (ev.key == TB_KEY_MOUSE_WHEEL_DOWN) {
  
733
						if (log_scroll_offset > 0) {
  
734
							log_scroll_offset--;
  
735
						}
  
736
					}
  
737
				}
  
738
  
  
739
				// Source window scrolling
  
740
				if (ev.x < split_x && ev.y < main_window_height) {
  
741
					SBLineEntry le = frame.GetLineEntry();
  
742
					if (le.IsValid()) {
  
743
						std::string fullpath;
  
744
						if (le.GetFileSpec().GetDirectory()) {
  
745
							fullpath = std::string(le.GetFileSpec().GetDirectory()) + "/" + le.GetFileSpec().GetFilename();
  
746
						} else {
  
747
							fullpath = le.GetFileSpec().GetFilename();
  
748
						}
  
749
						const std::vector<std::string>& lines = source_cache.get_lines(fullpath);
  
750
						int total_lines = (int)lines.size();
  
751
						int ch = main_window_height - 2;
  
752
						int max_scroll = std::max(0, total_lines - ch);
  
753
  
  
754
						if (ev.key == TB_KEY_MOUSE_WHEEL_UP) {
  
755
							if (source_scroll_offset > 0) {
  
756
								source_scroll_offset--;
  
757
							}
  
758
						} else if (ev.key == TB_KEY_MOUSE_WHEEL_DOWN) {
  
759
							if (source_scroll_offset < max_scroll) {
  
760
								source_scroll_offset++;
  
761
							}
  
762
						}
  
763
					}
  
764
				}
  
765
  
  
766
				// Locals window scrolling
  
767
				int split_x = tb_width() - SIDEBAR_WIDTH;
  
768
				int locals_window_height = main_window_height - BREAKPOINTS_WINDOW_HEIGHT;
  
769
				if (ev.x >= split_x && ev.y < locals_window_height) {
  
770
					std::vector<VarLine> lines;
  
771
					if (frame.IsValid()) {
  
772
						SBValueList vars = frame.GetVariables(true, true, false, true);
  
773
						for (uint32_t i = 0; i < vars.GetSize(); ++i) {
  
774
							collect_variables_recursive(vars.GetValueAtIndex(i), 0, lines, SIDEBAR_WIDTH - 2);
  
775
						}
  
776
					}
  
777
					int max_scroll = std::max(0, (int)lines.size() - (locals_window_height - 2));
  
778
  
  
779
					if (ev.key == TB_KEY_MOUSE_WHEEL_UP) {
  
780
						if (locals_scroll_offset > 0) {
  
781
							locals_scroll_offset--;
  
782
						}
  
783
					} else if (ev.key == TB_KEY_MOUSE_WHEEL_DOWN) {
  
784
						if (locals_scroll_offset < max_scroll) {
  
785
							locals_scroll_offset++;
  
786
						}
614
					}
787
					}
615
				}
788
				}
616
			}
789
			}
...