logiguard fork: GPUI xdg-activation keyboard-focus serial fix
Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled

Single-commit orphan branch: full zed-industries/zed @ 8c74db0 source tree
with a 3-file patch applied (no upstream history).

Patch (crates/gpui_linux/src/linux/wayland/):
  - serial.rs: add SerialKind::KeyboardEnter
  - client.rs: store wl_keyboard.enter serial; add latest_serial_of()
  - window.rs: activate() uses keyboard-enter serial (Mutter focus gate)

Mutter honors window activation only when the token carries the keyboard-
focus serial from wl_keyboard.enter; GPUI used a stale mouse-press serial.
See docs/tray-window-focus-wayland.md in logiguard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mohamad Khani
2026-07-14 02:22:17 +03:30
commit b72a46db68
3984 changed files with 1583326 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
[package]
name = "edit_prediction_context"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/edit_prediction_context.rs"
[dependencies]
anyhow.workspace = true
clock.workspace = true
collections.workspace = true
futures.workspace = true
gpui.workspace = true
language.workspace = true
log.workspace = true
lsp.workspace = true
parking_lot.workspace = true
project.workspace = true
serde.workspace = true
smallvec.workspace = true
text.workspace = true
tree-sitter.workspace = true
util.workspace = true
zeta_prompt.workspace = true
[dev-dependencies]
env_logger.workspace = true
indoc.workspace = true
futures.workspace = true
gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
lsp = { workspace = true, features = ["test-support"] }
pretty_assertions.workspace = true
project = {workspace= true, features = ["test-support"]}
serde_json.workspace = true
settings = {workspace= true, features = ["test-support"]}
text = { workspace = true, features = ["test-support"] }
util = { workspace = true, features = ["test-support"] }

View File

@@ -0,0 +1 @@
../../LICENSE-GPL

View File

@@ -0,0 +1,158 @@
use language::{BufferSnapshot, OffsetRangeExt as _, Point};
use std::ops::Range;
#[cfg(not(test))]
const MAX_OUTLINE_ITEM_BODY_SIZE: usize = 512;
#[cfg(test)]
const MAX_OUTLINE_ITEM_BODY_SIZE: usize = 24;
pub fn assemble_excerpt_ranges(
buffer: &BufferSnapshot,
input_ranges: Vec<(Range<Point>, usize)>,
) -> Vec<(Range<u32>, usize)> {
let mut input_ranges: Vec<(Range<Point>, usize)> = input_ranges
.into_iter()
.map(|(range, order)| (clip_range_to_lines(&range, false, buffer), order))
.collect();
merge_ranges(&mut input_ranges);
let mut outline_ranges: Vec<(Range<Point>, usize)> = Vec::new();
let outline_items = buffer.outline_items_as_points_containing(0..buffer.len(), false, None);
let mut outline_ix = 0;
for (input_range, input_order) in &mut input_ranges {
while let Some(outline_item) = outline_items.get(outline_ix) {
let item_range = clip_range_to_lines(&outline_item.range, false, buffer);
if item_range.start > input_range.start {
break;
}
if item_range.end > input_range.start {
let body_range = outline_item
.body_range(buffer)
.map(|body| clip_range_to_lines(&body, true, buffer))
.filter(|body_range| {
body_range.to_offset(buffer).len() > MAX_OUTLINE_ITEM_BODY_SIZE
});
add_outline_item(
item_range.clone(),
body_range.clone(),
*input_order,
buffer,
&mut outline_ranges,
);
if let Some(body_range) = body_range
&& input_range.start < body_range.start
{
let mut child_outline_ix = outline_ix + 1;
while let Some(next_outline_item) = outline_items.get(child_outline_ix) {
if next_outline_item.range.end > body_range.end {
break;
}
if next_outline_item.depth == outline_item.depth + 1 {
let next_item_range =
clip_range_to_lines(&next_outline_item.range, false, buffer);
add_outline_item(
next_item_range,
next_outline_item
.body_range(buffer)
.map(|body| clip_range_to_lines(&body, true, buffer)),
*input_order,
buffer,
&mut outline_ranges,
);
}
child_outline_ix += 1;
}
}
}
outline_ix += 1;
}
}
input_ranges.extend(outline_ranges);
merge_ranges(&mut input_ranges);
input_ranges
.into_iter()
.map(|(range, order)| (range.start.row..range.end.row, order))
.collect()
}
fn clip_range_to_lines(
range: &Range<Point>,
inward: bool,
buffer: &BufferSnapshot,
) -> Range<Point> {
let mut range = range.clone();
if inward {
if range.start.column > 0 {
range.start.column = buffer.line_len(range.start.row);
}
range.end.column = 0;
} else {
range.start.column = 0;
if range.end.column > 0 {
range.end.column = buffer.line_len(range.end.row);
}
}
range
}
fn add_outline_item(
mut item_range: Range<Point>,
body_range: Option<Range<Point>>,
order: usize,
buffer: &BufferSnapshot,
outline_ranges: &mut Vec<(Range<Point>, usize)>,
) {
if let Some(mut body_range) = body_range {
if body_range.start.column > 0 {
body_range.start.column = buffer.line_len(body_range.start.row);
}
body_range.end.column = 0;
let head_range = item_range.start..body_range.start;
if head_range.start < head_range.end {
outline_ranges.push((head_range, order));
}
let tail_range = body_range.end..item_range.end;
if tail_range.start < tail_range.end {
outline_ranges.push((tail_range, order));
}
} else {
item_range.start.column = 0;
item_range.end.column = buffer.line_len(item_range.end.row);
outline_ranges.push((item_range, order));
}
}
pub fn merge_ranges(ranges: &mut Vec<(Range<Point>, usize)>) {
ranges.sort_unstable_by(|(a, _), (b, _)| a.start.cmp(&b.start).then(b.end.cmp(&a.end)));
let mut index = 1;
while index < ranges.len() {
let mut prev_range_end = ranges[index - 1].0.end;
if prev_range_end.column > 0 {
prev_range_end += Point::new(1, 0);
}
if (prev_range_end + Point::new(1, 0))
.cmp(&ranges[index].0.start)
.is_ge()
{
let removed = ranges.remove(index);
if removed.0.end.cmp(&ranges[index - 1].0.end).is_gt() {
ranges[index - 1].0.end = removed.0.end;
}
ranges[index - 1].1 = ranges[index - 1].1.min(removed.1);
} else {
index += 1;
}
}
}

