feat: expand connected databases in a single explorer sidebar
This commit is contained in:
+141
-19
@@ -22,18 +22,35 @@ pub fn run(terminal: &mut DefaultTerminal, initial: Option<Database>) -> Result<
|
||||
ListState::default().with_selected((!profiles.entries.is_empty()).then_some(0));
|
||||
let mut databases_focus = true;
|
||||
let mut active = None;
|
||||
let mut expanded = true;
|
||||
let mut error: Option<String> = None;
|
||||
let mut form = Form::default();
|
||||
let mut show_form = false;
|
||||
loop {
|
||||
let entries = explorer_entries(
|
||||
profiles.entries.len(),
|
||||
active,
|
||||
browser.as_ref().map(|app| app.relations.len()),
|
||||
expanded,
|
||||
);
|
||||
terminal.draw(|frame| {
|
||||
let [sidebar, content] = Layout::horizontal([Constraint::Length(24), Constraint::Min(1)])
|
||||
let [sidebar, content] = Layout::horizontal([Constraint::Length(32), Constraint::Min(1)])
|
||||
.areas(frame.area());
|
||||
let items = profiles.entries.iter().enumerate().map(|(index, entry)| {
|
||||
ListItem::new(format!("{}{}", if active == Some(index) { "● " } else { " " }, entry.label()))
|
||||
let items = entries.iter().map(|entry| {
|
||||
ListItem::new(match *entry {
|
||||
ExplorerEntry::Database(index) => {
|
||||
let connected = browser.is_some() && index == active;
|
||||
let label = index.map_or_else(|| "Session database".into(), |index| profiles.entries[index].label());
|
||||
format!("{} {label}", if connected && expanded { "▼" } else { "▶" })
|
||||
}
|
||||
ExplorerEntry::Relation(index) => {
|
||||
let relation = &browser.as_ref().unwrap().relations[index];
|
||||
format!(" ├ {} [{}]", relation.label, relation.kind)
|
||||
}
|
||||
})
|
||||
});
|
||||
let list = List::new(items)
|
||||
.block(Block::bordered().title(" Databases · c add ").border_style(
|
||||
.block(Block::bordered().title(" Explorer · c add ").border_style(
|
||||
Style::default().fg(if databases_focus { Color::Cyan } else { Color::DarkGray })))
|
||||
.highlight_style(Style::default().bg(Color::Blue).fg(Color::White));
|
||||
frame.render_stateful_widget(list, sidebar, &mut selection);
|
||||
@@ -65,12 +82,30 @@ pub fn run(terminal: &mut DefaultTerminal, initial: Option<Database>) -> Result<
|
||||
let result = if form.reconnect {
|
||||
form.connect().and_then(App::new).map(|connected| {
|
||||
browser = Some(connected);
|
||||
active = selection.selected();
|
||||
databases_focus = false;
|
||||
active = selection.selected().and_then(|index| {
|
||||
match entries[index] {
|
||||
ExplorerEntry::Database(index) => index,
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
selection.select(active);
|
||||
expanded = true;
|
||||
databases_focus = true;
|
||||
})
|
||||
} else {
|
||||
profiles.save(&form).map(|()| {
|
||||
selection.select(Some(profiles.entries.len() - 1));
|
||||
let updated = explorer_entries(
|
||||
profiles.entries.len(),
|
||||
active,
|
||||
browser.as_ref().map(|app| app.relations.len()),
|
||||
expanded,
|
||||
);
|
||||
selection.select(updated.iter().position(|entry| {
|
||||
*entry
|
||||
== ExplorerEntry::Database(Some(
|
||||
profiles.entries.len() - 1,
|
||||
))
|
||||
}));
|
||||
databases_focus = true;
|
||||
})
|
||||
};
|
||||
@@ -94,20 +129,29 @@ pub fn run(terminal: &mut DefaultTerminal, initial: Option<Database>) -> Result<
|
||||
}
|
||||
KeyCode::Esc => databases_focus = true,
|
||||
KeyCode::Tab | KeyCode::BackTab => {
|
||||
if let Some(browser) = &mut browser {
|
||||
let backwards = key.code == KeyCode::BackTab;
|
||||
if databases_focus {
|
||||
databases_focus = false;
|
||||
browser.sidebar_focus = !backwards;
|
||||
} else if browser.sidebar_focus != backwards {
|
||||
browser.sidebar_focus = backwards;
|
||||
} else {
|
||||
databases_focus = true;
|
||||
}
|
||||
if browser.is_some() {
|
||||
databases_focus = !databases_focus;
|
||||
}
|
||||
}
|
||||
KeyCode::Enter if databases_focus => {
|
||||
if let Some(index) = selection.selected() {
|
||||
if let Some(entry) =
|
||||
selection.selected().and_then(|index| entries.get(index))
|
||||
{
|
||||
let index = match *entry {
|
||||
ExplorerEntry::Relation(index) => {
|
||||
browser.as_mut().unwrap().open_relation(Some(index));
|
||||
databases_focus = false;
|
||||
continue;
|
||||
}
|
||||
ExplorerEntry::Database(index)
|
||||
if browser.is_some() && index == active =>
|
||||
{
|
||||
expanded = !expanded;
|
||||
continue;
|
||||
}
|
||||
ExplorerEntry::Database(Some(index)) => index,
|
||||
ExplorerEntry::Database(None) => continue,
|
||||
};
|
||||
form = profiles.entries[index].clone();
|
||||
if form.provider == 0 {
|
||||
form.reconnect = true;
|
||||
@@ -118,7 +162,9 @@ pub fn run(terminal: &mut DefaultTerminal, initial: Option<Database>) -> Result<
|
||||
Ok(connected) => {
|
||||
browser = Some(connected);
|
||||
active = Some(index);
|
||||
databases_focus = false;
|
||||
selection.select(Some(index));
|
||||
expanded = true;
|
||||
databases_focus = true;
|
||||
error = None;
|
||||
}
|
||||
Err(failure) => error = Some(error_text(failure.as_ref())),
|
||||
@@ -126,6 +172,33 @@ pub fn run(terminal: &mut DefaultTerminal, initial: Option<Database>) -> Result<
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyCode::Left | KeyCode::Char('h') if databases_focus => {
|
||||
if matches!(
|
||||
selection.selected().and_then(|index| entries.get(index)),
|
||||
Some(ExplorerEntry::Relation(_))
|
||||
) || selection.selected().and_then(|index| entries.get(index))
|
||||
== Some(&ExplorerEntry::Database(active))
|
||||
{
|
||||
expanded = false;
|
||||
selection.select(
|
||||
entries.iter().position(|entry| {
|
||||
*entry == ExplorerEntry::Database(active)
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
KeyCode::Right | KeyCode::Char('l') if databases_focus => {
|
||||
if selection.selected().and_then(|index| entries.get(index))
|
||||
== Some(&ExplorerEntry::Database(active))
|
||||
{
|
||||
expanded = true;
|
||||
}
|
||||
}
|
||||
KeyCode::Char('r') if databases_focus => {
|
||||
if let Some(browser) = &mut browser {
|
||||
browser.handle_key(key);
|
||||
}
|
||||
}
|
||||
KeyCode::Down | KeyCode::Char('j') if databases_focus => {
|
||||
selection.select_next()
|
||||
}
|
||||
@@ -158,6 +231,33 @@ pub fn run(terminal: &mut DefaultTerminal, initial: Option<Database>) -> Result<
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum ExplorerEntry {
|
||||
Database(Option<usize>),
|
||||
Relation(usize),
|
||||
}
|
||||
|
||||
fn explorer_entries(
|
||||
count: usize,
|
||||
active: Option<usize>,
|
||||
relations: Option<usize>,
|
||||
expanded: bool,
|
||||
) -> Vec<ExplorerEntry> {
|
||||
let mut entries = Vec::new();
|
||||
let session = active.is_none() && relations.is_some();
|
||||
for database in session
|
||||
.then_some(None)
|
||||
.into_iter()
|
||||
.chain((0..count).map(Some))
|
||||
{
|
||||
entries.push(ExplorerEntry::Database(database));
|
||||
if database == active && expanded {
|
||||
entries.extend((0..relations.unwrap_or(0)).map(ExplorerEntry::Relation));
|
||||
}
|
||||
}
|
||||
entries
|
||||
}
|
||||
|
||||
fn config_path() -> Result<PathBuf> {
|
||||
let root = std::env::var_os("XDG_CONFIG_HOME")
|
||||
.filter(|path| Path::new(path).is_absolute())
|
||||
@@ -614,6 +714,28 @@ impl Input {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn explorer_keeps_relations_under_the_connected_database() {
|
||||
use ExplorerEntry::{Database as Db, Relation as Rel};
|
||||
assert_eq!(
|
||||
explorer_entries(3, Some(1), Some(2), true),
|
||||
vec![Db(Some(0)), Db(Some(1)), Rel(0), Rel(1), Db(Some(2))]
|
||||
);
|
||||
assert_eq!(
|
||||
explorer_entries(3, Some(1), Some(2), false),
|
||||
vec![Db(Some(0)), Db(Some(1)), Db(Some(2))]
|
||||
);
|
||||
assert_eq!(
|
||||
explorer_entries(1, None, Some(1), true),
|
||||
vec![Db(None), Rel(0), Db(Some(0))]
|
||||
);
|
||||
assert_eq!(
|
||||
explorer_entries(1, Some(0), Some(0), true),
|
||||
vec![Db(Some(0))]
|
||||
);
|
||||
assert_eq!(explorer_entries(1, None, None, true), vec![Db(Some(0))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editing_preserves_unicode_and_filters_terminal_controls() {
|
||||
let mut input = Input::default();
|
||||
|
||||
Reference in New Issue
Block a user