mirror of
https://github.com/helix-editor/helix.git
synced 2025-10-06 00:13:28 +02:00
Replace tree-sitter with tree-house
This commit is contained in:
@@ -10,7 +10,7 @@ use helix_core::diagnostic::DiagnosticProvider;
|
||||
use helix_core::doc_formatter::TextFormat;
|
||||
use helix_core::encoding::Encoding;
|
||||
use helix_core::snippets::{ActiveSnippet, SnippetRenderCtx};
|
||||
use helix_core::syntax::{config::LanguageServerFeature, Highlight};
|
||||
use helix_core::syntax::config::LanguageServerFeature;
|
||||
use helix_core::text_annotations::{InlineAnnotation, Overlay};
|
||||
use helix_event::TaskController;
|
||||
use helix_lsp::util::lsp_pos_to_pos;
|
||||
@@ -219,7 +219,7 @@ pub struct Document {
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct DocumentColorSwatches {
|
||||
pub color_swatches: Vec<InlineAnnotation>,
|
||||
pub colors: Vec<Highlight>,
|
||||
pub colors: Vec<syntax::Highlight>,
|
||||
pub color_swatches_padding: Vec<InlineAnnotation>,
|
||||
}
|
||||
|
||||
@@ -1141,11 +1141,13 @@ impl Document {
|
||||
/// Detect the programming language based on the file type.
|
||||
pub fn detect_language_config(
|
||||
&self,
|
||||
config_loader: &syntax::Loader,
|
||||
loader: &syntax::Loader,
|
||||
) -> Option<Arc<syntax::config::LanguageConfiguration>> {
|
||||
config_loader
|
||||
.language_config_for_file_name(self.path.as_ref()?)
|
||||
.or_else(|| config_loader.language_config_for_shebang(self.text().slice(..)))
|
||||
let language = loader
|
||||
.language_for_filename(self.path.as_ref()?)
|
||||
.or_else(|| loader.language_for_shebang(self.text().slice(..)))?;
|
||||
|
||||
Some(loader.language(language).config().clone())
|
||||
}
|
||||
|
||||
/// Detect the indentation used in the file, or otherwise defaults to the language indentation
|
||||
@@ -1288,17 +1290,18 @@ impl Document {
|
||||
loader: &syntax::Loader,
|
||||
) {
|
||||
self.language = language_config;
|
||||
self.syntax = self
|
||||
.language
|
||||
.as_ref()
|
||||
.and_then(|config| config.highlight_config(&loader.scopes()))
|
||||
.and_then(|highlight_config| {
|
||||
Syntax::new(
|
||||
self.text.slice(..),
|
||||
highlight_config,
|
||||
self.syn_loader.clone(),
|
||||
)
|
||||
});
|
||||
self.syntax = self.language.as_ref().and_then(|config| {
|
||||
Syntax::new(self.text.slice(..), config.language(), loader)
|
||||
.map_err(|err| {
|
||||
// `NoRootConfig` means that there was an issue loading the language/syntax
|
||||
// config for the root language of the document. An error must have already
|
||||
// been logged by `LanguageData::syntax_config`.
|
||||
if err != syntax::HighlighterError::NoRootConfig {
|
||||
log::warn!("Error building syntax for '{}': {err}", self.display_name());
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
});
|
||||
}
|
||||
|
||||
/// Set the programming language for the file if you know the language but don't have the
|
||||
@@ -1308,10 +1311,11 @@ impl Document {
|
||||
language_id: &str,
|
||||
loader: &syntax::Loader,
|
||||
) -> anyhow::Result<()> {
|
||||
let language_config = loader
|
||||
.language_config_for_language_id(language_id)
|
||||
let language = loader
|
||||
.language_for_name(language_id)
|
||||
.ok_or_else(|| anyhow!("invalid language id: {}", language_id))?;
|
||||
self.set_language(Some(language_config), loader);
|
||||
let config = loader.language(language).config().clone();
|
||||
self.set_language(Some(config), loader);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1430,14 +1434,14 @@ impl Document {
|
||||
|
||||
// update tree-sitter syntax tree
|
||||
if let Some(syntax) = &mut self.syntax {
|
||||
// TODO: no unwrap
|
||||
let res = syntax.update(
|
||||
let loader = self.syn_loader.load();
|
||||
if let Err(err) = syntax.update(
|
||||
old_doc.slice(..),
|
||||
self.text.slice(..),
|
||||
transaction.changes(),
|
||||
);
|
||||
if res.is_err() {
|
||||
log::error!("TS parser failed, disabling TS for the current buffer: {res:?}");
|
||||
&loader,
|
||||
) {
|
||||
log::error!("TS parser failed, disabling TS for the current buffer: {err}");
|
||||
self.syntax = None;
|
||||
}
|
||||
}
|
||||
@@ -2245,8 +2249,7 @@ impl Document {
|
||||
viewport_width,
|
||||
wrap_indicator: wrap_indicator.into_boxed_str(),
|
||||
wrap_indicator_highlight: theme
|
||||
.and_then(|theme| theme.find_scope_index("ui.virtual.wrap"))
|
||||
.map(Highlight),
|
||||
.and_then(|theme| theme.find_highlight("ui.virtual.wrap")),
|
||||
soft_wrap_at_text_width,
|
||||
}
|
||||
}
|
||||
|
@@ -1362,7 +1362,7 @@ impl Editor {
|
||||
|
||||
fn set_theme_impl(&mut self, theme: Theme, preview: ThemeAction) {
|
||||
// `ui.selection` is the only scope required to be able to render a theme.
|
||||
if theme.find_scope_index_exact("ui.selection").is_none() {
|
||||
if theme.find_highlight_exact("ui.selection").is_none() {
|
||||
self.set_error("Invalid theme: `ui.selection` required");
|
||||
return;
|
||||
}
|
||||
@@ -1516,12 +1516,12 @@ impl Editor {
|
||||
if let helix_lsp::Error::ExecutableNotFound(err) = err {
|
||||
// Silence by default since some language servers might just not be installed
|
||||
log::debug!(
|
||||
"Language server not found for `{}` {} {}", language.scope(), lang, err,
|
||||
"Language server not found for `{}` {} {}", language.scope, lang, err,
|
||||
);
|
||||
} else {
|
||||
log::error!(
|
||||
"Failed to initialize the language servers for `{}` - `{}` {{ {} }}",
|
||||
language.scope(),
|
||||
language.scope,
|
||||
lang,
|
||||
err
|
||||
);
|
||||
|
@@ -294,43 +294,36 @@ fn build_theme_values(
|
||||
|
||||
impl Theme {
|
||||
/// To allow `Highlight` to represent arbitrary RGB colors without turning it into an enum,
|
||||
/// we interpret the last 3 bytes of a `Highlight` as RGB colors.
|
||||
const RGB_START: usize = (usize::MAX << (8 + 8 + 8)) - 1;
|
||||
/// we interpret the last 256^3 numbers as RGB.
|
||||
const RGB_START: u32 = (u32::MAX << (8 + 8 + 8)) - 1 - (u32::MAX - Highlight::MAX);
|
||||
|
||||
/// Interpret a Highlight with the RGB foreground
|
||||
fn decode_rgb_highlight(rgb: usize) -> Option<(u8, u8, u8)> {
|
||||
(rgb > Self::RGB_START).then(|| {
|
||||
let [b, g, r, ..] = rgb.to_ne_bytes();
|
||||
fn decode_rgb_highlight(highlight: Highlight) -> Option<(u8, u8, u8)> {
|
||||
(highlight.get() > Self::RGB_START).then(|| {
|
||||
let [b, g, r, ..] = (highlight.get() + 1).to_ne_bytes();
|
||||
(r, g, b)
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a Highlight that represents an RGB color
|
||||
pub fn rgb_highlight(r: u8, g: u8, b: u8) -> Highlight {
|
||||
Highlight(usize::from_ne_bytes([
|
||||
b,
|
||||
g,
|
||||
r,
|
||||
u8::MAX,
|
||||
u8::MAX,
|
||||
u8::MAX,
|
||||
u8::MAX,
|
||||
u8::MAX,
|
||||
]))
|
||||
// -1 because highlight is "non-max": u32::MAX is reserved for the null pointer
|
||||
// optimization.
|
||||
Highlight::new(u32::from_ne_bytes([b, g, r, u8::MAX]) - 1)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn highlight(&self, index: usize) -> Style {
|
||||
if let Some((red, green, blue)) = Self::decode_rgb_highlight(index) {
|
||||
pub fn highlight(&self, highlight: Highlight) -> Style {
|
||||
if let Some((red, green, blue)) = Self::decode_rgb_highlight(highlight) {
|
||||
Style::new().fg(Color::Rgb(red, green, blue))
|
||||
} else {
|
||||
self.highlights[index]
|
||||
self.highlights[highlight.idx()]
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scope(&self, index: usize) -> &str {
|
||||
&self.scopes[index]
|
||||
pub fn scope(&self, highlight: Highlight) -> &str {
|
||||
&self.scopes[highlight.idx()]
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
@@ -361,13 +354,16 @@ impl Theme {
|
||||
&self.scopes
|
||||
}
|
||||
|
||||
pub fn find_scope_index_exact(&self, scope: &str) -> Option<usize> {
|
||||
self.scopes().iter().position(|s| s == scope)
|
||||
pub fn find_highlight_exact(&self, scope: &str) -> Option<Highlight> {
|
||||
self.scopes()
|
||||
.iter()
|
||||
.position(|s| s == scope)
|
||||
.map(|idx| Highlight::new(idx as u32))
|
||||
}
|
||||
|
||||
pub fn find_scope_index(&self, mut scope: &str) -> Option<usize> {
|
||||
pub fn find_highlight(&self, mut scope: &str) -> Option<Highlight> {
|
||||
loop {
|
||||
if let Some(highlight) = self.find_scope_index_exact(scope) {
|
||||
if let Some(highlight) = self.find_highlight_exact(scope) {
|
||||
return Some(highlight);
|
||||
}
|
||||
if let Some(new_end) = scope.rfind('.') {
|
||||
@@ -626,23 +622,13 @@ mod tests {
|
||||
fn convert_to_and_from() {
|
||||
let (r, g, b) = (0xFF, 0xFE, 0xFA);
|
||||
let highlight = Theme::rgb_highlight(r, g, b);
|
||||
assert_eq!(Theme::decode_rgb_highlight(highlight.0), Some((r, g, b)));
|
||||
assert_eq!(Theme::decode_rgb_highlight(highlight), Some((r, g, b)));
|
||||
}
|
||||
|
||||
/// make sure we can store all the colors at the end
|
||||
/// ```
|
||||
/// FF FF FF FF FF FF FF FF
|
||||
/// xor
|
||||
/// FF FF FF FF FF 00 00 00
|
||||
/// =
|
||||
/// 00 00 00 00 00 FF FF FF
|
||||
/// ```
|
||||
///
|
||||
/// where the ending `(FF, FF, FF)` represents `(r, g, b)`
|
||||
#[test]
|
||||
fn full_numeric_range() {
|
||||
assert_eq!(usize::MAX ^ Theme::RGB_START, 256_usize.pow(3));
|
||||
assert_eq!(Theme::RGB_START + 256_usize.pow(3), usize::MAX);
|
||||
assert_eq!(Highlight::MAX - Theme::RGB_START, 256_u32.pow(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -650,30 +636,27 @@ mod tests {
|
||||
// color in the middle
|
||||
let (r, g, b) = (0x14, 0xAA, 0xF7);
|
||||
assert_eq!(
|
||||
Theme::default().highlight(Theme::rgb_highlight(r, g, b).0),
|
||||
Theme::default().highlight(Theme::rgb_highlight(r, g, b)),
|
||||
Style::new().fg(Color::Rgb(r, g, b))
|
||||
);
|
||||
// pure black
|
||||
let (r, g, b) = (0x00, 0x00, 0x00);
|
||||
assert_eq!(
|
||||
Theme::default().highlight(Theme::rgb_highlight(r, g, b).0),
|
||||
Theme::default().highlight(Theme::rgb_highlight(r, g, b)),
|
||||
Style::new().fg(Color::Rgb(r, g, b))
|
||||
);
|
||||
// pure white
|
||||
let (r, g, b) = (0xff, 0xff, 0xff);
|
||||
assert_eq!(
|
||||
Theme::default().highlight(Theme::rgb_highlight(r, g, b).0),
|
||||
Theme::default().highlight(Theme::rgb_highlight(r, g, b)),
|
||||
Style::new().fg(Color::Rgb(r, g, b))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "index out of bounds: the len is 0 but the index is 18446744073692774399"
|
||||
)]
|
||||
#[should_panic(expected = "index out of bounds: the len is 0 but the index is 4278190078")]
|
||||
fn out_of_bounds() {
|
||||
let (r, g, b) = (0x00, 0x00, 0x00);
|
||||
|
||||
Theme::default().highlight(Theme::rgb_highlight(r, g, b).0 - 1);
|
||||
let highlight = Highlight::new(Theme::rgb_highlight(0, 0, 0).get() - 1);
|
||||
Theme::default().highlight(highlight);
|
||||
}
|
||||
}
|
||||
|
@@ -11,7 +11,6 @@ use crate::{
|
||||
use helix_core::{
|
||||
char_idx_at_visual_offset,
|
||||
doc_formatter::TextFormat,
|
||||
syntax::Highlight,
|
||||
text_annotations::TextAnnotations,
|
||||
visual_offset_from_anchor, visual_offset_from_block, Position, RopeSlice, Selection,
|
||||
Transaction,
|
||||
@@ -446,9 +445,7 @@ impl View {
|
||||
let mut text_annotations = TextAnnotations::default();
|
||||
|
||||
if let Some(labels) = doc.jump_labels.get(&self.id) {
|
||||
let style = theme
|
||||
.and_then(|t| t.find_scope_index("ui.virtual.jump-label"))
|
||||
.map(Highlight);
|
||||
let style = theme.and_then(|t| t.find_highlight("ui.virtual.jump-label"));
|
||||
text_annotations.add_overlay(labels, style);
|
||||
}
|
||||
|
||||
@@ -461,15 +458,10 @@ impl View {
|
||||
padding_after_inlay_hints,
|
||||
}) = doc.inlay_hints.get(&self.id)
|
||||
{
|
||||
let type_style = theme
|
||||
.and_then(|t| t.find_scope_index("ui.virtual.inlay-hint.type"))
|
||||
.map(Highlight);
|
||||
let parameter_style = theme
|
||||
.and_then(|t| t.find_scope_index("ui.virtual.inlay-hint.parameter"))
|
||||
.map(Highlight);
|
||||
let other_style = theme
|
||||
.and_then(|t| t.find_scope_index("ui.virtual.inlay-hint"))
|
||||
.map(Highlight);
|
||||
let type_style = theme.and_then(|t| t.find_highlight("ui.virtual.inlay-hint.type"));
|
||||
let parameter_style =
|
||||
theme.and_then(|t| t.find_highlight("ui.virtual.inlay-hint.parameter"));
|
||||
let other_style = theme.and_then(|t| t.find_highlight("ui.virtual.inlay-hint"));
|
||||
|
||||
// Overlapping annotations are ignored apart from the first so the order here is not random:
|
||||
// types -> parameters -> others should hopefully be the "correct" order for most use cases,
|
||||
|
Reference in New Issue
Block a user