From ce97a2f05fcddf81d8210ec6b25411f8fd7d867a Mon Sep 17 00:00:00 2001
From: wojciechkepka <wojtek.kepka@protonmail.com>
Date: Sat, 19 Jun 2021 13:26:52 +0200
Subject: [PATCH] Add ability to change theme on editor

---
 Cargo.lock                      |   7 +++
 helix-core/Cargo.toml           |   1 +
 helix-core/src/indent.rs        |  37 ++++++-----
 helix-core/src/lib.rs           |   2 +-
 helix-core/src/syntax.rs        | 105 +++++++++++++++++++-------------
 helix-term/src/application.rs   |  40 +++++++++++-
 helix-term/src/ui/completion.rs |  43 +++++++------
 helix-term/src/ui/markdown.rs   |  31 ++++++----
 helix-view/src/document.rs      |  43 +++++++------
 helix-view/src/editor.rs        |  75 +++++++++++++++--------
 10 files changed, 242 insertions(+), 142 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 24c277e1..896f7bc1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -17,6 +17,12 @@ version = "1.0.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61"
 
+[[package]]
+name = "arc-swap"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e906254e445520903e7fc9da4f709886c84ae4bc4ddaf0e093188d66df4dc820"
+
 [[package]]
 name = "autocfg"
 version = "1.0.1"
