fix: resolve all 23 clippy warnings
Some checks failed
ci/woodpecker/pr/check Pipeline failed
CI / Check (pull_request) Has been cancelled
CI / Format (pull_request) Has been cancelled
CI / Clippy (pull_request) Has been cancelled
CI / Build (macos-latest) (pull_request) Has been cancelled
CI / Build (ubuntu-latest) (pull_request) Has been cancelled
CI / Build (windows-latest) (pull_request) Has been cancelled
Some checks failed
ci/woodpecker/pr/check Pipeline failed
CI / Check (pull_request) Has been cancelled
CI / Format (pull_request) Has been cancelled
CI / Clippy (pull_request) Has been cancelled
CI / Build (macos-latest) (pull_request) Has been cancelled
CI / Build (ubuntu-latest) (pull_request) Has been cancelled
CI / Build (windows-latest) (pull_request) Has been cancelled
This commit is contained in:
@@ -14,9 +14,10 @@ pub enum InputMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Состояния чата - взаимоисключающие режимы работы с чатом
|
/// Состояния чата - взаимоисключающие режимы работы с чатом
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub enum ChatState {
|
pub enum ChatState {
|
||||||
/// Обычный режим - просмотр сообщений, набор текста
|
/// Обычный режим - просмотр сообщений, набор текста
|
||||||
|
#[default]
|
||||||
Normal,
|
Normal,
|
||||||
|
|
||||||
/// Выбор сообщения для действия (edit/delete/reply/forward/reaction)
|
/// Выбор сообщения для действия (edit/delete/reply/forward/reaction)
|
||||||
@@ -90,12 +91,6 @@ pub enum ChatState {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ChatState {
|
|
||||||
fn default() -> Self {
|
|
||||||
ChatState::Normal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChatState {
|
impl ChatState {
|
||||||
/// Проверка: находимся в режиме выбора сообщения
|
/// Проверка: находимся в режиме выбора сообщения
|
||||||
pub fn is_message_selection(&self) -> bool {
|
pub fn is_message_selection(&self) -> bool {
|
||||||
|
|||||||
@@ -110,8 +110,19 @@ pub struct Keybindings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Keybindings {
|
impl Keybindings {
|
||||||
/// Создаёт дефолтную конфигурацию
|
/// Ищет команду по клавише
|
||||||
pub fn default() -> Self {
|
pub fn get_command(&self, event: &KeyEvent) -> Option<Command> {
|
||||||
|
for (command, bindings) in &self.bindings {
|
||||||
|
if bindings.iter().any(|binding| binding.matches(event)) {
|
||||||
|
return Some(*command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Keybindings {
|
||||||
|
fn default() -> Self {
|
||||||
let mut bindings = HashMap::new();
|
let mut bindings = HashMap::new();
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
@@ -301,22 +312,6 @@ impl Keybindings {
|
|||||||
|
|
||||||
Self { bindings }
|
Self { bindings }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ищет команду по клавише
|
|
||||||
pub fn get_command(&self, event: &KeyEvent) -> Option<Command> {
|
|
||||||
for (command, bindings) in &self.bindings {
|
|
||||||
if bindings.iter().any(|binding| binding.matches(event)) {
|
|
||||||
return Some(*command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Keybindings {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Сериализация KeyModifiers
|
/// Сериализация KeyModifiers
|
||||||
@@ -428,8 +423,8 @@ mod key_code_serde {
|
|||||||
return Ok(KeyCode::Char(c));
|
return Ok(KeyCode::Char(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.starts_with("F") {
|
if let Some(suffix) = s.strip_prefix("F") {
|
||||||
let n = s[1..].parse().map_err(serde::de::Error::custom)?;
|
let n = suffix.parse().map_err(serde::de::Error::custom)?;
|
||||||
return Ok(KeyCode::F(n));
|
return Ok(KeyCode::F(n));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ pub use keybindings::{Command, Keybindings};
|
|||||||
/// println!("Timezone: {}", config.general.timezone);
|
/// println!("Timezone: {}", config.general.timezone);
|
||||||
/// println!("Incoming color: {}", config.colors.incoming_message);
|
/// println!("Incoming color: {}", config.colors.incoming_message);
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// Общие настройки (timezone и т.д.).
|
/// Общие настройки (timezone и т.д.).
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -260,19 +260,6 @@ impl Default for NotificationsConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
general: GeneralConfig::default(),
|
|
||||||
colors: ColorsConfig::default(),
|
|
||||||
keybindings: Keybindings::default(),
|
|
||||||
notifications: NotificationsConfig::default(),
|
|
||||||
images: ImagesConfig::default(),
|
|
||||||
audio: AudioConfig::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -126,22 +126,22 @@ pub fn format_text_with_entities(
|
|||||||
let start = entity.offset as usize;
|
let start = entity.offset as usize;
|
||||||
let end = (entity.offset + entity.length) as usize;
|
let end = (entity.offset + entity.length) as usize;
|
||||||
|
|
||||||
for i in start..end.min(chars.len()) {
|
for item in char_styles.iter_mut().take(end.min(chars.len())).skip(start) {
|
||||||
match &entity.r#type {
|
match &entity.r#type {
|
||||||
TextEntityType::Bold => char_styles[i].bold = true,
|
TextEntityType::Bold => item.bold = true,
|
||||||
TextEntityType::Italic => char_styles[i].italic = true,
|
TextEntityType::Italic => item.italic = true,
|
||||||
TextEntityType::Underline => char_styles[i].underline = true,
|
TextEntityType::Underline => item.underline = true,
|
||||||
TextEntityType::Strikethrough => char_styles[i].strikethrough = true,
|
TextEntityType::Strikethrough => item.strikethrough = true,
|
||||||
TextEntityType::Code | TextEntityType::Pre | TextEntityType::PreCode(_) => {
|
TextEntityType::Code | TextEntityType::Pre | TextEntityType::PreCode(_) => {
|
||||||
char_styles[i].code = true
|
item.code = true
|
||||||
}
|
}
|
||||||
TextEntityType::Spoiler => char_styles[i].spoiler = true,
|
TextEntityType::Spoiler => item.spoiler = true,
|
||||||
TextEntityType::Url
|
TextEntityType::Url
|
||||||
| TextEntityType::TextUrl(_)
|
| TextEntityType::TextUrl(_)
|
||||||
| TextEntityType::EmailAddress
|
| TextEntityType::EmailAddress
|
||||||
| TextEntityType::PhoneNumber => char_styles[i].url = true,
|
| TextEntityType::PhoneNumber => item.url = true,
|
||||||
TextEntityType::Mention | TextEntityType::MentionName(_) => {
|
TextEntityType::Mention | TextEntityType::MentionName(_) => {
|
||||||
char_styles[i].mention = true
|
item.mention = true
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ pub async fn edit_message<T: TdClientTrait>(
|
|||||||
.interactions
|
.interactions
|
||||||
.reply_to
|
.reply_to
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(true, |r| r.sender_name == "Unknown")
|
.is_none_or(|r| r.sender_name == "Unknown")
|
||||||
{
|
{
|
||||||
edited_msg.interactions.reply_to = Some(old_reply);
|
edited_msg.interactions.reply_to = Some(old_reply);
|
||||||
}
|
}
|
||||||
@@ -780,7 +780,7 @@ async fn handle_play_voice<T: TdClientTrait>(app: &mut App<T>) {
|
|||||||
return handle_play_voice_from_path(
|
return handle_play_voice_from_path(
|
||||||
app,
|
app,
|
||||||
&found_path,
|
&found_path,
|
||||||
&voice,
|
voice,
|
||||||
&msg,
|
&msg,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
@@ -799,7 +799,7 @@ async fn handle_play_voice<T: TdClientTrait>(app: &mut App<T>) {
|
|||||||
let _ = cache.store(&file_id.to_string(), Path::new(&audio_path));
|
let _ = cache.store(&file_id.to_string(), Path::new(&audio_path));
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_play_voice_from_path(app, &audio_path, &voice, &msg).await;
|
handle_play_voice_from_path(app, &audio_path, voice, &msg).await;
|
||||||
}
|
}
|
||||||
VoiceDownloadState::Downloading => {
|
VoiceDownloadState::Downloading => {
|
||||||
app.status_message = Some("Загрузка голосового...".to_string());
|
app.status_message = Some("Загрузка голосового...".to_string());
|
||||||
@@ -809,7 +809,7 @@ async fn handle_play_voice<T: TdClientTrait>(app: &mut App<T>) {
|
|||||||
let cache_key = file_id.to_string();
|
let cache_key = file_id.to_string();
|
||||||
if let Some(cached_path) = app.voice_cache.as_mut().and_then(|c| c.get(&cache_key)) {
|
if let Some(cached_path) = app.voice_cache.as_mut().and_then(|c| c.get(&cache_key)) {
|
||||||
let path_str = cached_path.to_string_lossy().to_string();
|
let path_str = cached_path.to_string_lossy().to_string();
|
||||||
handle_play_voice_from_path(app, &path_str, &voice, &msg).await;
|
handle_play_voice_from_path(app, &path_str, voice, &msg).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -822,7 +822,7 @@ async fn handle_play_voice<T: TdClientTrait>(app: &mut App<T>) {
|
|||||||
let _ = cache.store(&cache_key, std::path::Path::new(&path));
|
let _ = cache.store(&cache_key, std::path::Path::new(&path));
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_play_voice_from_path(app, &path, &voice, &msg).await;
|
handle_play_voice_from_path(app, &path, voice, &msg).await;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
app.error_message = Some(format!("Ошибка загрузки: {}", e));
|
app.error_message = Some(format!("Ошибка загрузки: {}", e));
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ pub enum MessageGroup {
|
|||||||
sender_name: String,
|
sender_name: String,
|
||||||
},
|
},
|
||||||
/// Сообщение
|
/// Сообщение
|
||||||
Message(MessageInfo),
|
Message(Box<MessageInfo>),
|
||||||
/// Альбом (группа фото с одинаковым media_album_id)
|
/// Альбом (группа фото с одинаковым media_album_id)
|
||||||
Album(Vec<MessageInfo>),
|
Album(Vec<MessageInfo>),
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ pub fn group_messages(messages: &[MessageInfo]) -> Vec<MessageGroup> {
|
|||||||
result.push(MessageGroup::Album(std::mem::take(acc)));
|
result.push(MessageGroup::Album(std::mem::take(acc)));
|
||||||
} else {
|
} else {
|
||||||
// Одно сообщение — не альбом
|
// Одно сообщение — не альбом
|
||||||
result.push(MessageGroup::Message(acc.remove(0)));
|
result.push(MessageGroup::Message(Box::new(acc.remove(0))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ pub fn group_messages(messages: &[MessageInfo]) -> Vec<MessageGroup> {
|
|||||||
|
|
||||||
// Обычное сообщение (не альбом) — flush аккумулятор
|
// Обычное сообщение (не альбом) — flush аккумулятор
|
||||||
flush_album(&mut album_acc, &mut result);
|
flush_album(&mut album_acc, &mut result);
|
||||||
result.push(MessageGroup::Message(msg.clone()));
|
result.push(MessageGroup::Message(Box::new(msg.clone())));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush оставшийся аккумулятор
|
// Flush оставшийся аккумулятор
|
||||||
|
|||||||
@@ -490,19 +490,6 @@ impl TdClient {
|
|||||||
|
|
||||||
// ==================== Helper методы для упрощения обработки updates ====================
|
// ==================== Helper методы для упрощения обработки updates ====================
|
||||||
|
|
||||||
/// Находит мутабельную ссылку на чат по ID.
|
|
||||||
///
|
|
||||||
/// Упрощает повторяющийся паттерн `self.chats_mut().iter_mut().find(...)`.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `chat_id` - ID чата для поиска
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * `Some(&mut ChatInfo)` - если чат найден
|
|
||||||
/// * `None` - если чат не найден
|
|
||||||
|
|
||||||
/// Обрабатываем одно обновление от TDLib
|
/// Обрабатываем одно обновление от TDLib
|
||||||
pub fn handle_update(&mut self, update: Update) {
|
pub fn handle_update(&mut self, update: Update) {
|
||||||
match update {
|
match update {
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ pub fn extract_reply_info(client: &TdClient, message: &TdMessage) -> Option<Repl
|
|||||||
let sender_name = reply
|
let sender_name = reply
|
||||||
.origin
|
.origin
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|origin| get_origin_sender_name(origin))
|
.map(get_origin_sender_name)
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
// Пробуем найти оригинальное сообщение в текущем списке
|
// Пробуем найти оригинальное сообщение в текущем списке
|
||||||
let reply_msg_id = MessageId::new(reply.message_id);
|
let reply_msg_id = MessageId::new(reply.message_id);
|
||||||
|
|||||||
@@ -206,13 +206,11 @@ impl MessageManager {
|
|||||||
match result {
|
match result {
|
||||||
Ok(tdlib_rs::enums::Messages::Messages(messages_obj)) => {
|
Ok(tdlib_rs::enums::Messages::Messages(messages_obj)) => {
|
||||||
let mut messages = Vec::new();
|
let mut messages = Vec::new();
|
||||||
for msg_opt in messages_obj.messages.iter().rev() {
|
for msg in messages_obj.messages.iter().rev().flatten() {
|
||||||
if let Some(msg) = msg_opt {
|
|
||||||
if let Some(info) = self.convert_message(msg).await {
|
if let Some(info) = self.convert_message(msg).await {
|
||||||
messages.push(info);
|
messages.push(info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(messages)
|
Ok(messages)
|
||||||
}
|
}
|
||||||
Err(e) => Err(format!("Ошибка загрузки старых сообщений: {:?}", e)),
|
Err(e) => Err(format!("Ошибка загрузки старых сообщений: {:?}", e)),
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ pub struct MessageInfo {
|
|||||||
|
|
||||||
impl MessageInfo {
|
impl MessageInfo {
|
||||||
/// Создать новое сообщение
|
/// Создать новое сообщение
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
id: MessageId,
|
id: MessageId,
|
||||||
sender_name: String,
|
sender_name: String,
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ pub fn handle_chat_action_update(client: &mut TdClient, update: UpdateChatAction
|
|||||||
ChatAction::ChoosingSticker => Some("выбирает стикер...".to_string()),
|
ChatAction::ChoosingSticker => Some("выбирает стикер...".to_string()),
|
||||||
ChatAction::RecordingVideoNote => Some("записывает видеосообщение...".to_string()),
|
ChatAction::RecordingVideoNote => Some("записывает видеосообщение...".to_string()),
|
||||||
ChatAction::UploadingVideoNote(_) => Some("отправляет видеосообщение...".to_string()),
|
ChatAction::UploadingVideoNote(_) => Some("отправляет видеосообщение...".to_string()),
|
||||||
ChatAction::Cancel | _ => None, // Отмена или неизвестное действие
|
_ => None, // Отмена или неизвестное действие
|
||||||
};
|
};
|
||||||
|
|
||||||
match action_text {
|
match action_text {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ pub fn render_emoji_picker(
|
|||||||
) {
|
) {
|
||||||
// Размеры модалки (зависят от количества реакций)
|
// Размеры модалки (зависят от количества реакций)
|
||||||
let emojis_per_row = 8;
|
let emojis_per_row = 8;
|
||||||
let rows = (available_reactions.len() + emojis_per_row - 1) / emojis_per_row;
|
let rows = available_reactions.len().div_ceil(emojis_per_row);
|
||||||
let modal_width = 50u16;
|
let modal_width = 50u16;
|
||||||
let modal_height = (rows + 4) as u16; // +4 для заголовка, отступов и подсказки
|
let modal_height = (rows + 4) as u16; // +4 для заголовка, отступов и подсказки
|
||||||
|
|
||||||
|
|||||||
@@ -401,12 +401,10 @@ pub fn render_message_bubble(
|
|||||||
} else {
|
} else {
|
||||||
format!("[{}]", reaction.emoji)
|
format!("[{}]", reaction.emoji)
|
||||||
}
|
}
|
||||||
} else {
|
} else if reaction.count > 1 {
|
||||||
if reaction.count > 1 {
|
|
||||||
format!("{} {}", reaction.emoji, reaction.count)
|
format!("{} {}", reaction.emoji, reaction.count)
|
||||||
} else {
|
} else {
|
||||||
reaction.emoji.clone()
|
reaction.emoji.clone()
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let style = if reaction.is_chosen {
|
let style = if reaction.is_chosen {
|
||||||
@@ -548,7 +546,7 @@ pub fn render_album_bubble(
|
|||||||
let mut deferred: Vec<DeferredImageRender> = Vec::new();
|
let mut deferred: Vec<DeferredImageRender> = Vec::new();
|
||||||
|
|
||||||
let is_selected = messages.iter().any(|m| selected_msg_id == Some(m.id()));
|
let is_selected = messages.iter().any(|m| selected_msg_id == Some(m.id()));
|
||||||
let is_outgoing = messages.first().map_or(false, |m| m.is_outgoing());
|
let is_outgoing = messages.first().is_some_and(|m| m.is_outgoing());
|
||||||
|
|
||||||
// Selection marker
|
// Selection marker
|
||||||
let selection_marker = if is_selected { "▶ " } else { "" };
|
let selection_marker = if is_selected { "▶ " } else { "" };
|
||||||
@@ -567,7 +565,7 @@ pub fn render_album_bubble(
|
|||||||
|
|
||||||
// Grid layout
|
// Grid layout
|
||||||
let cols = photo_count.min(ALBUM_GRID_MAX_COLS);
|
let cols = photo_count.min(ALBUM_GRID_MAX_COLS);
|
||||||
let rows = (photo_count + cols - 1) / cols;
|
let rows = photo_count.div_ceil(cols);
|
||||||
|
|
||||||
// Добавляем маркер выбора на первую строку
|
// Добавляем маркер выбора на первую строку
|
||||||
if is_selected {
|
if is_selected {
|
||||||
|
|||||||
@@ -309,11 +309,7 @@ fn render_message_list<T: TdClientTrait>(f: &mut Frame, area: Rect, app: &mut Ap
|
|||||||
let total_lines = lines.len();
|
let total_lines = lines.len();
|
||||||
|
|
||||||
// Базовый скролл (показываем последние сообщения)
|
// Базовый скролл (показываем последние сообщения)
|
||||||
let base_scroll = if total_lines > visible_height {
|
let base_scroll = total_lines.saturating_sub(visible_height);
|
||||||
total_lines - visible_height
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
// Если выбрано сообщение, автоскроллим к нему
|
// Если выбрано сообщение, автоскроллим к нему
|
||||||
let scroll_offset = if app.is_selecting_message() {
|
let scroll_offset = if app.is_selecting_message() {
|
||||||
@@ -431,7 +427,7 @@ pub fn render<T: TdClientTrait>(f: &mut Frame, area: Rect, app: &mut App<T>) {
|
|||||||
1
|
1
|
||||||
};
|
};
|
||||||
// Минимум 3 строки (1 контент + 2 рамки), максимум 10
|
// Минимум 3 строки (1 контент + 2 рамки), максимум 10
|
||||||
let input_height = (input_lines + 2).min(10).max(3);
|
let input_height = (input_lines + 2).clamp(3, 10);
|
||||||
|
|
||||||
// Проверяем, есть ли закреплённое сообщение
|
// Проверяем, есть ли закреплённое сообщение
|
||||||
let has_pinned = app.td_client.current_pinned_message().is_some();
|
let has_pinned = app.td_client.current_pinned_message().is_some();
|
||||||
|
|||||||
Reference in New Issue
Block a user