View File

@@ -0,0 +1,752 @@
use crate::assemble_excerpts::assemble_excerpt_ranges;
use anyhow::Result;
use collections::HashMap;
use futures::{FutureExt, StreamExt as _, channel::mpsc, future};
use gpui::{
App, AppContext, AsyncApp, Context, Entity, EntityId, EventEmitter, Task, TaskExt, WeakEntity,
};
use language::{Anchor, Buffer, BufferSnapshot, OffsetRangeExt as _, Point, ToOffset as _};
use project::{LocationLink, Project, ProjectPath};
use smallvec::SmallVec;
use std::{
collections::hash_map,
ops::Range,
path::Path,
sync::Arc,
time::{Duration, Instant},
};
use util::paths::PathStyle;
use util::rel_path::RelPath;
use util::{RangeExt as _, ResultExt};
mod assemble_excerpts;
#[cfg(test)]
mod edit_prediction_context_tests;
#[cfg(test)]
mod fake_definition_lsp;
pub use zeta_prompt::{RelatedExcerpt, RelatedFile};
const IDENTIFIER_LINE_COUNT: u32 = 3;
pub struct RelatedExcerptStore {
project: WeakEntity<Project>,
related_buffers: Vec<RelatedBuffer>,
cache: HashMap<Identifier, Arc<CacheEntry>>,
update_tx: mpsc::UnboundedSender<(Entity<Buffer>, Anchor)>,
identifier_line_count: u32,
}
struct RelatedBuffer {
buffer: Entity<Buffer>,
path: Arc<Path>,
anchor_ranges: Vec<Range<Anchor>>,
excerpt_orders: Vec<usize>,
cached_file: Option<CachedRelatedFile>,
}
struct CachedRelatedFile {
excerpts: Vec<RelatedExcerpt>,
buffer_version: clock::Global,
}
pub enum RelatedExcerptStoreEvent {
StartedRefresh,
FinishedRefresh {
cache_hit_count: usize,
cache_miss_count: usize,
mean_definition_latency: Duration,
max_definition_latency: Duration,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
struct Identifier {
pub name: String,
pub range: Range<Anchor>,
}
enum DefinitionTask {
CacheHit(Arc<CacheEntry>),
CacheMiss(
Task<
Option<(
Task<Result<Option<Vec<LocationLink>>>>,
Task<Result<Option<Vec<LocationLink>>>>,
)>,
>,
),
}
#[derive(Debug)]
struct CacheEntry {
definitions: SmallVec<[CachedDefinition; 1]>,
type_definitions: SmallVec<[CachedDefinition; 1]>,
}
#[derive(Clone, Debug)]
struct CachedDefinition {
path: ProjectPath,
buffer: Entity<Buffer>,
anchor_range: Range<Anchor>,
}
const DEBOUNCE_DURATION: Duration = Duration::from_millis(100);
impl EventEmitter<RelatedExcerptStoreEvent> for RelatedExcerptStore {}
impl RelatedExcerptStore {
pub fn new(project: &Entity<Project>, cx: &mut Context<Self>) -> Self {
let (update_tx, mut update_rx) = mpsc::unbounded::<(Entity<Buffer>, Anchor)>();
cx.spawn(async move |this, cx| {
let executor = cx.background_executor().clone();
while let Some((mut buffer, mut position)) = update_rx.next().await {
let mut timer = executor.timer(DEBOUNCE_DURATION).fuse();
loop {
futures::select_biased! {
next = update_rx.next() => {
if let Some((new_buffer, new_position)) = next {
buffer = new_buffer;
position = new_position;
timer = executor.timer(DEBOUNCE_DURATION).fuse();
} else {
return anyhow::Ok(());
}
}
_ = timer => break,
}
}
Self::fetch_excerpts(this.clone(), buffer, position, cx).await?;
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
RelatedExcerptStore {
project: project.downgrade(),
update_tx,
related_buffers: Vec::new(),
cache: Default::default(),
identifier_line_count: IDENTIFIER_LINE_COUNT,
}
}
pub fn set_identifier_line_count(&mut self, count: u32) {
self.identifier_line_count = count;
}
pub fn refresh(&mut self, buffer: Entity<Buffer>, position: Anchor, _: &mut Context<Self>) {
self.update_tx.unbounded_send((buffer, position)).ok();
}
pub fn related_files(&mut self, cx: &App) -> Vec<RelatedFile> {
self.related_buffers
.iter_mut()
.map(|related| related.related_file(cx))
.collect()
}
pub fn related_files_with_buffers(
&mut self,
cx: &App,
) -> impl Iterator<Item = (RelatedFile, Entity<Buffer>)> {
self.related_buffers
.iter_mut()
.map(|related| (related.related_file(cx), related.buffer.clone()))
}
pub fn set_related_files(&mut self, files: Vec<RelatedFile>, cx: &App) {
self.related_buffers = files
.into_iter()
.filter_map(|file| {
let project = self.project.upgrade()?;
let project = project.read(cx);
let worktree = project.worktrees(cx).find(|wt| {
let root_name = wt.read(cx).root_name().as_unix_str();
file.path
.components()
.next()
.is_some_and(|c| c.as_os_str() == root_name)
})?;
let worktree = worktree.read(cx);
let relative_path = file
.path
.strip_prefix(worktree.root_name().as_unix_str())
.ok()?;
let relative_path = RelPath::new(relative_path, PathStyle::Posix).ok()?;
let project_path = ProjectPath {
worktree_id: worktree.id(),
path: relative_path.into_owned().into(),
};
let buffer = project.get_open_buffer(&project_path, cx)?;
let snapshot = buffer.read(cx).snapshot();
let mut anchor_ranges = Vec::with_capacity(file.excerpts.len());
let mut excerpt_orders = Vec::with_capacity(file.excerpts.len());
for excerpt in &file.excerpts {
let start = snapshot.anchor_before(Point::new(excerpt.row_range.start, 0));
let end_row = excerpt.row_range.end;
let end_col = snapshot.line_len(end_row);
let end = snapshot.anchor_after(Point::new(end_row, end_col));
anchor_ranges.push(start..end);
excerpt_orders.push(excerpt.order);
}
Some(RelatedBuffer {
buffer,
path: file.path.clone(),
anchor_ranges,
excerpt_orders,
cached_file: None,
})
})
.collect();
}
async fn fetch_excerpts(
this: WeakEntity<Self>,
buffer: Entity<Buffer>,
position: Anchor,
cx: &mut AsyncApp,
) -> Result<()> {
let (project, snapshot, identifier_line_count) = this.read_with(cx, |this, cx| {
(
this.project.upgrade(),
buffer.read(cx).snapshot(),
this.identifier_line_count,
)
})?;
let Some(project) = project else {
return Ok(());
};
let file = snapshot.file().cloned();
if let Some(file) = &file {
log::debug!("retrieving_context buffer:{}", file.path().as_unix_str());
}
this.update(cx, |_, cx| {
cx.emit(RelatedExcerptStoreEvent::StartedRefresh);
})?;
let identifiers_with_ranks = cx
.background_spawn(async move {
let cursor_offset = position.to_offset(&snapshot);
let identifiers =
identifiers_for_position(&snapshot, position, identifier_line_count);
// Compute byte distance from cursor to each identifier, then sort by
// distance so we can assign ordinal ranks. Identifiers at the same
// distance share the same rank.
let mut identifiers_with_distance: Vec<(Identifier, usize)> = identifiers
.into_iter()
.map(|id| {
let start = id.range.start.to_offset(&snapshot);
let end = id.range.end.to_offset(&snapshot);
let distance = if cursor_offset < start {
start - cursor_offset
} else if cursor_offset > end {
cursor_offset - end
} else {
0
};
(id, distance)
})
.collect();
identifiers_with_distance.sort_by_key(|(_, distance)| *distance);
let mut cursor_distances: HashMap<Identifier, usize> = HashMap::default();
let mut current_rank = 0;
let mut previous_distance = None;
for (identifier, distance) in &identifiers_with_distance {
if previous_distance != Some(*distance) {
current_rank = cursor_distances.len();
previous_distance = Some(*distance);
}
cursor_distances.insert(identifier.clone(), current_rank);
}
(identifiers_with_distance, cursor_distances)
})
.await;
let (identifiers_with_distance, cursor_distances) = identifiers_with_ranks;
let async_cx = cx.clone();
let start_time = Instant::now();
let futures = this.update(cx, |this, cx| {
identifiers_with_distance
.into_iter()
.map(|(identifier, _)| {
let task = if let Some(entry) = this.cache.get(&identifier) {
DefinitionTask::CacheHit(entry.clone())
} else {
let project = this.project.clone();
let buffer = buffer.downgrade();
DefinitionTask::CacheMiss(cx.spawn(async move |_, cx| {
let buffer = buffer.upgrade()?;
let definitions = project
.update(cx, |project, cx| {
project.definitions(&buffer, identifier.range.start, cx)
})
.ok()?;
let type_definitions = project
.update(cx, |project, cx| {
// tombi LSP for toml will open a scratch buffer with the JSON schema of
// the toml file when a goto type definition is requested
if is_tombi_lsp_in_toml(project, &buffer, cx) {
return Task::ready(Ok(None));
}
project.type_definitions(&buffer, identifier.range.start, cx)
})
.ok()?;
Some((definitions, type_definitions))
}))
};
let cx = async_cx.clone();
let project = project.clone();
async move {
match task {
DefinitionTask::CacheHit(cache_entry) => {
Some((identifier, cache_entry, None))
}
DefinitionTask::CacheMiss(task) => {
let (definitions, type_definitions) = task.await?;
let (definition_locations, type_definition_locations) =
futures::join!(definitions, type_definitions);
let duration = start_time.elapsed();
let definition_locations =
definition_locations.log_err().flatten().unwrap_or_default();
let type_definition_locations = type_definition_locations
.log_err()
.flatten()
.unwrap_or_default();
Some(cx.update(|cx| {
let definitions: SmallVec<[CachedDefinition; 1]> =
definition_locations
.into_iter()
.filter_map(|location| {
process_definition(location, &project, cx)
})
.collect();
let type_definitions: SmallVec<[CachedDefinition; 1]> =
type_definition_locations
.into_iter()
.filter_map(|location| {
process_definition(location, &project, cx)
})
.filter(|type_def| {
!definitions.iter().any(|def| {
def.buffer.entity_id()
== type_def.buffer.entity_id()
&& def.anchor_range == type_def.anchor_range
})
})
.collect();
(
identifier,
Arc::new(CacheEntry {
definitions,
type_definitions,
}),
Some(duration),
)
}))
}
}
}
})
.collect::<Vec<_>>()
})?;
let mut cache_hit_count = 0;
let mut cache_miss_count = 0;
let mut mean_definition_latency = Duration::ZERO;
let mut max_definition_latency = Duration::ZERO;
let mut new_cache = HashMap::default();
new_cache.reserve(futures.len());
for (identifier, entry, duration) in future::join_all(futures).await.into_iter().flatten() {
new_cache.insert(identifier, entry);
if let Some(duration) = duration {
cache_miss_count += 1;
mean_definition_latency += duration;
max_definition_latency = max_definition_latency.max(duration);
} else {
cache_hit_count += 1;
}
}
mean_definition_latency /= cache_miss_count.max(1) as u32;
let (new_cache, related_buffers) =
rebuild_related_files(&project, new_cache, &cursor_distances, cx).await?;
if let Some(file) = &file {
log::debug!(
"finished retrieving context buffer:{}, latency:{:?}",
file.path().as_unix_str(),
start_time.elapsed()
);
}
this.update(cx, |this, cx| {
this.cache = new_cache;
this.related_buffers = related_buffers;
cx.emit(RelatedExcerptStoreEvent::FinishedRefresh {
cache_hit_count,
cache_miss_count,
mean_definition_latency,
max_definition_latency,
});
})?;
anyhow::Ok(())
}
}
async fn rebuild_related_files(
project: &Entity<Project>,
mut new_entries: HashMap<Identifier, Arc<CacheEntry>>,
cursor_distances: &HashMap<Identifier, usize>,
cx: &mut AsyncApp,
) -> Result<(HashMap<Identifier, Arc<CacheEntry>>, Vec<RelatedBuffer>)> {
let mut snapshots = HashMap::default();
let mut worktree_root_names = HashMap::default();
for entry in new_entries.values() {
for definition in entry
.definitions
.iter()
.chain(entry.type_definitions.iter())
{
if let hash_map::Entry::Vacant(e) = snapshots.entry(definition.buffer.entity_id()) {
definition
.buffer
.read_with(cx, |buffer, _| buffer.parsing_idle())
.await;
e.insert(
definition
.buffer
.read_with(cx, |buffer, _| buffer.snapshot()),
);
}
let worktree_id = definition.path.worktree_id;
if let hash_map::Entry::Vacant(e) =
worktree_root_names.entry(definition.path.worktree_id)
{
project.read_with(cx, |project, cx| {
if let Some(worktree) = project.worktree_for_id(worktree_id, cx) {
e.insert(worktree.read(cx).root_name().as_unix_str().to_string());
}
});
}
}
}
let cursor_distances = cursor_distances.clone();
Ok(cx
.background_spawn(async move {
let mut ranges_by_buffer =
HashMap::<EntityId, (Entity<Buffer>, Vec<(Range<Point>, usize)>)>::default();
let mut paths_by_buffer = HashMap::default();
let mut min_rank_by_buffer = HashMap::<EntityId, usize>::default();
for (identifier, entry) in new_entries.iter_mut() {
let rank = cursor_distances
.get(identifier)
.copied()
.unwrap_or(usize::MAX);
for definition in entry
.definitions
.iter()
.chain(entry.type_definitions.iter())
{
let Some(snapshot) = snapshots.get(&definition.buffer.entity_id()) else {
continue;
};
paths_by_buffer.insert(definition.buffer.entity_id(), definition.path.clone());
let buffer_rank = min_rank_by_buffer
.entry(definition.buffer.entity_id())
.or_insert(usize::MAX);
*buffer_rank = (*buffer_rank).min(rank);
ranges_by_buffer
.entry(definition.buffer.entity_id())
.or_insert_with(|| (definition.buffer.clone(), Vec::new()))
.1
.push((definition.anchor_range.to_point(snapshot), rank));
}
}
let mut related_buffers: Vec<RelatedBuffer> = ranges_by_buffer
.into_iter()
.filter_map(|(entity_id, (buffer, ranges))| {
let snapshot = snapshots.get(&entity_id)?;
let project_path = paths_by_buffer.get(&entity_id)?;
let assembled = assemble_excerpt_ranges(snapshot, ranges);
let root_name = worktree_root_names.get(&project_path.worktree_id)?;
let path: Arc<Path> = Path::new(&format!(
"{}/{}",
root_name,
project_path.path.as_unix_str()
))
.into();
let mut anchor_ranges = Vec::with_capacity(assembled.len());
let mut excerpt_orders = Vec::with_capacity(assembled.len());
for (row_range, order) in assembled {
let start = snapshot.anchor_before(Point::new(row_range.start, 0));
let end_col = snapshot.line_len(row_range.end);
let end = snapshot.anchor_after(Point::new(row_range.end, end_col));
anchor_ranges.push(start..end);
excerpt_orders.push(order);
}
let mut related_buffer = RelatedBuffer {
buffer,
path,
anchor_ranges,
excerpt_orders,
cached_file: None,
};
related_buffer.fill_cache(snapshot);
Some(related_buffer)
})
.collect();
related_buffers.sort_by(|a, b| {
let rank_a = min_rank_by_buffer
.get(&a.buffer.entity_id())
.copied()
.unwrap_or(usize::MAX);
let rank_b = min_rank_by_buffer
.get(&b.buffer.entity_id())
.copied()
.unwrap_or(usize::MAX);
rank_a.cmp(&rank_b).then_with(|| a.path.cmp(&b.path))
});
(new_entries, related_buffers)
})
.await)
}
impl RelatedBuffer {
fn related_file(&mut self, cx: &App) -> RelatedFile {
let buffer = self.buffer.read(cx);
let path = self.path.clone();
let cached = if let Some(cached) = &self.cached_file
&& buffer.version() == cached.buffer_version
{
cached
} else {
self.fill_cache(buffer)
};
let related_file = RelatedFile {
path,
excerpts: cached.excerpts.clone(),
max_row: buffer.max_point().row,
in_open_source_repo: false,
};
return related_file;
}
fn fill_cache(&mut self, buffer: &text::BufferSnapshot) -> &CachedRelatedFile {
let excerpts = self
.anchor_ranges
.iter()
.zip(self.excerpt_orders.iter())
.map(|(range, &order)| {
let start = range.start.to_point(buffer);
let end = range.end.to_point(buffer);
RelatedExcerpt {
row_range: start.row..end.row,
text: buffer.text_for_range(start..end).collect::<String>().into(),
order,
}
})
.collect::<Vec<_>>();
self.cached_file = Some(CachedRelatedFile {
excerpts,
buffer_version: buffer.version().clone(),
});
self.cached_file.as_ref().unwrap()
}
}
use language::ToPoint as _;
const MAX_TARGET_LEN: usize = 128;
fn process_definition(
location: LocationLink,
project: &Entity<Project>,
cx: &mut App,
) -> Option<CachedDefinition> {
let buffer = location.target.buffer.read(cx);
let anchor_range = location.target.range;
let file = buffer.file()?;
let worktree = project.read(cx).worktree_for_id(file.worktree_id(cx), cx)?;
if worktree.read(cx).is_single_file() {
return None;
}
// If the target range is large, it likely means we requested the definition of an entire module.
// For individual definitions, the target range should be small as it only covers the symbol.
let buffer = location.target.buffer.read(cx);
let target_len = anchor_range.to_offset(&buffer).len();
if target_len > MAX_TARGET_LEN {
return None;
}
Some(CachedDefinition {
path: ProjectPath {
worktree_id: file.worktree_id(cx),
path: file.path().clone(),
},
buffer: location.target.buffer,
anchor_range,
})
}
/// Gets all of the identifiers that are present in the given line, and its containing
/// outline items.
fn identifiers_for_position(
buffer: &BufferSnapshot,
position: Anchor,
identifier_line_count: u32,
) -> Vec<Identifier> {
let offset = position.to_offset(buffer);
let point = buffer.offset_to_point(offset);
// Search for identifiers on lines adjacent to the cursor.
let start = Point::new(point.row.saturating_sub(identifier_line_count), 0);
let end = Point::new(point.row + identifier_line_count + 1, 0).min(buffer.max_point());
let line_range = start..end;
let mut ranges = vec![line_range.to_offset(&buffer)];
// Search for identifiers mentioned in headers/signatures of containing outline items.
let outline_items = buffer.outline_items_as_offsets_containing(offset..offset, false, None);
for item in outline_items {
if let Some(body_range) = item.body_range(&buffer) {
ranges.push(item.range.start..body_range.start.to_offset(&buffer));
} else {
ranges.push(item.range.clone());
}
}
ranges.sort_by(|a, b| a.start.cmp(&b.start).then(b.end.cmp(&a.end)));
ranges.dedup_by(|a, b| {
if a.start <= b.end {
b.start = b.start.min(a.start);
b.end = b.end.max(a.end);
true
} else {
false
}
});
let mut identifiers = Vec::new();
let outer_range =
ranges.first().map_or(0, |r| r.start)..ranges.last().map_or(buffer.len(), |r| r.end);
let mut captures = buffer.captures(outer_range.clone(), |grammar| {
grammar
.highlights_config
.as_ref()
.map(|config| &config.query)
});
for range in ranges {
captures.set_byte_range(range.start..outer_range.end);
let mut last_range = None;
while let Some(capture) = captures.peek() {
let node_range = capture.node.byte_range();
if node_range.start > range.end {
break;
}
let config = captures.grammars()[capture.grammar_index]
.highlights_config
.as_ref();
if let Some(config) = config
&& config.identifier_capture_indices.contains(&capture.index)
&& range.contains_inclusive(&node_range)
&& !is_tsx_tag(&buffer, &capture.node)
&& Some(&node_range) != last_range.as_ref()
{
let name = buffer.text_for_range(node_range.clone()).collect();
identifiers.push(Identifier {
range: buffer.anchor_after(node_range.start)
..buffer.anchor_before(node_range.end),
name,
});
last_range = Some(node_range);
}
captures.advance();
}
}
identifiers
}
fn is_tsx_tag(buffer: &BufferSnapshot, node: &tree_sitter::Node) -> bool {
let Some(language_config) = buffer
.language()
.and_then(|l| l.config().jsx_tag_auto_close.as_ref())
else {
return false;
};
let Some(parent_kind) = node.parent().map(|n| n.kind()) else {
return false;
};
if parent_kind != &language_config.open_tag_node_name
&& parent_kind != &language_config.close_tag_node_name
&& parent_kind != &language_config.tag_name_node_name
&& language_config
.erroneous_close_tag_name_node_name
.as_ref()
.is_some_and(|kind| parent_kind != kind)
&& language_config
.erroneous_close_tag_node_name
.as_ref()
.is_some_and(|kind| parent_kind == kind)
&& parent_kind != &language_config.jsx_element_node_name
{
return false;
}
// do fetch `<Component />`, model probably understands `<div>`, but needs info for user defined components
if !buffer
.text_for_range(node.byte_range())
.all(|str| str.chars().all(|c| c.is_lowercase()))
{
return false;
}
true
}
fn is_tombi_lsp_in_toml(
project: &Project,
buffer: &Entity<Buffer>,
cx: &mut Context<Project>,
) -> bool {
buffer.update(cx, |buffer, cx| {
if !buffer.language().is_some_and(|lang| lang.name() == "TOML") {
return false;
}
project.lsp_store().update(cx, |lsp_store, cx| {
for (_, lsp) in lsp_store.running_language_servers_for_local_buffer(buffer, cx) {
if "tombi".eq_ignore_ascii_case(lsp.name().as_ref()) {
return true;
}
}
false
})
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,471 @@
use collections::HashMap;
use futures::channel::mpsc::UnboundedReceiver;
use language::{Language, LanguageRegistry};
use lsp::{
FakeLanguageServer, LanguageServerBinary, TextDocumentSyncCapability, TextDocumentSyncKind, Uri,
};
use parking_lot::Mutex;
use project::Fs;
use std::{ops::Range, path::PathBuf, sync::Arc};
use tree_sitter::{Parser, QueryCursor, StreamingIterator, Tree};
/// Registers a fake language server that implements go-to-definition and
/// go-to-type-definition using tree-sitter, making the assumption that all
/// names are unique, and all variables' types are explicitly declared.
pub fn register_fake_definition_server(
language_registry: &Arc<LanguageRegistry>,
language: Arc<Language>,
fs: Arc<dyn Fs>,
) -> UnboundedReceiver<FakeLanguageServer> {
let index = Arc::new(Mutex::new(DefinitionIndex::new(language.clone())));
language_registry.register_fake_lsp(
language.name(),
language::FakeLspAdapter {
name: "fake-definition-lsp",
initialization_options: None,
prettier_plugins: Vec::new(),
disk_based_diagnostics_progress_token: None,
disk_based_diagnostics_sources: Vec::new(),
language_server_binary: LanguageServerBinary {
path: PathBuf::from("fake-definition-lsp"),
arguments: Vec::new(),
env: None,
},
capabilities: lsp::ServerCapabilities {
definition_provider: Some(lsp::OneOf::Left(true)),
type_definition_provider: Some(lsp::TypeDefinitionProviderCapability::Simple(true)),
text_document_sync: Some(TextDocumentSyncCapability::Kind(
TextDocumentSyncKind::FULL,
)),
..Default::default()
},
label_for_completion: None,
initializer: Some(Box::new({
move |server| {
server.handle_notification::<lsp::notification::DidOpenTextDocument, _>({
let index = index.clone();
move |params, _cx| {
index
.lock()
.open_buffer(params.text_document.uri, &params.text_document.text);
}
});
server.handle_notification::<lsp::notification::DidCloseTextDocument, _>({
let index = index.clone();
let fs = fs.clone();
move |params, cx| {
let uri = params.text_document.uri;
let path = uri.to_file_path().ok();
index.lock().mark_buffer_closed(&uri);
if let Some(path) = path {
let index = index.clone();
let fs = fs.clone();
cx.spawn(async move |_cx| {
if let Ok(content) = fs.load(&path).await {
index.lock().index_file(uri, &content);
}
})
.detach();
}
}
});
server.handle_notification::<lsp::notification::DidChangeWatchedFiles, _>({
let index = index.clone();
let fs = fs.clone();
move |params, cx| {
let index = index.clone();
let fs = fs.clone();
cx.spawn(async move |_cx| {
for event in params.changes {
if index.lock().is_buffer_open(&event.uri) {
continue;
}
match event.typ {
lsp::FileChangeType::DELETED => {
index.lock().remove_definitions_for_file(&event.uri);
}
lsp::FileChangeType::CREATED
| lsp::FileChangeType::CHANGED => {
if let Some(path) = event.uri.to_file_path().ok() {
if let Ok(content) = fs.load(&path).await {
index.lock().index_file(event.uri, &content);
}
}
}
_ => {}
}
}
})
.detach();
}
});
server.handle_notification::<lsp::notification::DidChangeTextDocument, _>({
let index = index.clone();
move |params, _cx| {
if let Some(change) = params.content_changes.into_iter().last() {
index
.lock()
.index_file(params.text_document.uri, &change.text);
}
}
});
server.handle_notification::<lsp::notification::DidChangeWorkspaceFolders, _>(
{
let index = index.clone();
let fs = fs.clone();
move |params, cx| {
let index = index.clone();
let fs = fs.clone();
let files = fs.as_fake().files();
cx.spawn(async move |_cx| {
for folder in params.event.added {
let Ok(path) = folder.uri.to_file_path() else {
continue;
};
for file in &files {
if let Some(uri) = Uri::from_file_path(&file).ok()
&& file.starts_with(&path)
&& let Ok(content) = fs.load(&file).await
{
index.lock().index_file(uri, &content);
}
}
}
})
.detach();
}
},
);
server.set_request_handler::<lsp::request::GotoDefinition, _, _>({
let index = index.clone();
move |params, _cx| {
let result = index.lock().get_definitions(
params.text_document_position_params.text_document.uri,
params.text_document_position_params.position,
);
async move { Ok(result) }
}
});
server.set_request_handler::<lsp::request::GotoTypeDefinition, _, _>({
let index = index.clone();
move |params, _cx| {
let result = index.lock().get_type_definitions(
params.text_document_position_params.text_document.uri,
params.text_document_position_params.position,
);
async move { Ok(result) }
}
});
}
})),
},
)
}
struct DefinitionIndex {
language: Arc<Language>,
definitions: HashMap<String, Vec<lsp::Location>>,
type_annotations_by_file: HashMap<Uri, HashMap<String, String>>,
files: HashMap<Uri, FileEntry>,
}
#[derive(Debug)]
struct FileEntry {
contents: String,
is_open_in_buffer: bool,
}
impl DefinitionIndex {
fn new(language: Arc<Language>) -> Self {
Self {
language,
definitions: HashMap::default(),
type_annotations_by_file: HashMap::default(),
files: HashMap::default(),
}
}
fn remove_definitions_for_file(&mut self, uri: &Uri) {
self.definitions.retain(|_, locations| {
locations.retain(|loc| &loc.uri != uri);
!locations.is_empty()
});
self.type_annotations_by_file.remove(uri);
self.files.remove(uri);
}
fn open_buffer(&mut self, uri: Uri, content: &str) {
self.index_file_inner(uri, content, true);
}
fn mark_buffer_closed(&mut self, uri: &Uri) {
if let Some(entry) = self.files.get_mut(uri) {
entry.is_open_in_buffer = false;
}
}
fn is_buffer_open(&self, uri: &Uri) -> bool {
self.files
.get(uri)
.map(|entry| entry.is_open_in_buffer)
.unwrap_or(false)
}
fn index_file(&mut self, uri: Uri, content: &str) {
self.index_file_inner(uri, content, false);
}
fn index_file_inner(&mut self, uri: Uri, content: &str, is_open_in_buffer: bool) -> Option<()> {
self.remove_definitions_for_file(&uri);
let grammar = self.language.grammar()?;
let outline_config = grammar.outline_config.as_ref()?;
let mut parser = Parser::new();
parser.set_language(&grammar.ts_language).ok()?;
let tree = parser.parse(content, None)?;
let declarations = extract_declarations_from_tree(&tree, content, outline_config);
for (name, byte_range) in declarations {
let range = byte_range_to_lsp_range(content, byte_range);
let location = lsp::Location {
uri: uri.clone(),
range,
};
self.definitions
.entry(name)
.or_insert_with(Vec::new)
.push(location);
}
let type_annotations = extract_type_annotations(content)
.into_iter()
.collect::<HashMap<_, _>>();
self.type_annotations_by_file
.insert(uri.clone(), type_annotations);
self.files.insert(
uri,
FileEntry {
contents: content.to_string(),
is_open_in_buffer,
},
);
Some(())
}
fn get_definitions(
&mut self,
uri: Uri,
position: lsp::Position,
) -> Option<lsp::GotoDefinitionResponse> {
let entry = self.files.get(&uri)?;
let name = word_at_position(&entry.contents, position)?;
let locations = self.definitions.get(name).cloned()?;
Some(lsp::GotoDefinitionResponse::Array(locations))
}
fn get_type_definitions(
&mut self,
uri: Uri,
position: lsp::Position,
) -> Option<lsp::GotoDefinitionResponse> {
let entry = self.files.get(&uri)?;
let name = word_at_position(&entry.contents, position)?;
if let Some(type_name) = self
.type_annotations_by_file
.get(&uri)
.and_then(|annotations| annotations.get(name))
{
if let Some(locations) = self.definitions.get(type_name) {
return Some(lsp::GotoDefinitionResponse::Array(locations.clone()));
}
}
// If the identifier itself is an uppercase name (a type), return its own definition.
// This mirrors real LSP behavior where GotoTypeDefinition on a type name
// resolves to that type's definition.
if name.starts_with(|c: char| c.is_uppercase()) {
if let Some(locations) = self.definitions.get(name) {
return Some(lsp::GotoDefinitionResponse::Array(locations.clone()));
}
}
None
}
}
/// Extracts `identifier_name -> type_name` mappings from field declarations
/// and function parameters. For example, `owner: Arc<Person>` produces
/// `"owner" -> "Person"` by unwrapping common generic wrappers.
fn extract_type_annotations(content: &str) -> Vec<(String, String)> {
let mut annotations = Vec::new();
for line in content.lines() {
let trimmed = line.trim();
if trimmed.starts_with("//")
|| trimmed.starts_with("use ")
|| trimmed.starts_with("pub use ")
{
continue;
}
let Some(colon_idx) = trimmed.find(':') else {
continue;
};
// The part before `:` should end with an identifier name.
let left = trimmed[..colon_idx].trim();
let Some(name) = left.split_whitespace().last() else {
continue;
};
if name.is_empty() || !name.chars().all(|c| c.is_alphanumeric() || c == '_') {
continue;
}
// Skip names that start uppercase — they're type names, not variables/fields.
if name.starts_with(|c: char| c.is_uppercase()) {
continue;
}
let right = trimmed[colon_idx + 1..].trim();
let type_name = extract_base_type_name(right);
if !type_name.is_empty() && type_name.starts_with(|c: char| c.is_uppercase()) {
annotations.push((name.to_string(), type_name));
}
}
annotations
}
/// Unwraps common generic wrappers (Arc, Box, Rc, Option, Vec) and trait
/// object prefixes (dyn, impl) to find the concrete type name. For example:
/// `Arc<Person>` → `"Person"`, `Box<dyn Trait>` → `"Trait"`.
fn extract_base_type_name(type_str: &str) -> String {
let trimmed = type_str
.trim()
.trim_start_matches('&')
.trim_start_matches("mut ")
.trim_end_matches(',')
.trim_end_matches('{')
.trim_end_matches(')')
.trim()
.trim_start_matches("dyn ")
.trim_start_matches("impl ")
.trim();
if let Some(angle_start) = trimmed.find('<') {
let outer = &trimmed[..angle_start];
if matches!(outer, "Arc" | "Box" | "Rc" | "Option" | "Vec" | "Cow") {
let inner_end = trimmed.rfind('>').unwrap_or(trimmed.len());
let inner = &trimmed[angle_start + 1..inner_end];
return extract_base_type_name(inner);
}
return outer.to_string();
}
if let Some(call_start) = trimmed.find("::") {
let outer = &trimmed[..call_start];
if matches!(outer, "Arc" | "Box" | "Rc" | "Option" | "Vec" | "Cow") {
let rest = trimmed[call_start + 2..].trim_start();
if let Some(paren_start) = rest.find('(') {
let inner = &rest[paren_start + 1..];
let inner = inner.trim();
if !inner.is_empty() {
return extract_base_type_name(inner);
}
}
}
}
trimmed
.split(|c: char| !c.is_alphanumeric() && c != '_')
.next()
.unwrap_or("")
.to_string()
}
fn extract_declarations_from_tree(
tree: &Tree,
content: &str,
outline_config: &language::OutlineConfig,
) -> Vec<(String, Range<usize>)> {
let mut cursor = QueryCursor::new();
let mut declarations = Vec::new();
let mut matches = cursor.matches(&outline_config.query, tree.root_node(), content.as_bytes());
while let Some(query_match) = matches.next() {
let mut name_range: Option<Range<usize>> = None;
let mut has_item_range = false;
for capture in query_match.captures {
let range = capture.node.byte_range();
if capture.index == outline_config.name_capture_ix {
name_range = Some(range);
} else if capture.index == outline_config.item_capture_ix {
has_item_range = true;
}
}
if let Some(name_range) = name_range
&& has_item_range
{
let name = content[name_range.clone()].to_string();
if declarations.iter().any(|(n, _)| n == &name) {
continue;
}
declarations.push((name, name_range));
}
}
declarations
}
fn byte_range_to_lsp_range(content: &str, byte_range: Range<usize>) -> lsp::Range {
let start = byte_offset_to_position(content, byte_range.start);
let end = byte_offset_to_position(content, byte_range.end);
lsp::Range { start, end }
}
fn byte_offset_to_position(content: &str, offset: usize) -> lsp::Position {
let mut line = 0;
let mut character = 0;
let mut current_offset = 0;
for ch in content.chars() {
if current_offset >= offset {
break;
}
if ch == '\n' {
line += 1;
character = 0;
} else {
character += 1;
}
current_offset += ch.len_utf8();
}
lsp::Position { line, character }
}
fn word_at_position(content: &str, position: lsp::Position) -> Option<&str> {
let mut lines = content.lines();
let line = lines.nth(position.line as usize)?;
let column = position.character as usize;
if column > line.len() {
return None;
}
let start = line[..column]
.rfind(|c: char| !c.is_alphanumeric() && c != '_')
.map(|i| i + 1)
.unwrap_or(0);
let end = line[column..]
.find(|c: char| !c.is_alphanumeric() && c != '_')
.map(|i| i + column)
.unwrap_or(line.len());
Some(&line[start..end]).filter(|word| !word.is_empty())
}