mirror of
https://github.com/mii443/vrclipboard-ime-gui.git
synced 2025-08-22 16:15:32 +00:00
refactoring
This commit is contained in:
@ -1,241 +1,423 @@
|
|||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
use tracing::{debug, error, info, trace};
|
use tracing::{debug, info, trace};
|
||||||
|
|
||||||
use super::client::AzookeyConversionClient;
|
use super::client::AzookeyConversionClient;
|
||||||
|
|
||||||
|
/// Maximum number of history entries to retain
|
||||||
|
const MAX_HISTORY_SIZE: usize = 3;
|
||||||
|
|
||||||
|
/// Maximum number of conversion candidates
|
||||||
|
const MAX_CANDIDATES: usize = 10;
|
||||||
|
|
||||||
|
/// AzookeyConversion - Provides romanized text to kanji conversion and candidate switching
|
||||||
|
///
|
||||||
|
/// This struct implements the logic for character conversion and candidate switching
|
||||||
|
/// in a Japanese input method system.
|
||||||
pub struct AzookeyConversion {
|
pub struct AzookeyConversion {
|
||||||
pub conversion_history: Vec<String>,
|
/// Conversion history (max 3 entries)
|
||||||
pub clipboard_history: Vec<String>,
|
conversion_history: Vec<String>,
|
||||||
pub now_reconvertion: bool,
|
|
||||||
pub target_text: String,
|
/// Pre-conversion text history (max 3 entries)
|
||||||
pub reconversion_candidates: Option<Vec<String>>,
|
input_history: Vec<String>,
|
||||||
pub reconversion_index: Option<i32>,
|
|
||||||
pub reconversion_prefix: Option<String>,
|
/// Whether currently in reconversion mode
|
||||||
pub client: AzookeyConversionClient,
|
is_reconversion_mode: bool,
|
||||||
|
|
||||||
|
/// Current text being converted
|
||||||
|
current_text: String,
|
||||||
|
|
||||||
|
/// List of reconversion candidates
|
||||||
|
reconversion_candidates: Option<Vec<String>>,
|
||||||
|
|
||||||
|
/// Index of currently selected reconversion candidate
|
||||||
|
candidate_index: Option<usize>,
|
||||||
|
|
||||||
|
/// Common prefix for reconversion
|
||||||
|
common_prefix: Option<String>,
|
||||||
|
|
||||||
|
/// Client that performs conversion operations
|
||||||
|
client: AzookeyConversionClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AzookeyConversion {
|
impl AzookeyConversion {
|
||||||
|
/// Creates a new AzookeyConversion instance
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `client` - Client instance that performs conversion operations
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * Initialized AzookeyConversion instance
|
||||||
pub fn new(client: AzookeyConversionClient) -> Self {
|
pub fn new(client: AzookeyConversionClient) -> Self {
|
||||||
info!("Creating new AzookeyConversion instance");
|
info!("Creating new AzookeyConversion instance");
|
||||||
|
|
||||||
let instance = Self {
|
Self {
|
||||||
conversion_history: Vec::new(),
|
conversion_history: Vec::new(),
|
||||||
clipboard_history: Vec::new(),
|
input_history: Vec::new(),
|
||||||
now_reconvertion: false,
|
is_reconversion_mode: false,
|
||||||
target_text: String::new(),
|
current_text: String::new(),
|
||||||
reconversion_candidates: None,
|
reconversion_candidates: None,
|
||||||
reconversion_index: None,
|
candidate_index: None,
|
||||||
reconversion_prefix: None,
|
common_prefix: None,
|
||||||
client,
|
client,
|
||||||
};
|
}
|
||||||
instance
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset_conversion_state(&mut self) {
|
|
||||||
debug!("Resetting conversion state");
|
|
||||||
trace!("Before reset - now_reconvertion: {}, reconversion_prefix: {:?}, reconversion_index: {:?}, reconversion_candidates: {:?}",
|
|
||||||
self.now_reconvertion, self.reconversion_prefix, self.reconversion_index, self.reconversion_candidates);
|
|
||||||
self.now_reconvertion = false;
|
|
||||||
self.reconversion_prefix = None;
|
|
||||||
self.reconversion_index = None;
|
|
||||||
self.reconversion_candidates = None;
|
|
||||||
trace!("After reset - now_reconvertion: {}, reconversion_prefix: {:?}, reconversion_index: {:?}, reconversion_candidates: {:?}",
|
|
||||||
self.now_reconvertion, self.reconversion_prefix, self.reconversion_index, self.reconversion_candidates);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_roman_to_kanji(&mut self, text: &str) -> Result<String> {
|
|
||||||
debug!("Converting roman to kanji: {}", text);
|
|
||||||
let o_minus_1 = self
|
|
||||||
.conversion_history
|
|
||||||
.get(if self.conversion_history.len() > 0 {
|
|
||||||
self.conversion_history.len() - 1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
})
|
|
||||||
.unwrap_or(&("".to_string()))
|
|
||||||
.clone();
|
|
||||||
trace!("Previous conversion (o_minus_1): {}", o_minus_1);
|
|
||||||
let mut first_diff_position = o_minus_1
|
|
||||||
.chars()
|
|
||||||
.zip(text.chars())
|
|
||||||
.position(|(a, b)| a != b);
|
|
||||||
|
|
||||||
if o_minus_1 != text && first_diff_position.is_none() {
|
|
||||||
first_diff_position = Some(o_minus_1.chars().count());
|
|
||||||
}
|
|
||||||
trace!("First difference position: {:?}", first_diff_position);
|
|
||||||
let diff = text
|
|
||||||
.chars()
|
|
||||||
.skip(first_diff_position.unwrap_or(0))
|
|
||||||
.collect::<String>();
|
|
||||||
debug!("Difference to convert: {}", diff);
|
|
||||||
|
|
||||||
let prefix = text
|
|
||||||
.chars()
|
|
||||||
.take(first_diff_position.unwrap_or(0))
|
|
||||||
.collect::<String>();
|
|
||||||
trace!("Prefix for conversion: {}", prefix);
|
|
||||||
self.client.reset_composing_text();
|
|
||||||
self.client.insert_at_cursor_position(&diff);
|
|
||||||
trace!("target: {}, prefix: {}", diff, prefix);
|
|
||||||
let converted = self
|
|
||||||
.client
|
|
||||||
.request_candidates("")
|
|
||||||
.first()
|
|
||||||
.unwrap()
|
|
||||||
.text
|
|
||||||
.clone();
|
|
||||||
trace!("Converted difference: {}", converted);
|
|
||||||
self.conversion_history.push(
|
|
||||||
o_minus_1
|
|
||||||
.chars()
|
|
||||||
.zip(text.chars())
|
|
||||||
.take_while(|(a, b)| a == b)
|
|
||||||
.map(|(a, _)| a)
|
|
||||||
.collect::<String>()
|
|
||||||
+ &converted,
|
|
||||||
);
|
|
||||||
self.clipboard_history.push(text.to_string());
|
|
||||||
info!(
|
|
||||||
"Roman to kanji conversion result: {}",
|
|
||||||
self.conversion_history.last().unwrap()
|
|
||||||
);
|
|
||||||
trace!("Updated conversion history: {:?}", self.conversion_history);
|
|
||||||
trace!("Updated clipboard history: {:?}", self.clipboard_history);
|
|
||||||
Ok(self.conversion_history.last().unwrap().clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_tsf(&mut self, text: &str) -> Result<String> {
|
|
||||||
debug!("Converting using TSF: {}", text);
|
|
||||||
self.now_reconvertion = true;
|
|
||||||
let mut diff = String::new();
|
|
||||||
if self.reconversion_prefix.is_none() {
|
|
||||||
let o_minus_2 = self
|
|
||||||
.conversion_history
|
|
||||||
.get(if self.conversion_history.len() > 1 {
|
|
||||||
self.conversion_history.len() - 2
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
})
|
|
||||||
.unwrap_or(&("".to_string()))
|
|
||||||
.clone();
|
|
||||||
let i_minus_1 = self
|
|
||||||
.clipboard_history
|
|
||||||
.get(if self.clipboard_history.len() > 0 {
|
|
||||||
self.clipboard_history.len() - 1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
})
|
|
||||||
.unwrap_or(&("".to_string()))
|
|
||||||
.clone();
|
|
||||||
trace!("o_minus_2: {}, i_minus_1: {}", o_minus_2, i_minus_1);
|
|
||||||
let mut first_diff_position = i_minus_1
|
|
||||||
.chars()
|
|
||||||
.zip(o_minus_2.chars())
|
|
||||||
.position(|(a, b)| a != b);
|
|
||||||
trace!("First difference position: {:?}", first_diff_position);
|
|
||||||
if o_minus_2 != i_minus_1 && first_diff_position.is_none() {
|
|
||||||
first_diff_position = Some(o_minus_2.chars().count());
|
|
||||||
}
|
|
||||||
diff = i_minus_1
|
|
||||||
.chars()
|
|
||||||
.skip(first_diff_position.unwrap_or(0))
|
|
||||||
.collect::<String>();
|
|
||||||
debug!("Difference to convert: {}", diff);
|
|
||||||
let prefix = i_minus_1
|
|
||||||
.chars()
|
|
||||||
.zip(o_minus_2.chars())
|
|
||||||
.take_while(|(a, b)| a == b)
|
|
||||||
.map(|(a, _)| a)
|
|
||||||
.collect::<String>();
|
|
||||||
self.reconversion_prefix = Some(prefix.clone());
|
|
||||||
trace!("Set reconversion prefix: {:?}", self.reconversion_prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
let candidates = self.reconversion_candidates.get_or_insert_with(|| {
|
|
||||||
debug!("Generating new candidates");
|
|
||||||
self.client.reset_composing_text();
|
|
||||||
self.client.insert_at_cursor_position(&diff);
|
|
||||||
let prefix = self.reconversion_prefix.clone().unwrap_or_default();
|
|
||||||
let mut candidates = self
|
|
||||||
.client
|
|
||||||
.request_candidates(&prefix)
|
|
||||||
.iter()
|
|
||||||
.map(|c| c.text.clone())
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
trace!("Initial candidates: {:?}", candidates);
|
|
||||||
candidates.insert(0, diff.to_string());
|
|
||||||
trace!("Final candidates: {:?}", candidates);
|
|
||||||
candidates
|
|
||||||
});
|
|
||||||
|
|
||||||
let index = self.reconversion_index.get_or_insert(-1);
|
|
||||||
trace!("Current reconversion index: {}", index);
|
|
||||||
|
|
||||||
if *index + 1 < candidates.len() as i32 {
|
|
||||||
*index += 1;
|
|
||||||
} else {
|
|
||||||
*index = 0;
|
|
||||||
}
|
|
||||||
debug!("Updated reconversion index: {}", index);
|
|
||||||
|
|
||||||
self.conversion_history.push(
|
|
||||||
self.reconversion_prefix.clone().unwrap()
|
|
||||||
+ &self.reconversion_candidates.as_ref().unwrap()
|
|
||||||
[self.reconversion_index.unwrap() as usize]
|
|
||||||
.clone(),
|
|
||||||
);
|
|
||||||
self.clipboard_history.push(text.to_string());
|
|
||||||
trace!("Updated conversion history: {:?}", self.conversion_history);
|
|
||||||
trace!("Updated clipboard history: {:?}", self.clipboard_history);
|
|
||||||
|
|
||||||
while self.conversion_history.len() > 3 {
|
|
||||||
self.conversion_history.remove(0);
|
|
||||||
}
|
|
||||||
while self.clipboard_history.len() > 3 {
|
|
||||||
self.clipboard_history.remove(0);
|
|
||||||
}
|
|
||||||
trace!("Trimmed conversion history: {:?}", self.conversion_history);
|
|
||||||
trace!("Trimmed clipboard history: {:?}", self.clipboard_history);
|
|
||||||
|
|
||||||
info!(
|
|
||||||
"TSF conversion result: {}",
|
|
||||||
self.conversion_history.last().unwrap()
|
|
||||||
);
|
|
||||||
Ok(self.conversion_history.last().unwrap().clone())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts text - Main entry point for conversion processing
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `text` - Text to be converted
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Result<String>` - Conversion result or error
|
||||||
pub fn convert(&mut self, text: &str) -> Result<String> {
|
pub fn convert(&mut self, text: &str) -> Result<String> {
|
||||||
debug!("Starting conversion for: {}", text);
|
debug!("Starting conversion: {}", text);
|
||||||
trace!("Current conversion history: {:?}", self.conversion_history);
|
trace!(
|
||||||
trace!("Current clipboard history: {:?}", self.clipboard_history);
|
"Current state: conversion_history={:?}, input_history={:?}, is_reconversion_mode={}",
|
||||||
let same_as_last_conversion = text.to_string()
|
self.conversion_history,
|
||||||
== self
|
self.input_history,
|
||||||
.conversion_history
|
self.is_reconversion_mode
|
||||||
.last()
|
);
|
||||||
.unwrap_or(&("".to_string()))
|
|
||||||
.clone();
|
self.current_text = text.to_string();
|
||||||
|
|
||||||
|
// Check if same as previous conversion result
|
||||||
|
let same_as_last_conversion = self.is_same_as_last_conversion(text);
|
||||||
trace!("Same as last conversion: {}", same_as_last_conversion);
|
trace!("Same as last conversion: {}", same_as_last_conversion);
|
||||||
|
|
||||||
self.target_text = text.to_string();
|
// Reset if input changed while in reconversion mode
|
||||||
trace!("Set target text: {}", self.target_text);
|
if !same_as_last_conversion && self.is_reconversion_mode {
|
||||||
|
|
||||||
if !same_as_last_conversion && self.now_reconvertion {
|
|
||||||
debug!("Resetting conversion state due to new input");
|
debug!("Resetting conversion state due to new input");
|
||||||
self.reset_conversion_state();
|
self.reset_reconversion_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.now_reconvertion && !same_as_last_conversion {
|
// Branch conversion processing
|
||||||
info!("Converting using roman_to_kanji");
|
if self.is_reconversion_mode || same_as_last_conversion {
|
||||||
return self.convert_roman_to_kanji(text);
|
// Same text re-entered or in reconversion mode
|
||||||
|
info!("Executing AzooKey conversion");
|
||||||
|
self.convert_with_candidates(text)
|
||||||
|
} else {
|
||||||
|
// Normal conversion processing
|
||||||
|
info!("Executing regular romaji->kanji conversion");
|
||||||
|
self.convert_roman_to_kanji(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines if input is the same as the last conversion result
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `text` - Text to check
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `bool` - True if same as last conversion
|
||||||
|
fn is_same_as_last_conversion(&self, text: &str) -> bool {
|
||||||
|
if let Some(last_conversion) = self.conversion_history.last() {
|
||||||
|
text == last_conversion
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resets reconversion-related state
|
||||||
|
fn reset_reconversion_state(&mut self) {
|
||||||
|
debug!("Resetting reconversion state");
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"Before reset - is_reconversion_mode: {}, common_prefix: {:?}, candidate_index: {:?}",
|
||||||
|
self.is_reconversion_mode,
|
||||||
|
self.common_prefix,
|
||||||
|
self.candidate_index
|
||||||
|
);
|
||||||
|
|
||||||
|
self.is_reconversion_mode = false;
|
||||||
|
self.common_prefix = None;
|
||||||
|
self.candidate_index = None;
|
||||||
|
self.reconversion_candidates = None;
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"After reset - is_reconversion_mode: {}, common_prefix: {:?}, candidate_index: {:?}",
|
||||||
|
self.is_reconversion_mode,
|
||||||
|
self.common_prefix,
|
||||||
|
self.candidate_index
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts romaji to kanji (initial conversion)
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `text` - Text to convert
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Result<String>` - Conversion result or error
|
||||||
|
fn convert_roman_to_kanji(&mut self, text: &str) -> Result<String> {
|
||||||
|
debug!("Converting romaji to kanji: {}", text);
|
||||||
|
|
||||||
|
// Get previous conversion result
|
||||||
|
let previous_conversion = self.get_previous_conversion();
|
||||||
|
trace!("Previous conversion: {}", previous_conversion);
|
||||||
|
|
||||||
|
// Detect difference position
|
||||||
|
let first_diff_position = self.find_first_difference(&previous_conversion, text);
|
||||||
|
trace!("First difference position: {:?}", first_diff_position);
|
||||||
|
|
||||||
|
// Extract difference text
|
||||||
|
let diff_text = text.chars().skip(first_diff_position).collect::<String>();
|
||||||
|
debug!("Difference to convert: {}", diff_text);
|
||||||
|
|
||||||
|
// Extract common prefix
|
||||||
|
let prefix = text.chars().take(first_diff_position).collect::<String>();
|
||||||
|
trace!("Conversion prefix: {}", prefix);
|
||||||
|
|
||||||
|
// Use client for conversion
|
||||||
|
self.client.reset_composing_text();
|
||||||
|
self.client.insert_at_cursor_position(&diff_text);
|
||||||
|
|
||||||
|
// Get and select conversion candidate
|
||||||
|
let converted = match self.client.request_candidates("").first() {
|
||||||
|
Some(candidate) => candidate.text.clone(),
|
||||||
|
None => return Err(anyhow!("No conversion candidates available")),
|
||||||
|
};
|
||||||
|
trace!("Conversion result: {}", converted);
|
||||||
|
|
||||||
|
// Update history
|
||||||
|
let result = prefix + &converted;
|
||||||
|
self.update_history(result.clone(), text.to_string());
|
||||||
|
|
||||||
|
info!("Romaji->kanji conversion result: {}", result);
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Switches between conversion candidates (reconversion)
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `text` - Text for reconversion
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Result<String>` - Conversion result or error
|
||||||
|
fn convert_with_candidates(&mut self, text: &str) -> Result<String> {
|
||||||
|
debug!("Converting with AzooKey: {}", text);
|
||||||
|
self.is_reconversion_mode = true;
|
||||||
|
|
||||||
|
// Prepare for reconversion if needed
|
||||||
|
self.prepare_reconversion_if_needed();
|
||||||
|
|
||||||
|
// Select and switch candidates
|
||||||
|
let result = self.select_next_candidate()?;
|
||||||
|
|
||||||
|
// Update history
|
||||||
|
self.update_history(result.clone(), text.to_string());
|
||||||
|
|
||||||
|
info!("AzooKey conversion result: {}", result);
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares difference processing for reconversion
|
||||||
|
fn prepare_reconversion_if_needed(&mut self) {
|
||||||
|
// Only execute on first reconversion
|
||||||
|
if self.common_prefix.is_none() {
|
||||||
|
debug!("Preparing for reconversion");
|
||||||
|
|
||||||
|
// Calculate differences from past history
|
||||||
|
let previous_output = self.get_previous_output(2);
|
||||||
|
let previous_input = self.get_previous_input(1);
|
||||||
|
trace!(
|
||||||
|
"Previous output: {}, previous input: {}",
|
||||||
|
previous_output,
|
||||||
|
previous_input
|
||||||
|
);
|
||||||
|
|
||||||
|
// Detect difference position
|
||||||
|
let first_diff_position = self.find_first_difference(&previous_input, &previous_output);
|
||||||
|
trace!("First difference position: {:?}", first_diff_position);
|
||||||
|
|
||||||
|
// Extract difference text
|
||||||
|
let diff_text = previous_input
|
||||||
|
.chars()
|
||||||
|
.skip(first_diff_position)
|
||||||
|
.collect::<String>();
|
||||||
|
debug!("Reconversion difference: {}", diff_text);
|
||||||
|
|
||||||
|
// Set common prefix
|
||||||
|
let prefix = previous_input
|
||||||
|
.chars()
|
||||||
|
.take(first_diff_position)
|
||||||
|
.collect::<String>();
|
||||||
|
self.common_prefix = Some(prefix.clone());
|
||||||
|
trace!("Set reconversion prefix: {}", prefix);
|
||||||
|
|
||||||
|
// Generate candidates
|
||||||
|
self.generate_candidates(&diff_text, &prefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates conversion candidates
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `diff_text` - Difference text to convert
|
||||||
|
/// * `prefix` - Common prefix
|
||||||
|
fn generate_candidates(&mut self, diff_text: &str, prefix: &str) {
|
||||||
|
debug!("Generating candidates");
|
||||||
|
|
||||||
|
self.client.reset_composing_text();
|
||||||
|
self.client.insert_at_cursor_position(diff_text);
|
||||||
|
|
||||||
|
// Get candidates from client
|
||||||
|
let mut candidates = self
|
||||||
|
.client
|
||||||
|
.request_candidates(prefix)
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.text.clone())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
trace!("Retrieved candidates: {:?}", candidates);
|
||||||
|
|
||||||
|
// Include raw text in candidates
|
||||||
|
candidates.insert(0, diff_text.to_string());
|
||||||
|
|
||||||
|
// Limit number of candidates (for safety)
|
||||||
|
if candidates.len() > MAX_CANDIDATES {
|
||||||
|
candidates.truncate(MAX_CANDIDATES);
|
||||||
}
|
}
|
||||||
|
|
||||||
if same_as_last_conversion || self.now_reconvertion {
|
trace!("Final candidate list: {:?}", candidates);
|
||||||
info!("Converting using TSF");
|
self.reconversion_candidates = Some(candidates);
|
||||||
return self.convert_tsf(text);
|
self.candidate_index = Some(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Selects the next candidate
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Result<String>` - Selected candidate or error
|
||||||
|
fn select_next_candidate(&mut self) -> Result<String> {
|
||||||
|
let candidates = match &self.reconversion_candidates {
|
||||||
|
Some(cands) => cands,
|
||||||
|
None => return Err(anyhow!("Candidate list does not exist")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let index = match self.candidate_index {
|
||||||
|
Some(i) => {
|
||||||
|
// Update index
|
||||||
|
let new_index = if i + 1 < candidates.len() { i + 1 } else { 0 };
|
||||||
|
self.candidate_index = Some(new_index);
|
||||||
|
new_index
|
||||||
|
}
|
||||||
|
None => return Err(anyhow!("Candidate index not initialized")),
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("Updated candidate index: {}", index);
|
||||||
|
|
||||||
|
// Get selected candidate
|
||||||
|
let prefix = self.common_prefix.clone().unwrap_or_default();
|
||||||
|
let selected_candidate = &candidates[index];
|
||||||
|
let result = prefix + selected_candidate;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates history
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `conversion` - Conversion result
|
||||||
|
/// * `input` - Input text
|
||||||
|
fn update_history(&mut self, conversion: String, input: String) {
|
||||||
|
self.conversion_history.push(conversion);
|
||||||
|
self.input_history.push(input);
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"Before trim: conversion_history={}, input_history={}",
|
||||||
|
self.conversion_history.len(),
|
||||||
|
self.input_history.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Limit history size
|
||||||
|
self.trim_history();
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"After trim: conversion_history={}, input_history={}",
|
||||||
|
self.conversion_history.len(),
|
||||||
|
self.input_history.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Limits history size
|
||||||
|
fn trim_history(&mut self) {
|
||||||
|
while self.conversion_history.len() > MAX_HISTORY_SIZE {
|
||||||
|
self.conversion_history.remove(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
error!("Failed to convert: {}", text);
|
while self.input_history.len() > MAX_HISTORY_SIZE {
|
||||||
Err(anyhow::anyhow!("Failed to convert"))
|
self.input_history.remove(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets previous conversion result
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `String` - Previous conversion result or empty string
|
||||||
|
fn get_previous_conversion(&self) -> String {
|
||||||
|
self.conversion_history
|
||||||
|
.last()
|
||||||
|
.map(|s| s.clone())
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets conversion history from specified index
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `offset` - Offset from the end
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `String` - Retrieved history or empty string
|
||||||
|
fn get_previous_output(&self, offset: usize) -> String {
|
||||||
|
if offset <= self.conversion_history.len() {
|
||||||
|
self.conversion_history[self.conversion_history.len() - offset].clone()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets input history from specified index
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `offset` - Offset from the end
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `String` - Retrieved history or empty string
|
||||||
|
fn get_previous_input(&self, offset: usize) -> String {
|
||||||
|
if offset <= self.input_history.len() {
|
||||||
|
self.input_history[self.input_history.len() - offset].clone()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds first difference position between two strings
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `s1` - First string to compare
|
||||||
|
/// * `s2` - Second string to compare
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `usize` - First difference position
|
||||||
|
fn find_first_difference(&self, s1: &str, s2: &str) -> usize {
|
||||||
|
let result = s1
|
||||||
|
.chars()
|
||||||
|
.zip(s2.chars())
|
||||||
|
.position(|(a, b)| a != b)
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
// If one is a prefix of the other
|
||||||
|
let min_len = s1.chars().count().min(s2.chars().count());
|
||||||
|
if s1.len() != s2.len() {
|
||||||
|
min_len
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"String comparison: \"{}\" and \"{}\" differ at position: {}",
|
||||||
|
s1,
|
||||||
|
s2,
|
||||||
|
result
|
||||||
|
);
|
||||||
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user