/* * 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/core/local_data.hh" #include #include #include #include namespace ricochet::core { std::string get_config_path( const std::string& filename ) { const char* home = std::getenv( "HOME" ); // NOLINT(concurrency-mt-unsafe) const std::string dir = ( home != nullptr ) ? std::string( home ) + "/.ricochet" : ".ricochet"; static_cast( mkdir( dir.c_str(), 0755 ) ); return dir + "/" + filename; } void save_bookmark( const std::string& title, // NOLINT(bugprone-easily-swappable-parameters) const std::string& url ) { if ( url == "ricochet://bookmarks" ) { return; } std::ofstream file( get_config_path( "bookmarks.txt" ), std::ios::app ); if ( !file.is_open() ) { return; } std::string safe_title = title; if ( safe_title.empty() ) { safe_title = url; } for ( char& c : safe_title ) { if ( c == '\n' || c == '\r' || c == '\t' ) { c = ' '; } } file << url << '\t' << safe_title << '\n'; } std::string generate_bookmarks_html() { std::ifstream file( get_config_path( "bookmarks.txt" ) ); std::string html = "My Bookmarks

Saved Bookmarks

    "; if ( !file.is_open() ) { html += "
  • No bookmarks found. Press 'b' on any page to add one!
  • "; } else { std::string line; while ( std::getline( file, line ) ) { const std::size_t tab_pos = line.find( '\t' ); if ( tab_pos != std::string::npos ) { const std::string url = line.substr( 0, tab_pos ); const std::string title = line.substr( tab_pos + 1 ); html += "
  • [Link] "; html += title; html += "
  • "; } } } html += "
"; return html; } void append_to_history( const std::string& url, // NOLINT(bugprone-easily-swappable-parameters) const std::string& title ) { if ( url.starts_with( "ricochet://" ) ) { return; } std::ofstream file( get_config_path( "history.txt" ), std::ios::app ); if ( !file.is_open() ) { return; } std::string safe_title = title; if ( safe_title.empty() ) { safe_title = url; } for ( char& c : safe_title ) { if ( c == '\n' || c == '\r' || c == '\t' ) { c = ' '; } } file << url << '\t' << safe_title << '\n'; } std::string generate_history_html() { std::ifstream file( get_config_path( "history.txt" ) ); std::string html = "History

Browsing History

    "; if ( !file.is_open() ) { html += "
  • No history found. Start browsing!
  • "; } else { std::vector lines; std::string line; while ( std::getline( file, line ) ) { lines.push_back( std::move( line ) ); } std::size_t count = 0; for ( auto it = lines.rbegin(); it != lines.rend() && count < 100; ++it, ++count ) { const std::size_t tab_pos = it->find( '\t' ); if ( tab_pos != std::string::npos ) { const std::string url = it->substr( 0, tab_pos ); const std::string title = it->substr( tab_pos + 1 ); html += "
  • [Link] "; html += title; html += "
  • "; } } } html += "
"; return html; } std::string generate_help_html() { return R"( Ricochet Help Manual

Ricochet User Manual

1. Navigation & Browsing

  • j / k : Scroll down / up
  • H / L : History backward / forward
  • / : Open prompt to enter a new URL

2. Page Interaction

  • f : Toggle Link Hints (type the two letters to follow a link instantly)
  • n : Find in page (search for text)
  • N : Jump to the next search match
  • i : Enter form input mode (fill textboxes, click buttons, toggle checkboxes)

3. Bookmarks & History

  • b : Bookmark the current page
  • B : View all saved bookmarks
  • h : View browsing history

4. System

  • ? : Show this help manual
  • q : Quit Ricochet
)"; } } // namespace ricochet::core