use super::{InlinePromptSuggestionSource, Session};
use crate::config::constants::ui;
use unicode_segmentation::UnicodeSegmentation;
impl Session {
pub(crate) fn refresh_input_edit_state(&mut self) {
self.clear_suggested_prompt_state();
self.clear_inline_prompt_suggestion();
self.input_compact_mode = self.input_compact_placeholder().is_some();
}
pub(crate) fn set_inline_prompt_suggestion(&mut self, suggestion: String, llm_generated: bool) {
let trimmed = suggestion.trim();
if trimmed.is_empty() {
self.clear_inline_prompt_suggestion();
return;
}
self.inline_prompt_suggestion.suggestion = Some(trimmed.to_string());
self.inline_prompt_suggestion.source = Some(if llm_generated {
InlinePromptSuggestionSource::Llm
} else {
InlinePromptSuggestionSource::Local
});
self.mark_dirty();
}
pub(crate) fn accept_inline_prompt_suggestion(&mut self) -> bool {
let Some(suffix) = self.visible_inline_prompt_suggestion_suffix() else {
return false;
};
self.input_manager.insert_text(&suffix);
self.clear_inline_prompt_suggestion();
self.mark_dirty();
true
}
pub(crate) fn insert_char(&mut self, ch: char) {
if ch == '\u{7f}' {
return;
}
if ch == '\n' && !self.can_insert_newline() {
return;
}
self.input_manager.insert_char(ch);
self.refresh_input_edit_state();
}
pub fn insert_paste_text(&mut self, text: &str) {
let sanitized: String = text
.chars()
.filter(|&ch| ch != '\r' && ch != '\u{7f}')
.collect();
if sanitized.is_empty() {
return;
}
self.input_manager.insert_text(&sanitized);
self.refresh_input_edit_state();
}
pub(crate) fn apply_suggested_prompt(&mut self, text: String) {
let trimmed = text.trim();
if trimmed.is_empty() {
return;
}
let merged = if self.input_manager.content().trim().is_empty() {
trimmed.to_string()
} else {
format!("{}\n\n{}", self.input_manager.content().trim_end(), trimmed)
};
self.input_manager.set_content(merged);
self.input_manager
.set_cursor(self.input_manager.content().len());
self.suggested_prompt_state.active = true;
self.input_compact_mode = self.input_compact_placeholder().is_some();
self.mark_dirty();
}
pub(crate) fn remaining_newline_capacity(&self) -> usize {
ui::INLINE_INPUT_MAX_LINES
.saturating_sub(1)
.saturating_sub(self.input_manager.content().matches('\n').count())
}
pub(crate) fn can_insert_newline(&self) -> bool {
self.remaining_newline_capacity() > 0
}
pub(crate) fn delete_char(&mut self) {
self.input_manager.backspace();
self.refresh_input_edit_state();
}
pub(crate) fn delete_char_forward(&mut self) {
self.input_manager.delete();
self.refresh_input_edit_state();
}
pub(crate) fn delete_word_backward(&mut self) {
if self.input_manager.delete_selection() {
self.refresh_input_edit_state();
return;
}
if self.input_manager.cursor() == 0 {
return;
}
let graphemes: Vec<(usize, &str)> = self
.input_manager
.content()
.grapheme_indices(true)
.take_while(|(idx, _)| *idx < self.input_manager.cursor())
.collect();
if graphemes.is_empty() {
return;
}
let mut index = graphemes.len();
while index > 0 {
let (_, grapheme) = graphemes[index - 1];
if !grapheme.chars().all(char::is_whitespace) {
break;
}
index -= 1;
}
while index > 0 {
let (_, grapheme) = graphemes[index - 1];
if grapheme.chars().all(char::is_whitespace) {
break;
}
index -= 1;
}
let delete_start = if index < graphemes.len() {
graphemes[index].0
} else {
self.input_manager.cursor()
};
if delete_start < self.input_manager.cursor() {
let before = &self.input_manager.content()[..delete_start];
let after = &self.input_manager.content()[self.input_manager.cursor()..];
let new_content = format!("{}{}", before, after);
self.input_manager.set_content(new_content);
self.input_manager.set_cursor(delete_start);
self.refresh_input_edit_state();
}
}
#[allow(dead_code)]
pub(crate) fn delete_word_forward(&mut self) {
self.input_manager.delete_word_forward();
self.refresh_input_edit_state();
}
pub(crate) fn delete_to_start_of_line(&mut self) {
if self.input_manager.delete_selection() {
self.refresh_input_edit_state();
return;
}
let content = self.input_manager.content();
let cursor = self.input_manager.cursor();
let before = &content[..cursor];
let delete_start = if let Some(newline_pos) = before.rfind('\n') {
newline_pos + 1 } else {
0 };
if delete_start < cursor {
let new_content = format!("{}{}", &content[..delete_start], &content[cursor..]);
self.input_manager.set_content(new_content);
self.input_manager.set_cursor(delete_start);
self.refresh_input_edit_state();
}
}
pub(crate) fn delete_to_end_of_line(&mut self) {
if self.input_manager.delete_selection() {
self.refresh_input_edit_state();
return;
}
let content = self.input_manager.content();
let cursor = self.input_manager.cursor();
let rest = &content[cursor..];
let delete_len = if let Some(newline_pos) = rest.find('\n') {
newline_pos
} else {
rest.len()
};
if delete_len > 0 {
let new_content = format!("{}{}", &content[..cursor], &content[cursor + delete_len..]);
self.input_manager.set_content(new_content);
self.refresh_input_edit_state();
}
}
pub(crate) fn move_left(&mut self) {
self.input_manager.move_cursor_left();
}
pub(crate) fn move_right(&mut self) {
self.input_manager.move_cursor_right();
}
pub(crate) fn select_left(&mut self) {
let cursor = self.input_manager.cursor();
if cursor == 0 {
self.input_manager.set_cursor_with_selection(0);
return;
}
let mut pos = cursor - 1;
let content = self.input_manager.content();
while pos > 0 && !content.is_char_boundary(pos) {
pos -= 1;
}
self.input_manager.set_cursor_with_selection(pos);
}
pub(crate) fn select_right(&mut self) {
let cursor = self.input_manager.cursor();
let content = self.input_manager.content();
if cursor >= content.len() {
self.input_manager.set_cursor_with_selection(content.len());
return;
}
let mut pos = cursor + 1;
while pos < content.len() && !content.is_char_boundary(pos) {
pos += 1;
}
self.input_manager.set_cursor_with_selection(pos);
}
pub(crate) fn move_left_word(&mut self) {
if self.input_manager.cursor() == 0 {
return;
}
let graphemes: Vec<(usize, &str)> = self
.input_manager
.content()
.grapheme_indices(true)
.take_while(|(idx, _)| *idx < self.input_manager.cursor())
.collect();
if graphemes.is_empty() {
return;
}
let mut index = graphemes.len();
while index > 0 {
let (_, grapheme) = graphemes[index - 1];
if !grapheme.chars().all(char::is_whitespace) {
break;
}
index -= 1;
}
while index > 0 {
let (_, grapheme) = graphemes[index - 1];
if grapheme.chars().all(char::is_whitespace) {
break;
}
index -= 1;
}
if index < graphemes.len() {
self.input_manager.set_cursor(graphemes[index].0);
} else {
self.input_manager.set_cursor(0);
}
}
pub(crate) fn move_right_word(&mut self) {
if self.input_manager.cursor() >= self.input_manager.content().len() {
return;
}
let graphemes: Vec<(usize, &str)> = self
.input_manager
.content()
.grapheme_indices(true)
.skip_while(|(idx, _)| *idx < self.input_manager.cursor())
.collect();
if graphemes.is_empty() {
self.input_manager.move_cursor_to_end();
return;
}
let mut index = 0;
let mut skipped_whitespace = false;
while index < graphemes.len() {
let (_, grapheme) = graphemes[index];
if !grapheme.chars().all(char::is_whitespace) {
break;
}
skipped_whitespace = true;
index += 1;
}
if index >= graphemes.len() {
self.input_manager.move_cursor_to_end();
return;
}
if skipped_whitespace {
self.input_manager.set_cursor(graphemes[index].0);
return;
}
while index < graphemes.len() {
let (_, grapheme) = graphemes[index];
if grapheme.chars().all(char::is_whitespace) {
break;
}
index += 1;
}
if index < graphemes.len() {
self.input_manager.set_cursor(graphemes[index].0);
} else {
self.input_manager.move_cursor_to_end();
}
}
pub(crate) fn move_to_start(&mut self) {
self.input_manager.move_cursor_to_start();
}
pub(crate) fn move_to_end(&mut self) {
self.input_manager.move_cursor_to_end();
}
pub(crate) fn select_to_start(&mut self) {
self.input_manager.set_cursor_with_selection(0);
}
pub(crate) fn select_to_end(&mut self) {
self.input_manager
.set_cursor_with_selection(self.input_manager.content().len());
}
pub(crate) fn remember_submitted_input(
&mut self,
submitted: super::input_manager::InputHistoryEntry,
) {
self.input_manager.add_to_history(submitted);
}
#[allow(dead_code)]
pub(crate) fn navigate_history_previous(&mut self) -> bool {
if let Some(previous) = self.input_manager.go_to_previous_history() {
self.input_manager.apply_history_entry(previous);
true
} else {
false
}
}
#[allow(dead_code)]
pub(crate) fn navigate_history_next(&mut self) -> bool {
if let Some(next) = self.input_manager.go_to_next_history() {
self.input_manager.apply_history_entry(next);
true
} else {
false
}
}
pub fn history_position(&self) -> Option<(usize, usize)> {
self.input_manager.history_index().map(|idx| {
let total = self.input_manager.history().len();
(total - idx, total)
})
}
}