Use COMP_WORDBREAKS for completion word breaking, fix cursor row in vi command mode, and append completion suffix instead of replacing full token

This commit is contained in:
2026-03-13 11:18:57 -04:00
parent 5500b081fe
commit a46ebe6868
3 changed files with 25 additions and 14 deletions

View File

@@ -6,6 +6,7 @@ use std::{
}; };
use nix::sys::signal::Signal; use nix::sys::signal::Signal;
use unicode_width::UnicodeWidthStr;
use crate::{ use crate::{
builtin::complete::{CompFlags, CompOptFlags, CompOpts}, expand::escape_str, libsh::{error::ShResult, guards::var_ctx_guard, sys::TTY_FILENO, utils::TkVecUtils}, parse::{ builtin::complete::{CompFlags, CompOptFlags, CompOpts}, expand::escape_str, libsh::{error::ShResult, guards::var_ctx_guard, sys::TTY_FILENO, utils::TkVecUtils}, parse::{
@@ -1172,10 +1173,11 @@ impl Completer for FuzzyCompleter {
log::debug!("Getting completed line for candidate: {}", _candidate); log::debug!("Getting completed line for candidate: {}", _candidate);
let selected = self.selector.selected_candidate().unwrap_or_default(); let selected = self.selector.selected_candidate().unwrap_or_default();
let escaped = escape_str(&selected, false); let (mut start, end) = self.completer.token_span;
log::debug!("Selected candidate: {}", selected); let slice = self.completer.original_input.get(start..end).unwrap_or_default();
let (start, end) = self.completer.token_span; start += slice.width();
log::debug!("Token span: ({}, {})", start, end); let completion = selected.strip_prefix(slice).unwrap_or(&selected);
let escaped = escape_str(completion, false);
let ret = format!( let ret = format!(
"{}{}{}", "{}{}{}",
&self.completer.original_input[..start], &self.completer.original_input[..start],
@@ -1432,8 +1434,11 @@ impl SimpleCompleter {
} }
let selected = &self.candidates[self.selected_idx]; let selected = &self.candidates[self.selected_idx];
let escaped = escape_str(selected, false); let (mut start, end) = self.token_span;
let (start, end) = self.token_span; let slice = self.original_input.get(start..end).unwrap_or("");
start += slice.width();
let completion = selected.strip_prefix(slice).unwrap_or(selected);
let escaped = escape_str(completion, false);
format!( format!(
"{}{}{}", "{}{}{}",
&self.original_input[..start], &self.original_input[..start],
@@ -1596,14 +1601,14 @@ impl SimpleCompleter {
.set_range(self.token_span.0..self.token_span.1); .set_range(self.token_span.0..self.token_span.1);
} }
// If token contains '=', only complete after the '=' // If token contains any COMP_WORDBREAKS, break the word
let token_str = cur_token.span.as_str(); let token_str = cur_token.span.as_str();
if let Some(eq_pos) = token_str.rfind('=') {
self.token_span.0 = cur_token.span.range().start + eq_pos + 1; let word_breaks = read_vars(|v| v.try_get_var("COMP_WORDBREAKS")).unwrap_or("=".into());
cur_token if let Some(break_pos) = token_str.rfind(|c: char| word_breaks.contains(c)) {
.span self.token_span.0 = cur_token.span.range().start + break_pos + 1;
.set_range(self.token_span.0..self.token_span.1); cur_token.span.set_range(self.token_span.0..self.token_span.1);
} }
let raw_tk = cur_token.as_str().to_string(); let raw_tk = cur_token.as_str().to_string();
let expanded_tk = cur_token.expand()?; let expanded_tk = cur_token.expand()?;

View File

@@ -1055,6 +1055,7 @@ impl ShedVi {
let pending_seq = self.mode.pending_seq().unwrap_or_default(); let pending_seq = self.mode.pending_seq().unwrap_or_default();
write!(buf, "\n: {pending_seq}").unwrap(); write!(buf, "\n: {pending_seq}").unwrap();
new_layout.end.row += 1; new_layout.end.row += 1;
new_layout.cursor.row += 1;
} }
write!(buf, "{}", &self.mode.cursor_style()).unwrap(); write!(buf, "{}", &self.mode.cursor_style()).unwrap();

View File

@@ -1045,7 +1045,7 @@ impl VarTab {
} }
} }
pub fn new() -> Self { pub fn new() -> Self {
let vars = HashMap::new(); let vars = Self::init_sh_vars();
let params = Self::init_params(); let params = Self::init_params();
Self::init_env(); Self::init_env();
let mut var_tab = Self { let mut var_tab = Self {
@@ -1064,6 +1064,11 @@ impl VarTab {
params.insert(ShellParam::LastJob, "".into()); // PID of the last background job (if any) params.insert(ShellParam::LastJob, "".into()); // PID of the last background job (if any)
params params
} }
fn init_sh_vars() -> HashMap<String,Var> {
let mut vars = HashMap::new();
vars.insert("COMP_WORDBREAKS".into(), " \t\n\"'@><=;|&(".into());
vars
}
fn init_env() { fn init_env() {
let pathbuf_to_string = let pathbuf_to_string =
|pb: Result<PathBuf, std::io::Error>| pb.unwrap_or_default().to_string_lossy().to_string(); |pb: Result<PathBuf, std::io::Error>| pb.unwrap_or_default().to_string_lossy().to_string();