mirror of
https://github.com/nyakang/nyaterm.git
synced 2026-10-04 00:00:53 +00:00
feat(sync): use real text inputs for group editing
This commit is contained in:
@@ -444,10 +444,6 @@ impl NyaTermApp {
|
||||
sync_groups_open: false,
|
||||
sync_groups_focus: cx.focus_handle(),
|
||||
sync_groups_search_draft: String::new(),
|
||||
sync_groups_search_marked_text: String::new(),
|
||||
sync_groups_name_marked_text: String::new(),
|
||||
sync_groups_search_focus: cx.focus_handle(),
|
||||
sync_groups_name_focus: cx.focus_handle(),
|
||||
sync_groups_selected_id: None,
|
||||
sync_groups_delete_pending: None,
|
||||
broadcast_to_all: false,
|
||||
|
||||
@@ -114,10 +114,6 @@ pub struct NyaTermApp {
|
||||
pub(in crate::features) sync_groups_open: bool,
|
||||
pub(in crate::features) sync_groups_focus: FocusHandle,
|
||||
pub(in crate::features) sync_groups_search_draft: String,
|
||||
pub(in crate::features) sync_groups_search_marked_text: String,
|
||||
pub(in crate::features) sync_groups_name_marked_text: String,
|
||||
pub(in crate::features) sync_groups_search_focus: FocusHandle,
|
||||
pub(in crate::features) sync_groups_name_focus: FocusHandle,
|
||||
pub(in crate::features) sync_groups_selected_id: Option<String>,
|
||||
pub(in crate::features) sync_groups_delete_pending: Option<String>,
|
||||
/// Broadcast keyboard input to every live session (Tauri broadcastToAll).
|
||||
|
||||
@@ -10,24 +10,27 @@ impl NyaTermApp {
|
||||
let dialog_width = (viewport_w - 24.).clamp(280., 900.);
|
||||
let dialog_height = (viewport_h - 56.).clamp(280., 500.);
|
||||
let groups_width = (dialog_width * 0.23).clamp(160., 208.);
|
||||
let search_input_entity = cx.entity();
|
||||
let name_input_entity = search_input_entity.clone();
|
||||
let selected_group = self.selected_sync_group().cloned();
|
||||
let selected_group_id = selected_group.as_ref().map(|group| group.id.clone());
|
||||
let selected_group_name = selected_group
|
||||
.as_ref()
|
||||
.map(|group| format!("{}{}", group.name, self.sync_groups_name_marked_text))
|
||||
.unwrap_or_default();
|
||||
let search_display = if self.sync_groups_search_draft.is_empty()
|
||||
&& self.sync_groups_search_marked_text.is_empty()
|
||||
{
|
||||
self.tr("syncGroup.searchPlaceholder").to_string()
|
||||
let (group_name_input, group_name_focus) = if let Some(group) = selected_group.as_ref() {
|
||||
let field = self.text_input(
|
||||
format!("sync.group-name.{}", group.id),
|
||||
&group.name,
|
||||
TextInputSetup::default(),
|
||||
cx,
|
||||
);
|
||||
let focus = field.read(cx).focus_handle();
|
||||
(Some(field), Some(focus))
|
||||
} else {
|
||||
format!(
|
||||
"{}{}",
|
||||
self.sync_groups_search_draft, self.sync_groups_search_marked_text
|
||||
)
|
||||
(None, None)
|
||||
};
|
||||
let search_input = self.text_input(
|
||||
"sync.groups.search",
|
||||
&self.sync_groups_search_draft.clone(),
|
||||
TextInputSetup::placeholder(self.tr("syncGroup.searchPlaceholder")),
|
||||
cx,
|
||||
);
|
||||
let search_focus = search_input.read(cx).focus_handle();
|
||||
let pending_delete_name = self
|
||||
.sync_groups_delete_pending
|
||||
.as_deref()
|
||||
@@ -327,9 +330,14 @@ impl NyaTermApp {
|
||||
window.focus(&this.sync_groups_focus);
|
||||
cx.notify();
|
||||
}))
|
||||
.on_key_down(cx.listener(|this, event: &KeyDownEvent, window, cx| {
|
||||
.on_key_down(cx.listener(|this, event: &KeyDownEvent, _, cx| {
|
||||
cx.stop_propagation();
|
||||
if this.handle_sync_groups_key_down(event, window, cx) {
|
||||
if this.sync_groups_delete_pending.is_some() {
|
||||
match event.keystroke.key.as_str() {
|
||||
"escape" => this.cancel_delete_sync_group(cx),
|
||||
"enter" => this.confirm_delete_sync_group(cx),
|
||||
_ => {}
|
||||
}
|
||||
return;
|
||||
}
|
||||
match event.keystroke.key.as_str() {
|
||||
@@ -457,7 +465,6 @@ impl NyaTermApp {
|
||||
.child(
|
||||
div()
|
||||
.id(SharedString::from("sync-group-name-input"))
|
||||
.relative()
|
||||
.h(px(34.))
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
@@ -470,31 +477,19 @@ impl NyaTermApp {
|
||||
.bg(rgb(palette.input))
|
||||
.text_sm()
|
||||
.text_color(rgb(palette.text))
|
||||
.track_focus(&self.sync_groups_name_focus)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
window.focus(&this.sync_groups_name_focus);
|
||||
cx.notify();
|
||||
}))
|
||||
.child(selected_group_name)
|
||||
.child(
|
||||
gpui::canvas(
|
||||
|_bounds, _window, _cx| {},
|
||||
move |bounds, _state, window, cx| {
|
||||
window.handle_input(
|
||||
&name_input_entity
|
||||
.read(cx)
|
||||
.sync_groups_name_focus,
|
||||
gpui::ElementInputHandler::new(
|
||||
bounds,
|
||||
name_input_entity.clone(),
|
||||
),
|
||||
cx,
|
||||
);
|
||||
},
|
||||
)
|
||||
.absolute()
|
||||
.inset_0(),
|
||||
),
|
||||
.cursor_text()
|
||||
.when_some(
|
||||
group_name_focus,
|
||||
|this, focus| {
|
||||
this.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
move |_, window, _| {
|
||||
window.focus(&focus);
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
.children(group_name_input),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
@@ -520,9 +515,8 @@ impl NyaTermApp {
|
||||
.id(SharedString::from(
|
||||
"sync-group-search-input",
|
||||
))
|
||||
.relative()
|
||||
.flex_1()
|
||||
.min_w(px(140.))
|
||||
.flex_1()
|
||||
.min_w(px(140.))
|
||||
.h(px(30.))
|
||||
.px_2()
|
||||
.flex()
|
||||
@@ -532,46 +526,15 @@ impl NyaTermApp {
|
||||
.border_color(rgb(palette.border))
|
||||
.bg(rgb(palette.input))
|
||||
.text_xs()
|
||||
.text_color(if self
|
||||
.sync_groups_search_draft
|
||||
.is_empty()
|
||||
{
|
||||
rgb(palette.text_muted)
|
||||
} else {
|
||||
rgb(palette.text)
|
||||
})
|
||||
.track_focus(
|
||||
&self.sync_groups_search_focus,
|
||||
)
|
||||
.on_click(cx.listener(
|
||||
|this, _, window, cx| {
|
||||
window.focus(
|
||||
&this.sync_groups_search_focus,
|
||||
);
|
||||
cx.notify();
|
||||
.text_color(rgb(palette.text))
|
||||
.cursor_text()
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
move |_, window, _| {
|
||||
window.focus(&search_focus);
|
||||
},
|
||||
))
|
||||
.child(search_display)
|
||||
.child(
|
||||
gpui::canvas(
|
||||
|_bounds, _window, _cx| {},
|
||||
move |bounds, _state, window, cx| {
|
||||
window.handle_input(
|
||||
&search_input_entity
|
||||
.read(cx)
|
||||
.sync_groups_search_focus,
|
||||
gpui::ElementInputHandler::new(
|
||||
bounds,
|
||||
search_input_entity
|
||||
.clone(),
|
||||
),
|
||||
cx,
|
||||
);
|
||||
},
|
||||
)
|
||||
.absolute()
|
||||
.inset_0(),
|
||||
),
|
||||
)
|
||||
.child(search_input),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
|
||||
@@ -10,8 +10,8 @@ impl NyaTermApp {
|
||||
) {
|
||||
self.sync_groups_open = true;
|
||||
self.sync_groups_search_draft.clear();
|
||||
self.sync_groups_search_marked_text.clear();
|
||||
self.sync_groups_name_marked_text.clear();
|
||||
self.forget_text_inputs("sync.groups.search");
|
||||
self.forget_text_inputs("sync.group-name.");
|
||||
self.sync_groups_delete_pending = None;
|
||||
if self.sync_groups_selected_id.is_none() {
|
||||
self.sync_groups_selected_id = self.sync_groups.first().map(|group| group.id.clone());
|
||||
@@ -24,8 +24,8 @@ impl NyaTermApp {
|
||||
pub(in crate::features) fn close_sync_groups(&mut self, cx: &mut Context<Self>) {
|
||||
self.sync_groups_open = false;
|
||||
self.sync_groups_search_draft.clear();
|
||||
self.sync_groups_search_marked_text.clear();
|
||||
self.sync_groups_name_marked_text.clear();
|
||||
self.forget_text_inputs("sync.groups.search");
|
||||
self.forget_text_inputs("sync.group-name.");
|
||||
self.sync_groups_delete_pending = None;
|
||||
self.terminal.view.status = "sync groups closed".to_string();
|
||||
cx.notify();
|
||||
@@ -86,24 +86,26 @@ impl NyaTermApp {
|
||||
self.delete_selected_sync_group(cx);
|
||||
}
|
||||
|
||||
pub(in crate::features) fn set_selected_sync_group_name(
|
||||
pub(in crate::features) fn apply_sync_groups_search(
|
||||
&mut self,
|
||||
name: String,
|
||||
text: String,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(group_id) = self.sync_groups_selected_id.clone() else {
|
||||
return;
|
||||
};
|
||||
if let Some(group) = self
|
||||
.sync_groups
|
||||
.iter_mut()
|
||||
.find(|group| group.id == group_id)
|
||||
{
|
||||
group.name = name;
|
||||
}
|
||||
self.sync_groups_search_draft = text;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(in crate::features) fn apply_sync_group_name(
|
||||
&mut self,
|
||||
group_id: &str,
|
||||
text: String,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if update_sync_group_name(&mut self.sync_groups, group_id, text) {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::features) fn select_all_sync_group_sessions(&mut self, cx: &mut Context<Self>) {
|
||||
let session_ids = self
|
||||
.ordered_sessions()
|
||||
@@ -234,70 +236,6 @@ impl NyaTermApp {
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::features) fn handle_sync_groups_key_down(
|
||||
&mut self,
|
||||
event: &KeyDownEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> bool {
|
||||
if self.sync_groups_delete_pending.is_some() {
|
||||
match event.keystroke.key.as_str() {
|
||||
"escape" => self.cancel_delete_sync_group(cx),
|
||||
"enter" => self.confirm_delete_sync_group(cx),
|
||||
_ => {}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
let search_focused = self.sync_groups_search_focus.is_focused(window);
|
||||
let name_focused = self.sync_groups_name_focus.is_focused(window);
|
||||
if !search_focused && !name_focused {
|
||||
return false;
|
||||
}
|
||||
if event.keystroke.modifiers.platform
|
||||
|| event.keystroke.modifiers.alt
|
||||
|| event.keystroke.modifiers.control
|
||||
{
|
||||
return true;
|
||||
}
|
||||
match event.keystroke.key.as_str() {
|
||||
"escape" => self.close_sync_groups(cx),
|
||||
"backspace" => {
|
||||
if search_focused {
|
||||
self.sync_groups_search_draft.pop();
|
||||
self.sync_groups_search_marked_text.clear();
|
||||
} else if let Some(group) = self.selected_sync_group_mut() {
|
||||
group.name.pop();
|
||||
self.sync_groups_name_marked_text.clear();
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
"delete" => {
|
||||
if search_focused {
|
||||
self.sync_groups_search_draft.pop();
|
||||
self.sync_groups_search_marked_text.clear();
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
"enter" => {}
|
||||
_ => {
|
||||
if let Some(input) = event
|
||||
.keystroke
|
||||
.key_char
|
||||
.as_deref()
|
||||
.filter(|text| !text.is_empty())
|
||||
{
|
||||
if search_focused {
|
||||
self.sync_groups_search_draft.push_str(input);
|
||||
} else if let Some(group) = self.selected_sync_group_mut() {
|
||||
group.name.push_str(input);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub(in crate::features) fn toggle_selected_sync_group_enabled(
|
||||
&mut self,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -558,3 +496,46 @@ impl NyaTermApp {
|
||||
.unwrap_or(SYNC_GROUP_COLORS[self.sync_groups.len() % SYNC_GROUP_COLORS.len()])
|
||||
}
|
||||
}
|
||||
|
||||
fn update_sync_group_name(groups: &mut [SyncInputGroup], group_id: &str, text: String) -> bool {
|
||||
let Some(group) = groups.iter_mut().find(|group| group.id == group_id) else {
|
||||
return false;
|
||||
};
|
||||
group.name = text;
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::update_sync_group_name;
|
||||
use crate::models::SyncInputGroup;
|
||||
|
||||
fn group(id: &str, name: &str) -> SyncInputGroup {
|
||||
SyncInputGroup {
|
||||
id: id.to_string(),
|
||||
name: name.to_string(),
|
||||
color: 0,
|
||||
session_ids: Vec::new(),
|
||||
paused_session_ids: Vec::new(),
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_name_input_updates_only_its_addressed_group() {
|
||||
let mut groups = vec![group("one", "One"), group("two", "Two")];
|
||||
|
||||
assert!(update_sync_group_name(
|
||||
&mut groups,
|
||||
"two",
|
||||
"Renamed".to_string()
|
||||
));
|
||||
assert_eq!(groups[0].name, "One");
|
||||
assert_eq!(groups[1].name, "Renamed");
|
||||
assert!(!update_sync_group_name(
|
||||
&mut groups,
|
||||
"missing",
|
||||
"Ignored".to_string()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,28 +122,6 @@ impl EntityInputHandler for NyaTermApp {
|
||||
*adjusted_range = Some(start..end);
|
||||
return Some(marked.clone());
|
||||
}
|
||||
if self.sync_groups_open
|
||||
&& self.sync_groups_search_focus.is_focused(window)
|
||||
&& !self.sync_groups_search_marked_text.is_empty()
|
||||
{
|
||||
let marked = &self.sync_groups_search_marked_text;
|
||||
let len = marked.encode_utf16().count();
|
||||
let start = range.start.min(len);
|
||||
let end = range.end.min(len).max(start);
|
||||
*adjusted_range = Some(start..end);
|
||||
return Some(marked.clone());
|
||||
}
|
||||
if self.sync_groups_open
|
||||
&& self.sync_groups_name_focus.is_focused(window)
|
||||
&& !self.sync_groups_name_marked_text.is_empty()
|
||||
{
|
||||
let marked = &self.sync_groups_name_marked_text;
|
||||
let len = marked.encode_utf16().count();
|
||||
let start = range.start.min(len);
|
||||
let end = range.end.min(len).max(start);
|
||||
*adjusted_range = Some(start..end);
|
||||
return Some(marked.clone());
|
||||
}
|
||||
if self.rename_session_id.is_some()
|
||||
&& self.rename_focus.is_focused(window)
|
||||
&& !self.rename_marked_text.is_empty()
|
||||
@@ -211,23 +189,6 @@ impl EntityInputHandler for NyaTermApp {
|
||||
reversed: false,
|
||||
});
|
||||
}
|
||||
if self.sync_groups_open && self.sync_groups_search_focus.is_focused(window) {
|
||||
let cursor = self.sync_groups_search_draft.encode_utf16().count();
|
||||
return Some(UTF16Selection {
|
||||
range: cursor..cursor,
|
||||
reversed: false,
|
||||
});
|
||||
}
|
||||
if self.sync_groups_open && self.sync_groups_name_focus.is_focused(window) {
|
||||
let cursor = self
|
||||
.selected_sync_group()
|
||||
.map(|group| group.name.encode_utf16().count())
|
||||
.unwrap_or_default();
|
||||
return Some(UTF16Selection {
|
||||
range: cursor..cursor,
|
||||
reversed: false,
|
||||
});
|
||||
}
|
||||
if self.multi_line_paste_focus.is_focused(window) {
|
||||
let text = self.multi_line_paste_text();
|
||||
let range = self.multi_line_paste_selected_byte_range();
|
||||
@@ -277,14 +238,6 @@ impl EntityInputHandler for NyaTermApp {
|
||||
let len = self.lock_password_marked_text.encode_utf16().count();
|
||||
return (len > 0).then_some(0..len);
|
||||
}
|
||||
if self.sync_groups_open && self.sync_groups_search_focus.is_focused(window) {
|
||||
let len = self.sync_groups_search_marked_text.encode_utf16().count();
|
||||
return (len > 0).then_some(0..len);
|
||||
}
|
||||
if self.sync_groups_open && self.sync_groups_name_focus.is_focused(window) {
|
||||
let len = self.sync_groups_name_marked_text.encode_utf16().count();
|
||||
return (len > 0).then_some(0..len);
|
||||
}
|
||||
if self.multi_line_paste.is_some() && self.multi_line_paste_focus.is_focused(window) {
|
||||
return self
|
||||
.multi_line_paste_marked_range
|
||||
@@ -320,14 +273,6 @@ impl EntityInputHandler for NyaTermApp {
|
||||
self.lock_password_marked_text.clear();
|
||||
return;
|
||||
}
|
||||
if self.sync_groups_open && self.sync_groups_search_focus.is_focused(window) {
|
||||
self.sync_groups_search_marked_text.clear();
|
||||
return;
|
||||
}
|
||||
if self.sync_groups_open && self.sync_groups_name_focus.is_focused(window) {
|
||||
self.sync_groups_name_marked_text.clear();
|
||||
return;
|
||||
}
|
||||
if self.multi_line_paste.is_some() && self.multi_line_paste_focus.is_focused(window) {
|
||||
self.multi_line_paste_marked_text.clear();
|
||||
self.multi_line_paste_marked_range = None;
|
||||
@@ -376,26 +321,6 @@ impl EntityInputHandler for NyaTermApp {
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
if self.sync_groups_open && self.sync_groups_search_focus.is_focused(window) {
|
||||
self.sync_groups_search_marked_text.clear();
|
||||
if !text.is_empty() {
|
||||
self.sync_groups_search_draft.push_str(text);
|
||||
}
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
if self.sync_groups_open && self.sync_groups_name_focus.is_focused(window) {
|
||||
self.sync_groups_name_marked_text.clear();
|
||||
if !text.is_empty() {
|
||||
let mut name = self
|
||||
.selected_sync_group()
|
||||
.map(|group| group.name.clone())
|
||||
.unwrap_or_default();
|
||||
name.push_str(text);
|
||||
self.set_selected_sync_group_name(name, cx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if self.multi_line_paste.is_some() && self.multi_line_paste_focus.is_focused(window) {
|
||||
let range = range
|
||||
.as_ref()
|
||||
@@ -474,16 +399,6 @@ impl EntityInputHandler for NyaTermApp {
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
if self.sync_groups_open && self.sync_groups_search_focus.is_focused(window) {
|
||||
self.sync_groups_search_marked_text = new_text.to_string();
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
if self.sync_groups_open && self.sync_groups_name_focus.is_focused(window) {
|
||||
self.sync_groups_name_marked_text = new_text.to_string();
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
if self.multi_line_paste.is_some() && self.multi_line_paste_focus.is_focused(window) {
|
||||
let range = range
|
||||
.as_ref()
|
||||
@@ -544,9 +459,6 @@ impl EntityInputHandler for NyaTermApp {
|
||||
}
|
||||
if self.quick_switch_open(cx)
|
||||
|| self.is_locked
|
||||
|| (self.sync_groups_open
|
||||
&& (self.sync_groups_search_focus.is_focused(window)
|
||||
|| self.sync_groups_name_focus.is_focused(window)))
|
||||
|| (self.multi_line_paste.is_some() && self.multi_line_paste_focus.is_focused(window))
|
||||
|| (self.rename_session_id.is_some() && self.rename_focus.is_focused(window))
|
||||
|| (self.startup_command_open && self.startup_command_focus.is_focused(window))
|
||||
@@ -601,16 +513,6 @@ impl EntityInputHandler for NyaTermApp {
|
||||
if self.is_locked {
|
||||
return Some(self.lock_password_draft.encode_utf16().count());
|
||||
}
|
||||
if self.sync_groups_open && self.sync_groups_search_focus.is_focused(window) {
|
||||
return Some(self.sync_groups_search_draft.encode_utf16().count());
|
||||
}
|
||||
if self.sync_groups_open && self.sync_groups_name_focus.is_focused(window) {
|
||||
return Some(
|
||||
self.selected_sync_group()
|
||||
.map(|group| group.name.encode_utf16().count())
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
if self.multi_line_paste.is_some() && self.multi_line_paste_focus.is_focused(window) {
|
||||
return Some(utf16_offset_for_byte(
|
||||
self.multi_line_paste_text(),
|
||||
|
||||
@@ -269,6 +269,10 @@ impl NyaTermApp {
|
||||
self.apply_recording_search(text, cx);
|
||||
} else if id.as_ref() == "settings.keybindings.search" {
|
||||
self.apply_keybinding_search(text, cx);
|
||||
} else if id.as_ref() == "sync.groups.search" {
|
||||
self.apply_sync_groups_search(text, cx);
|
||||
} else if let Some(group_id) = id.strip_prefix("sync.group-name.") {
|
||||
self.apply_sync_group_name(group_id, text, cx);
|
||||
} else if let Some(rest) = id.strip_prefix("ai.credential.") {
|
||||
self.apply_ai_credential_input(rest, text, cx);
|
||||
} else if let Some(field) = id
|
||||
|
||||
Reference in New Issue
Block a user