Add a bespoke LSP request path for edit prediction context (#60947)
Closes EP-193
Edit prediction context collection previously reused the editor
goto-definition / goto-type-definition path, which resolves every LSP
result into a `LocationLink` — opening a buffer per target (worktree
creation, buffer registration, anchor conversion) before edit prediction
gets a chance to filter. On remote projects the host additionally
created a peer-visible buffer per link and the client waited on each
one.
This adds a bespoke request path that returns raw results first and only
does the expensive work for results that survive filtering:
- New `EditPredictionDefinition { path: ProjectPath, range:
Range<Unclipped<PointUtf16>> }` boundary type. LSP wire types never
leave `lsp_command.rs`; the workspace-only filter and URI→`ProjectPath`
resolution happen while normalizing the LSP response, before any buffer
exists, so the type encodes the workspace-only invariant.
- New `GetEditPredictionDefinitions` /
`GetEditPredictionTypeDefinitions` LSP commands and proto messages.
Responses carry only path + UTF-16 range — the host never calls
`create_buffer_for_peer` and the client never waits on remote buffers.
- One public API: `Project::edit_prediction_definitions(buffer,
position, include_type_definitions, cx)` fires both LSP requests
concurrently and returns a merged, deduped list ("not capable" = empty).
The definition/type-definition split was never used downstream, so
`CacheEntry` now holds a single list and the fetch pipeline runs one
task per identifier.
- `edit_prediction_context` dedupes raw results before opening buffers;
survivors open via `project.open_buffer(ProjectPath)` (skipping the
invisible-worktree/yarn machinery of `open_local_buffer_via_lsp`, and
working identically on remote projects), then clip → anchor →
`MAX_TARGET_LEN`.
- Removes the `workspace_only` flag from `GetDefinitions` /
`GetTypeDefinitions`: edit prediction was its only user. The editor path
always sent `false`, which proto3 doesn't encode, so normal
goto-definition requests are wire-identical; old peers still sending
`true` get unfiltered results (graceful degradation). The proto field
numbers are `reserved`.
Tested with a local test proving filtering precedes buffer opening (an
out-of-workspace target never spawns a worktree, an oversized target is
excluded from related files) and a collab test proving the remote
round-trip returns correct paths/ranges without opening buffers on the
client.
Release Notes:
- N/A
This commit is contained in:
1 parent
31f1c84909
commit
d224ea86e8
12 files changed
+847
-258
No files matched your search
@@ -5139,6 +5139,109 @@ async fn test_definition(
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_edit_prediction_definition(
|
||||
executor: BackgroundExecutor,
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
let mut server = TestServer::start(executor.clone()).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
server
|
||||
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
let capabilities = lsp::ServerCapabilities {
|
||||
definition_provider: Some(OneOf::Left(true)),
|
||||
..lsp::ServerCapabilities::default()
|
||||
};
|
||||
client_a.language_registry().add(rust_lang());
|
||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: capabilities.clone(),
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
);
|
||||
client_b.language_registry().add(rust_lang());
|
||||
client_b.language_registry().register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities,
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
);
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
.insert_tree(
|
||||
path!("/root"),
|
||||
json!({
|
||||
"a.rs": "const ONE: usize = TWO;",
|
||||
"b.rs": "const TWO: usize = 2;",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await;
|
||||
let project_id = active_call_a
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
let (buffer_b, _handle) = project_b
|
||||
.update(cx_b, |project, cx| {
|
||||
project.open_buffer_with_lsp((worktree_id, rel_path("a.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
fake_language_server.set_request_handler::<lsp::request::GotoDefinition, _, _>(
|
||||
|_, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
||||
lsp::Location::new(
|
||||
lsp::Uri::from_file_path(path!("/root/b.rs")).unwrap(),
|
||||
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||
),
|
||||
)))
|
||||
},
|
||||
);
|
||||
cx_a.run_until_parked();
|
||||
cx_b.run_until_parked();
|
||||
|
||||
let definitions = project_b
|
||||
.update(cx_b, |project, cx| {
|
||||
project.edit_prediction_definitions(&buffer_b, 19, false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx_b.read(|cx| {
|
||||
assert_eq!(definitions.len(), 1);
|
||||
assert_eq!(
|
||||
definitions[0].path,
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: rel_path("b.rs").into(),
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
definitions[0].range.start.0,
|
||||
language::PointUtf16::new(0, 6)
|
||||
);
|
||||
assert_eq!(definitions[0].range.end.0, language::PointUtf16::new(0, 9));
|
||||
assert!(
|
||||
project_b
|
||||
.read(cx)
|
||||
.get_open_buffer(&definitions[0].path, cx)
|
||||
.is_none()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_references(
|
||||
executor: BackgroundExecutor,
|
||||
|
||||
@@ -5,8 +5,8 @@ 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 language::{Anchor, Bias, Buffer, BufferSnapshot, OffsetRangeExt as _, Point, ToOffset as _};
|
||||
use project::{EditPredictionDefinition, Project, ProjectPath};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
collections::hash_map,
|
||||
@@ -77,20 +77,15 @@ struct Identifier {
|
||||
|
||||
enum DefinitionTask {
|
||||
CacheHit(Arc<CacheEntry>),
|
||||
CacheMiss(
|
||||
Task<
|
||||
Option<(
|
||||
Task<Result<Option<Vec<LocationLink>>>>,
|
||||
Task<Result<Option<Vec<LocationLink>>>>,
|
||||
)>,
|
||||
>,
|
||||
),
|
||||
CacheMiss {
|
||||
project: WeakEntity<Project>,
|
||||
task: Task<Result<Vec<EditPredictionDefinition>>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CacheEntry {
|
||||
definitions: SmallVec<[CachedDefinition; 1]>,
|
||||
type_definitions: SmallVec<[CachedDefinition; 1]>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -312,34 +307,21 @@ impl RelatedExcerptStore {
|
||||
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.workspace_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.workspace_type_definitions(
|
||||
&buffer,
|
||||
identifier.range.start,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.ok()?;
|
||||
Some((definitions, type_definitions))
|
||||
}))
|
||||
let task = 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
|
||||
let include_type_definitions =
|
||||
!is_tombi_lsp_in_toml(project, &buffer, cx);
|
||||
project.edit_prediction_definitions(
|
||||
&buffer,
|
||||
identifier.range.start,
|
||||
include_type_definitions,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|_| Task::ready(Ok(Vec::new())));
|
||||
DefinitionTask::CacheMiss { project, task }
|
||||
};
|
||||
|
||||
let cx = async_cx.clone();
|
||||
@@ -348,50 +330,29 @@ impl RelatedExcerptStore {
|
||||
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);
|
||||
DefinitionTask::CacheMiss { project, task } => {
|
||||
let definition_locations = task.await.log_err().unwrap_or_default();
|
||||
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();
|
||||
|
||||
let definitions: SmallVec<[CachedDefinition; 1]> =
|
||||
definition_locations
|
||||
.into_iter()
|
||||
.filter_map(|location| {
|
||||
future::join_all(definition_locations.into_iter().map(
|
||||
|definition| {
|
||||
let project = project.clone();
|
||||
let mut cx = cx.clone();
|
||||
process_definition(location, &mut cx)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let type_definitions: SmallVec<[CachedDefinition; 1]> =
|
||||
type_definition_locations
|
||||
.into_iter()
|
||||
.filter_map(|location| {
|
||||
let mut cx = cx.clone();
|
||||
process_definition(location, &mut 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();
|
||||
async move {
|
||||
process_definition(definition, &project, &mut cx)
|
||||
.await
|
||||
}
|
||||
},
|
||||
))
|
||||
.await
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
Some((
|
||||
identifier,
|
||||
Arc::new(CacheEntry {
|
||||
definitions,
|
||||
type_definitions,
|
||||
}),
|
||||
Arc::new(CacheEntry { definitions }),
|
||||
Some(duration),
|
||||
))
|
||||
}
|
||||
@@ -469,11 +430,7 @@ async fn rebuild_related_files(
|
||||
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())
|
||||
{
|
||||
for definition in entry.definitions.iter() {
|
||||
if let hash_map::Entry::Vacant(e) = snapshots.entry(definition.buffer.entity_id()) {
|
||||
definition
|
||||
.buffer
|
||||
@@ -510,11 +467,7 @@ async fn rebuild_related_files(
|
||||
.get(identifier)
|
||||
.copied()
|
||||
.unwrap_or(usize::MAX);
|
||||
for definition in entry
|
||||
.definitions
|
||||
.iter()
|
||||
.chain(entry.type_definitions.iter())
|
||||
{
|
||||
for definition in entry.definitions.iter() {
|
||||
let Some(snapshot) = snapshots.get(&definition.buffer.entity_id()) else {
|
||||
continue;
|
||||
};
|
||||
@@ -635,16 +588,24 @@ use language::ToPoint as _;
|
||||
|
||||
const MAX_TARGET_LEN: usize = 128;
|
||||
|
||||
fn process_definition(location: LocationLink, cx: &mut AsyncApp) -> Option<CachedDefinition> {
|
||||
async fn process_definition(
|
||||
definition: EditPredictionDefinition,
|
||||
project: &WeakEntity<Project>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Option<CachedDefinition> {
|
||||
let EditPredictionDefinition { path, range } = definition;
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.open_buffer(path.clone(), cx))
|
||||
.ok()?
|
||||
.await
|
||||
.log_err()?;
|
||||
|
||||
cx.update(|cx| {
|
||||
let buffer = location.target.buffer;
|
||||
let buffer_snapshot = buffer.read(cx);
|
||||
let file = buffer_snapshot.file()?;
|
||||
let path = ProjectPath {
|
||||
worktree_id: file.worktree_id(cx),
|
||||
path: file.path().clone(),
|
||||
};
|
||||
let anchor_range = location.target.range;
|
||||
let target_start = buffer_snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||
let target_end = buffer_snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||
let anchor_range =
|
||||
buffer_snapshot.anchor_after(target_start)..buffer_snapshot.anchor_before(target_end);
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -5,10 +5,11 @@ use gpui::TestAppContext;
|
||||
use indoc::indoc;
|
||||
use language::{Point, ToPoint as _, rust_lang};
|
||||
use lsp::FakeLanguageServer;
|
||||
use project::{FakeFs, LocationLink, Project};
|
||||
use project::{FakeFs, LocationLink, Project, ProjectPath};
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use std::fmt::Write as _;
|
||||
use util::rel_path::rel_path;
|
||||
use util::{path, test::marked_text_ranges};
|
||||
|
||||
#[gpui::test]
|
||||
@@ -561,9 +562,10 @@ async fn test_type_definition_deduplication(cx: &mut TestAppContext) {
|
||||
|
||||
// In this project the only identifier near the cursor whose type definition
|
||||
// resolves is `TypeA`, and its GotoTypeDefinition returns the exact same
|
||||
// location as GotoDefinition. After deduplication the CacheEntry for `TypeA`
|
||||
// should have an empty `type_definitions` vec, meaning the type-definition
|
||||
// path contributes nothing extra to the related-file output.
|
||||
// location as GotoDefinition. After the definitions and type definitions are
|
||||
// merged and deduped, the type-definition location is dropped, so it
|
||||
// contributes nothing extra to the related-file output (the target location
|
||||
// appears only once).
|
||||
fs.insert_tree(
|
||||
path!("/root"),
|
||||
json!({
|
||||
@@ -638,6 +640,85 @@ async fn test_type_definition_deduplication(cx: &mut TestAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_edit_prediction_filters_raw_definitions_before_opening_buffers(
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
path!("/root"),
|
||||
json!({
|
||||
"src": {
|
||||
"main.rs": indoc! {"
|
||||
// fake-definition-lsp-extra target /root/src/large.rs 0 0 0 129
|
||||
// fake-definition-lsp-extra target /root/src/valid.rs 0 3 0 9
|
||||
// fake-definition-lsp-extra target /outside.rs 0 3 0 10
|
||||
fn main() {
|
||||
target();
|
||||
}
|
||||
"},
|
||||
"valid.rs": "fn target() {}\n",
|
||||
"large.rs": format!("{}\n", "a".repeat(MAX_TARGET_LEN + 16)),
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
fs.insert_file(path!("/outside.rs"), "fn outside() {}\n".into())
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
let mut servers = setup_fake_lsp(&project, cx);
|
||||
|
||||
let (buffer, _handle) = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let _fake_language_server = servers.next().await.unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
let related_excerpt_store = cx.new(|cx| RelatedExcerptStore::new(&project, cx));
|
||||
related_excerpt_store.update(cx, |store, cx| {
|
||||
let position = {
|
||||
let buffer = buffer.read(cx);
|
||||
let offset = buffer
|
||||
.text()
|
||||
.find("target();")
|
||||
.expect("target call not found");
|
||||
buffer.anchor_before(offset)
|
||||
};
|
||||
|
||||
store.set_identifier_line_count(0);
|
||||
store.refresh(buffer.clone(), position, cx);
|
||||
});
|
||||
|
||||
cx.executor().advance_clock(DEBOUNCE_DURATION);
|
||||
related_excerpt_store.update(cx, |store, cx| {
|
||||
assert_related_files(
|
||||
&store.related_files(cx),
|
||||
&[
|
||||
("root/src/valid.rs", &["fn target() {}"]),
|
||||
("root/src/main.rs", &["fn main() {\n target();\n}"]),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
let worktree_id = buffer.read_with(cx, |buffer, cx| {
|
||||
buffer.file().expect("buffer has file").worktree_id(cx)
|
||||
});
|
||||
let valid_path = ProjectPath {
|
||||
worktree_id,
|
||||
path: rel_path("src/valid.rs").into(),
|
||||
};
|
||||
project.read_with(cx, |project, cx| {
|
||||
assert!(project.get_open_buffer(&valid_path, cx).is_some());
|
||||
assert_eq!(project.worktrees(cx).count(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_definitions_ranked_by_cursor_proximity(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
@@ -243,6 +243,12 @@ impl DefinitionIndex {
|
||||
.or_insert_with(Vec::new)
|
||||
.push(location);
|
||||
}
|
||||
for (name, location) in extract_extra_definition_locations(content) {
|
||||
self.definitions
|
||||
.entry(name)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(location);
|
||||
}
|
||||
|
||||
let type_annotations = extract_type_annotations(content)
|
||||
.into_iter()
|
||||
@@ -303,6 +309,34 @@ impl DefinitionIndex {
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_extra_definition_locations(content: &str) -> Vec<(String, lsp::Location)> {
|
||||
content
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
let mut parts = line
|
||||
.trim()
|
||||
.strip_prefix("// fake-definition-lsp-extra ")?
|
||||
.split_whitespace();
|
||||
let name = parts.next()?.to_string();
|
||||
let path = PathBuf::from(parts.next()?);
|
||||
let start_row = parts.next()?.parse().ok()?;
|
||||
let start_column = parts.next()?.parse().ok()?;
|
||||
let end_row = parts.next()?.parse().ok()?;
|
||||
let end_column = parts.next()?.parse().ok()?;
|
||||
Some((
|
||||
name,
|
||||
lsp::Location::new(
|
||||
Uri::from_file_path(path).ok()?,
|
||||
lsp::Range::new(
|
||||
lsp::Position::new(start_row, start_column),
|
||||
lsp::Position::new(end_row, end_column),
|
||||
),
|
||||
),
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
CodeAction, CompletionSource, CoreCompletion, CoreCompletionResponse, DocumentColor,
|
||||
DocumentHighlight, DocumentSymbol, Hover, HoverBlock, HoverBlockKind, InlayHint,
|
||||
InlayHintLabel, InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location,
|
||||
LocationLink, LspAction, LspPullDiagnostics, MarkupContent, PrepareRenameResponse,
|
||||
LocationLink, LspAction, LspPullDiagnostics, MarkupContent, PrepareRenameResponse, ProjectPath,
|
||||
ProjectTransaction, PulledDiagnostics, ResolveState,
|
||||
lsp_store::{LocalLspStore, LspDocumentLink, LspFoldingRange, LspStore},
|
||||
};
|
||||
@@ -39,6 +39,7 @@ use std::{
|
||||
cmp::Reverse, collections::hash_map, mem, ops::Range, path::Path, str::FromStr, sync::Arc,
|
||||
};
|
||||
use text::{BufferId, LineEnding};
|
||||
use util::rel_path::RelPath;
|
||||
use util::{ResultExt as _, debug_panic};
|
||||
|
||||
pub use signature_help::SignatureHelp;
|
||||
@@ -189,7 +190,17 @@ pub(crate) struct PerformRename {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GetDefinitions {
|
||||
pub position: PointUtf16,
|
||||
pub workspace_only: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct EditPredictionDefinition {
|
||||
pub path: ProjectPath,
|
||||
pub range: Range<Unclipped<PointUtf16>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct GetEditPredictionDefinitions {
|
||||
pub position: PointUtf16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -200,7 +211,11 @@ pub(crate) struct GetDeclarations {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct GetTypeDefinitions {
|
||||
pub position: PointUtf16,
|
||||
pub workspace_only: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct GetEditPredictionTypeDefinitions {
|
||||
pub position: PointUtf16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -694,15 +709,7 @@ impl LspCommand for GetDefinitions {
|
||||
server_id: LanguageServerId,
|
||||
cx: AsyncApp,
|
||||
) -> Result<Vec<LocationLink>> {
|
||||
location_links_from_lsp(
|
||||
message,
|
||||
lsp_store,
|
||||
buffer,
|
||||
server_id,
|
||||
self.workspace_only,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
location_links_from_lsp(message, lsp_store, buffer, server_id, cx).await
|
||||
}
|
||||
|
||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDefinition {
|
||||
@@ -713,7 +720,6 @@ impl LspCommand for GetDefinitions {
|
||||
&buffer.anchor_before(self.position),
|
||||
)),
|
||||
version: serialize_version(&buffer.version()),
|
||||
workspace_only: self.workspace_only,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -734,7 +740,6 @@ impl LspCommand for GetDefinitions {
|
||||
.await?;
|
||||
Ok(Self {
|
||||
position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
|
||||
workspace_only: message.workspace_only,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -764,6 +769,106 @@ impl LspCommand for GetDefinitions {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspCommand for GetEditPredictionDefinitions {
|
||||
type Response = Vec<EditPredictionDefinition>;
|
||||
type LspRequest = lsp::request::GotoDefinition;
|
||||
type ProtoRequest = proto::GetEditPredictionDefinition;
|
||||
|
||||
fn display_name(&self) -> &str {
|
||||
"Get edit prediction definition"
|
||||
}
|
||||
|
||||
fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
|
||||
capabilities
|
||||
.server_capabilities
|
||||
.definition_provider
|
||||
.is_some_and(|capability| match capability {
|
||||
OneOf::Left(supported) => supported,
|
||||
OneOf::Right(_options) => true,
|
||||
})
|
||||
}
|
||||
|
||||
fn to_lsp(
|
||||
&self,
|
||||
path: &Path,
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &App,
|
||||
) -> Result<lsp::GotoDefinitionParams> {
|
||||
Ok(lsp::GotoDefinitionParams {
|
||||
text_document_position_params: make_lsp_text_document_position(path, self.position)?,
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
self,
|
||||
message: Option<lsp::GotoDefinitionResponse>,
|
||||
lsp_store: Entity<LspStore>,
|
||||
_: Entity<Buffer>,
|
||||
_: LanguageServerId,
|
||||
cx: AsyncApp,
|
||||
) -> Result<Vec<EditPredictionDefinition>> {
|
||||
edit_prediction_definitions_from_lsp(message, lsp_store, cx)
|
||||
}
|
||||
|
||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetEditPredictionDefinition {
|
||||
proto::GetEditPredictionDefinition {
|
||||
project_id,
|
||||
buffer_id: buffer.remote_id().into(),
|
||||
position: Some(language::proto::serialize_anchor(
|
||||
&buffer.anchor_before(self.position),
|
||||
)),
|
||||
version: serialize_version(&buffer.version()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_proto(
|
||||
message: proto::GetEditPredictionDefinition,
|
||||
_: Entity<LspStore>,
|
||||
buffer: Entity<Buffer>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
position: edit_prediction_position_from_proto(
|
||||
message.position,
|
||||
message.version,
|
||||
buffer,
|
||||
&mut cx,
|
||||
)
|
||||
.await?,
|
||||
})
|
||||
}
|
||||
|
||||
fn response_to_proto(
|
||||
response: Vec<EditPredictionDefinition>,
|
||||
_: &mut LspStore,
|
||||
_: PeerId,
|
||||
_: &clock::Global,
|
||||
_: &mut App,
|
||||
) -> proto::GetEditPredictionDefinitionResponse {
|
||||
proto::GetEditPredictionDefinitionResponse {
|
||||
definitions: edit_prediction_definitions_to_proto(response),
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_proto(
|
||||
self,
|
||||
message: proto::GetEditPredictionDefinitionResponse,
|
||||
_: Entity<LspStore>,
|
||||
_: Entity<Buffer>,
|
||||
_: AsyncApp,
|
||||
) -> Result<Vec<EditPredictionDefinition>> {
|
||||
edit_prediction_definitions_from_proto(message.definitions)
|
||||
}
|
||||
|
||||
fn buffer_id_from_proto(message: &proto::GetEditPredictionDefinition) -> Result<BufferId> {
|
||||
BufferId::new(message.buffer_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspCommand for GetDeclarations {
|
||||
type Response = Vec<LocationLink>;
|
||||
@@ -807,7 +912,7 @@ impl LspCommand for GetDeclarations {
|
||||
server_id: LanguageServerId,
|
||||
cx: AsyncApp,
|
||||
) -> Result<Vec<LocationLink>> {
|
||||
location_links_from_lsp(message, lsp_store, buffer, server_id, false, cx).await
|
||||
location_links_from_lsp(message, lsp_store, buffer, server_id, cx).await
|
||||
}
|
||||
|
||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDeclaration {
|
||||
@@ -909,7 +1014,7 @@ impl LspCommand for GetImplementations {
|
||||
server_id: LanguageServerId,
|
||||
cx: AsyncApp,
|
||||
) -> Result<Vec<LocationLink>> {
|
||||
location_links_from_lsp(message, lsp_store, buffer, server_id, false, cx).await
|
||||
location_links_from_lsp(message, lsp_store, buffer, server_id, cx).await
|
||||
}
|
||||
|
||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetImplementation {
|
||||
@@ -1008,7 +1113,7 @@ impl LspCommand for GetTypeDefinitions {
|
||||
server_id: LanguageServerId,
|
||||
cx: AsyncApp,
|
||||
) -> Result<Vec<LocationLink>> {
|
||||
location_links_from_lsp(message, project, buffer, server_id, self.workspace_only, cx).await
|
||||
location_links_from_lsp(message, project, buffer, server_id, cx).await
|
||||
}
|
||||
|
||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetTypeDefinition {
|
||||
@@ -1019,7 +1124,6 @@ impl LspCommand for GetTypeDefinitions {
|
||||
&buffer.anchor_before(self.position),
|
||||
)),
|
||||
version: serialize_version(&buffer.version()),
|
||||
workspace_only: self.workspace_only,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1040,7 +1144,6 @@ impl LspCommand for GetTypeDefinitions {
|
||||
.await?;
|
||||
Ok(Self {
|
||||
position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
|
||||
workspace_only: message.workspace_only,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1070,6 +1173,103 @@ impl LspCommand for GetTypeDefinitions {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspCommand for GetEditPredictionTypeDefinitions {
|
||||
type Response = Vec<EditPredictionDefinition>;
|
||||
type LspRequest = lsp::request::GotoTypeDefinition;
|
||||
type ProtoRequest = proto::GetEditPredictionTypeDefinition;
|
||||
|
||||
fn display_name(&self) -> &str {
|
||||
"Get edit prediction type definition"
|
||||
}
|
||||
|
||||
fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
|
||||
!matches!(
|
||||
&capabilities.server_capabilities.type_definition_provider,
|
||||
None | Some(lsp::TypeDefinitionProviderCapability::Simple(false))
|
||||
)
|
||||
}
|
||||
|
||||
fn to_lsp(
|
||||
&self,
|
||||
path: &Path,
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &App,
|
||||
) -> Result<lsp::GotoTypeDefinitionParams> {
|
||||
Ok(lsp::GotoTypeDefinitionParams {
|
||||
text_document_position_params: make_lsp_text_document_position(path, self.position)?,
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
self,
|
||||
message: Option<lsp::GotoDefinitionResponse>,
|
||||
lsp_store: Entity<LspStore>,
|
||||
_: Entity<Buffer>,
|
||||
_: LanguageServerId,
|
||||
cx: AsyncApp,
|
||||
) -> Result<Vec<EditPredictionDefinition>> {
|
||||
edit_prediction_definitions_from_lsp(message, lsp_store, cx)
|
||||
}
|
||||
|
||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetEditPredictionTypeDefinition {
|
||||
proto::GetEditPredictionTypeDefinition {
|
||||
project_id,
|
||||
buffer_id: buffer.remote_id().into(),
|
||||
position: Some(language::proto::serialize_anchor(
|
||||
&buffer.anchor_before(self.position),
|
||||
)),
|
||||
version: serialize_version(&buffer.version()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_proto(
|
||||
message: proto::GetEditPredictionTypeDefinition,
|
||||
_: Entity<LspStore>,
|
||||
buffer: Entity<Buffer>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
position: edit_prediction_position_from_proto(
|
||||
message.position,
|
||||
message.version,
|
||||
buffer,
|
||||
&mut cx,
|
||||
)
|
||||
.await?,
|
||||
})
|
||||
}
|
||||
|
||||
fn response_to_proto(
|
||||
response: Vec<EditPredictionDefinition>,
|
||||
_: &mut LspStore,
|
||||
_: PeerId,
|
||||
_: &clock::Global,
|
||||
_: &mut App,
|
||||
) -> proto::GetEditPredictionTypeDefinitionResponse {
|
||||
proto::GetEditPredictionTypeDefinitionResponse {
|
||||
definitions: edit_prediction_definitions_to_proto(response),
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_proto(
|
||||
self,
|
||||
message: proto::GetEditPredictionTypeDefinitionResponse,
|
||||
_: Entity<LspStore>,
|
||||
_: Entity<Buffer>,
|
||||
_: AsyncApp,
|
||||
) -> Result<Vec<EditPredictionDefinition>> {
|
||||
edit_prediction_definitions_from_proto(message.definitions)
|
||||
}
|
||||
|
||||
fn buffer_id_from_proto(message: &proto::GetEditPredictionTypeDefinition) -> Result<BufferId> {
|
||||
BufferId::new(message.buffer_id)
|
||||
}
|
||||
}
|
||||
|
||||
fn language_server_for_buffer(
|
||||
lsp_store: &Entity<LspStore>,
|
||||
buffer: &Entity<Buffer>,
|
||||
@@ -1165,57 +1365,13 @@ pub async fn location_links_from_lsp(
|
||||
lsp_store: Entity<LspStore>,
|
||||
buffer: Entity<Buffer>,
|
||||
server_id: LanguageServerId,
|
||||
workspace_only: bool,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<Vec<LocationLink>> {
|
||||
let message = match message {
|
||||
Some(message) => message,
|
||||
None => return Ok(Vec::new()),
|
||||
};
|
||||
|
||||
let mut unresolved_links = Vec::new();
|
||||
match message {
|
||||
lsp::GotoDefinitionResponse::Scalar(loc) => {
|
||||
unresolved_links.push((None, loc.uri, loc.range));
|
||||
}
|
||||
|
||||
lsp::GotoDefinitionResponse::Array(locs) => {
|
||||
unresolved_links.extend(locs.into_iter().map(|l| (None, l.uri, l.range)));
|
||||
}
|
||||
|
||||
lsp::GotoDefinitionResponse::Link(links) => {
|
||||
unresolved_links.extend(links.into_iter().map(|l| {
|
||||
(
|
||||
l.origin_selection_range,
|
||||
l.target_uri,
|
||||
l.target_selection_range,
|
||||
)
|
||||
}));
|
||||
}
|
||||
}
|
||||
let unresolved_links = definition_locations_from_lsp(message);
|
||||
|
||||
let (_, language_server) = language_server_for_buffer(&lsp_store, &buffer, server_id, &mut cx)?;
|
||||
let mut definitions = Vec::new();
|
||||
for (origin_range, target_uri, target_range) in unresolved_links {
|
||||
if workspace_only
|
||||
&& !lsp_store.update(&mut cx, |this, cx| {
|
||||
use util::paths::UrlExt as _;
|
||||
let worktree_store = this.worktree_store().read(cx);
|
||||
let path_style = worktree_store.path_style();
|
||||
let Ok(abs_path) = target_uri.clone().to_file_path_ext(path_style) else {
|
||||
return false;
|
||||
};
|
||||
worktree_store
|
||||
.find_worktree(&abs_path, cx)
|
||||
.is_some_and(|(worktree, _)| {
|
||||
let worktree = worktree.read(cx);
|
||||
worktree.is_visible() && !worktree.is_single_file()
|
||||
})
|
||||
})
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let target_buffer_handle = lsp_store
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.open_local_buffer_via_lsp(target_uri, language_server.server_id(), cx)
|
||||
@@ -1256,6 +1412,94 @@ pub async fn location_links_from_lsp(
|
||||
Ok(definitions)
|
||||
}
|
||||
|
||||
fn definition_locations_from_lsp(
|
||||
message: Option<lsp::GotoDefinitionResponse>,
|
||||
) -> Vec<(Option<lsp::Range>, lsp::Uri, lsp::Range)> {
|
||||
let Some(message) = message else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let mut locations = Vec::new();
|
||||
match message {
|
||||
lsp::GotoDefinitionResponse::Scalar(location) => {
|
||||
locations.push((None, location.uri, location.range));
|
||||
}
|
||||
|
||||
lsp::GotoDefinitionResponse::Array(locations_from_lsp) => {
|
||||
locations.extend(
|
||||
locations_from_lsp
|
||||
.into_iter()
|
||||
.map(|location| (None, location.uri, location.range)),
|
||||
);
|
||||
}
|
||||
|
||||
lsp::GotoDefinitionResponse::Link(links) => {
|
||||
locations.extend(links.into_iter().map(|link| {
|
||||
(
|
||||
link.origin_selection_range,
|
||||
link.target_uri,
|
||||
link.target_selection_range,
|
||||
)
|
||||
}));
|
||||
}
|
||||
}
|
||||
locations
|
||||
}
|
||||
|
||||
fn edit_prediction_definitions_from_lsp(
|
||||
message: Option<lsp::GotoDefinitionResponse>,
|
||||
lsp_store: Entity<LspStore>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<Vec<EditPredictionDefinition>> {
|
||||
let unresolved_locations = definition_locations_from_lsp(message);
|
||||
lsp_store.update(&mut cx, |lsp_store, cx| {
|
||||
use util::paths::UrlExt as _;
|
||||
let mut definitions = Vec::new();
|
||||
let worktree_store = lsp_store.worktree_store().read(cx);
|
||||
let path_style = worktree_store.path_style();
|
||||
|
||||
for (_, uri, range) in unresolved_locations {
|
||||
let Ok(abs_path) = uri.to_file_path_ext(path_style) else {
|
||||
continue;
|
||||
};
|
||||
let Some((worktree, relative_path)) = worktree_store.find_worktree(&abs_path, cx)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let worktree = worktree.read(cx);
|
||||
if !worktree.is_visible() || worktree.is_single_file() {
|
||||
continue;
|
||||
}
|
||||
definitions.push(EditPredictionDefinition {
|
||||
path: ProjectPath {
|
||||
worktree_id: worktree.id(),
|
||||
path: relative_path,
|
||||
},
|
||||
range: point_from_lsp(range.start)..point_from_lsp(range.end),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(definitions)
|
||||
})
|
||||
}
|
||||
|
||||
async fn edit_prediction_position_from_proto(
|
||||
position: Option<proto::Anchor>,
|
||||
version: Vec<proto::VectorClockEntry>,
|
||||
buffer: Entity<Buffer>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<PointUtf16> {
|
||||
let position = position
|
||||
.and_then(deserialize_anchor)
|
||||
.context("invalid position")?;
|
||||
buffer
|
||||
.update(cx, |buffer, _| {
|
||||
buffer.wait_for_version(deserialize_version(&version))
|
||||
})
|
||||
.await?;
|
||||
Ok(buffer.read_with(cx, |buffer, _| position.to_point_utf16(buffer)))
|
||||
}
|
||||
|
||||
pub async fn location_link_from_lsp(
|
||||
link: lsp::LocationLink,
|
||||
lsp_store: &Entity<LspStore>,
|
||||
@@ -1363,6 +1607,46 @@ pub fn location_link_to_proto(
|
||||
}
|
||||
}
|
||||
|
||||
fn edit_prediction_definitions_to_proto(
|
||||
definitions: Vec<EditPredictionDefinition>,
|
||||
) -> Vec<proto::EditPredictionDefinition> {
|
||||
definitions
|
||||
.into_iter()
|
||||
.map(|definition| proto::EditPredictionDefinition {
|
||||
worktree_id: definition.path.worktree_id.to_proto(),
|
||||
path: definition.path.path.as_ref().to_proto(),
|
||||
start: Some(proto::PointUtf16 {
|
||||
row: definition.range.start.0.row,
|
||||
column: definition.range.start.0.column,
|
||||
}),
|
||||
end: Some(proto::PointUtf16 {
|
||||
row: definition.range.end.0.row,
|
||||
column: definition.range.end.0.column,
|
||||
}),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn edit_prediction_definitions_from_proto(
|
||||
definitions: Vec<proto::EditPredictionDefinition>,
|
||||
) -> Result<Vec<EditPredictionDefinition>> {
|
||||
definitions
|
||||
.into_iter()
|
||||
.map(|definition| {
|
||||
let start = definition.start.context("missing definition start")?;
|
||||
let end = definition.end.context("missing definition end")?;
|
||||
Ok(EditPredictionDefinition {
|
||||
path: ProjectPath {
|
||||
worktree_id: worktree::WorktreeId::from_proto(definition.worktree_id),
|
||||
path: RelPath::from_proto(&definition.path).context("invalid path")?,
|
||||
},
|
||||
range: Unclipped(PointUtf16::new(start.row, start.column))
|
||||
..Unclipped(PointUtf16::new(end.row, end.column)),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspCommand for GetReferences {
|
||||
type Response = Vec<Location>;
|
||||
|
||||
+139
-64
@@ -6171,31 +6171,9 @@ impl LspStore {
|
||||
buffer: &Entity<Buffer>,
|
||||
position: PointUtf16,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Option<Vec<LocationLink>>>> {
|
||||
self.definitions_with_filter(buffer, position, false, cx)
|
||||
}
|
||||
|
||||
pub fn workspace_definitions(
|
||||
&mut self,
|
||||
buffer: &Entity<Buffer>,
|
||||
position: PointUtf16,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Option<Vec<LocationLink>>>> {
|
||||
self.definitions_with_filter(buffer, position, true, cx)
|
||||
}
|
||||
|
||||
fn definitions_with_filter(
|
||||
&mut self,
|
||||
buffer: &Entity<Buffer>,
|
||||
position: PointUtf16,
|
||||
workspace_only: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Option<Vec<LocationLink>>>> {
|
||||
if let Some((upstream_client, project_id)) = self.upstream_client() {
|
||||
let request = GetDefinitions {
|
||||
position,
|
||||
workspace_only,
|
||||
};
|
||||
let request = GetDefinitions { position };
|
||||
if !self.is_capable_for_proto_request(buffer, &request, cx) {
|
||||
return Task::ready(Ok(None));
|
||||
}
|
||||
@@ -6220,11 +6198,7 @@ impl LspStore {
|
||||
return Ok(None);
|
||||
};
|
||||
let actions = join_all(responses.payload.into_iter().map(|response| {
|
||||
GetDefinitions {
|
||||
position,
|
||||
workspace_only,
|
||||
}
|
||||
.response_from_proto(
|
||||
GetDefinitions { position }.response_from_proto(
|
||||
response.response,
|
||||
lsp_store.clone(),
|
||||
buffer.clone(),
|
||||
@@ -6247,10 +6221,7 @@ impl LspStore {
|
||||
let definitions_task = self.request_multiple_lsp_locally(
|
||||
buffer,
|
||||
Some(position),
|
||||
GetDefinitions {
|
||||
position,
|
||||
workspace_only,
|
||||
},
|
||||
GetDefinitions { position },
|
||||
cx,
|
||||
);
|
||||
cx.background_spawn(async move {
|
||||
@@ -6266,6 +6237,107 @@ impl LspStore {
|
||||
}
|
||||
}
|
||||
|
||||
fn edit_prediction_definitions_for_command<C>(
|
||||
&mut self,
|
||||
buffer: &Entity<Buffer>,
|
||||
request: C,
|
||||
position: PointUtf16,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Vec<EditPredictionDefinition>>>
|
||||
where
|
||||
C: LspCommand<Response = Vec<EditPredictionDefinition>> + Clone,
|
||||
C::ProtoRequest: proto::LspRequestMessage,
|
||||
<C::ProtoRequest as proto::LspRequestMessage>::Response:
|
||||
Into<<C::ProtoRequest as proto::RequestMessage>::Response>,
|
||||
<C::LspRequest as lsp::request::Request>::Result: Send,
|
||||
<C::LspRequest as lsp::request::Request>::Params: Send,
|
||||
{
|
||||
if let Some((upstream_client, project_id)) = self.upstream_client() {
|
||||
if !self.is_capable_for_proto_request(buffer, &request, cx) {
|
||||
return Task::ready(Ok(Vec::new()));
|
||||
}
|
||||
|
||||
let request_timeout = ProjectSettings::get_global(cx)
|
||||
.global_lsp_settings
|
||||
.get_request_timeout();
|
||||
|
||||
let request_task = upstream_client.request_lsp(
|
||||
project_id,
|
||||
None,
|
||||
request_timeout,
|
||||
cx.background_executor().clone(),
|
||||
request.to_proto(project_id, buffer.read(cx)),
|
||||
);
|
||||
let buffer = buffer.clone();
|
||||
cx.spawn(async move |weak_lsp_store, cx| {
|
||||
let Some(lsp_store) = weak_lsp_store.upgrade() else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let Some(responses) = request_task.await? else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let actions = join_all(responses.payload.into_iter().map(|response| {
|
||||
request.clone().response_from_proto(
|
||||
response.response.into(),
|
||||
lsp_store.clone(),
|
||||
buffer.clone(),
|
||||
cx.clone(),
|
||||
)
|
||||
}))
|
||||
.await;
|
||||
|
||||
Ok(actions
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<Vec<_>>>>()?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect())
|
||||
})
|
||||
} else {
|
||||
let definitions_task =
|
||||
self.request_multiple_lsp_locally(buffer, Some(position), request, cx);
|
||||
cx.background_spawn(async move {
|
||||
Ok(definitions_task
|
||||
.await
|
||||
.into_iter()
|
||||
.flat_map(|(_, definitions)| definitions)
|
||||
.collect())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn edit_prediction_definitions(
|
||||
&mut self,
|
||||
buffer: &Entity<Buffer>,
|
||||
position: PointUtf16,
|
||||
include_type_definitions: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Vec<EditPredictionDefinition>>> {
|
||||
let definitions = self.edit_prediction_definitions_for_command(
|
||||
buffer,
|
||||
GetEditPredictionDefinitions { position },
|
||||
position,
|
||||
cx,
|
||||
);
|
||||
let type_definitions = include_type_definitions.then(|| {
|
||||
self.edit_prediction_definitions_for_command(
|
||||
buffer,
|
||||
GetEditPredictionTypeDefinitions { position },
|
||||
position,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.background_spawn(async move {
|
||||
let mut merged = definitions.await?;
|
||||
if let Some(type_definitions) = type_definitions {
|
||||
merged.extend(type_definitions.await?);
|
||||
}
|
||||
let mut seen = HashSet::default();
|
||||
merged.retain(|definition| seen.insert(definition.clone()));
|
||||
Ok(merged)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn declarations(
|
||||
&mut self,
|
||||
buffer: &Entity<Buffer>,
|
||||
@@ -6340,31 +6412,9 @@ impl LspStore {
|
||||
buffer: &Entity<Buffer>,
|
||||
position: PointUtf16,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Option<Vec<LocationLink>>>> {
|
||||
self.type_definitions_with_filter(buffer, position, false, cx)
|
||||
}
|
||||
|
||||
pub fn workspace_type_definitions(
|
||||
&mut self,
|
||||
buffer: &Entity<Buffer>,
|
||||
position: PointUtf16,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Option<Vec<LocationLink>>>> {
|
||||
self.type_definitions_with_filter(buffer, position, true, cx)
|
||||
}
|
||||
|
||||
fn type_definitions_with_filter(
|
||||
&mut self,
|
||||
buffer: &Entity<Buffer>,
|
||||
position: PointUtf16,
|
||||
workspace_only: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Option<Vec<LocationLink>>>> {
|
||||
if let Some((upstream_client, project_id)) = self.upstream_client() {
|
||||
let request = GetTypeDefinitions {
|
||||
position,
|
||||
workspace_only,
|
||||
};
|
||||
let request = GetTypeDefinitions { position };
|
||||
if !self.is_capable_for_proto_request(buffer, &request, cx) {
|
||||
return Task::ready(Ok(None));
|
||||
}
|
||||
@@ -6387,11 +6437,7 @@ impl LspStore {
|
||||
return Ok(None);
|
||||
};
|
||||
let actions = join_all(responses.payload.into_iter().map(|response| {
|
||||
GetTypeDefinitions {
|
||||
position,
|
||||
workspace_only,
|
||||
}
|
||||
.response_from_proto(
|
||||
GetTypeDefinitions { position }.response_from_proto(
|
||||
response.response,
|
||||
lsp_store.clone(),
|
||||
buffer.clone(),
|
||||
@@ -6414,10 +6460,7 @@ impl LspStore {
|
||||
let type_definitions_task = self.request_multiple_lsp_locally(
|
||||
buffer,
|
||||
Some(position),
|
||||
GetTypeDefinitions {
|
||||
position,
|
||||
workspace_only,
|
||||
},
|
||||
GetTypeDefinitions { position },
|
||||
cx,
|
||||
);
|
||||
cx.background_spawn(async move {
|
||||
@@ -9615,6 +9658,22 @@ impl LspStore {
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Request::GetEditPredictionDefinition(get_edit_prediction_definition) => {
|
||||
let position = get_edit_prediction_definition
|
||||
.position
|
||||
.clone()
|
||||
.and_then(deserialize_anchor);
|
||||
Self::query_lsp_locally::<GetEditPredictionDefinitions>(
|
||||
lsp_store,
|
||||
server_id,
|
||||
sender_id,
|
||||
lsp_request_id,
|
||||
get_edit_prediction_definition,
|
||||
position,
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Request::GetDeclaration(get_declaration) => {
|
||||
let position = get_declaration
|
||||
.position
|
||||
@@ -9647,6 +9706,22 @@ impl LspStore {
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Request::GetEditPredictionTypeDefinition(get_edit_prediction_type_definition) => {
|
||||
let position = get_edit_prediction_type_definition
|
||||
.position
|
||||
.clone()
|
||||
.and_then(deserialize_anchor);
|
||||
Self::query_lsp_locally::<GetEditPredictionTypeDefinitions>(
|
||||
lsp_store,
|
||||
server_id,
|
||||
sender_id,
|
||||
lsp_request_id,
|
||||
get_edit_prediction_type_definition,
|
||||
position,
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Request::GetImplementation(get_implementation) => {
|
||||
let position = get_implementation
|
||||
.position
|
||||
|
||||
@@ -443,7 +443,6 @@ impl LspCommand for GoToParentModule {
|
||||
lsp_store,
|
||||
buffer,
|
||||
server_id,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -99,6 +99,7 @@ use lsp::{
|
||||
LanguageServerBinary, LanguageServerId, LanguageServerName, LanguageServerSelector,
|
||||
MessageActionItem,
|
||||
};
|
||||
pub use lsp_command::EditPredictionDefinition;
|
||||
use lsp_command::*;
|
||||
use lsp_store::{CompletionDocumentation, LspFormatTarget, OpenLspBufferHandle};
|
||||
pub use manifest_tree::ManifestProvidersStore;
|
||||
@@ -4287,21 +4288,16 @@ impl Project {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn workspace_definitions<T: ToPointUtf16>(
|
||||
pub fn edit_prediction_definitions<T: ToPointUtf16>(
|
||||
&mut self,
|
||||
buffer: &Entity<Buffer>,
|
||||
position: T,
|
||||
include_type_definitions: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Option<Vec<LocationLink>>>> {
|
||||
) -> Task<Result<Vec<EditPredictionDefinition>>> {
|
||||
let position = position.to_point_utf16(buffer.read(cx));
|
||||
let guard = self.retain_remotely_created_models(cx);
|
||||
let task = self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.workspace_definitions(buffer, position, cx)
|
||||
});
|
||||
cx.background_spawn(async move {
|
||||
let result = task.await;
|
||||
drop(guard);
|
||||
result
|
||||
self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.edit_prediction_definitions(buffer, position, include_type_definitions, cx)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4341,24 +4337,6 @@ impl Project {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn workspace_type_definitions<T: ToPointUtf16>(
|
||||
&mut self,
|
||||
buffer: &Entity<Buffer>,
|
||||
position: T,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Option<Vec<LocationLink>>>> {
|
||||
let position = position.to_point_utf16(buffer.read(cx));
|
||||
let guard = self.retain_remotely_created_models(cx);
|
||||
let task = self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.workspace_type_definitions(buffer, position, cx)
|
||||
});
|
||||
cx.background_spawn(async move {
|
||||
let result = task.await;
|
||||
drop(guard);
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
pub fn implementations<T: ToPointUtf16>(
|
||||
&mut self,
|
||||
buffer: &Entity<Buffer>,
|
||||
|
||||
@@ -8,13 +8,31 @@ message GetDefinition {
|
||||
uint64 buffer_id = 2;
|
||||
Anchor position = 3;
|
||||
repeated VectorClockEntry version = 4;
|
||||
bool workspace_only = 5;
|
||||
reserved 5;
|
||||
}
|
||||
|
||||
message GetDefinitionResponse {
|
||||
repeated LocationLink links = 1;
|
||||
}
|
||||
|
||||
message GetEditPredictionDefinition {
|
||||
uint64 project_id = 1;
|
||||
uint64 buffer_id = 2;
|
||||
Anchor position = 3;
|
||||
repeated VectorClockEntry version = 4;
|
||||
}
|
||||
|
||||
message GetEditPredictionDefinitionResponse {
|
||||
repeated EditPredictionDefinition definitions = 1;
|
||||
}
|
||||
|
||||
message EditPredictionDefinition {
|
||||
uint64 worktree_id = 1;
|
||||
string path = 2;
|
||||
PointUtf16 start = 3;
|
||||
PointUtf16 end = 4;
|
||||
}
|
||||
|
||||
message GetDeclaration {
|
||||
uint64 project_id = 1;
|
||||
uint64 buffer_id = 2;
|
||||
@@ -31,12 +49,24 @@ message GetTypeDefinition {
|
||||
uint64 buffer_id = 2;
|
||||
Anchor position = 3;
|
||||
repeated VectorClockEntry version = 4;
|
||||
bool workspace_only = 5;
|
||||
reserved 5;
|
||||
}
|
||||
|
||||
message GetTypeDefinitionResponse {
|
||||
repeated LocationLink links = 1;
|
||||
}
|
||||
|
||||
message GetEditPredictionTypeDefinition {
|
||||
uint64 project_id = 1;
|
||||
uint64 buffer_id = 2;
|
||||
Anchor position = 3;
|
||||
repeated VectorClockEntry version = 4;
|
||||
}
|
||||
|
||||
message GetEditPredictionTypeDefinitionResponse {
|
||||
repeated EditPredictionDefinition definitions = 1;
|
||||
}
|
||||
|
||||
message GetImplementation {
|
||||
uint64 project_id = 1;
|
||||
uint64 buffer_id = 2;
|
||||
@@ -897,6 +927,8 @@ message LspQuery {
|
||||
GetFoldingRanges get_folding_ranges = 17;
|
||||
GetDocumentSymbols get_document_symbols = 18;
|
||||
GetDocumentLinks get_document_links = 19;
|
||||
GetEditPredictionDefinition get_edit_prediction_definition = 20;
|
||||
GetEditPredictionTypeDefinition get_edit_prediction_type_definition = 21;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -924,6 +956,8 @@ message LspResponse {
|
||||
GetFoldingRangesResponse get_folding_ranges_response = 15;
|
||||
GetDocumentSymbolsResponse get_document_symbols_response = 16;
|
||||
GetDocumentLinksResponse get_document_links_response = 17;
|
||||
GetEditPredictionDefinitionResponse get_edit_prediction_definition_response = 18;
|
||||
GetEditPredictionTypeDefinitionResponse get_edit_prediction_type_definition_response = 19;
|
||||
}
|
||||
uint64 server_id = 7;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,10 @@ message Envelope {
|
||||
|
||||
GetDefinition get_definition = 32;
|
||||
GetDefinitionResponse get_definition_response = 33;
|
||||
GetEditPredictionDefinition get_edit_prediction_definition = 462;
|
||||
GetEditPredictionDefinitionResponse get_edit_prediction_definition_response = 463;
|
||||
GetEditPredictionTypeDefinition get_edit_prediction_type_definition = 464;
|
||||
GetEditPredictionTypeDefinitionResponse get_edit_prediction_type_definition_response = 465; // current max
|
||||
GetDeclaration get_declaration = 237;
|
||||
GetDeclarationResponse get_declaration_response = 238;
|
||||
GetTypeDefinition get_type_definition = 34;
|
||||
@@ -489,7 +493,7 @@ message Envelope {
|
||||
GitWorktreeCreatedAtResponse git_worktree_created_at_response = 458;
|
||||
TelemetryEvent telemetry_event = 459;
|
||||
ResolveCodeAction resolve_code_action = 460;
|
||||
ResolveCodeActionResponse resolve_code_action_response = 461; // current max
|
||||
ResolveCodeActionResponse resolve_code_action_response = 461;
|
||||
}
|
||||
|
||||
reserved 87 to 88;
|
||||
|
||||
@@ -94,6 +94,10 @@ messages!(
|
||||
(GetDeclarationResponse, Background),
|
||||
(GetDefinition, Background),
|
||||
(GetDefinitionResponse, Background),
|
||||
(GetEditPredictionDefinition, Background),
|
||||
(GetEditPredictionDefinitionResponse, Background),
|
||||
(GetEditPredictionTypeDefinition, Background),
|
||||
(GetEditPredictionTypeDefinitionResponse, Background),
|
||||
(GetDocumentHighlights, Background),
|
||||
(GetDocumentHighlightsResponse, Background),
|
||||
(GetDocumentSymbols, Background),
|
||||
@@ -415,6 +419,14 @@ request_messages!(
|
||||
(GetCodeActions, GetCodeActionsResponse),
|
||||
(GetCompletions, GetCompletionsResponse),
|
||||
(GetDefinition, GetDefinitionResponse),
|
||||
(
|
||||
GetEditPredictionDefinition,
|
||||
GetEditPredictionDefinitionResponse
|
||||
),
|
||||
(
|
||||
GetEditPredictionTypeDefinition,
|
||||
GetEditPredictionTypeDefinitionResponse
|
||||
),
|
||||
(GetDeclaration, GetDeclarationResponse),
|
||||
(GetImplementation, GetImplementationResponse),
|
||||
(GetDocumentHighlights, GetDocumentHighlightsResponse),
|
||||
@@ -610,6 +622,16 @@ lsp_messages!(
|
||||
(GetCodeLens, GetCodeLensResponse, true),
|
||||
(GetDocumentDiagnostics, GetDocumentDiagnosticsResponse, true),
|
||||
(GetDefinition, GetDefinitionResponse, true),
|
||||
(
|
||||
GetEditPredictionDefinition,
|
||||
GetEditPredictionDefinitionResponse,
|
||||
true
|
||||
),
|
||||
(
|
||||
GetEditPredictionTypeDefinition,
|
||||
GetEditPredictionTypeDefinitionResponse,
|
||||
true
|
||||
),
|
||||
(GetDeclaration, GetDeclarationResponse, true),
|
||||
(GetTypeDefinition, GetTypeDefinitionResponse, true),
|
||||
(GetImplementation, GetImplementationResponse, true),
|
||||
@@ -650,6 +672,8 @@ entity_messages!(
|
||||
GetCodeLens,
|
||||
GetCompletions,
|
||||
GetDefinition,
|
||||
GetEditPredictionDefinition,
|
||||
GetEditPredictionTypeDefinition,
|
||||
GetDeclaration,
|
||||
GetImplementation,
|
||||
GetDocumentHighlights,
|
||||
@@ -987,6 +1011,12 @@ impl LspQuery {
|
||||
("GetDocumentDiagnostics", false)
|
||||
}
|
||||
Some(lsp_query::Request::GetDefinition(_)) => ("GetDefinition", false),
|
||||
Some(lsp_query::Request::GetEditPredictionDefinition(_)) => {
|
||||
("GetEditPredictionDefinition", false)
|
||||
}
|
||||
Some(lsp_query::Request::GetEditPredictionTypeDefinition(_)) => {
|
||||
("GetEditPredictionTypeDefinition", false)
|
||||
}
|
||||
Some(lsp_query::Request::GetDeclaration(_)) => ("GetDeclaration", false),
|
||||
Some(lsp_query::Request::GetTypeDefinition(_)) => ("GetTypeDefinition", false),
|
||||
Some(lsp_query::Request::GetImplementation(_)) => ("GetImplementation", false),
|
||||
|
||||
@@ -396,12 +396,18 @@ impl AnyProtoClient {
|
||||
Response::GetDefinitionResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetEditPredictionDefinitionResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetDeclarationResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetTypeDefinitionResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetEditPredictionTypeDefinitionResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
Response::GetImplementationResponse(response) => {
|
||||
to_any_envelope(&envelope, response)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user