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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user