Replace tree-sitter with tree-house

This commit is contained in:
Michael Davis
2025-02-20 20:38:14 -05:00
parent 24e3ccc31b
commit aea53523dd
34 changed files with 1521 additions and 3151 deletions

View File

@@ -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,
}
}

View File

@@ -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
);

View File

@@ -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);
}
}

View File

@@ -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,