Skip to content

Commit b3fcf52

Browse files
committed
use PartialEq and Hash instead of a freestanding function
1 parent a9625da commit b3fcf52

File tree

1 file changed

+56
-55
lines changed

1 file changed

+56
-55
lines changed

helix-core/src/syntax.rs

Lines changed: 56 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use std::{
1818
cell::RefCell,
1919
collections::{HashMap, VecDeque},
2020
fmt,
21-
hash::{BuildHasher, Hash, Hasher},
21+
hash::{Hash, Hasher},
2222
mem::{replace, transmute},
2323
path::Path,
2424
str::FromStr,
@@ -711,8 +711,15 @@ thread_local! {
711711

712712
pub struct Syntax {
713713
layers: HopSlotMap<LayerId, LanguageLayer>,
714-
layers_lut: RawTable<LayerId>,
715-
layers_lut_hasher: RandomState,
714+
/// This table allows inverse indexing of `layers`.
715+
/// That is by hashing a `Layer` you can find
716+
/// the `LayerId` of an existing equivalent `Layer` in `layers`.
717+
///
718+
/// This hashtable is populated inside the `update` function
719+
/// and used to determine if a new layer exists for an injection
720+
/// or if an existing layer needs to be updated.
721+
layers_tabel: RawTable<LayerId>,
722+
layers_hasher: RandomState,
716723
root: LayerId,
717724
loader: Arc<Loader>,
718725
}
@@ -756,8 +763,8 @@ impl Syntax {
756763
root,
757764
layers,
758765
loader,
759-
layers_lut: RawTable::new(),
760-
layers_lut_hasher: RandomState::new(),
766+
layers_tabel: RawTable::new(),
767+
layers_hasher: RandomState::new(),
761768
};
762769

763770
syntax
@@ -803,11 +810,10 @@ impl Syntax {
803810
}
804811
}
805812

806-
// Ensure lut is large enough to hold all layers.
807-
// The lut should always be empty at this point because it is only
808-
// kept to avoid realloctions so rehashing is never requied (hence unreachable).
809-
assert!(self.layers_lut.is_empty());
810-
self.layers_lut
813+
self.layers_tabel.clear_no_drop();
814+
// Ensure table is large enough to hold all layers.
815+
// Because the table is cleared first, rehashing is never requied (hence unreachable).
816+
self.layers_tabel
811817
.reserve(self.layers.len(), |_| unreachable!());
812818

813819
for (layer_id, layer) in self.layers.iter_mut() {
@@ -881,17 +887,12 @@ impl Syntax {
881887
}
882888
}
883889

884-
let hash = hash_injection_layer(
885-
&self.layers_lut_hasher,
886-
layer.depth,
887-
&layer.config,
888-
&layer.ranges,
889-
);
890+
let hash = self.layers_hasher.hash_one(layer);
890891
// Safety: insert_no_grow is unsafe because it assumes that the table
891892
// has enough capacity to hold additional elements.
892893
// This is always the case as we reserved enough capacity above.
893894
unsafe {
894-
self.layers_lut.insert_no_grow(hash, layer_id);
895+
self.layers_tabel.insert_no_grow(hash, layer_id);
895896
}
896897
}
897898
}
@@ -1018,31 +1019,24 @@ impl Syntax {
10181019
let depth = layer.depth + 1;
10191020
// TODO: can't inline this since matches borrows self.layers
10201021
for (config, ranges) in injections {
1021-
// Find an existing layer
1022+
let new_layer = LanguageLayer {
1023+
tree: None,
1024+
config,
1025+
depth,
1026+
ranges,
1027+
flags: LayerUpdateFlags::empty(),
1028+
};
10221029

1023-
let hash =
1024-
hash_injection_layer(&self.layers_lut_hasher, depth, &config, &ranges);
1030+
// Find an identical existing layer
10251031
let layer = self
1026-
.layers_lut
1027-
.get(hash, |&it| {
1028-
let layer = &self.layers[it];
1029-
layer.depth == depth && // TODO: track parent id instead
1030-
layer.config.language == config.language &&
1031-
layer.ranges == ranges
1032+
.layers_tabel
1033+
.get(self.layers_hasher.hash_one(&new_layer), |&it| {
1034+
self.layers[it] == new_layer
10321035
})
10331036
.copied();
10341037

10351038
// ...or insert a new one.
1036-
let layer_id = layer.unwrap_or_else(|| {
1037-
self.layers.insert(LanguageLayer {
1038-
tree: None,
1039-
config,
1040-
depth,
1041-
ranges,
1042-
// set the modified flag to ensure the layer is parsed
1043-
flags: LayerUpdateFlags::empty(),
1044-
})
1045-
});
1039+
let layer_id = layer.unwrap_or_else(|| self.layers.insert(new_layer));
10461040

10471041
queue.push_back(layer_id);
10481042
}
@@ -1060,8 +1054,6 @@ impl Syntax {
10601054
.contains(LayerUpdateFlags::TOUCHED)
10611055
});
10621056

1063-
self.layers_lut.clear_no_drop();
1064-
10651057
Ok(())
10661058
})
10671059
}
@@ -1181,23 +1173,32 @@ pub struct LanguageLayer {
11811173
flags: LayerUpdateFlags,
11821174
}
11831175

1184-
fn hash_injection_layer(
1185-
state: &RandomState,
1186-
depth: u32,
1187-
config: &HighlightConfiguration,
1188-
ranges: &[Range],
1189-
) -> u64 {
1190-
let mut state = state.build_hasher();
1191-
depth.hash(&mut state);
1192-
// The transmute is necessary here because tree_sitter::Language does not derive Hash at the moment.
1193-
// However it does use #[repr] transparent so the transmute here is safe
1194-
// as `Language` (which `Grammar` is an alias for) is just a newtype wrapper around a (thin) pointer.
1195-
// This is also compatible with the PartialEq implementation of language
1196-
// as that is just a pointer comparison.
1197-
let language: *const () = unsafe { transmute(config.language) };
1198-
language.hash(&mut state);
1199-
ranges.hash(&mut state);
1200-
state.finish()
1176+
/// This PartialEq implementation only checks if that
1177+
/// two layers are theoretically identical (meaning they highlight the same text range with the same language).
1178+
/// It does not check whether the layers have the same internal treesitter
1179+
/// state.
1180+
impl PartialEq for LanguageLayer {
1181+
fn eq(&self, other: &Self) -> bool {
1182+
self.depth == other.depth
1183+
&& self.config.language == other.config.language
1184+
&& self.ranges == other.ranges
1185+
}
1186+
}
1187+
1188+
/// Hash implementation belongs to PartialEq implementation above.
1189+
/// See its documentation for details.
1190+
impl Hash for LanguageLayer {
1191+
fn hash<H: Hasher>(&self, state: &mut H) {
1192+
self.depth.hash(state);
1193+
// The transmute is necessary here because tree_sitter::Language does not derive Hash at the moment.
1194+
// However it does use #[repr] transparent so the transmute here is safe
1195+
// as `Language` (which `Grammar` is an alias for) is just a newtype wrapper around a (thin) pointer.
1196+
// This is also compatible with the PartialEq implementation of language
1197+
// as that is just a pointer comparison.
1198+
let language: *const () = unsafe { transmute(self.config.language) };
1199+
language.hash(state);
1200+
self.ranges.hash(state);
1201+
}
12011202
}
12021203

12031204
impl LanguageLayer {

0 commit comments

Comments
 (0)