summaryrefslogtreecommitdiff
path: root/samples
diff options
context:
space:
mode:
Diffstat (limited to 'samples')
-rw-r--r--samples/format.txt10
-rw-r--r--samples/lsp.c7
-rw-r--r--samples/ollama.py6
-rw-r--r--samples/test.c495
-rw-r--r--samples/test.cpp1093
-rw-r--r--samples/test.css77
-rw-r--r--samples/test.dockerfile34
-rw-r--r--samples/test.html43
-rw-r--r--samples/test.js40
-rw-r--r--samples/test.lua180
-rw-r--r--samples/test.md209
-rw-r--r--samples/test.php72
-rw-r--r--samples/test.py20
-rw-r--r--samples/test.rb53
-rw-r--r--samples/test.sh57
-rw-r--r--samples/test.sql45
-rw-r--r--samples/test.ts138
-rw-r--r--samples/test.tsx107
18 files changed, 2686 insertions, 0 deletions
diff --git a/samples/format.txt b/samples/format.txt
new file mode 100644
index 0000000..11bbc8a
--- /dev/null
+++ b/samples/format.txt
@@ -0,0 +1,10 @@
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer eget justo volutpat, accumsan arcu vel, dapibus metus. Aenean interdum placerat bibendum. Nunc a aliquam sem. Etiam consectetur vestibulum sem in semper. Aliquam blandit justo ac turpis posuere dignissim. In hac habitasse platea dictumst. Phasellus egestas eros in leo laoreet, vel sollicitudin quam consectetur. Mauris viverra dui tortor, mattis lacinia eros porttitor at. Mauris egestas felis at rhoncus interdum. Cras eu orci sit amet risus efficitur dictum. Donec cursus aliquet suscipit. Vestibulum quis sem porttitor, bibendum risus finibus, eleifend quam. Mauris cursus lacus ut ligula ultrices rhoncus. Sed viverra mauris eu mi aliquet, nec posuere purus porttitor.
+
+ // Praesent iaculis est nec nunc finibus faucibus. Praesent quis lectus in enim interdum bibendum. Vivamus ultrices tellus non imperdiet porttitor. Aenean a orci nulla. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla sit amet eros eleifend, placerat est id, tempus urna. Mauris at diam bibendum, mollis ex in, bibendum ex. Aenean ornare felis ut interdum laoreet. Morbi quis felis vitae nisl euismod bibendum in sit amet velit. Vivamus imperdiet dignissim ipsum, non egestas ante ultricies id. Donec ligula justo, aliquam in aliquet non, luctus sed velit. Vivamus eu ipsum velit. Aliquam erat volutpat. Aenean at auctor dui, non dignissim mi. Pellentesque vel sapien sagittis, elementum lorem blandit, sodales tortor.
+
+// Interdum et malesuada fames ac ante ipsum primis in faucibus. Proin viverra pharetra dui ut sollicitudin.
+// Nunc in urna ut mauris facilisis tincidunt ut in sem. Vestibulum mattis, leo sit amet dignissim vehicula, ante purus fringilla neque, quis dictum erat risus a elit. Aliquam ut felis id nisl interdum sollicitudin. Pellentesque bibendum, leo eu feugiat imperdiet, felis quam tristique lectus, quis pretium nibh erat et leo. Donec ut dolor ullamcorper, ornare nisl sed, commodo ipsum. Duis laoreet mi urna, eget iaculis eros varius ac. Praesent posuere turpis eget lacus gravida semper. Quisque ullamcorper est tortor, at porttitor leo blandit fringilla. Nulla varius, dui et molestie maximus, orci est egestas sapien, sit amet porta orci sem ac tortor.
+
+Mauris ex magna, pellentesque ut rhoncus quis, molestie faucibus elit. Proin gravida imperdiet dictum. Nullam at dui tempor, tempus augue at, maximus enim. Curabitur eget est leo. Aliquam risus augue, tristique et tincidunt a, rhoncus in risus. Aliquam erat volutpat. Nullam ac tortor leo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque at euismod enim, ut porttitor eros. Cras quis vestibulum nisi, nec egestas quam. Praesent laoreet, dolor at pellentesque feugiat, sapien ante rutrum magna, id accumsan ante eros eget lectus. In tincidunt elementum nunc quis tristique.
+
+> Praesent sit amet purus pulvinar dui fermentum aliquam a ac ante. Nullam facilisis posuere pellentesque. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec imperdiet velit eget turpis lacinia ullamcorper a ut eros. Suspendisse velit dolor, maximus non purus a, vehicula rhoncus turpis. Curabitur vitae sollicitudin velit. Aenean mollis convallis viverra. Vivamus feugiat elit purus, vestibulum dignissim ex semper et. \ No newline at end of file
diff --git a/samples/lsp.c b/samples/lsp.c
new file mode 100644
index 0000000..a2ba13d
--- /dev/null
+++ b/samples/lsp.c
@@ -0,0 +1,7 @@
+#include <stdio.h>
+
+int main() {
+ int x = "not an integer";
+ printf("Hello\n");
+ return 0
+} \ No newline at end of file
diff --git a/samples/ollama.py b/samples/ollama.py
new file mode 100644
index 0000000..ea1a6ec
--- /dev/null
+++ b/samples/ollama.py
@@ -0,0 +1,6 @@
+# generate a for loop from 10 to 100
+# this is a line
+
+# generate a for loop from 20 to 40
+
+# this is a line \ No newline at end of file
diff --git a/samples/test.c b/samples/test.c
new file mode 100644
index 0000000..570310d
--- /dev/null
+++ b/samples/test.c
@@ -0,0 +1,495 @@
+#include <stdlib.h>
+
+// Comment 1
+// Comment 2
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/keysym.h>
+#include <X11/XF86keysym.h>
+#include <X11/cursorfont.h>
+
+#include "glitch.h"
+#include "config.h"
+
+#define MAX 100
+
+extern WindowManager wm;
+
+static Atom _NET_WM_DESKTOP;
+static Atom _NET_CURRENT_DESKTOP;
+static Atom _NET_NUMBER_OF_DESKTOPS;
+static Atom _NET_CLIENT_LIST;
+static Atom _NET_WM_STATE;
+static Atom _NET_WM_STATE_FULLSCREEN;
+static Atom _NET_ACTIVE_WINDOW;
+
+void init_window_manager(void) {
+ wm.dpy = XOpenDisplay(NULL);
+ if (!wm.dpy) {
+ log_message(stdout, LOG_ERROR, "Cannot open display");
+ abort();
+ }
+
+ wm.screen = DefaultScreen(wm.dpy);
+ wm.root = RootWindow(wm.dpy, wm.screen);
+
+ // Create and sets up cursors.
+ wm.cursor_default = XCreateFontCursor(wm.dpy, XC_left_ptr);
+ wm.cursor_move = XCreateFontCursor(wm.dpy, XC_fleur);
+ wm.cursor_resize = XCreateFontCursor(wm.dpy, XC_sizing);
+ XDefineCursor(wm.dpy, wm.root, wm.cursor_default);
+ log_message(stdout, LOG_DEBUG, "Setting up default cursors");
+
+ // Root window input selection masks.
+ XSelectInput(wm.dpy, wm.root,
+ SubstructureRedirectMask | SubstructureNotifyMask |
+ FocusChangeMask | EnterWindowMask | LeaveWindowMask |
+ ButtonPressMask | ExposureMask | PropertyChangeMask);
+
+ // Initialize EWMH atoms.
+ _NET_WM_DESKTOP = XInternAtom(wm.dpy, "_NET_WM_DESKTOP", False);
+ _NET_CURRENT_DESKTOP = XInternAtom(wm.dpy, "_NET_CURRENT_DESKTOP", False);
+ _NET_NUMBER_OF_DESKTOPS = XInternAtom(wm.dpy, "_NET_NUMBER_OF_DESKTOPS", False);
+ _NET_CLIENT_LIST = XInternAtom(wm.dpy, "_NET_CLIENT_LIST", False);
+ _NET_WM_STATE = XInternAtom(wm.dpy, "_NET_WM_STATE", False);
+ _NET_WM_STATE_FULLSCREEN = XInternAtom(wm.dpy, "_NET_WM_STATE_FULLSCREEN", False);
+ _NET_ACTIVE_WINDOW = XInternAtom(wm.dpy, "_NET_ACTIVE_WINDOW", False);
+
+ // Set number of desktops and current desktop.
+ static unsigned long num_desktops = NUM_DESKTOPS;
+ XChangeProperty(wm.dpy, wm.root, _NET_NUMBER_OF_DESKTOPS, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&num_desktops, 1);
+ XChangeProperty(wm.dpy, wm.root, _NET_CURRENT_DESKTOP, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&num_desktops, 1);
+ log_message(stdout, LOG_DEBUG, "Registering %d desktops", NUM_DESKTOPS);
+
+ // Grab keys for keybinds.
+ for (unsigned int i = 0; i < LENGTH(keybinds); i++) {
+ KeyCode keycode = XKeysymToKeycode(wm.dpy, keybinds[i].keysym);
+ if (keycode) {
+ XGrabKey(wm.dpy, keycode, keybinds[i].mod, wm.root, True, GrabModeAsync, GrabModeAsync);
+ log_message(stdout, LOG_DEBUG, "Grabbed key: mod=0x%x, keysym=0x%lx", keybinds[i].mod, keybinds[i].keysym);
+ }
+ }
+
+ // Grab keys for shortcuts.
+ for (unsigned int i = 0; i < LENGTH(shortcuts); i++) {
+ KeyCode keycode = XKeysymToKeycode(wm.dpy, shortcuts[i].keysym);
+ if (keycode) {
+ XGrabKey(wm.dpy, keycode, shortcuts[i].mod, wm.root, True, GrabModeAsync, GrabModeAsync);
+ log_message(stdout, LOG_DEBUG, "Grabbed shortcut: mod=0x%x, keysym=0x%lx, command=%s", shortcuts[i].mod, shortcuts[i].keysym, shortcuts[i].cmd);
+ }
+ }
+
+ // Grab keys for window dragging (with MODKEY).
+ XGrabButton(wm.dpy, 1, MODKEY, wm.root, True, ButtonPressMask|ButtonReleaseMask|PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None);
+ XGrabButton(wm.dpy, 3, MODKEY, wm.root, True, ButtonPressMask|ButtonReleaseMask|PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None);
+ log_message(stdout, LOG_DEBUG, "Registering grab keys for window dragging");
+
+ // Prepare border colors.
+ wm.cmap = DefaultColormap(wm.dpy, wm.screen);
+ XColor active_color, inactive_color, sticky_active_color, sticky_inactive_color, dummy;
+
+ wm.borders.normal_active = BlackPixel(wm.dpy, wm.screen);
+ wm.borders.normal_inactive = BlackPixel(wm.dpy, wm.screen);
+ wm.borders.sticky_active = BlackPixel(wm.dpy, wm.screen);
+ wm.borders.sticky_inactive = BlackPixel(wm.dpy, wm.screen);
+
+ if (XAllocNamedColor(wm.dpy, wm.cmap, active_border_color, &active_color, &dummy)) {
+ wm.borders.normal_active = active_color.pixel;
+ }
+
+ if (XAllocNamedColor(wm.dpy, wm.cmap, inactive_border_color, &inactive_color, &dummy)) {
+ wm.borders.normal_inactive = inactive_color.pixel;
+ }
+
+ if (XAllocNamedColor(wm.dpy, wm.cmap, sticky_active_border_color, &sticky_active_color, &dummy)) {
+ wm.borders.sticky_active = sticky_active_color.pixel;
+ }
+
+ if (XAllocNamedColor(wm.dpy, wm.cmap, sticky_inactive_border_color, &sticky_inactive_color, &dummy)) {
+ wm.borders.sticky_inactive = sticky_inactive_color.pixel;
+ }
+
+ XSync(wm.dpy, False);
+}
+
+void deinit_window_manager(void) {
+ XFreeCursor(wm.dpy, wm.cursor_default);
+ XFreeCursor(wm.dpy, wm.cursor_move);
+ XFreeCursor(wm.dpy, wm.cursor_resize);
+}
+
+static int ignore_x_error(Display *dpy, XErrorEvent *err) {
+ (void)dpy;
+ (void)err;
+ return 0;
+}
+
+int window_exists(Window window) {
+ if (window == None) return 0;
+ XErrorHandler old = XSetErrorHandler(ignore_x_error);
+ XWindowAttributes attr;
+ Status status = XGetWindowAttributes(wm.dpy, window, &attr);
+ XSync(wm.dpy, False);
+ XSetErrorHandler(old);
+ return status != 0;
+}
+
+void set_active_window(Window window) {
+ if (window != None) {
+ XChangeProperty(wm.dpy, wm.root, _NET_ACTIVE_WINDOW, XA_WINDOW, 32, PropModeReplace, (unsigned char *)&window, 1);
+ wm.active = window;
+ } else {
+ XDeleteProperty(wm.dpy, wm.root, _NET_ACTIVE_WINDOW);
+ }
+ XFlush(wm.dpy);
+}
+
+Window get_active_window(void) {
+ Atom _NET_ACTIVE_WINDOW = XInternAtom(wm.dpy, "_NET_ACTIVE_WINDOW", False);
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems, bytes_after;
+ unsigned char *prop = NULL;
+ Window active = None;
+
+ if (XGetWindowProperty(wm.dpy, wm.root, _NET_ACTIVE_WINDOW, 0, (~0L), False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop) == Success) {
+ if (prop && nitems >= 1) {
+ active = *(Window *)prop;
+ }
+ }
+
+ if (prop) XFree(prop);
+ return active;
+}
+
+void get_cursor_offset(Window window, int *dx, int *dy) {
+ Window root, child;
+ int root_x, root_y;
+ unsigned int mask;
+ XQueryPointer(wm.dpy, window, &root, &child, &root_x, &root_y, dx, dy, &mask);
+}
+
+// https://tronche.com/gui/x/xlib/events/structure-control/map.html
+void handle_map_request(void) {
+ Window window = wm.ev.xmaprequest.window;
+
+ // Move window under cursor position and clamps inside the screen bounds.
+ XWindowAttributes check_attr;
+ if (XGetWindowAttributes(wm.dpy, window, &check_attr)) {
+ XSelectInput(wm.dpy, window, EnterWindowMask | LeaveWindowMask);
+
+ Window root_return, child_return;
+ int root_x, root_y, win_x, win_y;
+ unsigned int mask;
+
+ if (XQueryPointer(wm.dpy, wm.root, &root_return, &child_return, &root_x, &root_y, &win_x, &win_y, &mask)) {
+ int new_x = root_x - (check_attr.width / 2);
+ int new_y = root_y - (check_attr.height / 2);
+ int screen_width = DisplayWidth(wm.dpy, wm.screen);
+ int screen_height = DisplayHeight(wm.dpy, wm.screen);
+
+ if (new_x < 0) new_x = 0;
+ if (new_y < 0) new_y = 0;
+ if (new_x + check_attr.width > screen_width) new_x = screen_width - check_attr.width;
+ if (new_y + check_attr.height > screen_height) new_y = screen_height - check_attr.height;
+
+ XMoveWindow(wm.dpy, window, new_x, new_y);
+ log_message(stdout, LOG_DEBUG, "Positioned new window 0x%lx at cursor (%d, %d)", window, root_x, root_y);
+ }
+ }
+
+ // Shows, raises and focuses the window.
+ set_active_border(window);
+ set_active_window(window);
+
+ XMapWindow(wm.dpy, window);
+ XRaiseWindow(wm.dpy, window);
+ XSetInputFocus(wm.dpy, window, RevertToPointerRoot, CurrentTime);
+
+ log_message(stdout, LOG_DEBUG, "Window 0x%lx mapped", window);
+}
+
+// https://tronche.com/gui/x/xlib/events/window-state-change/unmap.html
+void handle_unmap_notify(void) {
+ Window window = wm.ev.xunmap.window;
+ log_message(stdout, LOG_DEBUG, "Window 0x%lx unmapped", window);
+}
+
+// https://tronche.com/gui/x/xlib/events/window-state-change/destroy.html
+void handle_destroy_notify(void) {
+ Window window = wm.ev.xdestroywindow.window;
+ log_message(stdout, LOG_DEBUG, "Window 0x%lx destroyed", window);
+}
+
+// https://tronche.com/gui/x/xlib/events/client-communication/property.html
+void handle_property_notify(void) {
+ Window window = wm.ev.xproperty.window;
+ Atom prop = wm.ev.xproperty.atom;
+ char *name = XGetAtomName(wm.dpy, prop);
+ log_message(stdout, LOG_DEBUG, "Window 0x%lx got property notification %s", window, name);
+}
+
+// https://tronche.com/gui/x/xlib/events/keyboard-pointer/keyboard-pointer.html
+void handle_motion_notify(void) {
+ if (wm.start.subwindow != None && (wm.start.state & MODKEY)) {
+ int xdiff = wm.ev.xmotion.x_root - wm.start.x_root;
+ int ydiff = wm.ev.xmotion.y_root - wm.start.y_root;
+
+ XMoveResizeWindow(wm.dpy, wm.start.subwindow,
+ wm.attr.x + (wm.start.button == 1 ? xdiff : 0),
+ wm.attr.y + (wm.start.button == 1 ? ydiff : 0),
+ MAX(100, wm.attr.width + (wm.start.button == 3 ? xdiff : 0)),
+ MAX(100, wm.attr.height + (wm.start.button == 3 ? ydiff : 0)));
+ }
+}
+
+// https://tronche.com/gui/x/xlib/events/client-communication/client-message.html
+void handle_client_message(void) {
+ Window window = wm.ev.xclient.window;
+ int message_type = wm.ev.xclient.message_type;
+ log_message(stdout, LOG_DEBUG, "Window 0x%lx got message type of %d", window, message_type);
+}
+
+// https://tronche.com/gui/x/xlib/events/keyboard-pointer/keyboard-pointer.html
+void handle_button_press(void) {
+ Window window = wm.ev.xbutton.subwindow;
+ if (window == None) return;
+
+ if (wm.ev.xbutton.state & MODKEY) {
+ XRaiseWindow(wm.dpy, window);
+ XGetWindowAttributes(wm.dpy, window, &wm.attr);
+ wm.start = wm.ev.xbutton;
+
+ set_active_border(window);
+ set_active_window(window);
+
+ switch (wm.ev.xbutton.button) {
+ case 1: {
+ XDefineCursor(wm.dpy, window, wm.cursor_move);
+ log_message(stdout, LOG_DEBUG, "Setting cursor to move");
+ } break;
+ case 3: {
+ XDefineCursor(wm.dpy, window, wm.cursor_resize);
+ log_message(stdout, LOG_DEBUG, "Setting cursor to resize");
+ } break;
+ }
+
+ log_message(stdout, LOG_DEBUG, "Window 0x%lx got button press press", window);
+ XFlush(wm.dpy);
+ }
+}
+
+// https://tronche.com/gui/x/xlib/events/keyboard-pointer/keyboard-pointer.html
+void handle_button_release(void) {
+ Window window = wm.ev.xbutton.subwindow;
+ if (window == None) return;
+
+ if (wm.start.state & MODKEY) {
+ XDefineCursor(wm.dpy, wm.start.subwindow, None);
+ log_message(stdout, LOG_DEBUG, "Setting cursor to resize");
+ }
+
+ log_message(stdout, LOG_DEBUG, "Window 0x%lx got button release", window);
+ XFlush(wm.dpy);
+}
+
+// https://tronche.com/gui/x/xlib/events/keyboard-pointer/keyboard-pointer.html
+void handle_key_press(void) {
+ log_message(stdout, LOG_DEBUG, ">> Key pressed > active window 0x%lx", wm.ev.xkey.subwindow);
+ if (wm.ev.type != KeyPress) return;
+ if (wm.ev.xkey.subwindow == None) return;
+
+ // TODO: Check why XkbKeycodeToKeysym worked in previous version.
+ /* KeySym keysym = XkbKeycodeToKeysym(wm.dpy, wm.ev.xkey.keycode, 0, 0); */
+ KeySym keysym = XLookupKeysym(&wm.ev.xkey, 0);
+
+ // Check keybinds first.
+ for (unsigned int i = 0; i < LENGTH(keybinds); i++) {
+ if (keysym == keybinds[i].keysym && (wm.ev.xkey.state & (Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|ControlMask|ShiftMask)) == keybinds[i].mod) {
+ keybinds[i].func(&keybinds[i].arg);
+ break;
+ }
+ }
+
+ XFlush(wm.dpy);
+}
+
+void handle_key_release(void) {}
+
+void handle_focus_in(void) {
+ Window window = wm.ev.xfocus.window;
+ if (window != wm.root) {
+ log_message(stdout, LOG_DEBUG, "Window 0x%lx focus in", window);
+ }
+}
+
+void handle_focus_out(void) {
+ Window window = wm.ev.xfocus.window;
+ if (window != wm.root) {
+ log_message(stdout, LOG_DEBUG, "Window 0x%lx focus out", window);
+ }
+}
+
+void handle_enter_notify(void) {
+ Window window = wm.ev.xcrossing.window;
+ if (window != wm.root) {
+ set_active_border(window);
+ set_active_window(window);
+ log_message(stdout, LOG_DEBUG, "Window 0x%lx enter notify", window);
+ }
+}
+
+void set_active_border(Window window) {
+ if (window == None) return;
+
+ // Setting current active window to inactive.
+ if (wm.active != None) {
+ XSetWindowBorderWidth(wm.dpy, wm.active, border_size);
+ XSetWindowBorder(wm.dpy, wm.active, wm.borders.normal_inactive);
+ log_message(stdout, LOG_DEBUG, "Active window 0x%lx border set to inactive", window);
+ }
+
+ // Setting desired window to active.
+ XSetWindowBorderWidth(wm.dpy, window, border_size);
+ XSetWindowBorder(wm.dpy, window, wm.borders.normal_active);
+ XFlush(wm.dpy);
+
+ log_message(stdout, LOG_DEBUG, "Desired window 0x%lx border set to active", window);
+}
+
+void move_window_x(const Arg *arg) {
+ if (wm.active == None) return;
+
+ XWindowAttributes attr;
+ XGetWindowAttributes(wm.dpy, wm.active, &attr);
+ XMoveWindow(wm.dpy, wm.active, attr.x + arg->i, attr.y);
+ log_message(stdout, LOG_DEBUG, "Move window 0x%lx on X by %d", wm.active, arg->i);
+
+ int rel_x, rel_y;
+ get_cursor_offset(wm.active, &rel_x, &rel_y);
+ XWarpPointer(wm.dpy, None, wm.active, 0, 0, 0, 0, rel_x + arg->i, rel_y);
+
+ XSync(wm.dpy, True);
+ XFlush(wm.dpy);
+}
+
+void move_window_y(const Arg *arg) {
+ if (wm.active == None) return;
+
+ XWindowAttributes attr;
+ XGetWindowAttributes(wm.dpy, wm.active, &attr);
+ XMoveWindow(wm.dpy, wm.active, attr.x, attr.y + arg->i);
+ log_message(stdout, LOG_DEBUG, "Move window 0x%lx on Y by %d", wm.active, arg->i);
+
+ int rel_x, rel_y;
+ get_cursor_offset(wm.active, &rel_x, &rel_y);
+ XWarpPointer(wm.dpy, None, wm.active, 0, 0, 0, 0, rel_x, rel_y + arg->i);
+
+ XSync(wm.dpy, True);
+ XFlush(wm.dpy);
+}
+
+void resize_window_x(const Arg *arg) {
+ if (wm.active == None) return;
+
+ XWindowAttributes attr;
+ XGetWindowAttributes(wm.dpy, wm.active, &attr);
+ XResizeWindow(wm.dpy, wm.active, MAX(1, attr.width + arg->i), attr.height);
+ XFlush(wm.dpy);
+
+ log_message(stdout, LOG_DEBUG, "Resize window 0x%lx on X by %d", wm.active, arg->i);
+}
+
+void resize_window_y(const Arg *arg) {
+ if (wm.active == None) return;
+
+ XWindowAttributes attr;
+ XGetWindowAttributes(wm.dpy, wm.active, &attr);
+ XResizeWindow(wm.dpy, wm.active, attr.width, MAX(1, attr.height + arg->i));
+ XFlush(wm.dpy);
+
+ log_message(stdout, LOG_DEBUG, "Resize window 0x%lx on Y by %d", wm.active, arg->i);
+}
+
+void window_snap_up(const Arg *arg) {
+ (void)arg;
+ if (wm.active == None) return;
+
+ XWindowAttributes attr;
+ if (!XGetWindowAttributes(wm.dpy, wm.active, &attr)) {
+ log_message(stdout, LOG_DEBUG, "Failed to get window attributes for 0x%lx", wm.active);
+ return;
+ }
+
+ int rel_x, rel_y;
+ get_cursor_offset(wm.active, &rel_x, &rel_y);
+
+ XMoveWindow(wm.dpy, wm.active, attr.x, 0);
+ XWarpPointer(wm.dpy, None, wm.active, 0, 0, 0, 0, rel_x, rel_y);
+ XFlush(wm.dpy);
+
+ log_message(stdout, LOG_DEBUG, "Snapped window 0x%lx to top edge", wm.active);
+}
+
+void window_snap_down(const Arg *arg) {
+ (void)arg;
+ if (wm.active == None) return;
+
+ XWindowAttributes attr;
+ if (!XGetWindowAttributes(wm.dpy, wm.active, &attr)) {
+ log_message(stdout, LOG_DEBUG, "Failed to get window attributes for 0x%lx", wm.active);
+ return;
+ }
+
+ int rel_x, rel_y;
+ int y = DisplayHeight(wm.dpy, DefaultScreen(wm.dpy)) - attr.height - (2 * attr.border_width);
+ get_cursor_offset(wm.active, &rel_x, &rel_y);
+
+ XMoveWindow(wm.dpy, wm.active, attr.x, y);
+ XWarpPointer(wm.dpy, None, wm.active, 0, 0, 0, 0, rel_x, rel_y);
+ XFlush(wm.dpy);
+
+ log_message(stdout, LOG_DEBUG, "Snapped window 0x%lx to bottom edge", wm.active);
+}
+
+void window_snap_left(const Arg *arg) {
+ (void)arg;
+ if (wm.active == None) return;
+
+ XWindowAttributes attr;
+ if (!XGetWindowAttributes(wm.dpy, wm.active, &attr)) {
+ log_message(stdout, LOG_DEBUG, "Failed to get window attributes for 0x%lx", wm.active);
+ return;
+ }
+
+ int rel_x, rel_y;
+ get_cursor_offset(wm.active, &rel_x, &rel_y);
+
+ XMoveWindow(wm.dpy, wm.active, 0, attr.y);
+ XWarpPointer(wm.dpy, None, wm.active, 0, 0, 0, 0, rel_x, rel_y);
+ XFlush(wm.dpy);
+
+ log_message(stdout, LOG_DEBUG, "Snapped window 0x%lx to left edge", wm.active);
+}
+
+void window_snap_right(const Arg *arg) {
+ (void)arg;
+ if (wm.active == None) return;
+
+ XWindowAttributes attr;
+ if (!XGetWindowAttributes(wm.dpy, wm.active, &attr)) {
+ log_message(stdout, LOG_DEBUG, "Failed to get window attributes for 0x%lx", wm.active);
+ return;
+ }
+
+ int rel_x, rel_y;
+ int x = DisplayWidth(wm.dpy, DefaultScreen(wm.dpy)) - attr.width - (2 * attr.border_width);
+ get_cursor_offset(wm.active, &rel_x, &rel_y);
+
+ XMoveWindow(wm.dpy, wm.active, x, attr.y);
+ XWarpPointer(wm.dpy, None, wm.active, 0, 0, 0, 0, rel_x, rel_y);
+ XFlush(wm.dpy);
+
+ log_message(stdout, LOG_DEBUG, "Snapped window 0x%lx to right edge", wm.active);
+}
diff --git a/samples/test.cpp b/samples/test.cpp
new file mode 100644
index 0000000..b078f0a
--- /dev/null
+++ b/samples/test.cpp
@@ -0,0 +1,1093 @@
+#include <lldb/API/LLDB.h>
+
+#include <iostream>
+#include <string>
+#include <chrono>
+#include <fstream>
+#include <vector>
+#include <algorithm>
+#include <fcntl.h>
+#include <unistd.h>
+#include <iomanip>
+#include <ctime>
+#include <sstream>
+
+#define TB_IMPL
+#include "termbox2.h"
+
+using namespace lldb;
+
+struct LayoutConfig {
+ int status_height = 1;
+ int watch_height = 15;
+ int sidebar_width = 50;
+ int log_height = 5;
+} layout_config;
+
+// https://unicodeplus.com
+const uint32_t SCROLLBAR_THUMB = 0x2593; // Dark shade
+const uint32_t SCROLLBAR_LINE = 0x2502; // Vertical line
+const uint32_t BREAKPOINT_CIRCLE = 0x25B6; // Filled triangle
+
+enum InputMode {
+ INPUT_MODE_NORMAL,
+ INPUT_MODE_BREAKPOINT,
+ INPUT_MODE_VARIABLE,
+ INPUT_MODE_WATCH,
+ INPUT_MODE_HELP
+};
+
+struct LLDBGuard {
+ LLDBGuard() { SBDebugger::Initialize(); }
+ ~LLDBGuard() { SBDebugger::Terminate(); }
+};
+
+struct TermboxGuard {
+ TermboxGuard() { tb_init(); }
+ ~TermboxGuard() { tb_shutdown(); }
+};
+
+struct SourceCache {
+ std::string path;
+ std::vector<std::string> lines;
+
+ const std::vector<std::string>& get_lines(const std::string& fullpath) {
+ if (path != fullpath) {
+ path = fullpath;
+ lines.clear();
+ std::ifstream file(fullpath);
+ std::string line;
+ while (std::getline(file, line)) {
+ lines.push_back(line);
+ }
+ }
+ return lines;
+ }
+};
+
+struct VarLine {
+ std::string text;
+ int indent;
+ int prefix_start;
+ int prefix_end;
+};
+
+std::string get_timestamp() {
+ auto now = std::chrono::system_clock::now();
+ std::time_t now_c = std::chrono::system_clock::to_time_t(now);
+ std::tm now_tm = *std::localtime(&now_c);
+ std::stringstream ss;
+ ss << std::put_time(&now_tm, "%H:%M:%S");
+ return ss.str();
+}
+
+void log_msg(std::vector<std::string>& log_buffer, const std::string& msg) {
+ log_buffer.push_back(get_timestamp() + " " + msg);
+}
+
+void draw_text(int x, int y, uint16_t fg, uint16_t bg, const std::string& text) {
+ for (char c : text) {
+ tb_set_cell(x++, y, c, fg, bg);
+ }
+}
+
+void draw_box(int x, int y, int w, int h, const std::string& title) {
+ // Corners
+ tb_set_cell(x, y, 0x250C, TB_DEFAULT, TB_DEFAULT);
+ tb_set_cell(x + w - 1, y, 0x2510, TB_DEFAULT, TB_DEFAULT);
+ tb_set_cell(x, y + h - 1, 0x2514, TB_DEFAULT, TB_DEFAULT);
+ tb_set_cell(x + w - 1, y + h - 1, 0x2518, TB_DEFAULT, TB_DEFAULT);
+
+ // Borders
+ for (int i = 1; i < w - 1; ++i) {
+ tb_set_cell(x + i, y, 0x2500, TB_DEFAULT, TB_DEFAULT);
+ tb_set_cell(x + i, y + h - 1, 0x2500, TB_DEFAULT, TB_DEFAULT);
+ }
+ for (int i = 1; i < h - 1; ++i) {
+ tb_set_cell(x, y + i, 0x2502, TB_DEFAULT, TB_DEFAULT);
+ tb_set_cell(x + w - 1, y + i, 0x2502, TB_DEFAULT, TB_DEFAULT);
+ }
+
+ if (!title.empty()) {
+ draw_text(x + 2, y, TB_BOLD | TB_GREEN, TB_DEFAULT, " " + title + " ");
+ }
+}
+
+char get_type_char(SBType type) {
+ if (!type.IsValid()) return '?';
+
+ // Resolve typedefs to their underlying canonical type
+ type = type.GetCanonicalType();
+
+ if (type.IsPointerType()) return 'p';
+ if (type.IsReferenceType()) return '&';
+ if (type.IsArrayType()) return 'a';
+
+ BasicType basic_type = type.GetBasicType();
+ switch (basic_type) {
+ case eBasicTypeInt:
+ case eBasicTypeUnsignedInt:
+ return 'i';
+ case eBasicTypeChar:
+ case eBasicTypeUnsignedChar:
+ return 'c';
+ case eBasicTypeFloat:
+ return 'f';
+ case eBasicTypeDouble:
+ return 'd';
+ case eBasicTypeBool:
+ return 'b';
+ case eBasicTypeLong:
+ case eBasicTypeUnsignedLong:
+ case eBasicTypeLongLong:
+ case eBasicTypeUnsignedLongLong:
+ return 'l';
+ case eBasicTypeShort:
+ case eBasicTypeUnsignedShort:
+ return 's';
+ case eBasicTypeVoid:
+ return 'v';
+ default:
+ break;
+ }
+
+ TypeClass type_class = type.GetTypeClass();
+ if (type_class & eTypeClassStruct) return 's';
+ if (type_class & eTypeClassClass) return 'c';
+ if (type_class & eTypeClassEnumeration) return 'e';
+
+ const char* name = type.GetName();
+ if (name && *name) return name[0];
+
+ return '?';
+}
+
+void collect_variables_recursive(SBValue val, int indent, std::vector<VarLine>& lines, int width, const std::string& name_override = "") {
+ if (indent > 3) return;
+
+ std::string original_name = name_override.empty() ? (val.GetName() ? val.GetName() : "") : name_override;
+ char type_char = get_type_char(val.GetType());
+ std::string prefix = std::string("(") + type_char + ") ";
+
+ std::string val_str = val.GetValue() ? val.GetValue() : "";
+ std::string summary_str = val.GetSummary() ? val.GetSummary() : "";
+ std::string value;
+
+ if (!val.IsValid()) value = "(invalid)";
+ else {
+ if (!val_str.empty() && !summary_str.empty()) value = val_str + " " + summary_str;
+ else if (!val_str.empty()) value = val_str;
+ else if (!summary_str.empty()) value = summary_str;
+ }
+
+ std::string indent_str(indent * 2, ' ');
+ std::string content = original_name;
+ if (!value.empty()) content += " = " + value;
+
+ std::string line_text = indent_str + prefix + content;
+ if ((int)line_text.length() > width) line_text = line_text.substr(0, width - 3) + "...";
+
+ VarLine vl;
+ vl.text = line_text;
+ vl.indent = indent;
+ vl.prefix_start = indent * 2;
+ vl.prefix_end = vl.prefix_start + 4; // length of "(x) "
+ lines.push_back(vl);
+
+ if (val.GetNumChildren() > 0) {
+ uint32_t n = val.GetNumChildren();
+ for (uint32_t i = 0; i < n; ++i) {
+ collect_variables_recursive(val.GetChildAtIndex(i), indent + 1, lines, width);
+ }
+ }
+}
+
+void format_variable_log(SBValue val, std::vector<std::string>& log_buffer, int indent, const std::string& name_override = "") {
+ if (indent > 3) return;
+
+ std::string name = name_override.empty() ? (val.GetName() ? val.GetName() : "") : name_override;
+ char type_char = get_type_char(val.GetType());
+
+ std::string val_str = val.GetValue() ? val.GetValue() : "";
+ std::string summary_str = val.GetSummary() ? val.GetSummary() : "";
+ std::string value;
+
+ if (!val.IsValid()) value = "(invalid)";
+ else {
+ if (!val_str.empty() && !summary_str.empty()) value = val_str + " " + summary_str;
+ else if (!val_str.empty()) value = val_str;
+ else if (!summary_str.empty()) value = summary_str;
+ }
+
+ std::string indent_str(indent * 2, ' ');
+ std::string line = get_timestamp() + " " + indent_str + "(" + type_char + ") " + name;
+ if (!value.empty()) line += " = " + value;
+
+ log_buffer.push_back(line);
+
+ if (val.GetNumChildren() > 0) {
+ uint32_t n = val.GetNumChildren();
+ for (uint32_t i = 0; i < n; ++i) {
+ format_variable_log(val.GetChildAtIndex(i), log_buffer, indent + 1);
+ }
+ }
+}
+
+void draw_variables_view(SBFrame &frame, int x, int y, int w, int h, int scroll_offset) {
+ draw_box(x, y, w, h, "Locals");
+
+ // Content area
+ int cx = x + 1;
+ int cy = y + 1;
+ int ch = h - 2;
+ int cw = w - 2;
+
+ if (!frame.IsValid()) {
+ draw_text(cx, cy, TB_RED, TB_DEFAULT, "No frame selected.");
+ return;
+ }
+
+ std::vector<VarLine> lines;
+ SBValueList vars = frame.GetVariables(true, true, false, true);
+ for (uint32_t i = 0; i < vars.GetSize(); ++i) {
+ collect_variables_recursive(vars.GetValueAtIndex(i), 0, lines, cw);
+ }
+
+ int total_lines = (int)lines.size();
+ int display_count = std::min(total_lines, ch);
+
+ for (int i = 0; i < display_count; ++i) {
+ int line_idx = scroll_offset + i;
+ if (line_idx < 0 || line_idx >= total_lines) continue;
+
+ const VarLine& vl = lines[line_idx];
+ for (int j = 0; j < (int)vl.text.length() && j < cw; ++j) {
+ uint16_t fg = TB_DEFAULT;
+ if (j >= vl.prefix_start && j < vl.prefix_end) {
+ fg = TB_BLACK | TB_BOLD;
+ }
+ tb_set_cell(cx + j, cy + i, vl.text[j], fg, TB_DEFAULT);
+ }
+ }
+
+ // Draw scrollbar
+ if (total_lines > ch) {
+ int thumb_height = std::max(1, (ch * ch) / total_lines);
+ int max_scroll = total_lines - ch;
+ double scroll_percent = (double)scroll_offset / (double)max_scroll;
+ int thumb_pos = (ch - thumb_height) * scroll_percent;
+
+ for (int i = 0; i < ch; ++i) {
+ uint32_t cell_char = SCROLLBAR_LINE;
+ uint16_t fg = TB_DEFAULT;
+ if (i >= thumb_pos && i < thumb_pos + thumb_height) {
+ cell_char = SCROLLBAR_THUMB;
+ fg = TB_WHITE;
+ }
+ tb_set_cell(x + w - 1, cy + i, cell_char, fg, TB_DEFAULT);
+ }
+ }
+}
+
+std::string get_breakpoint_name(SBBreakpoint bp) {
+ if (!bp.IsValid()) return "???";
+
+ std::string name = "???";
+ if (bp.GetNumLocations() > 0) {
+ SBBreakpointLocation loc = bp.GetLocationAtIndex(0);
+ SBAddress addr = loc.GetAddress();
+
+ SBFunction func = addr.GetFunction();
+ if (func.IsValid()) {
+ const char* n = func.GetName();
+ if (n) name = n;
+ } else {
+ SBSymbol sym = addr.GetSymbol();
+ if (sym.IsValid()) {
+ const char* n = sym.GetName();
+ if (n) name = n;
+ }
+ }
+
+ SBLineEntry line_entry = addr.GetLineEntry();
+ if (line_entry.IsValid()) {
+ std::string file_name;
+ SBFileSpec fs = line_entry.GetFileSpec();
+ if (fs.IsValid()) file_name = fs.GetFilename();
+
+ if (name == "???") {
+ if (!file_name.empty()) name = file_name + ":" + std::to_string(line_entry.GetLine());
+ } else {
+ if (!file_name.empty()) name += " (" + file_name + ":" + std::to_string(line_entry.GetLine()) + ")";
+ }
+ }
+ }
+ return name;
+}
+
+void draw_source_view(SBFrame &frame, int x, int y, int w, int h, SourceCache& cache, int scroll_offset) {
+ draw_box(x, y, w, h, "Source");
+
+ int cx = x + 1;
+ int cy = y + 1;
+ int ch = h - 2;
+ int cw = w - 2;
+
+ if (!frame.IsValid()) {
+ draw_text(cx, cy, TB_RED, TB_DEFAULT, "No frame selected.");
+ return;
+ }
+
+ SBLineEntry line_entry = frame.GetLineEntry();
+ if (!line_entry.IsValid()) {
+ draw_text(cx, cy, TB_RED, TB_DEFAULT, "No line entry info.");
+ return;
+ }
+
+ SBFileSpec file_spec = line_entry.GetFileSpec();
+ if (!file_spec.IsValid()) return;
+
+ // Construct full path
+ std::string fullpath;
+ if (file_spec.GetDirectory()) {
+ fullpath = std::string(file_spec.GetDirectory()) + "/" + file_spec.GetFilename();
+ } else {
+ fullpath = file_spec.GetFilename();
+ }
+
+ SBAddress addr = frame.GetPCAddress();
+ SBTarget target = frame.GetThread().GetProcess().GetTarget();
+
+ const std::vector<std::string>& lines = cache.get_lines(fullpath);
+ if (lines.empty() && !std::ifstream(fullpath).good()) {
+ draw_text(cx, cy, TB_RED | TB_BOLD, TB_DEFAULT, "Could not open source: " + fullpath);
+
+ SBFunction func = frame.GetFunction();
+ std::string func_name = func.IsValid() ? func.GetName() : "???";
+
+ char addr_buf[64];
+ snprintf(addr_buf, sizeof(addr_buf), "At address: 0x%lx", (unsigned long)addr.GetLoadAddress(target));
+
+ draw_text(cx, cy + 2, TB_WHITE, TB_DEFAULT, "Function: " + func_name);
+ draw_text(cx, cy + 3, TB_WHITE, TB_DEFAULT, addr_buf);
+ draw_text(cx, cy + 5, TB_YELLOW, TB_DEFAULT, "Press 'n' (Step Over) or 'o' (Step Out) to return to your code.");
+
+ // Disassembly fallback
+ SBInstructionList instructions = target.ReadInstructions(addr, (uint32_t)(ch - 8));
+ if (instructions.IsValid()) {
+ for (uint32_t i = 0; i < instructions.GetSize() && (int)i < ch - 8; ++i) {
+ SBInstruction insn = instructions.GetInstructionAtIndex(i);
+ std::string dis = insn.GetMnemonic(target);
+ dis += " ";
+ dis += insn.GetOperands(target);
+
+ uint16_t fg = (insn.GetAddress() == addr) ? TB_WHITE | TB_BOLD : TB_DEFAULT;
+ uint16_t bg = (insn.GetAddress() == addr) ? TB_BLUE : TB_DEFAULT;
+
+ char insn_addr_buf[32];
+ snprintf(insn_addr_buf, sizeof(insn_addr_buf), "0x%lx: ", (unsigned long)insn.GetAddress().GetLoadAddress(target));
+
+ draw_text(cx, cy + 7 + i, fg, bg, std::string(insn_addr_buf) + dis);
+ }
+ }
+ return;
+ }
+
+ // Get breakpoints for this file
+ std::vector<uint32_t> bp_lines;
+ uint32_t num_breakpoints = target.GetNumBreakpoints();
+ for (uint32_t i = 0; i < num_breakpoints; ++i) {
+ SBBreakpoint bp = target.GetBreakpointAtIndex(i);
+ uint32_t num_locs = bp.GetNumLocations();
+ for (uint32_t j = 0; j < num_locs; ++j) {
+ SBBreakpointLocation loc = bp.GetLocationAtIndex(j);
+ SBLineEntry le = loc.GetAddress().GetLineEntry();
+ if (le.IsValid()) {
+ SBFileSpec fs = le.GetFileSpec();
+ if (fs.IsValid()) {
+ std::string bp_path;
+ if (fs.GetDirectory()) {
+ bp_path = std::string(fs.GetDirectory()) + "/" + fs.GetFilename();
+ } else {
+ bp_path = fs.GetFilename();
+ }
+ if (bp_path == fullpath) {
+ bp_lines.push_back(le.GetLine());
+ }
+ }
+ }
+ }
+ }
+
+ int total_lines = (int)lines.size();
+ int current_line = line_entry.GetLine();
+ for (int i = 0; i < ch; ++i) {
+ int line_idx = scroll_offset + i + 1;
+ if (line_idx > total_lines) break;
+
+ std::string src = lines[line_idx - 1];
+ // Handle basic tab expansion (simple version)
+ std::string expanded;
+ for (char c : src) {
+ if (c == '\t') expanded += " ";
+ else expanded += c;
+ }
+ src = expanded;
+
+ bool is_current = (line_idx == current_line);
+ bool has_breakpoint = std::find(bp_lines.begin(), bp_lines.end(), (uint32_t)line_idx) != bp_lines.end();
+
+ char buf[32];
+ snprintf(buf, sizeof(buf), "%4d ", line_idx);
+ std::string num_str(buf);
+
+ uint16_t bg = is_current ? TB_BLUE : TB_DEFAULT;
+ uint16_t fg = is_current ? TB_WHITE | TB_BOLD : TB_DEFAULT;
+
+ // Draw breakpoint indicator
+ if (has_breakpoint) {
+ tb_set_cell(cx, cy + i, BREAKPOINT_CIRCLE, TB_RED | TB_BOLD, bg);
+ } else {
+ tb_set_cell(cx, cy + i, ' ', fg, bg);
+ }
+
+ draw_text(cx + 1, cy + i, fg, bg, num_str);
+
+ int src_max_len = cw - (int)num_str.length() - 1;
+ if ((int)src.length() > src_max_len) {
+ src = src.substr(0, src_max_len);
+ }
+ draw_text(cx + 1 + num_str.length(), cy + i, fg, bg, src);
+
+ if (is_current) {
+ for (int k = cx + 1 + num_str.length() + src.length(); k < cx + cw; ++k) {
+ tb_set_cell(k, cy + i, ' ', fg, bg);
+ }
+ }
+ }
+
+ // Draw scrollbar
+ if (total_lines > ch) {
+ int thumb_height = std::max(1, (ch * ch) / total_lines);
+ int max_scroll = total_lines - ch;
+ double scroll_percent = (double)scroll_offset / (double)max_scroll;
+ int thumb_pos = (ch - thumb_height) * scroll_percent;
+
+ for (int i = 0; i < ch; ++i) {
+ uint32_t cell_char = SCROLLBAR_LINE;
+ uint16_t fg = TB_DEFAULT;
+ if (i >= thumb_pos && i < thumb_pos + thumb_height) {
+ cell_char = SCROLLBAR_THUMB;
+ fg = TB_WHITE;
+ }
+ tb_set_cell(x + w - 1, cy + i, cell_char, fg, TB_DEFAULT);
+ }
+ }
+}
+
+void draw_breakpoints_view(SBTarget& target, int x, int y, int w, int h) {
+ draw_box(x, y, w, h, "Breakpoints");
+ int cx = x + 1;
+ int cy = y + 1;
+ int mh = h - 2;
+
+ if (!target.IsValid()) return;
+
+ int num_bps = target.GetNumBreakpoints();
+ for (int i = 0; i < num_bps && i < mh; ++i) {
+ SBBreakpoint bp = target.GetBreakpointAtIndex(i);
+ std::string name = get_breakpoint_name(bp);
+
+ char buf[128];
+ snprintf(buf, sizeof(buf), "%d: %s", bp.GetID(), name.c_str());
+ std::string line = buf;
+ draw_text(cx, cy + i, TB_DEFAULT, TB_DEFAULT, line);
+ }
+}
+
+void draw_watch_view(SBFrame& frame, int x, int y, int w, int h, const std::vector<std::string>& expressions, int scroll_offset) {
+ draw_box(x, y, w, h, "Watch");
+ int cx = x + 1;
+ int cy = y + 1;
+ int ch = h - 2;
+ int cw = w - 2;
+
+ if (expressions.empty()) {
+ draw_text(cx, cy, TB_DEFAULT, TB_DEFAULT, "No watch expressions.");
+ return;
+ }
+
+ std::vector<VarLine> lines;
+ for (const auto& expr : expressions) {
+ SBValue val = frame.EvaluateExpression(expr.c_str());
+ if (val.IsValid() && !val.GetError().Fail()) {
+ collect_variables_recursive(val, 0, lines, cw, expr);
+ } else {
+ VarLine vl;
+ vl.text = expr + " = (error)";
+ if (val.GetError().GetCString()) {
+ vl.text += ": " + std::string(val.GetError().GetCString());
+ }
+ if ((int)vl.text.length() > cw) vl.text = vl.text.substr(0, cw - 3) + "...";
+ vl.indent = 0;
+ vl.prefix_start = 0;
+ vl.prefix_end = 0;
+ lines.push_back(vl);
+ }
+ }
+
+ int total_lines = (int)lines.size();
+ int display_count = std::min(total_lines, ch);
+
+ for (int i = 0; i < display_count; ++i) {
+ int line_idx = scroll_offset + i;
+ if (line_idx < 0 || line_idx >= total_lines) continue;
+
+ const VarLine& vl = lines[line_idx];
+ for (int j = 0; j < (int)vl.text.length() && j < cw; ++j) {
+ uint16_t fg = TB_DEFAULT;
+ if (j >= vl.prefix_start && j < vl.prefix_end) {
+ fg = TB_BLACK | TB_BOLD;
+ }
+ tb_set_cell(cx + j, cy + i, vl.text[j], fg, TB_DEFAULT);
+ }
+ }
+
+ // Draw scrollbar
+ if (total_lines > ch) {
+ int thumb_height = std::max(1, (ch * ch) / total_lines);
+ int max_scroll = total_lines - ch;
+ double scroll_percent = (double)scroll_offset / (double)max_scroll;
+ int thumb_pos = (ch - thumb_height) * scroll_percent;
+
+ for (int i = 0; i < ch; ++i) {
+ uint32_t cell_char = SCROLLBAR_LINE;
+ uint16_t fg = TB_DEFAULT;
+ if (i >= thumb_pos && i < thumb_pos + thumb_height) {
+ cell_char = SCROLLBAR_THUMB;
+ fg = TB_WHITE;
+ }
+ tb_set_cell(x + w - 1, cy + i, cell_char, fg, TB_DEFAULT);
+ }
+ }
+}
+
+SBBreakpoint create_breakpoint(SBTarget& target, const std::string& input) {
+ SBBreakpoint bp;
+ size_t colon_pos = input.rfind(':');
+
+ if (colon_pos != std::string::npos && colon_pos < input.length() - 1) {
+ std::string line_str = input.substr(colon_pos + 1);
+ bool is_number = !line_str.empty() && std::all_of(line_str.begin(), line_str.end(), ::isdigit);
+
+ if (is_number) {
+ std::string filename = input.substr(0, colon_pos);
+ uint32_t line_no = (uint32_t)std::stoi(line_str);
+ bp = target.BreakpointCreateByLocation(filename.c_str(), line_no);
+ if (bp.IsValid() && bp.GetNumLocations() > 0) return bp;
+ }
+ }
+
+ return target.BreakpointCreateByName(input.c_str());
+}
+
+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) {
+ bool input_mode = (mode == INPUT_MODE_BREAKPOINT || mode == INPUT_MODE_VARIABLE || mode == INPUT_MODE_WATCH);
+ std::string title = input_mode ? "Input (Esc to Cancel)" : "Logs";
+ if (!input_mode && scroll_offset > 0) {
+ title += " (Scrolled up: " + std::to_string(scroll_offset) + ")";
+ }
+ draw_box(x, y, w, h, title);
+
+ int cx = x + 1;
+ int cy = y + 1;
+ int ch = h - 2;
+ int cw = w - 2;
+
+ if (input_mode) {
+ std::string prompt;
+ if (mode == INPUT_MODE_BREAKPOINT) prompt = "Add Breakpoint: ";
+ else if (mode == INPUT_MODE_VARIABLE) prompt = "Print Variable: ";
+ else if (mode == INPUT_MODE_WATCH) prompt = "Watch Variable: ";
+
+ prompt += input_buffer;
+ if ((int)prompt.length() > cw) prompt = prompt.substr(prompt.length() - cw);
+ draw_text(cx, cy, TB_WHITE | TB_BOLD, TB_DEFAULT, prompt);
+ tb_set_cell(cx + prompt.length(), cy, '_', TB_WHITE | TB_BOLD | TB_REVERSE, TB_DEFAULT);
+ } else {
+ int total_logs = log_buffer.size();
+ int display_count = std::min(total_logs, ch);
+
+ for (int i = 0; i < display_count; ++i) {
+ int log_idx = total_logs - display_count - scroll_offset + i;
+ if (log_idx < 0 || log_idx >= total_logs) continue;
+
+ const std::string& msg = log_buffer[log_idx];
+ std::string disp = msg;
+ if ((int)disp.length() > cw) disp = disp.substr(0, cw);
+ draw_text(cx, cy + i, TB_DEFAULT, TB_DEFAULT, disp);
+ }
+
+ // Draw scrollbar
+ if (total_logs > ch) {
+ int thumb_height = std::max(1, (ch * ch) / total_logs);
+ int max_scroll = total_logs - ch;
+ double scroll_percent = (double)scroll_offset / (double)max_scroll;
+ int thumb_pos = (ch - thumb_height) * (1.0 - scroll_percent);
+
+ for (int i = 0; i < ch; ++i) {
+ uint32_t cell_char = SCROLLBAR_LINE;
+ uint16_t fg = TB_DEFAULT;
+ if (i >= thumb_pos && i < thumb_pos + thumb_height) {
+ cell_char = SCROLLBAR_THUMB;
+ fg = TB_WHITE;
+ }
+ tb_set_cell(x + w - 1, cy + i, cell_char, fg, TB_DEFAULT);
+ }
+ }
+ }
+}
+
+void draw_help_view(int width, int height) {
+ int w = 60;
+ int h = 18;
+ int x = (width - w) / 2;
+ int y = (height - h) / 2;
+
+ // Fill background
+ for (int i = 0; i < h; ++i) {
+ for (int j = 0; j < w; ++j) {
+ tb_set_cell(x + j, y + i, ' ', TB_DEFAULT, TB_DEFAULT);
+ }
+ }
+
+ draw_box(x, y, w, h, "Help / Keybinds");
+
+ int ty = y + 2;
+ int tx = x + 3;
+
+ auto d = [&](const std::string& key, const std::string& desc) {
+ draw_text(tx, ty, TB_YELLOW | TB_BOLD, TB_DEFAULT, key);
+ draw_text(tx + 13, ty, TB_DEFAULT, TB_DEFAULT, desc);
+ ty++;
+ };
+
+ d("r", "Run / Launch program");
+ d("b", "Add breakpoint (file:line or func)");
+ d("p", "Print variable / Evaluate expr");
+ d("w", "Add watch expression");
+ d("n", "Step Over (next line)");
+ d("s", "Step Into (into function)");
+ d("o", "Step Out (to caller)");
+ d("c", "Continue execution");
+ d("h", "Toggle help window");
+ d("q", "Quit debugger");
+ d("Esc", "Cancel input / Close help");
+ d("Ctrl+Arrows", "Resize layout");
+ d("Mouse Wheel", "Scroll active window");
+
+ draw_text(x + (w - 24) / 2, y + h - 2, TB_BLACK, TB_WHITE, " Press any key to close ");
+}
+
+void draw_status_bar(SBProcess &process, InputMode mode, int width, int height) {
+ std::string state_str = "Status: ";
+ if (!process.IsValid()) {
+ state_str += "Not Running";
+ } else {
+ StateType state = process.GetState();
+ if (state == eStateStopped) state_str += "Stopped";
+ else if (state == eStateRunning) state_str += "Running";
+ else if (state == eStateExited) state_str += "Exited";
+ else state_str += "Unknown";
+ }
+
+ state_str += (mode == INPUT_MODE_NORMAL)
+ ? " | r=Run, b=Add bp, p=Print, w=Watch, n=Step, s=Step In, o=Step Out, c=Cont, h=Help, q=Quit"
+ : (mode == INPUT_MODE_HELP ? " | Press any key to close help" : " | Enter=Confirm, Esc=Cancel");
+
+ for (int x = 0; x < width; ++x) {
+ tb_set_cell(x, height - 1, ' ', TB_BLACK, TB_WHITE);
+ }
+
+ draw_text(1, height - 1, TB_BLACK, TB_WHITE, state_str);
+}
+
+SBProcess launch_target(SBTarget& target, const std::string& target_path, const std::vector<std::string>& debuggee_args, const std::vector<std::string>& target_env, std::vector<std::string>& log_buffer) {
+ if (target.GetNumBreakpoints() == 0) {
+ SBBreakpoint bp = target.BreakpointCreateByName("main");
+ if (bp.IsValid() && bp.GetNumLocations() > 0) {
+ log_msg(log_buffer, "No breakpoints. Added breakpoint at 'main'");
+ } else {
+ log_msg(log_buffer, "No breakpoints. Failed to add breakpoint at 'main'");
+ }
+ }
+ log_msg(log_buffer, "Launching...");
+
+ std::vector<const char*> launch_argv;
+ launch_argv.push_back(target_path.c_str());
+ for (const auto& arg : debuggee_args) {
+ launch_argv.push_back(arg.c_str());
+ }
+ launch_argv.push_back(nullptr);
+
+ std::vector<const char*> launch_env;
+ for (const auto& env : target_env) {
+ launch_env.push_back(env.c_str());
+ }
+ launch_env.push_back(nullptr);
+
+ SBLaunchInfo launch_info(launch_argv.data());
+ launch_info.SetEnvironmentEntries(launch_env.data(), true);
+ launch_info.SetWorkingDirectory(".");
+
+ SBError error;
+ SBProcess process = target.Launch(launch_info, error);
+
+ if (!process.IsValid() || error.Fail()) {
+ std::string err_msg = "Launch failed";
+ if (error.GetCString()) {
+ err_msg += ": ";
+ err_msg += error.GetCString();
+ }
+ log_msg(log_buffer, err_msg);
+ } else {
+ log_msg(log_buffer, "Launched");
+ }
+ return process;
+}
+
+int main(int argc, char** argv) {
+ std::vector<std::string> target_env;
+ std::vector<std::string> startup_breakpoints;
+ std::vector<std::string> debuggee_args;
+ std::string target_path;
+ bool auto_run = false;
+
+ for (int i = 1; i < argc; ++i) {
+ std::string arg = argv[i];
+ if (arg == "-e" && i + 1 < argc) {
+ target_env.push_back(argv[++i]);
+ } else if (arg == "-b" && i + 1 < argc) {
+ startup_breakpoints.push_back(argv[++i]);
+ } else if (arg == "-run") {
+ auto_run = true;
+ } else if (arg == "-h" || arg == "--help") {
+ std::cout << "Usage: " << argv[0] << " [options] <target_executable> [-- arg1 arg2 ...]\n\n"
+ << "Options:\n"
+ << " -e KEY=VALUE Set environment variable\n"
+ << " -b BREAKPOINT Set startup breakpoint (name or file:line)\n"
+ << " -run Automatically run the target on startup\n"
+ << " -h, --help Show this help message\n";
+ return 0;
+ } else if (arg == "--") {
+ for (int j = i + 1; j < argc; ++j) {
+ debuggee_args.push_back(argv[j]);
+ }
+ break;
+ } else if (target_path.empty()) {
+ target_path = arg;
+ } else {
+ debuggee_args.push_back(arg);
+ }
+ }
+
+ if (target_path.empty()) {
+ std::cerr << "Usage: " << argv[0] << " [-e KEY=VALUE] [-b BREAKPOINT] [-run] ... <target_executable> [-- arg1 arg2 ...]\n";
+ return 1;
+ }
+
+ int log_fd = open("tdbg.log", O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (log_fd != -1) {
+ dup2(log_fd, STDERR_FILENO);
+ close(log_fd);
+ }
+
+ LLDBGuard lldb_guard;
+ SBDebugger debugger = SBDebugger::Create();
+ debugger.SetAsync(false);
+
+ SBTarget target = debugger.CreateTarget(target_path.c_str());
+ if (!target.IsValid()) {
+ std::cerr << "Failed to create target for " << target_path << "\n";
+ return 1;
+ }
+
+ std::vector<std::string> log_buffer;
+ for (const auto& bp_spec : startup_breakpoints) {
+ SBBreakpoint bp = create_breakpoint(target, bp_spec);
+ if (bp.IsValid() && bp.GetNumLocations() > 0) {
+ log_msg(log_buffer, "Set startup breakpoint: " + bp_spec);
+ } else {
+ log_msg(log_buffer, "Failed to set startup breakpoint: " + bp_spec);
+ }
+ }
+
+ SBProcess process;
+ if (auto_run) {
+ process = launch_target(target, target_path, debuggee_args, target_env, log_buffer);
+ }
+
+ SBThread thread;
+ TermboxGuard tb_guard;
+
+ bool running = true;
+ InputMode mode = INPUT_MODE_NORMAL;
+ std::string input_buffer;
+ std::string current_source_filename;
+ std::vector<std::string> watch_expressions;
+ int log_scroll_offset = 0;
+ int locals_scroll_offset = 0;
+ int watch_scroll_offset = 0;
+ int source_scroll_offset = 0;
+ uint64_t last_pc = 0;
+ SourceCache source_cache;
+ log_buffer.push_back("Debugger started. Press 'b' to add breakpoint, 'r' to run.");
+
+ tb_set_input_mode(TB_INPUT_ESC | TB_INPUT_MOUSE);
+
+ while (running) {
+ tb_clear();
+
+ int width = tb_width();
+ int height = tb_height();
+ int main_window_height = height - layout_config.log_height - layout_config.status_height;
+ int split_x = width - layout_config.sidebar_width;
+ int locals_window_height = main_window_height - layout_config.watch_height;
+
+ SBFrame frame;
+ if (process.IsValid() && process.GetState() != eStateExited) {
+ thread = process.GetSelectedThread();
+ if (thread.IsValid()) {
+ frame = thread.GetSelectedFrame();
+ if (frame.IsValid()) {
+ uint64_t current_pc = frame.GetPC();
+ if (current_pc != last_pc) {
+ last_pc = current_pc;
+ SBLineEntry le = frame.GetLineEntry();
+ if (le.IsValid()) {
+ std::string fullpath;
+ if (le.GetFileSpec().GetDirectory()) {
+ fullpath = std::string(le.GetFileSpec().GetDirectory()) + "/" + le.GetFileSpec().GetFilename();
+ current_source_filename = le.GetFileSpec().GetFilename();
+ } else {
+ fullpath = le.GetFileSpec().GetFilename();
+ current_source_filename = fullpath;
+ }
+ const std::vector<std::string>& lines = source_cache.get_lines(fullpath);
+ int total_lines = (int)lines.size();
+ int ch = main_window_height - 2;
+ source_scroll_offset = std::max(0, (int)le.GetLine() - ch / 2 - 1);
+ if (source_scroll_offset + ch > total_lines) {
+ source_scroll_offset = std::max(0, total_lines - ch);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ draw_source_view(frame, 0, 0, split_x, main_window_height, source_cache, source_scroll_offset);
+ draw_variables_view(frame, split_x, 0, layout_config.sidebar_width, locals_window_height, locals_scroll_offset);
+ draw_watch_view(frame, split_x, locals_window_height, layout_config.sidebar_width, layout_config.watch_height, watch_expressions, watch_scroll_offset);
+ draw_log_view(0, main_window_height, split_x, layout_config.log_height, log_buffer, mode, input_buffer, log_scroll_offset);
+ draw_breakpoints_view(target, split_x, main_window_height, layout_config.sidebar_width, layout_config.log_height);
+ draw_status_bar(process, mode, width, height);
+
+ if (mode == INPUT_MODE_HELP) {
+ draw_help_view(width, height);
+ }
+
+ tb_present();
+
+ struct tb_event ev;
+ if (tb_poll_event(&ev) == 0) {
+ if (ev.type == TB_EVENT_KEY) {
+ if (mode == INPUT_MODE_NORMAL) {
+ if (ev.ch == 'q') {
+ running = false;
+ } else if (ev.ch == 'r') {
+ if (!process.IsValid() || process.GetState() == eStateExited) {
+ process = launch_target(target, target_path, debuggee_args, target_env, log_buffer);
+ } else {
+ log_msg(log_buffer, "Already running");
+ }
+ } else if (ev.ch == 'b') {
+ mode = INPUT_MODE_BREAKPOINT;
+ if (!current_source_filename.empty()) {
+ input_buffer = current_source_filename + ":";
+ } else {
+ input_buffer.clear();
+ }
+ } else if (ev.ch == 'p') {
+ mode = INPUT_MODE_VARIABLE;
+ input_buffer.clear();
+ } else if (ev.ch == 'w') {
+ mode = INPUT_MODE_WATCH;
+ input_buffer.clear();
+ } else if (ev.ch == 'h') {
+ mode = INPUT_MODE_HELP;
+ } else {
+ if (process.IsValid() && process.GetState() == eStateStopped) {
+ switch (ev.ch) {
+ case 'n': if (thread.IsValid()) thread.StepOver(); break;
+ case 's': if (thread.IsValid()) thread.StepInto(); break;
+ case 'o': if (thread.IsValid()) thread.StepOut(); break;
+ case 'c': process.Continue(); break;
+ }
+ }
+
+ if (ev.key == TB_KEY_ARROW_LEFT && (ev.mod & TB_MOD_CTRL)) {
+ layout_config.sidebar_width = std::min(width - 20, layout_config.sidebar_width + 2);
+ } else if (ev.key == TB_KEY_ARROW_RIGHT && (ev.mod & TB_MOD_CTRL)) {
+ layout_config.sidebar_width = std::max(20, layout_config.sidebar_width - 2);
+ } else if (ev.key == TB_KEY_ARROW_UP && (ev.mod & TB_MOD_CTRL)) {
+ layout_config.log_height = std::min(height - 10, layout_config.log_height + 1);
+ } else if (ev.key == TB_KEY_ARROW_DOWN && (ev.mod & TB_MOD_CTRL)) {
+ layout_config.log_height = std::max(5, layout_config.log_height - 1);
+ }
+ }
+ } else if (mode == INPUT_MODE_BREAKPOINT || mode == INPUT_MODE_VARIABLE || mode == INPUT_MODE_WATCH) {
+ if (ev.key == TB_KEY_ESC) {
+ mode = INPUT_MODE_NORMAL;
+ input_buffer.clear();
+ } else if (ev.key == TB_KEY_ENTER) {
+ if (!input_buffer.empty()) {
+ if (mode == INPUT_MODE_BREAKPOINT) {
+ SBBreakpoint bp = create_breakpoint(target, input_buffer);
+ if (bp.IsValid() && bp.GetNumLocations() > 0) {
+ log_msg(log_buffer, "Breakpoint added: " + input_buffer);
+ } else {
+ log_msg(log_buffer, "Failed/Invalid breakpoint: " + input_buffer);
+ }
+ } else if (mode == INPUT_MODE_VARIABLE) {
+ if (!frame.IsValid()) {
+ log_msg(log_buffer, "Error: No stack frame available to evaluate '" + input_buffer + "'");
+ } else {
+ SBValue val = frame.EvaluateExpression(input_buffer.c_str());
+ if (val.IsValid() && !val.GetError().Fail()) {
+ format_variable_log(val, log_buffer, 0, input_buffer);
+ } else {
+ std::string err = "Error evaluating '" + input_buffer + "'";
+ if (val.GetError().GetCString()) {
+ err += ": ";
+ err += val.GetError().GetCString();
+ }
+ log_msg(log_buffer, err);
+ }
+ }
+ } else if (mode == INPUT_MODE_WATCH) {
+ watch_expressions.push_back(input_buffer);
+ log_msg(log_buffer, "Added to watch: " + input_buffer);
+ }
+ }
+ mode = INPUT_MODE_NORMAL;
+ input_buffer.clear();
+ } else if (ev.key == TB_KEY_BACKSPACE || ev.key == TB_KEY_BACKSPACE2) {
+ if (!input_buffer.empty()) input_buffer.pop_back();
+ } else if (ev.ch != 0) {
+ input_buffer += (char)ev.ch;
+ }
+ } else if (mode == INPUT_MODE_HELP) {
+ mode = INPUT_MODE_NORMAL;
+ }
+ } else if (ev.type == TB_EVENT_MOUSE) {
+ int main_window_height = tb_height() - layout_config.log_height - layout_config.status_height;
+
+ // Log window scrolling
+ int log_start_y = main_window_height;
+ int log_end_y = tb_height() - layout_config.status_height;
+ if (ev.x < split_x && ev.y >= log_start_y && ev.y < log_end_y) {
+ if (ev.key == TB_KEY_MOUSE_WHEEL_UP) {
+ int max_scroll = std::max(0, (int)log_buffer.size() - (layout_config.log_height - 2));
+ if (log_scroll_offset < max_scroll) {
+ log_scroll_offset++;
+ }
+ } else if (ev.key == TB_KEY_MOUSE_WHEEL_DOWN) {
+ if (log_scroll_offset > 0) {
+ log_scroll_offset--;
+ }
+ }
+ }
+
+ // Source window scrolling
+ if (ev.x < split_x && ev.y < main_window_height) {
+ SBLineEntry le = frame.GetLineEntry();
+ if (le.IsValid()) {
+ std::string fullpath;
+ if (le.GetFileSpec().GetDirectory()) {
+ fullpath = std::string(le.GetFileSpec().GetDirectory()) + "/" + le.GetFileSpec().GetFilename();
+ } else {
+ fullpath = le.GetFileSpec().GetFilename();
+ }
+ const std::vector<std::string>& lines = source_cache.get_lines(fullpath);
+ int total_lines = (int)lines.size();
+ int ch = main_window_height - 2;
+ int max_scroll = std::max(0, total_lines - ch);
+
+ if (ev.key == TB_KEY_MOUSE_WHEEL_UP) {
+ if (source_scroll_offset > 0) {
+ source_scroll_offset--;
+ }
+ } else if (ev.key == TB_KEY_MOUSE_WHEEL_DOWN) {
+ if (source_scroll_offset < max_scroll) {
+ source_scroll_offset++;
+ }
+ }
+ }
+ }
+
+ // Locals window scrolling
+ int split_x = tb_width() - layout_config.sidebar_width;
+ int locals_window_height = main_window_height - layout_config.watch_height;
+ if (ev.x >= split_x && ev.y < locals_window_height) {
+ std::vector<VarLine> lines;
+ if (frame.IsValid()) {
+ SBValueList vars = frame.GetVariables(true, true, false, true);
+ for (uint32_t i = 0; i < vars.GetSize(); ++i) {
+ collect_variables_recursive(vars.GetValueAtIndex(i), 0, lines, layout_config.sidebar_width - 2);
+ }
+ }
+ int max_scroll = std::max(0, (int)lines.size() - (locals_window_height - 2));
+
+ if (ev.key == TB_KEY_MOUSE_WHEEL_UP) {
+ if (locals_scroll_offset > 0) {
+ locals_scroll_offset--;
+ }
+ } else if (ev.key == TB_KEY_MOUSE_WHEEL_DOWN) {
+ if (locals_scroll_offset < max_scroll) {
+ locals_scroll_offset++;
+ }
+ }
+ }
+
+ // Watch window scrolling
+ if (ev.x >= split_x && ev.y >= locals_window_height && ev.y < main_window_height) {
+ std::vector<VarLine> lines;
+ for (const auto& expr : watch_expressions) {
+ SBValue val = frame.EvaluateExpression(expr.c_str());
+ if (val.IsValid() && !val.GetError().Fail()) {
+ collect_variables_recursive(val, 0, lines, layout_config.sidebar_width - 2, expr);
+ } else {
+ VarLine vl;
+ vl.text = expr + " = (error)";
+ lines.push_back(vl);
+ }
+ }
+ int max_scroll = std::max(0, (int)lines.size() - (layout_config.watch_height - 2));
+
+ if (ev.key == TB_KEY_MOUSE_WHEEL_UP) {
+ if (watch_scroll_offset > 0) {
+ watch_scroll_offset--;
+ }
+ } else if (ev.key == TB_KEY_MOUSE_WHEEL_DOWN) {
+ if (watch_scroll_offset < max_scroll) {
+ watch_scroll_offset++;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/samples/test.css b/samples/test.css
new file mode 100644
index 0000000..b7dfe86
--- /dev/null
+++ b/samples/test.css
@@ -0,0 +1,77 @@
+@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap");
+
+/* Basic CSS Reset */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Arial', sans-serif;
+ line-height: 1.6;
+ background-color: #f4f4f4;
+ color: #333;
+}
+
+/* Header Styles */
+header {
+ background: #35424a;
+ color: #ffffff;
+ padding-top: 30px;
+ min-height: 70px;
+ border-bottom: #e8491d 3px solid;
+}
+
+header a {
+ color: #ffffff;
+ text-decoration: none;
+ text-transform: uppercase;
+ font-size: 16px;
+}
+
+header ul {
+ margin: 0;
+ padding: 0;
+}
+
+header li {
+ float: left;
+ display: inline;
+ padding: 0 20px 0 20px;
+}
+
+/* Container */
+.container {
+ width: 80%;
+ margin: auto;
+ overflow: hidden;
+}
+
+/* Buttons */
+.button {
+ display: inline-block;
+ height: 38px;
+ padding: 0 30px;
+ color: #555;
+ text-align: center;
+ font-size: 11px;
+ font-weight: 600;
+ line-height: 38px;
+ letter-spacing: .1rem;
+ text-transform: uppercase;
+ text-decoration: none;
+ white-space: nowrap;
+ background-color: transparent;
+ border-radius: 4px;
+ border: 1px solid #bbb;
+ cursor: pointer;
+ box-sizing: border-box;
+}
+
+.button:hover,
+.button:focus {
+ color: #333;
+ border-color: #888;
+ outline: 0;
+} \ No newline at end of file
diff --git a/samples/test.dockerfile b/samples/test.dockerfile
new file mode 100644
index 0000000..28f71fd
--- /dev/null
+++ b/samples/test.dockerfile
@@ -0,0 +1,34 @@
+# --------------------
+# Build stage
+# --------------------
+FROM node:20-alpine AS build
+
+# Set working directory
+WORKDIR /app
+
+# Copy dependency files first (better caching)
+COPY package.json package-lock.json ./
+
+# Install dependencies
+RUN npm ci
+
+# Copy the rest of the source code
+COPY . .
+
+# Build the app
+RUN npm run build
+
+
+# --------------------
+# Production stage
+# --------------------
+FROM nginx:alpine
+
+# Copy built files from build stage
+COPY --from=build /app/dist /usr/share/nginx/html
+
+# Expose port 80
+EXPOSE 80
+
+# Start nginx
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/samples/test.html b/samples/test.html
new file mode 100644
index 0000000..5777899
--- /dev/null
+++ b/samples/test.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Test Page</title>
+ <style>
+ body { font-family: sans-serif; }
+ .container { max-width: 800px; margin: 0 auto; }
+ </style>
+</head>
+<body>
+ <div class="container">
+ <header>
+ <h1>Welcome to QWE Editor Test</h1>
+ </header>
+ <main>
+ <section>
+ <h2>Article Section</h2>
+ <article>
+ <p>This is a paragraph inside an article. It tests <strong>bold</strong> and <em>italic</em> rendering concepts.</p>
+ <img src="test.jpg" alt="Test Image">
+ </article>
+ </section>
+ <aside>
+ <ul>
+ <li>List item 1</li>
+ <li>List item 2</li>
+ <li><a href="#">Link item</a></li>
+ </ul>
+ </aside>
+ <form action="/submit">
+ <label for="name">Name:</label>
+ <input type="text" id="name" name="name">
+ <button type="submit">Send</button>
+ </form>
+ </main>
+ <footer>
+ <p>&copy; 2024 QWE Editor</p>
+ </footer>
+ </div>
+</body>
+</html>
diff --git a/samples/test.js b/samples/test.js
new file mode 100644
index 0000000..032b94d
--- /dev/null
+++ b/samples/test.js
@@ -0,0 +1,40 @@
+// JavaScript Test File
+
+// explain javascript to me
+
+// convert to fat arrow style
+function heavyComputation(a, b) {
+ const factor = 2.5;
+ let result = 0;
+
+ // Perform loop
+ for (let i = 0; i < 10; i++) {
+ if (i % 2 === 0) {
+ result += (a * b) * factor;
+ } else {
+ result -= i;
+ }
+ }
+
+ return result;
+}
+
+const user = {
+ name: "John Doe",
+ age: 30,
+ isActive: true
+};
+
+console.log("Starting computation...");
+var output = heavyComputation(10, 20);
+console.log(`Final Result: ${output}`);
+
+class Processor {
+ constructor(data) {
+ this.data = data;
+ }
+
+ process() {
+ return this.data.map(x => x * 2);
+ }
+}
diff --git a/samples/test.lua b/samples/test.lua
new file mode 100644
index 0000000..c5546ce
--- /dev/null
+++ b/samples/test.lua
@@ -0,0 +1,180 @@
+-----------------------------------------------------------------------
+-- LEARN LUA BY EXAMPLE
+-- Read from top to bottom. Run the file and modify values as you go.
+-----------------------------------------------------------------------
+
+print("Hello, Lua!") -- Printing to the console
+
+-----------------------------------------------------------------------
+-- 1. VARIABLES & TYPES
+-----------------------------------------------------------------------
+
+local name = "Lua" -- string
+local version = 5.1 -- number (Lua only has one numeric type)
+local isFun = true -- boolean
+local nothing = nil -- nil means "no value"
+
+print(name, version, isFun, nothing)
+
+-----------------------------------------------------------------------
+-- 2. BASIC OPERATIONS
+-----------------------------------------------------------------------
+
+local a = 10
+local b = 3
+
+print("Addition:", a + b)
+print("Subtraction:", a - b)
+print("Multiplication:", a * b)
+print("Division:", a / b)
+print("Power:", a ^ b)
+print("Modulo:", a % b)
+
+-----------------------------------------------------------------------
+-- 3. STRINGS
+-----------------------------------------------------------------------
+
+local first = "Hello"
+local second = "World"
+
+-- Concatenation uses ..
+local message = first .. " " .. second .. "!"
+print(message)
+
+print("Length of message:", #message)
+
+-----------------------------------------------------------------------
+-- 4. CONDITIONS (if / elseif / else)
+-----------------------------------------------------------------------
+
+local health = 25
+
+if health > 50 then
+ print("You are healthy")
+elseif health > 0 then
+ print("You are injured")
+else
+ print("You are dead")
+end
+
+-----------------------------------------------------------------------
+-- 5. LOOPS
+-----------------------------------------------------------------------
+
+-- for loop
+for i = 1, 5 do
+ print("For loop i =", i)
+end
+
+-- while loop
+local counter = 1
+while counter <= 3 do
+ print("While loop counter =", counter)
+ counter = counter + 1
+end
+
+-----------------------------------------------------------------------
+-- 6. TABLES (VERY IMPORTANT IN LUA)
+-- Tables are arrays, dictionaries, objects, and structs all in one
+-----------------------------------------------------------------------
+
+-- Array-like table
+local fruits = { "apple", "banana", "cherry" }
+
+print("First fruit:", fruits[1]) -- Lua arrays start at 1
+
+-- Loop over array
+for i, fruit in ipairs(fruits) do
+ print(i, fruit)
+end
+
+-- Dictionary-like table
+local player = {
+ name = "Hunter",
+ level = 60,
+ alive = true
+}
+
+print(player.name, player.level)
+
+-----------------------------------------------------------------------
+-- 7. FUNCTIONS
+-----------------------------------------------------------------------
+
+local function add(x, y)
+ return x + y
+end
+
+print("Function result:", add(4, 6))
+
+-----------------------------------------------------------------------
+-- 8. FUNCTIONS AS VALUES
+-----------------------------------------------------------------------
+
+local function apply(a, b, fn)
+ return fn(a, b)
+end
+
+print("Apply add:", apply(2, 3, add))
+
+-----------------------------------------------------------------------
+-- 9. SIMPLE OBJECT-LIKE TABLE
+-----------------------------------------------------------------------
+
+local Enemy = {}
+Enemy.__index = Enemy
+
+function Enemy:new(name, hp)
+ local obj = {
+ name = name,
+ hp = hp
+ }
+ setmetatable(obj, self)
+ return obj
+end
+
+function Enemy:takeDamage(amount)
+ self.hp = self.hp - amount
+ print(self.name .. " takes " .. amount .. " damage. HP =", self.hp)
+end
+
+local wolf = Enemy:new("Wolf", 40)
+wolf:takeDamage(15)
+
+-----------------------------------------------------------------------
+-- 10. NIL AND CHECKING VALUES
+-----------------------------------------------------------------------
+
+local value = nil
+
+if value == nil then
+ print("Value is nil")
+end
+
+-----------------------------------------------------------------------
+-- 11. GLOBAL VS LOCAL
+-----------------------------------------------------------------------
+
+globalVar = "I am global" -- avoid globals when possible!
+local localVar = "I am local"
+
+print(globalVar)
+print(localVar)
+
+-----------------------------------------------------------------------
+-- 12. ERROR HANDLING
+-----------------------------------------------------------------------
+
+local function risky()
+ error("Something went wrong!")
+end
+
+local success, err = pcall(risky)
+print("Success:", success)
+print("Error:", err)
+
+-----------------------------------------------------------------------
+-- END
+-----------------------------------------------------------------------
+
+print("You just walked through the basics of Lua πŸŽ‰")
diff --git a/samples/test.md b/samples/test.md
new file mode 100644
index 0000000..24b7e7a
--- /dev/null
+++ b/samples/test.md
@@ -0,0 +1,209 @@
+# Learn Markdown
+
+Markdown is a lightweight markup language for formatting plain text. It’s widely used for README files, documentation, notes, and writing on platforms like GitHub.
+
+---
+
+## 1. Headings
+
+Use `#` characters for headings. More `#` means a smaller heading.
+
+```md
+# Heading 1
+## Heading 2
+### Heading 3
+#### Heading 4
+```
+
+---
+
+## 2. Paragraphs & Line Breaks
+
+Just write text normally.
+
+* Leave a **blank line** between paragraphs.
+* Add two spaces at the end of a line for a manual line break.
+
+---
+
+## 3. Emphasis
+
+```md
+*Italic* or _Italic_
+**Bold** or __Bold__
+***Bold and Italic***
+~~Strikethrough~~
+```
+
+*Italic*
+**Bold**
+***Bold and Italic***
+~~Strikethrough~~
+
+---
+
+## 4. Lists
+
+### Unordered Lists
+
+```md
+- Item
+- Item
+ - Nested item
+* Item
+```
+
+### Ordered Lists
+
+```md
+1. First
+2. Second
+3. Third
+```
+
+---
+
+## 5. Links
+
+```md
+[Link text](https://example.com)
+```
+
+[Example link](https://example.com)
+
+---
+
+## 6. Images
+
+```md
+![Alt text](https://example.com/image.png)
+```
+
+---
+
+## 7. Code
+
+### Inline Code
+
+```md
+Use `inline code` for short snippets.
+```
+
+Use `inline code` for short snippets.
+
+### Code Blocks
+
+````md
+```python
+def hello():
+ print("Hello, Markdown!")
+````
+
+````
+
+```python
+def hello():
+ print("Hello, Markdown!")
+````
+
+---
+
+## 8. Blockquotes
+
+```md
+> This is a blockquote.
+>> Nested blockquote.
+```
+
+> This is a blockquote.
+>
+> > Nested blockquote.
+
+---
+
+## 9. Horizontal Rules
+
+```md
+---
+***
+___
+```
+
+---
+
+## 10. Tables
+
+```md
+| Name | Level | Class |
+|------|-------|-------|
+| Alex | 60 | Mage |
+| Sam | 45 | Rogue |
+```
+
+| Name | Level | Class |
+| ---- | ----- | ----- |
+| Alex | 60 | Mage |
+| Sam | 45 | Rogue |
+
+---
+
+## 11. Task Lists
+
+```md
+- [x] Learn Markdown basics
+- [ ] Write documentation
+- [ ] Become awesome
+```
+
+* [x] Learn Markdown basics
+* [ ] Write documentation
+* [ ] Become awesome
+
+---
+
+## 12. Escaping Characters
+
+Use a backslash (`\`) to escape Markdown characters.
+
+```md
+\*This is not italic\*
+```
+
+*This is not italic*
+
+---
+
+## 13. Common Markdown Extensions (Platform-dependent)
+
+Some platforms (like GitHub) support extra features:
+
+* Syntax highlighting
+* Task lists
+* Tables
+* Strikethrough
+* Emoji `:smile:` πŸ˜„
+
+---
+
+## 14. Tips for Learning
+
+* Read Markdown source and rendered output side by side
+* Practice by writing a README.md
+* Use a Markdown preview editor
+
+---
+
+## 15. Minimal Cheat Sheet
+
+```md
+# Heading
+**Bold** *Italic*
+[Link](url)
+`Code`
+- List item
+> Quote
+```
+
+---
+
+Happy writing! ✨
diff --git a/samples/test.php b/samples/test.php
new file mode 100644
index 0000000..2fc5bad
--- /dev/null
+++ b/samples/test.php
@@ -0,0 +1,72 @@
+<?php
+
+// A simple PHP script
+
+namespace App;
+
+use Exception;
+
+class User {
+ private $name;
+ private $email;
+
+ public function __construct($name, $email) {
+ $this->name = $name;
+ $this->email = $email;
+ }
+
+ public function getName() {
+ return $this->name;
+ }
+
+ public function getEmail() {
+ return $this->email;
+ }
+
+ public function save() {
+ // Simulate saving to database
+ echo "Saving user {$this->name} to database.\n";
+ return true;
+ }
+}
+
+function processUsers($users) {
+ foreach ($users as $user) {
+ try {
+ if ($user->save()) {
+ echo "User saved successfully.\n";
+ }
+ } catch (Exception $e) {
+ echo "Error: " . $e->getMessage() . "\n";
+ }
+ }
+}
+
+// Data array
+$users_data = [
+ ['name' => 'Alice', 'email' => 'alice@example.com'],
+ ['name' => 'Bob', 'email' => 'bob@example.com'],
+];
+
+$user_objects = [];
+
+// Loop and create objects
+if (!empty($users_data)) {
+ foreach ($users_data as $data) {
+ $user_objects[] = new User($data['name'], $data['email']);
+ }
+}
+
+// Call function
+processUsers($user_objects);
+
+// Alternative syntax for control structures
+$count = 0;
+while ($count < 3):
+ echo "Count is $count\n";
+ $count++;
+endwhile;
+
+echo "Script completed.\n";
+
+?>
diff --git a/samples/test.py b/samples/test.py
new file mode 100644
index 0000000..aaa4ad7
--- /dev/null
+++ b/samples/test.py
@@ -0,0 +1,20 @@
+def factorial(n):
+ # This is a comment explaining the function
+ if n <= 1:
+ return 1
+ else:
+ return n * factorial(n - 1)
+
+class Calculator:
+ def __init__(self):
+ self.result = 0
+
+ def add(self, a, b):
+ """Docstring for add method"""
+ return a + b
+
+if __name__ == "__main__":
+ print("Factorial of 5 is:", factorial(5))
+ calc = Calculator()
+ res = calc.add(10, 20)
+ print(f"Result: {res}")
diff --git a/samples/test.rb b/samples/test.rb
new file mode 100644
index 0000000..3fbfb9f
--- /dev/null
+++ b/samples/test.rb
@@ -0,0 +1,53 @@
+# A simple Ruby class demonstration
+
+class Person
+ attr_accessor :name, :age
+
+ def initialize(name, age)
+ @name = name
+ @age = age
+ end
+
+ def introduce
+ puts "Hi, I'm #{@name} and I am #{@age} years old."
+ end
+
+ def can_vote?
+ @age >= 18
+ end
+end
+
+# Module definition
+module Greeter
+ def self.say_hello(name)
+ puts "Hello, #{name}!"
+ end
+end
+
+# Main execution
+if __FILE__ == $0
+ alice = Person.new("Alice", 25)
+ alice.introduce
+
+ if alice.can_vote?
+ puts "#{alice.name} can vote."
+ else
+ puts "#{alice.name} cannot vote."
+ end
+
+ Greeter.say_hello("Bob")
+
+ # Array and block
+ numbers = [1, 2, 3, 4, 5]
+ squared = numbers.map { |n| n * n }
+ puts "Squared numbers: #{squared.inspect}"
+
+ # Hash
+ config = {
+ :env => "production",
+ :retries => 3,
+ :timeout => 500
+ }
+
+ puts "Environment: #{config[:env]}"
+end
diff --git a/samples/test.sh b/samples/test.sh
new file mode 100644
index 0000000..5696456
--- /dev/null
+++ b/samples/test.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+
+# A simple bash script demo
+
+APP_NAME="MyShellApp"
+VERSION="1.0.0"
+
+echo "Starting $APP_NAME version $VERSION..."
+
+# Check if a directory exists
+if [ -d "/tmp" ]; then
+ echo "/tmp directory exists."
+else
+ echo "/tmp directory does not exist, creating it..."
+ mkdir /tmp
+fi
+
+# Loop through arguments
+echo "Arguments provided:"
+for arg in "$@"; do
+ echo "- $arg"
+done
+
+# Function definition
+greet() {
+ local name=$1
+ echo "Hello, $name!"
+}
+
+# Call function
+greet "User"
+
+# While loop
+count=0
+while [ $count -lt 5 ]; do
+ echo "Count: $count"
+ ((count++))
+done
+
+# Case statement
+case "$1" in
+ start)
+ echo "Starting service..."
+ ;;
+ stop)
+ echo "Stopping service..."
+ ;;
+ restart)
+ echo "Restarting service..."
+ ;;
+ *)
+ echo "Usage: $0 {start|stop|restart}"
+ exit 1
+ ;;
+esac
+
+echo "Done."
diff --git a/samples/test.sql b/samples/test.sql
new file mode 100644
index 0000000..40311ef
--- /dev/null
+++ b/samples/test.sql
@@ -0,0 +1,45 @@
+-- Create a new table called 'users'
+CREATE TABLE users (
+ id SERIAL PRIMARY KEY,
+ username VARCHAR(50) NOT NULL UNIQUE,
+ email VARCHAR(100) NOT NULL UNIQUE,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ active BOOLEAN DEFAULT TRUE
+);
+
+-- Insert some sample data
+INSERT INTO users (username, email) VALUES
+ ('john_doe', 'john@example.com'),
+ ('jane_smith', 'jane@example.com'),
+ ('bob_jones', 'bob@test.com');
+
+-- Select data with a condition
+SELECT * FROM users
+WHERE active = TRUE AND created_at > '2023-01-01';
+
+-- Update a record
+UPDATE users
+SET active = FALSE
+WHERE username = 'bob_jones';
+
+-- Join with another table
+SELECT u.username, p.title
+FROM users u
+JOIN posts p ON u.id = p.user_id
+WHERE u.active = TRUE
+ORDER BY p.created_at DESC;
+
+-- Create an index
+CREATE INDEX idx_users_email ON users(email);
+
+-- Create a view
+CREATE OR REPLACE VIEW active_users AS
+SELECT id, username, email
+FROM users
+WHERE active = TRUE;
+
+-- Delete a record
+DELETE FROM users WHERE id = 100;
+
+-- Drop the table
+-- DROP TABLE users;
diff --git a/samples/test.ts b/samples/test.ts
new file mode 100644
index 0000000..ebbea8e
--- /dev/null
+++ b/samples/test.ts
@@ -0,0 +1,138 @@
+// sample.ts
+// A practical TypeScript tour in one file πŸš€
+
+// --------------------
+// Basic Types
+// --------------------
+let username: string = "Alice";
+let age: number = 30;
+let isAdmin: boolean = false;
+
+let hobbies: string[] = ["gaming", "coding"];
+let scores: Array<number> = [10, 20, 30];
+
+// Tuple
+let userTuple: [string, number] = ["Bob", 42];
+
+// --------------------
+// Enums
+// --------------------
+enum Role {
+ User = "USER",
+ Admin = "ADMIN",
+ Moderator = "MODERATOR",
+}
+
+// --------------------
+// Interfaces & Types
+// --------------------
+interface User {
+ id: number;
+ name: string;
+ role: Role;
+ email?: string; // optional
+}
+
+type ApiResponse<T> = {
+ success: boolean;
+ data: T;
+ error?: string;
+};
+
+// --------------------
+// Functions
+// --------------------
+function greet(user: User): string {
+ return `Hello, ${user.name}! Your role is ${user.role}.`;
+}
+
+const add = (a: number, b: number): number => a + b;
+
+// Function with default value
+function log(message: string, level: "info" | "warn" | "error" = "info") {
+ console.log(`[${level.toUpperCase()}] ${message}`);
+}
+
+// --------------------
+// Classes
+// --------------------
+class UserService {
+ private users: User[] = [];
+
+ constructor(initialUsers: User[] = []) {
+ this.users = initialUsers;
+ }
+
+ addUser(user: User): void {
+ this.users.push(user);
+ }
+
+ findByRole(role: Role): User[] {
+ return this.users.filter(u => u.role === role);
+ }
+}
+
+// --------------------
+// Generics
+// --------------------
+function wrapResponse<T>(data: T): ApiResponse<T> {
+ return {
+ success: true,
+ data,
+ };
+}
+
+// --------------------
+// Async / Await
+// --------------------
+async function fetchUser(id: number): Promise<User> {
+ // Fake async call
+ return new Promise(resolve => {
+ setTimeout(() => {
+ resolve({
+ id,
+ name: "Charlie",
+ role: Role.User,
+ });
+ }, 500);
+ });
+}
+
+// --------------------
+// Type Narrowing
+// --------------------
+function printValue(value: string | number) {
+ if (typeof value === "string") {
+ console.log("String value:", value.toUpperCase());
+ } else {
+ console.log("Number value:", value.toFixed(2));
+ }
+}
+
+// --------------------
+// Usage Example
+// --------------------
+(async () => {
+ const user: User = {
+ id: 1,
+ name: username,
+ role: isAdmin ? Role.Admin : Role.User,
+ };
+
+ log(greet(user));
+
+ const service = new UserService([user]);
+ service.addUser({ id: 2, name: "Dana", role: Role.Admin });
+
+ console.log("Admins:", service.findByRole(Role.Admin));
+
+ const fetchedUser = await fetchUser(3);
+ const response = wrapResponse(fetchedUser);
+
+ console.log("API response:", response);
+
+ printValue("hello");
+ printValue(123.456);
+
+ console.log("2 + 3 =", add(2, 3));
+})();
diff --git a/samples/test.tsx b/samples/test.tsx
new file mode 100644
index 0000000..0578ce4
--- /dev/null
+++ b/samples/test.tsx
@@ -0,0 +1,107 @@
+// Sample.tsx
+// A practical TSX + React + TypeScript example 🧩
+
+import React, { useEffect, useState } from "react";
+
+// --------------------
+// Types & Interfaces
+// --------------------
+type Role = "USER" | "ADMIN";
+
+interface User {
+ id: number;
+ name: string;
+ role: Role;
+}
+
+// Props for a component
+interface UserCardProps {
+ user: User;
+ onSelect: (id: number) => void;
+}
+
+// --------------------
+// Reusable Component
+// --------------------
+const UserCard: React.FC<UserCardProps> = ({ user, onSelect }) => {
+ return (
+ <div style={{ border: "1px solid #ccc", padding: 12, marginBottom: 8 }}>
+ <h3>{user.name}</h3>
+ <p>Role: {user.role}</p>
+ <button onClick={() => onSelect(user.id)}>Select</button>
+ </div>
+ );
+};
+
+// --------------------
+// Main Component
+// --------------------
+const Sample: React.FC = () => {
+ // Typed state
+ const [users, setUsers] = useState<User[]>([]);
+ const [selectedUserId, setSelectedUserId] = useState<number | null>(null);
+ const [loading, setLoading] = useState<boolean>(true);
+
+ // --------------------
+ // Side Effects
+ // --------------------
+ useEffect(() => {
+ // Fake async fetch
+ const loadUsers = async () => {
+ setLoading(true);
+
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+ setUsers([
+ { id: 1, name: "Alice", role: "USER" },
+ { id: 2, name: "Bob", role: "ADMIN" },
+ ]);
+
+ setLoading(false);
+ };
+
+ loadUsers();
+ }, []);
+
+ // --------------------
+ // Event Handlers
+ // --------------------
+ const handleSelectUser = (id: number) => {
+ setSelectedUserId(id);
+ };
+
+ // --------------------
+ // Derived Values
+ // --------------------
+ const selectedUser = users.find(u => u.id === selectedUserId);
+
+ // --------------------
+ // Conditional Rendering
+ // --------------------
+ if (loading) {
+ return <div>Loading users...</div>;
+ }
+
+ return (
+ <div style={{ maxWidth: 400, margin: "0 auto" }}>
+ <h2>User List</h2>
+
+ {users.map(user => (
+ <UserCard
+ key={user.id}
+ user={user}
+ onSelect={handleSelectUser}
+ />
+ ))}
+
+ {selectedUser && (
+ <div style={{ marginTop: 16 }}>
+ <strong>Selected User:</strong>
+ <div>{selectedUser.name}</div>
+ </div>
+ )}
+ </div>
+ );
+};
+
+export default Sample;