diff options
| author | Richard Qin <richard.qin.x@gmail.com> | 2026-04-12 20:35:05 +0800 |
|---|---|---|
| committer | Richard Qin <richard.qin.x@gmail.com> | 2026-04-12 20:35:05 +0800 |
| commit | 32bce7c278e8f52d96579650088f610d62920f00 (patch) | |
| tree | 72265c1703aab26373fbe7670585d02eb79cb9cd | |
| parent | 5a8bb74e922f9ecebffbb03832b559f9dec21473 (diff) | |
| download | rwebbrowser-32bce7c278e8f52d96579650088f610d62920f00.tar.gz rwebbrowser-32bce7c278e8f52d96579650088f610d62920f00.tar.xz | |
feat: add support for interacting with form inputs via the 'i' key
| -rw-r--r-- | include/ricochet/render/renderer.hh | 1 | ||||
| -rw-r--r-- | src/core/browser.cc | 93 | ||||
| -rw-r--r-- | src/render/renderer.cc | 30 |
3 files changed, 114 insertions, 10 deletions
diff --git a/include/ricochet/render/renderer.hh b/include/ricochet/render/renderer.hh index 2a9acae..8ea660c 100644 --- a/include/ricochet/render/renderer.hh +++ b/include/ricochet/render/renderer.hh @@ -26,6 +26,7 @@ struct RenderResult { std::string text {}; std::vector<std::string> links {}; + std::vector<std::string> inputs {}; }; class Renderer diff --git a/src/core/browser.cc b/src/core/browser.cc index 4c42bd0..e3a739a 100644 --- a/src/core/browser.cc +++ b/src/core/browser.cc @@ -33,6 +33,7 @@ struct PageData { std::vector<std::string> lines; std::vector<std::string> links; + std::vector<std::string> inputs; std::string title; }; @@ -83,7 +84,8 @@ PageData load_page( std::string_view url ) const net::HttpClient client; auto response_result = client.fetch( url ); if ( !response_result.has_value() ) { - return { .lines = { "[!] Failed to load: " + std::string( url ) }, .links = {}, .title = "Error" }; + return { + .lines = { "[!] Failed to load: " + std::string( url ) }, .links = {}, .inputs = {}, .title = "Error" }; } const parser::HtmlLexer lexer; @@ -105,7 +107,7 @@ PageData load_page( std::string_view url ) start = end + 1; } const std::string title = extract_title( dom_root ); - return { .lines = lines, .links = result.links, .title = title }; + return { .lines = lines, .links = result.links, .inputs = result.inputs, .title = title }; } void draw_view( const tui::Terminal& terminal, @@ -147,7 +149,8 @@ void draw_view( const tui::Terminal& terminal, } } - std::string footer = " URL: " + std::string( url ) + " | [j/k]Scroll [/]Jump [f]Follow [H/L]History [q]Quit "; + std::string footer + = " URL: " + std::string( url ) + " | [j/k]Scroll [/]Jump [f]Follow [i]Input [H/L]History [q]Quit "; if ( footer.size() < width ) { footer.append( width - footer.size(), ' ' ); } else if ( footer.size() > width && width > 3 ) { @@ -327,6 +330,86 @@ enum class InputAction : std::uint8_t Navigate }; +std::string get_terminal_input( const tui::Terminal& terminal, const std::string& prompt ) +{ + auto [width, height] = terminal.get_size(); + terminal.move_cursor( height, 1 ); + std::cout << "\x1b[2K\033[7m " << prompt << " \033[0m "; + std::cout.flush(); + + std::string input; + terminal.show_cursor( true ); + while ( true ) { + const char ch = terminal.read_key(); + if ( ch == '\r' || ch == '\n' ) { + break; + } + if ( ch == 27 ) { + input.clear(); + break; + } + if ( ch == 127 || ch == 8 ) { + if ( !input.empty() ) { + input.pop_back(); + std::cout << "\b \b"; + std::cout.flush(); + } + } else if ( std::isprint( static_cast<unsigned char>( ch ) ) ) { + input += ch; + std::cout << ch; + std::cout.flush(); + } + } + terminal.show_cursor( false ); + return input; +} + +InputAction handle_form_input( const tui::Terminal& terminal, + const PageData& page_data, + std::string& current_url, + std::vector<std::string>& history, + std::size_t& history_idx ) +{ + const std::string num_input = get_terminal_input( terminal, "Select Input [#]:" ); + if ( num_input.empty() ) { + return InputAction::None; + } + + std::size_t index = 0; + try { + index = std::stoul( num_input ); + } catch ( ... ) { + (void)0; + } + if ( index == 0 || index > page_data.inputs.size() ) { + return InputAction::None; + } + + const std::string query = get_terminal_input( terminal, "Enter Text [I" + std::to_string( index ) + "]:" ); + if ( query.empty() ) { + return InputAction::None; + } + + std::string target_url; + std::string encoded_query = query; + for ( char& ch : encoded_query ) { + if ( ch == ' ' ) { + ch = '+'; + } + } + + if ( current_url.contains( "wikipedia.org" ) ) { + target_url = "https://en.wikipedia.org/w/index.php?search=" + encoded_query; + } else { + target_url = "https://lite.duckduckgo.com/lite/?q=" + encoded_query; + } + + history.resize( history_idx + 1 ); + history.push_back( target_url ); + history_idx++; + return InputAction::Navigate; +} + InputAction process_key( char c, const tui::Terminal& terminal, const PageData& page_data, @@ -377,6 +460,10 @@ InputAction process_key( char c, return InputAction::Navigate; } } + + if ( c == 'i' && !page_data.inputs.empty() ) { + return handle_form_input( terminal, page_data, current_url, history, history_idx ); + } return InputAction::None; } diff --git a/src/render/renderer.cc b/src/render/renderer.cc index ed622b4..5f65ea7 100644 --- a/src/render/renderer.cc +++ b/src/render/renderer.cc @@ -90,16 +90,28 @@ std::string extract_href( const std::string& tag_name ) return ""; } +bool is_ignored_tag( std::string_view tag ) +{ + return tag == "head" || tag == "style" || tag == "script" || tag == "meta" || tag == "title" || tag == "link"; +} + +bool is_heading_tag( std::string_view tag ) +{ + return tag == "h1" || tag == "h2" || tag == "h3" || tag == "h4"; +} + void render_node( const parser::DomNode& node, // NOLINT(misc-no-recursion) - std::string& output, - std::vector<std::string>& links ) + RenderResult& result ) // NOLINT(bugprone-easily-swappable-parameters) { + std::string& output = result.text; + std::vector<std::string>& links = result.links; + std::vector<std::string>& inputs = result.inputs; + const std::size_t space_pos = node.tag_name.find( ' ' ); const std::string base_tag = ( space_pos == std::string::npos ) ? node.tag_name : node.tag_name.substr( 0, space_pos ); - if ( base_tag == "head" || base_tag == "style" || base_tag == "script" || base_tag == "meta" - || base_tag == "title" || base_tag == "link" ) { + if ( is_ignored_tag( base_tag ) ) { return; } @@ -108,9 +120,10 @@ void render_node( const parser::DomNode& node, // NOLINT(misc-no-recursion) return; } - const bool is_header = ( base_tag == "h1" || base_tag == "h2" || base_tag == "h3" || base_tag == "h4" ); + const bool is_header = is_heading_tag( base_tag ); const bool is_link = ( base_tag == "a" ); const bool is_list_item = ( base_tag == "li" ); + const bool is_input = ( base_tag == "input" || base_tag == "textarea" ); if ( is_header ) { output += "\n\n\033[1;31m"; @@ -118,6 +131,9 @@ void render_node( const parser::DomNode& node, // NOLINT(misc-no-recursion) output += "\033[4;34m"; } else if ( is_list_item ) { output += "\n • "; + } else if ( is_input ) { + inputs.emplace_back( "input_field" ); + output += " \033[7;33m[I" + std::to_string( inputs.size() ) + "]\033[0m "; } else if ( base_tag == "p" || base_tag == "div" || is_header ) { if ( !output.empty() && output.back() != '\n' ) { output += "\n"; @@ -125,7 +141,7 @@ void render_node( const parser::DomNode& node, // NOLINT(misc-no-recursion) } for ( const auto& child : node.children ) { - render_node( child, output, links ); + render_node( child, result ); } if ( is_header ) { @@ -161,7 +177,7 @@ RenderResult Renderer::render( // NOLINT(readability-convert-member-functions-to const parser::DomNode& node ) const { RenderResult result; - render_node( node, result.text, result.links ); + render_node( node, result ); return result; } |
