From 617b10ded3722a8c2d615fbee5dd256f5f3c6a7f Mon Sep 17 00:00:00 2001 From: Richard Qin Date: Sat, 11 Apr 2026 03:40:54 +0800 Subject: feat: implement terminal renderer and integrate into browser core --- include/ricochet/render/renderer.hh | 43 ++++++++++++++++++++++++ src/core/browser.cc | 35 +++++++++++--------- src/net/http_client.cc | 3 +- src/render/renderer.cc | 65 +++++++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 17 deletions(-) create mode 100644 include/ricochet/render/renderer.hh create mode 100644 src/render/renderer.cc diff --git a/include/ricochet/render/renderer.hh b/include/ricochet/render/renderer.hh new file mode 100644 index 0000000..8a5c463 --- /dev/null +++ b/include/ricochet/render/renderer.hh @@ -0,0 +1,43 @@ +/* + * Ricochet - A minimalist CLI web browser for fast, distraction-free navigation. + * Copyright (C) 2026 Richard Qin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "ricochet/parser/dom_tree.hh" + +namespace ricochet::render { + +class Renderer +{ +public: + Renderer() = default; + ~Renderer() = default; + + Renderer( const Renderer& ) = delete; + Renderer& operator=( const Renderer& ) = delete; + Renderer( Renderer&& ) = delete; + Renderer& operator=( Renderer&& ) = delete; + + /** + * @brief Traverse the DOM tree and output the formatted result to the terminal using ANSI escape codes + * @param node The root node of the DOM tree + */ + void render( const parser::DomNode& node ) const; +}; + +} // namespace ricochet::render \ No newline at end of file diff --git a/src/core/browser.cc b/src/core/browser.cc index e4c4c60..31f4df0 100644 --- a/src/core/browser.cc +++ b/src/core/browser.cc @@ -20,23 +20,24 @@ #include "ricochet/net/http_client.hh" #include "ricochet/parser/html_lexer.hh" #include "ricochet/parser/tree_builder.hh" +#include "ricochet/render/renderer.hh" #include -namespace { -void print_dom_tree( const ricochet::parser::DomNode& node, std::size_t depth = 0 ) // NOLINT(misc-no-recursion) -{ - const std::string indent( depth * 2, ' ' ); +// namespace { +// void print_dom_tree( const ricochet::parser::DomNode& node, std::size_t depth = 0 ) // NOLINT(misc-no-recursion) +// { +// const std::string indent( depth * 2, ' ' ); - if ( node.tag_name.empty() ) { - std::cout << indent << "\"" << node.text_content << "\"\n"; - } else { - std::cout << indent << "<" << node.tag_name << ">\n"; - for ( const auto& child : node.children ) { - print_dom_tree( child, depth + 1 ); - } - } -} -} // namespace +// if ( node.tag_name.empty() ) { +// std::cout << indent << "\"" << node.text_content << "\"\n"; +// } else { +// std::cout << indent << "<" << node.tag_name << ">\n"; +// for ( const auto& child : node.children ) { +// print_dom_tree( child, depth + 1 ); +// } +// } +// } +// } // namespace namespace ricochet::core { int Browser::run( std::string_view initial_url ) @@ -59,8 +60,10 @@ int Browser::run( std::string_view initial_url ) const parser::TreeBuilder builder; const auto dom_root = builder.build( tokens ); - std::cout << "\n[=== Abstract Syntax Tree (DOM) ===]\n"; - print_dom_tree( dom_root ); + // std::cout << "\033[2J\033[1;1H"; + + const render::Renderer renderer; + renderer.render( dom_root ); return 0; } diff --git a/src/net/http_client.cc b/src/net/http_client.cc index d45de40..b31073c 100644 --- a/src/net/http_client.cc +++ b/src/net/http_client.cc @@ -59,7 +59,8 @@ std::expected parse_url( std::string_view url ) } } // namespace -std::expected HttpClient::fetch( // NOLINT(readability-convert-member-functions-to-static) +std::expected +HttpClient::fetch( // NOLINT(readability-convert-member-functions-to-static) std::string_view url ) const { auto parsed_url = parse_url( url ); diff --git a/src/render/renderer.cc b/src/render/renderer.cc new file mode 100644 index 0000000..81c2be2 --- /dev/null +++ b/src/render/renderer.cc @@ -0,0 +1,65 @@ +/* + * Ricochet - A minimalist CLI web browser for fast, distraction-free navigation. + * Copyright (C) 2026 Richard Qin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ricochet/render/renderer.hh" +#include + +namespace ricochet::render { + +namespace { + +void render_node( const parser::DomNode& node ) // NOLINT(misc-no-recursion) +{ + if ( node.tag_name == "head" || node.tag_name == "style" || node.tag_name == "script" || node.tag_name == "meta" + || node.tag_name == "title" || node.tag_name == "link" ) { + return; + } + + if ( node.tag_name.empty() ) { + std::cout << node.text_content; + return; + } + if ( node.tag_name == "h1" ) { + std::cout << "\n\n\033[1;31m"; + } else if ( node.tag_name == "a" ) { + std::cout << "\033[4;34m"; + } else if ( node.tag_name == "p" || node.tag_name == "div" ) { + std::cout << "\n"; + } + + for ( const auto& child : node.children ) { + render_node( child ); + } + if ( node.tag_name == "h1" || node.tag_name == "a" ) { + std::cout << "\033[0m"; + } + + if ( node.tag_name == "h1" || node.tag_name == "p" || node.tag_name == "div" ) { + std::cout << "\n"; + } +} + +} // namespace + +void Renderer::render( const parser::DomNode& node ) const // NOLINT(readability-convert-member-functions-to-static) +{ + render_node( node ); + std::cout << "\n"; +} + +} // namespace ricochet::render \ No newline at end of file -- cgit v1.2.3