smart-tree 8.0.1

Smart Tree - An intelligent, AI-friendly directory visualization tool
Documentation
// Smart Tips System - "Helpful hints without the hassle!"
// Shows tips at the top, detects cool terminals, and respects user preferences

use anyhow::Result;
use chrono::{DateTime, Utc};
use colored::*;
use rand::Rng;
use serde::{Deserialize, Serialize};
use std::env;
use std::fs;
use std::path::PathBuf;

#[derive(Debug, Serialize, Deserialize)]
struct TipsState {
    enabled: bool,
    last_shown: Option<DateTime<Utc>>,
    run_count: u32,
    next_show_at: u32,
}

impl Default for TipsState {
    fn default() -> Self {
        Self {
            enabled: true,
            last_shown: None,
            run_count: 0,
            next_show_at: 1, // Show on first run
        }
    }
}

pub struct TipsManager {
    state: TipsState,
    state_file: PathBuf,
    is_cool_terminal: bool,
}

impl TipsManager {
    pub fn new() -> Result<Self> {
        let state_file = dirs::home_dir()
            .unwrap_or_else(|| PathBuf::from("."))
            .join(".st")
            .join("tips_state.json");

        // Create .st directory if it doesn't exist - our home for all persistence!
        if let Some(parent) = state_file.parent() {
            fs::create_dir_all(parent).ok();
        }

        // Load or create state
        let state = if state_file.exists() {
            let content = fs::read_to_string(&state_file)?;
            serde_json::from_str(&content).unwrap_or_default()
        } else {
            TipsState::default()
        };

        // Detect cool terminal
        let is_cool_terminal = Self::detect_cool_terminal();

        Ok(Self {
            state,
            state_file,
            is_cool_terminal,
        })
    }

    fn detect_cool_terminal() -> bool {
        // Check for cool terminal features
        if let Ok(term) = env::var("TERM") {
            // Check for 256 color support or better
            if term.contains("256color") || term.contains("truecolor") {
                return true;
            }
        }

        // Check for specific terminals known to be cool
        if let Ok(term_program) = env::var("TERM_PROGRAM") {
            match term_program.as_str() {
                "iTerm.app" | "WezTerm" | "Alacritty" | "kitty" | "Hyper" => return true,
                _ => {}
            }
        }

        // Check for Windows Terminal
        if env::var("WT_SESSION").is_ok() {
            return true;
        }

        // Check for cool terminal emulators via env vars
        env::var("KITTY_WINDOW_ID").is_ok()
            || env::var("ALACRITTY_SOCKET").is_ok()
            || env::var("WEZTERM_PANE").is_ok()
    }

    pub fn should_show_tip(&mut self) -> bool {
        if !self.state.enabled {
            return false;
        }

        self.state.run_count += 1;

        // Show on first run or when we hit the random interval
        if self.state.run_count >= self.state.next_show_at {
            // Set next random show between 10-20 runs
            let mut rng = rand::thread_rng();
            self.state.next_show_at = self.state.run_count + rng.gen_range(10..=20);
            self.state.last_shown = Some(Utc::now());
            self.save_state().ok();
            true
        } else {
            self.save_state().ok();
            false
        }
    }

    pub fn disable_tips(&mut self) -> Result<()> {
        self.state.enabled = false;
        self.save_state()?;
        Ok(())
    }

    pub fn enable_tips(&mut self) -> Result<()> {
        self.state.enabled = true;
        self.state.next_show_at = self.state.run_count + 1; // Show on next run
        self.save_state()?;
        Ok(())
    }

    fn save_state(&self) -> Result<()> {
        let json = serde_json::to_string_pretty(&self.state)?;
        fs::write(&self.state_file, json)?;
        Ok(())
    }

