Format remaining C++ sources

This commit is contained in:
2026-04-24 06:35:05 +03:00
parent 70c2e77957
commit 4910fb5e8e
10 changed files with 610 additions and 554 deletions

View File

@@ -21,12 +21,8 @@ namespace telegram_tui {
namespace {
constexpr std::array<AttachmentType, 7> kAttachmentTypes = {
AttachmentType::Photo,
AttachmentType::Video,
AttachmentType::Document,
AttachmentType::Audio,
AttachmentType::Voice,
AttachmentType::Animation,
AttachmentType::Photo, AttachmentType::Video, AttachmentType::Document,
AttachmentType::Audio, AttachmentType::Voice, AttachmentType::Animation,
AttachmentType::Sticker,
};
@@ -60,8 +56,8 @@ std::filesystem::path downloads_directory() {
std::string sanitize_filename(std::string value) {
for (char &ch : value) {
if (ch == '/' || ch == '\\' || ch == ':' || ch == '*' || ch == '?' || ch == '"' || ch == '<' ||
ch == '>' || ch == '|') {
if (ch == '/' || ch == '\\' || ch == ':' || ch == '*' || ch == '?' || ch == '"' ||
ch == '<' || ch == '>' || ch == '|') {
ch = '_';
}
}
@@ -80,7 +76,8 @@ std::filesystem::path preferred_attachment_filename(const AttachmentInfo& attach
std::filesystem::path candidate(filename);
if (!candidate.has_extension() && !attachment.local_path.empty()) {
const std::string extension = std::filesystem::path(attachment.local_path).extension().string();
const std::string extension =
std::filesystem::path(attachment.local_path).extension().string();
if (!extension.empty()) {
candidate += extension;
}
@@ -88,13 +85,14 @@ std::filesystem::path preferred_attachment_filename(const AttachmentInfo& attach
return candidate;
}
std::filesystem::path unique_destination_path(
const std::filesystem::path& directory, const std::filesystem::path& filename) {
std::filesystem::path unique_destination_path(const std::filesystem::path &directory,
const std::filesystem::path &filename) {
const std::filesystem::path stem = filename.stem();
const std::filesystem::path extension = filename.extension();
std::filesystem::path candidate = directory / filename;
for (int index = 1; std::filesystem::exists(candidate); ++index) {
candidate = directory / (stem.string() + " (" + std::to_string(index) + ")" + extension.string());
candidate = directory / (stem.string() + " (" + std::to_string(index) + ")" +
extension.string());
}
return candidate;
}
@@ -108,7 +106,8 @@ std::string inline_preview_unavailable_message(const AttachmentInfo& attachment)
if (!std::filesystem::exists(attachment.local_path)) {
return "Downloaded photo file is missing on disk.";
}
return "No supported preview backend found. Install `kitten`, `img2sixel`, or `chafa`.";
return "No supported preview backend found. Install `kitten`, `img2sixel`, or "
"`chafa`.";
case AttachmentType::Video:
if (!attachment.is_downloaded || attachment.local_path.empty()) {
return "Video is not downloaded yet.";
@@ -116,7 +115,8 @@ std::string inline_preview_unavailable_message(const AttachmentInfo& attachment)
if (!std::filesystem::exists(attachment.local_path)) {
return "Downloaded video file is missing on disk.";
}
return "Video preview requires `ffmpegthumbnailer` or `ffmpeg`, plus `kitten`, `img2sixel`, or `chafa`.";
return "Video preview requires `ffmpegthumbnailer` or `ffmpeg`, plus `kitten`, "
"`img2sixel`, or `chafa`.";
default:
return "Inline preview is only supported for photos and videos.";
}
@@ -142,7 +142,8 @@ std::string attachment_progress_bar(const AttachmentInfo& attachment, int width)
if (attachment.is_downloaded) {
filled = inner_width;
} else if (attachment.size_bytes > 0 && attachment.downloaded_size > 0) {
filled = std::clamp((inner_width * attachment_progress_percent(attachment)) / 100, 0, inner_width);
filled = std::clamp((inner_width * attachment_progress_percent(attachment)) / 100,
0, inner_width);
}
return "[" + std::string(filled, '#') + std::string(inner_width - filled, '.') + "]";
}
@@ -209,7 +210,8 @@ bool command_exists(const char* command) {
return cached->second;
}
const std::string resolved = run_command_capture("command -v " + std::string(command) + " 2>/dev/null");
const std::string resolved =
run_command_capture("command -v " + std::string(command) + " 2>/dev/null");
const bool exists = !trim_copy(resolved).empty();
cache[command] = exists;
return exists;
@@ -320,8 +322,8 @@ bool terminal_supports_sixels() {
const std::string term = lower_copy(get_env("TERM"));
const std::string term_program = lower_copy(get_env("TERM_PROGRAM"));
return term == "foot" || term.find("xterm") != std::string::npos ||
term.find("mlterm") != std::string::npos || term.find("wezterm") != std::string::npos ||
term_program == "wezterm";
term.find("mlterm") != std::string::npos ||
term.find("wezterm") != std::string::npos || term_program == "wezterm";
}
struct TerminalWindowSize {
@@ -403,7 +405,8 @@ void App::sync_message_attachment_selection() {
}
message_attachment_message_id_ = 0;
for (auto it = chat_it->second.messages.rbegin(); it != chat_it->second.messages.rend(); ++it) {
for (auto it = chat_it->second.messages.rbegin(); it != chat_it->second.messages.rend();
++it) {
if (it->has_attachment) {
message_attachment_message_id_ = it->id;
return;
@@ -438,9 +441,7 @@ bool App::move_message_attachment_selection(int delta) {
return false;
}
auto selected = std::find(
attachment_message_ids.begin(),
attachment_message_ids.end(),
auto selected = std::find(attachment_message_ids.begin(), attachment_message_ids.end(),
message_attachment_message_id_);
if (selected == attachment_message_ids.end()) {
message_attachment_message_id_ = attachment_message_ids.back();
@@ -449,11 +450,13 @@ bool App::move_message_attachment_selection(int delta) {
const std::ptrdiff_t index = selected - attachment_message_ids.begin();
const std::ptrdiff_t next_index = index + (delta < 0 ? -1 : 1);
if (next_index < 0 || next_index >= static_cast<std::ptrdiff_t>(attachment_message_ids.size())) {
if (next_index < 0 ||
next_index >= static_cast<std::ptrdiff_t>(attachment_message_ids.size())) {
return false;
}
message_attachment_message_id_ = attachment_message_ids[static_cast<std::size_t>(next_index)];
message_attachment_message_id_ =
attachment_message_ids[static_cast<std::size_t>(next_index)];
return true;
}
@@ -502,7 +505,8 @@ std::optional<AttachmentInfo> App::selected_attachment() const {
const AttachmentType selected_type = kAttachmentTypes[category_index];
std::vector<AttachmentInfo> attachments;
for (auto it = chat_it->second.messages.rbegin(); it != chat_it->second.messages.rend(); ++it) {
for (auto it = chat_it->second.messages.rbegin(); it != chat_it->second.messages.rend();
++it) {
if (it->has_attachment && it->attachment.type == selected_type) {
attachments.push_back(it->attachment);
}
@@ -522,7 +526,8 @@ std::optional<AttachmentInfo> App::selected_attachment() const {
return attachments[static_cast<std::size_t>(selection_index)];
}
std::string App::render_attachment_preview(const AttachmentInfo& attachment, int width, int height) const {
std::string App::render_attachment_preview(const AttachmentInfo &attachment, int width,
int height) const {
const std::string preview_path = attachment_preview_path(attachment);
if (preview_path.empty()) {
return inline_preview_unavailable_message(attachment);
@@ -530,9 +535,10 @@ std::string App::render_attachment_preview(const AttachmentInfo& attachment, int
const int render_width = std::max(10, width);
const int render_height = std::max(4, height);
const std::string command =
"command -v chafa >/dev/null 2>&1 && chafa --format symbols --symbols all --colors none --size " +
std::to_string(render_width) + "x" + std::to_string(render_height) + " " +
const std::string command = "command -v chafa >/dev/null 2>&1 && chafa --format symbols "
"--symbols all --colors none --size " +
std::to_string(render_width) + "x" +
std::to_string(render_height) + " " +
shell_quote(preview_path) + " 2>/dev/null";
std::string preview = run_command_capture(command);
if (!preview.empty()) {
@@ -541,29 +547,32 @@ std::string App::render_attachment_preview(const AttachmentInfo& attachment, int
return "No supported preview backend found. Install `kitten`, `img2sixel`, or `chafa`.";
}
std::string App::build_attachment_preview_graphics(const AttachmentInfo& attachment, int width, int height) const {
std::string App::build_attachment_preview_graphics(const AttachmentInfo &attachment, int width,
int height) const {
const std::string preview_path = attachment_preview_path(attachment);
if (preview_path.empty()) {
return {};
}
const std::string forced_protocol = configured_image_protocol();
const bool want_sixels =
forced_protocol == "sixels" || (forced_protocol.empty() && terminal_supports_sixels());
const bool want_sixels = forced_protocol == "sixels" ||
(forced_protocol.empty() && terminal_supports_sixels());
if (want_sixels) {
if (command_exists("chafa")) {
return "chafa --format sixels --size " + std::to_string(std::max(4, width)) + "x" +
return "chafa --format sixels --size " +
std::to_string(std::max(4, width)) + "x" +
std::to_string(std::max(2, height)) + " " +
shell_quote(preview_path) + " >/dev/tty 2>/dev/null";
}
if (command_exists("img2sixel")) {
return "img2sixel -w " + std::to_string(approximate_pixel_width(width)) + "px -h " +
std::to_string(approximate_pixel_height(height)) + "px " +
return "img2sixel -w " + std::to_string(approximate_pixel_width(width)) +
"px -h " + std::to_string(approximate_pixel_height(height)) + "px " +
shell_quote(preview_path) + " >/dev/tty 2>/dev/null";
}
}
if ((forced_protocol == "kitty" || (forced_protocol.empty() && terminal_supports_kitty_graphics())) &&
if ((forced_protocol == "kitty" ||
(forced_protocol.empty() && terminal_supports_kitty_graphics())) &&
command_exists("kitten")) {
TerminalWindowSize size = terminal_window_size();
if (size.cols <= 0) {
@@ -578,17 +587,21 @@ std::string App::build_attachment_preview_graphics(const AttachmentInfo& attachm
if (size.height_pixels <= 0) {
size.height_pixels = std::max(1, size.rows * 16);
}
return "kitten icat --stdin=no --passthrough=none --transfer-mode=stream --align=left "
"--place " + std::to_string(std::max(1, width)) + "x" + std::to_string(std::max(1, height)) +
"@0x0 --use-window-size " + std::to_string(size.cols) + "," + std::to_string(size.rows) + "," +
std::to_string(size.width_pixels) + "," + std::to_string(size.height_pixels) + " " +
shell_quote(preview_path) + " >/dev/tty 2>/dev/null";
return "kitten icat --stdin=no --passthrough=none --transfer-mode=stream "
"--align=left "
"--place " +
std::to_string(std::max(1, width)) + "x" +
std::to_string(std::max(1, height)) + "@0x0 --use-window-size " +
std::to_string(size.cols) + "," + std::to_string(size.rows) + "," +
std::to_string(size.width_pixels) + "," +
std::to_string(size.height_pixels) + " " + shell_quote(preview_path) +
" >/dev/tty 2>/dev/null";
}
if (command_exists("chafa")) {
return "chafa --size " + std::to_string(std::max(4, width)) + "x" +
std::to_string(std::max(2, height)) + " " +
shell_quote(preview_path) + " >/dev/tty 2>/dev/null";
std::to_string(std::max(2, height)) + " " + shell_quote(preview_path) +
" >/dev/tty 2>/dev/null";
}
return {};
@@ -600,7 +613,8 @@ void App::request_attachment_download(const AttachmentInfo& attachment, bool ope
return;
}
pending_attachment_open_ = open_after_download ? std::optional<AttachmentInfo>(attachment) : std::nullopt;
pending_attachment_open_ =
open_after_download ? std::optional<AttachmentInfo>(attachment) : std::nullopt;
if (open_after_download) {
pending_attachment_download_.reset();
}
@@ -639,7 +653,8 @@ void App::open_attachment(const AttachmentInfo& attachment) {
if (open_with_system_app(attachment.local_path)) {
status_line_ = "Video playback failed. Opened in the system app.";
} else {
status_line_ = "No supported video player found. Install `mpv` or `ffplay`.";
status_line_ =
"No supported video player found. Install `mpv` or `ffplay`.";
}
return;
}
@@ -652,7 +667,8 @@ void App::open_attachment(const AttachmentInfo& attachment) {
attachment_viewer_frame_index_ = 0;
if (attachment.type == AttachmentType::Video) {
attachment_viewer_animation_frames_ = attachment_animation_frames(attachment);
attachment_viewer_next_frame_at_ = std::chrono::steady_clock::now() + std::chrono::milliseconds(125);
attachment_viewer_next_frame_at_ =
std::chrono::steady_clock::now() + std::chrono::milliseconds(125);
}
if (!has_inline_attachment_preview(attachment, preview_width, preview_height)) {
attachment_viewer_attachment_.reset();
@@ -660,7 +676,8 @@ void App::open_attachment(const AttachmentInfo& attachment) {
attachment_viewer_is_animated_ = false;
attachment_viewer_frame_index_ = 0;
if (open_with_system_app(attachment.local_path)) {
status_line_ = "Inline preview unavailable. Opened attachment in the system app.";
status_line_ =
"Inline preview unavailable. Opened attachment in the system app.";
} else {
status_line_ = inline_preview_unavailable_message(attachment);
}
@@ -695,7 +712,8 @@ void App::delete_attachment(const AttachmentInfo& attachment) {
return;
}
if (attachment_viewer_attachment_.has_value() && attachment_viewer_attachment_->file_id == attachment.file_id) {
if (attachment_viewer_attachment_.has_value() &&
attachment_viewer_attachment_->file_id == attachment.file_id) {
attachment_viewer_open_ = false;
attachment_viewer_attachment_.reset();
attachment_viewer_animation_frames_.clear();
@@ -726,10 +744,9 @@ bool App::export_attachment_to_downloads(const AttachmentInfo& attachment) {
const std::filesystem::path target_name = preferred_attachment_filename(attachment);
try {
std::filesystem::create_directories(target_dir);
const std::filesystem::path target_path = unique_destination_path(target_dir, target_name);
std::filesystem::copy_file(
attachment.local_path,
target_path,
const std::filesystem::path target_path =
unique_destination_path(target_dir, target_name);
std::filesystem::copy_file(attachment.local_path, target_path,
std::filesystem::copy_options::none);
status_line_ = "Saved to " + target_path.string();
return true;
@@ -783,20 +800,18 @@ void App::refresh_attachment_viewer_content(const AttachmentInfo& attachment) {
attachment_viewer_lines_.clear();
return;
}
const std::string preview = render_attachment_preview(
attachment,
preview_width,
preview_height);
attachment_viewer_lines_ = split_preserved_lines(
preview,
std::max(10, (COLS > 0 ? COLS : 80) - 8));
const std::string preview =
render_attachment_preview(attachment, preview_width, preview_height);
attachment_viewer_lines_ =
split_preserved_lines(preview, std::max(10, (COLS > 0 ? COLS : 80) - 8));
if (attachment_viewer_lines_.empty()) {
attachment_viewer_lines_.push_back("Preview is empty.");
}
}
std::vector<std::string> App::attachment_animation_frames(const AttachmentInfo &attachment) const {
if (attachment.type != AttachmentType::Video || !attachment.is_downloaded || attachment.local_path.empty()) {
if (attachment.type != AttachmentType::Video || !attachment.is_downloaded ||
attachment.local_path.empty()) {
return {};
}
if (!std::filesystem::exists(attachment.local_path) || !command_exists("ffmpeg")) {
@@ -804,7 +819,8 @@ std::vector<std::string> App::attachment_animation_frames(const AttachmentInfo&
}
const std::filesystem::path preview_dir =
files_dir_ / "previews" / (std::string("video-") + std::to_string(attachment.file_id));
files_dir_ / "previews" /
(std::string("video-") + std::to_string(attachment.file_id));
std::vector<std::string> frames;
try {
std::filesystem::create_directories(preview_dir);
@@ -857,7 +873,8 @@ std::vector<std::string> App::attachment_animation_frames(const AttachmentInfo&
}
bool App::advance_attachment_animation() {
if (!attachment_viewer_open_ || !attachment_viewer_is_animated_ || attachment_viewer_animation_frames_.empty() ||
if (!attachment_viewer_open_ || !attachment_viewer_is_animated_ ||
attachment_viewer_animation_frames_.empty() ||
!attachment_viewer_attachment_.has_value()) {
return false;
}
@@ -881,7 +898,8 @@ std::string App::attachment_preview_path(const AttachmentInfo& attachment) const
if (!std::filesystem::exists(attachment.local_path)) {
return {};
}
if (attachment_viewer_attachment_.has_value() && attachment_viewer_attachment_->file_id == attachment.file_id &&
if (attachment_viewer_attachment_.has_value() &&
attachment_viewer_attachment_->file_id == attachment.file_id &&
!attachment_viewer_animation_frames_.empty() &&
attachment_viewer_frame_index_ < attachment_viewer_animation_frames_.size()) {
return attachment_viewer_animation_frames_[attachment_viewer_frame_index_];
@@ -894,11 +912,13 @@ std::string App::attachment_preview_path(const AttachmentInfo& attachment) const
}
const std::filesystem::path preview_dir = files_dir_ / "previews";
const std::filesystem::path preview_path = preview_dir / (std::to_string(attachment.file_id) + ".jpg");
const std::filesystem::path preview_path =
preview_dir / (std::to_string(attachment.file_id) + ".jpg");
try {
std::filesystem::create_directories(preview_dir);
if (std::filesystem::exists(preview_path)) {
const auto source_time = std::filesystem::last_write_time(attachment.local_path);
const auto source_time =
std::filesystem::last_write_time(attachment.local_path);
const auto preview_time = std::filesystem::last_write_time(preview_path);
if (preview_time >= source_time) {
return preview_path.string();
@@ -914,7 +934,8 @@ std::string App::attachment_preview_path(const AttachmentInfo& attachment) const
shell_quote(preview_path.string()) + " -s 0 -q 8 >/dev/null 2>&1";
} else if (command_exists("ffmpeg")) {
command = "ffmpeg -y -ss 1 -i " + shell_quote(attachment.local_path) +
" -frames:v 1 -vf thumbnail " + shell_quote(preview_path.string()) + " >/dev/null 2>&1";
" -frames:v 1 -vf thumbnail " + shell_quote(preview_path.string()) +
" >/dev/null 2>&1";
} else {
return {};
}
@@ -925,11 +946,13 @@ std::string App::attachment_preview_path(const AttachmentInfo& attachment) const
return preview_path.string();
}
bool App::has_inline_attachment_preview(const AttachmentInfo& attachment, int width, int height) const {
bool App::has_inline_attachment_preview(const AttachmentInfo &attachment, int width,
int height) const {
if (attachment_preview_path(attachment).empty()) {
return false;
}
return command_exists("chafa") || !build_attachment_preview_graphics(attachment, width, height).empty();
return command_exists("chafa") ||
!build_attachment_preview_graphics(attachment, width, height).empty();
}
void App::clear_attachment_preview_graphics() {
@@ -963,20 +986,21 @@ void App::render_attachment_preview_graphics(int top, int left, int width, int h
return;
}
const std::string forced_protocol = configured_image_protocol();
const bool want_kitty =
forced_protocol == "kitty" || (forced_protocol.empty() && terminal_supports_kitty_graphics());
const std::string signature =
preview_path + "|" + std::to_string(width) + "x" + std::to_string(height) + "|" +
std::to_string(top) + ":" + std::to_string(left) + "|" + forced_protocol;
const bool want_sixels =
forced_protocol == "sixels" || (forced_protocol.empty() && terminal_supports_sixels());
const bool want_kitty = forced_protocol == "kitty" ||
(forced_protocol.empty() && terminal_supports_kitty_graphics());
const std::string signature = preview_path + "|" + std::to_string(width) + "x" +
std::to_string(height) + "|" + std::to_string(top) + ":" +
std::to_string(left) + "|" + forced_protocol;
const bool want_sixels = forced_protocol == "sixels" ||
(forced_protocol.empty() && terminal_supports_sixels());
const std::string command = build_attachment_preview_graphics(attachment, width, height);
if (command.empty()) {
clear_attachment_preview_graphics();
return;
}
const bool same_signature = attachment_preview_signature_ == signature && attachment_preview_graphics_visible_;
const bool same_signature =
attachment_preview_signature_ == signature && attachment_preview_graphics_visible_;
if (!same_signature) {
clear_attachment_preview_graphics();
}
@@ -997,18 +1021,23 @@ void App::render_attachment_preview_graphics(int top, int left, int width, int h
}
const std::string kitty_command =
"kitten icat --stdin=no --passthrough=none --transfer-mode=stream --align=left "
"--place " + std::to_string(std::max(1, width)) + "x" + std::to_string(std::max(1, height)) +
"@" + std::to_string(std::max(0, left)) + "x" + std::to_string(std::max(0, top)) +
" --use-window-size " + std::to_string(size.cols) + "," + std::to_string(size.rows) + "," +
std::to_string(size.width_pixels) + "," + std::to_string(size.height_pixels) +
" " + shell_quote(preview_path) + " >/dev/tty 2>/dev/null";
"kitten icat --stdin=no --passthrough=none --transfer-mode=stream "
"--align=left "
"--place " +
std::to_string(std::max(1, width)) + "x" +
std::to_string(std::max(1, height)) + "@" +
std::to_string(std::max(0, left)) + "x" + std::to_string(std::max(0, top)) +
" --use-window-size " + std::to_string(size.cols) + "," +
std::to_string(size.rows) + "," + std::to_string(size.width_pixels) + "," +
std::to_string(size.height_pixels) + " " + shell_quote(preview_path) +
" >/dev/tty 2>/dev/null";
if (run_command_to_terminal(kitty_command) != 0) {
attachment_preview_graphics_is_sixel_ = false;
return;
}
} else {
write_terminal_output("\0337\033[" + std::to_string(top + 1) + ";" + std::to_string(left + 1) + "H");
write_terminal_output("\0337\033[" + std::to_string(top + 1) + ";" +
std::to_string(left + 1) + "H");
if (run_command_to_terminal(command) != 0) {
write_terminal_output("\0338");
attachment_preview_graphics_is_sixel_ = false;
@@ -1046,14 +1075,14 @@ void App::handle_attachments_menu_key(int ch) {
status_line_ = "Attachment actions.";
return;
case KEY_LEFT:
attachment_category_index_ =
(attachment_category_index_ + static_cast<int>(kAttachmentTypes.size()) - 1) %
attachment_category_index_ = (attachment_category_index_ +
static_cast<int>(kAttachmentTypes.size()) - 1) %
static_cast<int>(kAttachmentTypes.size());
attachment_selection_index_ = 0;
return;
case KEY_RIGHT:
attachment_category_index_ =
(attachment_category_index_ + 1) % static_cast<int>(kAttachmentTypes.size());
attachment_category_index_ = (attachment_category_index_ + 1) %
static_cast<int>(kAttachmentTypes.size());
attachment_selection_index_ = 0;
return;
case KEY_UP:
@@ -1141,9 +1170,11 @@ void App::handle_attachment_viewer_key(int ch) {
return;
}
{
const std::string local_path = attachment_viewer_attachment_->local_path;
const std::string local_path =
attachment_viewer_attachment_->local_path;
const std::string caption = attachment_viewer_send_caption_;
const auto reply_to_message_id = attachment_viewer_send_reply_to_message_id_;
const auto reply_to_message_id =
attachment_viewer_send_reply_to_message_id_;
const std::int64_t chat_id = attachment_viewer_send_chat_id_;
attachment_viewer_open_ = false;
attachment_viewer_attachment_.reset();
@@ -1152,7 +1183,8 @@ void App::handle_attachment_viewer_key(int ch) {
attachment_viewer_frame_index_ = 0;
reset_attachment_viewer_send_preview();
clear_attachment_preview_graphics();
send_photo_message(chat_id, local_path, caption, reply_to_message_id);
send_photo_message(chat_id, local_path, caption,
reply_to_message_id);
}
return;
default:
@@ -1163,8 +1195,7 @@ void App::handle_attachment_viewer_key(int ch) {
switch (ch) {
case 27:
case 'q':
case ' ':
{
case ' ': {
const bool was_send_preview = attachment_viewer_send_on_enter_;
attachment_viewer_open_ = false;
attachment_viewer_attachment_.reset();
@@ -1173,8 +1204,7 @@ void App::handle_attachment_viewer_key(int ch) {
attachment_viewer_frame_index_ = 0;
reset_attachment_viewer_send_preview();
clear_attachment_preview_graphics();
status_line_ = was_send_preview
? "Cancelled clipboard image send."
status_line_ = was_send_preview ? "Cancelled clipboard image send."
: "Closed attachment preview.";
return;
}
@@ -1232,7 +1262,8 @@ void App::draw_attachments_menu(int height, int width) {
const AttachmentType selected_type = kAttachmentTypes[attachment_category_index_];
std::vector<const MessageInfo *> attachments;
for (auto it = chat_it->second.messages.rbegin(); it != chat_it->second.messages.rend(); ++it) {
for (auto it = chat_it->second.messages.rbegin(); it != chat_it->second.messages.rend();
++it) {
if (it->has_attachment && it->attachment.type == selected_type) {
attachments.push_back(&*it);
}
@@ -1269,7 +1300,8 @@ void App::draw_attachments_menu(int height, int width) {
int date_width = std::min(16, std::max(8, content_width / 3));
int sender_width = std::max(8, content_width - status_width - size_width - date_width - 3);
if (sender_width + status_width + size_width + date_width + 3 > content_width) {
date_width = std::max(8, content_width - sender_width - status_width - size_width - 3);
date_width =
std::max(8, content_width - sender_width - status_width - size_width - 3);
}
mvwaddnstr(window, 3, 2, "Sender", sender_width);
@@ -1286,7 +1318,8 @@ void App::draw_attachments_menu(int height, int width) {
}
if (attachments.empty()) {
const std::string message = "No " + attachment_type_label(selected_type) + " in this chat.";
const std::string message =
"No " + attachment_type_label(selected_type) + " in this chat.";
mvwaddnstr(window, list_top, 2, message.c_str(), menu_width - 4);
} else {
for (int row = 0; row < list_height; ++row) {
@@ -1297,24 +1330,29 @@ void App::draw_attachments_menu(int height, int width) {
continue;
}
const MessageInfo& message = *attachments[static_cast<std::size_t>(item_index)];
const MessageInfo &message =
*attachments[static_cast<std::size_t>(item_index)];
if (item_index == attachment_selection_index_) {
wattron(window, A_REVERSE);
}
const std::string sender = truncate_text(message.sender, sender_width);
const std::string dl_status = truncate_text(
message.attachment.is_downloading_active && !message.attachment.is_downloaded
? attachment_progress_bar(message.attachment, std::max(10, status_width - 2))
message.attachment.is_downloading_active &&
!message.attachment.is_downloaded
? attachment_progress_bar(message.attachment,
std::max(10, status_width - 2))
: (message.attachment.is_downloaded ? "Ready" : ""),
status_width);
const std::string file_size = truncate_text(
format_file_size(message.attachment.size_bytes),
size_width);
const std::string sent = truncate_text(format_datetime(message.date), date_width);
format_file_size(message.attachment.size_bytes), size_width);
const std::string sent =
truncate_text(format_datetime(message.date), date_width);
mvwaddnstr(window, y, 2, sender.c_str(), sender_width);
mvwaddnstr(window, y, 3 + sender_width, dl_status.c_str(), status_width);
mvwaddnstr(window, y, 4 + sender_width + status_width, file_size.c_str(), size_width);
mvwaddnstr(window, y, 5 + sender_width + status_width + size_width, sent.c_str(), date_width);
mvwaddnstr(window, y, 4 + sender_width + status_width, file_size.c_str(),
size_width);
mvwaddnstr(window, y, 5 + sender_width + status_width + size_width,
sent.c_str(), date_width);
if (item_index == attachment_selection_index_) {
wattroff(window, A_REVERSE);
}
@@ -1325,7 +1363,8 @@ void App::draw_attachments_menu(int height, int width) {
std::string title_line;
std::string actions_line = "Left/Right type Up/Down move Space actions Esc close";
if (!attachments.empty()) {
const MessageInfo& selected = *attachments[static_cast<std::size_t>(attachment_selection_index_)];
const MessageInfo &selected =
*attachments[static_cast<std::size_t>(attachment_selection_index_)];
title_line = truncate_text(selected.attachment.name, menu_width - 4);
} else {
title_line = truncate_text("No attachment selected.", menu_width - 4);
@@ -1357,14 +1396,16 @@ void App::draw_attachment_action_menu(int height, int width) {
box(window, 0, 0);
mvwprintw(window, 0, 2, " Attachment Action ");
mvwaddnstr(window, 1, 2, truncate_text(attachment->name, menu_width - 4).c_str(), menu_width - 4);
mvwaddnstr(window, 1, 2, truncate_text(attachment->name, menu_width - 4).c_str(),
menu_width - 4);
mvwhline(window, 3, 1, ACS_HLINE, menu_width - 2);
const std::vector<std::string> prompt_lines = wrap_text(
"What do you want to do with this file?",
std::max(10, menu_width - 8));
for (std::size_t i = 0; i < prompt_lines.size() && static_cast<int>(i) < menu_height - 9; ++i) {
mvwaddnstr(window, 5 + static_cast<int>(i), 4, prompt_lines[i].c_str(), menu_width - 8);
const std::vector<std::string> prompt_lines =
wrap_text("What do you want to do with this file?", std::max(10, menu_width - 8));
for (std::size_t i = 0; i < prompt_lines.size() && static_cast<int>(i) < menu_height - 9;
++i) {
mvwaddnstr(window, 5 + static_cast<int>(i), 4, prompt_lines[i].c_str(),
menu_width - 8);
}
mvwhline(window, menu_height - 5, 1, ACS_HLINE, menu_width - 2);
@@ -1378,20 +1419,23 @@ void App::draw_attachment_action_menu(int height, int width) {
const int button_y = menu_height - 3;
const int inner_width = menu_width - 2;
const int gap = std::max(2, (inner_width - total_label_width) / (static_cast<int>(labels.size()) + 1));
const int gap = std::max(2, (inner_width - total_label_width) /
(static_cast<int>(labels.size()) + 1));
int button_x = 1 + gap;
for (std::size_t i = 0; i < actions.size(); ++i) {
if (static_cast<int>(i) == attachment_action_index_) {
wattron(window, A_REVERSE | A_BOLD);
}
mvwaddnstr(window, button_y, button_x, labels[i].c_str(), menu_width - button_x - 2);
mvwaddnstr(window, button_y, button_x, labels[i].c_str(),
menu_width - button_x - 2);
if (static_cast<int>(i) == attachment_action_index_) {
wattroff(window, A_REVERSE | A_BOLD);
}
button_x += static_cast<int>(labels[i].size()) + gap;
}
mvwaddnstr(window, menu_height - 2, 2, "Left/Right move Enter select Esc close", menu_width - 4);
mvwaddnstr(window, menu_height - 2, 2, "Left/Right move Enter select Esc close",
menu_width - 4);
wrefresh(window);
delwin(window);
}
@@ -1414,7 +1458,8 @@ void App::draw_attachment_viewer(int height, int width) {
const int content_top = 3;
const int content_height = std::max(1, menu_height - 5);
const int max_scroll = std::max(0, static_cast<int>(attachment_viewer_lines_.size()) - content_height);
const int max_scroll =
std::max(0, static_cast<int>(attachment_viewer_lines_.size()) - content_height);
if (attachment_viewer_scroll_ > max_scroll) {
attachment_viewer_scroll_ = max_scroll;
}
@@ -1425,10 +1470,7 @@ void App::draw_attachment_viewer(int height, int width) {
if (line_index >= static_cast<int>(attachment_viewer_lines_.size())) {
continue;
}
mvwaddnstr(
window,
content_top + row,
2,
mvwaddnstr(window, content_top + row, 2,
attachment_viewer_lines_[static_cast<std::size_t>(line_index)].c_str(),
menu_width - 4);
}
@@ -1439,7 +1481,8 @@ void App::draw_attachment_viewer(int height, int width) {
mvwaddnstr(window, menu_height - 2, 2, controls.c_str(), menu_width - 4);
wrefresh(window);
if (attachment_viewer_attachment_.has_value()) {
render_attachment_preview_graphics(top + content_top, left + 2, menu_width - 4, content_height);
render_attachment_preview_graphics(top + content_top, left + 2, menu_width - 4,
content_height);
} else {
clear_attachment_preview_graphics();
}

View File

@@ -9,7 +9,8 @@ namespace telegram_tui {
namespace {
std::string format_download_progress(std::int64_t downloaded_size, std::int64_t size_bytes, bool is_downloaded) {
std::string format_download_progress(std::int64_t downloaded_size, std::int64_t size_bytes,
bool is_downloaded) {
if (is_downloaded) {
return "100%";
}
@@ -59,15 +60,19 @@ void App::handle_td_object(const json& object) {
return;
}
if (type == "updateUserStatus") {
update_user_status(safe_i64(object, "user_id"), object.value("status", json::object()));
update_user_status(safe_i64(object, "user_id"),
object.value("status", json::object()));
return;
}
if (type == "updateBasicGroup" || type == "basicGroup") {
upsert_basic_group(type == "basicGroup" ? object : object.value("basic_group", json::object()));
upsert_basic_group(type == "basicGroup"
? object
: object.value("basic_group", json::object()));
return;
}
if (type == "updateSupergroup" || type == "supergroup") {
upsert_supergroup(type == "supergroup" ? object : object.value("supergroup", json::object()));
upsert_supergroup(
type == "supergroup" ? object : object.value("supergroup", json::object()));
return;
}
if (type == "updateNewChat") {
@@ -91,7 +96,8 @@ void App::handle_td_object(const json& object) {
if (type == "updateChatLastMessage") {
ChatInfo &chat = chats_[safe_i64(object, "chat_id")];
chat.id = safe_i64(object, "chat_id");
chat.last_message_preview = preview_message(object.value("last_message", json::object()));
chat.last_message_preview =
preview_message(object.value("last_message", json::object()));
if (object.contains("positions") && object.at("positions").is_array()) {
chat.in_main_list = false;
chat.main_order = 0;
@@ -140,7 +146,8 @@ void App::handle_td_object(const json& object) {
if (message.id == message_id) {
const json content = object.value("new_content", json::object());
message.text = content_to_text(content, true);
if (const auto attachment = parse_attachment(content); attachment.has_value()) {
if (const auto attachment = parse_attachment(content);
attachment.has_value()) {
message.has_attachment = true;
message.attachment = *attachment;
} else {
@@ -393,7 +400,8 @@ void App::sync_chat_ids_from_response(const json& response) {
if (!chat_ids.empty()) {
sorted_chat_ids_ = std::move(chat_ids);
if (selected_chat_index_ >= static_cast<int>(sorted_chat_ids_.size())) {
selected_chat_index_ = std::max(0, static_cast<int>(sorted_chat_ids_.size()) - 1);
selected_chat_index_ =
std::max(0, static_cast<int>(sorted_chat_ids_.size()) - 1);
}
if (!open_chat_id().has_value()) {
const auto chat_id = highlighted_chat_id();
@@ -412,7 +420,8 @@ void App::sync_chat_ids_from_response(const json& response) {
void App::append_message(std::int64_t chat_id, MessageInfo message) {
ChatInfo &chat = chats_[chat_id];
chat.id = chat_id;
auto existing = std::find_if(chat.messages.begin(), chat.messages.end(),
auto existing =
std::find_if(chat.messages.begin(), chat.messages.end(),
[&](const MessageInfo &item) { return item.id == message.id; });
if (existing != chat.messages.end()) {
*existing = std::move(message);
@@ -429,10 +438,10 @@ void App::append_message(std::int64_t chat_id, MessageInfo message) {
});
if (kMaxMessagesPerChat > 0 && chat.messages.size() > kMaxMessagesPerChat) {
chat.messages.erase(
chat.messages.begin(),
chat.messages.erase(chat.messages.begin(),
chat.messages.begin() +
static_cast<std::ptrdiff_t>(chat.messages.size() - kMaxMessagesPerChat));
static_cast<std::ptrdiff_t>(chat.messages.size() -
kMaxMessagesPerChat));
}
}
@@ -448,9 +457,7 @@ void App::remove_message(std::int64_t chat_id, std::int64_t message_id) {
auto &messages = chat_it->second.messages;
messages.erase(
std::remove_if(
messages.begin(),
messages.end(),
std::remove_if(messages.begin(), messages.end(),
[&](const MessageInfo &item) { return item.id == message_id; }),
messages.end());
}
@@ -474,7 +481,8 @@ void App::update_attachment_file(const json& file) {
return;
}
const std::int64_t size_bytes = std::max(safe_i64(file, "size"), safe_i64(file, "expected_size"));
const std::int64_t size_bytes =
std::max(safe_i64(file, "size"), safe_i64(file, "expected_size"));
const json local = file.value("local", json::object());
const std::string local_path = safe_string(local, "path");
const std::int64_t downloaded_size = safe_i64(local, "downloaded_size");
@@ -502,7 +510,8 @@ void App::update_attachment_file(const json& file) {
}
if (pending_attachment_open_.has_value() && pending_attachment_open_->file_id == file_id) {
pending_attachment_open_->size_bytes = size_bytes > 0 ? size_bytes : pending_attachment_open_->size_bytes;
pending_attachment_open_->size_bytes =
size_bytes > 0 ? size_bytes : pending_attachment_open_->size_bytes;
pending_attachment_open_->downloaded_size = downloaded_size;
pending_attachment_open_->local_path = local_path;
pending_attachment_open_->is_downloading_active = is_downloading_active;
@@ -510,9 +519,9 @@ void App::update_attachment_file(const json& file) {
pending_attachment_open_->can_be_downloaded = can_be_downloaded;
pending_attachment_open_->can_be_deleted = can_be_deleted;
if (!is_downloaded && is_downloading_active) {
status_line_ = "Downloading attachment to open " +
format_download_progress(
pending_attachment_open_->downloaded_size,
status_line_ =
"Downloading attachment to open " +
format_download_progress(pending_attachment_open_->downloaded_size,
pending_attachment_open_->size_bytes,
pending_attachment_open_->is_downloaded);
}
@@ -522,7 +531,8 @@ void App::update_attachment_file(const json& file) {
}
}
if (pending_attachment_download_.has_value() && pending_attachment_download_->file_id == file_id) {
if (pending_attachment_download_.has_value() &&
pending_attachment_download_->file_id == file_id) {
pending_attachment_download_->size_bytes =
size_bytes > 0 ? size_bytes : pending_attachment_download_->size_bytes;
pending_attachment_download_->downloaded_size = downloaded_size;

View File

@@ -33,7 +33,8 @@ bool is_utf8_continuation(unsigned char ch) {
return (ch & 0xC0U) == 0x80U;
}
std::uint32_t decode_utf8_codepoint(const std::string& text, std::size_t offset, std::size_t* size_out = nullptr) {
std::uint32_t decode_utf8_codepoint(const std::string &text, std::size_t offset,
std::size_t *size_out = nullptr) {
if (offset >= text.size()) {
if (size_out != nullptr) {
*size_out = 0;
@@ -103,8 +104,8 @@ std::string single_line(std::string text) {
}
bool is_decimal_number(const std::string &value) {
return !value.empty() &&
std::all_of(value.begin(), value.end(), [](unsigned char ch) { return std::isdigit(ch); });
return !value.empty() && std::all_of(value.begin(), value.end(),
[](unsigned char ch) { return std::isdigit(ch); });
}
std::filesystem::path data_root() {

View File

@@ -30,10 +30,12 @@ bool save_app_config(const StoredConfig& config);
[[nodiscard]] std::string format_datetime(std::int32_t unix_time);
[[nodiscard]] std::string format_file_size(std::int64_t size_bytes);
[[nodiscard]] std::vector<std::string> wrap_text(const std::string &text, int width);
[[nodiscard]] std::size_t utf8_byte_index_from_utf16_offset(const std::string& text, std::size_t utf16_offset);
[[nodiscard]] std::size_t utf8_byte_index_from_utf16_offset(const std::string &text,
std::size_t utf16_offset);
[[nodiscard]] std::size_t utf8_prev_index(const std::string &text, std::size_t byte_index);
[[nodiscard]] std::size_t utf8_next_index(const std::string &text, std::size_t byte_index);
[[nodiscard]] int utf8_display_width(const std::string& text, std::size_t byte_limit = std::string::npos);
[[nodiscard]] int utf8_display_width(const std::string &text,
std::size_t byte_limit = std::string::npos);
void pop_utf8_back(std::string &text);
} // namespace telegram_tui