Code colors per language: comments, keywords and constants by file; Shell comments start words (Ely R2.12)
This commit is contained in:
6 files changed
+317
-88
No files matched your search
+57
-68
@@ -10,6 +10,7 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
editor::{code_colors_with, stack},
|
||||
forms::Syntax,
|
||||
theme::Palette,
|
||||
};
|
||||
|
||||
@@ -78,77 +79,24 @@ pub(super) fn washes(kind: LineKind, colors: &Palette) -> (Option<Hsla>, Hsla) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extensions of prose and plain text.
|
||||
const PROSE: [&str; 13] = [
|
||||
"md", "markdown", "mdown", "mkd", "txt", "text", "rst", "adoc", "asciidoc", "org", "csv",
|
||||
"tsv", "log",
|
||||
];
|
||||
|
||||
/// Line comment marks by extension; others take `//`.
|
||||
const COMMENTS: [(&str, &[&str]); 4] = [
|
||||
(
|
||||
"#",
|
||||
&[
|
||||
"py",
|
||||
"rb",
|
||||
"sh",
|
||||
"bash",
|
||||
"zsh",
|
||||
"fish",
|
||||
"toml",
|
||||
"yaml",
|
||||
"yml",
|
||||
"pl",
|
||||
"r",
|
||||
"conf",
|
||||
"nix",
|
||||
"ex",
|
||||
"exs",
|
||||
"cmake",
|
||||
"mk",
|
||||
"ps1",
|
||||
"tcl",
|
||||
"jl",
|
||||
"cr",
|
||||
"nim",
|
||||
"gitignore",
|
||||
],
|
||||
),
|
||||
("--", &["sql", "lua", "hs", "elm", "ada"]),
|
||||
(
|
||||
";",
|
||||
&["ini", "clj", "cljs", "lisp", "el", "scm", "asm", "s"],
|
||||
),
|
||||
("%", &["tex", "sty", "erl", "m"]),
|
||||
];
|
||||
|
||||
/// How `path` reads: none for prose, else its comment mark.
|
||||
pub(super) fn syntax(path: &str) -> Option<&'static str> {
|
||||
let name = path.rsplit_once('/').map_or(path, |(_, name)| name);
|
||||
let ext = match name.rsplit_once('.') {
|
||||
Some((stem, ext)) if !stem.is_empty() => ext.to_ascii_lowercase(),
|
||||
Some(("", dotfile)) => dotfile.to_ascii_lowercase(),
|
||||
_ if matches!(name, "Makefile" | "Dockerfile" | "CMakeLists.txt") => return Some("#"),
|
||||
_ => return None,
|
||||
};
|
||||
if PROSE.contains(&ext.as_str()) {
|
||||
return None;
|
||||
}
|
||||
let found = COMMENTS
|
||||
.iter()
|
||||
.find(|(_, exts)| exts.contains(&ext.as_str()));
|
||||
Some(found.map_or("//", |(mark, _)| *mark))
|
||||
}
|
||||
|
||||
/// A line's syntax colors as the file at `path` reads.
|
||||
pub(super) fn colors_of(text: &str, path: &str, cx: &App) -> Vec<(Range<usize>, HighlightStyle)> {
|
||||
syntax(path).map_or_else(Vec::new, |comment| code_colors_with(text, comment, cx))
|
||||
/// A line's syntax colors; none for prose.
|
||||
pub(super) fn colors_of(
|
||||
text: &str,
|
||||
syntax: Option<&Syntax>,
|
||||
cx: &App,
|
||||
) -> Vec<(Range<usize>, HighlightStyle)> {
|
||||
syntax.map_or_else(Vec::new, |syntax| code_colors_with(text, syntax, cx))
|
||||
}
|
||||
|
||||
/// A line colored as its file reads, changed words washed.
|
||||
pub(super) fn code(line: &DiffLine, colors: &Palette, path: &str, cx: &App) -> StyledText {
|
||||
pub(super) fn code(
|
||||
line: &DiffLine,
|
||||
colors: &Palette,
|
||||
syntax: Option<&Syntax>,
|
||||
cx: &App,
|
||||
) -> StyledText {
|
||||
let (_, word) = washes(line.kind, colors);
|
||||
let mut styles = colors_of(&line.text, path, cx);
|
||||
let mut styles = colors_of(&line.text, syntax, cx);
|
||||
styles.extend(line.words.iter().map(|range| {
|
||||
(
|
||||
range.clone(),
|
||||
@@ -181,7 +129,8 @@ mod tests {
|
||||
crate::init_for_tests(cx);
|
||||
let syntax = crate::theme::ActiveTheme::theme(cx).colors.syntax.clone();
|
||||
let color = |text: &str, path: &str, cx: &gpui::App| {
|
||||
super::colors_of(text, path, cx)
|
||||
let syntax = crate::forms::syntax_of(path);
|
||||
super::colors_of(text, syntax, cx)
|
||||
.into_iter()
|
||||
.map(|(range, style)| (range, style.color))
|
||||
.collect::<Vec<_>>()
|
||||
@@ -204,6 +153,8 @@ mod tests {
|
||||
"run.sh",
|
||||
".gitignore",
|
||||
"Makefile",
|
||||
"CMakeLists.txt",
|
||||
"build.cmake",
|
||||
] {
|
||||
assert_eq!(
|
||||
color("# return 42", path, cx),
|
||||
@@ -218,9 +169,47 @@ mod tests {
|
||||
let rust = color("return 42 // done", "src/main.rs", cx);
|
||||
assert_eq!(rust[0], (0..6, Some(syntax.keyword)), "{rust:?}");
|
||||
assert_eq!(rust.last(), Some(&(10..17, Some(syntax.comment))));
|
||||
// Each language's own words.
|
||||
let python = color("assert ready and True", "a.py", cx);
|
||||
assert_eq!(
|
||||
[python[0].1, python[2].1, python[3].1],
|
||||
[
|
||||
Some(syntax.keyword),
|
||||
Some(syntax.keyword),
|
||||
Some(syntax.constant)
|
||||
]
|
||||
);
|
||||
assert_ne!(
|
||||
color("return = 42", "config.toml", cx)[0].1,
|
||||
Some(syntax.keyword),
|
||||
"a TOML key"
|
||||
);
|
||||
// Shell comments start words only.
|
||||
let shell = color("echo foo#bar 42", "run.sh", cx);
|
||||
assert!(
|
||||
shell.iter().all(|(_, c)| *c != Some(syntax.comment)),
|
||||
"{shell:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
color("echo x # note", "run.sh", cx).last(),
|
||||
Some(&(7..13, Some(syntax.comment)))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_viewer_reads_its_file_once_built() {
|
||||
let viewer = |path: &str| {
|
||||
super::super::viewer::DiffViewer::new("d", path.to_string(), "a\n", "b\n").syntax
|
||||
};
|
||||
assert_eq!(viewer("notes.md"), None);
|
||||
assert_eq!(viewer("a.py"), crate::forms::syntax_of("a.py"));
|
||||
assert_eq!(
|
||||
viewer("CMakeLists.txt"),
|
||||
crate::forms::syntax_of("Makefile")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_line_speaks_its_number_change_and_text() {
|
||||
let stretches = super::super::diff::diff("a\nb\n", "a\nc\n", 1);
|
||||
|
||||
+9
-4
@@ -13,6 +13,7 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
buttons::{Button, ButtonVariant, SegmentedControl},
|
||||
forms::{Syntax, syntax_of},
|
||||
i18n,
|
||||
primitives::FocusRing,
|
||||
theme::{ActiveTheme, ControlSize, TextSize},
|
||||
@@ -38,6 +39,8 @@ type OnLine = Rc<dyn Fn(Place, bool, &mut Window, &mut App)>;
|
||||
pub struct DiffViewer {
|
||||
id: ElementId,
|
||||
path: SharedString,
|
||||
/// How its lines read, from the path.
|
||||
pub(super) syntax: Option<&'static Syntax>,
|
||||
stretches: Rc<Vec<Stretch>>,
|
||||
stat: (usize, usize),
|
||||
layout: DiffLayout,
|
||||
@@ -57,9 +60,11 @@ impl DiffViewer {
|
||||
old: &str,
|
||||
new: &str,
|
||||
) -> Self {
|
||||
let path: SharedString = path.into();
|
||||
Self {
|
||||
id: id.into(),
|
||||
path: path.into(),
|
||||
syntax: syntax_of(&path),
|
||||
path,
|
||||
stretches: Rc::new(diff(old, new, CONTEXT)),
|
||||
stat: super::diff::stat(old, new),
|
||||
layout: DiffLayout::Unified,
|
||||
@@ -194,7 +199,7 @@ impl RenderOnce for DiffViewer {
|
||||
.len()
|
||||
.max(2);
|
||||
let (id, on_open) = (self.id.clone(), self.on_open.clone());
|
||||
let path = self.path.clone();
|
||||
let syntax = self.syntax;
|
||||
let (button, button_height) = (
|
||||
self.hunk_action.is_some(),
|
||||
theme.control_height(ControlSize::Sm),
|
||||
@@ -272,7 +277,7 @@ impl RenderOnce for DiffViewer {
|
||||
.children(line.map(|(_, line)| {
|
||||
div()
|
||||
.whitespace_nowrap()
|
||||
.child(code(line, &colors, &path, cx))
|
||||
.child(code(line, &colors, syntax, cx))
|
||||
}));
|
||||
match line {
|
||||
Some((place, line)) => {
|
||||
@@ -331,7 +336,7 @@ impl RenderOnce for DiffViewer {
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.overflow_hidden()
|
||||
.child(code(line, &colors, &path, cx)),
|
||||
.child(code(line, &colors, syntax, cx)),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user