@@ -254,6 +260,7 @@ dependencies = [
 name = "helix-core"
 version = "0.2.0"
 dependencies = [
+ "arc-swap",
  "etcetera",
  "helix-syntax",
  "once_cell",
diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml
index 13ac35fb..346dc050 100644
--- a/helix-core/Cargo.toml
+++ b/helix-core/Cargo.toml
@@ -25,6 +25,7 @@ unicode-general-category = "0.4.0"
 # slab = "0.4.2"
 tree-sitter = "0.19"
 once_cell = "1.8"
+arc-swap = "1"
 regex = "1"
 
 serde = { version = "1.0", features = ["derive"] }
diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs
index 58124ed2..8e0379e2 100644
--- a/helix-core/src/indent.rs
+++ b/helix-core/src/indent.rs
@@ -254,26 +254,23 @@ where
             Configuration, IndentationConfiguration, Lang, LanguageConfiguration, Loader,
         };
         use once_cell::sync::OnceCell;
-        let loader = Loader::new(
-            Configuration {
-                language: vec![LanguageConfiguration {
-                    scope: "source.rust".to_string(),
-                    file_types: vec!["rs".to_string()],
-                    language_id: Lang::Rust,
-                    highlight_config: OnceCell::new(),
-                    //
-                    roots: vec![],
-                    auto_format: false,
-                    language_server: None,
-                    indent: Some(IndentationConfiguration {
-                        tab_width: 4,
-                        unit: String::from("    "),
-                    }),
-                    indent_query: OnceCell::new(),
-                }],
-            },
-            Vec::new(),
-        );
+        let loader = Loader::new(Configuration {
+            language: vec![LanguageConfiguration {
+                scope: "source.rust".to_string(),
+                file_types: vec!["rs".to_string()],
+                language_id: Lang::Rust,
+                highlight_config: OnceCell::new(),
+                //
+                roots: vec![],
+                auto_format: false,
+                language_server: None,
+                indent: Some(IndentationConfiguration {
+                    tab_width: 4,
+                    unit: String::from("    "),
+                }),
+                indent_query: OnceCell::new(),
+            }],
+        });
 
         // set runtime path so we can find the queries
         let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs
index 03741719..d669fa49 100644
--- a/helix-core/src/lib.rs
+++ b/helix-core/src/lib.rs
@@ -50,7 +50,7 @@ pub fn find_root(root: Option<&str>) -> Option<std::path::PathBuf> {
 }
 
 #[cfg(not(embed_runtime))]
-fn runtime_dir() -> std::path::PathBuf {
+pub fn runtime_dir() -> std::path::PathBuf {
     if let Ok(dir) = std::env::var("HELIX_RUNTIME") {
         return dir.into();
     }
diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs
index ae058eb1..78623fd6 100644
--- a/helix-core/src/syntax.rs
+++ b/helix-core/src/syntax.rs
@@ -1,6 +1,8 @@
 use crate::{regex::Regex, Change, Rope, RopeSlice, Transaction};
 pub use helix_syntax::{get_language, get_language_name, Lang};
 
+use arc_swap::ArcSwap;
+
 use std::{
     borrow::Cow,
     cell::RefCell,
@@ -143,35 +145,48 @@ fn read_query(language: &str, filename: &str) -> String {
 }
 
 impl LanguageConfiguration {
+    fn initialize_highlight(&self, scopes: &[String]) -> Option<Arc<HighlightConfiguration>> {
+        let language = get_language_name(self.language_id).to_ascii_lowercase();
+
+        let highlights_query = read_query(&language, "highlights.scm");
+        // always highlight syntax errors
+        // highlights_query += "\n(ERROR) @error";
+
+        let injections_query = read_query(&language, "injections.scm");
+
+        let locals_query = "";
+
+        if highlights_query.is_empty() {
+            None
+        } else {
+            let language = get_language(self.language_id);
+            let mut config = HighlightConfiguration::new(
+                language,
+                &highlights_query,
+                &injections_query,
+                locals_query,
+            )
+            .unwrap(); // TODO: no unwrap
+            config.configure(scopes);
+            Some(Arc::new(config))
+        }
+    }
+
     pub fn highlight_config(&self, scopes: &[String]) -> Option<Arc<HighlightConfiguration>> {
-        self.highlight_config
-            .get_or_init(|| {
-                let language = get_language_name(self.language_id).to_ascii_lowercase();
+        if let Some(config) = self.highlight_config.get() {
+            if let Some(config) = config {
+                config.configure(scopes);
+            }
+            config.clone()
+        } else {
+            self.highlight_config
+                .get_or_init(|| self.initialize_highlight(scopes))
+                .clone()
+        }
+    }
 
-                let highlights_query = read_query(&language, "highlights.scm");
-                // always highlight syntax errors
-                // highlights_query += "\n(ERROR) @error";
-
-                let injections_query = read_query(&language, "injections.scm");
-
-                let locals_query = "";
-
-                if highlights_query.is_empty() {
-                    None
-                } else {
-                    let language = get_language(self.language_id);
-                    let mut config = HighlightConfiguration::new(
-                        language,
-                        &highlights_query,
-                        &injections_query,
-                        locals_query,
-                    )
-                    .unwrap(); // TODO: no unwrap
-                    config.configure(scopes);
-                    Some(Arc::new(config))
-                }
-            })
-            .clone()
+    pub fn is_highlight_initialized(&self) -> bool {
+        self.highlight_config.get().is_some()
     }
 
     pub fn indent_query(&self) -> Option<&IndentQuery> {
@@ -190,22 +205,18 @@ impl LanguageConfiguration {
     }
 }
 
-pub static LOADER: OnceCell<Loader> = OnceCell::new();
-
 #[derive(Debug)]
 pub struct Loader {
     // highlight_names ?
     language_configs: Vec<Arc<LanguageConfiguration>>,
     language_config_ids_by_file_type: HashMap<String, usize>, // Vec<usize>
-    scopes: Vec<String>,
 }
 
 impl Loader {
-    pub fn new(config: Configuration, scopes: Vec<String>) -> Self {
+    pub fn new(config: Configuration) -> Self {
         let mut loader = Self {
             language_configs: Vec::new(),
             language_config_ids_by_file_type: HashMap::new(),
-            scopes,
         };
 
         for config in config.language {
@@ -225,10 +236,6 @@ impl Loader {
         loader
     }
 
-    pub fn scopes(&self) -> &[String] {
-        &self.scopes
-    }
-
     pub fn language_config_for_file_name(&self, path: &Path) -> Option<Arc<LanguageConfiguration>> {
         // Find all the language configurations that match this file name
         // or a suffix of the file name.
@@ -253,6 +260,10 @@ impl Loader {
             .find(|config| config.scope == scope)
             .cloned()
     }
+
+    pub fn language_configs_iter(&self) -> impl Iterator<Item = &Arc<LanguageConfiguration>> {
+        self.language_configs.iter()
+    }
 }
 
 pub struct TsParser {
@@ -771,7 +782,7 @@ pub struct HighlightConfiguration {
     combined_injections_query: Option<Query>,
     locals_pattern_index: usize,
     highlights_pattern_index: usize,
-    highlight_indices: Vec<Option<Highlight>>,
+    highlight_indices: ArcSwap<Vec<Option<Highlight>>>,
     non_local_variable_patterns: Vec<bool>,
     injection_content_capture_index: Option<u32>,
     injection_language_capture_index: Option<u32>,
@@ -923,7 +934,7 @@ impl HighlightConfiguration {
             }
         }
 
-        let highlight_indices = vec![None; query.capture_names().len()];
+        let highlight_indices = ArcSwap::from_pointee(vec![None; query.capture_names().len()]);
         Ok(Self {
             language,
             query,
@@ -956,17 +967,20 @@ impl HighlightConfiguration {
     ///
     /// When highlighting, results are returned as `Highlight` values, which contain the index
     /// of the matched highlight this list of highlight names.
-    pub fn configure(&mut self, recognized_names: &[String]) {
+    pub fn configure(&self, recognized_names: &[String]) {
         let mut capture_parts = Vec::new();
-        self.highlight_indices.clear();
-        self.highlight_indices
-            .extend(self.query.capture_names().iter().map(move |capture_name| {
+        let indices: Vec<_> = self
+            .query
+            .capture_names()
+            .iter()
+            .map(move |capture_name| {
                 capture_parts.clear();
                 capture_parts.extend(capture_name.split('.'));
 
                 let mut best_index = None;
                 let mut best_match_len = 0;
                 for (i, recognized_name) in recognized_names.iter().enumerate() {
+                    let recognized_name = recognized_name;
                     let mut len = 0;
                     let mut matches = true;
                     for part in recognized_name.split('.') {
@@ -982,7 +996,10 @@ impl HighlightConfiguration {
                     }
                 }
                 best_index.map(Highlight)
-            }));
+            })
+            .collect();
+
+        self.highlight_indices.store(Arc::new(indices));
     }
 }
 
@@ -1561,7 +1578,7 @@ where
                 }
             }
 
-            let current_highlight = layer.config.highlight_indices[capture.index as usize];
+            let current_highlight = layer.config.highlight_indices.load()[capture.index as usize];
 
             // If this node represents a local definition, then store the current
             // highlight value on the local scope entry representing this node.
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index 2fae467f..08853ed0 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -1,5 +1,6 @@
+use helix_core::syntax;
 use helix_lsp::{lsp, LspProgressMap};
-use helix_view::{document::Mode, Document, Editor, Theme, View};
+use helix_view::{document::Mode, theme, Document, Editor, Theme, View};
 
 use crate::{args::Args, compositor::Compositor, config::Config, keymap::Keymaps, ui};
 
@@ -14,7 +15,7 @@ use std::{
     time::Duration,
 };
 
-use anyhow::Error;
+use anyhow::{Context, Error};
 
 use crossterm::{
     event::{Event, EventStream},
@@ -36,6 +37,8 @@ pub struct Application {
     compositor: Compositor,
     editor: Editor,
 
+    theme_loader: Arc<theme::Loader>,
+    syn_loader: Arc<syntax::Loader>,
     callbacks: LspCallbacks,
 
     lsp_progress: LspProgressMap,
@@ -47,7 +50,34 @@ impl Application {
         use helix_view::editor::Action;
         let mut compositor = Compositor::new()?;
         let size = compositor.size();
-        let mut editor = Editor::new(size);
+
+        let conf_dir = helix_core::config_dir();
+
+        let theme_loader =
+            std::sync::Arc::new(theme::Loader::new(&conf_dir, &helix_core::runtime_dir()));
+
+        // load $HOME/.config/helix/languages.toml, fallback to default config
+        let lang_conf = std::fs::read(conf_dir.join("languages.toml"));
+        let lang_conf = lang_conf
+            .as_deref()
+            .unwrap_or(include_bytes!("../../languages.toml"));
+
+        let theme = if let Some(theme) = &config.global.theme {
+            match theme_loader.load(theme) {
+                Ok(theme) => theme,
+                Err(e) => {
+                    log::warn!("failed to load theme `{}` - {}", theme, e);
+                    theme_loader.default()
+                }
+            }
+        } else {
+            theme_loader.default()
+        };
+
+        let syn_loader_conf = toml::from_slice(lang_conf).expect("Could not parse languages.toml");
+        let syn_loader = std::sync::Arc::new(syntax::Loader::new(syn_loader_conf));
+
+        let mut editor = Editor::new(size, theme_loader.clone(), syn_loader.clone());
 
         let mut editor_view = Box::new(ui::EditorView::new(config.keymaps));
         compositor.push(editor_view);
@@ -72,10 +102,14 @@ impl Application {
             editor.new_file(Action::VerticalSplit);
         }
 
+        editor.set_theme(theme);
+
         let mut app = Self {
             compositor,
             editor,
 
+            theme_loader,
+            syn_loader,
             callbacks: FuturesUnordered::new(),
             lsp_progress: LspProgressMap::new(),
             lsp_progress_enabled: config.global.lsp_progress,
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs
index 06ed966d..88a71534 100644
--- a/helix-term/src/ui/completion.rs
+++ b/helix-term/src/ui/completion.rs
@@ -246,34 +246,43 @@ impl Component for Completion {
                     value: contents,
                 })) => {
                     // TODO: convert to wrapped text
-                    Markdown::new(format!(
-                        "```{}\n{}\n```\n{}",
-                        language,
-                        option.detail.as_deref().unwrap_or_default(),
-                        contents.clone()
-                    ))
+                    Markdown::new(
+                        format!(
+                            "```{}\n{}\n```\n{}",
+                            language,
+                            option.detail.as_deref().unwrap_or_default(),
+                            contents.clone()
+                        ),
+                        cx.editor.syn_loader.clone(),
+                    )
                 }
                 Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
                     kind: lsp::MarkupKind::Markdown,
                     value: contents,
                 })) => {
                     // TODO: set language based on doc scope
-                    Markdown::new(format!(
-                        "```{}\n{}\n```\n{}",
-                        language,
-                        option.detail.as_deref().unwrap_or_default(),
-                        contents.clone()
-                    ))
+                    Markdown::new(
+                        format!(
+                            "```{}\n{}\n```\n{}",
+                            language,
+                            option.detail.as_deref().unwrap_or_default(),
+                            contents.clone()
+                        ),
+                        cx.editor.syn_loader.clone(),
+                    )
                 }
                 None if option.detail.is_some() => {
                     // TODO: copied from above
 
                     // TODO: set language based on doc scope
-                    Markdown::new(format!(
-                        "```{}\n{}\n```",
-                        language,
-                        option.detail.as_deref().unwrap_or_default(),
-                    ))
+                    Markdown::new(
+                        format!(
+                            "```{}\n{}\n```",
+                            language,
+                            option.detail.as_deref().unwrap_or_default(),
+                        ),
+                        cx.editor.syn_loader.clone(),
+                    )
                 }
                 None => return,
             };
diff --git a/helix-term/src/ui/markdown.rs b/helix-term/src/ui/markdown.rs
index be113747..91086f7b 100644
--- a/helix-term/src/ui/markdown.rs
+++ b/helix-term/src/ui/markdown.rs
@@ -7,25 +7,34 @@ use tui::{
     text::Text,
 };
 
-use std::borrow::Cow;
+use std::{borrow::Cow, sync::Arc};
 
-use helix_core::Position;
+use helix_core::{syntax, Position};
 use helix_view::{Editor, Theme};
 
 pub struct Markdown {
     contents: String,
+
+    config_loader: Arc<syntax::Loader>,
 }
 
 // TODO: pre-render and self reference via Pin
 // better yet, just use Tendril + subtendril for references
 
 impl Markdown {
-    pub fn new(contents: String) -> Self {
-        Self { contents }
+    pub fn new(contents: String, config_loader: Arc<syntax::Loader>) -> Self {
+        Self {
+            contents,
+            config_loader,
+        }
     }
 }
 
-fn parse<'a>(contents: &'a str, theme: Option<&Theme>) -> tui::text::Text<'a> {
+fn parse<'a>(
+    contents: &'a str,
+    theme: Option<&Theme>,
+    loader: &syntax::Loader,
+) -> tui::text::Text<'a> {
     use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag};
     use tui::text::{Span, Spans, Text};
 
@@ -79,9 +88,7 @@ fn parse<'a>(contents: &'a str, theme: Option<&Theme>) -> tui::text::Text<'a> {
                         use helix_core::Rope;
 
                         let rope = Rope::from(text.as_ref());
-                        let syntax = syntax::LOADER
-                            .get()
-                            .unwrap()
+                        let syntax = loader
                             .language_config_for_scope(&format!("source.{}", language))
                             .and_then(|config| config.highlight_config(theme.scopes()))
                             .map(|config| Syntax::new(&rope, config));
@@ -101,9 +108,7 @@ fn parse<'a>(contents: &'a str, theme: Option<&Theme>) -> tui::text::Text<'a> {
                                     }
                                     HighlightEvent::Source { start, end } => {
                                         let style = match highlights.first() {
-                                            Some(span) => {
-                                                theme.get(theme.scopes()[span.0].as_str())
-                                            }
+                                            Some(span) => theme.get(&theme.scopes()[span.0]),
                                             None => text_style,
                                         };
 
@@ -196,7 +201,7 @@ impl Component for Markdown {
     fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
         use tui::widgets::{Paragraph, Widget, Wrap};
 
-        let text = parse(&self.contents, Some(&cx.editor.theme));
+        let text = parse(&self.contents, Some(&cx.editor.theme), &self.config_loader);
 
         let par = Paragraph::new(text)
             .wrap(Wrap { trim: false })
@@ -207,7 +212,7 @@ impl Component for Markdown {
     }
 
     fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
-        let contents = parse(&self.contents, None);
+        let contents = parse(&self.contents, None, &self.config_loader);
         let padding = 2;
         let width = std::cmp::min(contents.width() as u16 + padding, viewport.0);
         let height = std::cmp::min(contents.height() as u16 + padding, viewport.1);
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index e9a8097c..4d5a23b6 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -9,11 +9,11 @@ use std::sync::Arc;
 use helix_core::{
     chars::{char_is_linebreak, char_is_whitespace},
     history::History,
-    syntax::{LanguageConfiguration, LOADER},
+    syntax::{self, LanguageConfiguration},
     ChangeSet, Diagnostic, Rope, Selection, State, Syntax, Transaction,
 };
 
-use crate::{DocumentId, ViewId};
+use crate::{DocumentId, Theme, ViewId};
 
 use std::collections::HashMap;
 
@@ -236,7 +236,11 @@ impl Document {
     }
 
     // TODO: async fn?
-    pub fn load(path: PathBuf) -> Result<Self, Error> {
+    pub fn load(
+        path: PathBuf,
+        theme: Option<&Theme>,
+        config_loader: Option<&syntax::Loader>,
+    ) -> Result<Self, Error> {
         use std::{fs::File, io::BufReader};
 
         let doc = if !path.exists() {
@@ -256,6 +260,10 @@ impl Document {
         doc.set_path(&path)?;
         doc.detect_indent_style();
 
+        if let Some(loader) = config_loader {
+            doc.detect_language(theme, loader);
+        }
+
         Ok(doc)
     }
 
@@ -330,12 +338,10 @@ impl Document {
         }
     }
 
-    fn detect_language(&mut self) {
-        if let Some(path) = self.path() {
-            let loader = LOADER.get().unwrap();
-            let language_config = loader.language_config_for_file_name(path);
-            let scopes = loader.scopes();
-            self.set_language(language_config, scopes);
+    pub fn detect_language(&mut self, theme: Option<&Theme>, config_loader: &syntax::Loader) {
+        if let Some(path) = &self.path {
+            let language_config = config_loader.language_config_for_file_name(path);
+            self.set_language(theme, language_config);
         }
     }
 
@@ -472,18 +478,16 @@ impl Document {
         // and error out when document is saved
         self.path = Some(path);
 
-        // try detecting the language based on filepath
-        self.detect_language();
-
         Ok(())
     }
 
     pub fn set_language(
         &mut self,
+        theme: Option<&Theme>,
         language_config: Option<Arc<helix_core::syntax::LanguageConfiguration>>,
-        scopes: &[String],
     ) {
         if let Some(language_config) = language_config {
+            let scopes = theme.map(|theme| theme.scopes()).unwrap_or(&[]);
             if let Some(highlight_config) = language_config.highlight_config(scopes) {
                 let syntax = Syntax::new(&self.text, highlight_config);
                 self.syntax = Some(syntax);
@@ -497,12 +501,15 @@ impl Document {
         };
     }
 
-    pub fn set_language2(&mut self, scope: &str) {
-        let loader = LOADER.get().unwrap();
-        let language_config = loader.language_config_for_scope(scope);
-        let scopes = loader.scopes();
+    pub fn set_language2(
+        &mut self,
+        scope: &str,
+        theme: Option<&Theme>,
+        config_loader: Arc<syntax::Loader>,
+    ) {
+        let language_config = config_loader.language_config_for_scope(scope);
 
-        self.set_language(language_config, scopes);
+        self.set_language(theme, language_config);
     }
 
     pub fn set_language_server(&mut self, language_server: Option<Arc<helix_lsp::Client>>) {
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index db8ae87a..83d5cbf6 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -1,10 +1,14 @@
-use crate::{theme::Theme, tree::Tree, Document, DocumentId, RegisterSelection, View, ViewId};
+use crate::{
+    theme::{self, Theme},
+    tree::Tree,
+    Document, DocumentId, RegisterSelection, View, ViewId,
+};
+use helix_core::syntax;
 use tui::layout::Rect;
 use tui::terminal::CursorKind;
 
 use futures_util::future;
-use std::path::PathBuf;
-use std::time::Duration;
+use std::{path::PathBuf, sync::Arc, time::Duration};
 
 use slotmap::SlotMap;
 
@@ -24,6 +28,9 @@ pub struct Editor {
     pub theme: Theme,
     pub language_servers: helix_lsp::Registry,
 
+    pub syn_loader: Arc<syntax::Loader>,
+    pub theme_loader: Arc<theme::Loader>,
+
     pub status_msg: Option<(String, Severity)>,
 }
 
@@ -35,27 +42,11 @@ pub enum Action {
 }
 
 impl Editor {
-    pub fn new(mut area: tui::layout::Rect) -> Self {
-        use helix_core::config_dir;
-        let config = std::fs::read(config_dir().join("theme.toml"));
-        // load $HOME/.config/helix/theme.toml, fallback to default config
-        let toml = config
-            .as_deref()
-            .unwrap_or(include_bytes!("../../theme.toml"));
-        let theme: Theme = toml::from_slice(toml).expect("failed to parse theme.toml");
-
-        // initialize language registry
-        use helix_core::syntax::{Loader, LOADER};
-
-        // load $HOME/.config/helix/languages.toml, fallback to default config
-        let config = std::fs::read(helix_core::config_dir().join("languages.toml"));
-        let toml = config
-            .as_deref()
-            .unwrap_or(include_bytes!("../../languages.toml"));
-
-        let config = toml::from_slice(toml).expect("Could not parse languages.toml");
-        LOADER.get_or_init(|| Loader::new(config, theme.scopes().to_vec()));
-
+    pub fn new(
+        mut area: tui::layout::Rect,
+        themes: Arc<theme::Loader>,
+        config_loader: Arc<syntax::Loader>,
+    ) -> Self {
         let language_servers = helix_lsp::Registry::new();
 
         // HAXX: offset the render area height by 1 to account for prompt/commandline
@@ -66,8 +57,10 @@ impl Editor {
             documents: SlotMap::with_key(),
             count: None,
             selected_register: RegisterSelection::default(),
-            theme,
+            theme: themes.default(),
             language_servers,
+            syn_loader: config_loader,
+            theme_loader: themes,
             registers: Registers::default(),
             status_msg: None,
         }
@@ -85,6 +78,32 @@ impl Editor {
         self.status_msg = Some((error, Severity::Error));
     }
 
+    pub fn set_theme(&mut self, theme: Theme) {
+        let scopes = theme.scopes();
+        for config in self
+            .syn_loader
+            .language_configs_iter()
+            .filter(|cfg| cfg.is_highlight_initialized())
+        {
+            config.highlight_config(scopes);
+        }
+
+        self.theme = theme;
+        self._refresh();
+    }
+
+    pub fn set_theme_from_name(&mut self, theme: &str) {
+        let theme = match self.theme_loader.load(theme.as_ref()) {
+            Ok(theme) => theme,
+            Err(e) => {
+                log::warn!("failed setting theme `{}` - {}", theme, e);
+                return;
+            }
+        };
+
+        self.set_theme(theme);
+    }
+
     fn _refresh(&mut self) {
         for (view, _) in self.tree.views_mut() {
             let doc = &self.documents[view.doc];
@@ -168,7 +187,7 @@ impl Editor {
         let id = if let Some(id) = id {
             id
         } else {
-            let mut doc = Document::load(path)?;
+            let mut doc = Document::load(path, Some(&self.theme), Some(&self.syn_loader))?;
 
             // try to find a language server based on the language name
             let language_server = doc
@@ -254,6 +273,10 @@ impl Editor {
         self.documents.iter().map(|(_id, doc)| doc)
     }
 
+    pub fn documents_mut(&mut self) -> impl Iterator<Item = &mut Document> {
+        self.documents.iter_mut().map(|(_id, doc)| doc)
+    }
+
     // pub fn current_document(&self) -> Document {
     //     let id = self.view().doc;
     //     let doc = &mut editor.documents[id];