1use language::{BufferSnapshot, Point};
2use std::ops::Range;
3
4pub fn editable_and_context_ranges_for_cursor_position(
5 position: Point,
6 snapshot: &BufferSnapshot,
7 editable_region_token_limit: usize,
8 context_token_limit: usize,
9) -> (Range<Point>, Range<Point>) {
10 let mut scope_range = position..position;
11 let mut remaining_edit_tokens = editable_region_token_limit;
12
13 while let Some(parent) = snapshot.syntax_ancestor(scope_range.clone()) {
14 let parent_tokens = guess_token_count(parent.byte_range().len());
15 let parent_point_range = Point::new(
16 parent.start_position().row as u32,
17 parent.start_position().column as u32,
18 )
19 ..Point::new(
20 parent.end_position().row as u32,
21 parent.end_position().column as u32,
22 );
23 if parent_point_range == scope_range {
24 break;
25 } else if parent_tokens <= editable_region_token_limit {
26 scope_range = parent_point_range;
27 remaining_edit_tokens = editable_region_token_limit - parent_tokens;
28 } else {
29 break;
30 }
31 }
32
33 let editable_range = expand_range(snapshot, scope_range, remaining_edit_tokens);
34 let context_range = expand_range(snapshot, editable_range.clone(), context_token_limit);
35 (editable_range, context_range)
36}
37
38fn expand_range(
39 snapshot: &BufferSnapshot,
40 range: Range<Point>,
41 mut remaining_tokens: usize,
42) -> Range<Point> {
43 let mut expanded_range = range;
44 expanded_range.start.column = 0;
45 expanded_range.end.column = snapshot.line_len(expanded_range.end.row);
46 loop {
47 let mut expanded = false;
48
49 if remaining_tokens > 0 && expanded_range.start.row > 0 {
50 expanded_range.start.row -= 1;
51 let line_tokens =
52 guess_token_count(snapshot.line_len(expanded_range.start.row) as usize);
53 remaining_tokens = remaining_tokens.saturating_sub(line_tokens);
54 expanded = true;
55 }
56
57 if remaining_tokens > 0 && expanded_range.end.row < snapshot.max_point().row {
58 expanded_range.end.row += 1;
59 expanded_range.end.column = snapshot.line_len(expanded_range.end.row);
60 let line_tokens = guess_token_count(expanded_range.end.column as usize);
61 remaining_tokens = remaining_tokens.saturating_sub(line_tokens);
62 expanded = true;
63 }
64
65 if !expanded {
66 break;
67 }
68 }
69 expanded_range
70}
71
72/// Typical number of string bytes per token for the purposes of limiting model input. This is
73/// intentionally low to err on the side of underestimating limits.
74pub(crate) const BYTES_PER_TOKEN_GUESS: usize = 3;
75
76pub fn guess_token_count(bytes: usize) -> usize {
77 bytes / BYTES_PER_TOKEN_GUESS
78}