feat: add semantic search tool for code symbol indexing and searching

- Implemented a new tool for semantic code search that indexes Rust code symbols (functions, structs, enums, traits, modules) and allows searching by name, concept, or meaning.
- Introduced a symbol index structure with methods for rebuilding the index and searching symbols.
- Added regex patterns for extracting various code symbols from Rust source files.
- Implemented scoring logic for search results based on exact matches, prefix matches, and context relevance.
- Created a web search tool that interacts with a SearXNG instance to fetch documentation and API information based on user queries.
- Added a diff preview overlay for rendering git diff output with color-coded additions and deletions in a TUI interface.
This commit is contained in:
asepharyana
2026-07-20 16:59:27 +07:00
parent 785ae19757
commit fef3c925cd
17 changed files with 1968 additions and 0 deletions
@@ -0,0 +1,138 @@
//! Diff preview overlay — renders git diff output with color-coded
//! additions (green) and deletions (red) in a scrollable panel.
//!
//! Flow: read `state.misc.diff_content` → parse diff lines → apply syntax
//! coloring (green `+`, red `-`, blue header, dim context) → render in a
//! scrollable paragraph widget.
use crate::state::AppStateRest;
use ratatui::layout::Rect;
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Paragraph, Wrap};
use ratatui::Frame;
use super::super::theme::Theme;
use super::overlay_block;
/// Render the diff preview overlay with color-coded +/- lines and scrolling.
///
/// The overlay fills most of the screen and shows:
/// - Green lines for additions (`+`)
/// - Red lines for deletions (`-`)
/// - Blue/dim headers (diff --git, @@ hunk headers)
/// - Dim context lines
/// - Scroll hint at the bottom
pub fn render(frame: &mut Frame, area: Rect, block: Block<'static>, state: &AppStateRest) {
let block = overlay_block(block, " Git Diff Preview ", Theme::PRIMARY);
let inner = block.inner(area);
// Render the block background
frame.render_widget(block.clone(), area);
let diff_content = &state.misc.diff_content;
let diff_scroll = state.misc.diff_scroll;
// Parse lines with ANSI-style coloring
let lines: Vec<Line> = diff_content
.lines()
.skip(diff_scroll)
.map(|line| {
let trimmed = line.trim_end();
if trimmed.starts_with('+') && !trimmed.starts_with("+++") {
// Addition — green
Line::from(Span::styled(
format!("{trimmed}\n"),
Style::default().fg(Theme::SUCCESS),
))
} else if trimmed.starts_with('-') && !trimmed.starts_with("---") {
// Deletion — red
Line::from(Span::styled(
format!("{trimmed}\n"),
Style::default().fg(Theme::ERROR),
))
} else if trimmed.starts_with("@@") {
// Hunk header — cyan
Line::from(Span::styled(
format!("{trimmed}\n"),
Style::default()
.fg(Theme::INFO)
.add_modifier(Modifier::DIM),
))
} else if trimmed.starts_with("diff --git")
|| trimmed.starts_with("index ")
|| trimmed.starts_with("--- ")
|| trimmed.starts_with("+++ ")
|| trimmed.starts_with("===")
{
// File header — blue/bold
Line::from(Span::styled(
format!("{trimmed}\n"),
Style::default()
.fg(Theme::PRIMARY)
.add_modifier(Modifier::BOLD),
))
} else {
// Context — dim
Line::from(Span::styled(
format!("{trimmed}\n"),
Style::default().fg(Theme::TEXT_DIM),
))
}
})
.collect();
let total_lines = diff_content.lines().count();
let visible_lines = inner.height as usize;
let has_more = diff_scroll + visible_lines < total_lines;
// Create the content paragraph
let paragraph = Paragraph::new(lines).wrap(Wrap { trim: false });
frame.render_widget(paragraph, inner);
// Render scroll indicator at the bottom
if has_more {
let scroll_pct = if total_lines > 0 {
((diff_scroll as f64 / total_lines as f64) * 100.0) as u32
} else {
0
};
let scroll_text = format!(
" Lines {}-{} of {} ({}%) ",
diff_scroll + 1,
(diff_scroll + visible_lines).min(total_lines),
total_lines,
scroll_pct
);
let status_line = Line::from(Span::styled(
scroll_text,
Style::default()
.fg(Theme::TEXT_DIM)
.add_modifier(Modifier::ITALIC),
));
let status_area = Rect {
x: area.x,
y: area.y + area.height - 1,
width: area.width,
height: 1,
};
frame.render_widget(Paragraph::new(status_line), status_area);
}
// Render keybinding hint at the top
let hint = Line::from(Span::styled(
" ↑/↓: scroll Esc: close /diff: refresh",
Style::default()
.fg(Theme::TEXT_DIM)
.add_modifier(Modifier::ITALIC),
));
let hint_area = Rect {
x: area.x,
y: area.y,
width: area.width,
height: 1,
};
frame.render_widget(Paragraph::new(hint), hint_area);
}
@@ -4,6 +4,7 @@
pub mod bash;
pub mod clear_confirm;
pub mod diff;
pub mod editor;
pub mod effort;
pub mod help;
@@ -123,5 +124,8 @@ pub fn render_overlay(
Overlay::ClearConfirm => {
clear_confirm::render(frame, overlay_area, block, state);
}
Overlay::Diff => {
diff::render(frame, overlay_area, block, state);
}
}
}