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
+12
-5
@@ -3,7 +3,7 @@ use std::ops::Range;
|
|||||||
use gpui::{App, HighlightStyle};
|
use gpui::{App, HighlightStyle};
|
||||||
|
|
||||||
use super::buffer::Buffer;
|
use super::buffer::Buffer;
|
||||||
use crate::forms::{Kind, code_highlights_with, lex};
|
use crate::forms::{Kind, Syntax, code_highlights_with, lex};
|
||||||
|
|
||||||
/// Each line that opens a block, and the last line the block holds, read from indentation.
|
/// Each line that opens a block, and the last line the block holds, read from indentation.
|
||||||
pub(crate) fn folds(buffer: &Buffer) -> Vec<(usize, usize)> {
|
pub(crate) fn folds(buffer: &Buffer) -> Vec<(usize, usize)> {
|
||||||
@@ -110,16 +110,23 @@ pub(crate) fn matched(brackets: &[Bracket], caret: usize) -> Option<(usize, usiz
|
|||||||
|
|
||||||
/// A line's syntax colors as highlight styles.
|
/// A line's syntax colors as highlight styles.
|
||||||
pub(crate) fn colors(text: &str, cx: &App) -> Vec<(Range<usize>, HighlightStyle)> {
|
pub(crate) fn colors(text: &str, cx: &App) -> Vec<(Range<usize>, HighlightStyle)> {
|
||||||
colors_with(text, "//", cx)
|
styles(crate::forms::code_highlights(text, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Colors for a line whose comments start with `comment`.
|
/// A line's colors as `lang` reads it.
|
||||||
pub(crate) fn colors_with(
|
pub(crate) fn colors_with(
|
||||||
text: &str,
|
text: &str,
|
||||||
comment: &str,
|
lang: &Syntax,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> Vec<(Range<usize>, HighlightStyle)> {
|
) -> Vec<(Range<usize>, HighlightStyle)> {
|
||||||
code_highlights_with(text, comment, cx)
|
styles(code_highlights_with(text, lang, cx))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Highlights as gpui styles.
|
||||||
|
fn styles(
|
||||||
|
highlights: Vec<(Range<usize>, crate::forms::Highlight)>,
|
||||||
|
) -> Vec<(Range<usize>, HighlightStyle)> {
|
||||||
|
highlights
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(range, highlight)| {
|
.map(|(range, highlight)| {
|
||||||
(
|
(
|
||||||
|
|||||||
+25
-11
@@ -5,7 +5,7 @@ use gpui::{
|
|||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{Highlight, Input, TextInput};
|
use super::{Highlight, Input, TextInput, syntax::Syntax};
|
||||||
use crate::theme::{ActiveTheme, ControlSize, TextSize};
|
use crate::theme::{ActiveTheme, ControlSize, TextSize};
|
||||||
|
|
||||||
/// What a span of source is.
|
/// What a span of source is.
|
||||||
@@ -42,13 +42,21 @@ const CONSTANTS: &[&str] = &[
|
|||||||
"FALSE",
|
"FALSE",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// Every language at once, for code of unknown kind.
|
||||||
|
const GENERIC: Syntax = Syntax {
|
||||||
|
comment: "//",
|
||||||
|
word_comment: false,
|
||||||
|
keywords: KEYWORDS,
|
||||||
|
constants: CONSTANTS,
|
||||||
|
};
|
||||||
|
|
||||||
/// Splits source into spans; spaces fall between them.
|
/// Splits source into spans; spaces fall between them.
|
||||||
pub(crate) fn lex(source: &str) -> Vec<(Range<usize>, Kind)> {
|
pub(crate) fn lex(source: &str) -> Vec<(Range<usize>, Kind)> {
|
||||||
lex_with(source, "//")
|
lex_with(source, &GENERIC)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Splits source whose line comments start with `comment`.
|
/// Splits source as `syntax` reads it.
|
||||||
pub(crate) fn lex_with(source: &str, comment: &str) -> Vec<(Range<usize>, Kind)> {
|
pub(crate) fn lex_with(source: &str, syntax: &Syntax) -> Vec<(Range<usize>, Kind)> {
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
let mut chars = source.char_indices().peekable();
|
let mut chars = source.char_indices().peekable();
|
||||||
while let Some((start, ch)) = chars.next() {
|
while let Some((start, ch)) = chars.next() {
|
||||||
@@ -66,7 +74,13 @@ pub(crate) fn lex_with(source: &str, comment: &str) -> Vec<(Range<usize>, Kind)>
|
|||||||
}
|
}
|
||||||
Kind::Text
|
Kind::Text
|
||||||
}
|
}
|
||||||
_ if source[start..].starts_with(comment) => {
|
_ if source[start..].starts_with(syntax.comment)
|
||||||
|
&& (!syntax.word_comment
|
||||||
|
|| source[..start]
|
||||||
|
.chars()
|
||||||
|
.next_back()
|
||||||
|
.is_none_or(char::is_whitespace)) =>
|
||||||
|
{
|
||||||
while chars.next_if(|(_, next)| *next != '\n').is_some() {}
|
while chars.next_if(|(_, next)| *next != '\n').is_some() {}
|
||||||
Kind::Comment
|
Kind::Comment
|
||||||
}
|
}
|
||||||
@@ -101,17 +115,17 @@ fn painted(spans: impl Iterator<Item = (Range<usize>, Hsla)>) -> Vec<(Range<usiz
|
|||||||
|
|
||||||
/// Colors for one line of code in most languages, from the syntax palette.
|
/// Colors for one line of code in most languages, from the syntax palette.
|
||||||
pub fn code_highlights(source: &str, cx: &App) -> Vec<(Range<usize>, Highlight)> {
|
pub fn code_highlights(source: &str, cx: &App) -> Vec<(Range<usize>, Highlight)> {
|
||||||
code_highlights_with(source, "//", cx)
|
code_highlights_with(source, &GENERIC, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Colors for code whose line comments start with `comment`.
|
/// Colors for code as `lang` reads it.
|
||||||
pub(crate) fn code_highlights_with(
|
pub(crate) fn code_highlights_with(
|
||||||
source: &str,
|
source: &str,
|
||||||
comment: &str,
|
lang: &Syntax,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> Vec<(Range<usize>, Highlight)> {
|
) -> Vec<(Range<usize>, Highlight)> {
|
||||||
let syntax = &cx.theme().colors.syntax;
|
let syntax = &cx.theme().colors.syntax;
|
||||||
let spans = lex_with(source, comment);
|
let spans = lex_with(source, lang);
|
||||||
let next_is = |ix: usize, mark: &str| {
|
let next_is = |ix: usize, mark: &str| {
|
||||||
spans
|
spans
|
||||||
.get(ix + 1)
|
.get(ix + 1)
|
||||||
@@ -123,8 +137,8 @@ pub(crate) fn code_highlights_with(
|
|||||||
Kind::Comment => syntax.comment,
|
Kind::Comment => syntax.comment,
|
||||||
Kind::Text => syntax.string,
|
Kind::Text => syntax.string,
|
||||||
Kind::Number => syntax.number,
|
Kind::Number => syntax.number,
|
||||||
Kind::Word if KEYWORDS.contains(&text) => syntax.keyword,
|
Kind::Word if lang.keywords.contains(&text) => syntax.keyword,
|
||||||
Kind::Word if CONSTANTS.contains(&text) => syntax.constant,
|
Kind::Word if lang.constants.contains(&text) => syntax.constant,
|
||||||
Kind::Word if next_is(ix, "(") => syntax.function,
|
Kind::Word if next_is(ix, "(") => syntax.function,
|
||||||
Kind::Word if text.starts_with(char::is_uppercase) => syntax.type_name,
|
Kind::Word if text.starts_with(char::is_uppercase) => syntax.type_name,
|
||||||
Kind::Word => syntax.variable,
|
Kind::Word => syntax.variable,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ mod slider;
|
|||||||
mod stepper;
|
mod stepper;
|
||||||
mod structure;
|
mod structure;
|
||||||
mod switch;
|
mod switch;
|
||||||
|
mod syntax;
|
||||||
mod tags;
|
mod tags;
|
||||||
#[cfg(all(test, feature = "test-support"))]
|
#[cfg(all(test, feature = "test-support"))]
|
||||||
mod tests;
|
mod tests;
|
||||||
@@ -112,6 +113,7 @@ pub use structure::{
|
|||||||
DirtyIndicator, Form, FormError, FormField, FormLabel, FormSection, InlineForm,
|
DirtyIndicator, Form, FormError, FormField, FormLabel, FormSection, InlineForm,
|
||||||
};
|
};
|
||||||
pub use switch::Switch;
|
pub use switch::Switch;
|
||||||
|
pub(crate) use syntax::{Syntax, syntax_of};
|
||||||
pub use tags::TagInput;
|
pub use tags::TagInput;
|
||||||
pub(crate) use text::{
|
pub(crate) use text::{
|
||||||
Backspace, Copy, Cut, Delete, DeleteWordLeft, Down, Enter, Redo, Submit, Undo, Up, bind_keys,
|
Backspace, Copy, Cut, Delete, DeleteWordLeft, Down, Enter, Redo, Submit, Undo, Up, bind_keys,
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
//! How one language's code reads, by file name.
|
||||||
|
|
||||||
|
/// A language's comments, keywords and constants.
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub(crate) struct Syntax {
|
||||||
|
/// The mark that opens a line comment.
|
||||||
|
pub comment: &'static str,
|
||||||
|
/// The mark counts only where a word starts (Shell).
|
||||||
|
pub word_comment: bool,
|
||||||
|
pub keywords: &'static [&'static str],
|
||||||
|
pub constants: &'static [&'static str],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// C, Rust, JavaScript, Go, Java, Swift and kin.
|
||||||
|
pub(crate) const C_LIKE: Syntax = Syntax {
|
||||||
|
comment: "//",
|
||||||
|
word_comment: false,
|
||||||
|
keywords: &[
|
||||||
|
"as",
|
||||||
|
"async",
|
||||||
|
"await",
|
||||||
|
"break",
|
||||||
|
"case",
|
||||||
|
"catch",
|
||||||
|
"class",
|
||||||
|
"const",
|
||||||
|
"continue",
|
||||||
|
"do",
|
||||||
|
"else",
|
||||||
|
"enum",
|
||||||
|
"export",
|
||||||
|
"extends",
|
||||||
|
"fn",
|
||||||
|
"for",
|
||||||
|
"from",
|
||||||
|
"func",
|
||||||
|
"function",
|
||||||
|
"if",
|
||||||
|
"impl",
|
||||||
|
"import",
|
||||||
|
"in",
|
||||||
|
"interface",
|
||||||
|
"let",
|
||||||
|
"match",
|
||||||
|
"mut",
|
||||||
|
"new",
|
||||||
|
"package",
|
||||||
|
"pub",
|
||||||
|
"return",
|
||||||
|
"static",
|
||||||
|
"struct",
|
||||||
|
"switch",
|
||||||
|
"throw",
|
||||||
|
"trait",
|
||||||
|
"try",
|
||||||
|
"type",
|
||||||
|
"use",
|
||||||
|
"var",
|
||||||
|
"where",
|
||||||
|
"while",
|
||||||
|
"yield",
|
||||||
|
],
|
||||||
|
constants: &[
|
||||||
|
"false",
|
||||||
|
"nil",
|
||||||
|
"null",
|
||||||
|
"self",
|
||||||
|
"Self",
|
||||||
|
"this",
|
||||||
|
"true",
|
||||||
|
"undefined",
|
||||||
|
"None",
|
||||||
|
"Some",
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const PYTHON: Syntax = Syntax {
|
||||||
|
comment: "#",
|
||||||
|
word_comment: false,
|
||||||
|
keywords: &[
|
||||||
|
"and", "as", "assert", "async", "await", "break", "class", "continue", "def", "del",
|
||||||
|
"elif", "else", "except", "finally", "for", "from", "global", "if", "import", "in", "is",
|
||||||
|
"lambda", "nonlocal", "not", "or", "pass", "raise", "return", "try", "while", "with",
|
||||||
|
"yield",
|
||||||
|
],
|
||||||
|
constants: &["True", "False", "None", "self"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const SHELL: Syntax = Syntax {
|
||||||
|
comment: "#",
|
||||||
|
word_comment: true,
|
||||||
|
keywords: &[
|
||||||
|
"case", "do", "done", "elif", "else", "esac", "export", "fi", "for", "function", "if",
|
||||||
|
"in", "local", "return", "then", "until", "while",
|
||||||
|
],
|
||||||
|
constants: &["true", "false"],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// TOML, YAML, INI and other settings: keys are not words.
|
||||||
|
const SETTINGS: Syntax = Syntax {
|
||||||
|
comment: "#",
|
||||||
|
word_comment: true,
|
||||||
|
keywords: &[],
|
||||||
|
constants: &["true", "false", "null", "yes", "no", "on", "off"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const INI: Syntax = Syntax {
|
||||||
|
comment: ";",
|
||||||
|
..SETTINGS
|
||||||
|
};
|
||||||
|
|
||||||
|
const SQL: Syntax = Syntax {
|
||||||
|
comment: "--",
|
||||||
|
word_comment: false,
|
||||||
|
keywords: &[
|
||||||
|
"SELECT", "FROM", "WHERE", "JOIN", "LEFT", "RIGHT", "INNER", "OUTER", "ON", "AND", "OR",
|
||||||
|
"NOT", "AS", "GROUP", "BY", "ORDER", "HAVING", "LIMIT", "OFFSET", "INSERT", "INTO",
|
||||||
|
"VALUES", "UPDATE", "SET", "DELETE", "CREATE", "TABLE", "INDEX", "DROP", "ALTER", "DESC",
|
||||||
|
"ASC", "DISTINCT", "UNION", "WITH", "CASE", "WHEN", "THEN", "ELSE", "END", "IS", "IN",
|
||||||
|
"LIKE", "BETWEEN", "EXISTS",
|
||||||
|
],
|
||||||
|
constants: &["NULL", "TRUE", "FALSE"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const LUA: Syntax = Syntax {
|
||||||
|
comment: "--",
|
||||||
|
word_comment: false,
|
||||||
|
keywords: &[
|
||||||
|
"and", "break", "do", "else", "elseif", "end", "for", "function", "goto", "if", "in",
|
||||||
|
"local", "not", "or", "repeat", "return", "then", "until", "while",
|
||||||
|
],
|
||||||
|
constants: &["true", "false", "nil"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const LISP: Syntax = Syntax {
|
||||||
|
comment: ";",
|
||||||
|
word_comment: false,
|
||||||
|
keywords: &[
|
||||||
|
"defun", "defn", "define", "lambda", "let", "if", "cond", "quote",
|
||||||
|
],
|
||||||
|
constants: &["nil", "t", "true", "false"],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Ruby, Perl, CMake and other `#` languages.
|
||||||
|
const HASH: Syntax = Syntax {
|
||||||
|
comment: "#",
|
||||||
|
..C_LIKE
|
||||||
|
};
|
||||||
|
|
||||||
|
const TEX: Syntax = Syntax {
|
||||||
|
comment: "%",
|
||||||
|
word_comment: false,
|
||||||
|
keywords: &[],
|
||||||
|
constants: &[],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Extensions of prose and plain text, which read plain.
|
||||||
|
const PROSE: &[&str] = &[
|
||||||
|
"md", "markdown", "mdown", "mkd", "txt", "text", "rst", "adoc", "asciidoc", "org", "csv",
|
||||||
|
"tsv", "log",
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Languages by extension; any other code reads C-like.
|
||||||
|
const BY_EXTENSION: &[(&Syntax, &[&str])] = &[
|
||||||
|
(&PYTHON, &["py", "pyi", "pyw"]),
|
||||||
|
(&SHELL, &["sh", "bash", "zsh", "fish", "ksh"]),
|
||||||
|
(
|
||||||
|
&HASH,
|
||||||
|
&[
|
||||||
|
"rb", "pl", "r", "cmake", "mk", "nix", "ex", "exs", "jl", "nim", "cr", "tcl", "ps1",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&SETTINGS,
|
||||||
|
&[
|
||||||
|
"toml",
|
||||||
|
"yaml",
|
||||||
|
"yml",
|
||||||
|
"conf",
|
||||||
|
"cfg",
|
||||||
|
"gitignore",
|
||||||
|
"env",
|
||||||
|
"properties",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(&INI, &["ini"]),
|
||||||
|
(&SQL, &["sql"]),
|
||||||
|
(&LUA, &["lua"]),
|
||||||
|
(&LISP, &["clj", "cljs", "lisp", "el", "scm"]),
|
||||||
|
(&TEX, &["tex", "sty", "bib"]),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// How the file at `path` reads; none for prose.
|
||||||
|
pub(crate) fn syntax_of(path: &str) -> Option<&'static Syntax> {
|
||||||
|
let name = path.rsplit_once('/').map_or(path, |(_, name)| name);
|
||||||
|
// Named files first: CMakeLists.txt is not prose.
|
||||||
|
match name {
|
||||||
|
"Makefile" | "GNUmakefile" | "Dockerfile" | "CMakeLists.txt" => return Some(&SHELL),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
let ext = match name.rsplit_once('.') {
|
||||||
|
Some((_, ext)) => ext.to_ascii_lowercase(),
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
if PROSE.contains(&ext.as_str()) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let found = BY_EXTENSION
|
||||||
|
.iter()
|
||||||
|
.find(|(_, exts)| exts.contains(&ext.as_str()));
|
||||||
|
Some(found.map_or(&C_LIKE, |(syntax, _)| *syntax))
|
||||||
|
}
|
||||||
+57
-68
@@ -10,6 +10,7 @@ use super::{
|
|||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
editor::{code_colors_with, stack},
|
editor::{code_colors_with, stack},
|
||||||
|
forms::Syntax,
|
||||||
theme::Palette,
|
theme::Palette,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -78,77 +79,24 @@ pub(super) fn washes(kind: LineKind, colors: &Palette) -> (Option<Hsla>, Hsla) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extensions of prose and plain text.
|
/// A line's syntax colors; none for prose.
|
||||||
const PROSE: [&str; 13] = [
|
pub(super) fn colors_of(
|
||||||
"md", "markdown", "mdown", "mkd", "txt", "text", "rst", "adoc", "asciidoc", "org", "csv",
|
text: &str,
|
||||||
"tsv", "log",
|
syntax: Option<&Syntax>,
|
||||||
];
|
cx: &App,
|
||||||
|
) -> Vec<(Range<usize>, HighlightStyle)> {
|
||||||
/// Line comment marks by extension; others take `//`.
|
syntax.map_or_else(Vec::new, |syntax| code_colors_with(text, syntax, cx))
|
||||||
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 colored as its file reads, changed words washed.
|
/// 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 (_, 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| {
|
styles.extend(line.words.iter().map(|range| {
|
||||||
(
|
(
|
||||||
range.clone(),
|
range.clone(),
|
||||||
@@ -181,7 +129,8 @@ mod tests {
|
|||||||
crate::init_for_tests(cx);
|
crate::init_for_tests(cx);
|
||||||
let syntax = crate::theme::ActiveTheme::theme(cx).colors.syntax.clone();
|
let syntax = crate::theme::ActiveTheme::theme(cx).colors.syntax.clone();
|
||||||
let color = |text: &str, path: &str, cx: &gpui::App| {
|
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()
|
.into_iter()
|
||||||
.map(|(range, style)| (range, style.color))
|
.map(|(range, style)| (range, style.color))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
@@ -204,6 +153,8 @@ mod tests {
|
|||||||
"run.sh",
|
"run.sh",
|
||||||
".gitignore",
|
".gitignore",
|
||||||
"Makefile",
|
"Makefile",
|
||||||
|
"CMakeLists.txt",
|
||||||
|
"build.cmake",
|
||||||
] {
|
] {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
color("# return 42", path, cx),
|
color("# return 42", path, cx),
|
||||||
@@ -218,9 +169,47 @@ mod tests {
|
|||||||
let rust = color("return 42 // done", "src/main.rs", cx);
|
let rust = color("return 42 // done", "src/main.rs", cx);
|
||||||
assert_eq!(rust[0], (0..6, Some(syntax.keyword)), "{rust:?}");
|
assert_eq!(rust[0], (0..6, Some(syntax.keyword)), "{rust:?}");
|
||||||
assert_eq!(rust.last(), Some(&(10..17, Some(syntax.comment))));
|
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]
|
#[test]
|
||||||
fn a_line_speaks_its_number_change_and_text() {
|
fn a_line_speaks_its_number_change_and_text() {
|
||||||
let stretches = super::super::diff::diff("a\nb\n", "a\nc\n", 1);
|
let stretches = super::super::diff::diff("a\nb\n", "a\nc\n", 1);
|
||||||
|
|||||||
+9
-4
@@ -13,6 +13,7 @@ use super::{
|
|||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
buttons::{Button, ButtonVariant, SegmentedControl},
|
buttons::{Button, ButtonVariant, SegmentedControl},
|
||||||
|
forms::{Syntax, syntax_of},
|
||||||
i18n,
|
i18n,
|
||||||
primitives::FocusRing,
|
primitives::FocusRing,
|
||||||
theme::{ActiveTheme, ControlSize, TextSize},
|
theme::{ActiveTheme, ControlSize, TextSize},
|
||||||
@@ -38,6 +39,8 @@ type OnLine = Rc<dyn Fn(Place, bool, &mut Window, &mut App)>;
|
|||||||
pub struct DiffViewer {
|
pub struct DiffViewer {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
path: SharedString,
|
path: SharedString,
|
||||||
|
/// How its lines read, from the path.
|
||||||
|
pub(super) syntax: Option<&'static Syntax>,
|
||||||
stretches: Rc<Vec<Stretch>>,
|
stretches: Rc<Vec<Stretch>>,
|
||||||
stat: (usize, usize),
|
stat: (usize, usize),
|
||||||
layout: DiffLayout,
|
layout: DiffLayout,
|
||||||
@@ -57,9 +60,11 @@ impl DiffViewer {
|
|||||||
old: &str,
|
old: &str,
|
||||||
new: &str,
|
new: &str,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let path: SharedString = path.into();
|
||||||
Self {
|
Self {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
path: path.into(),
|
syntax: syntax_of(&path),
|
||||||
|
path,
|
||||||
stretches: Rc::new(diff(old, new, CONTEXT)),
|
stretches: Rc::new(diff(old, new, CONTEXT)),
|
||||||
stat: super::diff::stat(old, new),
|
stat: super::diff::stat(old, new),
|
||||||
layout: DiffLayout::Unified,
|
layout: DiffLayout::Unified,
|
||||||
@@ -194,7 +199,7 @@ impl RenderOnce for DiffViewer {
|
|||||||
.len()
|
.len()
|
||||||
.max(2);
|
.max(2);
|
||||||
let (id, on_open) = (self.id.clone(), self.on_open.clone());
|
let (id, on_open) = (self.id.clone(), self.on_open.clone());
|
||||||
let path = self.path.clone();
|
let syntax = self.syntax;
|
||||||
let (button, button_height) = (
|
let (button, button_height) = (
|
||||||
self.hunk_action.is_some(),
|
self.hunk_action.is_some(),
|
||||||
theme.control_height(ControlSize::Sm),
|
theme.control_height(ControlSize::Sm),
|
||||||
@@ -272,7 +277,7 @@ impl RenderOnce for DiffViewer {
|
|||||||
.children(line.map(|(_, line)| {
|
.children(line.map(|(_, line)| {
|
||||||
div()
|
div()
|
||||||
.whitespace_nowrap()
|
.whitespace_nowrap()
|
||||||
.child(code(line, &colors, &path, cx))
|
.child(code(line, &colors, syntax, cx))
|
||||||
}));
|
}));
|
||||||
match line {
|
match line {
|
||||||
Some((place, line)) => {
|
Some((place, line)) => {
|
||||||
@@ -331,7 +336,7 @@ impl RenderOnce for DiffViewer {
|
|||||||
.flex_1()
|
.flex_1()
|
||||||
.min_w_0()
|
.min_w_0()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.child(code(line, &colors, &path, cx)),
|
.child(code(line, &colors, syntax, cx)),
|
||||||
)
|
)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user