DB & Dev Tools · Databases: connections, schemas, queries, structure, Redis

A new chapter, src/devtools. ConnectionManager lists saved connections
with their engine and address, or why one failed, and connects on Enter;
ConnectionForm fills a connection with a port checked from 1 to 65535,
tests it and saves it. SchemaTree shows schemas, tables, views and
fields with keys marked; ERDiagram draws tables and their foreign keys
on NodeGraph, whose fixed wires show only the sockets that hold one.
QueryHistory finds past runs and runs one again; QueryPlanViewer shows
each step's share of the cost. TableStructureEditor edits fields in
place; IndexManager lists and draws up indexes; RedisKeyBrowser finds
keys by glob and lays out each type. QueryEditor is editor::CodeEditor,
whose lexer now knows SQL's words, and QueryResultGrid is
tables::DataGrid. lists::row_action keeps a row's button its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-09-27 08:51:45 -04:00
1 parent 94ae7e73e7
commit 4c9727d67c
20 files changed
+2602 -14

No files matched your search

+133
View File
@@ -0,0 +1,133 @@
use gpui::{
AnyElement, Context, Entity, IntoElement, KeyBinding, ParentElement, Render, Styled,
TestAppContext, VisualTestContext, Window, div, px,
};
use super::{Connection, ConnectionManager, Engine, Field, TableStructureEditor};
use crate::{forms, primitives::FocusNext, theme::Theme};
/// A view that shows one tool and keeps the words it heard.
struct Bench {
part: fn(Entity<Bench>) -> AnyElement,
said: Vec<String>,
}
impl Render for Bench {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div().w(px(640.0)).child((self.part)(cx.entity()))
}
}
fn settle(cx: &mut VisualTestContext) {
for _ in 0..3 {
cx.run_until_parked();
cx.update(|window, _| window.refresh());
}
cx.run_until_parked();
}
fn bench(
part: fn(Entity<Bench>) -> AnyElement,
cx: &mut TestAppContext,
) -> (Entity<Bench>, &mut VisualTestContext) {
cx.update(|cx| {
Theme::init(cx);
Theme::update(cx, |theme| theme.reduced_motion = true);
forms::bind_keys(cx);
cx.bind_keys([KeyBinding::new("tab", FocusNext, None)]);
});
let (host, cx) = cx.add_window_view(|_, _| Bench {
part,
said: Vec::new(),
});
settle(cx);
(host, cx)
}
fn say(owner: &Entity<Bench>, words: String, cx: &mut gpui::App) {
owner.update(cx, |bench, cx| {
bench.said.push(words);
cx.notify();
});
}
fn said(host: &Entity<Bench>, cx: &mut VisualTestContext) -> Vec<String> {
host.read_with(cx, |bench, _| bench.said.clone())
}
fn tab(stops: usize, cx: &mut VisualTestContext) {
cx.update(|window, _| {
window.blur();
(0..stops).for_each(|_| window.focus_next());
});
settle(cx);
}
fn tap(key: &str, cx: &mut VisualTestContext) {
cx.simulate_keystrokes(key);
cx.simulate_event(gpui::KeyUpEvent {
keystroke: gpui::Keystroke::parse(key).expect("a key"),
});
settle(cx);
}
fn manager(owner: Entity<Bench>) -> AnyElement {
let [opened, edited, picked] = [(); 3].map(|_| owner.clone());
ConnectionManager::new(
"connections",
[
Connection::new("local", "Local", Engine::Postgres),
Connection::new("cache", "Cache", Engine::Redis),
],
)
.on_open(move |key, _, cx| say(&opened, format!("open {key}"), cx))
.on_edit(move |key, _, cx| say(&edited, format!("edit {key}"), cx))
.on_select(move |key, _, cx| say(&picked, format!("select {key}"), cx))
.into_any_element()
}
/// Stops run: New, the list, then each row's Edit and Remove.
#[gpui::test]
fn enter_opens_a_connection_and_a_rows_button_leaves_the_row_alone(cx: &mut TestAppContext) {
let (host, cx) = bench(manager, cx);
tab(2, cx);
tap("enter", cx);
tab(3, cx);
tap("space", cx);
assert_eq!(said(&host, cx), ["open local", "edit local"]);
}
fn structure(owner: Entity<Bench>) -> AnyElement {
TableStructureEditor::new("structure", [Field::new("id", "int").primary()])
.on_change(move |fields, _, cx| {
let names: Vec<String> = fields
.iter()
.map(|field| format!("{}:{}", field.name, field.ty))
.collect();
say(&owner, names.join(" "), cx)
})
.into_any_element()
}
/// A row's stops: name, type, empty, key, default and remove; Add field comes after.
#[gpui::test]
fn add_field_appends_a_free_name(cx: &mut TestAppContext) {
let (host, cx) = bench(structure, cx);
tab(7, cx);
tap("space", cx);
assert_eq!(said(&host, cx), ["id:int field_1:text"]);
}
#[test]
fn an_address_names_the_host_or_the_file() {
let mut local = Connection::new("local", "Local", Engine::Postgres);
local.database = "shop".into();
assert_eq!(local.address(), "localhost:5432/shop");
assert_eq!(
Connection::new("cache", "Cache", Engine::Redis).address(),
"localhost:6379"
);
let mut file = Connection::new("file", "File", Engine::Sqlite);
file.database = "app.db".into();
assert_eq!(file.address(), "app.db");
}