    pub fn get_random_tip(&self) -> String {
        let tips = vec![
            (
                "🚀",
                "Speed tip",
                "Use --mode quantum for 100x compression on massive dirs!",
            ),
            (
                "🎨",
                "Format tip",
                "Try --mode markdown for beautiful documentation!",
            ),
            (
                "📊",
                "Stats tip",
                "Use --mode stats for instant project metrics!",
            ),
            (
                "🔍",
                "Search tip",
                "Smart Tree's MCP tools can search code 10x faster!",
            ),
            (
                "💾",
                "Memory tip",
                "Your consciousness is saved in .m8 files automatically!",
            ),
            (
                "🌊",
                "Stream tip",
                "Use --stream for directories with >100k files!",
            ),
            (
                "🧠",
                "Context tip",
                "Try --claude-restore to reload previous session!",
            ),
            (
                "",
                "Performance tip",
                "Release builds are 10x faster than debug!",
            ),
            (
                "🎯",
                "Focus tip",
                "Use --focus <file> for relationship analysis!",
            ),
            (
                "🔐",
                "Privacy tip",
                "Your .m8 memories stay local, never in git!",
            ),
            (
                "🎭",
                "Fun tip",
                "Try --persona cheetah for motivational output!",
            ),
            (
                "📈",
                "Git tip",
                "Use --git-aware to see repository status inline!",
            ),
            (
                "🎪",
                "MCP tip",
                "Run 'st --mcp' to expose 30+ tools to Claude!",
            ),
            (
                "🌈",
                "Color tip",
                "Your terminal supports full colors - enjoy the show!",
            ),
            (
                "⏱️",
                "Time tip",
                "Add --timings to see performance metrics!",
            ),
        ];

        let mut rng = rand::thread_rng();
        let tip = &tips[rng.gen_range(0..tips.len())];

        format!("{} {} - {}", tip.0, tip.1, tip.2)
    }

    pub fn display_tip(&self, terminal_width: usize) {
        let tip = self.get_random_tip();
        let disable_hint = "--tips off";

        if self.is_cool_terminal {
            // Fancy display for cool terminals
            self.display_fancy_tip(&tip, disable_hint, terminal_width);
        } else {
            // Simple display for basic terminals
            self.display_simple_tip(&tip, disable_hint);
        }
    }

    fn display_fancy_tip(&self, tip: &str, hint: &str, width: usize) {
        let hint_part = format!(" {} ", hint);
        let hint_len = hint_part.len();

        // Calculate how much space we have for the tip
        let available = width.saturating_sub(hint_len + 10); // Leave some padding

        // Truncate tip if needed
        let tip_display = if tip.len() > available {
            format!("{}...", &tip[..available.saturating_sub(3)])
        } else {
            tip.to_string()
        };

        // Create the fancy line
        let tip_part = format!(" {} ", tip_display);
        let remaining = width.saturating_sub(tip_part.len() + hint_len);
        let left_dashes = remaining / 2;
        let right_dashes = remaining - left_dashes;

        println!(
            "{}{}{}{}{}",
            "".repeat(left_dashes).bright_black(),
            tip_part.bright_cyan().bold(),
            "".repeat(3).bright_black(),
            hint_part.bright_yellow(),
            "".repeat(right_dashes.saturating_sub(3)).bright_black(),
        );
    }

    fn display_simple_tip(&self, tip: &str, hint: &str) {
        println!("Tip: {} (use {} to disable)", tip, hint);
    }
}

// Get terminal width helper
pub fn get_terminal_width() -> usize {
    terminal_size::terminal_size()
        .map(|(w, _)| w.0 as usize)
        .unwrap_or(80)
}

// Public API functions for main.rs
pub fn maybe_show_tip() -> Result<()> {
    let mut manager = TipsManager::new()?;

    if manager.should_show_tip() {
        let width = get_terminal_width();
        manager.display_tip(width);
        println!(); // Extra line for spacing
    }

    Ok(())
}

pub fn handle_tips_flag(enable: bool) -> Result<()> {
    let mut manager = TipsManager::new()?;

    if enable {
        manager.enable_tips()?;
        println!("✅ Smart tips enabled! You'll see helpful hints periodically.");
    } else {
        manager.disable_tips()?;
        println!("🔕 Smart tips disabled. Run with --tips on to re-enable.");
    }

    Ok(())
}