Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/core/src/keybindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub mod action_id {
pub const DB_FOCUS_SEARCH: &str = "db.focus_search";
pub const DB_OPEN_TABLE_QUERY: &str = "db.open_table_query";
pub const DB_OPEN_TABLE_DESIGNER: &str = "db.open_table_designer";
pub const DB_SELECT_ALL_OBJECTS: &str = "db.select_all_objects";
pub const SQL_RUN_QUERY: &str = "sql.run_query";
pub const SQL_RUN_ALL_QUERY: &str = "sql.run_all_query";
pub const TERMINAL_SEND_TAB: &str = "terminal.send_tab";
Expand Down
204 changes: 194 additions & 10 deletions crates/db_view/src/database_objects_tab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ use crate::extension_menu::{
DbTreeExtensionActionContext, DbTreeExtensionMenuContext, DbTreeExtensionMenuItem,
DbTreeExtensionMenuRegistry, GlobalDbTreeExtensionActionHandler,
};
use crate::object_list_selection::{
RowDragState, VisibleRowSpan, exceeds_drag_threshold, replace_with_span, select_all_rows,
};
use crate::search_shortcut::{
DB_SEARCH_CONTEXT, FocusSearchInput, OpenSelectedTableQuery, focus_search_input,
DB_SEARCH_CONTEXT, FocusSearchInput, OpenSelectedTableQuery, SelectAllObjects,
focus_search_input,
};
use crate::table_copy_menu::append_table_copy_items;
use db::DbNodeType::QueryFolder;
Expand All @@ -25,8 +29,9 @@ use gpui::prelude::FluentBuilder;
use gpui::{
AnyElement, App, AppContext, AsyncApp, Context, Entity, EventEmitter, FocusHandle, Focusable,
HighlightStyle, InteractiveElement, IntoElement, ListSizingBehavior, MouseButton,
MouseDownEvent, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled,
StyledText, Subscription, WeakEntity, Window, div, px, uniform_list,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render,
ScrollStrategy, SharedString, StatefulInteractiveElement, Styled, StyledText, Subscription,
UniformListScrollHandle, WeakEntity, Window, div, px, uniform_list,
};
use gpui_component::button::Button;
use gpui_component::input::{Input, InputEvent, InputState};
Expand Down Expand Up @@ -289,6 +294,14 @@ pub struct DatabaseObjects {
current_node: Option<DbNode>,
selected_indices: HashSet<usize>,
context_menu_row: Option<usize>,
/// 鼠标行选择的交互状态(按下行、拖选锚点、加选快照)
drag_state: RowDragState,
/// 按下点在窗口中的位置,用于判定拖选阈值
drag_origin: Option<Point<Pixels>>,
/// 列表滚动句柄:Shift 点击时把目标行滚入可视区
list_scroll_handle: UniformListScrollHandle,
/// 拖选过程中最近悬停的可见行,用来避免同一行重复重算选择集
drag_hover_row: Option<usize>,
_subscriptions: Vec<Subscription>,
}

Expand All @@ -302,6 +315,101 @@ impl DatabaseObjects {
focus_search_input(&self.search_input, window, cx);
}

fn on_action_select_all_objects(
&mut self,
_: &SelectAllObjects,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.select_all_visible_rows(cx);
}

/// 行按下:区分单选、多选(Cmd/Ctrl)与 Shift 区间选择,并开启拖选待判定状态。
fn on_row_mouse_down(&mut self, row_ix: usize, event: &MouseDownEvent, cx: &mut Context<Self>) {
let additive = event.modifiers.secondary();
if event.modifiers.shift {
self.drag_state.cancel();
self.drag_origin = None;
self.drag_hover_row = None;
self.extend_selection_to(row_ix, cx);
return;
}

self.toggle_selection(row_ix, additive);
// Cmd/Ctrl 拖选在按下快照之上叠加区间,普通拖选直接替换选择集
let additive_base = additive.then(|| self.selected_indices.clone());
self.drag_state.press(row_ix, additive_base);
self.drag_origin = Some(event.position);
self.drag_hover_row = Some(row_ix);
cx.notify();
}

/// 行悬停:超过拖选阈值后把锚点到当前位置的整段行纳入选择集。
fn on_row_mouse_move(&mut self, row_ix: usize, event: &MouseMoveEvent, cx: &mut Context<Self>) {
if self.drag_state.pressed().is_none() {
return;
}

if !self.drag_state.is_dragging() {
let Some(origin) = self.drag_origin else {
return;
};
let origin = (f32::from(origin.x), f32::from(origin.y));
let position = (f32::from(event.position.x), f32::from(event.position.y));
if !exceeds_drag_threshold(origin, position) {
return;
}
}

// 先确立锚点,再判断是否需要重算区间:首个越过阈值的悬停行也要参与计算
if self.drag_state.start_drag().is_none() {
return;
}
if self.drag_hover_row == Some(row_ix) {
return;
}
self.drag_hover_row = Some(row_ix);

let visible_row_count = self.visible_row_count();
self.drag_state
.apply_drag_to(row_ix, &mut self.selected_indices, visible_row_count);
cx.notify();
}

/// 行释放:未发生拖选时保持按下时的单击/多选结果。
fn on_row_mouse_up(&mut self, _: &MouseUpEvent, _window: &mut Window, cx: &mut Context<Self>) {
if self.drag_state.release().is_some() {
cx.notify();
}
self.drag_origin = None;
self.drag_hover_row = None;
}

/// 面板根节点释放:兜住“按下后拖到行外才松手”的情况。
fn on_panel_mouse_up(
&mut self,
_: &MouseUpEvent,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.end_row_drag(cx);
}

/// 结束拖选但不改变已确认的选择集。
///
/// 指针在列表外松开(拖出列表、切到别的面板)时,gpui 只把 `mouse_up` 发给
/// 命中路径上的元素,本视图收不到,因此 `RowDragState::press` 会自己丢弃旧锚点,
/// 这里的收尾只负责清掉拖选过程中的临时状态。
fn end_row_drag(&mut self, cx: &mut Context<Self>) {
let was_active = self.drag_state.pressed().is_some() || self.drag_state.is_dragging();
self.drag_state.cancel();
self.drag_origin = None;
self.drag_hover_row = None;
if was_active {
cx.notify();
}
}

fn on_action_open_selected_table_query(
&mut self,
_: &OpenSelectedTableQuery,
Expand Down Expand Up @@ -403,6 +511,10 @@ impl DatabaseObjects {
current_node: None,
selected_indices: HashSet::new(),
context_menu_row: None,
drag_state: RowDragState::default(),
drag_origin: None,
list_scroll_handle: UniformListScrollHandle::new(),
drag_hover_row: None,
_subscriptions: vec![search_sub],
}
}
Expand Down Expand Up @@ -545,6 +657,48 @@ impl DatabaseObjects {
}
}

