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("")
|
.unwrap_or("")
|
||||||
.to_lowercase();
|
.to_lowercase();
|
||||||
match ext.as_str() {
|
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" => "🦀",
|
"rs" => "🦀",
|
||||||
"png" | "jpg" | "jpeg" | "gif" | "bmp" | "svg" | "webp" | "ico" => "🖼",
|
"png" | "jpg" | "jpeg" | "gif" | "bmp" | "svg" | "webp" | "ico" => "📷",
|
||||||
"mp4" | "mkv" | "avi" | "mov" | "webm" => "🎬",
|
"mp4" | "mkv" | "avi" | "mov" | "webm" => "🎬",
|
||||||
"mp3" | "flac" | "ogg" | "wav" | "aac" => "🎵",
|
"mp3" | "flac" | "ogg" | "wav" | "aac" => "🎵",
|
||||||
"zip" | "tar" | "gz" | "bz2" | "xz" | "7z" | "rar" => "📦",
|
"zip" | "tar" | "gz" | "bz2" | "xz" | "7z" | "rar" => "📦",
|
||||||
"pdf" => "📕",
|
"pdf" => "📕",
|
||||||
"txt" | "md" | "log" => "📄",
|
"txt" | "md" | "log" => "📄",
|
||||||
"json" | "yaml" | "yml" | "toml" | "xml" => "⚙",
|
"json" | "yaml" | "yml" | "toml" | "xml" => "🔩",
|
||||||
"sh" | "bash" | "zsh" | "fish" => "🐚",
|
"sh" | "bash" | "zsh" | "fish" => "🐚",
|
||||||
"py" => "🐍",
|
"py" => "🐍",
|
||||||
"js" | "ts" => "📜",
|
"js" | "ts" => "📜",
|
||||||
"c" | "cpp" | "h" | "hpp" => "🔧",
|
"c" | "cpp" | "h" | "hpp" => "🔧",
|
||||||
"exe" | "bin" | "out" | "run" => "⚙",
|
"exe" | "bin" | "out" | "run" => "⚡",
|
||||||
_ => "📎",
|
_ => "📎",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,3 +299,56 @@ pub fn get_root_dirs() -> Vec<PathBuf> {
|
|||||||
drives
|
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 ──────────────────────────────────────────────────────────
|
// ── Go-up button ──────────────────────────────────────────────────────────
|
||||||
if app.unicode_support {
|
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![
|
lines.push(Line::from(vec![
|
||||||
Span::raw(" "),
|
Span::raw(" "),
|
||||||
Span::styled("⬆", go_up_s),
|
Span::styled("↑", go_up_s),
|
||||||
Span::styled(" Gå upp", go_up_s),
|
Span::styled(" Gå upp", go_up_s),
|
||||||
]));
|
]));
|
||||||
} else {
|
} else {
|
||||||
@@ -496,8 +499,11 @@ fn draw_bottom_bar(f: &mut Frame, app: &App, area: Rect) {
|
|||||||
|
|
||||||
// Gear button at bottom-right
|
// Gear button at bottom-right
|
||||||
let gear_x = inner.x + inner.width.saturating_sub(5);
|
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 {
|
let (gear_label, gear_w) = if app.unicode_support {
|
||||||
(" \u{2699} ", 4u16)
|
(" \u{2261} ", 4u16)
|
||||||
} else {
|
} else {
|
||||||
(" [S] ", 5u16)
|
(" [S] ", 5u16)
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user