refactor: reduce nesting with early returns and guard clauses

Применены паттерны упрощения вложенности:
- handle_profile_mode: упрощён блок Enter с let-else
- handle_profile_open: применён early return guard
- handle_enter_key: разделена на 3 функции + early returns
  - edit_message() - редактирование сообщения
  - send_new_message() - отправка нового сообщения
  - Сокращено с ~130 до ~40 строк
- handle_message_search_mode: извлечена функция perform_message_search()
  - Упрощены блоки Backspace и Char с let-else

Результат: код стал более линейным, уменьшена глубина вложенности с 6+ до 2-3 уровней

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Kilin
2026-02-03 20:34:19 +03:00
parent 7e372bffef
commit 67fd7506b3

View File

@@ -73,59 +73,61 @@ async fn handle_profile_mode<T: TdClientTrait>(app: &mut App<T>, key: KeyEvent)
} }
KeyCode::Enter => { KeyCode::Enter => {
// Выполнить выбранное действие // Выполнить выбранное действие
if let Some(profile) = app.get_profile_info() { let Some(profile) = app.get_profile_info() else {
let actions = get_available_actions_count(profile); return;
let action_index = app.get_selected_profile_action().unwrap_or(0); };
if action_index < actions { let actions = get_available_actions_count(profile);
// Определяем какое действие выбрано let action_index = app.get_selected_profile_action().unwrap_or(0);
let mut current_idx = 0;
// Действие: Открыть в браузере // Guard: проверяем, что индекс действия валидный
if profile.username.is_some() { if action_index >= actions {
if action_index == current_idx { return;
if let Some(username) = &profile.username { }
let url = format!(
"https://t.me/{}", // Определяем какое действие выбрано
username.trim_start_matches('@') let mut current_idx = 0;
);
#[cfg(feature = "url-open")] // Действие: Открыть в браузере
{ if let Some(username) = &profile.username {
match open::that(&url) { if action_index == current_idx {
Ok(_) => { let url = format!(
app.status_message = Some(format!("Открыто: {}", url)); "https://t.me/{}",
} username.trim_start_matches('@')
Err(e) => { );
app.error_message = #[cfg(feature = "url-open")]
Some(format!("Ошибка открытия браузера: {}", e)); {
} match open::that(&url) {
} Ok(_) => {
} app.status_message = Some(format!("Открыто: {}", url));
#[cfg(not(feature = "url-open"))] }
{ Err(e) => {
app.error_message = Some( app.error_message =
"Открытие URL недоступно (требуется feature 'url-open')".to_string() Some(format!("Ошибка открытия браузера: {}", e));
);
}
} }
return;
} }
current_idx += 1;
} }
#[cfg(not(feature = "url-open"))]
// Действие: Скопировать ID {
if action_index == current_idx { app.error_message = Some(
app.status_message = "Открытие URL недоступно (требуется feature 'url-open')".to_string()
Some(format!("ID скопирован: {}", profile.chat_id)); );
return;
}
current_idx += 1;
// Действие: Покинуть группу
if profile.is_group && action_index == current_idx {
app.show_leave_group_confirmation();
} }
return;
} }
current_idx += 1;
}
// Действие: Скопировать ID
if action_index == current_idx {
app.status_message = Some(format!("ID скопирован: {}", profile.chat_id));
return;
}
current_idx += 1;
// Действие: Покинуть группу
if profile.is_group && action_index == current_idx {
app.show_leave_group_confirmation();
} }
} }
_ => {} _ => {}
@@ -136,23 +138,25 @@ async fn handle_profile_mode<T: TdClientTrait>(app: &mut App<T>, key: KeyEvent)
/// ///
/// Загружает информацию о профиле и переключает в режим просмотра профиля /// Загружает информацию о профиле и переключает в режим просмотра профиля
async fn handle_profile_open<T: TdClientTrait>(app: &mut App<T>) { async fn handle_profile_open<T: TdClientTrait>(app: &mut App<T>) {
if let Some(chat_id) = app.selected_chat_id { let Some(chat_id) = app.selected_chat_id else {
app.status_message = Some("Загрузка профиля...".to_string()); return;
match with_timeout_msg( };
Duration::from_secs(5),
app.td_client.get_profile_info(chat_id), app.status_message = Some("Загрузка профиля...".to_string());
"Таймаут загрузки профиля", match with_timeout_msg(
) Duration::from_secs(5),
.await app.td_client.get_profile_info(chat_id),
{ "Таймаут загрузки профиля",
Ok(profile) => { )
app.enter_profile_mode(profile); .await
app.status_message = None; {
} Ok(profile) => {
Err(e) => { app.enter_profile_mode(profile);
app.error_message = Some(e); app.status_message = None;
app.status_message = None; }
} Err(e) => {
app.error_message = Some(e);
app.status_message = None;
} }
} }
} }
@@ -284,6 +288,105 @@ async fn handle_escape_key<T: TdClientTrait>(app: &mut App<T>) {
} }
} }
/// Редактирование существующего сообщения
async fn edit_message<T: TdClientTrait>(app: &mut App<T>, chat_id: i64, msg_id: MessageId, text: String) {
// Проверяем, что сообщение есть в локальном кэше
let msg_exists = app.td_client.current_chat_messages()
.iter()
.any(|m| m.id() == msg_id);
if !msg_exists {
app.error_message = Some(format!(
"Сообщение {} не найдено в кэше чата {}",
msg_id.as_i64(), chat_id
));
app.chat_state = crate::app::ChatState::Normal;
app.message_input.clear();
app.cursor_position = 0;
return;
}
match with_timeout_msg(
Duration::from_secs(5),
app.td_client.edit_message(ChatId::new(chat_id), msg_id, text),
"Таймаут редактирования",
)
.await
{
Ok(mut edited_msg) => {
// Сохраняем reply_to из старого сообщения (если есть)
let messages = app.td_client.current_chat_messages_mut();
if let Some(pos) = messages.iter().position(|m| m.id() == msg_id) {
let old_reply_to = messages[pos].interactions.reply_to.clone();
// Если в старом сообщении был reply и в новом он "Unknown" - сохраняем старый
if let Some(old_reply) = old_reply_to {
if edited_msg.interactions.reply_to.as_ref()
.map_or(true, |r| r.sender_name == "Unknown") {
edited_msg.interactions.reply_to = Some(old_reply);
}
}
// Заменяем сообщение
messages[pos] = edited_msg;
}
// Очищаем инпут и сбрасываем состояние ПОСЛЕ успешного редактирования
app.message_input.clear();
app.cursor_position = 0;
app.chat_state = crate::app::ChatState::Normal;
app.needs_redraw = true;
}
Err(e) => {
app.error_message = Some(e);
}
}
}
/// Отправка нового сообщения (с опциональным reply)
async fn send_new_message<T: TdClientTrait>(app: &mut App<T>, chat_id: i64, text: String) {
let reply_to_id = if app.is_replying() {
app.chat_state.selected_message_id()
} else {
None
};
// Создаём ReplyInfo ДО отправки, пока сообщение точно доступно
let reply_info = app.get_replying_to_message().map(|m| {
crate::tdlib::ReplyInfo {
message_id: m.id(),
sender_name: m.sender_name().to_string(),
text: m.text().to_string(),
}
});
app.message_input.clear();
app.cursor_position = 0;
// Сбрасываем режим reply если он был активен
if app.is_replying() {
app.chat_state = crate::app::ChatState::Normal;
}
app.last_typing_sent = None;
// Отменяем typing status
app.td_client.send_chat_action(ChatId::new(chat_id), ChatAction::Cancel).await;
match with_timeout_msg(
Duration::from_secs(5),
app.td_client.send_message(ChatId::new(chat_id), text, reply_to_id, reply_info),
"Таймаут отправки",
)
.await
{
Ok(sent_msg) => {
// Добавляем отправленное сообщение в список (с лимитом)
app.td_client.push_message(sent_msg);
// Сбрасываем скролл чтобы видеть новое сообщение
app.message_scroll_offset = 0;
}
Err(e) => {
app.error_message = Some(e);
}
}
}
/// Обработка клавиши Enter /// Обработка клавиши Enter
/// ///
/// Обрабатывает три сценария: /// Обрабатывает три сценария:
@@ -291,125 +394,8 @@ async fn handle_escape_key<T: TdClientTrait>(app: &mut App<T>) {
/// 2. В открытом чате: отправить новое или редактировать существующее сообщение /// 2. В открытом чате: отправить новое или редактировать существующее сообщение
/// 3. В списке чатов: открыть выбранный чат /// 3. В списке чатов: открыть выбранный чат
async fn handle_enter_key<T: TdClientTrait>(app: &mut App<T>) { async fn handle_enter_key<T: TdClientTrait>(app: &mut App<T>) {
if app.selected_chat_id.is_some() { // Сценарий 1: Открытие чата из списка
// Режим выбора сообщения if app.selected_chat_id.is_none() {
if app.is_selecting_message() {
// Начать редактирование выбранного сообщения
if app.start_editing_selected() {
// Редактирование начато
} else {
// Нельзя редактировать это сообщение
app.chat_state = crate::app::ChatState::Normal;
}
return;
}
// Отправка или редактирование сообщения
if is_non_empty(&app.message_input) {
if let Some(chat_id) = app.get_selected_chat_id() {
let text = app.message_input.clone();
if app.is_editing() {
// Режим редактирования
if let Some(msg_id) = app.chat_state.selected_message_id() {
// Проверяем, что сообщение есть в локальном кэше
let msg_exists = app.td_client.current_chat_messages()
.iter()
.any(|m| m.id() == msg_id);
if !msg_exists {
app.error_message = Some(format!(
"Сообщение {} не найдено в кэше чата {}",
msg_id.as_i64(), chat_id
));
app.chat_state = crate::app::ChatState::Normal;
app.message_input.clear();
app.cursor_position = 0;
return;
}
match with_timeout_msg(
Duration::from_secs(5),
app.td_client.edit_message(ChatId::new(chat_id), msg_id, text),
"Таймаут редактирования",
)
.await
{
Ok(mut edited_msg) => {
// Сохраняем reply_to из старого сообщения (если есть)
let messages = app.td_client.current_chat_messages_mut();
if let Some(pos) = messages.iter().position(|m| m.id() == msg_id) {
let old_reply_to = messages[pos].interactions.reply_to.clone();
// Если в старом сообщении был reply и в новом он "Unknown" - сохраняем старый
if let Some(old_reply) = old_reply_to {
if edited_msg.interactions.reply_to.as_ref()
.map_or(true, |r| r.sender_name == "Unknown") {
edited_msg.interactions.reply_to = Some(old_reply);
}
}
// Заменяем сообщение
messages[pos] = edited_msg;
}
// Очищаем инпут и сбрасываем состояние ПОСЛЕ успешного редактирования
app.message_input.clear();
app.cursor_position = 0;
app.chat_state = crate::app::ChatState::Normal;
app.needs_redraw = true; // ВАЖНО: перерисовываем UI
}
Err(e) => {
app.error_message = Some(e);
}
}
}
} else {
// Обычная отправка (или reply)
let reply_to_id = if app.is_replying() {
app.chat_state.selected_message_id()
} else {
None
};
// Создаём ReplyInfo ДО отправки, пока сообщение точно доступно
let reply_info = app.get_replying_to_message().map(|m| {
crate::tdlib::ReplyInfo {
message_id: m.id(),
sender_name: m.sender_name().to_string(),
text: m.text().to_string(),
}
});
app.message_input.clear();
app.cursor_position = 0;
// Сбрасываем режим reply если он был активен
if app.is_replying() {
app.chat_state = crate::app::ChatState::Normal;
}
app.last_typing_sent = None;
// Отменяем typing status
app.td_client.send_chat_action(ChatId::new(chat_id), ChatAction::Cancel).await;
match with_timeout_msg(
Duration::from_secs(5),
app.td_client
.send_message(ChatId::new(chat_id), text, reply_to_id, reply_info),
"Таймаут отправки",
)
.await
{
Ok(sent_msg) => {
// Добавляем отправленное сообщение в список (с лимитом)
app.td_client.push_message(sent_msg);
// Сбрасываем скролл чтобы видеть новое сообщение
app.message_scroll_offset = 0;
}
Err(e) => {
app.error_message = Some(e);
}
}
}
}
}
} else {
// Открываем чат
let prev_selected = app.selected_chat_id; let prev_selected = app.selected_chat_id;
app.select_current_chat(); app.select_current_chat();
@@ -418,6 +404,37 @@ async fn handle_enter_key<T: TdClientTrait>(app: &mut App<T>) {
open_chat_and_load_data(app, chat_id).await; open_chat_and_load_data(app, chat_id).await;
} }
} }
return;
}
// Сценарий 2: Режим выбора сообщения - начать редактирование
if app.is_selecting_message() {
if !app.start_editing_selected() {
// Нельзя редактировать это сообщение
app.chat_state = crate::app::ChatState::Normal;
}
return;
}
// Сценарий 3: Отправка или редактирование сообщения
if !is_non_empty(&app.message_input) {
return;
}
let Some(chat_id) = app.get_selected_chat_id() else {
return;
};
let text = app.message_input.clone();
if app.is_editing() {
// Редактирование существующего сообщения
if let Some(msg_id) = app.chat_state.selected_message_id() {
edit_message(app, chat_id, msg_id, text).await;
}
} else {
// Отправка нового сообщения
send_new_message(app, chat_id, text).await;
} }
} }
@@ -698,6 +715,27 @@ async fn handle_pinned_mode<T: TdClientTrait>(app: &mut App<T>, key: KeyEvent) {
} }
} }
/// Выполняет поиск по сообщениям с обновлением результатов
async fn perform_message_search<T: TdClientTrait>(app: &mut App<T>, query: &str) {
let Some(chat_id) = app.get_selected_chat_id() else {
return;
};
if query.is_empty() {
app.set_search_results(Vec::new());
return;
}
if let Ok(results) = with_timeout(
Duration::from_secs(3),
app.td_client.search_messages(ChatId::new(chat_id), query),
)
.await
{
app.set_search_results(results);
}
}
/// Обработка режима поиска по сообщениям в открытом чате /// Обработка режима поиска по сообщениям в открытом чате
/// ///
/// Обрабатывает: /// Обрабатывает:
@@ -735,43 +773,21 @@ async fn handle_message_search_mode<T: TdClientTrait>(app: &mut App<T>, key: Key
} }
KeyCode::Backspace => { KeyCode::Backspace => {
// Удаляем символ из запроса // Удаляем символ из запроса
if let Some(mut query) = app.get_search_query().map(|s| s.to_string()) { let Some(mut query) = app.get_search_query().map(|s| s.to_string()) else {
query.pop(); return;
app.update_search_query(query.clone()); };
// Выполняем поиск при изменении запроса query.pop();
if let Some(chat_id) = app.get_selected_chat_id() { app.update_search_query(query.clone());
if !query.is_empty() { perform_message_search(app, &query).await;
if let Ok(results) = with_timeout(
Duration::from_secs(3),
app.td_client.search_messages(ChatId::new(chat_id), &query),
)
.await
{
app.set_search_results(results);
}
} else {
app.set_search_results(Vec::new());
}
}
}
} }
KeyCode::Char(c) => { KeyCode::Char(c) => {
// Добавляем символ к запросу // Добавляем символ к запросу
if let Some(mut query) = app.get_search_query().map(|s| s.to_string()) { let Some(mut query) = app.get_search_query().map(|s| s.to_string()) else {
query.push(c); return;
app.update_search_query(query.clone()); };
// Выполняем поиск при изменении запроса query.push(c);
if let Some(chat_id) = app.get_selected_chat_id() { app.update_search_query(query.clone());
if let Ok(results) = with_timeout( perform_message_search(app, &query).await;
Duration::from_secs(3),
app.td_client.search_messages(ChatId::new(chat_id), &query),
)
.await
{
app.set_search_results(results);
}
}
}
} }
_ => {} _ => {}
} }