chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:08:36 +08:00
commit 93bdeb5e95
383 changed files with 83019 additions and 0 deletions
+192
View File
@@ -0,0 +1,192 @@
use std::path::PathBuf;
use directories::{BaseDirs, ProjectDirs};
use crate::meta::NAME;
pub struct Directory {}
impl Directory {
pub fn home_dir() -> Option<PathBuf> {
BaseDirs::new().map(|d| PathBuf::from(d.home_dir()))
}
#[cfg(not(feature = "portable"))]
fn project_dirs() -> Option<ProjectDirs> {
ProjectDirs::from("dev", "lapce", NAME)
}
/// Return path adjacent to lapce executable when built as portable
#[cfg(feature = "portable")]
fn project_dirs() -> Option<ProjectDirs> {
if let Ok(current_exe) = std::env::current_exe() {
if let Some(parent) = current_exe.parent() {
return ProjectDirs::from_path(parent.join("lapce-data"));
}
unreachable!("Couldn't obtain current process parent path");
}
unreachable!("Couldn't obtain current process path");
}
// Get path of local data directory
// Local data directory differs from data directory
// on some platforms and is not transferred across
// machines
pub fn data_local_directory() -> Option<PathBuf> {
match Self::project_dirs() {
Some(dir) => {
let dir = dir.data_local_dir();
if !dir.exists() {
if let Err(err) = std::fs::create_dir_all(dir) {
tracing::error!("{:?}", err);
}
}
Some(dir.to_path_buf())
}
None => None,
}
}
/// Get the path to logs directory
/// Each log file is for individual application startup
pub fn logs_directory() -> Option<PathBuf> {
if let Some(dir) = Self::data_local_directory() {
let dir = dir.join("logs");
if !dir.exists() {
if let Err(err) = std::fs::create_dir(&dir) {
tracing::error!("{:?}", err);
}
}
Some(dir)
} else {
None
}
}
/// Get the path to cache directory
pub fn cache_directory() -> Option<PathBuf> {
if let Some(dir) = Self::data_local_directory() {
let dir = dir.join("cache");
if !dir.exists() {
if let Err(err) = std::fs::create_dir(&dir) {
tracing::error!("{:?}", err);
}
}
Some(dir)
} else {
None
}
}
/// Directory to store proxy executables used on local
/// host as well, as ones uploaded to remote host when
/// connecting
pub fn proxy_directory() -> Option<PathBuf> {
if let Some(dir) = Self::data_local_directory() {
let dir = dir.join("proxy");
if !dir.exists() {
if let Err(err) = std::fs::create_dir(&dir) {
tracing::error!("{:?}", err);
}
}
Some(dir)
} else {
None
}
}
/// Get the path to the themes folder
/// Themes are stored within as individual toml files
pub fn themes_directory() -> Option<PathBuf> {
if let Some(dir) = Self::data_local_directory() {
let dir = dir.join("themes");
if !dir.exists() {
if let Err(err) = std::fs::create_dir(&dir) {
tracing::error!("{:?}", err);
}
}
Some(dir)
} else {
None
}
}
// Get the path to plugins directory
// Each plugin has own directory that contains
// metadata file and plugin wasm
pub fn plugins_directory() -> Option<PathBuf> {
if let Some(dir) = Self::data_local_directory() {
let dir = dir.join("plugins");
if !dir.exists() {
if let Err(err) = std::fs::create_dir(&dir) {
tracing::error!("{:?}", err);
}
}
Some(dir)
} else {
None
}
}
// Config directory contain only configuration files
pub fn config_directory() -> Option<PathBuf> {
match Self::project_dirs() {
Some(dir) => {
let dir = dir.config_dir();
if !dir.exists() {
if let Err(err) = std::fs::create_dir_all(dir) {
tracing::error!("{:?}", err);
}
}
Some(dir.to_path_buf())
}
None => None,
}
}
pub fn local_socket() -> Option<PathBuf> {
Self::data_local_directory().map(|dir| dir.join("local.sock"))
}
pub fn updates_directory() -> Option<PathBuf> {
if let Some(dir) = Self::data_local_directory() {
let dir = dir.join("updates");
if !dir.exists() {
if let Err(err) = std::fs::create_dir(&dir) {
tracing::error!("{:?}", err);
}
}
Some(dir)
} else {
None
}
}
pub fn queries_directory() -> Option<PathBuf> {
if let Some(dir) = Self::config_directory() {
let dir = dir.join("queries");
if !dir.exists() {
if let Err(err) = std::fs::create_dir(&dir) {
tracing::error!("{:?}", err);
}
}
Some(dir)
} else {
None
}
}
pub fn grammars_directory() -> Option<PathBuf> {
if let Some(dir) = Self::data_local_directory() {
let dir = dir.join("grammars");
if !dir.exists() {
if let Err(err) = std::fs::create_dir(&dir) {
tracing::error!("{:?}", err);
}
}
Some(dir)
} else {
None
}
}
}
+163
View File
@@ -0,0 +1,163 @@
/// Convert a utf8 offset into a utf16 offset, if possible
/// `text` is what the offsets are into
pub fn offset_utf8_to_utf16(
char_indices: impl Iterator<Item = (usize, char)>,
offset: usize,
) -> usize {
if offset == 0 {
return 0;
}
let mut utf16_offset = 0;
let mut last_ich = None;
for (utf8_offset, ch) in char_indices {
last_ich = Some((utf8_offset, ch));
match utf8_offset.cmp(&offset) {
std::cmp::Ordering::Less => {}
// We found the right offset
std::cmp::Ordering::Equal => {
return utf16_offset;
}
// Implies that the offset was inside of a character
std::cmp::Ordering::Greater => return utf16_offset,
}
utf16_offset += ch.len_utf16();
}
// TODO: We could use TrustedLen when that is stabilized and it is impl'd on
// the iterators we use
// We did not find the offset. This means that it is either at the end
// or past the end.
let text_len = last_ich.map(|(i, c)| i + c.len_utf8());
if text_len == Some(offset) {
// Since the utf16 offset was being incremented each time, by now it is equivalent to the length
// but in utf16 characters
return utf16_offset;
}
utf16_offset
}
pub fn offset_utf8_to_utf16_str(text: &str, offset: usize) -> usize {
offset_utf8_to_utf16(text.char_indices(), offset)
}
/// Convert a utf16 offset into a utf8 offset, if possible
/// `char_indices` is an iterator over utf8 offsets and the characters
/// It is cloneable so that it can be iterated multiple times. Though it should be cheaply cloneable.
pub fn offset_utf16_to_utf8(
char_indices: impl Iterator<Item = (usize, char)>,
offset: usize,
) -> usize {
if offset == 0 {
return 0;
}
// We accumulate the utf16 char lens until we find the utf8 offset that matches it
// or, we find out that it went into the middle of sometext
// We also keep track of the last offset and char in order to calculate the length of the text
// if we the index was at the end of the string
let mut utf16_offset = 0;
let mut last_ich = None;
for (utf8_offset, ch) in char_indices {
last_ich = Some((utf8_offset, ch));
let ch_utf16_len = ch.len_utf16();
match utf16_offset.cmp(&offset) {
std::cmp::Ordering::Less => {}
// We found the right offset
std::cmp::Ordering::Equal => {
return utf8_offset;
}
// This implies that the offset was in the middle of a character as we skipped over it
std::cmp::Ordering::Greater => return utf8_offset,
}
utf16_offset += ch_utf16_len;
}
// We did not find the offset, this means that it was either at the end
// or past the end
// Since we've iterated over all the char indices, the utf16_offset is now the
// utf16 length
if let Some((last_utf8_offset, last_ch)) = last_ich {
last_utf8_offset + last_ch.len_utf8()
} else {
0
}
}
pub fn offset_utf16_to_utf8_str(text: &str, offset: usize) -> usize {
offset_utf16_to_utf8(text.char_indices(), offset)
}
#[cfg(test)]
mod tests {
// TODO: more tests with unicode characters
use crate::encoding::{offset_utf8_to_utf16_str, offset_utf16_to_utf8_str};
#[test]
fn utf8_to_utf16() {
let text = "hello world";
assert_eq!(offset_utf8_to_utf16_str(text, 0), 0);
assert_eq!(offset_utf8_to_utf16_str("", 0), 0);
assert_eq!(offset_utf8_to_utf16_str("", 1), 0);
assert_eq!(offset_utf8_to_utf16_str("h", 0), 0);
assert_eq!(offset_utf8_to_utf16_str("h", 1), 1);
assert_eq!(offset_utf8_to_utf16_str(text, text.len()), text.len());
assert_eq!(
offset_utf8_to_utf16_str(text, text.len() - 1),
text.len() - 1
);
assert_eq!(offset_utf8_to_utf16_str(text, text.len() + 1), text.len());
assert_eq!(offset_utf8_to_utf16_str("×", 0), 0);
assert_eq!(offset_utf8_to_utf16_str("×", 1), 1);
assert_eq!(offset_utf8_to_utf16_str("×", 2), 1);
assert_eq!(offset_utf8_to_utf16_str("a×", 0), 0);
assert_eq!(offset_utf8_to_utf16_str("a×", 1), 1);
assert_eq!(offset_utf8_to_utf16_str("a×", 2), 2);
assert_eq!(offset_utf8_to_utf16_str("a×", 3), 2);
}
#[test]
fn utf16_to_utf8() {
let text = "hello world";
assert_eq!(offset_utf16_to_utf8_str(text, 0), 0);
assert_eq!(offset_utf16_to_utf8_str("", 0), 0);
assert_eq!(offset_utf16_to_utf8_str("", 1), 0);
assert_eq!(offset_utf16_to_utf8_str("h", 0), 0);
assert_eq!(offset_utf16_to_utf8_str("h", 1), 1);
assert_eq!(offset_utf16_to_utf8_str(text, text.len()), text.len());
assert_eq!(
offset_utf16_to_utf8_str(text, text.len() - 1),
text.len() - 1
);
assert_eq!(offset_utf16_to_utf8_str(text, text.len() + 1), text.len());
assert_eq!(offset_utf16_to_utf8_str("×", 0), 0);
assert_eq!(offset_utf16_to_utf8_str("×", 1), 2);
assert_eq!(offset_utf16_to_utf8_str("a×", 0), 0);
assert_eq!(offset_utf16_to_utf8_str("a×", 1), 1);
assert_eq!(offset_utf16_to_utf8_str("a×", 2), 3);
assert_eq!(offset_utf16_to_utf8_str("×a", 1), 2);
assert_eq!(offset_utf16_to_utf8_str("×a", 2), 3);
}
}
File diff suppressed because it is too large Load Diff
+388
View File
@@ -0,0 +1,388 @@
use std::mem;
use lapce_xi_rope::{
Cursor, Delta, Interval, Metric,
interval::IntervalBounds,
tree::{DefaultMetric, Leaf, Node, NodeInfo, TreeBuilder},
};
const MIN_LEAF: usize = 5;
const MAX_LEAF: usize = 10;
pub type LensNode = Node<LensInfo>;
#[derive(Clone)]
pub struct Lens(LensNode);
#[derive(Clone, Debug)]
pub struct LensInfo(usize);
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct LensData {
len: usize,
line_height: usize,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct LensLeaf {
len: usize,
data: Vec<LensData>,
total_height: usize,
}
pub struct LensIter<'a> {
cursor: Cursor<'a, LensInfo>,
end: usize,
}
impl Lens {
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn line_of_height(&self, height: usize) -> usize {
let max_height = self.0.count::<LensMetric>(self.0.len());
if height >= max_height {
return self.0.len();
}
self.0.count_base_units::<LensMetric>(height)
}
pub fn height_of_line(&self, line: usize) -> usize {
let line = self.0.len().min(line);
self.0.count::<LensMetric>(line)
}
pub fn iter(&self) -> LensIter<'_> {
LensIter {
cursor: Cursor::new(&self.0, 0),
end: self.len(),
}
}
pub fn iter_chunks<I: IntervalBounds>(&self, range: I) -> LensIter<'_> {
let Interval { start, end } = range.into_interval(self.len());
LensIter {
cursor: Cursor::new(&self.0, start),
end,
}
}
pub fn apply_delta<M: NodeInfo>(&mut self, _delta: &Delta<M>) {}
}
impl NodeInfo for LensInfo {
type L = LensLeaf;
fn accumulate(&mut self, other: &Self) {
self.0 += other.0;
}
fn compute_info(l: &LensLeaf) -> LensInfo {
LensInfo(l.total_height)
}
}
impl Leaf for LensLeaf {
fn len(&self) -> usize {
self.len
}
fn is_ok_child(&self) -> bool {
self.data.len() >= MIN_LEAF
}
fn push_maybe_split(
&mut self,
other: &LensLeaf,
iv: Interval,
) -> Option<LensLeaf> {
let (iv_start, iv_end) = iv.start_end();
let mut accum = 0;
let mut added_len = 0;
let mut added_height = 0;
for sec in &other.data {
if accum + sec.len < iv_start {
accum += sec.len;
continue;
}
if accum + sec.len <= iv_end {
accum += sec.len;
self.data.push(LensData {
len: sec.len,
line_height: sec.line_height,
});
added_len += sec.len;
added_height += sec.len * sec.line_height;
continue;
}
let len = iv_end - (accum + sec.len);
self.data.push(LensData {
len,
line_height: sec.line_height,
});
added_len += len;
added_height += sec.len * sec.line_height;
break;
}
self.len += added_len;
self.total_height += added_height;
if self.data.len() <= MAX_LEAF {
None
} else {
let splitpoint = self.data.len() / 2; // number of spans
let new = self.data.split_off(splitpoint);
let new_len = new.iter().map(|d| d.len).sum();
let new_height = new.iter().map(|d| d.len * d.line_height).sum();
self.len -= new_len;
self.total_height -= new_height;
Some(LensLeaf {
len: new_len,
data: new,
total_height: new_height,
})
}
}
}
#[derive(Copy, Clone)]
pub struct LensMetric(());
impl Metric<LensInfo> for LensMetric {
fn measure(info: &LensInfo, _len: usize) -> usize {
info.0
}
fn to_base_units(l: &LensLeaf, in_measured_units: usize) -> usize {
if in_measured_units > l.total_height {
l.len
} else if in_measured_units == 0 {
0
} else {
let mut line = 0;
let mut accum = 0;
for data in l.data.iter() {
let leaf_height = data.line_height * data.len;
let accum_height = accum + leaf_height;
if accum_height > in_measured_units {
return line + (in_measured_units - accum) / data.line_height;
}
accum = accum_height;
line += data.len;
}
line
}
}
fn from_base_units(l: &LensLeaf, in_base_units: usize) -> usize {
let mut line = 0;
let mut accum = 0;
for data in l.data.iter() {
if in_base_units < line + data.len {
return accum + (in_base_units - line) * data.line_height;
}
accum += data.len * data.line_height;
line += data.len;
}
accum
}
fn is_boundary(_l: &LensLeaf, _offset: usize) -> bool {
true
}
fn prev(_l: &LensLeaf, offset: usize) -> Option<usize> {
if offset == 0 { None } else { Some(offset - 1) }
}
fn next(l: &LensLeaf, offset: usize) -> Option<usize> {
if offset < l.len {
Some(offset + 1)
} else {
None
}
}
fn can_fragment() -> bool {
false
}
}
impl DefaultMetric for LensInfo {
type DefaultMetric = LensBaseMetric;
}
#[derive(Copy, Clone)]
pub struct LensBaseMetric(());
impl Metric<LensInfo> for LensBaseMetric {
fn measure(_: &LensInfo, len: usize) -> usize {
len
}
fn to_base_units(_: &LensLeaf, in_measured_units: usize) -> usize {
in_measured_units
}
fn from_base_units(_: &LensLeaf, in_base_units: usize) -> usize {
in_base_units
}
fn is_boundary(l: &LensLeaf, offset: usize) -> bool {
LensMetric::is_boundary(l, offset)
}
fn prev(l: &LensLeaf, offset: usize) -> Option<usize> {
LensMetric::prev(l, offset)
}
fn next(l: &LensLeaf, offset: usize) -> Option<usize> {
LensMetric::next(l, offset)
}
fn can_fragment() -> bool {
false
}
}
pub struct LensBuilder {
b: TreeBuilder<LensInfo>,
leaf: LensLeaf,
}
impl Default for LensBuilder {
fn default() -> LensBuilder {
LensBuilder {
b: TreeBuilder::new(),
leaf: LensLeaf::default(),
}
}
}
impl LensBuilder {
pub fn new() -> LensBuilder {
LensBuilder::default()
}
pub fn add_section(&mut self, len: usize, line_height: usize) {
if self.leaf.data.len() == MAX_LEAF {
let leaf = mem::take(&mut self.leaf);
self.b.push(Node::from_leaf(leaf));
}
self.leaf.len += len;
self.leaf.total_height += len * line_height;
self.leaf.data.push(LensData { len, line_height });
}
pub fn build(mut self) -> Lens {
self.b.push(Node::from_leaf(self.leaf));
Lens(self.b.build())
}
}
impl Iterator for LensIter<'_> {
type Item = (usize, usize);
fn next(&mut self) -> Option<Self::Item> {
if self.cursor.pos() >= self.end {
return None;
}
if let Some((leaf, leaf_pos)) = self.cursor.get_leaf() {
if leaf.data.is_empty() {
return None;
}
let line = self.cursor.pos();
self.cursor.next::<LensMetric>();
let mut lines = 0;
for data in leaf.data.iter() {
if leaf_pos < data.len + lines {
return Some((line, data.line_height));
}
lines += data.len;
}
return None;
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lens_metric() {
let mut builder = LensBuilder::new();
builder.add_section(10, 2);
builder.add_section(1, 25);
builder.add_section(20, 3);
let lens = builder.build();
assert_eq!(31, lens.len());
assert_eq!(0, lens.height_of_line(0));
assert_eq!(2, lens.height_of_line(1));
assert_eq!(20, lens.height_of_line(10));
assert_eq!(45, lens.height_of_line(11));
assert_eq!(48, lens.height_of_line(12));
assert_eq!(105, lens.height_of_line(31));
assert_eq!(105, lens.height_of_line(32));
assert_eq!(105, lens.height_of_line(62));
assert_eq!(0, lens.line_of_height(0));
assert_eq!(0, lens.line_of_height(1));
assert_eq!(1, lens.line_of_height(2));
assert_eq!(1, lens.line_of_height(3));
assert_eq!(2, lens.line_of_height(4));
assert_eq!(2, lens.line_of_height(5));
assert_eq!(3, lens.line_of_height(6));
assert_eq!(10, lens.line_of_height(20));
assert_eq!(10, lens.line_of_height(44));
assert_eq!(11, lens.line_of_height(45));
assert_eq!(11, lens.line_of_height(46));
assert_eq!(31, lens.line_of_height(105));
assert_eq!(31, lens.line_of_height(106));
}
#[test]
fn test_lens_iter() {
let mut builder = LensBuilder::new();
builder.add_section(10, 2);
builder.add_section(1, 25);
builder.add_section(2, 3);
let lens = builder.build();
let mut iter = lens.iter();
assert_eq!(Some((0, 2)), iter.next());
assert_eq!(Some((1, 2)), iter.next());
assert_eq!(Some((2, 2)), iter.next());
for _ in 0..7 {
iter.next();
}
assert_eq!(Some((10, 25)), iter.next());
assert_eq!(Some((11, 3)), iter.next());
assert_eq!(Some((12, 3)), iter.next());
assert_eq!(None, iter.next());
let mut iter = lens.iter_chunks(9..12);
assert_eq!(Some((9, 2)), iter.next());
assert_eq!(Some((10, 25)), iter.next());
assert_eq!(Some((11, 3)), iter.next());
assert_eq!(None, iter.next());
let mut iter = lens.iter_chunks(9..15);
assert_eq!(Some((9, 2)), iter.next());
assert_eq!(Some((10, 25)), iter.next());
assert_eq!(Some((11, 3)), iter.next());
assert_eq!(Some((12, 3)), iter.next());
assert_eq!(None, iter.next());
}
}
+13
View File
@@ -0,0 +1,13 @@
#![allow(clippy::manual_clamp)]
pub mod directory;
pub mod encoding;
pub mod language;
pub mod lens;
pub mod meta;
pub mod rope_text_pos;
pub mod style;
pub mod syntax;
// This is primarily being re-exported to avoid changing every single usage
// in lapce-app. We should probably remove this at some point.
pub use floem_editor_core::*;
+8
View File
@@ -0,0 +1,8 @@
#[derive(strum_macros::AsRefStr, PartialEq, Eq)]
pub enum ReleaseType {
Debug,
Stable,
Nightly,
}
include!(concat!(env!("OUT_DIR"), "/meta.rs"));
+40
View File
@@ -0,0 +1,40 @@
use floem_editor_core::buffer::rope_text::RopeText;
use lsp_types::Position;
use crate::encoding::{offset_utf8_to_utf16, offset_utf16_to_utf8};
pub trait RopeTextPosition: RopeText {
/// Converts a UTF8 offset to a UTF16 LSP position
/// Returns None if it is not a valid UTF16 offset
fn offset_to_position(&self, offset: usize) -> Position {
let (line, col) = self.offset_to_line_col(offset);
let line_offset = self.offset_of_line(line);
let utf16_col =
offset_utf8_to_utf16(self.char_indices_iter(line_offset..), col);
Position {
line: line as u32,
character: utf16_col as u32,
}
}
fn offset_of_position(&self, pos: &Position) -> usize {
let (line, column) = self.position_to_line_col(pos);
self.offset_of_line_col(line, column)
}
fn position_to_line_col(&self, pos: &Position) -> (usize, usize) {
let line = pos.line as usize;
let line_offset = self.offset_of_line(line);
let column = offset_utf16_to_utf8(
self.char_indices_iter(line_offset..),
pos.character as usize,
);
(line, column)
}
}
impl<T: RopeText> RopeTextPosition for T {}
+75
View File
@@ -0,0 +1,75 @@
use std::str;
use lapce_rpc::style::{LineStyle, Style};
use lapce_xi_rope::{LinesMetric, Rope, spans::Spans};
pub const SCOPES: &[&str] = &[
"constant",
"type",
"type.builtin",
"property",
"comment",
"constructor",
"function",
"label",
"keyword",
"string",
"variable",
"variable.other.member",
"operator",
"attribute",
"escape",
"embedded",
"symbol",
"punctuation",
"punctuation.special",
"punctuation.delimiter",
"text",
"text.literal",
"text.title",
"text.uri",
"text.reference",
"string.escape",
"conceal",
"none",
"tag",
"markup.bold",
"markup.italic",
"markup.list",
"markup.quote",
"markup.heading",
"markup.link.url",
"markup.link.label",
"markup.link.text",
];
pub fn line_styles(
text: &Rope,
line: usize,
styles: &Spans<Style>,
) -> Vec<LineStyle> {
let max_line = text.measure::<LinesMetric>() + 1;
if line >= max_line {
return Vec::new();
}
let start_offset = text.offset_of_line(line);
let end_offset = text.offset_of_line(line + 1);
let line_styles: Vec<LineStyle> = styles
.iter_chunks(start_offset..end_offset)
.filter_map(|(iv, style)| {
let start = iv.start();
let end = iv.end();
if start > end_offset || end < start_offset {
None
} else {
let start = start.saturating_sub(start_offset);
let end = end - start_offset;
let style = style.clone();
Some(LineStyle { start, end, style })
}
})
.collect();
line_styles
}
+119
View File
@@ -0,0 +1,119 @@
use floem_editor_core::buffer::{
InsertsValueIter,
rope_text::{RopeText, RopeTextRef},
};
use lapce_xi_rope::{
Rope, RopeDelta, RopeInfo,
delta::InsertDelta,
multiset::{CountMatcher, Subset},
};
use tree_sitter::Point;
#[derive(Clone)]
pub struct SyntaxEdit(pub(crate) Vec<tree_sitter::InputEdit>);
impl SyntaxEdit {
pub fn new(edits: Vec<tree_sitter::InputEdit>) -> Self {
Self(edits)
}
pub fn from_delta(text: &Rope, delta: RopeDelta) -> SyntaxEdit {
let (ins_delta, deletes) = delta.factor();
Self::from_factored_delta(text, &ins_delta, &deletes)
}
pub fn from_factored_delta(
text: &Rope,
ins_delta: &InsertDelta<RopeInfo>,
deletes: &Subset,
) -> SyntaxEdit {
let deletes = deletes.transform_expand(&ins_delta.inserted_subset());
let mut edits = Vec::new();
let mut insert_edits: Vec<tree_sitter::InputEdit> =
InsertsValueIter::new(ins_delta)
.map(|insert| {
let start = insert.old_offset;
let inserted = insert.node;
create_insert_edit(text, start, inserted)
})
.collect();
insert_edits.reverse();
edits.append(&mut insert_edits);
let text = ins_delta.apply(text);
let mut delete_edits: Vec<tree_sitter::InputEdit> = deletes
.range_iter(CountMatcher::NonZero)
.map(|(start, end)| create_delete_edit(&text, start, end))
.collect();
delete_edits.reverse();
edits.append(&mut delete_edits);
SyntaxEdit::new(edits)
}
}
fn point_at_offset(text: &Rope, offset: usize) -> Point {
let text = RopeTextRef::new(text);
let line = text.line_of_offset(offset);
let col = text.offset_of_line(line + 1).saturating_sub(offset);
Point::new(line, col)
}
fn traverse(point: Point, text: &str) -> Point {
let Point {
mut row,
mut column,
} = point;
for ch in text.chars() {
if ch == '\n' {
row += 1;
column = 0;
} else {
column += 1;
}
}
Point { row, column }
}
pub fn create_insert_edit(
old_text: &Rope,
start: usize,
inserted: &Rope,
) -> tree_sitter::InputEdit {
let start_position = point_at_offset(old_text, start);
tree_sitter::InputEdit {
start_byte: start,
old_end_byte: start,
new_end_byte: start + inserted.len(),
start_position,
old_end_position: start_position,
new_end_position: traverse(
start_position,
&inserted.slice_to_cow(0..inserted.len()),
),
}
}
pub fn create_delete_edit(
old_text: &Rope,
start: usize,
end: usize,
) -> tree_sitter::InputEdit {
let start_position = point_at_offset(old_text, start);
let end_position = point_at_offset(old_text, end);
tree_sitter::InputEdit {
start_byte: start,
// The old end byte position was at the end
old_end_byte: end,
// but since we're deleting everything up to it, it gets 'moved' to where we start
new_end_byte: start,
start_position,
old_end_position: end_position,
new_end_position: start_position,
}
}
+831
View File
@@ -0,0 +1,831 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Much of the code in this file is modified from [helix](https://github.com/helix-editor/helix)'s implementation of their syntax highlighting, which is under the MPL.
*/
use std::{
borrow::Cow,
cell::RefCell,
collections::HashMap,
path::Path,
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
};
use arc_swap::ArcSwap;
use lapce_xi_rope::Rope;
use once_cell::sync::Lazy;
use regex::Regex;
use tree_sitter::{
Language, Point, Query, QueryCaptures, QueryCursor, QueryMatch, Tree,
};
use super::{PARSER, util::RopeProvider};
use crate::{language::LapceLanguage, style::SCOPES};
thread_local! {
static HIGHLIGHT_CONFIGS: RefCell<HashMap<LapceLanguage, Result<Arc<HighlightConfiguration>, HighlightIssue>>> = Default::default();
}
pub fn reset_highlight_configs() {
HIGHLIGHT_CONFIGS.with_borrow_mut(|configs| {
configs.clear();
});
}
pub(crate) fn get_highlight_config(
lang: LapceLanguage,
) -> Result<Arc<HighlightConfiguration>, HighlightIssue> {
HIGHLIGHT_CONFIGS.with(|configs| {
let mut configs = configs.borrow_mut();
let config = configs
.entry(lang)
.or_insert_with(|| lang.new_highlight_config().map(Arc::new));
config.clone()
})
}
/// Indicates which highlight should be applied to a region of source code.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Highlight(pub usize);
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HighlightIssue {
Error(String),
NotAvailable,
}
/// Represents a single step in rendering a syntax-highlighted document.
#[derive(Copy, Clone, Debug)]
pub enum HighlightEvent {
Source { start: usize, end: usize },
HighlightStart(Highlight),
HighlightEnd,
}
#[derive(Debug)]
pub(crate) struct LocalDef<'a> {
name: Cow<'a, str>,
value_range: std::ops::Range<usize>,
highlight: Option<Highlight>,
}
#[derive(Debug)]
pub(crate) struct LocalScope<'a> {
pub(crate) inherits: bool,
pub(crate) range: std::ops::Range<usize>,
pub(crate) local_defs: Vec<LocalDef<'a>>,
}
#[derive(Debug, Clone)]
pub enum InjectionLanguageMarker<'a> {
Name(Cow<'a, str>),
Filename(Cow<'a, Path>),
Shebang(String),
}
const SHEBANG: &str = r"#!\s*(?:\S*[/\\](?:env\s+(?:\-\S+\s+)*)?)?([^\s\.\d]+)";
const CANCELLATION_CHECK_INTERVAL: usize = 100;
/// Contains the data needed to highlight code written in a particular language.
///
/// This struct is immutable and can be shared between threads.
#[derive(Debug)]
pub struct HighlightConfiguration {
pub language: Language,
pub query: Query,
pub injections_query: Query,
pub combined_injections_patterns: Vec<usize>,
pub highlights_pattern_index: usize,
pub highlight_indices: ArcSwap<Vec<Option<Highlight>>>,
pub non_local_variable_patterns: Vec<bool>,
pub injection_content_capture_index: Option<u32>,
pub injection_language_capture_index: Option<u32>,
pub injection_filename_capture_index: Option<u32>,
pub injection_shebang_capture_index: Option<u32>,
pub local_scope_capture_index: Option<u32>,
pub local_def_capture_index: Option<u32>,
pub local_def_value_capture_index: Option<u32>,
pub local_ref_capture_index: Option<u32>,
}
impl HighlightConfiguration {
/// Creates a `HighlightConfiguration` for a given `Language` and set of highlighting
/// queries.
///
/// # Parameters
///
/// * `language` - The Tree-sitter `Language` that should be used for parsing.
/// * `highlights_query` - A string containing tree patterns for syntax highlighting. This
/// should be non-empty, otherwise no syntax highlights will be added.
/// * `injections_query` - A string containing tree patterns for injecting other languages
/// into the document. This can be empty if no injections are desired.
/// * `locals_query` - A string containing tree patterns for tracking local variable
/// definitions and references. This can be empty if local variable tracking is not needed.
///
/// Returns a `HighlightConfiguration` that can then be used with the `highlight` method.
pub fn new(
language: Language,
highlights_query: &str,
injection_query: &str,
locals_query: &str,
) -> Result<Self, tree_sitter::QueryError> {
// Concatenate the query strings, keeping track of the start offset of each section.
let mut query_source = String::new();
query_source.push_str(locals_query);
let highlights_query_offset = query_source.len();
query_source.push_str(highlights_query);
// Construct a single query by concatenating the three query strings, but record the
// range of pattern indices that belong to each individual string.
let query = Query::new(&language, &query_source)?;
let mut highlights_pattern_index = 0;
for i in 0..(query.pattern_count()) {
let pattern_offset = query.start_byte_for_pattern(i);
if pattern_offset < highlights_query_offset {
highlights_pattern_index += 1;
}
}
let injections_query = Query::new(&language, injection_query)?;
let combined_injections_patterns = (0..injections_query.pattern_count())
.filter(|&i| {
injections_query
.property_settings(i)
.iter()
.any(|s| &*s.key == "injection.combined")
})
.collect();
// Find all of the highlighting patterns that are disabled for nodes that
// have been identified as local variables.
let non_local_variable_patterns = (0..query.pattern_count())
.map(|i| {
query.property_predicates(i).iter().any(|(prop, positive)| {
!*positive && prop.key.as_ref() == "local"
})
})
.collect();
// Store the numeric ids for all of the special captures.
let mut injection_content_capture_index = None;
let mut injection_language_capture_index = None;
let mut injection_filename_capture_index = None;
let mut injection_shebang_capture_index = None;
let mut local_def_capture_index = None;
let mut local_def_value_capture_index = None;
let mut local_ref_capture_index = None;
let mut local_scope_capture_index = None;
for (i, name) in query.capture_names().iter().enumerate() {
let i = Some(i as u32);
match *name {
"local.definition" => local_def_capture_index = i,
"local.definition-value" => local_def_value_capture_index = i,
"local.reference" => local_ref_capture_index = i,
"local.scope" => local_scope_capture_index = i,
_ => {}
}
}
for (i, name) in injections_query.capture_names().iter().enumerate() {
let i = Some(i as u32);
match *name {
"injection.content" => injection_content_capture_index = i,
"injection.language" => injection_language_capture_index = i,
"injection.filename" => injection_filename_capture_index = i,
"injection.shebang" => injection_shebang_capture_index = i,
_ => {}
}
}
let highlight_indices: ArcSwap<Vec<_>> =
ArcSwap::from_pointee(vec![None; query.capture_names().len()]);
let conf = Self {
language,
query,
injections_query,
combined_injections_patterns,
highlights_pattern_index,
highlight_indices,
non_local_variable_patterns,
injection_content_capture_index,
injection_language_capture_index,
injection_shebang_capture_index,
injection_filename_capture_index,
local_scope_capture_index,
local_def_capture_index,
local_def_value_capture_index,
local_ref_capture_index,
};
conf.configure(SCOPES);
Ok(conf)
}
/// Get a slice containing all of the highlight names used in the configuration.
pub fn names(&self) -> &[&str] {
self.query.capture_names()
}
/// Set the list of recognized highlight names.
///
/// Tree-sitter syntax-highlighting queries specify highlights in the form of dot-separated
/// highlight names like `punctuation.bracket` and `function.method.builtin`. Consumers of
/// these queries can choose to recognize highlights with different levels of specificity.
/// For example, the string `function.builtin` will match against `function.builtin.constructor`
/// but will not match `function.method.builtin` and `function.method`.
///
/// When highlighting, results are returned as `Highlight` values, which contain the index
/// of the matched highlight this list of highlight names.
pub fn configure(&self, recognized_names: &[&str]) {
let mut capture_parts = Vec::new();
let indices: Vec<_> = self
.query
.capture_names()
.iter()
.map(move |capture_name| {
capture_parts.clear();
capture_parts.extend(capture_name.split('.'));
let mut best_index = None;
let mut best_match_len = 0;
for (i, recognized_name) in recognized_names.iter().enumerate() {
let mut len = 0;
let mut matches = true;
for (i, part) in recognized_name.split('.').enumerate() {
match capture_parts.get(i) {
Some(capture_part) if *capture_part == part => len += 1,
_ => {
matches = false;
break;
}
}
}
if matches && len > best_match_len {
best_index = Some(i);
best_match_len = len;
}
}
best_index.map(Highlight)
})
.collect();
self.highlight_indices.store(Arc::new(indices));
}
pub fn injection_pair<'a>(
&self,
query_match: &QueryMatch<'a, 'a>,
source: &'a Rope,
) -> (
Option<InjectionLanguageMarker<'a>>,
Option<tree_sitter::Node<'a>>,
) {
let mut injection_capture = None;
let mut content_node = None;
for capture in query_match.captures {
let index = Some(capture.index);
if index == self.injection_language_capture_index {
let name = source.slice_to_cow(capture.node.byte_range());
injection_capture = Some(InjectionLanguageMarker::Name(name));
} else if index == self.injection_filename_capture_index {
let name = source.slice_to_cow(capture.node.byte_range());
let path = Path::new(name.as_ref()).to_path_buf();
injection_capture =
Some(InjectionLanguageMarker::Filename(path.into()));
} else if index == self.injection_shebang_capture_index {
let node_slice = source.slice(capture.node.byte_range());
let max_line = node_slice.line_of_offset(node_slice.len());
let node_slice = if max_line > 0 {
node_slice.slice(..node_slice.offset_of_line(1))
} else {
node_slice
};
static SHEBANG_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(SHEBANG).unwrap());
injection_capture = SHEBANG_REGEX
.captures_iter(&node_slice.slice_to_cow(..))
.map(|cap| InjectionLanguageMarker::Shebang(cap[1].to_string()))
.next()
} else if index == self.injection_content_capture_index {
content_node = Some(capture.node);
}
}
(injection_capture, content_node)
}
pub(crate) fn injection_for_match<'a>(
&self,
query: &'a Query,
query_match: &QueryMatch<'a, 'a>,
source: &'a Rope,
) -> (
Option<InjectionLanguageMarker<'a>>,
Option<tree_sitter::Node<'a>>,
IncludedChildren,
) {
let (mut injection_capture, content_node) =
self.injection_pair(query_match, source);
let mut included_children = IncludedChildren::default();
for prop in query.property_settings(query_match.pattern_index) {
match prop.key.as_ref() {
// In addition to specifying the language name via the text of a
// captured node, it can also be hard-coded via a `#set!` predicate
// that sets the injection.language key.
"injection.language" if injection_capture.is_none() => {
injection_capture = prop
.value
.as_ref()
.map(|s| InjectionLanguageMarker::Name(s.as_ref().into()));
}
// By default, injections do not include the *children* of an
// `injection.content` node - only the ranges that belong to the
// node itself. This can be changed using a `#set!` predicate that
// sets the `injection.include-children` key.
"injection.include-children" => {
included_children = IncludedChildren::All
}
// Some queries might only exclude named children but include unnamed
// children in their `injection.content` node. This can be enabled using
// a `#set!` predicate that sets the `injection.include-unnamed-children` key.
"injection.include-unnamed-children" => {
included_children = IncludedChildren::Unnamed
}
_ => {}
}
}
(injection_capture, content_node, included_children)
}
}
#[derive(Debug)]
pub(crate) struct HighlightIter<'a> {
pub(crate) source: &'a Rope,
pub(crate) byte_offset: usize,
pub(crate) cancellation_flag: Option<&'a AtomicUsize>,
pub(crate) layers: Vec<HighlightIterLayer<'a>>,
pub(crate) iter_count: usize,
pub(crate) next_event: Option<HighlightEvent>,
pub(crate) last_highlight_range: Option<(usize, usize, usize)>,
}
pub(crate) struct HighlightIterLayer<'a> {
pub(crate) _tree: Option<Tree>,
pub(crate) cursor: QueryCursor,
pub(crate) captures: RefCell<
std::iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>, &'a [u8]>>,
>,
pub(crate) config: &'a HighlightConfiguration,
pub(crate) highlight_end_stack: Vec<usize>,
pub(crate) scope_stack: Vec<LocalScope<'a>>,
pub(crate) depth: usize,
}
impl std::fmt::Debug for HighlightIterLayer<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HighlightIterLayer").finish()
}
}
impl HighlightIterLayer<'_> {
// First, sort scope boundaries by their byte offset in the document. At a
// given position, emit scope endings before scope beginnings. Finally, emit
// scope boundaries from deeper layers first.
pub fn sort_key(&self) -> Option<(usize, bool, isize)> {
let depth = -(self.depth as isize);
let next_start = self
.captures
.borrow_mut()
.peek()
.map(|(m, i)| m.captures[*i].node.start_byte());
let next_end = self.highlight_end_stack.last().cloned();
match (next_start, next_end) {
(Some(start), Some(end)) => {
if start < end {
Some((start, true, depth))
} else {
Some((end, false, depth))
}
}
(Some(i), None) => Some((i, true, depth)),
(None, Some(j)) => Some((j, false, depth)),
_ => None,
}
}
}
impl HighlightIter<'_> {
fn emit_event(
&mut self,
offset: usize,
event: Option<HighlightEvent>,
) -> Option<Result<HighlightEvent, super::Error>> {
let result;
if self.byte_offset < offset {
result = Some(Ok(HighlightEvent::Source {
start: self.byte_offset,
end: offset,
}));
self.byte_offset = offset;
self.next_event = event;
} else {
result = event.map(Ok);
}
self.sort_layers();
result
}
pub(crate) fn sort_layers(&mut self) {
while !self.layers.is_empty() {
if let Some(sort_key) = self.layers[0].sort_key() {
let mut i = 0;
while i + 1 < self.layers.len() {
if let Some(next_offset) = self.layers[i + 1].sort_key() {
if next_offset < sort_key {
i += 1;
continue;
}
} else {
let layer = self.layers.remove(i + 1);
PARSER.with(|ts_parser| {
let highlighter = &mut ts_parser.borrow_mut();
highlighter.cursors.push(layer.cursor);
});
}
break;
}
if i > 0 {
self.layers[0..(i + 1)].rotate_left(1);
}
break;
} else {
let layer = self.layers.remove(0);
PARSER.with(|ts_parser| {
let highlighter = &mut ts_parser.borrow_mut();
highlighter.cursors.push(layer.cursor);
});
}
}
}
}
impl Iterator for HighlightIter<'_> {
type Item = Result<HighlightEvent, super::Error>;
fn next(&mut self) -> Option<Self::Item> {
'main: loop {
// If we've already determined the next highlight boundary, just return it.
if let Some(e) = self.next_event.take() {
return Some(Ok(e));
}
// Periodically check for cancellation, returning `Cancelled` error if the
// cancellation flag was flipped.
if let Some(cancellation_flag) = self.cancellation_flag {
self.iter_count += 1;
if self.iter_count >= CANCELLATION_CHECK_INTERVAL {
self.iter_count = 0;
if cancellation_flag.load(Ordering::Relaxed) != 0 {
return Some(Err(super::Error::Cancelled));
}
}
}
// If none of the layers have any more highlight boundaries, terminate.
if self.layers.is_empty() {
let len = self.source.len();
return if self.byte_offset < len {
let result = Some(Ok(HighlightEvent::Source {
start: self.byte_offset,
end: len,
}));
self.byte_offset = len;
result
} else {
None
};
}
// Get the next capture from whichever layer has the earliest highlight boundary.
let range;
let layer = &mut self.layers[0];
let captures = layer.captures.get_mut();
if let Some((next_match, capture_index)) = captures.peek() {
let next_capture = next_match.captures[*capture_index];
range = next_capture.node.byte_range();
// If any previous highlight ends before this node starts, then before
// processing this capture, emit the source code up until the end of the
// previous highlight, and an end event for that highlight.
if let Some(end_byte) = layer.highlight_end_stack.last().cloned() {
if end_byte <= range.start {
layer.highlight_end_stack.pop();
return self.emit_event(
end_byte,
Some(HighlightEvent::HighlightEnd),
);
}
}
}
// If there are no more captures, then emit any remaining highlight end events.
// And if there are none of those, then just advance to the end of the document.
else if let Some(end_byte) = layer.highlight_end_stack.last().cloned()
{
layer.highlight_end_stack.pop();
return self
.emit_event(end_byte, Some(HighlightEvent::HighlightEnd));
} else {
return self.emit_event(self.source.len(), None);
};
let (mut match_, capture_index) = captures.next().unwrap();
let mut capture = match_.captures[capture_index];
// Remove from the local scope stack any local scopes that have already ended.
while range.start > layer.scope_stack.last().unwrap().range.end {
layer.scope_stack.pop();
}
// If this capture is for tracking local variables, then process the
// local variable info.
let mut reference_highlight = None;
let mut definition_highlight = None;
while match_.pattern_index < layer.config.highlights_pattern_index {
// If the node represents a local scope, push a new local scope onto
// the scope stack.
if Some(capture.index) == layer.config.local_scope_capture_index {
definition_highlight = None;
let mut scope = LocalScope {
inherits: true,
range: range.clone(),
local_defs: Vec::new(),
};
for prop in
layer.config.query.property_settings(match_.pattern_index)
{
if let "local.scope-inherits" = prop.key.as_ref() {
scope.inherits = prop
.value
.as_ref()
.is_none_or(|r| r.as_ref() == "true");
}
}
layer.scope_stack.push(scope);
}
// If the node represents a definition, add a new definition to the
// local scope at the top of the scope stack.
else if Some(capture.index) == layer.config.local_def_capture_index
{
reference_highlight = None;
let scope = layer.scope_stack.last_mut().unwrap();
let mut value_range = 0..0;
for capture in match_.captures {
if Some(capture.index)
== layer.config.local_def_value_capture_index
{
value_range = capture.node.byte_range();
}
}
let name = self.source.slice_to_cow(range.clone());
scope.local_defs.push(LocalDef {
name,
value_range,
highlight: None,
});
definition_highlight =
scope.local_defs.last_mut().map(|s| &mut s.highlight);
}
// If the node represents a reference, then try to find the corresponding
// definition in the scope stack.
else if Some(capture.index) == layer.config.local_ref_capture_index
&& definition_highlight.is_none()
{
definition_highlight = None;
let name = self.source.slice_to_cow(range.clone());
for scope in layer.scope_stack.iter().rev() {
if let Some(highlight) =
scope.local_defs.iter().rev().find_map(|def| {
if def.name == name
&& range.start >= def.value_range.end
{
Some(def.highlight)
} else {
None
}
})
{
reference_highlight = highlight;
break;
}
if !scope.inherits {
break;
}
}
}
// Continue processing any additional matches for the same node.
if let Some((next_match, next_capture_index)) = captures.peek() {
let next_capture = next_match.captures[*next_capture_index];
if next_capture.node == capture.node {
capture = next_capture;
match_ = captures.next().unwrap().0;
continue;
}
}
self.sort_layers();
continue 'main;
}
// Otherwise, this capture must represent a highlight.
// If this exact range has already been highlighted by an earlier pattern, or by
// a different layer, then skip over this one.
if let Some((last_start, last_end, last_depth)) =
self.last_highlight_range
{
if range.start == last_start
&& range.end == last_end
&& layer.depth < last_depth
{
self.sort_layers();
continue 'main;
}
}
// If the current node was found to be a local variable, then skip over any
// highlighting patterns that are disabled for local variables.
if definition_highlight.is_some() || reference_highlight.is_some() {
while layer.config.non_local_variable_patterns[match_.pattern_index]
{
if let Some((next_match, next_capture_index)) = captures.peek() {
let next_capture = next_match.captures[*next_capture_index];
if next_capture.node == capture.node {
capture = next_capture;
match_ = captures.next().unwrap().0;
continue;
}
}
self.sort_layers();
continue 'main;
}
}
// Once a highlighting pattern is found for the current node, skip over
// any later highlighting patterns that also match this node. Captures
// for a given node are ordered by pattern index, so these subsequent
// captures are guaranteed to be for highlighting, not injections or
// local variables.
while let Some((next_match, next_capture_index)) = captures.peek() {
let next_capture = next_match.captures[*next_capture_index];
if next_capture.node == capture.node {
captures.next();
} else {
break;
}
}
let current_highlight =
layer.config.highlight_indices.load()[capture.index as usize];
// If this node represents a local definition, then store the current
// highlight value on the local scope entry representing this node.
if let Some(definition_highlight) = definition_highlight {
*definition_highlight = current_highlight;
}
// Emit a scope start event and push the node's end position to the stack.
if let Some(highlight) = reference_highlight.or(current_highlight) {
self.last_highlight_range =
Some((range.start, range.end, layer.depth));
layer.highlight_end_stack.push(range.end);
return self.emit_event(
range.start,
Some(HighlightEvent::HighlightStart(highlight)),
);
}
self.sort_layers();
}
}
}
#[derive(Clone, Default)]
pub(crate) enum IncludedChildren {
#[default]
None,
All,
Unnamed,
}
// Compute the ranges that should be included when parsing an injection.
// This takes into account three things:
// * `parent_ranges` - The ranges must all fall within the *current* layer's ranges.
// * `nodes` - Every injection takes place within a set of nodes. The injection ranges
// are the ranges of those nodes.
// * `includes_children` - For some injections, the content nodes' children should be
// excluded from the nested document, so that only the content nodes' *own* content
// is reparsed. For other injections, the content nodes' entire ranges should be
// reparsed, including the ranges of their children.
pub(crate) fn intersect_ranges(
parent_ranges: &[tree_sitter::Range],
nodes: &[tree_sitter::Node],
included_children: IncludedChildren,
) -> Vec<tree_sitter::Range> {
let mut cursor = nodes[0].walk();
let mut result = Vec::new();
let mut parent_range_iter = parent_ranges.iter();
let mut parent_range = parent_range_iter
.next()
.expect("Layers should only be constructed with non-empty ranges vectors");
for node in nodes.iter() {
let mut preceding_range = tree_sitter::Range {
start_byte: 0,
start_point: Point::new(0, 0),
end_byte: node.start_byte(),
end_point: node.start_position(),
};
let following_range = tree_sitter::Range {
start_byte: node.end_byte(),
start_point: node.end_position(),
end_byte: usize::MAX,
end_point: Point::new(usize::MAX, usize::MAX),
};
for excluded_range in node
.children(&mut cursor)
.filter_map(|child| match included_children {
IncludedChildren::None => Some(child.range()),
IncludedChildren::All => None,
IncludedChildren::Unnamed => {
if child.is_named() {
Some(child.range())
} else {
None
}
}
})
.chain([following_range].iter().cloned())
{
let mut range = tree_sitter::Range {
start_byte: preceding_range.end_byte,
start_point: preceding_range.end_point,
end_byte: excluded_range.start_byte,
end_point: excluded_range.start_point,
};
preceding_range = excluded_range;
if range.end_byte < parent_range.start_byte {
continue;
}
while parent_range.start_byte <= range.end_byte {
if parent_range.end_byte > range.start_byte {
if range.start_byte < parent_range.start_byte {
range.start_byte = parent_range.start_byte;
range.start_point = parent_range.start_point;
}
if parent_range.end_byte < range.end_byte {
if range.start_byte < parent_range.end_byte {
result.push(tree_sitter::Range {
start_byte: range.start_byte,
start_point: range.start_point,
end_byte: parent_range.end_byte,
end_point: parent_range.end_point,
});
}
range.start_byte = parent_range.end_byte;
range.start_point = parent_range.end_point;
} else {
if range.start_byte < range.end_byte {
result.push(range);
}
break;
}
}
if let Some(next_range) = parent_range_iter.next() {
parent_range = next_range;
} else {
return result;
}
}
}
}
result
}
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
use lapce_xi_rope::{Rope, rope::ChunkIter};
use tree_sitter::TextProvider;
pub struct RopeChunksIterBytes<'a> {
chunks: ChunkIter<'a>,
}
impl<'a> Iterator for RopeChunksIterBytes<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
self.chunks.next().map(str::as_bytes)
}
}
/// This allows tree-sitter to iterate over our Rope without us having to convert it into
/// a contiguous byte-list.
pub struct RopeProvider<'a>(pub &'a Rope);
impl<'a> TextProvider<&'a [u8]> for RopeProvider<'a> {
type I = RopeChunksIterBytes<'a>;
fn text(&mut self, node: tree_sitter::Node) -> Self::I {
let start = node.start_byte();
let end = node.end_byte().min(self.0.len());
let chunks = self.0.iter_chunks(start..end);
RopeChunksIterBytes { chunks }
}
}