From 70887b7378d642226ad5102fbe601fecb225225c Mon Sep 17 00:00:00 2001
From: Michael Davis <mcarsondavis@gmail.com>
Date: Tue, 24 Jan 2023 11:07:46 -0600
Subject: [PATCH] Refactor toml::Value->Theme conversion

The `From<Value>` implementation for `Theme` converted the Value to a
string and re-parsed the string to convert it to
`HashMap<String, Value>` which feels a bit wasteful. This change uses
the underlying `toml::map::Map` directly when the value is a table and
warns about the unexpected `Value` shape otherwise.

This is necessary because toml 0.6.0 changes the Display implementation
for Value::Table so that the `to_string` no longer encodes the value as
a Document, just a Value. So the parse of the Value fails to be decoded
as a HashMap.

The behavior for returning `Default::default` matches the previous
code's behavior except that it did not warn when the input Value was
failed to parse.
---
 helix-view/src/theme.rs | 76 ++++++++++++++++++++---------------------
 1 file changed, 38 insertions(+), 38 deletions(-)

diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs
index ead0b9fb..43d4a7a7 100644
--- a/helix-view/src/theme.rs
+++ b/helix-view/src/theme.rs
@@ -4,7 +4,7 @@ use std::{
     str,
 };
 
-use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, Result};
 use helix_core::hashmap;
 use helix_loader::merge_toml_values;
 use log::warn;
@@ -209,16 +209,18 @@ pub struct Theme {
 
 impl From<Value> for Theme {
     fn from(value: Value) -> Self {
-        let values: Result<HashMap<String, Value>> =
-            toml::from_str(&value.to_string()).context("Failed to load theme");
+        if let Value::Table(table) = value {
+            let (styles, scopes, highlights) = build_theme_values(table);
 
-        let (styles, scopes, highlights) = build_theme_values(values);
-
-        Self {
-            styles,
-            scopes,
-            highlights,
-            ..Default::default()
+            Self {
+                styles,
+                scopes,
+                highlights,
+                ..Default::default()
+            }
+        } else {
+            warn!("Expected theme TOML value to be a table, found {:?}", value);
+            Default::default()
         }
     }
 }
@@ -228,9 +230,9 @@ impl<'de> Deserialize<'de> for Theme {
     where
         D: Deserializer<'de>,
     {
-        let values = HashMap::<String, Value>::deserialize(deserializer)?;
+        let values = Map::<String, Value>::deserialize(deserializer)?;
 
-        let (styles, scopes, highlights) = build_theme_values(Ok(values));
+        let (styles, scopes, highlights) = build_theme_values(values);
 
         Ok(Self {
             styles,
@@ -242,39 +244,37 @@ impl<'de> Deserialize<'de> for Theme {
 }
 
 fn build_theme_values(
-    values: Result<HashMap<String, Value>>,
+    mut values: Map<String, Value>,
 ) -> (HashMap<String, Style>, Vec<String>, Vec<Style>) {
     let mut styles = HashMap::new();
     let mut scopes = Vec::new();
     let mut highlights = Vec::new();
 
-    if let Ok(mut colors) = values {
-        // TODO: alert user of parsing failures in editor
-        let palette = colors
-            .remove("palette")
-            .map(|value| {
-                ThemePalette::try_from(value).unwrap_or_else(|err| {
-                    warn!("{}", err);
-                    ThemePalette::default()
-                })
-            })
-            .unwrap_or_default();
-        // remove inherits from value to prevent errors
-        let _ = colors.remove("inherits");
-        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) {
+    // TODO: alert user of parsing failures in editor
+    let palette = values
+        .remove("palette")
+        .map(|value| {
+            ThemePalette::try_from(value).unwrap_or_else(|err| {
                 warn!("{}", err);
-            }
-
-            // these are used both as UI and as highlights
-            styles.insert(name.clone(), style);
-            scopes.push(name);
-            highlights.push(style);
+                ThemePalette::default()
+            })
+        })
+        .unwrap_or_default();
+    // remove inherits from value to prevent errors
+    let _ = values.remove("inherits");
+    styles.reserve(values.len());
+    scopes.reserve(values.len());
+    highlights.reserve(values.len());
+    for (name, style_value) in values {
+        let mut style = Style::default();
+        if let Err(err) = palette.parse_style(&mut style, style_value) {
+            warn!("{}", err);
         }
+
+        // these are used both as UI and as highlights
+        styles.insert(name.clone(), style);
+        scopes.push(name);
+        highlights.push(style);
     }
 
     (styles, scopes, highlights)