aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt9
-rw-r--r--src/core/browser.cc65
-rw-r--r--src/render/renderer.cc21
-rw-r--r--src/tui/terminal.cc62
4 files changed, 131 insertions, 26 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 9ddc717..dcc8f49 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -3,8 +3,17 @@ file(GLOB_RECURSE ALL_SOURCES CONFIGURE_DEPENDS "*.cc")
set(MAIN_SRC "${CMAKE_CURRENT_SOURCE_DIR}/main.cc")
list(REMOVE_ITEM ALL_SOURCES ${MAIN_SRC})
+find_package(OpenSSL REQUIRED)
+message(STATUS "Found OpenSSL Version: ${OPENSSL_VERSION}")
+message(STATUS "OpenSSL Include dir: ${OPENSSL_INCLUDE_DIR}")
+
add_library(ricochet_core STATIC ${ALL_SOURCES})
target_include_directories(ricochet_core PUBLIC ${PROJECT_SOURCE_DIR}/include)
+target_link_libraries(ricochet_core
+ PUBLIC
+ OpenSSL::SSL
+ OpenSSL::Crypto
+)
add_executable(ricochet ${MAIN_SRC})
target_link_libraries(ricochet PRIVATE ricochet_core)
diff --git a/src/core/browser.cc b/src/core/browser.cc
index 31f4df0..0636de6 100644
--- a/src/core/browser.cc
+++ b/src/core/browser.cc
@@ -21,23 +21,11 @@
#include "ricochet/parser/html_lexer.hh"
#include "ricochet/parser/tree_builder.hh"
#include "ricochet/render/renderer.hh"
+#include "ricochet/tui/terminal.hh"
+#include <cstddef>
#include <iostream>
+#include <sstream>
-// 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
namespace ricochet::core {
int Browser::run( std::string_view initial_url )
@@ -63,8 +51,53 @@ int Browser::run( std::string_view initial_url )
// std::cout << "\033[2J\033[1;1H";
const render::Renderer renderer;
- renderer.render( dom_root );
+ const std::string rendered_text = renderer.render( dom_root );
+ std::vector<std::string> lines;
+ std::stringstream ss(rendered_text);
+ std::string line;
+ while (std::getline(ss, line, '\n')) {
+ lines.push_back(line);
+ }
+
+ // TUI Event Loop
+ const tui::Terminal terminal;
+
+ std::size_t scroll_y = 0;
+ const std::size_t screen_height = 24;
+
+ while (true) {
+ terminal.clear_screen();
+
+ for (std::size_t i = 0; i < screen_height - 1; ++i) {
+ const std::size_t line_idx = scroll_y + i;
+ if (line_idx < lines.size()) {
+ std::cout << lines[line_idx] << "\r\n";
+ } else {
+ std::cout << "~\r\n";
+ }
+ }
+
+ std::cout << "\033[7m" << " URL: " << initial_url
+ << " | [j] Down [k] Up [q] Quit " << "\033[0m";
+ std::cout.flush();
+
+ const char c = terminal.read_key();
+ if (c == 'q') {
+ break; // Break loop, terminal destructor will restore terminal
+ }
+ if (c == 'j') {
+ if (scroll_y + screen_height - 1 < lines.size()) {
+ scroll_y++; // Scroll down
+ }
+ } else if (c == 'k') {
+ if (scroll_y > 0) {
+ scroll_y--; // Scroll up
+ }
+ }
+ }
+
+ terminal.clear_screen();
return 0;
}
diff --git a/src/render/renderer.cc b/src/render/renderer.cc
index 81c2be2..0a91d49 100644
--- a/src/render/renderer.cc
+++ b/src/render/renderer.cc
@@ -23,7 +23,7 @@ namespace ricochet::render {
namespace {
-void render_node( const parser::DomNode& node ) // NOLINT(misc-no-recursion)
+void render_node( const parser::DomNode& node, std::string& output ) // 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" ) {
@@ -31,35 +31,36 @@ void render_node( const parser::DomNode& node ) // NOLINT(misc-no-recursion)
}
if ( node.tag_name.empty() ) {
- std::cout << node.text_content;
+ output += node.text_content;
return;
}
if ( node.tag_name == "h1" ) {
- std::cout << "\n\n\033[1;31m";
+ output += "\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";
+ output += "\n";
}
for ( const auto& child : node.children ) {
- render_node( child );
+ render_node( child, output );
}
if ( node.tag_name == "h1" || node.tag_name == "a" ) {
- std::cout << "\033[0m";
+ output += "\033[0m";
}
if ( node.tag_name == "h1" || node.tag_name == "p" || node.tag_name == "div" ) {
- std::cout << "\n";
+ output += "\n";
}
}
} // namespace
-void Renderer::render( const parser::DomNode& node ) const // NOLINT(readability-convert-member-functions-to-static)
+std::string Renderer::render( const parser::DomNode& node ) const // NOLINT(readability-convert-member-functions-to-static)
{
- render_node( node );
- std::cout << "\n";
+ std::string output;
+ render_node( node, output );
+ return output;
}
} // namespace ricochet::render \ No newline at end of file
diff --git a/src/tui/terminal.cc b/src/tui/terminal.cc
new file mode 100644
index 0000000..8fdb585
--- /dev/null
+++ b/src/tui/terminal.cc
@@ -0,0 +1,62 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "ricochet/tui/terminal.hh"
+#include <cstdlib>
+#include <iostream>
+#include <unistd.h>
+
+namespace ricochet::tui {
+
+Terminal::Terminal() {
+ // get terminal attributes
+ tcgetattr(STDIN_FILENO, &original_termios_);
+
+ struct termios raw = original_termios_;
+
+ // close echo, close canonical mode
+ // close terminal signals (ISIG disable Ctrl+C and Ctrl+Z, let the program handle it)
+ raw.c_lflag &= ~(ECHO | ICANON | ISIG);
+
+ // close software flow control (Ctrl+S, Ctrl+Q)
+ raw.c_iflag &= ~(IXON | ICRNL);
+
+ // apply new terminal attributes
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
+}
+
+Terminal::~Terminal() {
+ // RAII: restore terminal attributes when program exits
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &original_termios_);
+}
+
+char Terminal::read_key() const { // NOLINT(readability-convert-member-functions-to-static)
+ char c = '\0';
+ // read 1 byte
+ if (read(STDIN_FILENO, &c, 1) == -1) {
+ return '\0';
+ }
+ return c;
+}
+
+void Terminal::clear_screen() const { // NOLINT(readability-convert-member-functions-to-static)
+ // ANSI escape codes: \x1b[2J clear screen, \x1b[H move cursor to (1,1)
+ std::cout << "\x1b[2J\x1b[H";
+}
+
+} // namespace ricochet::tui \ No newline at end of file