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