Skip to repository content763 lines · 28.7 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T06:06:22.270Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
edit_prediction_context.rs
1use crate::assemble_excerpts::assemble_excerpt_ranges;
2use anyhow::Result;
3use collections::HashMap;
4use futures::{FutureExt, StreamExt as _, channel::mpsc, future};
5use gpui::{
6 App, AppContext, AsyncApp, Context, Entity, EntityId, EventEmitter, Task, TaskExt, WeakEntity,
7};
8use language::{Anchor, Bias, Buffer, BufferSnapshot, OffsetRangeExt as _, Point, ToOffset as _};
9use project::{EditPredictionDefinition, Project, ProjectPath};
10use smallvec::SmallVec;
11use std::{
12 collections::hash_map,
13 ops::Range,
14 path::Path,
15 sync::Arc,
16 time::{Duration, Instant},
17};
18use util::paths::PathStyle;
19use util::rel_path::RelPath;
20use util::{RangeExt as _, ResultExt};
21
22mod assemble_excerpts;
23mod bm25_context;
24#[cfg(test)]
25mod edit_prediction_context_tests;
26mod editable_context;
27#[cfg(test)]
28mod fake_definition_lsp;
29mod git_log_context;
30
31pub use editable_context::{
32 EditHistoryContextEntry, OracleTarget, collect_editable_context,
33 limit_retrieved_context_to_bytes,
34};
35
36pub use zeta_prompt::{ContextSource, RelatedExcerpt, RelatedFile};
37
38const IDENTIFIER_LINE_COUNT: u32 = 3;
39const MAX_CONTEXT_IDENTIFIER_COUNT: usize = 32;
40
41pub struct RelatedExcerptStore {
42 project: WeakEntity<Project>,
43 related_buffers: Vec<RelatedBuffer>,
44 cache: HashMap<Identifier, Arc<CacheEntry>>,
45 update_tx: mpsc::UnboundedSender<(Entity<Buffer>, Anchor)>,
46 identifier_line_count: u32,
47}
48
49struct RelatedBuffer {
50 buffer: Entity<Buffer>,
51 path: Arc<Path>,
52 anchor_ranges: Vec<Range<Anchor>>,
53 excerpt_orders: Vec<usize>,
54 cached_file: Option<CachedRelatedFile>,
55}
56
57struct CachedRelatedFile {
58 excerpts: Vec<RelatedExcerpt>,
59 buffer_version: clock::Global,
60}
61
62pub enum RelatedExcerptStoreEvent {
63 StartedRefresh,
64 FinishedRefresh {
65 cache_hit_count: usize,
66 cache_miss_count: usize,
67 mean_definition_latency: Duration,
68 max_definition_latency: Duration,
69 },
70}
71
72#[derive(Clone, Debug, PartialEq, Eq, Hash)]
73struct Identifier {
74 pub name: String,
75 pub range: Range<Anchor>,
76}
77
78enum DefinitionTask {
79 CacheHit(Arc<CacheEntry>),
80 CacheMiss {
81 project: WeakEntity<Project>,
82 task: Task<Result<Vec<EditPredictionDefinition>>>,
83 },
84}
85
86#[derive(Debug)]
87struct CacheEntry {
88 definitions: SmallVec<[CachedDefinition; 1]>,
89}
90
91#[derive(Clone, Debug)]
92struct CachedDefinition {
93 path: ProjectPath,
94 buffer: Entity<Buffer>,
95 anchor_range: Range<Anchor>,
96}
97
98const DEBOUNCE_DURATION: Duration = Duration::from_millis(100);
99
100impl EventEmitter<RelatedExcerptStoreEvent> for RelatedExcerptStore {}
101
102impl RelatedExcerptStore {
103 pub fn new(project: &Entity<Project>, cx: &mut Context<Self>) -> Self {
104 let (update_tx, mut update_rx) = mpsc::unbounded::<(Entity<Buffer>, Anchor)>();
105 cx.spawn(async move |this, cx| {
106 let executor = cx.background_executor().clone();
107 while let Some((mut buffer, mut position)) = update_rx.next().await {
108 let mut timer = executor.timer(DEBOUNCE_DURATION).fuse();
109 loop {
110 futures::select_biased! {
111 next = update_rx.next() => {
112 if let Some((new_buffer, new_position)) = next {
113 buffer = new_buffer;
114 position = new_position;
115 timer = executor.timer(DEBOUNCE_DURATION).fuse();
116 } else {
117 return anyhow::Ok(());
118 }
119 }
120 _ = timer => break,
121 }
122 }
123
124 Self::fetch_excerpts(this.clone(), buffer, position, cx).await?;
125 }
126 anyhow::Ok(())
127 })
128 .detach_and_log_err(cx);
129
130 RelatedExcerptStore {
131 project: project.downgrade(),
132 update_tx,
133 related_buffers: Vec::new(),
134 cache: Default::default(),
135 identifier_line_count: IDENTIFIER_LINE_COUNT,
136 }
137 }
138
139 pub fn set_identifier_line_count(&mut self, count: u32) {
140 self.identifier_line_count = count;
141 }
142
143 pub fn refresh(&mut self, buffer: Entity<Buffer>, position: Anchor, _: &mut Context<Self>) {
144 self.update_tx.unbounded_send((buffer, position)).ok();
145 }
146
147 pub fn related_files(&mut self, cx: &App) -> Vec<RelatedFile> {
148 self.related_buffers
149 .iter_mut()
150 .map(|related| related.related_file(cx))
151 .collect()
152 }
153
154 pub fn related_files_with_buffers(
155 &mut self,
156 cx: &App,
157 ) -> impl Iterator<Item = (RelatedFile, Entity<Buffer>)> {
158 self.related_buffers
159 .iter_mut()
160 .map(|related| (related.related_file(cx), related.buffer.clone()))
161 }
162
163 pub fn set_related_files(&mut self, files: Vec<RelatedFile>, cx: &App) {
164 self.related_buffers = files
165 .into_iter()
166 .filter_map(|file| {
167 let project = self.project.upgrade()?;
168 let project = project.read(cx);
169 let worktree = project.worktrees(cx).find(|wt| {
170 let root_name = wt.read(cx).root_name().as_unix_str();
171 file.path
172 .components()
173 .next()
174 .is_some_and(|c| c.as_os_str() == root_name)
175 })?;
176 let worktree = worktree.read(cx);
177 let relative_path = file
178 .path
179 .strip_prefix(worktree.root_name().as_unix_str())
180 .ok()?;
181 let relative_path = RelPath::new(relative_path, PathStyle::Unix).ok()?;
182 let project_path = ProjectPath {
183 worktree_id: worktree.id(),
184 path: relative_path.into_owned().into(),
185 };
186 let buffer = project.get_open_buffer(&project_path, cx)?;
187 let snapshot = buffer.read(cx).snapshot();
188 let mut anchor_ranges = Vec::with_capacity(file.excerpts.len());
189 let mut excerpt_orders = Vec::with_capacity(file.excerpts.len());
190 for excerpt in &file.excerpts {
191 let start = snapshot.anchor_before(Point::new(excerpt.row_range.start, 0));
192 let end_row = excerpt.row_range.end;
193 let end_col = snapshot.line_len(end_row);
194 let end = snapshot.anchor_after(Point::new(end_row, end_col));
195 anchor_ranges.push(start..end);
196 excerpt_orders.push(excerpt.order);
197 }
198 Some(RelatedBuffer {
199 buffer,
200 path: file.path.clone(),
201 anchor_ranges,
202 excerpt_orders,
203 cached_file: None,
204 })
205 })
206 .collect();
207 }
208
209 async fn fetch_excerpts(
210 this: WeakEntity<Self>,
211 buffer: Entity<Buffer>,
212 position: Anchor,
213 cx: &mut AsyncApp,
214 ) -> Result<()> {
215 let (project, snapshot, identifier_line_count) = this.read_with(cx, |this, cx| {
216 (
217 this.project.upgrade(),
218 buffer.read(cx).snapshot(),
219 this.identifier_line_count,
220 )
221 })?;
222 let Some(project) = project else {
223 return Ok(());
224 };
225
226 let file = snapshot.file().cloned();
227 let file_extension = file
228 .as_ref()
229 .and_then(|file| file.path().extension())
230 .unwrap_or("")
231 .to_string();
232 if let Some(file) = &file {
233 log::debug!("retrieving_context buffer:{}", file.path().as_unix_str());
234 }
235 let (lsp_store, is_via_ssh) = project.read_with(cx, |project, _| {
236 (project.lsp_store(), project.is_via_remote_server())
237 });
238 let lsp_names = lsp_store.update(cx, |lsp_store, cx| {
239 buffer.update(cx, |buffer, cx| {
240 lsp_store
241 .running_language_servers_for_local_buffer(buffer, cx)
242 .map(|(_, server)| server.name().to_string())
243 .collect::<Vec<_>>()
244 })
245 });
246
247 this.update(cx, |_, cx| {
248 cx.emit(RelatedExcerptStoreEvent::StartedRefresh);
249 })?;
250
251 let identifiers_with_ranks = cx
252 .background_spawn(async move {
253 let cursor_offset = position.to_offset(&snapshot);
254 let identifiers =
255 identifiers_for_position(&snapshot, position, identifier_line_count);
256
257 // Compute byte distance from cursor to each identifier, then sort by
258 // distance so we can assign ordinal ranks. Identifiers at the same
259 // distance share the same rank.
260 let mut identifiers_with_distance: Vec<(Identifier, usize)> = identifiers
261 .into_iter()
262 .map(|id| {
263 let start = id.range.start.to_offset(&snapshot);
264 let end = id.range.end.to_offset(&snapshot);
265 let distance = if cursor_offset < start {
266 start - cursor_offset
267 } else if cursor_offset > end {
268 cursor_offset - end
269 } else {
270 0
271 };
272 (id, distance)
273 })
274 .collect();
275 // Only the closest `MAX_CONTEXT_IDENTIFIER_COUNT` identifiers are
276 // used below, so select that prefix instead of fully sorting.
277 util::truncate_to_bottom_n_sorted_by(
278 &mut identifiers_with_distance,
279 MAX_CONTEXT_IDENTIFIER_COUNT,
280 &|(_, a), (_, b)| a.cmp(b),
281 );
282
283 let mut cursor_distances: HashMap<Identifier, usize> = HashMap::default();
284 let mut current_rank = 0;
285 let mut previous_distance = None;
286 for (identifier, distance) in &identifiers_with_distance {
287 if previous_distance != Some(*distance) {
288 current_rank = cursor_distances.len();
289 previous_distance = Some(*distance);
290 }
291 cursor_distances.insert(identifier.clone(), current_rank);
292 }
293
294 (identifiers_with_distance, cursor_distances)
295 })
296 .await;
297
298 let (identifiers_with_distance, cursor_distances) = identifiers_with_ranks;
299
300 let async_cx = cx.clone();
301 let start_time = Instant::now();
302 let futures = this.update(cx, |this, cx| {
303 identifiers_with_distance
304 .into_iter()
305 .map(|(identifier, _)| {
306 let task = if let Some(entry) = this.cache.get(&identifier) {
307 DefinitionTask::CacheHit(entry.clone())
308 } else {
309 let project = this.project.clone();
310 let task = project
311 .update(cx, |project, cx| {
312 // tombi LSP for toml will open a scratch buffer with the JSON schema of
313 // the toml file when a goto type definition is requested
314 let include_type_definitions =
315 !is_tombi_lsp_in_toml(project, &buffer, cx);
316 project.edit_prediction_definitions(
317 &buffer,
318 identifier.range.start,
319 include_type_definitions,
320 cx,
321 )
322 })
323 .unwrap_or_else(|_| Task::ready(Ok(Vec::new())));
324 DefinitionTask::CacheMiss { project, task }
325 };
326
327 let cx = async_cx.clone();
328 async move {
329 match task {
330 DefinitionTask::CacheHit(cache_entry) => {
331 Some((identifier, cache_entry, None))
332 }
333 DefinitionTask::CacheMiss { project, task } => {
334 let definition_locations = task.await.log_err().unwrap_or_default();
335 let duration = start_time.elapsed();
336
337 let definitions: SmallVec<[CachedDefinition; 1]> =
338 future::join_all(definition_locations.into_iter().map(
339 |definition| {
340 let project = project.clone();
341 let mut cx = cx.clone();
342 async move {
343 process_definition(definition, &project, &mut cx)
344 .await
345 }
346 },
347 ))
348 .await
349 .into_iter()
350 .flatten()
351 .collect();
352
353 Some((
354 identifier,
355 Arc::new(CacheEntry { definitions }),
356 Some(duration),
357 ))
358 }
359 }
360 }
361 })
362 .collect::<Vec<_>>()
363 })?;
364
365 let mut cache_hit_count = 0;
366 let mut cache_miss_count = 0;
367 let mut mean_definition_latency = Duration::ZERO;
368 let mut max_definition_latency = Duration::ZERO;
369 let mut new_cache = HashMap::default();
370 new_cache.reserve(futures.len());
371 for (identifier, entry, duration) in future::join_all(futures).await.into_iter().flatten() {
372 new_cache.insert(identifier, entry);
373 if let Some(duration) = duration {
374 cache_miss_count += 1;
375 mean_definition_latency += duration;
376 max_definition_latency = max_definition_latency.max(duration);
377 } else {
378 cache_hit_count += 1;
379 }
380 }
381 let lsp_fetch_latency_ms = start_time.elapsed().as_millis();
382 mean_definition_latency /= cache_miss_count.max(1) as u32;
383
384 let (new_cache, related_buffers) =
385 rebuild_related_files(&project, new_cache, &cursor_distances, cx).await?;
386 let latency_ms = start_time.elapsed().as_millis();
387 let returned_excerpt_count = related_buffers
388 .iter()
389 .map(|related_buffer| related_buffer.anchor_ranges.len())
390 .sum::<usize>();
391 telemetry::event!(
392 "Edit Prediction LSP Context Retrieved",
393 lsp_names,
394 file_extension,
395 latency_ms,
396 lsp_fetch_latency_ms,
397 returned_excerpt_count,
398 is_via_ssh
399 );
400
401 if let Some(file) = &file {
402 log::debug!(
403 "finished retrieving context buffer:{}, latency:{:?}",
404 file.path().as_unix_str(),
405 start_time.elapsed()
406 );
407 }
408
409 this.update(cx, |this, cx| {
410 this.cache = new_cache;
411 this.related_buffers = related_buffers;
412 cx.emit(RelatedExcerptStoreEvent::FinishedRefresh {
413 cache_hit_count,
414 cache_miss_count,
415 mean_definition_latency,
416 max_definition_latency,
417 });
418 })?;
419
420 anyhow::Ok(())
421 }
422}
423
424async fn rebuild_related_files(
425 project: &Entity<Project>,
426 mut new_entries: HashMap<Identifier, Arc<CacheEntry>>,
427 cursor_distances: &HashMap<Identifier, usize>,
428 cx: &mut AsyncApp,
429) -> Result<(HashMap<Identifier, Arc<CacheEntry>>, Vec<RelatedBuffer>)> {
430 let mut snapshots = HashMap::default();
431 let mut worktree_root_names = HashMap::default();
432 for entry in new_entries.values() {
433 for definition in entry.definitions.iter() {
434 if let hash_map::Entry::Vacant(e) = snapshots.entry(definition.buffer.entity_id()) {
435 definition
436 .buffer
437 .read_with(cx, |buffer, _| buffer.parsing_idle())
438 .await;
439 e.insert(
440 definition
441 .buffer
442 .read_with(cx, |buffer, _| buffer.snapshot()),
443 );
444 }
445 let worktree_id = definition.path.worktree_id;
446 if let hash_map::Entry::Vacant(e) =
447 worktree_root_names.entry(definition.path.worktree_id)
448 {
449 project.read_with(cx, |project, cx| {
450 if let Some(worktree) = project.worktree_for_id(worktree_id, cx) {
451 e.insert(worktree.read(cx).root_name().as_unix_str().to_string());
452 }
453 });
454 }
455 }
456 }
457
458 let cursor_distances = cursor_distances.clone();
459 Ok(cx
460 .background_spawn(async move {
461 let mut ranges_by_buffer =
462 HashMap::<EntityId, (Entity<Buffer>, Vec<(Range<Point>, usize)>)>::default();
463 let mut paths_by_buffer = HashMap::default();
464 let mut min_rank_by_buffer = HashMap::<EntityId, usize>::default();
465 for (identifier, entry) in new_entries.iter_mut() {
466 let rank = cursor_distances
467 .get(identifier)
468 .copied()
469 .unwrap_or(usize::MAX);
470 for definition in entry.definitions.iter() {
471 let Some(snapshot) = snapshots.get(&definition.buffer.entity_id()) else {
472 continue;
473 };
474 paths_by_buffer.insert(definition.buffer.entity_id(), definition.path.clone());
475
476 let buffer_rank = min_rank_by_buffer
477 .entry(definition.buffer.entity_id())
478 .or_insert(usize::MAX);
479 *buffer_rank = (*buffer_rank).min(rank);
480
481 ranges_by_buffer
482 .entry(definition.buffer.entity_id())
483 .or_insert_with(|| (definition.buffer.clone(), Vec::new()))
484 .1
485 .push((definition.anchor_range.to_point(snapshot), rank));
486 }
487 }
488
489 let mut related_buffers: Vec<RelatedBuffer> = ranges_by_buffer
490 .into_iter()
491 .filter_map(|(entity_id, (buffer, ranges))| {
492 let snapshot = snapshots.get(&entity_id)?;
493 let project_path = paths_by_buffer.get(&entity_id)?;
494 let assembled = assemble_excerpt_ranges(snapshot, ranges);
495 let root_name = worktree_root_names.get(&project_path.worktree_id)?;
496
497 let path: Arc<Path> = Path::new(&format!(
498 "{}/{}",
499 root_name,
500 project_path.path.as_unix_str()
501 ))
502 .into();
503
504 let mut anchor_ranges = Vec::with_capacity(assembled.len());
505 let mut excerpt_orders = Vec::with_capacity(assembled.len());
506 for (row_range, order) in assembled {
507 let start = snapshot.anchor_before(Point::new(row_range.start, 0));
508 let end_col = snapshot.line_len(row_range.end);
509 let end = snapshot.anchor_after(Point::new(row_range.end, end_col));
510 anchor_ranges.push(start..end);
511 excerpt_orders.push(order);
512 }
513
514 let mut related_buffer = RelatedBuffer {
515 buffer,
516 path,
517 anchor_ranges,
518 excerpt_orders,
519 cached_file: None,
520 };
521 related_buffer.fill_cache(snapshot);
522 Some(related_buffer)
523 })
524 .collect();
525
526 related_buffers.sort_by(|a, b| {
527 let rank_a = min_rank_by_buffer
528 .get(&a.buffer.entity_id())
529 .copied()
530 .unwrap_or(usize::MAX);
531 let rank_b = min_rank_by_buffer
532 .get(&b.buffer.entity_id())
533 .copied()
534 .unwrap_or(usize::MAX);
535 rank_a.cmp(&rank_b).then_with(|| a.path.cmp(&b.path))
536 });
537
538 (new_entries, related_buffers)
539 })
540 .await)
541}
542
543impl RelatedBuffer {
544 fn related_file(&mut self, cx: &App) -> RelatedFile {
545 let buffer = self.buffer.read(cx);
546 let path = self.path.clone();
547 let cached = if let Some(cached) = &self.cached_file
548 && buffer.version() == cached.buffer_version
549 {
550 cached
551 } else {
552 self.fill_cache(buffer)
553 };
554 let related_file = RelatedFile {
555 path,
556 excerpts: cached.excerpts.clone(),
557 max_row: buffer.max_point().row,
558 in_open_source_repo: false,
559 };
560 return related_file;
561 }
562
563 fn fill_cache(&mut self, buffer: &text::BufferSnapshot) -> &CachedRelatedFile {
564 let excerpts = self
565 .anchor_ranges
566 .iter()
567 .zip(self.excerpt_orders.iter())
568 .map(|(range, &order)| {
569 let start = range.start.to_point(buffer);
570 let end = range.end.to_point(buffer);
571 RelatedExcerpt {
572 row_range: start.row..end.row,
573 text: buffer.text_for_range(start..end).collect::<String>().into(),
574 order,
575 context_source: ContextSource::Lsp,
576 }
577 })
578 .collect::<Vec<_>>();
579 self.cached_file = Some(CachedRelatedFile {
580 excerpts,
581 buffer_version: buffer.version().clone(),
582 });
583 self.cached_file.as_ref().unwrap()
584 }
585}
586
587use language::ToPoint as _;
588
589const MAX_TARGET_LEN: usize = 128;
590
591async fn process_definition(
592 definition: EditPredictionDefinition,
593 project: &WeakEntity<Project>,
594 cx: &mut AsyncApp,
595) -> Option<CachedDefinition> {
596 let EditPredictionDefinition { path, range } = definition;
597 let buffer = project
598 .update(cx, |project, cx| project.open_buffer(path.clone(), cx))
599 .ok()?
600 .await
601 .log_err()?;
602
603 cx.update(|cx| {
604 let buffer_snapshot = buffer.read(cx);
605 let target_start = buffer_snapshot.clip_point_utf16(range.start, Bias::Left);
606 let target_end = buffer_snapshot.clip_point_utf16(range.end, Bias::Left);
607 let anchor_range =
608 buffer_snapshot.anchor_after(target_start)..buffer_snapshot.anchor_before(target_end);
609
610 // If the target range is large, it likely means we requested the definition of an entire module.
611 // For individual definitions, the target range should be small as it only covers the symbol.
612 let target_len = anchor_range.to_offset(&buffer_snapshot).len();
613 if target_len > MAX_TARGET_LEN {
614 return None;
615 }
616
617 Some(CachedDefinition {
618 path,
619 buffer: buffer.clone(),
620 anchor_range,
621 })
622 })
623}
624
625/// Gets all of the identifiers that are present in the given line, and its containing
626/// outline items.
627fn identifiers_for_position(
628 buffer: &BufferSnapshot,
629 position: Anchor,
630 identifier_line_count: u32,
631) -> Vec<Identifier> {
632 let offset = position.to_offset(buffer);
633 let point = buffer.offset_to_point(offset);
634
635 // Search for identifiers on lines adjacent to the cursor.
636 let start = Point::new(point.row.saturating_sub(identifier_line_count), 0);
637 let end = Point::new(point.row + identifier_line_count + 1, 0).min(buffer.max_point());
638 let line_range = start..end;
639 let mut ranges = vec![line_range.to_offset(&buffer)];
640
641 // Search for identifiers mentioned in headers/signatures of containing outline items.
642 let outline_items = buffer.outline_items_as_offsets_containing(offset..offset, false, None);
643 for item in outline_items {
644 if let Some(body_range) = item.body_range(&buffer) {
645 ranges.push(item.range.start..body_range.start.to_offset(&buffer));
646 } else {
647 ranges.push(item.range.clone());
648 }
649 }
650
651 ranges.sort_by(|a, b| a.start.cmp(&b.start).then(b.end.cmp(&a.end)));
652 ranges.dedup_by(|a, b| {
653 if a.start <= b.end {
654 b.start = b.start.min(a.start);
655 b.end = b.end.max(a.end);
656 true
657 } else {
658 false
659 }
660 });
661
662 let mut identifiers = Vec::new();
663 let outer_range =
664 ranges.first().map_or(0, |r| r.start)..ranges.last().map_or(buffer.len(), |r| r.end);
665
666 let mut captures = buffer.captures(outer_range.clone(), |grammar| {
667 grammar
668 .highlights_config
669 .as_ref()
670 .map(|config| &config.query)
671 });
672
673 for range in ranges {
674 captures.set_byte_range(range.start..outer_range.end);
675
676 let mut last_range = None;
677 while let Some(capture) = captures.peek() {
678 let node_range = capture.node.byte_range();
679 if node_range.start > range.end {
680 break;
681 }
682 let config = captures.grammars()[capture.grammar_index]
683 .highlights_config
684 .as_ref();
685
686 if let Some(config) = config
687 && config.identifier_capture_indices.contains(&capture.index)
688 && range.contains_inclusive(&node_range)
689 && !is_tsx_tag(&buffer, &capture.node)
690 && Some(&node_range) != last_range.as_ref()
691 {
692 let name = buffer.text_for_range(node_range.clone()).collect();
693 identifiers.push(Identifier {
694 range: buffer.anchor_after(node_range.start)
695 ..buffer.anchor_before(node_range.end),
696 name,
697 });
698 last_range = Some(node_range);
699 }
700
701 captures.advance();
702 }
703 }
704
705 identifiers
706}
707
708fn is_tsx_tag(buffer: &BufferSnapshot, node: &tree_sitter::Node) -> bool {
709 let Some(language_config) = buffer
710 .language()
711 .and_then(|l| l.config().jsx_tag_auto_close.as_ref())
712 else {
713 return false;
714 };
715 let Some(parent_kind) = node.parent().map(|n| n.kind()) else {
716 return false;
717 };
718
719 if parent_kind != &language_config.open_tag_node_name
720 && parent_kind != &language_config.close_tag_node_name
721 && parent_kind != &language_config.tag_name_node_name
722 && language_config
723 .erroneous_close_tag_name_node_name
724 .as_ref()
725 .is_some_and(|kind| parent_kind != kind)
726 && language_config
727 .erroneous_close_tag_node_name
728 .as_ref()
729 .is_some_and(|kind| parent_kind == kind)
730 && parent_kind != &language_config.jsx_element_node_name
731 {
732 return false;
733 }
734 // do fetch `<Component />`, model probably understands `<div>`, but needs info for user defined components
735 if !buffer
736 .text_for_range(node.byte_range())
737 .all(|str| str.chars().all(|c| c.is_lowercase()))
738 {
739 return false;
740 }
741 true
742}
743
744fn is_tombi_lsp_in_toml(
745 project: &Project,
746 buffer: &Entity<Buffer>,
747 cx: &mut Context<Project>,
748) -> bool {
749 buffer.update(cx, |buffer, cx| {
750 if !buffer.language().is_some_and(|lang| lang.name() == "TOML") {
751 return false;
752 }
753 project.lsp_store().update(cx, |lsp_store, cx| {
754 for (_, lsp) in lsp_store.running_language_servers_for_local_buffer(buffer, cx) {
755 if "tombi".eq_ignore_ascii_case(lsp.name().as_ref()) {
756 return true;
757 }
758 }
759 false
760 })
761 })
762}
763