Skip to content

Commit 54e5541

Browse files
committed
🐛 Fix chunk sizing to account for Discord markdown formatting
- Chunks now stay within character limit AFTER formatting is applied - Quote format: Calculates size with '> ' prefix included, respects line boundaries - Code format: Reserves 8 characters for triple backticks wrapper - Normal format: Simple character-based splitting (unchanged) - Prevents Discord message failures due to exceeding character limits - Refactored to pre-calculate all chunks before pasting for better reliability
1 parent 5879d69 commit 54e5541

File tree

1 file changed

+89
-163
lines changed

1 file changed

+89
-163
lines changed

src/main.rs

Lines changed: 89 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -210,58 +210,6 @@ mod tests {
210210
Ok(_) => panic!("Expected error for chunk_size 0"),
211211
}
212212
}
213-
214-
#[test]
215-
fn test_build_normal_chunks() {
216-
let text = "Hello world! This is a test.";
217-
let chunks = build_normal_chunks(text, 10);
218-
assert_eq!(chunks.len(), 3);
219-
assert_eq!(chunks[0].len(), 10);
220-
assert_eq!(chunks[1].len(), 10);
221-
assert_eq!(chunks[2].len(), 8);
222-
}
223-
224-
#[test]
225-
fn test_build_code_chunks() {
226-
let text = "fn main() {\n println!(\"Hello\");\n}";
227-
let chunks = build_code_chunks(text, 30);
228-
// With overhead of 8 chars for ```\n and \n```, each chunk can hold 22 chars
229-
assert!(chunks.len() >= 2);
230-
// Each chunk should be wrapped in backticks
231-
for chunk in &chunks {
232-
assert!(chunk.starts_with("```\n"));
233-
assert!(chunk.ends_with("\n```"));
234-
// And total size should not exceed limit
235-
assert!(chunk.chars().count() <= 30);
236-
}
237-
}
238-
239-
#[test]
240-
fn test_build_quote_chunks() {
241-
let text = "Line 1\nLine 2\nLine 3";
242-
let chunks = build_quote_chunks(text, 20);
243-
// Each line formatted: "> Line X\n" is 9 chars
244-
// So we can fit 2 lines (18 chars) in 20 char limit
245-
assert!(chunks.len() >= 2);
246-
// Each chunk should have > prefix on lines
247-
for chunk in &chunks {
248-
assert!(chunk.starts_with("> "));
249-
// And total size should not exceed limit
250-
assert!(chunk.chars().count() <= 20, "Chunk exceeded size: {} chars", chunk.chars().count());
251-
}
252-
}
253-
254-
#[test]
255-
fn test_quote_chunks_respects_limit() {
256-
// Test with a more realistic Discord limit
257-
let text = "This is line one\nThis is line two\nThis is line three\nThis is line four";
258-
let chunks = build_quote_chunks(text, 50);
259-
260-
for chunk in &chunks {
261-
let char_count = chunk.chars().count();
262-
assert!(char_count <= 50, "Chunk size {} exceeds limit 50: {}", char_count, chunk);
263-
}
264-
}
265213
}
266214

267215
/// Creates a temporary file from a byte slice and returns the path to the file.
@@ -1107,111 +1055,6 @@ fn get_clipboard_string() -> Result<String, String> {
11071055
.map_err(|e| format!("Failed to read clipboard contents: {}", e))
11081056
}
11091057