/// 可见行总数(已应用搜索过滤)。
fn visible_row_count(&self) -> usize {
self.filtered_rows.len()
}

/// 全选当前可见行(Ctrl/Cmd + A)。
///
/// 列表本身已是当前范围的全量数据(`ObjectView` 不做截断),可见行即全部对象,
/// 因此全选不需要再去请求宿主补全。
fn select_all_visible_rows(&mut self, cx: &mut Context<Self>) {
let visible_row_count = self.visible_row_count();
select_all_rows(&mut self.selected_indices, visible_row_count);
self.context_menu_row = None;
cx.notify();
}

/// Shift 点击:从当前锚点行扩展到目标行。
///
/// 锚点优先取首个已选行;没有已选行时退化为单选目标行。
fn extend_selection_to(&mut self, row_ix: usize, cx: &mut Context<Self>) {
self.context_menu_row = None;
let anchor = self
.selected_indices
.iter()
.copied()
.min()
.filter(|_| !self.selected_indices.is_empty());
match anchor {
Some(anchor) => {
let span = VisibleRowSpan::new(anchor, row_ix, self.visible_row_count());
replace_with_span(&mut self.selected_indices, span);
}
None => {
self.selected_indices.clear();
self.selected_indices.insert(row_ix);
}
}
self.list_scroll_handle
.scroll_to_item(row_ix, ScrollStrategy::Nearest);
cx.notify();
}

