diff --git a/crates/cli/src/tests/node_test.rs b/crates/cli/src/tests/node_test.rs index 515d73aa..614bfdb9 100644 --- a/crates/cli/src/tests/node_test.rs +++ b/crates/cli/src/tests/node_test.rs @@ -1,4 +1,4 @@ -use tree_sitter::{Node, Parser, Point, Tree}; +use tree_sitter::{InputEdit, Node, Parser, Point, Tree}; use tree_sitter_generate::load_grammar_file; use super::{ @@ -843,6 +843,92 @@ fn test_node_is_error() { assert!(child.is_error()); } +#[test] +fn test_edit_point() { + let edit = InputEdit { + start_byte: 5, + old_end_byte: 5, + new_end_byte: 10, + start_position: Point::new(0, 5), + old_end_position: Point::new(0, 5), + new_end_position: Point::new(0, 10), + }; + + // Point after edit + let mut point = Point::new(0, 8); + let mut byte = 8; + edit.edit_point(&mut point, &mut byte); + assert_eq!(point, Point::new(0, 13)); + assert_eq!(byte, 13); + + // Point before edit + let mut point = Point::new(0, 2); + let mut byte = 2; + edit.edit_point(&mut point, &mut byte); + assert_eq!(point, Point::new(0, 2)); + assert_eq!(byte, 2); + + // Point at edit start + let mut point = Point::new(0, 5); + let mut byte = 5; + edit.edit_point(&mut point, &mut byte); + assert_eq!(point, Point::new(0, 10)); + assert_eq!(byte, 10); +} + +#[test] +fn test_edit_range() { + use tree_sitter::{InputEdit, Point, Range}; + + let edit = InputEdit { + start_byte: 10, + old_end_byte: 15, + new_end_byte: 20, + start_position: Point::new(1, 0), + old_end_position: Point::new(1, 5), + new_end_position: Point::new(2, 0), + }; + + // Range after edit + let mut range = Range { + start_byte: 20, + end_byte: 25, + start_point: Point::new(2, 0), + end_point: Point::new(2, 5), + }; + edit.edit_range(&mut range); + assert_eq!(range.start_byte, 25); + assert_eq!(range.end_byte, 30); + assert_eq!(range.start_point, Point::new(3, 0)); + assert_eq!(range.end_point, Point::new(3, 5)); + + // Range before edit + let mut range = Range { + start_byte: 5, + end_byte: 8, + start_point: Point::new(0, 5), + end_point: Point::new(0, 8), + }; + edit.edit_range(&mut range); + assert_eq!(range.start_byte, 5); + assert_eq!(range.end_byte, 8); + assert_eq!(range.start_point, Point::new(0, 5)); + assert_eq!(range.end_point, Point::new(0, 8)); + + // Range overlapping edit + let mut range = Range { + start_byte: 8, + end_byte: 12, + start_point: Point::new(0, 8), + end_point: Point::new(1, 2), + }; + edit.edit_range(&mut range); + assert_eq!(range.start_byte, 8); + assert_eq!(range.end_byte, 10); + assert_eq!(range.start_point, Point::new(0, 8)); + assert_eq!(range.end_point, Point::new(1, 0)); +} + #[test] fn test_node_sexp() { let mut parser = Parser::new(); diff --git a/lib/binding_rust/bindings.rs b/lib/binding_rust/bindings.rs index 77bfef32..3feb8409 100644 --- a/lib/binding_rust/bindings.rs +++ b/lib/binding_rust/bindings.rs @@ -495,6 +495,14 @@ extern "C" { #[doc = " Check if two nodes are identical."] pub fn ts_node_eq(self_: TSNode, other: TSNode) -> bool; } +extern "C" { + #[doc = " Edit a point to keep it in-sync with source code that has been edited.\n\n This function updates a single point's byte offset and row/column position\n based on an edit operation. This is useful for editing points without\n requiring a tree or node instance."] + pub fn ts_point_edit(point: *mut TSPoint, point_byte: *mut u32, edit: *const TSInputEdit); +} +extern "C" { + #[doc = " Edit a range to keep it in-sync with source code that has been edited.\n\n This function updates a range's start and end positions based on an edit\n operation. This is useful for editing ranges without requiring a tree\n or node instance."] + pub fn ts_range_edit(range: *mut TSRange, edit: *const TSInputEdit); +} extern "C" { #[doc = " Create a new tree cursor starting from the given node.\n\n A tree cursor allows you to walk a syntax tree more efficiently than is\n possible using the [`TSNode`] functions. It is a mutable object that is always\n on a certain syntax node, and can be moved imperatively to different nodes.\n\n Note that the given node is considered the root of the cursor,\n and the cursor cannot walk outside this node."] pub fn ts_tree_cursor_new(node: TSNode) -> TSTreeCursor; diff --git a/lib/binding_rust/lib.rs b/lib/binding_rust/lib.rs index 074981da..701ef7ac 100644 --- a/lib/binding_rust/lib.rs +++ b/lib/binding_rust/lib.rs @@ -120,6 +120,48 @@ pub struct InputEdit { pub new_end_position: Point, } +impl InputEdit { + /// Edit a point to keep it in-sync with source code that has been edited. + /// + /// This function updates a single point's byte offset and row/column position + /// based on this edit operation. This is useful for editing points without + /// requiring a tree or node instance. + #[doc(alias = "ts_point_edit")] + pub fn edit_point(&self, point: &mut Point, byte: &mut usize) { + let edit = self.into(); + let mut ts_point = (*point).into(); + let mut ts_byte = *byte as u32; + + unsafe { + ffi::ts_point_edit( + core::ptr::addr_of_mut!(ts_point), + core::ptr::addr_of_mut!(ts_byte), + &edit, + ); + } + + *point = ts_point.into(); + *byte = ts_byte as usize; + } + + /// Edit a range to keep it in-sync with source code that has been edited. + /// + /// This function updates a range's start and end positions based on this edit + /// operation. This is useful for editing ranges without requiring a tree + /// or node instance. + #[doc(alias = "ts_range_edit")] + pub fn edit_range(&self, range: &mut Range) { + let edit = self.into(); + let mut ts_range = (*range).into(); + + unsafe { + ffi::ts_range_edit(core::ptr::addr_of_mut!(ts_range), &edit); + } + + *range = ts_range.into(); + } +} + /// A single node within a syntax [`Tree`]. #[doc(alias = "TSNode")] #[derive(Clone, Copy)] diff --git a/lib/include/tree_sitter/api.h b/lib/include/tree_sitter/api.h index f1c69dec..264d405d 100644 --- a/lib/include/tree_sitter/api.h +++ b/lib/include/tree_sitter/api.h @@ -708,6 +708,24 @@ void ts_node_edit(TSNode *self, const TSInputEdit *edit); */ bool ts_node_eq(TSNode self, TSNode other); +/** + * Edit a point to keep it in-sync with source code that has been edited. + * + * This function updates a single point's byte offset and row/column position + * based on an edit operation. This is useful for editing points without + * requiring a tree or node instance. + */ +void ts_point_edit(TSPoint *point, uint32_t *point_byte, const TSInputEdit *edit); + +/** + * Edit a range to keep it in-sync with source code that has been edited. + * + * This function updates a range's start and end positions based on an edit + * operation. This is useful for editing ranges without requiring a tree + * or node instance. + */ +void ts_range_edit(TSRange *range, const TSInputEdit *edit); + /************************/ /* Section - TreeCursor */ /************************/ diff --git a/lib/src/get_changed_ranges.c b/lib/src/get_changed_ranges.c index 11084c33..c4c63653 100644 --- a/lib/src/get_changed_ranges.c +++ b/lib/src/get_changed_ranges.c @@ -103,6 +103,40 @@ void ts_range_array_get_changed_ranges( } } +void ts_range_edit(TSRange *range, const TSInputEdit *edit) { + if (range->end_byte >= edit->old_end_byte) { + if (range->end_byte != UINT32_MAX) { + range->end_byte = edit->new_end_byte + (range->end_byte - edit->old_end_byte); + range->end_point = point_add( + edit->new_end_point, + point_sub(range->end_point, edit->old_end_point) + ); + if (range->end_byte < edit->new_end_byte) { + range->end_byte = UINT32_MAX; + range->end_point = POINT_MAX; + } + } + } else if (range->end_byte > edit->start_byte) { + range->end_byte = edit->start_byte; + range->end_point = edit->start_point; + } + + if (range->start_byte >= edit->old_end_byte) { + range->start_byte = edit->new_end_byte + (range->start_byte - edit->old_end_byte); + range->start_point = point_add( + edit->new_end_point, + point_sub(range->start_point, edit->old_end_point) + ); + if (range->start_byte < edit->new_end_byte) { + range->start_byte = UINT32_MAX; + range->start_point = POINT_MAX; + } + } else if (range->start_byte > edit->start_byte) { + range->start_byte = edit->start_byte; + range->start_point = edit->start_point; + } +} + typedef struct { TreeCursor cursor; const TSLanguage *language; diff --git a/lib/src/lib.c b/lib/src/lib.c index 9bfb69f0..f56be97a 100644 --- a/lib/src/lib.c +++ b/lib/src/lib.c @@ -4,6 +4,7 @@ #include "./lexer.c" #include "./node.c" #include "./parser.c" +#include "./point.c" #include "./query.c" #include "./stack.c" #include "./subtree.c" diff --git a/lib/src/node.c b/lib/src/node.c index d83fa90b..84ffed28 100644 --- a/lib/src/node.c +++ b/lib/src/node.c @@ -861,13 +861,7 @@ void ts_node_edit(TSNode *self, const TSInputEdit *edit) { uint32_t start_byte = ts_node_start_byte(*self); TSPoint start_point = ts_node_start_point(*self); - if (start_byte >= edit->old_end_byte) { - start_byte = edit->new_end_byte + (start_byte - edit->old_end_byte); - start_point = point_add(edit->new_end_point, point_sub(start_point, edit->old_end_point)); - } else if (start_byte > edit->start_byte) { - start_byte = edit->new_end_byte; - start_point = edit->new_end_point; - } + ts_point_edit(&start_point, &start_byte, edit); self->context[0] = start_byte; self->context[1] = start_point.row; diff --git a/lib/src/point.c b/lib/src/point.c new file mode 100644 index 00000000..da46f61b --- /dev/null +++ b/lib/src/point.c @@ -0,0 +1,17 @@ +#include "point.h" + +void ts_point_edit(TSPoint *point, uint32_t *byte, const TSInputEdit *edit) { + uint32_t start_byte = *byte; + TSPoint start_point = *point; + + if (start_byte >= edit->old_end_byte) { + start_byte = edit->new_end_byte + (start_byte - edit->old_end_byte); + start_point = point_add(edit->new_end_point, point_sub(start_point, edit->old_end_point)); + } else if (start_byte > edit->start_byte) { + start_byte = edit->new_end_byte; + start_point = edit->new_end_point; + } + + *point = start_point; + *byte = start_byte; +} diff --git a/lib/src/tree.c b/lib/src/tree.c index 705f174c..6747fd6d 100644 --- a/lib/src/tree.c +++ b/lib/src/tree.c @@ -54,37 +54,7 @@ const TSLanguage *ts_tree_language(const TSTree *self) { void ts_tree_edit(TSTree *self, const TSInputEdit *edit) { for (unsigned i = 0; i < self->included_range_count; i++) { - TSRange *range = &self->included_ranges[i]; - if (range->end_byte >= edit->old_end_byte) { - if (range->end_byte != UINT32_MAX) { - range->end_byte = edit->new_end_byte + (range->end_byte - edit->old_end_byte); - range->end_point = point_add( - edit->new_end_point, - point_sub(range->end_point, edit->old_end_point) - ); - if (range->end_byte < edit->new_end_byte) { - range->end_byte = UINT32_MAX; - range->end_point = POINT_MAX; - } - } - } else if (range->end_byte > edit->start_byte) { - range->end_byte = edit->start_byte; - range->end_point = edit->start_point; - } - if (range->start_byte >= edit->old_end_byte) { - range->start_byte = edit->new_end_byte + (range->start_byte - edit->old_end_byte); - range->start_point = point_add( - edit->new_end_point, - point_sub(range->start_point, edit->old_end_point) - ); - if (range->start_byte < edit->new_end_byte) { - range->start_byte = UINT32_MAX; - range->start_point = POINT_MAX; - } - } else if (range->start_byte > edit->start_byte) { - range->start_byte = edit->start_byte; - range->start_point = edit->start_point; - } + ts_range_edit(&self->included_ranges[i], edit); } SubtreePool pool = ts_subtree_pool_new(0);