use super::{ChatPanel, ChatView, MessageRole};
fn char_to_byte_offset(s: &str, char_offset: usize) -> usize {
s.char_indices()
.nth(char_offset)
.map(|(i, _)| i)
.unwrap_or(s.len())
}
impl ChatView {
pub fn get_selected_text(&self) -> Option<String> {
let selection = self.text_selection.as_ref()?;
let (start, end) = selection.normalized();
let mut result = String::new();
for (idx, msg) in self.messages.iter().enumerate() {
if idx < start.message_index || idx > end.message_index {
continue;
}
let content = &msg.content;
if idx == start.message_index && idx == end.message_index {
let start_byte = char_to_byte_offset(content, start.char_offset);
let end_byte = char_to_byte_offset(content, end.char_offset);
if start_byte < content.len() && end_byte <= content.len() {
result.push_str(&content[start_byte..end_byte]);
}
} else if idx == start.message_index {
let start_byte = char_to_byte_offset(content, start.char_offset);
if start_byte < content.len() {
result.push_str(&content[start_byte..]);
result.push('\n');
}
} else if idx == end.message_index {
let end_byte = char_to_byte_offset(content, end.char_offset);
if end_byte <= content.len() {
result.push_str(&content[..end_byte]);
}
} else {
result.push_str(content);
result.push('\n');
}
}
if result.is_empty() {
None
} else {
Some(result)
}
}
pub fn copy_selection(&mut self) -> bool {
if let Some(text) = self.get_selected_text() {
if let Some(ref mut clipboard) = self.clipboard {
if clipboard.set_text(&text).is_ok() {
if let Some(ref selection) = self.text_selection {
let (start, _) = selection.normalized();
self.copy_flash_index = Some(start.message_index);
self.copy_flash_start = self.frame;
}
return true;
}
}
}
false
}
pub fn clear_selection(&mut self) {
self.text_selection = None;
self.is_selecting = false;
}
pub fn copy_selected_message(&mut self, text_only: bool) -> bool {
if self.focused_panel != ChatPanel::Conversation {
return false;
}
let cursor = self.conversation_scroll.cursor;
if cursor >= self.messages.len() {
return false;
}
let msg = &self.messages[cursor];
let text = if text_only {
msg.content.clone()
} else {
let role = match msg.role {
MessageRole::User => "User",
MessageRole::Nika => "Nika",
MessageRole::System => "System",
MessageRole::Tool => "Tool",
};
format!("[{}] {}", role, msg.content)
};
if let Some(ref mut clipboard) = self.clipboard {
let success = clipboard.set_text(text).is_ok();
if success {
self.copy_flash_index = Some(cursor);
self.copy_flash_start = self.frame;
}
success
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_char_to_byte_offset_ascii() {
let s = "hello world";
assert_eq!(char_to_byte_offset(s, 0), 0);
assert_eq!(char_to_byte_offset(s, 5), 5);
assert_eq!(char_to_byte_offset(s, 11), 11);
assert_eq!(char_to_byte_offset(s, 100), 11); }
#[test]
fn test_char_to_byte_offset_unicode() {
let s = "héllo 🦋 world"; assert_eq!(char_to_byte_offset(s, 0), 0); assert_eq!(char_to_byte_offset(s, 1), 1); assert_eq!(char_to_byte_offset(s, 2), 3); assert_eq!(char_to_byte_offset(s, 6), 7); assert_eq!(char_to_byte_offset(s, 7), 11); }
}