Convert query capture indices to style ids

* Introduce a Theme struct as a new part of the app's settings
* Store on each Language a ThemeMap, which converts the capture ids
  from that language's highlight query into StyleIds, which identify
  styles in the current Theme.
* Update `highlighted_chunks` methods to provide StyleIds instead of
  capture ids.
This commit is contained in:
Max Brunsfeld 2021-05-24 15:38:43 -07:00
parent bc8d2b2f1d
commit 8340958b33
11 changed files with 389 additions and 26 deletions

12
Cargo.lock generated
View file

@ -347,7 +347,7 @@ dependencies = [
"tar", "tar",
"target_build_utils", "target_build_utils",
"term", "term",
"toml", "toml 0.4.10",
"uuid", "uuid",
"walkdir", "walkdir",
] ]
@ -2702,6 +2702,15 @@ dependencies = [
"serde 1.0.125", "serde 1.0.125",
] ]
[[package]]
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde 1.0.125",
]
[[package]] [[package]]
name = "tree-sitter" name = "tree-sitter"
version = "0.19.5" version = "0.19.5"
@ -2996,6 +3005,7 @@ dependencies = [
"smallvec", "smallvec",
"smol", "smol",
"tempdir", "tempdir",
"toml 0.5.8",
"tree-sitter", "tree-sitter",
"tree-sitter-rust", "tree-sitter-rust",
"unindent", "unindent",

View file

@ -38,6 +38,7 @@ similar = "1.3"
simplelog = "0.9" simplelog = "0.9"
smallvec = {version = "1.6", features = ["union"]} smallvec = {version = "1.6", features = ["union"]}
smol = "1.2.5" smol = "1.2.5"
toml = "0.5"
tree-sitter = "0.19.5" tree-sitter = "0.19.5"
tree-sitter-rust = "0.19.0" tree-sitter-rust = "0.19.0"

View file

@ -0,0 +1,13 @@
[ui]
background = 0xffffff
line_numbers = 0x237791
text = 0x0d0d0d
[syntax]
keyword = 0xaf00db
function = 0x795e26
string = 0xa31515
type = 0x267599
number = 0x0d885b
comment = 0x048204
property = 0x001080

View file

@ -1,6 +1,49 @@
(type_identifier) @type
(call_expression
function: [
(identifier) @function
(scoped_identifier
name: (identifier) @function)
(field_expression
field: (field_identifier) @function.method)
])
(field_identifier) @property
(function_item
name: (identifier) @function.definition)
[ [
"async"
"break"
"const"
"continue"
"dyn"
"else" "else"
"enum"
"for"
"fn" "fn"
"if" "if"
"impl"
"let"
"loop"
"match"
"mod"
"move"
"pub"
"return"
"struct"
"trait"
"type"
"use"
"where"
"while" "while"
] @keyword ] @keyword
(string_literal) @string
[
(line_comment)
(block_comment)
] @comment

View file

@ -16,6 +16,7 @@ use crate::{
editor::Bias, editor::Bias,
language::{Language, Tree}, language::{Language, Tree},
operation_queue::{self, OperationQueue}, operation_queue::{self, OperationQueue},
settings::{StyleId, ThemeMap},
sum_tree::{self, FilterCursor, SeekBias, SumTree}, sum_tree::{self, FilterCursor, SeekBias, SumTree},
time::{self, ReplicaId}, time::{self, ReplicaId},
worktree::FileHandle, worktree::FileHandle,
@ -617,6 +618,7 @@ impl Buffer {
handle.update(&mut ctx, |this, ctx| { handle.update(&mut ctx, |this, ctx| {
this.tree = Some((new_tree, new_version)); this.tree = Some((new_tree, new_version));
ctx.emit(Event::Reparsed); ctx.emit(Event::Reparsed);
ctx.notify();
}); });
} }
handle.update(&mut ctx, |this, _| this.is_parsing = false); handle.update(&mut ctx, |this, _| this.is_parsing = false);
@ -778,6 +780,7 @@ impl Buffer {
highlights: Some(Highlights { highlights: Some(Highlights {
captures: captures.peekable(), captures: captures.peekable(),
stack: Default::default(), stack: Default::default(),
theme_mapping: language.theme_mapping(),
cursor, cursor,
}), }),
buffer: self, buffer: self,
@ -2200,8 +2203,9 @@ impl<'a> tree_sitter::TextProvider<'a> for TextProvider<'a> {
struct Highlights<'a> { struct Highlights<'a> {
captures: iter::Peekable<tree_sitter::QueryCaptures<'a, 'a, TextProvider<'a>>>, captures: iter::Peekable<tree_sitter::QueryCaptures<'a, 'a, TextProvider<'a>>>,
stack: Vec<(usize, usize)>, stack: Vec<(usize, StyleId)>,
cursor: QueryCursor, cursor: QueryCursor,
theme_mapping: ThemeMap,
} }
pub struct HighlightedChunks<'a> { pub struct HighlightedChunks<'a> {
@ -2240,7 +2244,7 @@ impl<'a> HighlightedChunks<'a> {
} }
impl<'a> Iterator for HighlightedChunks<'a> { impl<'a> Iterator for HighlightedChunks<'a> {
type Item = (&'a str, Option<usize>); type Item = (&'a str, StyleId);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let mut next_capture_start = usize::MAX; let mut next_capture_start = usize::MAX;
@ -2260,9 +2264,8 @@ impl<'a> Iterator for HighlightedChunks<'a> {
next_capture_start = capture.node.start_byte(); next_capture_start = capture.node.start_byte();
break; break;
} else { } else {
highlights let style_id = highlights.theme_mapping.get(capture.index);
.stack highlights.stack.push((capture.node.end_byte(), style_id));
.push((capture.node.end_byte(), capture.index as usize));
highlights.captures.next().unwrap(); highlights.captures.next().unwrap();
} }
} }
@ -2271,12 +2274,12 @@ impl<'a> Iterator for HighlightedChunks<'a> {
if let Some(chunk) = self.chunks.peek() { if let Some(chunk) = self.chunks.peek() {
let chunk_start = self.range.start; let chunk_start = self.range.start;
let mut chunk_end = (self.chunks.offset() + chunk.len()).min(next_capture_start); let mut chunk_end = (self.chunks.offset() + chunk.len()).min(next_capture_start);
let mut capture_ix = None; let mut capture_ix = StyleId::default();
if let Some((parent_capture_end, parent_capture_ix)) = if let Some((parent_capture_end, parent_style_id)) =
self.highlights.as_ref().and_then(|h| h.stack.last()) self.highlights.as_ref().and_then(|h| h.stack.last())
{ {
chunk_end = chunk_end.min(*parent_capture_end); chunk_end = chunk_end.min(*parent_capture_end);
capture_ix = Some(*parent_capture_ix); capture_ix = *parent_style_id;
} }
let slice = let slice =

View file

@ -2,7 +2,12 @@ use super::{
buffer, movement, Anchor, Bias, Buffer, BufferElement, DisplayMap, DisplayPoint, Point, buffer, movement, Anchor, Bias, Buffer, BufferElement, DisplayMap, DisplayPoint, Point,
Selection, SelectionGoal, SelectionSetId, ToOffset, ToPoint, Selection, SelectionGoal, SelectionSetId, ToOffset, ToPoint,
}; };
use crate::{settings::Settings, util::post_inc, workspace, worktree::FileHandle}; use crate::{
settings::{Settings, StyleId},
util::post_inc,
workspace,
worktree::FileHandle,
};
use anyhow::Result; use anyhow::Result;
use gpui::{ use gpui::{
color::ColorU, fonts::Properties as FontProperties, geometry::vector::Vector2F, color::ColorU, fonts::Properties as FontProperties, geometry::vector::Vector2F,
@ -2145,8 +2150,9 @@ impl BufferView {
let mut row = rows.start; let mut row = rows.start;
let snapshot = self.display_map.snapshot(ctx); let snapshot = self.display_map.snapshot(ctx);
let chunks = snapshot.highlighted_chunks_at(rows.start, ctx); let chunks = snapshot.highlighted_chunks_at(rows.start, ctx);
let theme = settings.theme.clone();
'outer: for (chunk, capture_ix) in chunks.chain(Some(("\n", None))) { 'outer: for (chunk, style_ix) in chunks.chain(Some(("\n", StyleId::default()))) {
for (ix, line_chunk) in chunk.split('\n').enumerate() { for (ix, line_chunk) in chunk.split('\n').enumerate() {
if ix > 0 { if ix > 0 {
layouts.push(layout_cache.layout_str(&line, font_size, &styles)); layouts.push(layout_cache.layout_str(&line, font_size, &styles));
@ -2160,7 +2166,7 @@ impl BufferView {
if !line_chunk.is_empty() { if !line_chunk.is_empty() {
line.push_str(line_chunk); line.push_str(line_chunk);
styles.push((line_chunk.len(), font_id, ColorU::black())); styles.push((line_chunk.len(), font_id, theme.syntax_style(style_ix).0));
} }
} }
} }

View file

@ -4,6 +4,7 @@ use super::{
}; };
use crate::{ use crate::{
editor::buffer, editor::buffer,
settings::StyleId,
sum_tree::{self, Cursor, FilterCursor, SeekBias, SumTree}, sum_tree::{self, Cursor, FilterCursor, SeekBias, SumTree},
time, time,
}; };
@ -741,12 +742,12 @@ impl<'a> Iterator for Chunks<'a> {
pub struct HighlightedChunks<'a> { pub struct HighlightedChunks<'a> {
transform_cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>, transform_cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>,
buffer_chunks: buffer::HighlightedChunks<'a>, buffer_chunks: buffer::HighlightedChunks<'a>,
buffer_chunk: Option<(usize, &'a str, Option<usize>)>, buffer_chunk: Option<(usize, &'a str, StyleId)>,
buffer_offset: usize, buffer_offset: usize,
} }
impl<'a> Iterator for HighlightedChunks<'a> { impl<'a> Iterator for HighlightedChunks<'a> {
type Item = (&'a str, Option<usize>); type Item = (&'a str, StyleId);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let transform = if let Some(item) = self.transform_cursor.item() { let transform = if let Some(item) = self.transform_cursor.item() {
@ -768,7 +769,7 @@ impl<'a> Iterator for HighlightedChunks<'a> {
self.transform_cursor.next(); self.transform_cursor.next();
} }
return Some((display_text, None)); return Some((display_text, StyleId::default()));
} }
// Retrieve a chunk from the current location in the buffer. // Retrieve a chunk from the current location in the buffer.

View file

@ -1,5 +1,7 @@
mod fold_map; mod fold_map;
use crate::settings::StyleId;
use super::{buffer, Anchor, Bias, Buffer, Edit, Point, ToOffset, ToPoint}; use super::{buffer, Anchor, Bias, Buffer, Edit, Point, ToOffset, ToPoint};
pub use fold_map::BufferRows; pub use fold_map::BufferRows;
use fold_map::{FoldMap, FoldMapSnapshot}; use fold_map::{FoldMap, FoldMapSnapshot};
@ -163,7 +165,7 @@ impl DisplayMapSnapshot {
column: 0, column: 0,
tab_size: self.tab_size, tab_size: self.tab_size,
chunk: "", chunk: "",
capture_ix: None, style_id: Default::default(),
} }
} }
@ -355,19 +357,19 @@ impl<'a> Iterator for Chunks<'a> {
pub struct HighlightedChunks<'a> { pub struct HighlightedChunks<'a> {
fold_chunks: fold_map::HighlightedChunks<'a>, fold_chunks: fold_map::HighlightedChunks<'a>,
chunk: &'a str, chunk: &'a str,
capture_ix: Option<usize>, style_id: StyleId,
column: usize, column: usize,
tab_size: usize, tab_size: usize,
} }
impl<'a> Iterator for HighlightedChunks<'a> { impl<'a> Iterator for HighlightedChunks<'a> {
type Item = (&'a str, Option<usize>); type Item = (&'a str, StyleId);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if self.chunk.is_empty() { if self.chunk.is_empty() {
if let Some((chunk, capture_ix)) = self.fold_chunks.next() { if let Some((chunk, style_id)) = self.fold_chunks.next() {
self.chunk = chunk; self.chunk = chunk;
self.capture_ix = capture_ix; self.style_id = style_id;
} else { } else {
return None; return None;
} }
@ -379,12 +381,12 @@ impl<'a> Iterator for HighlightedChunks<'a> {
if ix > 0 { if ix > 0 {
let (prefix, suffix) = self.chunk.split_at(ix); let (prefix, suffix) = self.chunk.split_at(ix);
self.chunk = suffix; self.chunk = suffix;
return Some((prefix, self.capture_ix)); return Some((prefix, self.style_id));
} else { } else {
self.chunk = &self.chunk[1..]; self.chunk = &self.chunk[1..];
let len = self.tab_size - self.column % self.tab_size; let len = self.tab_size - self.column % self.tab_size;
self.column += len; self.column += len;
return Some((&SPACES[0..len], self.capture_ix)); return Some((&SPACES[0..len], self.style_id));
} }
} }
'\n' => self.column = 0, '\n' => self.column = 0,
@ -392,7 +394,9 @@ impl<'a> Iterator for HighlightedChunks<'a> {
} }
} }
Some((mem::take(&mut self.chunk), self.capture_ix.take())) let style_id = self.style_id;
self.style_id = StyleId::default();
Some((mem::take(&mut self.chunk), style_id))
} }
} }