fn apply_filter(&mut self) {
if self.search_query.is_empty() {
self.filtered_rows = (0..self.rows.len()).collect();
Expand Down Expand Up @@ -1396,6 +1550,8 @@ impl Render for DatabaseObjects {
.key_context(DB_SEARCH_CONTEXT)
.on_action(cx.listener(Self::on_action_focus_search))
.on_action(cx.listener(Self::on_action_open_selected_table_query))
.on_action(cx.listener(Self::on_action_select_all_objects))
.on_mouse_up(MouseButton::Left, cx.listener(Self::on_panel_mouse_up))
.child(
h_flex()
.gap_1()
Expand Down Expand Up @@ -1453,15 +1609,38 @@ impl Render for DatabaseObjects {
cx.listener(
move |this,
event: &MouseDownEvent,
_window,
window,
cx| {
let multi_select =
event.modifiers.secondary();
this.toggle_selection(
row_ix,
multi_select,
window.focus(
&this.focus_handle,
cx,
);
this.on_row_mouse_down(
row_ix, event, cx,
);
},
),
)
.on_mouse_move(cx.listener(
move |this,
event: &MouseMoveEvent,
_window,
cx| {
this.on_row_mouse_move(
row_ix, event, cx,
);
},
))
.on_mouse_up(
MouseButton::Left,
cx.listener(
move |this,
event: &MouseUpEvent,
window,
cx| {
this.on_row_mouse_up(
event, window, cx,
);
cx.notify();
},
),
)
Expand Down Expand Up @@ -1513,6 +1692,7 @@ impl Render for DatabaseObjects {
})
.flex_grow_1()
.size_full()
.track_scroll(&self.list_scroll_handle)
.with_sizing_behavior(ListSizingBehavior::Auto),
),
),
Expand All @@ -1538,6 +1718,10 @@ impl Clone for DatabaseObjects {
current_node: self.current_node.clone(),
selected_indices: self.selected_indices.clone(),
context_menu_row: self.context_menu_row,
drag_state: RowDragState::default(),
drag_origin: None,
list_scroll_handle: UniformListScrollHandle::new(),
drag_hover_row: None,
_subscriptions: vec![],
}
}
Expand Down
23 changes: 23 additions & 0 deletions crates/db_view/src/database_objects_tab_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,26 @@ fn object_name_highlights_preserve_identifier_boundaries() {
object_name_highlight_ranges(text, "")
);
}

#[test]
fn object_rows_wire_keyboard_and_mouse_range_selection() {
let source = include_str!("database_objects_tab.rs");

// Ctrl/Cmd+A 走 key context action,保证列表聚焦时即可全选可见行
assert!(source.contains(".key_context(DB_SEARCH_CONTEXT)"));
assert!(source.contains(".on_action(cx.listener(Self::on_action_select_all_objects))"));
assert!(source.contains("select_all_rows(&mut self.selected_indices"));

// Shift 点击扩到区间,拖选走 mouse_move/mouse_up
assert!(source.contains("event.modifiers.shift"));
assert!(source.contains("replace_with_span(&mut self.selected_indices"));
assert!(source.contains(".apply_drag_to(row_ix, &mut self.selected_indices"));
assert!(source.contains("exceeds_drag_threshold("));
assert!(source.contains(".on_mouse_move(cx.listener("));
// 行内释放走 on_row_mouse_up,面板根节点兜住“拖到行外松手”
assert!(
source.contains(".on_mouse_up(MouseButton::Left, cx.listener(Self::on_panel_mouse_up))")
);
assert!(source.contains("fn on_panel_mouse_up("));
assert!(source.contains("fn end_row_drag("));
}
5 changes: 5 additions & 0 deletions crates/db_view/src/db_tree_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1480,6 +1480,11 @@ impl DbTreeView {
}
}

/// 取已加载到树中的节点。
pub fn node(&self, node_id: &str) -> Option<DbNode> {
self.db_nodes.get(node_id).cloned()
}

/// 刷新指定节点及其子节点
///
/// 这个方法会:
Expand Down
1 change: 1 addition & 0 deletions crates/db_view/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub mod extension_menu;
#[cfg(test)]
mod extension_menu_contract_tests;
mod import_export;
mod object_list_selection;
pub mod search_shortcut;
pub mod settings;
mod sidebar;
Expand Down
Loading
Loading