use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::{Color, Modifier, Style};
use ratatui::widgets::Widget;
use crate::editor::Editor;
use crate::lsp;
pub struct WorkspaceSymbolsPopup<'a> {
editor: &'a Editor,
}
impl<'a> WorkspaceSymbolsPopup<'a> {
pub fn new(editor: &'a Editor) -> Self {
Self { editor }
}
}
impl Widget for WorkspaceSymbolsPopup<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
if !self.editor.showing_workspace_symbols {
return;
}
let popup_width = (area.width * 3 / 5).max(40).min(area.width.saturating_sub(4));
let max_items = 15.min(self.editor.workspace_symbol_results.len());
let popup_height = (max_items as u16 + 3).min(area.height.saturating_sub(2));
let popup_x = area.x + (area.width.saturating_sub(popup_width)) / 2;
let popup_y = area.y + (area.height.saturating_sub(popup_height)) / 3;
let border_style = Style::default()
.fg(Color::Rgb(80, 90, 110))
.bg(Color::Rgb(30, 33, 40));
let bg_style = Style::default()
.fg(Color::White)
.bg(Color::Rgb(30, 33, 40));
let selected_style = Style::default()
.fg(Color::White)
.bg(Color::Rgb(80, 90, 110))
.add_modifier(Modifier::BOLD);
let input_style = Style::default()
.fg(Color::White)
.bg(Color::Rgb(40, 44, 52));
for dy in 0..popup_height {
let y = popup_y + dy;
if y >= area.bottom() {
break;
}
for dx in 0..popup_width {
let x = popup_x + dx;
if x >= area.right() {
break;
}
let is_border =
dy == 0 || dy == popup_height - 1 || dx == 0 || dx == popup_width - 1;
let ch = if is_border {
if dy == 0 && dx == 0 {
'┌'
} else if dy == 0 && dx == popup_width - 1 {
'┐'
} else if dy == popup_height - 1 && dx == 0 {
'└'
} else if dy == popup_height - 1 && dx == popup_width - 1 {
'┘'
} else if dy == 0 || dy == popup_height - 1 {
'─'
} else {
'│'
}
} else {
' '
};
let style = if is_border { border_style } else { bg_style };
buf[(x, y)].set_char(ch).set_style(style);
}
}
let title = " Workspace Symbols ";
let title_x = popup_x + (popup_width.saturating_sub(title.len() as u16)) / 2;
for (i, ch) in title.chars().enumerate() {
let x = title_x + i as u16;
if x < area.right() && x > popup_x && x < popup_x + popup_width - 1 {
buf[(x, popup_y)].set_char(ch).set_style(
Style::default()
.fg(Color::Yellow)
.bg(Color::Rgb(30, 33, 40))
.add_modifier(Modifier::BOLD),
);
}
}
let input_y = popup_y + 1;
if input_y < area.bottom() {
let content_width = popup_width.saturating_sub(2) as usize;
for dx in 1..popup_width - 1 {
let x = popup_x + dx;
if x < area.right() {
buf[(x, input_y)].set_char(' ').set_style(input_style);
}
}
let prompt = "> ";
for (i, ch) in prompt.chars().enumerate() {
let x = popup_x + 1 + i as u16;
if x < area.right() {
buf[(x, input_y)].set_char(ch).set_style(
Style::default()
.fg(Color::Cyan)
.bg(Color::Rgb(40, 44, 52)),
);
}
}
for (i, ch) in self.editor.workspace_symbol_query.chars().enumerate() {
if i >= content_width - 2 {
break;
}
let x = popup_x + 3 + i as u16;
if x < area.right() {
buf[(x, input_y)].set_char(ch).set_style(input_style);
}
}
let cursor_x = popup_x + 3 + self.editor.workspace_symbol_query.len() as u16;
if cursor_x < popup_x + popup_width - 1 && cursor_x < area.right() {
buf[(cursor_x, input_y)]
.set_char(' ')
.set_style(Style::default().bg(Color::White));
}
let count_str = format!(" {}", self.editor.workspace_symbol_results.len());
let count_x = (popup_x + popup_width - 1).saturating_sub(count_str.len() as u16);
for (i, ch) in count_str.chars().enumerate() {
let x = count_x + i as u16;
if x < popup_x + popup_width - 1 && x < area.right() {
buf[(x, input_y)].set_char(ch).set_style(
Style::default()
.fg(Color::DarkGray)
.bg(Color::Rgb(40, 44, 52)),
);
}
}
}
let list_start_y = popup_y + 2;
let content_height = popup_height.saturating_sub(3) as usize;
let selected = self.editor.workspace_symbol_index;
let scroll = if selected >= content_height {
selected - content_height + 1
} else {
0
};
for i in 0..content_height {
let item_idx = scroll + i;
let y = list_start_y + i as u16;
if y >= popup_y + popup_height - 1 || y >= area.bottom() {
break;
}
if item_idx >= self.editor.workspace_symbol_results.len() {
continue;
}
let style = if item_idx == selected {
selected_style
} else {
bg_style
};
for dx in 1..popup_width - 1 {
let x = popup_x + dx;
if x < area.right() {
buf[(x, y)].set_char(' ').set_style(style);
}
}
let sym = &self.editor.workspace_symbol_results[item_idx];
let kind_label = lsp::symbol_kind_label(sym.kind);
let file_name = sym.uri.rsplit('/').next().unwrap_or(&sym.uri);
let display = format!("[{}] {} {}:{}", kind_label, sym.name, file_name, sym.start_line + 1);
for (j, ch) in display.chars().enumerate() {
let x = popup_x + 2 + j as u16;
if x >= popup_x + popup_width - 1 || x >= area.right() {
break;
}
let char_style = if j < kind_label.len() + 2 {
let kind_color = match sym.kind {
6 | 12 => Color::Rgb(97, 175, 239), 23 | 5 => Color::Rgb(229, 192, 123), 10 | 22 => Color::Rgb(198, 120, 221), 11 => Color::Rgb(86, 182, 194), 14 => Color::Rgb(209, 154, 102), 2 => Color::Rgb(152, 195, 121), _ => Color::Rgb(171, 178, 191),
};
if item_idx == selected {
Style::default().fg(kind_color).bg(Color::Rgb(80, 90, 110)).add_modifier(Modifier::BOLD)
} else {
Style::default().fg(kind_color).bg(Color::Rgb(30, 33, 40))
}
} else {
style
};
buf[(x, y)].set_char(ch).set_style(char_style);
}
}
}
}