Skip to content
Merged
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ toml = "0.8"
tracing = "0.1.44"
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
unicode-width = "0.2"
unicode-segmentation = "1.13.1"
schemars = { version = "1.2.1", features = ["derive"] }

[patch.crates-io]
Expand Down
26 changes: 2 additions & 24 deletions src/client/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ mod scroll;
mod settings;
mod state;
mod surface_patch;
mod text_editor;
mod word_selection;
mod worktrees;
use text_editor::TextEditor;
use word_selection::ClientWordSelection;

pub(in crate::client::shell) use render::sidebar;
Expand Down Expand Up @@ -64,30 +66,6 @@ use crate::protocol::{
#[cfg(test)]
use crate::raw_input::RawInputEvent;

fn delete_overlay_word(rename: &mut ClientRenameOverlay) {
if rename.replace_on_type {
rename.input.clear();
rename.replace_on_type = false;
return;
}
while rename.input.chars().last().is_some_and(char::is_whitespace) {
rename.input.pop();
}
let Some(word) = rename
.input
.chars()
.last()
.map(|character| character.is_alphanumeric() || character == '_')
else {
return;
};
while rename.input.chars().last().is_some_and(|character| {
!character.is_whitespace() && (character.is_alphanumeric() || character == '_') == word
}) {
rename.input.pop();
}
}

fn target_event_message(target: ClientInputTarget, event: ClientPaneInputEvent) -> ClientMessage {
match target {
ClientInputTarget::Pane(pane_id) => ClientMessage::ClientShellPaneInput {
Expand Down
2 changes: 1 addition & 1 deletion src/client/shell/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ impl ClientShellState {
}
if action == crate::input::KeybindAction::Help {
self.overlay = Some(ClientShellOverlay::Help(ClientHelpOverlay {
query: String::new(),
query: TextEditor::default(),
search_focused: false,
scroll: 0,
}));
Expand Down
6 changes: 4 additions & 2 deletions src/client/shell/composition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,12 +384,14 @@ impl ClientShellState {
.saturating_sub(copy_mode.offset_from_bottom)
.min(u32::MAX as usize) as u32;
let viewport_row = copy_mode.cursor.row.saturating_sub(viewport_top);
let x = hit.inner_rect.x.saturating_add(copy_mode.cursor.col);
let y = hit.inner_rect.y.saturating_add(viewport_row as u16);
if viewport_row < u32::from(hit.inner_rect.height)
&& copy_mode.cursor.col < hit.inner_rect.width
&& x < frame.width
&& y < frame.height
{
let mut composed = frame.to_ratatui_buffer()?;
let x = hit.inner_rect.x + copy_mode.cursor.col;
let y = hit.inner_rect.y + viewport_row as u16;
composed[(x, y)].set_style(
Style::default()
.fg(match self.config.palette.panel_bg {
Expand Down
12 changes: 4 additions & 8 deletions src/client/shell/context_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,7 @@ impl ClientShellState {
if let Some(label) = label {
self.overlay = Some(ClientShellOverlay::Rename(ClientRenameOverlay {
title: "rename workspace",
input: label,
replace_on_type: false,
input: TextEditor::new(&label, false),
target: ClientRenameTarget::Workspace { workspace_id },
}));
}
Expand Down Expand Up @@ -322,8 +321,7 @@ impl ClientShellState {
.to_string();
self.overlay = Some(ClientShellOverlay::Rename(ClientRenameOverlay {
title: "new tab",
input: default_name.clone(),
replace_on_type: true,
input: TextEditor::new(&default_name, true),
target: ClientRenameTarget::NewTab {
workspace_id,
default_name,
Expand All @@ -350,8 +348,7 @@ impl ClientShellState {
if let Some(tab) = tab {
self.overlay = Some(ClientShellOverlay::Rename(ClientRenameOverlay {
title: "rename tab",
input: tab.label.clone(),
replace_on_type: false,
input: TextEditor::new(&tab.label, false),
target: ClientRenameTarget::Tab {
tab_id,
auto_name: !tab.custom_label,
Expand Down Expand Up @@ -392,8 +389,7 @@ impl ClientShellState {
});
self.overlay = Some(ClientShellOverlay::Rename(ClientRenameOverlay {
title: "rename pane",
input: label.clone().unwrap_or_default(),
replace_on_type: label.is_none(),
input: TextEditor::new(label.as_deref().unwrap_or_default(), label.is_none()),
target: ClientRenameTarget::Pane { pane_id },
}));
}
Expand Down
39 changes: 12 additions & 27 deletions src/client/shell/copy_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,38 +279,18 @@ impl ClientShellState {
}
}
KeyCode::Enter => {
submit = Some((prompt.query.clone(), prompt.direction));
submit = Some((prompt.query.to_string(), prompt.direction));
if let Some(copy_mode) = self.copy_mode.as_mut() {
copy_mode.search_prompt = None;
}
}
KeyCode::Backspace => {
if let Some(prompt) = self
.copy_mode
.as_mut()
.and_then(|copy_mode| copy_mode.search_prompt.as_mut())
{
prompt.query.pop();
}
}
KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
_ => {
if let Some(prompt) = self
.copy_mode
.as_mut()
.and_then(|copy_mode| copy_mode.search_prompt.as_mut())
{
prompt.query.clear();
}
}
_ => {
if let Some(ch) = crate::copy_mode::copy_mode_command_char(key.clone()) {
if let Some(prompt) = self
.copy_mode
.as_mut()
.and_then(|copy_mode| copy_mode.search_prompt.as_mut())
{
prompt.query.push(ch);
}
prompt.query.handle_key(key);
}
}
}
Expand All @@ -322,16 +302,21 @@ impl ClientShellState {
}

pub(super) fn insert_copy_search_text(&mut self, text: &str) -> bool {
if self.mode != ClientShellMode::Copy
|| self.overlay.is_some()
|| self.popup_terminal_id.is_some()
|| self.popup_pending
{
return false;
}
let Some(prompt) = self
.copy_mode
.as_mut()
.and_then(|copy_mode| copy_mode.search_prompt.as_mut())
else {
return false;
};
prompt
.query
.extend(text.chars().filter(|character| !character.is_control()));
prompt.query.insert(text);
true
}

Expand All @@ -341,7 +326,7 @@ impl ClientShellState {
};
copy_mode.search_prompt = Some(ClientCopySearchPrompt {
direction,
query: String::new(),
query: TextEditor::default(),
});
}

Expand Down
26 changes: 17 additions & 9 deletions src/client/shell/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ pub(super) fn is_modal_paste_shortcut_for_platform(
key: &crate::input::TerminalKey,
macos: bool,
) -> bool {
matches!(key.code, KeyCode::Char('v' | 'V'))
&& (key.modifiers.contains(KeyModifiers::CONTROL)
|| macos && key.modifiers.contains(KeyModifiers::SUPER))
key.generated_text.as_deref().is_none_or(str::is_empty)
&& matches!(key.code, KeyCode::Char('v' | 'V'))
&& (key.modifiers.difference(KeyModifiers::SHIFT) == KeyModifiers::CONTROL
|| macos && key.modifiers.difference(KeyModifiers::SHIFT) == KeyModifiers::SUPER)
}

fn is_modal_paste_shortcut(key: &crate::input::TerminalKey) -> bool {
Expand Down Expand Up @@ -444,12 +445,14 @@ impl ClientShellState {
{
return false;
}
if self
.copy_mode
.as_ref()
.is_some_and(|copy_mode| copy_mode.search_prompt.is_some())
if self.mode == ClientShellMode::Copy
&& self.overlay.is_none()
&& self
.copy_mode
.as_ref()
.is_some_and(|copy_mode| copy_mode.search_prompt.is_some())
{
return self.overlay.is_none();
return true;
}
matches!(
self.overlay.as_ref(),
Expand Down Expand Up @@ -609,7 +612,12 @@ impl ClientShellState {
None
}
ClientShellMode::Copy => {
if crate::config::terminal_key_matches_combo(key, self.config.keybinds.prefix) {
if self
.copy_mode
.as_ref()
.is_none_or(|copy_mode| copy_mode.search_prompt.is_none())
&& crate::config::terminal_key_matches_combo(key, self.config.keybinds.prefix)
{
self.mode = ClientShellMode::Prefix;
outcome.repaint = true;
} else {
Expand Down
1 change: 0 additions & 1 deletion src/client/shell/mouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1679,7 +1679,6 @@ impl ClientShellState {
} else if super::contains(self.hits.overlay_clear, point) {
if let Some(ClientShellOverlay::Rename(rename)) = self.overlay.as_mut() {
rename.input.clear();
rename.replace_on_type = false;
outcome.repaint = true;
}
} else {
Expand Down
Loading