Skip to content

Commit 3982ae0

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

File tree

1 file changed

+58
-55
lines changed

1 file changed

+58
-55
lines changed

helix-core/src/syntax.rs

Lines changed: 58 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,17 @@ thread_local! {
711711

712712
pub struct Syntax {
713713
layers: HopSlotMap<LayerId, LanguageLayer>,
714-
layers_lut: RawTable<LayerId>,
715-
layers_lut_hasher: RandomState,
714+
/// A hashtable that allows quick lookups of injections
715+
/// to check if they are already contained in layers
716+
/// and can be incrementally updated.
717+
/// This hashtable is populated and used inside the `update` function.
718+
/// It is stored here to avoid reallocating the table
719+
/// on every keypress.
720+
///
721+
/// The hashes for use with this table are produced with the
722+
/// `hash_injection_layer` function (and the `layers_hasher` hasher)
723+
layers_tabel: RawTable<LayerId>,
724+
layers_hasher: RandomState,
716725
root: LayerId,
717726
loader: Arc<Loader>,
718727
}
@@ -756,8 +765,8 @@ impl Syntax {
756765
root,
757766
layers,
758767
loader,
759-
layers_lut: RawTable::new(),
760-
layers_lut_hasher: RandomState::new(),
768+
layers_tabel: RawTable::new(),
769+
layers_hasher: RandomState::new(),
761770
};
762771

763772
syntax
@@ -803,11 +812,10 @@ impl Syntax {
803812
}
804813
}
805814

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
815+
self.layers_tabel.clear_no_drop();
816+
// Ensure table is large enough to hold all layers.
817+
// Because the table is cleared first, rehashing is never requied (hence unreachable).
818+
self.layers_tabel
811819
.reserve(self.layers.len(), |_| unreachable!());
812820

813821
for (layer_id, layer) in self.layers.iter_mut() {
@@ -881,17 +889,12 @@ impl Syntax {
881889
}
882890
}
883891

884-
let hash = hash_injection_layer(
885-
&self.layers_lut_hasher,
886-
layer.depth,
887-
&layer.config,
888-
&layer.ranges,
889-
);
892+
let hash = self.layers_hasher.hash_one(layer);
890893
// Safety: insert_no_grow is unsafe because it assumes that the table
891894
// has enough capacity to hold additional elements.
892895
// This is always the case as we reserved enough capacity above.
893896
unsafe {
894-
self.layers_lut.insert_no_grow(hash, layer_id);
897+
self.layers_tabel.insert_no_grow(hash, layer_id);
895898
}
896899
}
897900
}
@@ -1018,31 +1021,24 @@ impl Syntax {
10181021
let depth = layer.depth + 1;
10191022
// TODO: can't inline this since matches borrows self.layers
10201023
for (config, ranges) in injections {
1021-
// Find an existing layer
1024+
let new_layer = LanguageLayer {
1025+
tree: None,
1026+
config,
1027+
depth,
1028+
ranges,
1029+
flags: LayerUpdateFlags::empty(),
1030+
};
10221031

1023-
let hash =
1024-
hash_injection_layer(&self.layers_lut_hasher, depth, &config, &ranges);
1032+
// Find an identical existing layer
10251033
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
1034+
.layers_tabel
1035+
.get(self.layers_hasher.hash_one(&new_layer), |&it| {
1036+
self.layers[it] == new_layer
10321037
})
10331038
.copied();
10341039

10351040
// ...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-
});
1041+
let layer_id = layer.unwrap_or_else(|| self.layers.insert(new_layer));
10461042

10471043
queue.push_back(layer_id);
10481044
}
@@ -1060,8 +1056,6 @@ impl Syntax {
10601056
.contains(LayerUpdateFlags::TOUCHED)
10611057
});
10621058

1063-
self.layers_lut.clear_no_drop();
1064-
10651059
Ok(())
10661060
})
10671061
}
@@ -1181,23 +1175,32 @@ pub struct LanguageLayer {
11811175
flags: LayerUpdateFlags,
11821176
}
11831177

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

12031206
impl LanguageLayer {

0 commit comments

Comments
 (0)