View file

@ -1,3 +1,5 @@
use crate::settings::{Theme, ThemeMap};
use parking_lot::Mutex;
use rust_embed::RustEmbed; use rust_embed::RustEmbed;
use std::{path::Path, sync::Arc}; use std::{path::Path, sync::Arc};
use tree_sitter::{Language as Grammar, Query}; use tree_sitter::{Language as Grammar, Query};
@ -13,12 +15,23 @@ pub struct Language {
pub grammar: Grammar, pub grammar: Grammar,
pub highlight_query: Query, pub highlight_query: Query,
path_suffixes: Vec<String>, path_suffixes: Vec<String>,
theme_mapping: Mutex<ThemeMap>,
} }
pub struct LanguageRegistry { pub struct LanguageRegistry {
languages: Vec<Arc<Language>>, languages: Vec<Arc<Language>>,
} }
impl Language {
pub fn theme_mapping(&self) -> ThemeMap {
self.theme_mapping.lock().clone()
}
fn set_theme(&self, theme: &Theme) {
*self.theme_mapping.lock() = ThemeMap::new(self.highlight_query.capture_names(), theme);
}
}
impl LanguageRegistry { impl LanguageRegistry {
pub fn new() -> Self { pub fn new() -> Self {
let grammar = tree_sitter_rust::language(); let grammar = tree_sitter_rust::language();
@ -32,6 +45,7 @@ impl LanguageRegistry {
) )
.unwrap(), .unwrap(),
path_suffixes: vec!["rs".to_string()], path_suffixes: vec!["rs".to_string()],
theme_mapping: Mutex::new(ThemeMap::default()),
}; };
Self { Self {
@ -39,6 +53,12 @@ impl LanguageRegistry {
} }
} }
pub fn set_theme(&self, theme: &Theme) {
for language in &self.languages {
language.set_theme(theme);
}
}
pub fn select_language(&self, path: impl AsRef<Path>) -> Option<&Arc<Language>> { pub fn select_language(&self, path: impl AsRef<Path>) -> Option<&Arc<Language>> {
let path = path.as_ref(); let path = path.as_ref();
let filename = path.file_name().and_then(|name| name.to_str()); let filename = path.file_name().and_then(|name| name.to_str());
@ -67,12 +87,14 @@ mod tests {
grammar, grammar,
highlight_query: Query::new(grammar, "").unwrap(), highlight_query: Query::new(grammar, "").unwrap(),
path_suffixes: vec!["rs".to_string()], path_suffixes: vec!["rs".to_string()],
theme_mapping: Default::default(),
}), }),
Arc::new(Language { Arc::new(Language {
name: "Make".to_string(), name: "Make".to_string(),
grammar, grammar,
highlight_query: Query::new(grammar, "").unwrap(), highlight_query: Query::new(grammar, "").unwrap(),
path_suffixes: vec!["Makefile".to_string(), "mk".to_string()], path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
theme_mapping: Default::default(),
}), }),
], ],
}; };

