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
+25
-11
@@ -5,7 +5,7 @@ use gpui::{
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
use super::{Highlight, Input, TextInput};
|
||||
use super::{Highlight, Input, TextInput, syntax::Syntax};
|
||||
use crate::theme::{ActiveTheme, ControlSize, TextSize};
|
||||
|
||||
/// What a span of source is.
|
||||
@@ -42,13 +42,21 @@ const CONSTANTS: &[&str] = &[
|
||||
"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.
|
||||
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`.
|
||||
pub(crate) fn lex_with(source: &str, comment: &str) -> Vec<(Range<usize>, Kind)> {
|
||||
/// Splits source as `syntax` reads it.
|
||||
pub(crate) fn lex_with(source: &str, syntax: &Syntax) -> Vec<(Range<usize>, Kind)> {
|
||||
let mut out = Vec::new();
|
||||
let mut chars = source.char_indices().peekable();
|
||||
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
|
||||
}
|
||||
_ 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() {}
|
||||
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.
|
||||
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(
|
||||
source: &str,
|
||||
comment: &str,
|
||||
lang: &Syntax,
|
||||
cx: &App,
|
||||
) -> Vec<(Range<usize>, Highlight)> {
|
||||
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| {
|
||||
spans
|
||||
.get(ix + 1)
|
||||
@@ -123,8 +137,8 @@ pub(crate) fn code_highlights_with(
|
||||
Kind::Comment => syntax.comment,
|
||||
Kind::Text => syntax.string,
|
||||
Kind::Number => syntax.number,
|
||||
Kind::Word if KEYWORDS.contains(&text) => syntax.keyword,
|
||||
Kind::Word if CONSTANTS.contains(&text) => syntax.constant,
|
||||
Kind::Word if lang.keywords.contains(&text) => syntax.keyword,
|
||||
Kind::Word if lang.constants.contains(&text) => syntax.constant,
|
||||
Kind::Word if next_is(ix, "(") => syntax.function,
|
||||
Kind::Word if text.starts_with(char::is_uppercase) => syntax.type_name,
|
||||
Kind::Word => syntax.variable,
|
||||
|
||||
@@ -37,6 +37,7 @@ mod slider;
|
||||
mod stepper;
|
||||
mod structure;
|
||||
mod switch;
|
||||
mod syntax;
|
||||
mod tags;
|
||||
#[cfg(all(test, feature = "test-support"))]
|
||||
mod tests;
|
||||
@@ -112,6 +113,7 @@ pub use structure::{
|
||||
DirtyIndicator, Form, FormError, FormField, FormLabel, FormSection, InlineForm,
|
||||
};
|
||||
pub use switch::Switch;
|
||||
pub(crate) use syntax::{Syntax, syntax_of};
|
||||
pub use tags::TagInput;
|
||||
pub(crate) use text::{
|
||||
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))
|
||||
}
|
||||
Reference in new issue
Block a user