- 1
//! How a workspace file should be presented. - 2
//! - 3
//! Shared by every surface: the desktop editor, the HTTP layer and the TUI - 4
//! `/view` command must agree on what counts as text, or one of them will - 5
//! happily render — or worse, save over — bytes it cannot represent. - 6
- 7
use std::path::Path; - 8
- 9
/// Editable text, a renderable image, or opaque bytes. - 10
#[derive(Debug, Clone, Copy, PartialEq, Eq)] - 11
pub enum FileKind { - 12
Text, - 13
Image, - 14
Binary, - 15
} - 16
- 17
impl FileKind { - 18
pub fn as_str(self) -> &'static str { - 19
match self { - 20
FileKind::Text => "text", - 21
FileKind::Image => "image", - 22
FileKind::Binary => "binary", - 23
} - 24
} - 25
- 26
/// Only text can be round-tripped through an editor without loss. - 27
pub fn editable(self) -> bool { - 28
self == FileKind::Text - 29
} - 30
} - 31
- 32
fn extension_of(path: &Path) -> String { - 33
path.extension() - 34
.and_then(|e| e.to_str()) - 35
.unwrap_or("") - 36
.to_ascii_lowercase() - 37
} - 38
- 39
/// Content type for an image path, for building data URLs. - 40
pub fn mime_for(path: &Path) -> &'static str { - 41
match extension_of(path).as_str() { - 42
"png" => "image/png", - 43
"jpg" | "jpeg" => "image/jpeg", - 44
"gif" => "image/gif", - 45
"webp" => "image/webp", - 46
"bmp" => "image/bmp", - 47
"ico" => "image/x-icon", - 48
"svg" => "image/svg+xml", - 49
_ => "application/octet-stream", - 50
} - 51
} - 52
- 53
/// Classify by extension first, then by content: a NUL byte or invalid UTF-8 - 54
/// means the bytes are not text whatever the name claims. - 55
pub fn classify(path: &Path, bytes: &[u8]) -> FileKind { - 56
if matches!( - 57
extension_of(path).as_str(), - 58
"png" | "jpg" | "jpeg" | "gif" | "webp" | "bmp" | "ico" - 59
) { - 60
return FileKind::Image; - 61
} - 62
// SVG is XML: renderable *and* meaningfully editable, so it stays text. - 63
if bytes.contains(&0) || std::str::from_utf8(bytes).is_err() { - 64
return FileKind::Binary; - 65
} - 66
FileKind::Text - 67
} - 68
- 69
/// Human-readable byte count for status lines. - 70
pub fn format_bytes(n: u64) -> String { - 71
if n < 1024 { - 72
format!("{n} B") - 73
} else if n < 1024 * 1024 { - 74
format!("{:.1} KB", n as f64 / 1024.0) - 75
} else { - 76
format!("{:.1} MB", n as f64 / (1024.0 * 1024.0)) - 77
} - 78
} - 79
- 80
#[cfg(test)] - 81
mod tests { - 82
use super::*; - 83
- 84
#[test] - 85
fn nul_bytes_are_binary_even_with_a_text_extension() { - 86
assert_eq!(classify(Path::new("a.txt"), b"ok\0bad"), FileKind::Binary); - 87
} - 88
- 89
#[test] - 90
fn image_extensions_classify_before_content() { - 91
assert_eq!(classify(Path::new("a.png"), b"\x89PNG"), FileKind::Image); - 92
} - 93
- 94
#[test] - 95
fn svg_stays_editable_text() { - 96
let k = classify(Path::new("a.svg"), b"<svg></svg>"); - 97
assert_eq!(k, FileKind::Text); - 98
assert!(k.editable()); - 99
} - 100
- 101
#[test] - 102
fn invalid_utf8_is_binary() { - 103
assert_eq!( - 104
classify(Path::new("a.dat"), &[0xff, 0xfe]), - 105
FileKind::Binary - 106
); - 107
} - 108
- 109
#[test] - 110
fn plain_source_is_text() { - 111
assert_eq!(classify(Path::new("a.py"), b"print(1)\n"), FileKind::Text); - 112
} - 113
} - 114
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.