From 67fd7506b3401fe9053175aecabaedca4476ce31 Mon Sep 17 00:00:00 2001 From: Mikhail Kilin Date: Tue, 3 Feb 2026 20:34:19 +0300 Subject: [PATCH] refactor: reduce nesting with early returns and guard clauses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Применены паттерны упрощения вложенности: - 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 --- src/input/main_input.rs | 448 +++++++++++++++++++++------------------- 1 file changed, 232 insertions(+), 216 deletions(-) diff --git a/src/input/main_input.rs b/src/input/main_input.rs index 3cc3935..32d6b1c 100644 --- a/src/input/main_input.rs +++ b/src/input/main_input.rs @@ -73,59 +73,61 @@ async fn handle_profile_mode(app: &mut App, key: KeyEvent) } KeyCode::Enter => { // Выполнить выбранное действие - if let Some(profile) = app.get_profile_info() { - let actions = get_available_actions_count(profile); - let action_index = app.get_selected_profile_action().unwrap_or(0); + let Some(profile) = app.get_profile_info() else { + return; + }; - if action_index < actions { - // Определяем какое действие выбрано - let mut current_idx = 0; + let actions = get_available_actions_count(profile); + let action_index = app.get_selected_profile_action().unwrap_or(0); - // Действие: Открыть в браузере - if profile.username.is_some() { - if action_index == current_idx { - if let Some(username) = &profile.username { - let url = format!( - "https://t.me/{}", - username.trim_start_matches('@') - ); - #[cfg(feature = "url-open")] - { - match open::that(&url) { - Ok(_) => { - app.status_message = Some(format!("Открыто: {}", url)); - } - Err(e) => { - app.error_message = - Some(format!("Ошибка открытия браузера: {}", e)); - } - } - } - #[cfg(not(feature = "url-open"))] - { - app.error_message = Some( - "Открытие URL недоступно (требуется feature 'url-open')".to_string() - ); - } + // Guard: проверяем, что индекс действия валидный + if action_index >= actions { + return; + } + + // Определяем какое действие выбрано + let mut current_idx = 0; + + // Действие: Открыть в браузере + if let Some(username) = &profile.username { + if action_index == current_idx { + let url = format!( + "https://t.me/{}", + username.trim_start_matches('@') + ); + #[cfg(feature = "url-open")] + { + match open::that(&url) { + Ok(_) => { + app.status_message = Some(format!("Открыто: {}", url)); + } + Err(e) => { + app.error_message = + Some(format!("Ошибка открытия браузера: {}", e)); } - 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(); + #[cfg(not(feature = "url-open"))] + { + app.error_message = Some( + "Открытие URL недоступно (требуется feature 'url-open')".to_string() + ); } + 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(app: &mut App, key: KeyEvent) /// /// Загружает информацию о профиле и переключает в режим просмотра профиля async fn handle_profile_open(app: &mut App) { - if let Some(chat_id) = app.selected_chat_id { - app.status_message = Some("Загрузка профиля...".to_string()); - match with_timeout_msg( - Duration::from_secs(5), - app.td_client.get_profile_info(chat_id), - "Таймаут загрузки профиля", - ) - .await - { - Ok(profile) => { - app.enter_profile_mode(profile); - app.status_message = None; - } - Err(e) => { - app.error_message = Some(e); - app.status_message = None; - } + let Some(chat_id) = app.selected_chat_id else { + return; + }; + + app.status_message = Some("Загрузка профиля...".to_string()); + match with_timeout_msg( + Duration::from_secs(5), + app.td_client.get_profile_info(chat_id), + "Таймаут загрузки профиля", + ) + .await + { + Ok(profile) => { + app.enter_profile_mode(profile); + app.status_message = None; + } + Err(e) => { + app.error_message = Some(e); + app.status_message = None; } } } @@ -284,6 +288,105 @@ async fn handle_escape_key(app: &mut App) { } } +/// Редактирование существующего сообщения +async fn edit_message(app: &mut App, 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(app: &mut App, 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 /// /// Обрабатывает три сценария: @@ -291,125 +394,8 @@ async fn handle_escape_key(app: &mut App) { /// 2. В открытом чате: отправить новое или редактировать существующее сообщение /// 3. В списке чатов: открыть выбранный чат async fn handle_enter_key(app: &mut App) { - if app.selected_chat_id.is_some() { - // Режим выбора сообщения - 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 { - // Открываем чат + // Сценарий 1: Открытие чата из списка + if app.selected_chat_id.is_none() { let prev_selected = app.selected_chat_id; app.select_current_chat(); @@ -418,6 +404,37 @@ async fn handle_enter_key(app: &mut App) { 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(app: &mut App, key: KeyEvent) { } } +/// Выполняет поиск по сообщениям с обновлением результатов +async fn perform_message_search(app: &mut App, 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(app: &mut App, key: Key } KeyCode::Backspace => { // Удаляем символ из запроса - if let Some(mut query) = app.get_search_query().map(|s| s.to_string()) { - query.pop(); - app.update_search_query(query.clone()); - // Выполняем поиск при изменении запроса - if let Some(chat_id) = app.get_selected_chat_id() { - if !query.is_empty() { - 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()); - } - } - } + let Some(mut query) = app.get_search_query().map(|s| s.to_string()) else { + return; + }; + query.pop(); + app.update_search_query(query.clone()); + perform_message_search(app, &query).await; } KeyCode::Char(c) => { // Добавляем символ к запросу - if let Some(mut query) = app.get_search_query().map(|s| s.to_string()) { - query.push(c); - app.update_search_query(query.clone()); - // Выполняем поиск при изменении запроса - if let Some(chat_id) = app.get_selected_chat_id() { - 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); - } - } - } + let Some(mut query) = app.get_search_query().map(|s| s.to_string()) else { + return; + }; + query.push(c); + app.update_search_query(query.clone()); + perform_message_search(app, &query).await; } _ => {} }