Refactor: simplify word navigation logic, improve line wrapping, and enhance parameter initialization across core and TUI modules.

This commit is contained in:
2025-09-30 02:54:07 +02:00
parent 8ee4c5f384
commit 053f389b1e
6 changed files with 27 additions and 45 deletions

View File

@@ -106,13 +106,11 @@ impl Router {
return true; return true;
} }
if pattern.ends_with('*') { if let Some(prefix) = pattern.strip_suffix('*') {
let prefix = &pattern[..pattern.len() - 1];
return model.starts_with(prefix); return model.starts_with(prefix);
} }
if pattern.starts_with('*') { if let Some(suffix) = pattern.strip_prefix('*') {
let suffix = &pattern[1..];
return model.ends_with(suffix); return model.ends_with(suffix);
} }

View File

@@ -72,7 +72,7 @@ pub struct ChatRequest {
} }
/// Parameters for chat completion /// Parameters for chat completion
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ChatParameters { pub struct ChatParameters {
/// Temperature for randomness (0.0 to 2.0) /// Temperature for randomness (0.0 to 2.0)
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@@ -85,20 +85,10 @@ pub struct ChatParameters {
pub stream: bool, pub stream: bool,
/// Additional provider-specific parameters /// Additional provider-specific parameters
#[serde(flatten)] #[serde(flatten)]
#[serde(default)]
pub extra: HashMap<String, serde_json::Value>, pub extra: HashMap<String, serde_json::Value>,
} }
impl Default for ChatParameters {
fn default() -> Self {
Self {
temperature: None,
max_tokens: None,
stream: false,
extra: HashMap::new(),
}
}
}
/// Response from a chat completion request /// Response from a chat completion request
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatResponse { pub struct ChatResponse {

View File

@@ -305,9 +305,7 @@ pub fn find_word_end(line: &str, col: usize) -> Option<usize> {
pos += 1; pos += 1;
} }
// Move back one to be ON the last character // Move back one to be ON the last character
if pos > 0 { pos = pos.saturating_sub(1);
pos -= 1;
}
} else { } else {
// Skip non-word characters // Skip non-word characters
while pos < chars.len() && !is_word_char(chars[pos]) { while pos < chars.len() && !is_word_char(chars[pos]) {
@@ -317,9 +315,7 @@ pub fn find_word_end(line: &str, col: usize) -> Option<usize> {
while pos < chars.len() && is_word_char(chars[pos]) { while pos < chars.len() && is_word_char(chars[pos]) {
pos += 1; pos += 1;
} }
if pos > 0 { pos = pos.saturating_sub(1);
pos -= 1;
}
} }
Some(pos) Some(pos)
@@ -336,9 +332,7 @@ pub fn find_prev_word_boundary(line: &str, col: usize) -> Option<usize> {
let is_word_char = |c: char| c.is_alphanumeric() || c == '_'; let is_word_char = |c: char| c.is_alphanumeric() || c == '_';
// Move back one position first // Move back one position first
if pos > 0 { pos = pos.saturating_sub(1);
pos -= 1;
}
// Skip non-word characters // Skip non-word characters
while pos > 0 && !is_word_char(chars[pos]) { while pos > 0 && !is_word_char(chars[pos]) {

View File

@@ -52,8 +52,7 @@ pub fn build_cursor_map(text: &str, width: u16) -> Vec<ScreenPos> {
word_start_col = col; word_start_col = col;
word_start_idx = byte_offset + grapheme.len(); word_start_idx = byte_offset + grapheme.len();
} }
} else { } else if col + grapheme_width > width {
if col + grapheme_width > width {
if word_start_col > 0 && byte_offset == word_start_idx { if word_start_col > 0 && byte_offset == word_start_idx {
// This is the first character of a new word that won't fit, wrap it // This is the first character of a new word that won't fit, wrap it
row += 1; row += 1;
@@ -69,7 +68,6 @@ pub fn build_cursor_map(text: &str, width: u16) -> Vec<ScreenPos> {
} else { } else {
col += grapheme_width; col += grapheme_width;
} }
}
// Set position for the end of this grapheme and any intermediate bytes // Set position for the end of this grapheme and any intermediate bytes
let end_pos = ScreenPos { let end_pos = ScreenPos {

View File

@@ -1320,8 +1320,10 @@ impl ChatApp {
self.status = format!("Loading model '{}'...", self.controller.selected_model()); self.status = format!("Loading model '{}'...", self.controller.selected_model());
self.start_loading_animation(); self.start_loading_animation();
let mut parameters = ChatParameters::default(); let parameters = ChatParameters {
parameters.stream = self.controller.config().general.enable_streaming; stream: self.controller.config().general.enable_streaming,
..Default::default()
};
// Step 2: Start the actual request // Step 2: Start the actual request
match self match self
@@ -1392,7 +1394,7 @@ impl ChatApp {
} else { } else {
// If the current model is not in the filtered list, select the first one // If the current model is not in the filtered list, select the first one
self.selected_model = Some(0); self.selected_model = Some(0);
if let Some(model) = filtered_models.get(0) { if let Some(model) = filtered_models.first() {
self.controller.set_model(model.id.clone()); self.controller.set_model(model.id.clone());
// Also update the config with the new model and provider // Also update the config with the new model and provider
self.controller.config_mut().general.default_model = Some(model.id.clone()); self.controller.config_mut().general.default_model = Some(model.id.clone());

View File

@@ -30,7 +30,7 @@ pub fn render_chat(frame: &mut Frame<'_>, app: &mut ChatApp) {
} else { } else {
buffer_text.lines().collect() buffer_text.lines().collect()
}; };
let visual_lines = calculate_wrapped_line_count(lines.into_iter(), available_width); let visual_lines = calculate_wrapped_line_count(lines, available_width);
(visual_lines as u16).min(10) + 2 // +2 for borders (visual_lines as u16).min(10) + 2 // +2 for borders
}; };
@@ -283,7 +283,7 @@ fn compute_cursor_metrics(
for (row_idx, line) in lines.iter().enumerate() { for (row_idx, line) in lines.iter().enumerate() {
let display_owned = mask_char.map(|mask| mask_line(line, mask)); let display_owned = mask_char.map(|mask| mask_line(line, mask));
let display_line = display_owned.as_deref().unwrap_or_else(|| line.as_str()); let display_line = display_owned.as_deref().unwrap_or(line.as_str());
let mut segments = if wrap_lines { let mut segments = if wrap_lines {
wrap_line_segments(display_line, content_width) wrap_line_segments(display_line, content_width)
@@ -889,7 +889,7 @@ fn render_input(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) {
let lines: Vec<Line> = if input_text.is_empty() { let lines: Vec<Line> = if input_text.is_empty() {
vec![Line::from("Press 'i' to start typing")] vec![Line::from("Press 'i' to start typing")]
} else { } else {
input_text.lines().map(|line| Line::from(line)).collect() input_text.lines().map(Line::from).collect()
}; };
let paragraph = Paragraph::new(lines) let paragraph = Paragraph::new(lines)