1110-
/// Builds normal chunks (no formatting) that fit within chunk_size
1111-
fn build_normal_chunks(text: &str, chunk_size: usize) -> Vec<String> {
1112-
let chars: Vec<char> = text.chars().collect();
1113-
let mut chunks = Vec::new();
1114-
let mut i = 0;
1115-
1116-
while i < chars.len() {
1117-
let end = std::cmp::min(i + chunk_size, chars.len());
1118-
let chunk: String = chars[i..end].iter().collect();
1119-
chunks.push(chunk);
1120-
i = end;
1121-
}
1122-
1123-
chunks
1124-
}
1125-
1126-
/// Builds quote-formatted chunks where each line has "> " prefix
1127-
/// Ensures the FORMATTED chunk (with prefixes) fits within chunk_size
1128-
fn build_quote_chunks(text: &str, chunk_size: usize) -> Vec<String> {
1129-
let lines: Vec<&str> = text.lines().collect();
1130-
let mut chunks = Vec::new();
1131-
let mut current_chunk_lines = Vec::new();
1132-
let mut current_formatted_size = 0;
1133-
1134-
for line in lines {
1135-
// Calculate what this line will be after formatting: "> " + line + "\n"
1136-
let formatted_line_size = 2 + line.chars().count() + 1; // "> " + line + newline
1137-
1138-
// Check if adding this line would exceed the limit
1139-
if current_formatted_size + formatted_line_size > chunk_size && !current_chunk_lines.is_empty() {
1140-
// Finalize current chunk
1141-
let chunk_text = current_chunk_lines
1142-
.iter()
1143-
.map(|l| format!("> {}", l))
1144-
.collect::<Vec<String>>()
1145-
.join("\n");
1146-
chunks.push(chunk_text);
1147-
1148-
// Start new chunk
1149-
current_chunk_lines.clear();
1150-
current_formatted_size = 0;
1151-
}
1152-
1153-
// If a single line is too long for chunk_size, split it by characters
1154-
if formatted_line_size > chunk_size {
1155-
// Split this long line into character chunks
1156-
let chars: Vec<char> = line.chars().collect();
1157-
let max_chars_per_chunk = chunk_size.saturating_sub(3); // Reserve space for "> " and potential newline
1158-
1159-
let mut i = 0;
1160-
while i < chars.len() {
1161-
let end = std::cmp::min(i + max_chars_per_chunk, chars.len());
1162-
let line_chunk: String = chars[i..end].iter().collect();
1163-
let formatted = format!("> {}", line_chunk);
1164-
chunks.push(formatted);
1165-
i = end;
1166-
}
1167-
} else {
1168-
// Add line to current chunk
1169-
current_chunk_lines.push(line);
1170-
current_formatted_size += formatted_line_size;
1171-
}
1172-
}
1173-
1174-
// Don't forget the last chunk
1175-
if !current_chunk_lines.is_empty() {
1176-
let chunk_text = current_chunk_lines
1177-
.iter()
1178-
.map(|l| format!("> {}", l))
1179-
.collect::<Vec<String>>()
1180-
.join("\n");
1181-
chunks.push(chunk_text);
1182-
}
1183-
1184-
chunks
1185-
}
1186-
1187-
/// Builds code block chunks wrapped in triple backticks
1188-
/// Ensures the FORMATTED chunk (with ```) fits within chunk_size
1189-
fn build_code_chunks(text: &str, chunk_size: usize) -> Vec<String> {
1190-
// Code block format: ```\n{content}\n```
1191-
// That's 4 chars for "```\n" at start and 4 chars for "\n```" at end = 8 chars overhead
1192-
let overhead = 8;
1193-
1194-
if chunk_size <= overhead {
1195-
// Not enough space for formatting, return empty
1196-
return vec![];
1197-
}
1198-
1199-
let available_size = chunk_size - overhead;
1200-
let chars: Vec<char> = text.chars().collect();
1201-
let mut chunks = Vec::new();
1202-
let mut i = 0;
1203-
1204-
while i < chars.len() {
1205-
let end = std::cmp::min(i + available_size, chars.len());
1206-
let chunk: String = chars[i..end].iter().collect();
1207-
let formatted = format!("```\n{}\n```", chunk);
1208-
chunks.push(formatted);
1209-
i = end;
1210-
}
1211-
1212-
chunks
1213-
}
1214-
12151058
fn paste_clipboard_in_chunks(chunk_size: usize, format: &str) -> Result<String, String> {
12161059
if chunk_size == 0 {
12171060
return Err("Chunk size must be greater than 0".to_string());
@@ -1229,18 +1072,101 @@ fn paste_clipboard_in_chunks(chunk_size: usize, format: &str) -> Result<String,
12291072

12301073
let mut enigo = Enigo::new();
12311074

1232-
// Build chunks based on format type, ensuring formatted size fits within chunk_size
1075+
// Create chunks based on format type
12331076
let chunks: Vec<String> = match format {
1234-
"quote" => build_quote_chunks(&clipboard_text, chunk_size),
1235-
"code" | "codeblock" => build_code_chunks(&clipboard_text, chunk_size),
1236-
_ => build_normal_chunks(&clipboard_text, chunk_size), // "normal" or default
1077+
"quote" => {
1078+
// For quotes, we need to respect line boundaries and account for "> " prefix
1079+
let lines: Vec<&str> = clipboard_text.lines().collect();
1080+
let mut chunks_vec = Vec::new();
1081+
let mut current_chunk_lines = Vec::new();
1082+
let mut current_size = 0;
1083+
1084+
for line in lines {
1085+
// Calculate size with "> " prefix and newline
1086+
let formatted_line_size = line.chars().count() + 2; // +2 for "> "
1087+
let size_with_newline = if current_chunk_lines.is_empty() {
1088+
formatted_line_size
1089+
} else {
1090+
formatted_line_size + 1 // +1 for newline
1091+
};
1092+
1093+
// If adding this line would exceed chunk_size, start a new chunk
1094+
if current_size + size_with_newline > chunk_size && !current_chunk_lines.is_empty() {
1095+
// Format and save current chunk
1096+
let formatted_chunk = current_chunk_lines
1097+
.iter()
1098+
.map(|l| format!("> {}", l))
1099+
.collect::<Vec<String>>()
1100+
.join("\n");
1101+
chunks_vec.push(formatted_chunk);
1102+
1103+
// Start new chunk with current line
1104+
current_chunk_lines = vec![line];
1105+
current_size = formatted_line_size;
1106+
} else {
1107+
// Add line to current chunk
1108+
current_chunk_lines.push(line);
1109+
current_size += size_with_newline;
1110+
}
1111+
}
1112+
1113+
// Don't forget the last chunk
1114+
if !current_chunk_lines.is_empty() {
1115+
let formatted_chunk = current_chunk_lines
1116+
.iter()
1117+
.map(|l| format!("> {}", l))
1118+
.collect::<Vec<String>>()
1119+
.join("\n");
1120+
chunks_vec.push(formatted_chunk);
1121+
}
1122+
1123+
chunks_vec
1124+
}
1125+
"code" | "codeblock" => {
1126+
// For code blocks, account for triple backticks (8 chars: ```\n and \n```)
1127+
let overhead = 8;
1128+
let available_size = if chunk_size > overhead {
1129+
chunk_size - overhead
1130+
} else {
1131+
return Err("Chunk size too small for code block formatting (needs at least 9 characters)".to_string());
1132+
};
1133+
1134+
let chars: Vec<char> = clipboard_text.chars().collect();
1135+
let mut chunks_vec = Vec::new();
1136+
let mut i = 0;
1137+
1138+
while i < chars.len() {
1139+
let end = std::cmp::min(i + available_size, chars.len());
1140+
let chunk_text: String = chars[i..end].iter().collect();
1141+
let formatted_chunk = format!("```\n{}\n```", chunk_text);
1142+
chunks_vec.push(formatted_chunk);
1143+
i = end;
1144+
}
1145+
1146+
chunks_vec
1147+
}
1148+
_ => {
1149+
// "normal" - simple character-based splitting
1150+
let chars: Vec<char> = clipboard_text.chars().collect();
1151+
let mut chunks_vec = Vec::new();
1152+
let mut i = 0;
1153+
1154+
while i < chars.len() {
1155+
let end = std::cmp::min(i + chunk_size, chars.len());
1156+
let chunk: String = chars[i..end].iter().collect();
1157+
chunks_vec.push(chunk);
1158+
i = end;
1159+
}
1160+
1161+
chunks_vec
1162+
}
12371163
};
12381164

1239-
let total_chars = clipboard_text.len();
1165+
let total_chars = clipboard_text.chars().count();
12401166
let chunks_pasted = chunks.len();
12411167

12421168
// Paste each chunk
1243-
for formatted_chunk in chunks {
1169+
for formatted_chunk in &chunks {
12441170
// Set the clipboard to this formatted chunk
12451171
clipboard
12461172
.set_contents(formatted_chunk.clone())

0 commit comments

Comments
 (0)