Git · DiffViewer and BlameView: every line, header and fold reads out to assistive tech
This commit is contained in:
3 files changed
+52
-20
No files matched your search
@@ -231,6 +231,9 @@ impl RenderOnce for BlameView {
|
|||||||
let text = lines[line].clone();
|
let text = lines[line].clone();
|
||||||
let styles = code_colors(&text, cx);
|
let styles = code_colors(&text, cx);
|
||||||
div()
|
div()
|
||||||
|
.id((id.clone(), format!("line-{line}")))
|
||||||
|
.role(Role::Label)
|
||||||
|
.aria_label(SharedString::from(format!("{} {text}", line + 1)))
|
||||||
.w_full()
|
.w_full()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
|
|||||||
@@ -94,3 +94,30 @@ pub(super) fn code(line: &DiffLine, colors: &Palette, cx: &App) -> StyledText {
|
|||||||
styles.sort_by_key(|(range, _)| range.start);
|
styles.sort_by_key(|(range, _)| range.start);
|
||||||
StyledText::new(line.text.clone()).with_highlights(stack(styles))
|
StyledText::new(line.text.clone()).with_highlights(stack(styles))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A line as assistive tech reads it: number, change, text.
|
||||||
|
pub(super) fn spoken(line: &DiffLine) -> SharedString {
|
||||||
|
let (number, sign) = match line.kind {
|
||||||
|
LineKind::Same => (line.new, ""),
|
||||||
|
LineKind::Added => (line.new, "+ "),
|
||||||
|
LineKind::Removed => (line.old, "− "),
|
||||||
|
};
|
||||||
|
let number = number.map_or_else(String::new, |n| format!("{n} "));
|
||||||
|
format!("{number}{sign}{}", line.text).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn a_line_speaks_its_number_change_and_text() {
|
||||||
|
let stretches = super::super::diff::diff("a\nb\n", "a\nc\n", 1);
|
||||||
|
let super::Stretch::Hunk { lines, .. } = &stretches[0] else {
|
||||||
|
panic!("a hunk")
|
||||||
|
};
|
||||||
|
let said: Vec<String> = lines
|
||||||
|
.iter()
|
||||||
|
.map(|line| super::spoken(line).to_string())
|
||||||
|
.collect();
|
||||||
|
assert_eq!(said, ["1 a", "2 − b", "2 + c"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
-20
@@ -9,7 +9,7 @@ use gpui::{
|
|||||||
use super::{
|
use super::{
|
||||||
badges::DiffStat,
|
badges::DiffStat,
|
||||||
diff::{DiffLine, LineKind, Stretch, diff, side_numbers},
|
diff::{DiffLine, LineKind, Stretch, diff, side_numbers},
|
||||||
rows::{Place, Shown, code, shown, washes},
|
rows::{Place, Shown, code, shown, spoken, washes},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
buttons::{Button, ButtonVariant, SegmentedControl},
|
buttons::{Button, ButtonVariant, SegmentedControl},
|
||||||
@@ -228,15 +228,10 @@ impl RenderOnce for DiffViewer {
|
|||||||
match (&on_line, line.kind) {
|
match (&on_line, line.kind) {
|
||||||
(Some(on_line), LineKind::Added | LineKind::Removed) => {
|
(Some(on_line), LineKind::Added | LineKind::Removed) => {
|
||||||
let on_line = on_line.clone();
|
let on_line = on_line.clone();
|
||||||
let sign = if line.kind == LineKind::Added {
|
|
||||||
"+"
|
|
||||||
} else {
|
|
||||||
"−"
|
|
||||||
};
|
|
||||||
element
|
element
|
||||||
.id((id.clone(), format!("line-{}-{}", place.0, place.1)))
|
.id((id.clone(), format!("line-{}-{}", place.0, place.1)))
|
||||||
.role(Role::ListItem)
|
.role(Role::ListItem)
|
||||||
.aria_label(SharedString::from(format!("{sign} {}", line.text)))
|
.aria_label(spoken(line))
|
||||||
.aria_selected(picked)
|
.aria_selected(picked)
|
||||||
.tab_index(0)
|
.tab_index(0)
|
||||||
.focus_ring(cx)
|
.focus_ring(cx)
|
||||||
@@ -248,7 +243,7 @@ impl RenderOnce for DiffViewer {
|
|||||||
on_line(place, event.modifiers().shift, window, cx)
|
on_line(place, event.modifiers().shift, window, cx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => element.id(own),
|
_ => element.id(own).role(Role::Label).aria_label(spoken(line)),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let side = |line: Option<&(Place, DiffLine)>,
|
let side = |line: Option<&(Place, DiffLine)>,
|
||||||
@@ -303,6 +298,8 @@ impl RenderOnce for DiffViewer {
|
|||||||
.on_click(move |_, window, cx| run(stretch, window, cx))
|
.on_click(move |_, window, cx| run(stretch, window, cx))
|
||||||
});
|
});
|
||||||
row.id(ix)
|
row.id(ix)
|
||||||
|
.role(Role::Heading)
|
||||||
|
.aria_label(header.clone())
|
||||||
.bg(colors.hover)
|
.bg(colors.hover)
|
||||||
.items_center()
|
.items_center()
|
||||||
.text_color(colors.fg_muted)
|
.text_color(colors.fg_muted)
|
||||||
@@ -346,7 +343,23 @@ impl RenderOnce for DiffViewer {
|
|||||||
}
|
}
|
||||||
Shown::Fold(stretch, count) => {
|
Shown::Fold(stretch, count) => {
|
||||||
let (open, stretch) = (on_open.clone(), *stretch);
|
let (open, stretch) = (on_open.clone(), *stretch);
|
||||||
|
let said = i18n::text(
|
||||||
|
cx,
|
||||||
|
if *count == 1 {
|
||||||
|
"git.diff.unchanged.one"
|
||||||
|
} else {
|
||||||
|
"git.diff.unchanged.other"
|
||||||
|
},
|
||||||
|
&[("n", &count.to_string())],
|
||||||
|
);
|
||||||
|
let role = if open.is_some() {
|
||||||
|
Role::Button
|
||||||
|
} else {
|
||||||
|
Role::Label
|
||||||
|
};
|
||||||
row.id((id.clone(), format!("fold-{stretch}")))
|
row.id((id.clone(), format!("fold-{stretch}")))
|
||||||
|
.role(role)
|
||||||
|
.aria_label(said.clone())
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.bg(colors.hover.opacity(0.5))
|
.bg(colors.hover.opacity(0.5))
|
||||||
.text_color(colors.fg_subtle)
|
.text_color(colors.fg_subtle)
|
||||||
@@ -362,18 +375,7 @@ impl RenderOnce for DiffViewer {
|
|||||||
open(stretch, window, cx)
|
open(stretch, window, cx)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.child(format!(
|
.child(format!("⋯ {said}"))
|
||||||
"⋯ {}",
|
|
||||||
i18n::text(
|
|
||||||
cx,
|
|
||||||
if *count == 1 {
|
|
||||||
"git.diff.unchanged.one"
|
|
||||||
} else {
|
|
||||||
"git.diff.unchanged.other"
|
|
||||||
},
|
|
||||||
&[("n", &count.to_string())],
|
|
||||||
)
|
|
||||||
))
|
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user