Start on Project::definition that only works locally (for now)

This commit is contained in:
Antonio Scandurra 2022-01-20 12:11:41 +01:00
parent 11a83d01c2
commit cbbf7391e8
3 changed files with 176 additions and 12 deletions

View file

@ -11,11 +11,14 @@ use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
use gpui::{
AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task,
};
use language::{Buffer, DiagnosticEntry, Language, LanguageRegistry};
use language::{
Bias, Buffer, DiagnosticEntry, File as _, Language, LanguageRegistry, ToOffset, ToPointUtf16,
};
use lsp::{DiagnosticSeverity, LanguageServer};
use postage::{prelude::Stream, watch};
use smol::block_on;
use std::{
ops::Range,
path::{Path, PathBuf},
sync::{atomic::AtomicBool, Arc},
};
@ -83,6 +86,13 @@ pub struct DiagnosticSummary {
pub hint_count: usize,
}
#[derive(Debug)]
pub struct Definition {
pub source_range: Option<Range<language::Anchor>>,
pub target_buffer: ModelHandle<Buffer>,
pub target_range: Range<language::Anchor>,
}
impl DiagnosticSummary {
fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
let mut this = Self {
@ -487,7 +497,7 @@ impl Project {
abs_path: PathBuf,
cx: &mut ModelContext<Project>,
) -> Task<Result<()>> {
let worktree_task = self.worktree_for_abs_path(&abs_path, cx);
let worktree_task = self.find_or_create_worktree_for_abs_path(&abs_path, cx);
cx.spawn(|this, mut cx| async move {
let (worktree, path) = worktree_task.await?;
worktree
@ -691,26 +701,180 @@ impl Project {
Ok(())
}
pub fn worktree_for_abs_path(
pub fn definition<T: ToOffset>(
&self,
source_buffer_handle: &ModelHandle<Buffer>,
position: T,
cx: &mut ModelContext<Self>,
) -> Task<Result<Vec<Definition>>> {
let source_buffer_handle = source_buffer_handle.clone();
let buffer = source_buffer_handle.read(cx);
let worktree;
let buffer_abs_path;
if let Some(file) = File::from_dyn(buffer.file()) {
worktree = file.worktree.clone();
buffer_abs_path = file.abs_path();
} else {
return Task::ready(Err(anyhow!("buffer does not belong to any worktree")));
};
if worktree.read(cx).as_local().is_some() {
let point = buffer.offset_to_point_utf16(position.to_offset(buffer));
let buffer_abs_path = buffer_abs_path.unwrap();
let lang_name;
let lang_server;
if let Some(lang) = buffer.language() {
lang_name = lang.name().to_string();
if let Some(server) = self
.language_servers
.get(&(worktree.read(cx).id(), lang_name.clone()))
{
lang_server = server.clone();
} else {
return Task::ready(Err(anyhow!("buffer does not have a language server")));
};
} else {
return Task::ready(Err(anyhow!("buffer does not have a language")));
}
cx.spawn(|this, mut cx| async move {
let response = lang_server
.request::<lsp::request::GotoDefinition>(lsp::GotoDefinitionParams {
text_document_position_params: lsp::TextDocumentPositionParams {
text_document: lsp::TextDocumentIdentifier::new(
lsp::Url::from_file_path(&buffer_abs_path).unwrap(),
),
position: lsp::Position::new(point.row, point.column),
},
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
})
.await?;
let mut definitions = Vec::new();
if let Some(response) = response {
let mut unresolved_locations = Vec::new();
match response {
lsp::GotoDefinitionResponse::Scalar(loc) => {
unresolved_locations.push((None, loc.uri, loc.range));
}
lsp::GotoDefinitionResponse::Array(locs) => {
unresolved_locations
.extend(locs.into_iter().map(|l| (None, l.uri, l.range)));
}
lsp::GotoDefinitionResponse::Link(links) => {
unresolved_locations.extend(links.into_iter().map(|l| {
(
l.origin_selection_range,
l.target_uri,
l.target_selection_range,
)
}));
}
}
for (source_range, target_uri, target_range) in unresolved_locations {
let abs_path = target_uri
.to_file_path()
.map_err(|_| anyhow!("invalid target path"))?;
let (worktree, relative_path) = if let Some(result) = this
.read_with(&cx, |this, cx| {
this.find_worktree_for_abs_path(&abs_path, cx)
}) {
result
} else {
let (worktree, relative_path) = this
.update(&mut cx, |this, cx| {
this.create_worktree_for_abs_path(&abs_path, cx)
})
.await?;
this.update(&mut cx, |this, cx| {
this.language_servers.insert(
(worktree.read(cx).id(), lang_name.clone()),
lang_server.clone(),
);
});
(worktree, relative_path)
};
let project_path = ProjectPath {
worktree_id: worktree.read_with(&cx, |worktree, _| worktree.id()),
path: relative_path.into(),
};
let target_buffer_handle = this
.update(&mut cx, |this, cx| this.open_buffer(project_path, cx))
.await?;
cx.read(|cx| {
let source_buffer = source_buffer_handle.read(cx);
let target_buffer = target_buffer_handle.read(cx);
let source_range = source_range.map(|range| {
let start = source_buffer
.clip_point_utf16(range.start.to_point_utf16(), Bias::Left);
let end = source_buffer
.clip_point_utf16(range.end.to_point_utf16(), Bias::Left);
source_buffer.anchor_after(start)..source_buffer.anchor_before(end)
});
let target_start = target_buffer
.clip_point_utf16(target_range.start.to_point_utf16(), Bias::Left);
let target_end = target_buffer
.clip_point_utf16(target_range.end.to_point_utf16(), Bias::Left);
definitions.push(Definition {
source_range,
target_buffer: target_buffer_handle,
target_range: target_buffer.anchor_after(target_start)
..target_buffer.anchor_before(target_end),
});
});
}
}
Ok(definitions)
})
} else {
todo!()
}
}
pub fn find_or_create_worktree_for_abs_path(
&self,
abs_path: &Path,
cx: &mut ModelContext<Self>,
) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
if let Some((tree, relative_path)) = self.find_worktree_for_abs_path(abs_path, cx) {
Task::ready(Ok((tree.clone(), relative_path.into())))
} else {
self.create_worktree_for_abs_path(abs_path, cx)
}
}
fn create_worktree_for_abs_path(
&self,
abs_path: &Path,
cx: &mut ModelContext<Self>,
) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
let worktree = self.add_local_worktree(abs_path, cx);
cx.background().spawn(async move {
let worktree = worktree.await?;
Ok((worktree, PathBuf::new()))
})
}
fn find_worktree_for_abs_path(
&self,
abs_path: &Path,
cx: &AppContext,
) -> Option<(ModelHandle<Worktree>, PathBuf)> {
for tree in &self.worktrees {
if let Some(relative_path) = tree
.read(cx)
.as_local()
.and_then(|t| abs_path.strip_prefix(t.abs_path()).ok())
{
return Task::ready(Ok((tree.clone(), relative_path.into())));
return Some((tree.clone(), relative_path.into()));
}
}
let worktree = self.add_local_worktree(abs_path, cx);
cx.background().spawn(async move {
let worktree = worktree.await?;
Ok((worktree, PathBuf::new()))
})
None
}
pub fn is_shared(&self) -> bool {

View file

@ -1947,7 +1947,7 @@ impl fmt::Debug for Snapshot {
#[derive(Clone, PartialEq)]
pub struct File {
entry_id: Option<usize>,
worktree: ModelHandle<Worktree>,
pub worktree: ModelHandle<Worktree>,
worktree_path: Arc<Path>,
pub path: Arc<Path>,
pub mtime: SystemTime,

View file

@ -684,7 +684,7 @@ impl Workspace {
cx: &mut ViewContext<Self>,
) -> Task<Result<ProjectPath>> {
let entry = self.project().update(cx, |project, cx| {
project.worktree_for_abs_path(abs_path, cx)
project.find_or_create_worktree_for_abs_path(abs_path, cx)
});
cx.spawn(|_, cx| async move {
let (worktree, path) = entry.await?;