refactoring

This commit is contained in:
mii443
2025-05-13 22:53:47 +09:00
parent a1a1c1ab06
commit 9f8c133d70

View File

@ -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
} }
} }