on master help sign in
Add ability to change theme on editor
ce97a2fwojciechkepka
Cargo.lock+7
@@ -17,6 +17,12 @@ version = "1.0.41"
17source = "registry+https://github.com/rust-lang/crates.io-index"
17source = "registry+https://github.com/rust-lang/crates.io-index"
18checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61"
18checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61"
19
19
20+[[package]]
21+name = "arc-swap"
22+version = "1.3.0"
23+source = "registry+https://github.com/rust-lang/crates.io-index"
24+checksum = "e906254e445520903e7fc9da4f709886c84ae4bc4ddaf0e093188d66df4dc820"
25+
20[[package]]
26[[package]]
21name = "autocfg"
27name = "autocfg"
22version = "1.0.1"
28version = "1.0.1"
@@ -254,6 +260,7 @@ dependencies = [
254name = "helix-core"
260name = "helix-core"
255version = "0.2.0"
261version = "0.2.0"
256dependencies = [
262dependencies = [
263+ "arc-swap",
257 "etcetera",
264 "etcetera",
258 "helix-syntax",
265 "helix-syntax",
259 "once_cell",
266 "once_cell",
helix-core/Cargo.toml+1
@@ -25,6 +25,7 @@ unicode-general-category = "0.4.0"
25# slab = "0.4.2"
25# slab = "0.4.2"
26tree-sitter = "0.19"
26tree-sitter = "0.19"
27once_cell = "1.8"
27once_cell = "1.8"
28+arc-swap = "1"
28regex = "1"
29regex = "1"
29
30
30serde = { version = "1.0", features = ["derive"] }
31serde = { version = "1.0", features = ["derive"] }
helix-core/src/indent.rs+17−20
@@ -254,26 +254,23 @@ where
254 Configuration, IndentationConfiguration, Lang, LanguageConfiguration, Loader,
254 Configuration, IndentationConfiguration, Lang, LanguageConfiguration, Loader,
255 };
255 };
256 use once_cell::sync::OnceCell;
256 use once_cell::sync::OnceCell;
257− let loader = Loader::new(
257+ let loader = Loader::new(Configuration {
258− Configuration {
259− language: vec![LanguageConfiguration {
258+ language: vec![LanguageConfiguration {
260− scope: "source.rust".to_string(),
259+ scope: "source.rust".to_string(),
261− file_types: vec!["rs".to_string()],
260+ file_types: vec!["rs".to_string()],
262− language_id: Lang::Rust,
261+ language_id: Lang::Rust,
263− highlight_config: OnceCell::new(),
262+ highlight_config: OnceCell::new(),
264− //
263+ //
265− roots: vec![],
264+ roots: vec![],
266− auto_format: false,
265+ auto_format: false,
267− language_server: None,
266+ language_server: None,
268− indent: Some(IndentationConfiguration {
267+ indent: Some(IndentationConfiguration {
269− tab_width: 4,
268+ tab_width: 4,
270− unit: String::from(" "),
269+ unit: String::from(" "),
271− }),
270+ }),
272− indent_query: OnceCell::new(),
271+ indent_query: OnceCell::new(),
273− }],
274− },
272+ }],
275− Vec::new(),
276− );
273+ });
277
274
278 // set runtime path so we can find the queries
275 // set runtime path so we can find the queries
279 let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
276 let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
helix-core/src/lib.rs+1−1
@@ -50,7 +50,7 @@ pub fn find_root(root: Option<&str>) -> Option<std::path::PathBuf> {
50}
50}
51
51
52#[cfg(not(embed_runtime))]
52#[cfg(not(embed_runtime))]
53−fn runtime_dir() -> std::path::PathBuf {
53+pub fn runtime_dir() -> std::path::PathBuf {
54 if let Ok(dir) = std::env::var("HELIX_RUNTIME") {
54 if let Ok(dir) = std::env::var("HELIX_RUNTIME") {
55 return dir.into();
55 return dir.into();
56 }
56 }
helix-core/src/syntax.rs+59−42
@@ -1,6 +1,8 @@
1use crate::{regex::Regex, Change, Rope, RopeSlice, Transaction};
1use crate::{regex::Regex, Change, Rope, RopeSlice, Transaction};
2pub use helix_syntax::{get_language, get_language_name, Lang};
2pub use helix_syntax::{get_language, get_language_name, Lang};
3
3
4+use arc_swap::ArcSwap;
5+
4use std::{
6use std::{
5 borrow::Cow,
7 borrow::Cow,
6 cell::RefCell,
8 cell::RefCell,
@@ -143,35 +145,48 @@ fn read_query(language: &str, filename: &str) -> String {
143}
145}
144
146
145impl LanguageConfiguration {
147impl LanguageConfiguration {
146− pub fn highlight_config(&self, scopes: &[String]) -> Option<Arc<HighlightConfiguration>> {
148+ fn initialize_highlight(&self, scopes: &[String]) -> Option<Arc<HighlightConfiguration>> {
147− self.highlight_config
148− .get_or_init(|| {
149− let language = get_language_name(self.language_id).to_ascii_lowercase();
149+ let language = get_language_name(self.language_id).to_ascii_lowercase();
150
150
151− let highlights_query = read_query(&language, "highlights.scm");
151+ let highlights_query = read_query(&language, "highlights.scm");
152− // always highlight syntax errors
152+ // always highlight syntax errors
153− // highlights_query += "\n(ERROR) @error";
153+ // highlights_query += "\n(ERROR) @error";
154
154
155− let injections_query = read_query(&language, "injections.scm");
155+ let injections_query = read_query(&language, "injections.scm");
156
156
157− let locals_query = "";
157+ let locals_query = "";
158
158
159− if highlights_query.is_empty() {
159+ if highlights_query.is_empty() {
160− None
160+ None
161− } else {
161+ } else {
162− let language = get_language(self.language_id);
162+ let language = get_language(self.language_id);
163− let mut config = HighlightConfiguration::new(
163+ let mut config = HighlightConfiguration::new(
164− language,
164+ language,
165− &highlights_query,
165+ &highlights_query,
166− &injections_query,
166+ &injections_query,
167− locals_query,
167+ locals_query,
168− )
168+ )
169− .unwrap(); // TODO: no unwrap
169+ .unwrap(); // TODO: no unwrap
170− config.configure(scopes);
170+ config.configure(scopes);
171− Some(Arc::new(config))
171+ Some(Arc::new(config))
172+ }
172− }
173+ }
174+
175+ pub fn highlight_config(&self, scopes: &[String]) -> Option<Arc<HighlightConfiguration>> {
176+ if let Some(config) = self.highlight_config.get() {
177+ if let Some(config) = config {
178+ config.configure(scopes);
173− })
179+ }
180+ config.clone()
181+ } else {
182+ self.highlight_config
183+ .get_or_init(|| self.initialize_highlight(scopes))
174− .clone()
184+ .clone()
185+ }
186+ }
187+
188+ pub fn is_highlight_initialized(&self) -> bool {
189+ self.highlight_config.get().is_some()
175 }
190 }
176
191
177 pub fn indent_query(&self) -> Option<&IndentQuery> {
192 pub fn indent_query(&self) -> Option<&IndentQuery> {
@@ -190,22 +205,18 @@ impl LanguageConfiguration {
190 }
205 }
191}
206}
192
207
193−pub static LOADER: OnceCell<Loader> = OnceCell::new();
194−
195#[derive(Debug)]
208#[derive(Debug)]
196pub struct Loader {
209pub struct Loader {
197 // highlight_names ?
210 // highlight_names ?
198 language_configs: Vec<Arc<LanguageConfiguration>>,
211 language_configs: Vec<Arc<LanguageConfiguration>>,
199 language_config_ids_by_file_type: HashMap<String, usize>, // Vec<usize>
212 language_config_ids_by_file_type: HashMap<String, usize>, // Vec<usize>
200− scopes: Vec<String>,
201}
213}
202
214
203impl Loader {
215impl Loader {
204− pub fn new(config: Configuration, scopes: Vec<String>) -> Self {
216+ pub fn new(config: Configuration) -> Self {
205 let mut loader = Self {
217 let mut loader = Self {
206 language_configs: Vec::new(),
218 language_configs: Vec::new(),
207 language_config_ids_by_file_type: HashMap::new(),
219 language_config_ids_by_file_type: HashMap::new(),
208− scopes,
209 };
220 };
210
221
211 for config in config.language {
222 for config in config.language {
@@ -225,10 +236,6 @@ impl Loader {
225 loader
236 loader
226 }
237 }
227
238
228− pub fn scopes(&self) -> &[String] {
229− &self.scopes
230− }
231−
232 pub fn language_config_for_file_name(&self, path: &Path) -> Option<Arc<LanguageConfiguration>> {
239 pub fn language_config_for_file_name(&self, path: &Path) -> Option<Arc<LanguageConfiguration>> {
233 // Find all the language configurations that match this file name
240 // Find all the language configurations that match this file name
234 // or a suffix of the file name.
241 // or a suffix of the file name.
@@ -253,6 +260,10 @@ impl Loader {
253 .find(|config| config.scope == scope)
260 .find(|config| config.scope == scope)
254 .cloned()
261 .cloned()
255 }
262 }
263+
264+ pub fn language_configs_iter(&self) -> impl Iterator<Item = &Arc<LanguageConfiguration>> {
265+ self.language_configs.iter()
266+ }
256}
267}
257
268
258pub struct TsParser {
269pub struct TsParser {
@@ -771,7 +782,7 @@ pub struct HighlightConfiguration {
771 combined_injections_query: Option<Query>,
782 combined_injections_query: Option<Query>,
772 locals_pattern_index: usize,
783 locals_pattern_index: usize,
773 highlights_pattern_index: usize,
784 highlights_pattern_index: usize,
774− highlight_indices: Vec<Option<Highlight>>,
785+ highlight_indices: ArcSwap<Vec<Option<Highlight>>>,
775 non_local_variable_patterns: Vec<bool>,
786 non_local_variable_patterns: Vec<bool>,
776 injection_content_capture_index: Option<u32>,
787 injection_content_capture_index: Option<u32>,
777 injection_language_capture_index: Option<u32>,
788 injection_language_capture_index: Option<u32>,
@@ -923,7 +934,7 @@ impl HighlightConfiguration {
923 }
934 }
924 }
935 }
925
936
926− let highlight_indices = vec![None; query.capture_names().len()];
937+ let highlight_indices = ArcSwap::from_pointee(vec![None; query.capture_names().len()]);
927 Ok(Self {
938 Ok(Self {
928 language,
939 language,
929 query,
940 query,
@@ -956,17 +967,20 @@ impl HighlightConfiguration {
956 ///
967 ///
957 /// When highlighting, results are returned as `Highlight` values, which contain the index
968 /// When highlighting, results are returned as `Highlight` values, which contain the index
958 /// of the matched highlight this list of highlight names.
969 /// of the matched highlight this list of highlight names.
959− pub fn configure(&mut self, recognized_names: &[String]) {
970+ pub fn configure(&self, recognized_names: &[String]) {
960 let mut capture_parts = Vec::new();
971 let mut capture_parts = Vec::new();
961− self.highlight_indices.clear();
972+ let indices: Vec<_> = self
962− self.highlight_indices
973+ .query
974+ .capture_names()
975+ .iter()
963− .extend(self.query.capture_names().iter().map(move |capture_name| {
976+ .map(move |capture_name| {
964 capture_parts.clear();
977 capture_parts.clear();
965 capture_parts.extend(capture_name.split('.'));
978 capture_parts.extend(capture_name.split('.'));
966
979
967 let mut best_index = None;
980 let mut best_index = None;
968 let mut best_match_len = 0;
981 let mut best_match_len = 0;
969 for (i, recognized_name) in recognized_names.iter().enumerate() {
982 for (i, recognized_name) in recognized_names.iter().enumerate() {
983+ let recognized_name = recognized_name;
970 let mut len = 0;
984 let mut len = 0;
971 let mut matches = true;
985 let mut matches = true;
972 for part in recognized_name.split('.') {
986 for part in recognized_name.split('.') {
@@ -982,7 +996,10 @@ impl HighlightConfiguration {
982 }
996 }
983 }
997 }
984 best_index.map(Highlight)
998 best_index.map(Highlight)
985− }));
999+ })
1000+ .collect();
1001+
1002+ self.highlight_indices.store(Arc::new(indices));
986 }
1003 }
987}
1004}
988
1005
@@ -1561,7 +1578,7 @@ where
1561 }
1578 }
1562 }
1579 }
1563
1580
1564− let current_highlight = layer.config.highlight_indices[capture.index as usize];
1581+ let current_highlight = layer.config.highlight_indices.load()[capture.index as usize];
1565
1582
1566 // If this node represents a local definition, then store the current
1583 // If this node represents a local definition, then store the current
1567 // highlight value on the local scope entry representing this node.
1584 // highlight value on the local scope entry representing this node.
helix-term/src/application.rs+37−3
@@ -1,5 +1,6 @@
1+use helix_core::syntax;
1use helix_lsp::{lsp, LspProgressMap};
2use helix_lsp::{lsp, LspProgressMap};
2−use helix_view::{document::Mode, Document, Editor, Theme, View};
3+use helix_view::{document::Mode, theme, Document, Editor, Theme, View};
3
4
4use crate::{args::Args, compositor::Compositor, config::Config, keymap::Keymaps, ui};
5use crate::{args::Args, compositor::Compositor, config::Config, keymap::Keymaps, ui};
5
6
@@ -14,7 +15,7 @@ use std::{
14 time::Duration,
15 time::Duration,
15};
16};
16
17
17−use anyhow::Error;
18+use anyhow::{Context, Error};
18
19
19use crossterm::{
20use crossterm::{
20 event::{Event, EventStream},
21 event::{Event, EventStream},
@@ -36,6 +37,8 @@ pub struct Application {
36 compositor: Compositor,
37 compositor: Compositor,
37 editor: Editor,
38 editor: Editor,
38
39
40+ theme_loader: Arc<theme::Loader>,
41+ syn_loader: Arc<syntax::Loader>,
39 callbacks: LspCallbacks,
42 callbacks: LspCallbacks,
40
43
41 lsp_progress: LspProgressMap,
44 lsp_progress: LspProgressMap,
@@ -47,7 +50,34 @@ impl Application {
47 use helix_view::editor::Action;
50 use helix_view::editor::Action;
48 let mut compositor = Compositor::new()?;
51 let mut compositor = Compositor::new()?;
49 let size = compositor.size();
52 let size = compositor.size();
53+
54+ let conf_dir = helix_core::config_dir();
55+
56+ let theme_loader =
57+ std::sync::Arc::new(theme::Loader::new(&conf_dir, &helix_core::runtime_dir()));
58+
59+ // load $HOME/.config/helix/languages.toml, fallback to default config
60+ let lang_conf = std::fs::read(conf_dir.join("languages.toml"));
61+ let lang_conf = lang_conf
62+ .as_deref()
63+ .unwrap_or(include_bytes!("../../languages.toml"));
64+
65+ let theme = if let Some(theme) = &config.global.theme {
66+ match theme_loader.load(theme) {
67+ Ok(theme) => theme,
68+ Err(e) => {
69+ log::warn!("failed to load theme `{}` - {}", theme, e);
70+ theme_loader.default()
71+ }
72+ }
73+ } else {
74+ theme_loader.default()
75+ };
76+
77+ let syn_loader_conf = toml::from_slice(lang_conf).expect("Could not parse languages.toml");
78+ let syn_loader = std::sync::Arc::new(syntax::Loader::new(syn_loader_conf));
79+
50− let mut editor = Editor::new(size);
80+ let mut editor = Editor::new(size, theme_loader.clone(), syn_loader.clone());
51
81
52 let mut editor_view = Box::new(ui::EditorView::new(config.keymaps));
82 let mut editor_view = Box::new(ui::EditorView::new(config.keymaps));
53 compositor.push(editor_view);
83 compositor.push(editor_view);
@@ -72,10 +102,14 @@ impl Application {
72 editor.new_file(Action::VerticalSplit);
102 editor.new_file(Action::VerticalSplit);
73 }
103 }
74
104
105+ editor.set_theme(theme);
106+
75 let mut app = Self {
107 let mut app = Self {
76 compositor,
108 compositor,
77 editor,
109 editor,
78
110
111+ theme_loader,
112+ syn_loader,
79 callbacks: FuturesUnordered::new(),
113 callbacks: FuturesUnordered::new(),
80 lsp_progress: LspProgressMap::new(),
114 lsp_progress: LspProgressMap::new(),
81 lsp_progress_enabled: config.global.lsp_progress,
115 lsp_progress_enabled: config.global.lsp_progress,
helix-term/src/ui/completion.rs+26−17
@@ -246,34 +246,43 @@ impl Component for Completion {
246 value: contents,
246 value: contents,
247 })) => {
247 })) => {
248 // TODO: convert to wrapped text
248 // TODO: convert to wrapped text
249− Markdown::new(format!(
249+ Markdown::new(
250+ format!(
250− "```{}\n{}\n```\n{}",
251+ "```{}\n{}\n```\n{}",
251− language,
252+ language,
252− option.detail.as_deref().unwrap_or_default(),
253+ option.detail.as_deref().unwrap_or_default(),
253− contents.clone()
254+ contents.clone()
255+ ),
256+ cx.editor.syn_loader.clone(),
254− ))
257+ )
255 }
258 }
256 Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
259 Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
257 kind: lsp::MarkupKind::Markdown,
260 kind: lsp::MarkupKind::Markdown,
258 value: contents,
261 value: contents,
259 })) => {
262 })) => {
260 // TODO: set language based on doc scope
263 // TODO: set language based on doc scope
261− Markdown::new(format!(
264+ Markdown::new(
265+ format!(
262− "```{}\n{}\n```\n{}",
266+ "```{}\n{}\n```\n{}",
263− language,
267+ language,
264− option.detail.as_deref().unwrap_or_default(),
268+ option.detail.as_deref().unwrap_or_default(),
265− contents.clone()
269+ contents.clone()
270+ ),
271+ cx.editor.syn_loader.clone(),
266− ))
272+ )
267 }
273 }
268 None if option.detail.is_some() => {
274 None if option.detail.is_some() => {
269 // TODO: copied from above
275 // TODO: copied from above
270
276
271 // TODO: set language based on doc scope
277 // TODO: set language based on doc scope
272− Markdown::new(format!(
278+ Markdown::new(
279+ format!(
273− "```{}\n{}\n```",
280+ "```{}\n{}\n```",
274− language,
281+ language,
275− option.detail.as_deref().unwrap_or_default(),
282+ option.detail.as_deref().unwrap_or_default(),
283+ ),
284+ cx.editor.syn_loader.clone(),
276− ))
285+ )
277 }
286 }
278 None => return,
287 None => return,
279 };
288 };
helix-term/src/ui/markdown.rs+18−13
@@ -7,25 +7,34 @@ use tui::{
7 text::Text,
7 text::Text,
8};
8};
9
9
10−use std::borrow::Cow;
10+use std::{borrow::Cow, sync::Arc};
11
11
12−use helix_core::Position;
12+use helix_core::{syntax, Position};
13use helix_view::{Editor, Theme};
13use helix_view::{Editor, Theme};
14
14
15pub struct Markdown {
15pub struct Markdown {
16 contents: String,
16 contents: String,
17+
18+ config_loader: Arc<syntax::Loader>,
17}
19}
18
20
19// TODO: pre-render and self reference via Pin
21// TODO: pre-render and self reference via Pin
20// better yet, just use Tendril + subtendril for references
22// better yet, just use Tendril + subtendril for references
21
23
22impl Markdown {
24impl Markdown {
23− pub fn new(contents: String) -> Self {
25+ pub fn new(contents: String, config_loader: Arc<syntax::Loader>) -> Self {
26+ Self {
24− Self { contents }
27+ contents,
28+ config_loader,
29+ }
25 }
30 }
26}
31}
27
32
33+fn parse<'a>(
34+ contents: &'a str,
28−fn parse<'a>(contents: &'a str, theme: Option<&Theme>) -> tui::text::Text<'a> {
35+ theme: Option<&Theme>,
36+ loader: &syntax::Loader,
37+) -> tui::text::Text<'a> {
29 use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag};
38 use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag};
30 use tui::text::{Span, Spans, Text};
39 use tui::text::{Span, Spans, Text};
31
40
@@ -79,9 +88,7 @@ fn parse<'a>(contents: &'a str, theme: Option<&Theme>) -> tui::text::Text<'a> {
79 use helix_core::Rope;
88 use helix_core::Rope;
80
89
81 let rope = Rope::from(text.as_ref());
90 let rope = Rope::from(text.as_ref());
82− let syntax = syntax::LOADER
91+ let syntax = loader
83− .get()
84− .unwrap()
85 .language_config_for_scope(&format!("source.{}", language))
92 .language_config_for_scope(&format!("source.{}", language))
86 .and_then(|config| config.highlight_config(theme.scopes()))
93 .and_then(|config| config.highlight_config(theme.scopes()))
87 .map(|config| Syntax::new(&rope, config));
94 .map(|config| Syntax::new(&rope, config));
@@ -101,9 +108,7 @@ fn parse<'a>(contents: &'a str, theme: Option<&Theme>) -> tui::text::Text<'a> {
101 }
108 }
102 HighlightEvent::Source { start, end } => {
109 HighlightEvent::Source { start, end } => {
103 let style = match highlights.first() {
110 let style = match highlights.first() {
104− Some(span) => {
105− theme.get(theme.scopes()[span.0].as_str())
111+ Some(span) => theme.get(&theme.scopes()[span.0]),
106− }
107 None => text_style,
112 None => text_style,
108 };
113 };
109
114
@@ -196,7 +201,7 @@ impl Component for Markdown {
196 fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
201 fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
197 use tui::widgets::{Paragraph, Widget, Wrap};
202 use tui::widgets::{Paragraph, Widget, Wrap};
198
203
199− let text = parse(&self.contents, Some(&cx.editor.theme));
204+ let text = parse(&self.contents, Some(&cx.editor.theme), &self.config_loader);
200
205
201 let par = Paragraph::new(text)
206 let par = Paragraph::new(text)
202 .wrap(Wrap { trim: false })
207 .wrap(Wrap { trim: false })
@@ -207,7 +212,7 @@ impl Component for Markdown {
207 }
212 }
208
213
209 fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
214 fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
210− let contents = parse(&self.contents, None);
215+ let contents = parse(&self.contents, None, &self.config_loader);
211 let padding = 2;
216 let padding = 2;
212 let width = std::cmp::min(contents.width() as u16 + padding, viewport.0);
217 let width = std::cmp::min(contents.width() as u16 + padding, viewport.0);
213 let height = std::cmp::min(contents.height() as u16 + padding, viewport.1);
218 let height = std::cmp::min(contents.height() as u16 + padding, viewport.1);
helix-view/src/document.rs+25−18
@@ -9,11 +9,11 @@ use std::sync::Arc;
9use helix_core::{
9use helix_core::{
10 chars::{char_is_linebreak, char_is_whitespace},
10 chars::{char_is_linebreak, char_is_whitespace},
11 history::History,
11 history::History,
12− syntax::{LanguageConfiguration, LOADER},
12+ syntax::{self, LanguageConfiguration},
13 ChangeSet, Diagnostic, Rope, Selection, State, Syntax, Transaction,
13 ChangeSet, Diagnostic, Rope, Selection, State, Syntax, Transaction,
14};
14};
15
15
16−use crate::{DocumentId, ViewId};
16+use crate::{DocumentId, Theme, ViewId};
17
17
18use std::collections::HashMap;
18use std::collections::HashMap;
19
19
@@ -236,7 +236,11 @@ impl Document {
236 }
236 }
237
237
238 // TODO: async fn?
238 // TODO: async fn?
239+ pub fn load(
240+ path: PathBuf,
241+ theme: Option<&Theme>,
242+ config_loader: Option<&syntax::Loader>,
239− pub fn load(path: PathBuf) -> Result<Self, Error> {
243+ ) -> Result<Self, Error> {
240 use std::{fs::File, io::BufReader};
244 use std::{fs::File, io::BufReader};
241
245
242 let doc = if !path.exists() {
246 let doc = if !path.exists() {
@@ -256,6 +260,10 @@ impl Document {
256 doc.set_path(&path)?;
260 doc.set_path(&path)?;
257 doc.detect_indent_style();
261 doc.detect_indent_style();
258
262
263+ if let Some(loader) = config_loader {
264+ doc.detect_language(theme, loader);
265+ }
266+
259 Ok(doc)
267 Ok(doc)
260 }
268 }
261
269
@@ -330,12 +338,10 @@ impl Document {
330 }
338 }
331 }
339 }
332
340
333− fn detect_language(&mut self) {
341+ pub fn detect_language(&mut self, theme: Option<&Theme>, config_loader: &syntax::Loader) {
334− if let Some(path) = self.path() {
342+ if let Some(path) = &self.path {
335− let loader = LOADER.get().unwrap();
336− let language_config = loader.language_config_for_file_name(path);
343+ let language_config = config_loader.language_config_for_file_name(path);
337− let scopes = loader.scopes();
338− self.set_language(language_config, scopes);
344+ self.set_language(theme, language_config);
339 }
345 }
340 }
346 }
341
347
@@ -472,18 +478,16 @@ impl Document {
472 // and error out when document is saved
478 // and error out when document is saved
473 self.path = Some(path);
479 self.path = Some(path);
474
480
475− // try detecting the language based on filepath
476− self.detect_language();
477−
478 Ok(())
481 Ok(())
479 }
482 }
480
483
481 pub fn set_language(
484 pub fn set_language(
482 &mut self,
485 &mut self,
486+ theme: Option<&Theme>,
483 language_config: Option<Arc<helix_core::syntax::LanguageConfiguration>>,
487 language_config: Option<Arc<helix_core::syntax::LanguageConfiguration>>,
484− scopes: &[String],
485 ) {
488 ) {
486 if let Some(language_config) = language_config {
489 if let Some(language_config) = language_config {
490+ let scopes = theme.map(|theme| theme.scopes()).unwrap_or(&[]);
487 if let Some(highlight_config) = language_config.highlight_config(scopes) {
491 if let Some(highlight_config) = language_config.highlight_config(scopes) {
488 let syntax = Syntax::new(&self.text, highlight_config);
492 let syntax = Syntax::new(&self.text, highlight_config);
489 self.syntax = Some(syntax);
493 self.syntax = Some(syntax);
@@ -497,12 +501,15 @@ impl Document {
497 };
501 };
498 }
502 }
499
503
500− pub fn set_language2(&mut self, scope: &str) {
504+ pub fn set_language2(
501− let loader = LOADER.get().unwrap();
505+ &mut self,
506+ scope: &str,
507+ theme: Option<&Theme>,
508+ config_loader: Arc<syntax::Loader>,
509+ ) {
502− let language_config = loader.language_config_for_scope(scope);
510+ let language_config = config_loader.language_config_for_scope(scope);
503− let scopes = loader.scopes();
504
511
505− self.set_language(language_config, scopes);
512+ self.set_language(theme, language_config);
506 }
513 }
507
514
508 pub fn set_language_server(&mut self, language_server: Option<Arc<helix_lsp::Client>>) {
515 pub fn set_language_server(&mut self, language_server: Option<Arc<helix_lsp::Client>>) {
helix-view/src/editor.rs+49−26
@@ -1,10 +1,14 @@
1+use crate::{
2+ theme::{self, Theme},
3+ tree::Tree,
1−use crate::{theme::Theme, tree::Tree, Document, DocumentId, RegisterSelection, View, ViewId};
4+ Document, DocumentId, RegisterSelection, View, ViewId,
5+};
6+use helix_core::syntax;
2use tui::layout::Rect;
7use tui::layout::Rect;
3use tui::terminal::CursorKind;
8use tui::terminal::CursorKind;
4
9
5use futures_util::future;
10use futures_util::future;
6−use std::path::PathBuf;
11+use std::{path::PathBuf, sync::Arc, time::Duration};
7−use std::time::Duration;
8
12
9use slotmap::SlotMap;
13use slotmap::SlotMap;
10
14
@@ -24,6 +28,9 @@ pub struct Editor {
24 pub theme: Theme,
28 pub theme: Theme,
25 pub language_servers: helix_lsp::Registry,
29 pub language_servers: helix_lsp::Registry,
26
30
31+ pub syn_loader: Arc<syntax::Loader>,
32+ pub theme_loader: Arc<theme::Loader>,
33+
27 pub status_msg: Option<(String, Severity)>,
34 pub status_msg: Option<(String, Severity)>,
28}
35}
29
36
@@ -35,27 +42,11 @@ pub enum Action {
35}
42}
36
43
37impl Editor {
44impl Editor {
45+ pub fn new(
38− pub fn new(mut area: tui::layout::Rect) -> Self {
46+ mut area: tui::layout::Rect,
39− use helix_core::config_dir;
47+ themes: Arc<theme::Loader>,
40− let config = std::fs::read(config_dir().join("theme.toml"));
48+ config_loader: Arc<syntax::Loader>,
41− // load $HOME/.config/helix/theme.toml, fallback to default config
49+ ) -> Self {
42− let toml = config
43− .as_deref()
44− .unwrap_or(include_bytes!("../../theme.toml"));
45− let theme: Theme = toml::from_slice(toml).expect("failed to parse theme.toml");
46−
47− // initialize language registry
48− use helix_core::syntax::{Loader, LOADER};
49−
50− // load $HOME/.config/helix/languages.toml, fallback to default config
51− let config = std::fs::read(helix_core::config_dir().join("languages.toml"));
52− let toml = config
53− .as_deref()
54− .unwrap_or(include_bytes!("../../languages.toml"));
55−
56− let config = toml::from_slice(toml).expect("Could not parse languages.toml");
57− LOADER.get_or_init(|| Loader::new(config, theme.scopes().to_vec()));
58−
59 let language_servers = helix_lsp::Registry::new();
50 let language_servers = helix_lsp::Registry::new();
60
51
61 // HAXX: offset the render area height by 1 to account for prompt/commandline
52 // HAXX: offset the render area height by 1 to account for prompt/commandline
@@ -66,8 +57,10 @@ impl Editor {
66 documents: SlotMap::with_key(),
57 documents: SlotMap::with_key(),
67 count: None,
58 count: None,
68 selected_register: RegisterSelection::default(),
59 selected_register: RegisterSelection::default(),
69− theme,
60+ theme: themes.default(),
70 language_servers,
61 language_servers,
62+ syn_loader: config_loader,
63+ theme_loader: themes,
71 registers: Registers::default(),
64 registers: Registers::default(),
72 status_msg: None,
65 status_msg: None,
73 }
66 }
@@ -85,6 +78,32 @@ impl Editor {
85 self.status_msg = Some((error, Severity::Error));
78 self.status_msg = Some((error, Severity::Error));
86 }
79 }
87
80
81+ pub fn set_theme(&mut self, theme: Theme) {
82+ let scopes = theme.scopes();
83+ for config in self
84+ .syn_loader
85+ .language_configs_iter()
86+ .filter(|cfg| cfg.is_highlight_initialized())
87+ {
88+ config.highlight_config(scopes);
89+ }
90+
91+ self.theme = theme;
92+ self._refresh();
93+ }
94+
95+ pub fn set_theme_from_name(&mut self, theme: &str) {
96+ let theme = match self.theme_loader.load(theme.as_ref()) {
97+ Ok(theme) => theme,
98+ Err(e) => {
99+ log::warn!("failed setting theme `{}` - {}", theme, e);
100+ return;
101+ }
102+ };
103+
104+ self.set_theme(theme);
105+ }
106+
88 fn _refresh(&mut self) {
107 fn _refresh(&mut self) {
89 for (view, _) in self.tree.views_mut() {
108 for (view, _) in self.tree.views_mut() {
90 let doc = &self.documents[view.doc];
109 let doc = &self.documents[view.doc];
@@ -168,7 +187,7 @@ impl Editor {
168 let id = if let Some(id) = id {
187 let id = if let Some(id) = id {
169 id
188 id
170 } else {
189 } else {
171− let mut doc = Document::load(path)?;
190+ let mut doc = Document::load(path, Some(&self.theme), Some(&self.syn_loader))?;
172
191
173 // try to find a language server based on the language name
192 // try to find a language server based on the language name
174 let language_server = doc
193 let language_server = doc
@@ -254,6 +273,10 @@ impl Editor {
254 self.documents.iter().map(|(_id, doc)| doc)
273 self.documents.iter().map(|(_id, doc)| doc)
255 }
274 }
256
275
276+ pub fn documents_mut(&mut self) -> impl Iterator<Item = &mut Document> {
277+ self.documents.iter_mut().map(|(_id, doc)| doc)
278+ }
279+
257 // pub fn current_document(&self) -> Document {
280 // pub fn current_document(&self) -> Document {
258 // let id = self.view().doc;
281 // let id = self.view().doc;
259 // let doc = &mut editor.documents[id];
282 // let doc = &mut editor.documents[id];