Replace ambiguous-width icons that shift sidebar/file rows right
⬆ (U+2B06), 🖼 (U+1F5BC) and ⚙ (U+2699) are East-Asian-Width Neutral: unicode-width measures 1 column but emoji fonts draw 2, so every row containing them shifted right by one column. Replaced with 真 Wide emoji (📷, 🔩, ⚡) or pure text glyphs (↑, ≡), plus a regression test asserting every emoji icon measures 2 columns. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
@@ -30,19 +30,24 @@ impl FileEntry {
|
||||
.unwrap_or("")
|
||||
.to_lowercase();
|
||||
match ext.as_str() {
|
||||
// OBS: alla emoji-ikoner MÅSTE vara East-Asian-Width Wide
|
||||
// (mäts som 2 kolumner av unicode-width). Tecken med
|
||||
// EAW=Neutral/Ambiguous (t.ex. 🖼 U+1F5BC, ⚙ U+2699)
|
||||
// mäts som 1 men ritas 2 breda av emoji-fonter →
|
||||
// raden förskjuts åt höger. Se icon_widths-testet.
|
||||
"rs" => "🦀",
|
||||
"png" | "jpg" | "jpeg" | "gif" | "bmp" | "svg" | "webp" | "ico" => "🖼",
|
||||
"png" | "jpg" | "jpeg" | "gif" | "bmp" | "svg" | "webp" | "ico" => "📷",
|
||||
"mp4" | "mkv" | "avi" | "mov" | "webm" => "🎬",
|
||||
"mp3" | "flac" | "ogg" | "wav" | "aac" => "🎵",
|
||||
"zip" | "tar" | "gz" | "bz2" | "xz" | "7z" | "rar" => "📦",
|
||||
"pdf" => "📕",
|
||||
"txt" | "md" | "log" => "📄",
|
||||
"json" | "yaml" | "yml" | "toml" | "xml" => "⚙",
|
||||
"json" | "yaml" | "yml" | "toml" | "xml" => "🔩",
|
||||
"sh" | "bash" | "zsh" | "fish" => "🐚",
|
||||
"py" => "🐍",
|
||||
"js" | "ts" => "📜",
|
||||
"c" | "cpp" | "h" | "hpp" => "🔧",
|
||||
"exe" | "bin" | "out" | "run" => "⚙",
|
||||
"exe" | "bin" | "out" | "run" => "⚡",
|
||||
_ => "📎",
|
||||
}
|
||||
}
|
||||
@@ -294,3 +299,56 @@ pub fn get_root_dirs() -> Vec<PathBuf> {
|
||||
drives
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
fn entry(name: &str, ft: FileType) -> FileEntry {
|
||||
FileEntry {
|
||||
name: name.to_string(),
|
||||
path: PathBuf::from(name),
|
||||
file_type: ft,
|
||||
size: 0,
|
||||
is_hidden: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Alla emoji-ikoner måste vara East-Asian-Width Wide (mäts som
|
||||
/// 2 kolumner av unicode-width). En ikon med EAW=Neutral/Ambiguous
|
||||
/// (t.ex. 🖼 U+1F5BC eller ⚙ U+2699) mäts som 1 men ritas 2 bred av
|
||||
/// emoji-fonter — då förskjuts allt efter ikonen åt höger i UI:t.
|
||||
#[test]
|
||||
fn emoji_icons_are_wide() {
|
||||
let mut samples: Vec<FileEntry> = vec![
|
||||
entry("dir", FileType::Directory),
|
||||
entry("link", FileType::Symlink),
|
||||
];
|
||||
for ext in [
|
||||
"rs", "png", "mp4", "mp3", "zip", "pdf", "txt", "json", "sh",
|
||||
"py", "js", "c", "exe", "unknown",
|
||||
] {
|
||||
samples.push(entry(&format!("f.{}", ext), FileType::File));
|
||||
}
|
||||
for e in &samples {
|
||||
let icon = e.icon();
|
||||
assert_eq!(
|
||||
UnicodeWidthStr::width(icon), 2,
|
||||
"ikonen {:?} (för {:?}) är inte EAW Wide — den kommer \
|
||||
förskjuta raden åt höger i terminaler med emoji-font",
|
||||
icon, e.name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// ASCII-fallback-ikonerna ska alltid vara exakt 3 tecken ([/],
|
||||
/// [s], ...) så att kolumnerna ligger fast i ASCII-läge.
|
||||
#[test]
|
||||
fn ascii_icons_are_three_cols() {
|
||||
let e = entry("f.rs", FileType::File);
|
||||
assert_eq!(UnicodeWidthStr::width(e.icon_ascii()), 3);
|
||||
let d = entry("dir", FileType::Directory);
|
||||
assert_eq!(UnicodeWidthStr::width(d.icon_ascii()), 3);
|
||||
}
|
||||
}
|
||||
|
||||
10
src/ui.rs
10
src/ui.rs
@@ -156,9 +156,12 @@ fn draw_sidebar(f: &mut Frame, app: &App, area: Rect) {
|
||||
|
||||
// ── Go-up button ──────────────────────────────────────────────────────────
|
||||
if app.unicode_support {
|
||||
// ↑ U+2191 är ett rent textglyf (ingen emoji-presentation) →
|
||||
// ritas alltid 1 kolumn brett, till skillnad från ⬆ U+2B06 som
|
||||
// emoji-fonter ritar 2 brett fast unicode-width mäter 1.
|
||||
lines.push(Line::from(vec![
|
||||
Span::raw(" "),
|
||||
Span::styled("⬆", go_up_s),
|
||||
Span::styled("↑", go_up_s),
|
||||
Span::styled(" Gå upp", go_up_s),
|
||||
]));
|
||||
} else {
|
||||
@@ -496,8 +499,11 @@ fn draw_bottom_bar(f: &mut Frame, app: &App, area: Rect) {
|
||||
|
||||
// Gear button at bottom-right
|
||||
let gear_x = inner.x + inner.width.saturating_sub(5);
|
||||
// ≡ U+2261 är ett textglyf som alla monospace-fonter ritar 1 brett;
|
||||
// ⚙ U+2699 (EAW=Neutral) ritas 2 brett av emoji-fonter och förskjuter
|
||||
// raden åt höger.
|
||||
let (gear_label, gear_w) = if app.unicode_support {
|
||||
(" \u{2699} ", 4u16)
|
||||
(" \u{2261} ", 4u16)
|
||||
} else {
|
||||
(" [S] ", 5u16)
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user