diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index 6299014c..a4fbca99 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -311,8 +311,7 @@ impl EditorView {
                         if LineEnding::from_rope_slice(&grapheme).is_some() {
                             if !out_of_bounds {
                                 let style = spans.iter().fold(text_style, |acc, span| {
-                                    let style = theme.get(theme.scopes()[span.0].as_str());
-                                    acc.patch(style)
+                                    acc.patch(theme.highlight(span.0))
                                 });
 
                                 // we still want to render an empty cell with the style
@@ -346,8 +345,7 @@ impl EditorView {
 
                             if !out_of_bounds {
                                 let style = spans.iter().fold(text_style, |acc, span| {
-                                    let style = theme.get(theme.scopes()[span.0].as_str());
-                                    acc.patch(style)
+                                    acc.patch(theme.highlight(span.0))
                                 });
 
                                 // if we're offscreen just keep going until we hit a new line
diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs
index 757316bd..2366ff7d 100644
--- a/helix-view/src/theme.rs
+++ b/helix-view/src/theme.rs
@@ -78,8 +78,11 @@ impl Loader {
 
 #[derive(Clone, Debug)]
 pub struct Theme {
-    scopes: Vec<String>,
+    // UI styles are stored in a HashMap
     styles: HashMap<String, Style>,
+    // tree-sitter highlight styles are stored in a Vec to optimize lookups
+    scopes: Vec<String>,
+    highlights: Vec<Style>,
 }
 
 impl<'de> Deserialize<'de> for Theme {
@@ -88,6 +91,8 @@ impl<'de> Deserialize<'de> for Theme {
         D: Deserializer<'de>,
     {
         let mut styles = HashMap::new();
+        let mut scopes = Vec::new();
+        let mut highlights = Vec::new();
 
         if let Ok(mut colors) = HashMap::<String, Value>::deserialize(deserializer) {
             // TODO: alert user of parsing failures in editor
@@ -102,21 +107,35 @@ impl<'de> Deserialize<'de> for Theme {
                 .unwrap_or_default();
 
             styles.reserve(colors.len());
+            scopes.reserve(colors.len());
+            highlights.reserve(colors.len());
+
             for (name, style_value) in colors {
                 let mut style = Style::default();
                 if let Err(err) = palette.parse_style(&mut style, style_value) {
                     warn!("{}", err);
                 }
-                styles.insert(name, style);
+
+                // these are used both as UI and as highlights
+                styles.insert(name.clone(), style);
+                scopes.push(name);
+                highlights.push(style);
             }
         }
 
-        let scopes = styles.keys().map(ToString::to_string).collect();
-        Ok(Self { scopes, styles })
+        Ok(Self {
+            scopes,
+            styles,
+            highlights,
+        })
     }
 }
 
 impl Theme {
+    pub fn highlight(&self, index: usize) -> Style {
+        self.highlights[index]
+    }
+
     pub fn get(&self, scope: &str) -> Style {
         self.try_get(scope)
             .unwrap_or_else(|| Style::default().fg(Color::Rgb(0, 0, 255)))