vim: Add 'gf' command, make files cmd-clickable (#16534)

Release Notes:

- vim: Added `gf` command to open files under the cursor.
- Filenames can now be `cmd`/`ctrl`-clicked, which opens them.

TODOs:

- [x] `main_test.go` <-- works
- [x] `./my-pkg/my_pkg.go` <-- works
- [x] `../go.mod` <-- works
- [x] `my-pkg/my_pkg.go` <-- works
- [x] `my-pkg/subpkg/subpkg_test.go` <-- works
- [x] `file\ with\ space\ in\ it.txt` <-- works
- [x] `"file\ with\ space\ in\ it.txt"` <-- works
- [x] `"main_test.go"` <-- works
- [x] `/Users/thorstenball/.vimrc` <-- works, but only locally
- [x] `~/.vimrc` <--works, but only locally
- [x] Get it working over collab
- [x] Get hover links working

Demo:



https://github.com/user-attachments/assets/26af7f3b-c392-4aaf-849a-95d6c3b00067

Collab demo:




https://github.com/user-attachments/assets/272598bd-0e82-4556-8f9c-ba53d3a95682
This commit is contained in:
Thorsten Ball 2024-08-22 14:27:11 +02:00 committed by GitHub
parent 1e39d407c2
commit db0c1fd592
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 579 additions and 43 deletions

View file

@ -649,16 +649,17 @@ impl DirectoryLister {
};
"~/".to_string()
}
pub fn list_directory(&self, query: String, cx: &mut AppContext) -> Task<Result<Vec<PathBuf>>> {
pub fn list_directory(&self, path: String, cx: &mut AppContext) -> Task<Result<Vec<PathBuf>>> {
match self {
DirectoryLister::Project(project) => {
project.update(cx, |project, cx| project.list_directory(query, cx))
project.update(cx, |project, cx| project.list_directory(path, cx))
}
DirectoryLister::Local(fs) => {
let fs = fs.clone();
cx.background_executor().spawn(async move {
let mut results = vec![];
let expanded = shellexpand::tilde(&query);
let expanded = shellexpand::tilde(&path);
let query = Path::new(expanded.as_ref());
let mut response = fs.read_dir(query).await?;
while let Some(path) = response.next().await {
@ -7769,6 +7770,88 @@ impl Project {
}
}
// Returns the resolved version of `path`, that was found in `buffer`, if it exists.
pub fn resolve_existing_file_path(
&self,
path: &str,
buffer: &Model<Buffer>,
cx: &mut ModelContext<Self>,
) -> Task<Option<ResolvedPath>> {
// TODO: ssh based remoting.
if self.ssh_session.is_some() {
return Task::ready(None);
}
if self.is_local() {
let expanded = PathBuf::from(shellexpand::tilde(&path).into_owned());
if expanded.is_absolute() {
let fs = self.fs.clone();
cx.background_executor().spawn(async move {
let path = expanded.as_path();
let exists = fs.is_file(path).await;
exists.then(|| ResolvedPath::AbsPath(expanded))
})
} else {
self.resolve_path_in_worktrees(expanded, buffer, cx)
}
} else {
let path = PathBuf::from(path);
if path.is_absolute() || path.starts_with("~") {
return Task::ready(None);
}
self.resolve_path_in_worktrees(path, buffer, cx)
}
}
fn resolve_path_in_worktrees(
&self,
path: PathBuf,
buffer: &Model<Buffer>,
cx: &mut ModelContext<Self>,
) -> Task<Option<ResolvedPath>> {
let mut candidates = vec![path.clone()];
if let Some(file) = buffer.read(cx).file() {
if let Some(dir) = file.path().parent() {
let joined = dir.to_path_buf().join(path);
candidates.push(joined);
}
}
let worktrees = self.worktrees(cx).collect::<Vec<_>>();
cx.spawn(|_, mut cx| async move {
for worktree in worktrees {
for candidate in candidates.iter() {
let path = worktree
.update(&mut cx, |worktree, _| {
let root_entry_path = &worktree.root_entry().unwrap().path;
let resolved = resolve_path(&root_entry_path, candidate);
let stripped =
resolved.strip_prefix(&root_entry_path).unwrap_or(&resolved);
worktree.entry_for_path(stripped).map(|entry| {
ResolvedPath::ProjectPath(ProjectPath {
worktree_id: worktree.id(),
path: entry.path.clone(),
})
})
})
.ok()?;
if path.is_some() {
return path;
}
}
}
None
})
}
pub fn list_directory(
&self,
query: String,
@ -11230,6 +11313,14 @@ fn resolve_path(base: &Path, path: &Path) -> PathBuf {
result
}
/// ResolvedPath is a path that has been resolved to either a ProjectPath
/// or an AbsPath and that *exists*.
#[derive(Debug, Clone)]
pub enum ResolvedPath {
ProjectPath(ProjectPath),
AbsPath(PathBuf),
}
impl Item for Buffer {
fn try_open(
project: &Model<Project>,