Skip to content

Commit 3ad64a3

Browse files
authored
feat!: get completions camel case (#48)
* feat!: make API camelCase * fix(testbed): update llm-ls API * feat: sync docs with incremental change
1 parent 59185ab commit 3ad64a3

File tree

4 files changed

+149
-30
lines changed

4 files changed

+149
-30
lines changed

crates/llm-ls/src/document.rs

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use ropey::Rope;
22
use tower_lsp::jsonrpc::Result;
3-
use tree_sitter::{Parser, Tree};
3+
use tower_lsp::lsp_types::Range;
4+
use tracing::info;
5+
use tree_sitter::{InputEdit, Parser, Point, Tree};
46

5-
use crate::internal_error;
67
use crate::language_id::LanguageId;
8+
use crate::{get_position_idx, internal_error};
79

810
fn get_parser(language_id: LanguageId) -> Result<Parser> {
911
match language_id {
@@ -186,10 +188,85 @@ impl Document {
186188
})
187189
}
188190

189-
pub(crate) async fn change(&mut self, text: &str) -> Result<()> {
190-
let rope = Rope::from_str(text);
191-
self.tree = self.parser.parse(text, None);
192-
self.text = rope;
191+
pub(crate) async fn change(&mut self, range: Range, text: &str) -> Result<()> {
192+
let start_idx = get_position_idx(
193+
&self.text,
194+
range.start.line as usize,
195+
range.start.character as usize,
196+
)?;
197+
let start_byte = self
198+
.text
199+
.try_char_to_byte(start_idx)
200+
.map_err(internal_error)?;
201+
let old_end_idx = get_position_idx(
202+
&self.text,
203+
range.end.line as usize,
204+
range.end.character as usize,
205+
)?;
206+
let old_end_byte = self
207+
.text
208+
.try_char_to_byte(old_end_idx)
209+
.map_err(internal_error)?;
210+
let start_position = Point {
211+
row: range.start.line as usize,
212+
column: range.start.character as usize,
213+
};
214+
let old_end_position = Point {
215+
row: range.end.line as usize,
216+
column: range.end.character as usize,
217+
};
218+
let (new_end_idx, new_end_position) = if range.start == range.end {
219+
let row = range.start.line as usize;
220+
let column = range.start.character as usize;
221+
let idx = self.text.try_line_to_char(row).map_err(internal_error)? + column;
222+
let rope = Rope::from_str(text);
223+
let text_len = rope.len_chars();
224+
let end_idx = idx + text_len;
225+
self.text.insert(idx, text);
226+
(
227+
end_idx,
228+
Point {
229+
row,
230+
column: column + text_len,
231+
},
232+
)
233+
} else {
234+
let slice_size = old_end_idx - start_idx;
235+
self.text
236+
.try_remove(start_idx..old_end_idx)
237+
.map_err(internal_error)?;
238+
self.text.insert(start_idx, text);
239+
let rope = Rope::from_str(text);
240+
let text_len = rope.len_chars();
241+
let character_difference = text_len as isize - slice_size as isize;
242+
let new_end_idx = if character_difference.is_negative() {
243+
old_end_idx - character_difference.wrapping_abs() as usize
244+
} else {
245+
old_end_idx + character_difference as usize
246+
};
247+
let row = self
248+
.text
249+
.try_char_to_line(new_end_idx)
250+
.map_err(internal_error)?;
251+
let line_start = self.text.try_line_to_char(row).map_err(internal_error)?;
252+
let column = new_end_idx - line_start;
253+
(new_end_idx, Point { row, column })
254+
};
255+
if let Some(tree) = self.tree.as_mut() {
256+
let edit = InputEdit {
257+
start_byte,
258+
old_end_byte,
259+
new_end_byte: self
260+
.text
261+
.try_char_to_byte(new_end_idx)
262+
.map_err(internal_error)?,
263+
start_position,
264+
old_end_position,
265+
new_end_position,
266+
};
267+
tree.edit(&edit);
268+
}
269+
self.tree = self.parser.parse(self.text.to_string(), self.tree.as_ref());
193270
Ok(())
194271
}
195272
}

crates/llm-ls/src/main.rs

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ const MAX_WARNING_REPEAT: Duration = Duration::from_secs(3_600);
2525
const NAME: &str = "llm-ls";
2626
const VERSION: &str = env!("CARGO_PKG_VERSION");
2727

28+
fn get_position_idx(rope: &Rope, row: usize, col: usize) -> Result<usize> {
29+
Ok(rope.try_line_to_char(row).map_err(internal_error)?
30+
+ col.min(
31+
rope.get_line(row.min(rope.len_lines() - 1))
32+
.ok_or_else(|| internal_error(format!("failed to find line at {row}")))?
33+
.len_chars()
34+
- 1,
35+
))
36+
}
37+
2838
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
2939
enum CompletionType {
3040
Empty,
@@ -42,45 +52,71 @@ impl Display for CompletionType {
4252
}
4353
}
4454

45-
fn should_complete(document: &Document, position: Position) -> CompletionType {
55+
fn should_complete(document: &Document, position: Position) -> Result<CompletionType> {
4656
let row = position.line as usize;
4757
let column = position.character as usize;
4858
if let Some(tree) = &document.tree {
4959
let current_node = tree.root_node().descendant_for_point_range(
5060
tree_sitter::Point { row, column },
51-
tree_sitter::Point { row, column },
61+
tree_sitter::Point {
62+
row,
63+
column: column + 1,
64+
},
5265
);
5366
if let Some(node) = current_node {
5467
if node == tree.root_node() {
55-
return CompletionType::MultiLine;
68+
return Ok(CompletionType::MultiLine);
5669
}
5770
let start = node.start_position();
5871
let end = node.end_position();
59-
let mut start_offset = document.text.line_to_char(start.row) + start.column;
60-
let mut end_offset = document.text.line_to_char(end.row) + end.column - 1;
61-
let start_char = document.text.char(start_offset);
72+
let mut start_offset = get_position_idx(&document.text, start.row, start.column)?;
73+
let mut end_offset = get_position_idx(&document.text, end.row, end.column)? - 1;
74+
let start_char = document
75+
.text
76+
.get_char(start_offset.min(document.text.len_chars() - 1))
77+
.ok_or_else(|| {
78+
internal_error(format!("failed to find start char at {start_offset}"))
79+
})?;
80+
let end_char = document
81+
.text
82+
.get_char(end_offset.min(document.text.len_chars() - 1))
83+
.ok_or_else(|| {
84+
internal_error(format!("failed to find end char at {end_offset}"))
85+
})?;
6286
if !start_char.is_whitespace() {
6387
start_offset += 1;
6488
}
65-
let end_char = document.text.char(end_offset);
6689
if !end_char.is_whitespace() {
6790
end_offset -= 1;
6891
}
6992
if start_offset >= end_offset {
70-
return CompletionType::SingleLine;
93+
return Ok(CompletionType::SingleLine);
7194
}
72-
let slice = document.text.slice(start_offset..end_offset);
95+
let slice = document
96+
.text
97+
.get_slice(start_offset..end_offset)
98+
.ok_or_else(|| {
99+
internal_error(format!(
100+
"failed to find slice at {start_offset}..{end_offset}"
101+
))
102+
})?;
73103
if slice.to_string().trim().is_empty() {
74-
return CompletionType::MultiLine;
104+
return Ok(CompletionType::MultiLine);
75105
}
76106
}
77107
}
78-
let start_idx = document.text.line_to_char(row);
79-
let next_char = document.text.char(start_idx + column);
108+
let start_idx = document
109+
.text
110+
.try_line_to_char(row)
111+
.map_err(internal_error)?;
112+
let next_char = document
113+
.text
114+
.get_char(start_idx + column)
115+
.ok_or_else(|| internal_error(format!("failed to find char at {}", start_idx + column)))?;
80116
if next_char.is_whitespace() {
81-
CompletionType::SingleLine
117+
Ok(CompletionType::SingleLine)
82118
} else {
83-
CompletionType::Empty
119+
Ok(CompletionType::Empty)
84120
}
85121
}
86122

@@ -271,12 +307,12 @@ fn build_prompt(
271307
let mut after_iter = text.lines_at(pos.line as usize);
272308
let mut before_line = before_iter.next();
273309
if let Some(line) = before_line {
274-
let col = (pos.character as usize).clamp(0, line.len_chars());
310+
let col = (pos.character as usize).clamp(0, line.len_chars() - 1);
275311
before_line = Some(line.slice(0..col));
276312
}
277313
let mut after_line = after_iter.next();
278314
if let Some(line) = after_line {
279-
let col = (pos.character as usize).clamp(0, line.len_chars());
315+
let col = (pos.character as usize).clamp(0, line.len_chars() - 1);
280316
after_line = Some(line.slice(col..));
281317
}
282318
let mut before = vec![];
@@ -334,7 +370,7 @@ fn build_prompt(
334370
let mut first = true;
335371
for mut line in text.lines_at(pos.line as usize + 1).reversed() {
336372
if first {
337-
let col = (pos.character as usize).clamp(0, line.len_chars());
373+
let col = (pos.character as usize).clamp(0, line.len_chars() - 1);
338374
line = line.slice(0..col);
339375
first = false;
340376
}
@@ -582,7 +618,7 @@ impl Backend {
582618
*unauthenticated_warn_at = Instant::now();
583619
}
584620
}
585-
let completion_type = should_complete(document, params.text_document_position.position);
621+
let completion_type = should_complete(document, params.text_document_position.position)?;
586622
info!(%completion_type, "completion type: {completion_type:?}");
587623
if completion_type == CompletionType::Empty {
588624
return Ok(CompletionResult { request_id, completions: vec![]});
@@ -658,7 +694,7 @@ impl LanguageServer for Backend {
658694
}),
659695
capabilities: ServerCapabilities {
660696
text_document_sync: Some(TextDocumentSyncCapability::Kind(
661-
TextDocumentSyncKind::FULL,
697+
TextDocumentSyncKind::INCREMENTAL,
662698
)),
663699
..Default::default()
664700
},
@@ -702,9 +738,15 @@ impl LanguageServer for Backend {
702738
let mut document_map = self.document_map.write().await;
703739
let doc = document_map.get_mut(&uri);
704740
if let Some(doc) = doc {
705-
match doc.change(&params.content_changes[0].text).await {
706-
Ok(()) => info!("{uri} changed"),
707-
Err(err) => error!("error when changing {uri}: {err}"),
741+
for change in &params.content_changes {
742+
if let Some(range) = change.range {
743+
match doc.change(range, &change.text).await {
744+
Ok(()) => info!("{uri} changed"),
745+
Err(err) => error!("error when changing {uri}: {err}"),
746+
}
747+
} else {
748+
warn!("Could not update document, got change request with missing range");
749+
}
708750
}
709751
} else {
710752
warn!("textDocument/didChange {uri}: document not found");

crates/testbed/src/holes_generator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ pub(crate) async fn generate_holes(
9191
if trimmed.starts_with(repo.language.comment_token()) || trimmed.is_empty() {
9292
continue;
9393
}
94-
let column_nb = rng.gen_range(0..15.min(line.len_chars()));
94+
let column_nb = rng.gen_range(0..15.min(line.len_chars() - 1));
9595
holes.push(Hole::new(
9696
line_nb as u32,
9797
column_nb as u32,

crates/testbed/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ async fn complete_holes(
454454
.line(hole.cursor.line as usize)
455455
.slice(hole.cursor.character as usize..)
456456
.len_chars()
457-
- 1;
457+
- 1; // NOTE: -1 to preserve the trailing `\n`
458458
file_content.remove(hole_start..hole_end);
459459

460460
let uri = Url::parse(&format!("file:/{file_path_str}"))?;

0 commit comments

Comments
 (0)