Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:03:46.257Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

edit_prediction_context.rs

763 lines · 28.7 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.