From 815c479a90a9968b11086e41eaf523a6116722bb Mon Sep 17 00:00:00 2001 From: m1ngsama Date: Wed, 17 Dec 2025 13:53:46 +0800 Subject: [PATCH] feat: Add marks and mouse support for better navigation - Implement vim-style marks (ma to set, 'a to jump) * Store mark positions per character (a-z) * Display status messages when setting/jumping to marks * Integrated with vim keybinding infrastructure - Add full mouse support * Click on links to follow them directly * Mouse wheel scrolling (up/down) * Proper click detection within link ranges * Works with most modern terminal emulators - Enable ncurses mouse events * ALL_MOUSE_EVENTS for comprehensive support * Zero mouseinterval for instant response * Handle BUTTON1_CLICKED, BUTTON4_PRESSED (wheel up), BUTTON5_PRESSED (wheel down) - Update help documentation * Document marks keybindings * Add mouse support section * Note infrastructure for visual mode and tabs This brings TUT closer to feature parity with modern vim plugins while maintaining excellent usability for both keyboard and mouse users. --- src/browser.cpp | 98 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/src/browser.cpp b/src/browser.cpp index 70865de..5d6a6f1 100644 --- a/src/browser.cpp +++ b/src/browser.cpp @@ -3,6 +3,7 @@ #include #include #include +#include class Browser::Impl { public: @@ -26,6 +27,9 @@ public: int screen_height = 0; int screen_width = 0; + // Marks support (vim-style position bookmarks) + std::map marks; + void init_screen() { setlocale(LC_ALL, ""); initscr(); @@ -35,6 +39,11 @@ public: keypad(stdscr, TRUE); curs_set(0); timeout(0); + + // Enable mouse support + mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL); + mouseinterval(0); // No click delay + getmaxyx(stdscr, screen_height, screen_width); } @@ -73,6 +82,53 @@ public: return true; } + void handle_mouse(MEVENT& event) { + int visible_lines = screen_height - 2; + + // Mouse wheel up (scroll up) + if (event.bstate & BUTTON4_PRESSED) { + scroll_pos = std::max(0, scroll_pos - 3); + return; + } + + // Mouse wheel down (scroll down) + if (event.bstate & BUTTON5_PRESSED) { + int max_scroll = std::max(0, static_cast(rendered_lines.size()) - visible_lines); + scroll_pos = std::min(max_scroll, scroll_pos + 3); + return; + } + + // Left click + if (event.bstate & BUTTON1_CLICKED) { + int clicked_line = event.y; + int clicked_col = event.x; + + // Check if clicked on a link + if (clicked_line >= 0 && clicked_line < visible_lines) { + int doc_line_idx = scroll_pos + clicked_line; + if (doc_line_idx < static_cast(rendered_lines.size())) { + const auto& line = rendered_lines[doc_line_idx]; + + // Check if click is within any link range + for (const auto& [start, end] : line.link_ranges) { + if (clicked_col >= static_cast(start) && clicked_col < static_cast(end)) { + // Clicked on a link! + if (line.link_index >= 0 && line.link_index < static_cast(current_doc.links.size())) { + load_page(current_doc.links[line.link_index].url); + return; + } + } + } + + // If clicked on a line with a link but not on the link text itself + if (line.is_link && line.link_index >= 0) { + current_link = line.link_index; + } + } + } + } + } + void draw_status_bar() { attron(COLOR_PAIR(COLOR_STATUS_BAR)); mvprintw(screen_height - 1, 0, "%s", std::string(screen_width, ' ').c_str()); @@ -378,6 +434,27 @@ public: } break; + case Action::SET_MARK: + if (!result.text.empty()) { + char mark = result.text[0]; + marks[mark] = scroll_pos; + status_message = "Mark '" + std::string(1, mark) + "' set at line " + std::to_string(scroll_pos); + } + break; + + case Action::GOTO_MARK: + if (!result.text.empty()) { + char mark = result.text[0]; + auto it = marks.find(mark); + if (it != marks.end()) { + scroll_pos = std::min(it->second, max_scroll); + status_message = "Jumped to mark '" + std::string(1, mark) + "'"; + } else { + status_message = "Mark '" + std::string(1, mark) + "' not set"; + } + } + break; + case Action::HELP: show_help(); break; @@ -429,10 +506,22 @@ public: << "

:r or :refresh - Refresh page

" << "

:h or :help - Show this help

" << "

:[number] - Go to line number

" + << "

Vim Features

" + << "

m[a-z]: Set mark at letter (e.g., ma, mb)

" + << "

'[a-z]: Jump to mark (e.g., 'a, 'b)

" + << "

v: Enter visual mode (infrastructure ready)

" + << "

V: Enter visual line mode (infrastructure ready)

" + << "

gt: Next tab (infrastructure ready)

" + << "

gT: Previous tab (infrastructure ready)

" + << "

Mouse Support

" + << "

Click on links to follow them

" + << "

Scroll wheel to scroll up/down

" + << "

Works with most terminal emulators

" << "

Other

" << "

r: Refresh current page

" << "

q: Quit browser

" << "

?: Show help

" + << "

ESC: Cancel current mode

" << "

Important Limitations

" << "

JavaScript/SPA Websites: This browser cannot execute JavaScript. " << "Single Page Applications (SPAs) built with React, Vue, Angular, etc. will not work properly " @@ -489,6 +578,15 @@ void Browser::run(const std::string& initial_url) { continue; } + // Handle mouse events + if (ch == KEY_MOUSE) { + MEVENT event; + if (getmouse(&event) == OK) { + pImpl->handle_mouse(event); + } + continue; + } + auto result = pImpl->input_handler.handle_key(ch); if (result.action == Action::QUIT) {