Ferrite v0.2.6 adds comprehensive character encoding support, allowing users to open, edit, and save files in various encodings beyond UTF-8. The encoding is auto-detected on file open and can be manually changed via the status bar.
src/state.rs- Tab struct encoding fields, detection logic, encode/decode methodssrc/app.rs- Status bar encoding UI with dropdown pickerCargo.toml- Addedencoding_rsandchardetngdependencies
Three new fields added to Tab:
pub struct Tab {
// ... existing fields ...
/// Detected encoding when file was opened (e.g., "UTF-8", "WINDOWS-1252")
/// None for new/unsaved documents created in-app
pub detected_encoding: Option<&'static str>,
/// Original file bytes for re-decoding when user changes encoding
/// Empty for new documents created in-app
pub original_bytes: Vec<u8>,
/// Currently selected encoding label (used for save operations)
/// Defaults to "utf-8" for new documents
pub current_encoding: &'static str,
}- Read file as bytes -
std::fs::read(&path)instead ofread_to_string() - Check for BOM -
encoding_rs::Encoding::for_bom(&bytes)detects UTF-8/16 BOMs - Fallback to chardetng - Statistical detection for non-BOM files
- Decode to String -
encoding.decode_without_bom_handling()for BOM-detected files
// BOM detection takes priority
if let Some((bom_encoding, bom_len)) = encoding_rs::Encoding::for_bom(&bytes) {
let (decoded, _) = bom_encoding.decode_without_bom_handling(&bytes[bom_len..]);
// Use bom_encoding
} else {
// Use chardetng detection
let mut detector = chardetng::EncodingDetector::new();
detector.feed(&bytes, true);
let detected = detector.guess(None, true);
let (decoded, _, _) = detected.decode(&bytes);
}pub const COMMON_ENCODINGS: &'static [&'static str] = &[
"utf-8", "windows-1252", "iso-8859-1", "shift_jis",
"euc-jp", "gbk", "euc-kr", "iso-8859-15",
"utf-16le", "utf-16be",
];When saving, content is encoded from UTF-8 String to bytes using the selected encoding:
pub fn encode_content(&self) -> Vec<u8> {
if let Some(encoding) = encoding_rs::Encoding::for_label(self.current_encoding.as_bytes()) {
let (bytes, _, _) = encoding.encode(&self.content);
bytes.into_owned()
} else {
self.content.as_bytes().to_vec() // Fallback to UTF-8
}
}Users can re-decode file content with a different encoding:
pub fn set_encoding(&mut self, new_encoding: &'static str) -> Result<(), String> {
if self.original_bytes.is_empty() {
return Err("No original bytes available".to_string());
}
if let Some(encoding) = encoding_rs::Encoding::for_label(new_encoding.as_bytes()) {
let (decoded, _) = encoding.decode_without_bom_handling(&self.original_bytes);
self.content = decoded.into_owned();
self.current_encoding = new_encoding;
Ok(())
} else {
Err(format!("Unsupported encoding: {}", new_encoding))
}
}The encoding is displayed as a clickable button in the status bar. Clicking opens a dropdown with common encodings:
// In app.rs status bar
let encoding_btn = ui.button(tab.encoding_display_name());
if encoding_btn.clicked() {
ui.memory_mut(|mem| mem.toggle_popup(popup_id));
}
egui::popup_below_widget(ui, popup_id, &encoding_btn, |ui| {
for &enc in Tab::COMMON_ENCODINGS {
if ui.selectable_label(enc == tab.current_encoding, enc.to_uppercase()).clicked() {
pending_encoding_change = Some(enc);
ui.memory_mut(|mem| mem.close_popup());
}
}
});encoding_rs = "0.8"- Fast character encoding conversion (same library used by Firefox)chardetng = "0.1"- Encoding detection (same algorithm as Firefox)
When restoring sessions, files are re-read from disk with encoding detection:
enum ResolvedContent {
/// Content recovered from crash backup (already UTF-8)
Recovered(String),
/// Content loaded from disk with encoding info
FromDisk {
content: String,
original_bytes: Vec<u8>,
encoding: &'static str,
},
}Test files are provided in test_md/encoding_tests/:
| File | Encoding | Notes |
|---|---|---|
utf8_no_bom.md |
UTF-8 | Standard UTF-8 |
utf8_with_bom.md |
UTF-8 + BOM | EF BB BF prefix |
windows1252.txt |
Windows-1252 | Curly quotes, Euro symbol |
latin1.txt |
ISO-8859-1 | Western European |
shiftjis.txt |
Shift-JIS | Japanese |
eucjp.txt |
EUC-JP | Japanese Unix |
gbk.txt |
GBK | Simplified Chinese |
euckr.txt |
EUC-KR | Korean |
utf16le.txt |
UTF-16 LE | With FF FE BOM |
utf16be.txt |
UTF-16 BE | With FE FF BOM |
Regenerate with: python test_md/encoding_tests/create_test_files.py
- Open file - Encoding auto-detected, shown in status bar
- Check encoding - Look at status bar (e.g., "UTF-8", "SHIFT_JIS")
- Change encoding - Click encoding button, select from dropdown
- Save - File saved in currently selected encoding
- UTF-16 files require BOM for reliable detection
- Some similar encodings may be mis-detected (e.g., ISO-8859-1 vs Windows-1252)
- New documents always default to UTF-8
- Original bytes are kept in memory for re-decoding (increases memory per tab)