Skip to content

Commit a3cf087

Browse files
trepidityclaude
andcommitted
Display search results in table with DN, sAMAccountName, Display Name, Mail columns
Replace the flat list view with a columnar Table widget showing AD-relevant attributes. Widen the search popup to 90% of the terminal width. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e67d988 commit a3cf087

1 file changed

Lines changed: 45 additions & 21 deletions

File tree

crates/loom-tui/src/components/search_dialog.rs

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crossterm::event::{KeyCode, KeyEvent};
22
use ratatui::layout::{Constraint, Layout, Rect};
33
use ratatui::style::Modifier;
44
use ratatui::text::{Line, Span};
5-
use ratatui::widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph};
5+
use ratatui::widgets::{Block, Borders, Cell, Clear, Paragraph, Row, Table, TableState};
66
use ratatui::Frame;
77

88
use crate::action::Action;
@@ -14,7 +14,7 @@ pub struct SearchDialog {
1414
pub visible: bool,
1515
pub filter: String,
1616
pub results: Vec<LdapEntry>,
17-
list_state: ListState,
17+
table_state: TableState,
1818
theme: Theme,
1919
}
2020

@@ -24,15 +24,15 @@ impl SearchDialog {
2424
visible: false,
2525
filter: String::new(),
2626
results: Vec::new(),
27-
list_state: ListState::default(),
27+
table_state: TableState::default(),
2828
theme,
2929
}
3030
}
3131

3232
pub fn show_results(&mut self, filter: String, results: Vec<LdapEntry>) {
3333
self.filter = filter;
3434
self.results = results;
35-
self.list_state.select(if self.results.is_empty() {
35+
self.table_state.select(if self.results.is_empty() {
3636
None
3737
} else {
3838
Some(0)
@@ -51,21 +51,21 @@ impl SearchDialog {
5151

5252
match key.code {
5353
KeyCode::Up | KeyCode::Char('k') => {
54-
let i = self.list_state.selected().unwrap_or(0);
54+
let i = self.table_state.selected().unwrap_or(0);
5555
if i > 0 {
56-
self.list_state.select(Some(i - 1));
56+
self.table_state.select(Some(i - 1));
5757
}
5858
Action::None
5959
}
6060
KeyCode::Down | KeyCode::Char('j') => {
61-
let i = self.list_state.selected().unwrap_or(0);
61+
let i = self.table_state.selected().unwrap_or(0);
6262
if i + 1 < self.results.len() {
63-
self.list_state.select(Some(i + 1));
63+
self.table_state.select(Some(i + 1));
6464
}
6565
Action::None
6666
}
6767
KeyCode::Enter => {
68-
if let Some(idx) = self.list_state.selected() {
68+
if let Some(idx) = self.table_state.selected() {
6969
if let Some(entry) = self.results.get(idx) {
7070
let dn = entry.dn.clone();
7171
self.visible = false;
@@ -87,7 +87,7 @@ impl SearchDialog {
8787
return;
8888
}
8989

90-
let popup_width = (full.width as u32 * 80 / 100).min(100) as u16;
90+
let popup_width = (full.width as u32 * 90 / 100) as u16;
9191
let popup_height = (full.height as u32 * 70 / 100).min(40) as u16;
9292

9393
let x = full.x + (full.width.saturating_sub(popup_width)) / 2;
@@ -112,7 +112,7 @@ impl SearchDialog {
112112
return;
113113
}
114114

115-
// Layout: hint (1 line) | results list
115+
// Layout: hint (1 line) | results table
116116
let layout = Layout::vertical([Constraint::Length(1), Constraint::Min(1)]).split(inner);
117117

118118
let hint = Line::from(vec![
@@ -125,22 +125,46 @@ impl SearchDialog {
125125
]);
126126
frame.render_widget(Paragraph::new(hint), layout[0]);
127127

128-
let items: Vec<ListItem> = self
128+
let header = Row::new(vec![
129+
Cell::from(Span::styled("DN", self.theme.header)),
130+
Cell::from(Span::styled("sAMAccountName", self.theme.header)),
131+
Cell::from(Span::styled("Display Name", self.theme.header)),
132+
Cell::from(Span::styled("Mail", self.theme.header)),
133+
]);
134+
135+
let rows: Vec<Row> = self
129136
.results
130137
.iter()
131138
.map(|entry| {
132-
let oc = entry.object_classes().join(", ");
133-
let line = Line::from(vec![
134-
Span::styled(&entry.dn, self.theme.normal),
135-
Span::styled(format!(" [{}]", oc), self.theme.dimmed),
136-
]);
137-
ListItem::new(line)
139+
Row::new(vec![
140+
Cell::from(Span::styled(&entry.dn, self.theme.normal)),
141+
Cell::from(Span::styled(
142+
entry.first_value("sAMAccountName").unwrap_or(""),
143+
self.theme.normal,
144+
)),
145+
Cell::from(Span::styled(
146+
entry.first_value("displayName").unwrap_or(""),
147+
self.theme.normal,
148+
)),
149+
Cell::from(Span::styled(
150+
entry.first_value("mail").unwrap_or(""),
151+
self.theme.normal,
152+
)),
153+
])
138154
})
139155
.collect();
140156

141-
let list =
142-
List::new(items).highlight_style(self.theme.selected.add_modifier(Modifier::BOLD));
157+
let widths = [
158+
Constraint::Percentage(40),
159+
Constraint::Percentage(15),
160+
Constraint::Percentage(20),
161+
Constraint::Percentage(25),
162+
];
163+
164+
let table = Table::new(rows, widths)
165+
.header(header.style(self.theme.header))
166+
.highlight_style(self.theme.selected.add_modifier(Modifier::BOLD));
143167

144-
frame.render_stateful_widget(list, layout[1], &mut self.list_state.clone());
168+
frame.render_stateful_widget(table, layout[1], &mut self.table_state.clone());
145169
}
146170
}

0 commit comments

Comments
 (0)