From 05b161118c753e72fa61710b85c91f07f8e3c626 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Jul 2023 17:05:42 -0700 Subject: [PATCH 01/19] Don't call git status when ignored files change --- crates/fs/src/repository.rs | 1 + crates/project/src/worktree.rs | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 611427c0a841b27e1bb3e771aa4d0edc264da5db..0a43c7ee263cab972763e6c798c778c09fba10f8 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -109,6 +109,7 @@ impl GitRepository for LibGitRepository { } } } + fn branches(&self) -> Result> { let local_branches = self.branches(Some(BranchType::Local))?; let valid_branches = local_branches diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 2ce1693459c45e203ebd6a1752ad6093d0785ee3..880313b6b1a16d75abf5538feab8797e4904598a 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2162,10 +2162,12 @@ impl BackgroundScannerState { let path = entry.path.clone(); let ignore_stack = self.snapshot.ignore_stack_for_abs_path(&abs_path, true); let mut ancestor_inodes = self.snapshot.ancestor_inodes_for_path(&path); - let containing_repository = self - .snapshot - .local_repo_for_path(&path) - .map(|(path, repo)| (path, repo.repo_ptr.lock().statuses())); + let mut containing_repository = None; + if !ignore_stack.is_all() { + if let Some((workdir_path, repo)) = self.snapshot.local_repo_for_path(&path) { + containing_repository = Some((workdir_path, repo.repo_ptr.lock().statuses())); + } + } if !ancestor_inodes.contains(&entry.inode) { ancestor_inodes.insert(entry.inode); scan_job_tx @@ -3517,10 +3519,12 @@ impl BackgroundScanner { } } else { child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, false); - - if let Some((repository_dir, statuses)) = &job.containing_repository { - if let Ok(repo_path) = child_entry.path.strip_prefix(&repository_dir.0) { - child_entry.git_status = statuses.get(&RepoPath(repo_path.into())).copied(); + if !child_entry.is_ignored { + if let Some((repository_dir, statuses)) = &job.containing_repository { + if let Ok(repo_path) = child_entry.path.strip_prefix(&repository_dir.0) { + let repo_path = RepoPath(repo_path.into()); + child_entry.git_status = statuses.get(&repo_path).copied(); + } } } } From ff0864026ed9c981ed15252da4d4541e2190bfd9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Jul 2023 17:08:31 -0700 Subject: [PATCH 02/19] Only fetch statuses for changed paths --- crates/fs/src/repository.rs | 14 +++++++++----- crates/project/src/worktree.rs | 9 ++++++--- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 0a43c7ee263cab972763e6c798c778c09fba10f8..851d495b01e5753fc9110f907254b5f82908621b 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -27,7 +27,7 @@ pub trait GitRepository: Send { fn reload_index(&self); fn load_index_text(&self, relative_file_path: &Path) -> Option; fn branch_name(&self) -> Option; - fn statuses(&self) -> TreeMap; + fn statuses(&self, path_prefix: &Path) -> TreeMap; fn status(&self, path: &RepoPath) -> Result>; fn branches(&self) -> Result>; fn change_branch(&self, _: &str) -> Result<()>; @@ -78,9 +78,11 @@ impl GitRepository for LibGitRepository { Some(branch.to_string()) } - fn statuses(&self) -> TreeMap { + fn statuses(&self, path_prefix: &Path) -> TreeMap { let mut map = TreeMap::default(); - if let Some(statuses) = self.statuses(None).log_err() { + let mut options = git2::StatusOptions::new(); + options.pathspec(path_prefix); + if let Some(statuses) = self.statuses(Some(&mut options)).log_err() { for status in statuses .iter() .filter(|status| !status.status().contains(git2::Status::IGNORED)) @@ -201,11 +203,13 @@ impl GitRepository for FakeGitRepository { state.branch_name.clone() } - fn statuses(&self) -> TreeMap { + fn statuses(&self, path_prefix: &Path) -> TreeMap { let mut map = TreeMap::default(); let state = self.state.lock(); for (repo_path, status) in state.worktree_statuses.iter() { - map.insert(repo_path.to_owned(), status.to_owned()); + if repo_path.0.starts_with(path_prefix) { + map.insert(repo_path.to_owned(), status.to_owned()); + } } map } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 880313b6b1a16d75abf5538feab8797e4904598a..85cf03246487c143075c763f357ae47d92d458aa 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2165,7 +2165,10 @@ impl BackgroundScannerState { let mut containing_repository = None; if !ignore_stack.is_all() { if let Some((workdir_path, repo)) = self.snapshot.local_repo_for_path(&path) { - containing_repository = Some((workdir_path, repo.repo_ptr.lock().statuses())); + if let Ok(repo_path) = path.strip_prefix(&workdir_path.0) { + containing_repository = + Some((workdir_path, repo.repo_ptr.lock().statuses(repo_path))); + } } } if !ancestor_inodes.contains(&entry.inode) { @@ -2357,7 +2360,7 @@ impl BackgroundScannerState { .repository_entries .update(&work_dir, |entry| entry.branch = branch.map(Into::into)); - let statuses = repository.statuses(); + let statuses = repository.statuses(Path::new("")); self.update_git_statuses(&work_dir, &statuses); } } @@ -2415,7 +2418,7 @@ impl BackgroundScannerState { }, ); - let statuses = repo_lock.statuses(); + let statuses = repo_lock.statuses(Path::new("")); self.update_git_statuses(&work_directory, &statuses); drop(repo_lock); From 51d311affd108118f03aef42f2ad4937bfb334fb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 21 Jul 2023 17:51:00 -0700 Subject: [PATCH 03/19] Compute unstaged git status separately, to take advantage of our cached file mtimes --- crates/fs/src/repository.rs | 34 ++++++++++++-- crates/project/src/worktree.rs | 83 +++++++++++++++++++++++++--------- 2 files changed, 92 insertions(+), 25 deletions(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 851d495b01e5753fc9110f907254b5f82908621b..54f80c26a2e01936dc19e4ac09af51e5e3d0e152 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -10,6 +10,7 @@ use std::{ os::unix::prelude::OsStrExt, path::{Component, Path, PathBuf}, sync::Arc, + time::SystemTime, }; use sum_tree::{MapSeekTarget, TreeMap}; use util::ResultExt; @@ -27,7 +28,8 @@ pub trait GitRepository: Send { fn reload_index(&self); fn load_index_text(&self, relative_file_path: &Path) -> Option; fn branch_name(&self) -> Option; - fn statuses(&self, path_prefix: &Path) -> TreeMap; + fn staged_statuses(&self, path_prefix: &Path) -> TreeMap; + fn unstaged_status(&self, path: &RepoPath, mtime: SystemTime) -> Option; fn status(&self, path: &RepoPath) -> Result>; fn branches(&self) -> Result>; fn change_branch(&self, _: &str) -> Result<()>; @@ -78,10 +80,11 @@ impl GitRepository for LibGitRepository { Some(branch.to_string()) } - fn statuses(&self, path_prefix: &Path) -> TreeMap { + fn staged_statuses(&self, path_prefix: &Path) -> TreeMap { let mut map = TreeMap::default(); let mut options = git2::StatusOptions::new(); options.pathspec(path_prefix); + options.disable_pathspec_match(true); if let Some(statuses) = self.statuses(Some(&mut options)).log_err() { for status in statuses .iter() @@ -98,6 +101,27 @@ impl GitRepository for LibGitRepository { map } + fn unstaged_status(&self, path: &RepoPath, mtime: SystemTime) -> Option { + let index = self.index().log_err()?; + if let Some(entry) = index.get_path(&path, 0) { + let mtime = mtime.duration_since(SystemTime::UNIX_EPOCH).log_err()?; + if entry.mtime.seconds() == mtime.as_secs() as i32 + && entry.mtime.nanoseconds() == mtime.subsec_nanos() + { + None + } else { + let mut options = git2::StatusOptions::new(); + options.pathspec(&path.0); + options.disable_pathspec_match(true); + let statuses = self.statuses(Some(&mut options)).log_err()?; + let status = statuses.get(0).and_then(|s| read_status(s.status())); + status + } + } else { + Some(GitFileStatus::Added) + } + } + fn status(&self, path: &RepoPath) -> Result> { let status = self.status_file(path); match status { @@ -203,7 +227,7 @@ impl GitRepository for FakeGitRepository { state.branch_name.clone() } - fn statuses(&self, path_prefix: &Path) -> TreeMap { + fn staged_statuses(&self, path_prefix: &Path) -> TreeMap { let mut map = TreeMap::default(); let state = self.state.lock(); for (repo_path, status) in state.worktree_statuses.iter() { @@ -214,6 +238,10 @@ impl GitRepository for FakeGitRepository { map } + fn unstaged_status(&self, _path: &RepoPath, _mtime: SystemTime) -> Option { + None + } + fn status(&self, path: &RepoPath) -> Result> { let state = self.state.lock(); Ok(state.worktree_statuses.get(path).cloned()) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 85cf03246487c143075c763f357ae47d92d458aa..c4b6ed6ca09d0189a86d332a060e2e8cd64333df 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2166,8 +2166,11 @@ impl BackgroundScannerState { if !ignore_stack.is_all() { if let Some((workdir_path, repo)) = self.snapshot.local_repo_for_path(&path) { if let Ok(repo_path) = path.strip_prefix(&workdir_path.0) { - containing_repository = - Some((workdir_path, repo.repo_ptr.lock().statuses(repo_path))); + containing_repository = Some(( + workdir_path, + repo.repo_ptr.clone(), + repo.repo_ptr.lock().staged_statuses(repo_path), + )); } } } @@ -2360,8 +2363,7 @@ impl BackgroundScannerState { .repository_entries .update(&work_dir, |entry| entry.branch = branch.map(Into::into)); - let statuses = repository.statuses(Path::new("")); - self.update_git_statuses(&work_dir, &statuses); + self.update_git_statuses(&work_dir, &*repository); } } } @@ -2386,7 +2388,11 @@ impl BackgroundScannerState { &mut self, dot_git_path: Arc, fs: &dyn Fs, - ) -> Option<(RepositoryWorkDirectory, TreeMap)> { + ) -> Option<( + RepositoryWorkDirectory, + Arc>, + TreeMap, + )> { log::info!("build git repository {:?}", dot_git_path); let work_dir_path: Arc = dot_git_path.parent().unwrap().into(); @@ -2418,27 +2424,28 @@ impl BackgroundScannerState { }, ); - let statuses = repo_lock.statuses(Path::new("")); - self.update_git_statuses(&work_directory, &statuses); + let staged_statuses = self.update_git_statuses(&work_directory, &*repo_lock); drop(repo_lock); self.snapshot.git_repositories.insert( work_dir_id, LocalRepositoryEntry { git_dir_scan_id: 0, - repo_ptr: repository, + repo_ptr: repository.clone(), git_dir_path: dot_git_path.clone(), }, ); - Some((work_directory, statuses)) + Some((work_directory, repository, staged_statuses)) } fn update_git_statuses( &mut self, work_directory: &RepositoryWorkDirectory, - statuses: &TreeMap, - ) { + repo: &dyn GitRepository, + ) -> TreeMap { + let staged_statuses = repo.staged_statuses(Path::new("")); + let mut changes = vec![]; let mut edits = vec![]; @@ -2451,7 +2458,10 @@ impl BackgroundScannerState { continue; }; let repo_path = RepoPath(repo_path.to_path_buf()); - let git_file_status = statuses.get(&repo_path).copied(); + let git_file_status = combine_git_statuses( + staged_statuses.get(&repo_path).copied(), + repo.unstaged_status(&repo_path, entry.mtime), + ); if entry.git_status != git_file_status { entry.git_status = git_file_status; changes.push(entry.path.clone()); @@ -2461,6 +2471,7 @@ impl BackgroundScannerState { self.snapshot.entries_by_path.edit(edits, &()); util::extend_sorted(&mut self.changed_paths, changes, usize::MAX, Ord::cmp); + staged_statuses } } @@ -3523,10 +3534,17 @@ impl BackgroundScanner { } else { child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, false); if !child_entry.is_ignored { - if let Some((repository_dir, statuses)) = &job.containing_repository { + if let Some((repository_dir, repository, staged_statuses)) = + &job.containing_repository + { if let Ok(repo_path) = child_entry.path.strip_prefix(&repository_dir.0) { let repo_path = RepoPath(repo_path.into()); - child_entry.git_status = statuses.get(&repo_path).copied(); + child_entry.git_status = combine_git_statuses( + staged_statuses.get(&repo_path).copied(), + repository + .lock() + .unstaged_status(&repo_path, child_entry.mtime), + ); } } } @@ -3637,13 +3655,11 @@ impl BackgroundScanner { if let Some((work_dir, repo)) = state.snapshot.local_repo_for_path(&path) { - if let Ok(path) = path.strip_prefix(work_dir.0) { - fs_entry.git_status = repo - .repo_ptr - .lock() - .status(&RepoPath(path.into())) - .log_err() - .flatten() + if let Ok(repo_path) = path.strip_prefix(work_dir.0) { + let repo_path = RepoPath(repo_path.into()); + let repo = repo.repo_ptr.lock(); + fs_entry.git_status = + repo.status(&repo_path).log_err().flatten(); } } } @@ -3997,7 +4013,11 @@ struct ScanJob { scan_queue: Sender, ancestor_inodes: TreeSet, is_external: bool, - containing_repository: Option<(RepositoryWorkDirectory, TreeMap)>, + containing_repository: Option<( + RepositoryWorkDirectory, + Arc>, + TreeMap, + )>, } struct UpdateIgnoreStatusJob { @@ -4324,3 +4344,22 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry { } } } + +fn combine_git_statuses( + staged: Option, + unstaged: Option, +) -> Option { + if let Some(staged) = staged { + if let Some(unstaged) = unstaged { + if unstaged != staged { + Some(GitFileStatus::Modified) + } else { + Some(staged) + } + } else { + Some(staged) + } + } else { + unstaged + } +} From 6c09782aa2b132216a55d8028721eebe17786151 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Sat, 22 Jul 2023 11:53:26 -0700 Subject: [PATCH 04/19] Optimize full file status via passing in known file mtime --- crates/fs/src/repository.rs | 65 +++++++++++++++++++++++++--------- crates/project/src/worktree.rs | 3 +- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 54f80c26a2e01936dc19e4ac09af51e5e3d0e152..e6a41839b62b1bf568ee12850f4704af81605ec0 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -1,6 +1,6 @@ use anyhow::Result; use collections::HashMap; -use git2::{BranchType, ErrorCode}; +use git2::{BranchType, StatusShow}; use parking_lot::Mutex; use rpc::proto; use serde_derive::{Deserialize, Serialize}; @@ -28,9 +28,25 @@ pub trait GitRepository: Send { fn reload_index(&self); fn load_index_text(&self, relative_file_path: &Path) -> Option; fn branch_name(&self) -> Option; + + /// Get the statuses of all of the files in the index that start with the given + /// path and have changes with resepect to the HEAD commit. This is fast because + /// the index stores hashes of trees, so that unchanged directories can be skipped. fn staged_statuses(&self, path_prefix: &Path) -> TreeMap; + + /// Get the status of a given file in the working directory with respect to + /// the index. In the common case, when there are no changes, this only requires + /// an index lookup. The index stores the mtime of each file when it was added, + /// so there's no work to do if the mtime matches. fn unstaged_status(&self, path: &RepoPath, mtime: SystemTime) -> Option; - fn status(&self, path: &RepoPath) -> Result>; + + /// Get the status of a given file in the working directory with respect to + /// the HEAD commit. In the common case, when there are no changes, this only + /// requires an index lookup and blob comparison between the index and the HEAD + /// commit. The index stores the mtime of each file when it was added, so there's + /// no need to consider the working directory file if the mtime matches. + fn status(&self, path: &RepoPath, mtime: SystemTime) -> Option; + fn branches(&self) -> Result>; fn change_branch(&self, _: &str) -> Result<()>; fn create_branch(&self, _: &str) -> Result<()>; @@ -42,7 +58,6 @@ impl std::fmt::Debug for dyn GitRepository { } } -#[async_trait::async_trait] impl GitRepository for LibGitRepository { fn reload_index(&self) { if let Ok(mut index) = self.index() { @@ -122,18 +137,21 @@ impl GitRepository for LibGitRepository { } } - fn status(&self, path: &RepoPath) -> Result> { - let status = self.status_file(path); - match status { - Ok(status) => Ok(read_status(status)), - Err(e) => { - if e.code() == ErrorCode::NotFound { - Ok(None) - } else { - Err(e.into()) - } - } + fn status(&self, path: &RepoPath, mtime: SystemTime) -> Option { + let mut options = git2::StatusOptions::new(); + options.pathspec(&path.0); + options.disable_pathspec_match(true); + + // If the file has not changed since it was added to the index, then + // there's no need to examine the working directory file: just compare + // the blob in the index to the one in the HEAD commit. + if matches_index(self, path, mtime) { + options.show(StatusShow::Index); } + + let statuses = self.statuses(Some(&mut options)).log_err()?; + let status = statuses.get(0).and_then(|s| read_status(s.status())); + status } fn branches(&self) -> Result> { @@ -178,6 +196,21 @@ impl GitRepository for LibGitRepository { } } +fn matches_index(repo: &LibGitRepository, path: &RepoPath, mtime: SystemTime) -> bool { + if let Some(index) = repo.index().log_err() { + if let Some(entry) = index.get_path(&path, 0) { + if let Some(mtime) = mtime.duration_since(SystemTime::UNIX_EPOCH).log_err() { + if entry.mtime.seconds() == mtime.as_secs() as i32 + && entry.mtime.nanoseconds() == mtime.subsec_nanos() + { + return true; + } + } + } + } + false +} + fn read_status(status: git2::Status) -> Option { if status.contains(git2::Status::CONFLICTED) { Some(GitFileStatus::Conflict) @@ -242,9 +275,9 @@ impl GitRepository for FakeGitRepository { None } - fn status(&self, path: &RepoPath) -> Result> { + fn status(&self, path: &RepoPath, _mtime: SystemTime) -> Option { let state = self.state.lock(); - Ok(state.worktree_statuses.get(path).cloned()) + state.worktree_statuses.get(path).cloned() } fn branches(&self) -> Result> { diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index c4b6ed6ca09d0189a86d332a060e2e8cd64333df..b0795818b8d62557b688313fcfe8d017c2b55795 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3658,8 +3658,7 @@ impl BackgroundScanner { if let Ok(repo_path) = path.strip_prefix(work_dir.0) { let repo_path = RepoPath(repo_path.into()); let repo = repo.repo_ptr.lock(); - fs_entry.git_status = - repo.status(&repo_path).log_err().flatten(); + fs_entry.git_status = repo.status(&repo_path, fs_entry.mtime); } } } From b338ffe8d818411d0c4a824d7e3f5f92711794da Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Sat, 22 Jul 2023 17:47:36 -0700 Subject: [PATCH 05/19] Rely on git status for any paths not matching the git index --- crates/fs/src/repository.rs | 38 ++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index e6a41839b62b1bf568ee12850f4704af81605ec0..47e1bc1aab21405444171afb49e58ab6115a91ae 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -97,9 +97,11 @@ impl GitRepository for LibGitRepository { fn staged_statuses(&self, path_prefix: &Path) -> TreeMap { let mut map = TreeMap::default(); + let mut options = git2::StatusOptions::new(); options.pathspec(path_prefix); options.disable_pathspec_match(true); + if let Some(statuses) = self.statuses(Some(&mut options)).log_err() { for status in statuses .iter() @@ -117,30 +119,32 @@ impl GitRepository for LibGitRepository { } fn unstaged_status(&self, path: &RepoPath, mtime: SystemTime) -> Option { - let index = self.index().log_err()?; - if let Some(entry) = index.get_path(&path, 0) { - let mtime = mtime.duration_since(SystemTime::UNIX_EPOCH).log_err()?; - if entry.mtime.seconds() == mtime.as_secs() as i32 - && entry.mtime.nanoseconds() == mtime.subsec_nanos() - { - None - } else { - let mut options = git2::StatusOptions::new(); - options.pathspec(&path.0); - options.disable_pathspec_match(true); - let statuses = self.statuses(Some(&mut options)).log_err()?; - let status = statuses.get(0).and_then(|s| read_status(s.status())); - status - } - } else { - Some(GitFileStatus::Added) + // If the file has not changed since it was added to the index, then + // there can't be any changes. + if matches_index(self, path, mtime) { + return None; } + + let mut options = git2::StatusOptions::new(); + options.pathspec(&path.0); + options.disable_pathspec_match(true); + options.include_untracked(true); + options.recurse_untracked_dirs(true); + options.include_unmodified(true); + options.show(StatusShow::Workdir); + + let statuses = self.statuses(Some(&mut options)).log_err()?; + let status = statuses.get(0).and_then(|s| read_status(s.status())); + status } fn status(&self, path: &RepoPath, mtime: SystemTime) -> Option { let mut options = git2::StatusOptions::new(); options.pathspec(&path.0); options.disable_pathspec_match(true); + options.include_untracked(true); + options.recurse_untracked_dirs(true); + options.include_unmodified(true); // If the file has not changed since it was added to the index, then // there's no need to examine the working directory file: just compare From a3a9d024ba3323ae4b67e2dea4ecfb88ce6a489b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Sat, 22 Jul 2023 17:53:58 -0700 Subject: [PATCH 06/19] Fix filtering of staged statuses --- crates/fs/src/repository.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 47e1bc1aab21405444171afb49e58ab6115a91ae..f4678d933f2109d607bd1ab79dd08271817d1655 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -101,18 +101,17 @@ impl GitRepository for LibGitRepository { let mut options = git2::StatusOptions::new(); options.pathspec(path_prefix); options.disable_pathspec_match(true); + options.show(StatusShow::Index); if let Some(statuses) = self.statuses(Some(&mut options)).log_err() { - for status in statuses - .iter() - .filter(|status| !status.status().contains(git2::Status::IGNORED)) - { + for status in statuses.iter() { let path = RepoPath(PathBuf::from(OsStr::from_bytes(status.path_bytes()))); - let Some(status) = read_status(status.status()) else { - continue - }; - - map.insert(path, status) + let status = status.status(); + if !status.contains(git2::Status::IGNORED) { + if let Some(status) = read_status(status) { + map.insert(path, status) + } + } } } map From 8fff0b0ff8ade2b43f2608824a3cc7b3878afae3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Sun, 23 Jul 2023 21:34:12 -0700 Subject: [PATCH 07/19] Fix pathspec in staged_statuses Enable non-literal matching so that directory paths match all files contained within them. --- crates/fs/src/repository.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index f4678d933f2109d607bd1ab79dd08271817d1655..2b2aebe67959ed977c96ed60b36cf4c7ce3d23d2 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -100,7 +100,6 @@ impl GitRepository for LibGitRepository { let mut options = git2::StatusOptions::new(); options.pathspec(path_prefix); - options.disable_pathspec_match(true); options.show(StatusShow::Index); if let Some(statuses) = self.statuses(Some(&mut options)).log_err() { From a4914fcf3b0e80505ead8ef3734d572be027aab1 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Sun, 23 Jul 2023 16:32:14 -0700 Subject: [PATCH 08/19] Add tree-sitter-elm --- Cargo.lock | 11 ++++ Cargo.toml | 1 + crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 1 + crates/zed/src/languages/elm/config.toml | 11 ++++ crates/zed/src/languages/elm/highlights.scm | 71 +++++++++++++++++++++ crates/zed/src/languages/elm/injections.scm | 2 + 7 files changed, 98 insertions(+) create mode 100644 crates/zed/src/languages/elm/config.toml create mode 100644 crates/zed/src/languages/elm/highlights.scm create mode 100644 crates/zed/src/languages/elm/injections.scm diff --git a/Cargo.lock b/Cargo.lock index f0c8917aa28d300e1aea521f7c936c3ce918a984..eb6c51e47f69c4001e65da6fff38635e9f173ac4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7989,6 +7989,16 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-elm" +version = "5.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec83a2e1cfc69d03c8e73636e95662d6c6728539538d341b21251a77039fb94e" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-embedded-template" version = "0.20.0" @@ -9543,6 +9553,7 @@ dependencies = [ "tree-sitter-cpp", "tree-sitter-css", "tree-sitter-elixir", + "tree-sitter-elm", "tree-sitter-embedded-template", "tree-sitter-go", "tree-sitter-heex", diff --git a/Cargo.toml b/Cargo.toml index fa824115cb82b02c2428f2f1bd9889a0b93a5757..03614e61e4887c10fcd0ef6351dda5b34902d06a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,6 +112,7 @@ tree-sitter-c = "0.20.1" tree-sitter-cpp = "0.20.0" tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" } tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "4ba9dab6e2602960d95b2b625f3386c27e08084e" } +tree-sitter-elm = "5.6.4" tree-sitter-embedded-template = "0.20.0" tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" } tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index c5bf313701e76f1ea7084f7299f7596b7b0306df..df7f4bcce96d6c83b70d3b1044ca2a8c61c5bebb 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -109,6 +109,7 @@ tree-sitter-c.workspace = true tree-sitter-cpp.workspace = true tree-sitter-css.workspace = true tree-sitter-elixir.workspace = true +tree-sitter-elm.workspace = true tree-sitter-embedded-template.workspace = true tree-sitter-go.workspace = true tree-sitter-heex.workspace = true diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 365e8a30232e5dc9193b1077b16186f3c44e9d19..c7b5c59b876933b5ef553897c04ae16b85c7d53a 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -152,6 +152,7 @@ pub fn init(languages: Arc, node_runtime: Arc) { tree_sitter_php::language(), vec![Arc::new(php::IntelephenseLspAdapter::new(node_runtime))], ); + language("elm", tree_sitter_elm::language(), vec![]); } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/zed/src/languages/elm/config.toml b/crates/zed/src/languages/elm/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..5051427a93df4be56d86ac0b3a8f26a88f2cdde6 --- /dev/null +++ b/crates/zed/src/languages/elm/config.toml @@ -0,0 +1,11 @@ +name = "Elm" +path_suffixes = ["elm"] +line_comment = "-- " +block_comment = ["{- ", " -}"] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] }, + { start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] }, +] diff --git a/crates/zed/src/languages/elm/highlights.scm b/crates/zed/src/languages/elm/highlights.scm new file mode 100644 index 0000000000000000000000000000000000000000..f6be193f838db77549a879c6563bc00ac8f064f2 --- /dev/null +++ b/crates/zed/src/languages/elm/highlights.scm @@ -0,0 +1,71 @@ +[ + "if" + "then" + "else" + "let" + "in" + (case) + (of) + (backslash) + (as) + (port) + (exposing) + (alias) + (import) + (module) + (type) + ] @keyword + +[ + (arrow) + (eq) + (operator_identifier) + (colon) +] @operator + +(type_annotation(lower_case_identifier) @function) +(port_annotation(lower_case_identifier) @function) +(function_declaration_left(lower_case_identifier) @function.definition) +(function_call_expr target: (value_expr) @identifier) + +(exposed_value(lower_case_identifier) @function) +(exposed_type(upper_case_identifier) @type) + +(field_access_expr(value_expr(value_qid)) @identifier) +(lower_pattern) @variable +(record_base_identifier) @identifier + +[ + "(" + ")" +] @punctuation.bracket + +[ + "|" + "," +] @punctuation.delimiter + +(number_constant_expr) @constant + +(type_declaration(upper_case_identifier) @type) +(type_ref) @type +(type_alias_declaration name: (upper_case_identifier) @type) + +(union_variant(upper_case_identifier) @variant) +(union_pattern) @variant +(value_expr(upper_case_qid(upper_case_identifier)) @type) + +[ + (line_comment) + (block_comment) +] @comment + +(string_escape) @string.escape + +[ + (open_quote) + (close_quote) + (regular_string_part) + (open_char) + (close_char) +] @string diff --git a/crates/zed/src/languages/elm/injections.scm b/crates/zed/src/languages/elm/injections.scm new file mode 100644 index 0000000000000000000000000000000000000000..0567320675a89c6649a191fdef950c6670d65707 --- /dev/null +++ b/crates/zed/src/languages/elm/injections.scm @@ -0,0 +1,2 @@ +((glsl_content) @content + (#set! "language" "glsl")) From dd504f5965f62d4d3aa4d079b039e68461ae1487 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Sun, 23 Jul 2023 16:43:44 -0700 Subject: [PATCH 09/19] Add tree-sitter-glsl --- Cargo.lock | 10 ++ Cargo.toml | 1 + crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 1 + crates/zed/src/languages/glsl/config.toml | 9 ++ crates/zed/src/languages/glsl/highlights.scm | 118 +++++++++++++++++++ 6 files changed, 140 insertions(+) create mode 100644 crates/zed/src/languages/glsl/config.toml create mode 100644 crates/zed/src/languages/glsl/highlights.scm diff --git a/Cargo.lock b/Cargo.lock index eb6c51e47f69c4001e65da6fff38635e9f173ac4..3aa77e2ce141809e85ec3a8388dd3d34ef8aa3bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8009,6 +8009,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-glsl" +version = "0.1.4" +source = "git+https://github.com/theHamsta/tree-sitter-glsl?rev=2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3#2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-go" version = "0.19.1" @@ -9555,6 +9564,7 @@ dependencies = [ "tree-sitter-elixir", "tree-sitter-elm", "tree-sitter-embedded-template", + "tree-sitter-glsl", "tree-sitter-go", "tree-sitter-heex", "tree-sitter-html", diff --git a/Cargo.toml b/Cargo.toml index 03614e61e4887c10fcd0ef6351dda5b34902d06a..10f2160f456257f424e8afed6744db9b3ac2ecd9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,6 +114,7 @@ tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "4ba9dab6e2602960d95b2b625f3386c27e08084e" } tree-sitter-elm = "5.6.4" tree-sitter-embedded-template = "0.20.0" +tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" } tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" } tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" } tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index df7f4bcce96d6c83b70d3b1044ca2a8c61c5bebb..9bd2de0accc99f9c023779a3a99657cd31df71c1 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -111,6 +111,7 @@ tree-sitter-css.workspace = true tree-sitter-elixir.workspace = true tree-sitter-elm.workspace = true tree-sitter-embedded-template.workspace = true +tree-sitter-glsl.workspace = true tree-sitter-go.workspace = true tree-sitter-heex.workspace = true tree-sitter-json.workspace = true diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index c7b5c59b876933b5ef553897c04ae16b85c7d53a..09f5162c12f81286174859c8217316dc04faf3e9 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -153,6 +153,7 @@ pub fn init(languages: Arc, node_runtime: Arc) { vec![Arc::new(php::IntelephenseLspAdapter::new(node_runtime))], ); language("elm", tree_sitter_elm::language(), vec![]); + language("glsl", tree_sitter_glsl::language(), vec![]); } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/zed/src/languages/glsl/config.toml b/crates/zed/src/languages/glsl/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..4081a6381f6409260c558937c9321c8fdbad8f94 --- /dev/null +++ b/crates/zed/src/languages/glsl/config.toml @@ -0,0 +1,9 @@ +name = "GLSL" +path_suffixes = ["vert", "frag", "tesc", "tese", "geom", "comp"] +line_comment = "// " +block_comment = ["/* ", " */"] +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/glsl/highlights.scm b/crates/zed/src/languages/glsl/highlights.scm new file mode 100644 index 0000000000000000000000000000000000000000..e4503c6fbba298afb1f2eddb82ce960af74b03c0 --- /dev/null +++ b/crates/zed/src/languages/glsl/highlights.scm @@ -0,0 +1,118 @@ +"break" @keyword +"case" @keyword +"const" @keyword +"continue" @keyword +"default" @keyword +"do" @keyword +"else" @keyword +"enum" @keyword +"extern" @keyword +"for" @keyword +"if" @keyword +"inline" @keyword +"return" @keyword +"sizeof" @keyword +"static" @keyword +"struct" @keyword +"switch" @keyword +"typedef" @keyword +"union" @keyword +"volatile" @keyword +"while" @keyword + +"#define" @keyword +"#elif" @keyword +"#else" @keyword +"#endif" @keyword +"#if" @keyword +"#ifdef" @keyword +"#ifndef" @keyword +"#include" @keyword +(preproc_directive) @keyword + +"--" @operator +"-" @operator +"-=" @operator +"->" @operator +"=" @operator +"!=" @operator +"*" @operator +"&" @operator +"&&" @operator +"+" @operator +"++" @operator +"+=" @operator +"<" @operator +"==" @operator +">" @operator +"||" @operator + +"." @delimiter +";" @delimiter + +(string_literal) @string +(system_lib_string) @string + +(null) @constant +(number_literal) @number +(char_literal) @number + +(call_expression + function: (identifier) @function) +(call_expression + function: (field_expression + field: (field_identifier) @function)) +(function_declarator + declarator: (identifier) @function) +(preproc_function_def + name: (identifier) @function.special) + +(field_identifier) @property +(statement_identifier) @label +(type_identifier) @type +(primitive_type) @type +(sized_type_specifier) @type + +((identifier) @constant + (#match? @constant "^[A-Z][A-Z\\d_]*$")) + +(identifier) @variable + +(comment) @comment +; inherits: c + +[ + "in" + "out" + "inout" + "uniform" + "shared" + "layout" + "attribute" + "varying" + "buffer" + "coherent" + "readonly" + "writeonly" + "precision" + "highp" + "mediump" + "lowp" + "centroid" + "sample" + "patch" + "smooth" + "flat" + "noperspective" + "invariant" + "precise" +] @type.qualifier + +"subroutine" @keyword.function + +(extension_storage_class) @storageclass + +( + (identifier) @variable.builtin + (#match? @variable.builtin "^gl_") +) From 6ad0852a70402a482841d7cc3d090553a719c19b Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 24 Jul 2023 00:58:59 -0700 Subject: [PATCH 10/19] Add outline.scm for Elm --- crates/zed/src/languages/elm/outline.scm | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 crates/zed/src/languages/elm/outline.scm diff --git a/crates/zed/src/languages/elm/outline.scm b/crates/zed/src/languages/elm/outline.scm new file mode 100644 index 0000000000000000000000000000000000000000..c220676290ce8242c0890716493c4c6369a555e0 --- /dev/null +++ b/crates/zed/src/languages/elm/outline.scm @@ -0,0 +1,22 @@ +(type_declaration + (type) @context + (upper_case_identifier) @name) @item + +(type_alias_declaration + (type) @context + (alias) @context + name: (upper_case_identifier) @name) @item + +(type_alias_declaration + typeExpression: + (type_expression + part: (record_type + (field_type + name: (lower_case_identifier) @name)))) @item + +(union_variant + name: (upper_case_identifier) @name) @item + +(value_declaration + functionDeclarationLeft: + (function_declaration_left(lower_case_identifier) @name)) @item From 7dccb487de2238c67c1b37c41e520e10ba7aeb86 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 24 Jul 2023 15:42:10 +0300 Subject: [PATCH 11/19] Fixes a crash when SelectAllMatches action was called on no matches --- crates/search/src/buffer_search.rs | 81 ++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 7fade13a509dabd633b7da26601f90b770df3b6f..54293050989c146e5bb39363c12281c46eea1a53 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -568,7 +568,7 @@ impl BufferSearchBar { } fn select_all_matches(&mut self, _: &SelectAllMatches, cx: &mut ViewContext) { - if !self.dismissed { + if !self.dismissed && self.active_match_index.is_some() { if let Some(searchable_item) = self.active_searchable_item.as_ref() { if let Some(matches) = self .searchable_items_with_matches @@ -1175,9 +1175,16 @@ mod tests { .await .unwrap(); search_bar.update(cx, |search_bar, cx| { + cx.focus(search_bar.query_editor.as_any()); search_bar.activate_current_match(cx); }); + cx.read_window(window_id, |cx| { + assert!( + !editor.is_focused(cx), + "Initially, the editor should not be focused" + ); + }); let initial_selections = editor.update(cx, |editor, cx| { let initial_selections = editor.selections.display_ranges(cx); assert_eq!( @@ -1191,7 +1198,16 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { + cx.focus(search_bar.query_editor.as_any()); search_bar.select_all_matches(&SelectAllMatches, cx); + }); + cx.read_window(window_id, |cx| { + assert!( + editor.is_focused(cx), + "Should focus editor after successful SelectAllMatches" + ); + }); + search_bar.update(cx, |search_bar, cx| { let all_selections = editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); assert_eq!( @@ -1199,8 +1215,6 @@ mod tests { expected_query_matches_count, "Should select all `a` characters in the buffer, but got: {all_selections:?}" ); - }); - search_bar.update(cx, |search_bar, _| { assert_eq!( search_bar.active_match_index, Some(0), @@ -1210,6 +1224,14 @@ mod tests { search_bar.update(cx, |search_bar, cx| { search_bar.select_next_match(&SelectNextMatch, cx); + }); + cx.read_window(window_id, |cx| { + assert!( + editor.is_focused(cx), + "Should still have editor focused after SelectNextMatch" + ); + }); + search_bar.update(cx, |search_bar, cx| { let all_selections = editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); assert_eq!( @@ -1221,8 +1243,6 @@ mod tests { all_selections, initial_selections, "Next match should be different from the first selection" ); - }); - search_bar.update(cx, |search_bar, _| { assert_eq!( search_bar.active_match_index, Some(1), @@ -1231,7 +1251,16 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { + cx.focus(search_bar.query_editor.as_any()); search_bar.select_all_matches(&SelectAllMatches, cx); + }); + cx.read_window(window_id, |cx| { + assert!( + editor.is_focused(cx), + "Should focus editor after successful SelectAllMatches" + ); + }); + search_bar.update(cx, |search_bar, cx| { let all_selections = editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); assert_eq!( @@ -1239,8 +1268,6 @@ mod tests { expected_query_matches_count, "Should select all `a` characters in the buffer, but got: {all_selections:?}" ); - }); - search_bar.update(cx, |search_bar, _| { assert_eq!( search_bar.active_match_index, Some(1), @@ -1250,6 +1277,14 @@ mod tests { search_bar.update(cx, |search_bar, cx| { search_bar.select_prev_match(&SelectPrevMatch, cx); + }); + cx.read_window(window_id, |cx| { + assert!( + editor.is_focused(cx), + "Should still have editor focused after SelectPrevMatch" + ); + }); + let last_match_selections = search_bar.update(cx, |search_bar, cx| { let all_selections = editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); assert_eq!( @@ -1261,13 +1296,41 @@ mod tests { all_selections, initial_selections, "Previous match should be the same as the first selection" ); - }); - search_bar.update(cx, |search_bar, _| { assert_eq!( search_bar.active_match_index, Some(0), "Match index should be updated to the previous one" ); + all_selections + }); + + search_bar + .update(cx, |search_bar, cx| { + cx.focus(search_bar.query_editor.as_any()); + search_bar.search("abas_nonexistent_match", None, cx) + }) + .await + .unwrap(); + search_bar.update(cx, |search_bar, cx| { + search_bar.select_all_matches(&SelectAllMatches, cx); + }); + cx.read_window(window_id, |cx| { + assert!( + !editor.is_focused(cx), + "Should not switch focus to editor if SelectAllMatches does not find any matches" + ); + }); + search_bar.update(cx, |search_bar, cx| { + let all_selections = + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); + assert_eq!( + all_selections, last_match_selections, + "Should not select anything new if there are no matches" + ); + assert!( + search_bar.active_match_index.is_none(), + "For no matches, there should be no active match index" + ); }); } } From ea74734b0a8ff445609732fe334a15284a26acee Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 24 Jul 2023 08:51:44 -0700 Subject: [PATCH 12/19] Touch up elm tree sitter integration --- crates/zed/src/languages/elm/highlights.scm | 1 + crates/zed/src/languages/elm/outline.scm | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/zed/src/languages/elm/highlights.scm b/crates/zed/src/languages/elm/highlights.scm index f6be193f838db77549a879c6563bc00ac8f064f2..e20873af1bd35302d84a34cf65a2e5540aac03a5 100644 --- a/crates/zed/src/languages/elm/highlights.scm +++ b/crates/zed/src/languages/elm/highlights.scm @@ -53,6 +53,7 @@ (union_variant(upper_case_identifier) @variant) (union_pattern) @variant + (value_expr(upper_case_qid(upper_case_identifier)) @type) [ diff --git a/crates/zed/src/languages/elm/outline.scm b/crates/zed/src/languages/elm/outline.scm index c220676290ce8242c0890716493c4c6369a555e0..88ff9a5af2a7fcd725c79b2a737f07e50fb09dd5 100644 --- a/crates/zed/src/languages/elm/outline.scm +++ b/crates/zed/src/languages/elm/outline.scm @@ -12,11 +12,10 @@ (type_expression part: (record_type (field_type - name: (lower_case_identifier) @name)))) @item + name: (lower_case_identifier) @name) @item))) (union_variant name: (upper_case_identifier) @name) @item -(value_declaration - functionDeclarationLeft: - (function_declaration_left(lower_case_identifier) @name)) @item +(type_annotation + name: (_) @name) @item From d95c4fdb2b182c0ae6238614cdc966b829646de4 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 24 Jul 2023 09:01:15 -0700 Subject: [PATCH 13/19] Remove unbound highlight queries --- crates/zed/src/languages/elm/highlights.scm | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/zed/src/languages/elm/highlights.scm b/crates/zed/src/languages/elm/highlights.scm index e20873af1bd35302d84a34cf65a2e5540aac03a5..3ab1028a3a46522271a8c3444047df847092b69e 100644 --- a/crates/zed/src/languages/elm/highlights.scm +++ b/crates/zed/src/languages/elm/highlights.scm @@ -51,9 +51,6 @@ (type_ref) @type (type_alias_declaration name: (upper_case_identifier) @type) -(union_variant(upper_case_identifier) @variant) -(union_pattern) @variant - (value_expr(upper_case_qid(upper_case_identifier)) @type) [ From 41105136a45ff46eca75753fe1361705ba93b1b6 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 24 Jul 2023 10:20:10 -0700 Subject: [PATCH 14/19] Add MacOS standard key binding for file renames --- assets/keymaps/default.json | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index d970df1abdeb3a4d0de973e88294f17cb5baa4e4..5c1002a5fc6015c6d31c658a57759b2686c1d671 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -528,6 +528,7 @@ "cmd-alt-c": "project_panel::CopyPath", "alt-cmd-shift-c": "project_panel::CopyRelativePath", "f2": "project_panel::Rename", + "enter": "project_panel::Rename", "backspace": "project_panel::Delete", "alt-cmd-r": "project_panel::RevealInFinder", "alt-shift-f": "project_panel::NewSearchInDirectory" From fbe01089598fee96a5bbf87a44161dba330d7ec8 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 24 Jul 2023 10:44:56 -0700 Subject: [PATCH 15/19] Highlight Elm arrows as keywords --- crates/zed/src/languages/elm/highlights.scm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/zed/src/languages/elm/highlights.scm b/crates/zed/src/languages/elm/highlights.scm index 3ab1028a3a46522271a8c3444047df847092b69e..be6657845323044c744f3e634d3c85000698a468 100644 --- a/crates/zed/src/languages/elm/highlights.scm +++ b/crates/zed/src/languages/elm/highlights.scm @@ -14,10 +14,10 @@ (import) (module) (type) + (arrow) ] @keyword [ - (arrow) (eq) (operator_identifier) (colon) From 62ee52a5fc6e98409aa25e9e6dd7b815068f42be Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 24 Jul 2023 10:45:15 -0700 Subject: [PATCH 16/19] Highlight qualified Elm function calls --- crates/zed/src/languages/elm/highlights.scm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/zed/src/languages/elm/highlights.scm b/crates/zed/src/languages/elm/highlights.scm index be6657845323044c744f3e634d3c85000698a468..5838aa4796d4336603a18e63c9dd83c6945be131 100644 --- a/crates/zed/src/languages/elm/highlights.scm +++ b/crates/zed/src/languages/elm/highlights.scm @@ -26,6 +26,11 @@ (type_annotation(lower_case_identifier) @function) (port_annotation(lower_case_identifier) @function) (function_declaration_left(lower_case_identifier) @function.definition) + +(function_call_expr + target: (value_expr + name: (value_qid (dot) (lower_case_identifier) @function))) + (function_call_expr target: (value_expr) @identifier) (exposed_value(lower_case_identifier) @function) From 3cc88904bfa33f2fd84aa75271b4df851c2148dd Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 24 Jul 2023 10:45:46 -0700 Subject: [PATCH 17/19] Add all Elm functions to the outline, including locals --- crates/zed/src/languages/elm/outline.scm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/zed/src/languages/elm/outline.scm b/crates/zed/src/languages/elm/outline.scm index 88ff9a5af2a7fcd725c79b2a737f07e50fb09dd5..1d7d5a70b0183cb457ab993d864d3e9b0617254d 100644 --- a/crates/zed/src/languages/elm/outline.scm +++ b/crates/zed/src/languages/elm/outline.scm @@ -17,5 +17,6 @@ (union_variant name: (upper_case_identifier) @name) @item -(type_annotation - name: (_) @name) @item +(value_declaration + functionDeclarationLeft: + (function_declaration_left(lower_case_identifier) @name)) @item From e199a6a3a1024a4f49ed5bc9dd68c025e3d4ff66 Mon Sep 17 00:00:00 2001 From: Quinn Wilton Date: Mon, 24 Jul 2023 10:55:15 -0700 Subject: [PATCH 18/19] Highlight all Elm function calls --- crates/zed/src/languages/elm/highlights.scm | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/zed/src/languages/elm/highlights.scm b/crates/zed/src/languages/elm/highlights.scm index 5838aa4796d4336603a18e63c9dd83c6945be131..5723c7eecbf20eae4ea3b2d8570f4e1a7a6067ae 100644 --- a/crates/zed/src/languages/elm/highlights.scm +++ b/crates/zed/src/languages/elm/highlights.scm @@ -29,9 +29,7 @@ (function_call_expr target: (value_expr - name: (value_qid (dot) (lower_case_identifier) @function))) - -(function_call_expr target: (value_expr) @identifier) + name: (value_qid (lower_case_identifier) @function))) (exposed_value(lower_case_identifier) @function) (exposed_type(upper_case_identifier) @type) From bdd0b9f3878d3548c03d7f70d219172b655c7443 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 24 Jul 2023 16:35:01 -0400 Subject: [PATCH 19/19] Add open file in project finder via space (#2785) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @mikayla-maki for 👀 [This PR added in the ability to rename a file via `enter`](https://github.com/zed-industries/zed/pull/2784). Previously, `enter` was used to both open a file and confirm a rename, so this PR changes the opening of a file to use `space`, which is what VS Code uses. It also makes a bit more sense because now `enter` is just used to start a rename and confirm the rename, vs being used for 2 different actions. N/A on the release notes, as I adjusted the release note in the previously-tagged PR. Release Notes: - N/A --- assets/keymaps/default.json | 1 + crates/project_panel/src/project_panel.rs | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 5c1002a5fc6015c6d31c658a57759b2686c1d671..7553c199258a2b21337e4984ecca3c818de2ba7d 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -529,6 +529,7 @@ "alt-cmd-shift-c": "project_panel::CopyRelativePath", "f2": "project_panel::Rename", "enter": "project_panel::Rename", + "space": "project_panel::Open", "backspace": "project_panel::Delete", "alt-cmd-r": "project_panel::RevealInFinder", "alt-shift-f": "project_panel::NewSearchInDirectory" diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 2a6da7db28950972dd09afe7a3cb3b646b89e7c3..3e20c4986e30e037c0b408dfffba8f192ce0b1d5 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -125,6 +125,7 @@ actions!( Paste, Delete, Rename, + Open, ToggleFocus, NewSearchInDirectory, ] @@ -146,6 +147,7 @@ pub fn init(assets: impl AssetSource, cx: &mut AppContext) { cx.add_action(ProjectPanel::rename); cx.add_async_action(ProjectPanel::delete); cx.add_async_action(ProjectPanel::confirm); + cx.add_async_action(ProjectPanel::open_file); cx.add_action(ProjectPanel::cancel); cx.add_action(ProjectPanel::cut); cx.add_action(ProjectPanel::copy); @@ -560,15 +562,20 @@ impl ProjectPanel { fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) -> Option>> { if let Some(task) = self.confirm_edit(cx) { - Some(task) - } else if let Some((_, entry)) = self.selected_entry(cx) { + return Some(task); + } + + None + } + + fn open_file(&mut self, _: &Open, cx: &mut ViewContext) -> Option>> { + if let Some((_, entry)) = self.selected_entry(cx) { if entry.is_file() { self.open_entry(entry.id, true, cx); } - None - } else { - None } + + None } fn confirm_edit(&mut self, cx: &mut ViewContext) -> Option>> { @@ -2382,7 +2389,7 @@ mod tests { toggle_expand_dir(&panel, "src/test", cx); select_path(&panel, "src/test/first.rs", cx); - panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx)); + panel.update(cx, |panel, cx| panel.open_file(&Open, cx)); cx.foreground().run_until_parked(); assert_eq!( visible_entries_as_strings(&panel, 0..10, cx), @@ -2410,7 +2417,7 @@ mod tests { ensure_no_open_items_and_panes(window_id, &workspace, cx); select_path(&panel, "src/test/second.rs", cx); - panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx)); + panel.update(cx, |panel, cx| panel.open_file(&Open, cx)); cx.foreground().run_until_parked(); assert_eq!( visible_entries_as_strings(&panel, 0..10, cx),