View file

@ -18,6 +18,7 @@ fn main() {
let (_, settings) = settings::channel(&app.font_cache()).unwrap(); let (_, settings) = settings::channel(&app.font_cache()).unwrap();
let language_registry = Arc::new(language::LanguageRegistry::new()); let language_registry = Arc::new(language::LanguageRegistry::new());
language_registry.set_theme(&settings.borrow().theme);
let app_state = AppState { let app_state = AppState {
language_registry, language_registry,
settings, settings,

View file

@ -1,6 +1,15 @@
use anyhow::Result; use super::assets::Assets;
use gpui::font_cache::{FamilyId, FontCache}; use anyhow::{anyhow, Context, Result};
use gpui::{
color::ColorU,
font_cache::{FamilyId, FontCache},
fonts::Weight,
};
use postage::watch; use postage::watch;
use serde::Deserialize;
use std::{collections::HashMap, sync::Arc};
const DEFAULT_STYLE_ID: StyleId = StyleId(u32::MAX);
#[derive(Clone)] #[derive(Clone)]
pub struct Settings { pub struct Settings {
@ -9,8 +18,23 @@ pub struct Settings {
pub tab_size: usize, pub tab_size: usize,
pub ui_font_family: FamilyId, pub ui_font_family: FamilyId,
pub ui_font_size: f32, pub ui_font_size: f32,
pub theme: Arc<Theme>,
} }
#[derive(Clone, Default)]
pub struct Theme {
pub background_color: ColorU,
pub line_number_color: ColorU,
pub default_text_color: ColorU,
syntax_styles: Vec<(String, ColorU, Weight)>,
}
#[derive(Clone, Debug)]
pub struct ThemeMap(Arc<[StyleId]>);
#[derive(Clone, Copy, Debug)]
pub struct StyleId(u32);
impl Settings { impl Settings {
pub fn new(font_cache: &FontCache) -> Result<Self> { pub fn new(font_cache: &FontCache) -> Result<Self> {
Ok(Self { Ok(Self {
@ -19,12 +43,247 @@ impl Settings {
tab_size: 4, tab_size: 4,
ui_font_family: font_cache.load_family(&["SF Pro", "Helvetica"])?, ui_font_family: font_cache.load_family(&["SF Pro", "Helvetica"])?,
ui_font_size: 12.0, ui_font_size: 12.0,
theme: Arc::new(
Theme::parse(Assets::get("themes/light.toml").unwrap())
.expect("Failed to parse built-in theme"),
),
}) })
} }
} }
impl Theme {
pub fn parse(source: impl AsRef<[u8]>) -> Result<Self> {
#[derive(Deserialize)]
struct ThemeToml {
#[serde(default)]
syntax: HashMap<String, StyleToml>,
#[serde(default)]
ui: HashMap<String, u32>,
}
#[derive(Deserialize)]
#[serde(untagged)]
enum StyleToml {
Color(u32),
Full {
color: Option<u32>,
weight: Option<toml::Value>,
},
}
let theme_toml: ThemeToml =
toml::from_slice(source.as_ref()).context("failed to parse theme TOML")?;
let mut syntax_styles = Vec::<(String, ColorU, Weight)>::new();
for (key, style) in theme_toml.syntax {
let (color, weight) = match style {
StyleToml::Color(color) => (color, None),
StyleToml::Full { color, weight } => (color.unwrap_or(0), weight),
};
match syntax_styles.binary_search_by_key(&&key, |e| &e.0) {
Ok(i) | Err(i) => syntax_styles.insert(
i,
(key, deserialize_color(color), deserialize_weight(weight)?),
),
}
}
let background_color = theme_toml
.ui
.get("background")
.copied()
.map_or(ColorU::from_u32(0xffffffff), deserialize_color);
let line_number_color = theme_toml
.ui
.get("line_numbers")
.copied()
.map_or(ColorU::black(), deserialize_color);
let default_text_color = theme_toml
.ui
.get("text")
.copied()
.map_or(ColorU::black(), deserialize_color);
Ok(Theme {
background_color,
line_number_color,
default_text_color,
syntax_styles,
})
}
pub fn syntax_style(&self, id: StyleId) -> (ColorU, Weight) {
self.syntax_styles
.get(id.0 as usize)
.map_or((self.default_text_color, Weight::NORMAL), |entry| {
(entry.1, entry.2)
})
}
#[cfg(test)]
pub fn syntax_style_name(&self, id: StyleId) -> Option<&str> {
self.syntax_styles.get(id.0 as usize).map(|e| e.0.as_str())
}
}
impl ThemeMap {
pub fn new(capture_names: &[String], theme: &Theme) -> Self {
// For each capture name in the highlight query, find the longest
// key in the theme's syntax styles that matches all of the
// dot-separated components of the capture name.
ThemeMap(
capture_names
.iter()
.map(|capture_name| {
theme
.syntax_styles
.iter()
.enumerate()
.filter_map(|(i, (key, _, _))| {
let mut len = 0;
let capture_parts = capture_name.split('.');
for key_part in key.split('.') {
if capture_parts.clone().any(|part| part == key_part) {
len += 1;
} else {
return None;
}
}
Some((i, len))
})
.max_by_key(|(_, len)| *len)
.map_or(DEFAULT_STYLE_ID, |(i, _)| StyleId(i as u32))
})
.collect(),
)
}
pub fn get(&self, capture_id: u32) -> StyleId {
self.0
.get(capture_id as usize)
.copied()
.unwrap_or(DEFAULT_STYLE_ID)
}
}
impl Default for ThemeMap {
fn default() -> Self {
Self(Arc::new([]))
}
}
impl Default for StyleId {
fn default() -> Self {
DEFAULT_STYLE_ID
}
}
pub fn channel( pub fn channel(
font_cache: &FontCache, font_cache: &FontCache,
) -> Result<(watch::Sender<Settings>, watch::Receiver<Settings>)> { ) -> Result<(watch::Sender<Settings>, watch::Receiver<Settings>)> {
Ok(watch::channel_with(Settings::new(font_cache)?)) Ok(watch::channel_with(Settings::new(font_cache)?))
} }
fn deserialize_color(color: u32) -> ColorU {
ColorU::from_u32((color << 8) + 0xFF)
}
fn deserialize_weight(weight: Option<toml::Value>) -> Result<Weight> {
match &weight {
None => return Ok(Weight::NORMAL),
Some(toml::Value::Integer(i)) => return Ok(Weight(*i as f32)),
Some(toml::Value::String(s)) => match s.as_str() {
"normal" => return Ok(Weight::NORMAL),
"bold" => return Ok(Weight::BOLD),
"light" => return Ok(Weight::LIGHT),
"semibold" => return Ok(Weight::SEMIBOLD),
_ => {}
},
_ => {}
}
Err(anyhow!("Invalid weight {}", weight.unwrap()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_theme() {
let theme = Theme::parse(
r#"
[ui]
background = 0x00ed00
line_numbers = 0xdddddd
[syntax]
"beta.two" = 0xAABBCC
"alpha.one" = {color = 0x112233, weight = "bold"}
"gamma.three" = {weight = "light"}
"#,
)
.unwrap();
assert_eq!(theme.background_color, ColorU::from_u32(0x00ED00FF));
assert_eq!(theme.line_number_color, ColorU::from_u32(0xddddddff));
assert_eq!(
theme.syntax_styles,
&[
(
"alpha.one".to_string(),
ColorU::from_u32(0x112233FF),
Weight::BOLD
),
(
"beta.two".to_string(),
ColorU::from_u32(0xAABBCCFF),
Weight::NORMAL
),
(
"gamma.three".to_string(),
ColorU::from_u32(0x000000FF),
Weight::LIGHT,
),
]
);
}
#[test]
fn test_parse_empty_theme() {
Theme::parse("").unwrap();
}
#[test]
fn test_theme_map() {
let theme = Theme {
default_text_color: Default::default(),
background_color: ColorU::default(),
line_number_color: ColorU::default(),
syntax_styles: [
("function", ColorU::from_u32(0x100000ff)),
("function.method", ColorU::from_u32(0x200000ff)),
("function.async", ColorU::from_u32(0x300000ff)),
("variable.builtin.self.rust", ColorU::from_u32(0x400000ff)),
("variable.builtin", ColorU::from_u32(0x500000ff)),
("variable", ColorU::from_u32(0x600000ff)),
]
.iter()
.map(|e| (e.0.to_string(), e.1, Weight::NORMAL))
.collect(),
};
let capture_names = &[
"function.special".to_string(),
"function.async.rust".to_string(),
"variable.builtin.self".to_string(),
];
let map = ThemeMap::new(capture_names, &theme);
assert_eq!(theme.syntax_style_name(map.get(0)), Some("function"));
assert_eq!(theme.syntax_style_name(map.get(1)), Some("function.async"));
assert_eq!(
theme.syntax_style_name(map.get(2)),
Some("variable.builtin")
);
}
}