Compare commits
11 Commits
v0.147.2-p
...
v0.87.3-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1e4c8dec5 | ||
|
|
815b80e295 | ||
|
|
605213708a | ||
|
|
a46e2e82f3 | ||
|
|
5e3c5359c5 | ||
|
|
7da90451d9 | ||
|
|
e044da3447 | ||
|
|
5b733542ad | ||
|
|
10a2a592b3 | ||
|
|
c93269fd7a | ||
|
|
f69b94b0a2 |
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -1190,7 +1190,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.12.1"
|
||||
version = "0.12.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-tungstenite",
|
||||
@@ -4721,7 +4721,7 @@ dependencies = [
|
||||
"fuzzy",
|
||||
"git",
|
||||
"git2",
|
||||
"glob",
|
||||
"globset",
|
||||
"gpui",
|
||||
"ignore",
|
||||
"itertools",
|
||||
@@ -5777,7 +5777,7 @@ dependencies = [
|
||||
"collections",
|
||||
"editor",
|
||||
"futures 0.3.25",
|
||||
"glob",
|
||||
"globset",
|
||||
"gpui",
|
||||
"language",
|
||||
"log",
|
||||
@@ -8544,7 +8544,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.87.0"
|
||||
version = "0.87.3"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
|
||||
@@ -77,7 +77,8 @@ async-trait = { version = "0.1" }
|
||||
ctor = { version = "0.1" }
|
||||
env_logger = { version = "0.9" }
|
||||
futures = { version = "0.3" }
|
||||
glob = { version = "0.3.1" }
|
||||
glob = { version = "0.3" }
|
||||
globset = { version = "0.4" }
|
||||
lazy_static = { version = "1.4.0" }
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||
ordered-float = { version = "2.1.1" }
|
||||
|
||||
@@ -43,6 +43,19 @@
|
||||
// 3. Draw all invisible symbols:
|
||||
// "all"
|
||||
"show_whitespaces": "selection",
|
||||
// Whether to show the scrollbar in the editor.
|
||||
// This setting can take four values:
|
||||
//
|
||||
// 1. Show the scrollbar if there's important information or
|
||||
// follow the system's configured behavior (default):
|
||||
// "auto"
|
||||
// 2. Match the system's configured behavior:
|
||||
// "system"
|
||||
// 3. Always show the scrollbar:
|
||||
// "always"
|
||||
// 4. Never show the scrollbar:
|
||||
// "never"
|
||||
"show_scrollbars": "auto",
|
||||
// Whether the screen sharing icon is shown in the os status bar.
|
||||
"show_call_status_icon": true,
|
||||
// Whether to use language servers to provide code intelligence.
|
||||
|
||||
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
|
||||
default-run = "collab"
|
||||
edition = "2021"
|
||||
name = "collab"
|
||||
version = "0.12.1"
|
||||
version = "0.12.2"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
|
||||
@@ -328,10 +328,9 @@ async fn configure_disabled_globs(
|
||||
cx.global::<Settings>()
|
||||
.copilot
|
||||
.disabled_globs
|
||||
.clone()
|
||||
.iter()
|
||||
.map(|glob| glob.as_str().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.collect()
|
||||
});
|
||||
|
||||
if let Some(path_to_disable) = &path_to_disable {
|
||||
|
||||
@@ -516,6 +516,15 @@ pub struct EditorSnapshot {
|
||||
ongoing_scroll: OngoingScroll,
|
||||
}
|
||||
|
||||
impl EditorSnapshot {
|
||||
fn has_scrollbar_info(&self) -> bool {
|
||||
self.buffer_snapshot
|
||||
.git_diff_hunks_in_range(0..self.max_point().row(), false)
|
||||
.next()
|
||||
.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct SelectionHistoryEntry {
|
||||
selections: Arc<[Selection<Anchor>]>,
|
||||
|
||||
@@ -5459,10 +5459,12 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
|
||||
});
|
||||
|
||||
let is_still_following = Rc::new(RefCell::new(true));
|
||||
let follower_edit_event_count = Rc::new(RefCell::new(0));
|
||||
let pending_update = Rc::new(RefCell::new(None));
|
||||
follower.update(cx, {
|
||||
let update = pending_update.clone();
|
||||
let is_still_following = is_still_following.clone();
|
||||
let follower_edit_event_count = follower_edit_event_count.clone();
|
||||
|_, cx| {
|
||||
cx.subscribe(&leader, move |_, leader, event, cx| {
|
||||
leader
|
||||
@@ -5475,6 +5477,9 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
|
||||
if Editor::should_unfollow_on_event(event, cx) {
|
||||
*is_still_following.borrow_mut() = false;
|
||||
}
|
||||
if let Event::BufferEdited = event {
|
||||
*follower_edit_event_count.borrow_mut() += 1;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
@@ -5494,6 +5499,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
|
||||
assert_eq!(follower.selections.ranges(cx), vec![1..1]);
|
||||
});
|
||||
assert_eq!(*is_still_following.borrow(), true);
|
||||
assert_eq!(*follower_edit_event_count.borrow(), 0);
|
||||
|
||||
// Update the scroll position only
|
||||
leader.update(cx, |leader, cx| {
|
||||
@@ -5510,6 +5516,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
|
||||
vec2f(1.5, 3.5)
|
||||
);
|
||||
assert_eq!(*is_still_following.borrow(), true);
|
||||
assert_eq!(*follower_edit_event_count.borrow(), 0);
|
||||
|
||||
// Update the selections and scroll position. The follower's scroll position is updated
|
||||
// via autoscroll, not via the leader's exact scroll position.
|
||||
|
||||
@@ -1022,15 +1022,16 @@ impl EditorElement {
|
||||
let mut first_row_y_offset = 0.0;
|
||||
|
||||
// Impose a minimum height on the scrollbar thumb
|
||||
let row_height = height / max_row;
|
||||
let min_thumb_height =
|
||||
style.min_height_factor * cx.font_cache.line_height(self.style.text.font_size);
|
||||
let thumb_height = (row_range.end - row_range.start) * height / max_row;
|
||||
let thumb_height = (row_range.end - row_range.start) * row_height;
|
||||
if thumb_height < min_thumb_height {
|
||||
first_row_y_offset = (min_thumb_height - thumb_height) / 2.0;
|
||||
height -= min_thumb_height - thumb_height;
|
||||
}
|
||||
|
||||
let y_for_row = |row: f32| -> f32 { top + first_row_y_offset + row * height / max_row };
|
||||
let y_for_row = |row: f32| -> f32 { top + first_row_y_offset + row * row_height };
|
||||
|
||||
let thumb_top = y_for_row(row_range.start) - first_row_y_offset;
|
||||
let thumb_bottom = y_for_row(row_range.end) + first_row_y_offset;
|
||||
@@ -1044,6 +1045,50 @@ impl EditorElement {
|
||||
background: style.track.background_color,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let diff_style = cx.global::<Settings>().theme.editor.diff.clone();
|
||||
for hunk in layout
|
||||
.position_map
|
||||
.snapshot
|
||||
.buffer_snapshot
|
||||
.git_diff_hunks_in_range(0..(max_row.floor() as u32), false)
|
||||
{
|
||||
let start_y = y_for_row(hunk.buffer_range.start as f32);
|
||||
let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
|
||||
y_for_row((hunk.buffer_range.end + 1) as f32)
|
||||
} else {
|
||||
y_for_row((hunk.buffer_range.end) as f32)
|
||||
};
|
||||
|
||||
if end_y - start_y < 1. {
|
||||
end_y = start_y + 1.;
|
||||
}
|
||||
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
|
||||
|
||||
let color = match hunk.status() {
|
||||
DiffHunkStatus::Added => diff_style.inserted,
|
||||
DiffHunkStatus::Modified => diff_style.modified,
|
||||
DiffHunkStatus::Removed => diff_style.deleted,
|
||||
};
|
||||
|
||||
let border = Border {
|
||||
width: 1.,
|
||||
color: style.thumb.border.color,
|
||||
overlay: false,
|
||||
top: false,
|
||||
right: true,
|
||||
bottom: false,
|
||||
left: true,
|
||||
};
|
||||
|
||||
scene.push_quad(Quad {
|
||||
bounds,
|
||||
background: Some(color),
|
||||
border,
|
||||
corner_radius: style.thumb.corner_radius,
|
||||
})
|
||||
}
|
||||
|
||||
scene.push_quad(Quad {
|
||||
bounds: thumb_bounds,
|
||||
border: style.thumb.border,
|
||||
@@ -2013,7 +2058,15 @@ impl Element<Editor> for EditorElement {
|
||||
));
|
||||
}
|
||||
|
||||
let show_scrollbars = editor.scroll_manager.scrollbars_visible();
|
||||
let show_scrollbars = match cx.global::<Settings>().show_scrollbars {
|
||||
settings::ShowScrollbars::Auto => {
|
||||
snapshot.has_scrollbar_info() || editor.scroll_manager.scrollbars_visible()
|
||||
}
|
||||
settings::ShowScrollbars::System => editor.scroll_manager.scrollbars_visible(),
|
||||
settings::ShowScrollbars::Always => true,
|
||||
settings::ShowScrollbars::Never => false,
|
||||
};
|
||||
|
||||
let include_root = editor
|
||||
.project
|
||||
.as_ref()
|
||||
|
||||
@@ -1165,6 +1165,9 @@ impl MultiBuffer {
|
||||
) {
|
||||
self.sync(cx);
|
||||
let ids = excerpt_ids.into_iter().collect::<Vec<_>>();
|
||||
if ids.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut buffers = self.buffers.borrow_mut();
|
||||
let mut snapshot = self.snapshot.borrow_mut();
|
||||
@@ -4080,19 +4083,25 @@ mod tests {
|
||||
|
||||
let leader_multibuffer = cx.add_model(|_| MultiBuffer::new(0));
|
||||
let follower_multibuffer = cx.add_model(|_| MultiBuffer::new(0));
|
||||
let follower_edit_event_count = Rc::new(RefCell::new(0));
|
||||
|
||||
follower_multibuffer.update(cx, |_, cx| {
|
||||
cx.subscribe(&leader_multibuffer, |follower, _, event, cx| {
|
||||
match event.clone() {
|
||||
let follower_edit_event_count = follower_edit_event_count.clone();
|
||||
cx.subscribe(
|
||||
&leader_multibuffer,
|
||||
move |follower, _, event, cx| match event.clone() {
|
||||
Event::ExcerptsAdded {
|
||||
buffer,
|
||||
predecessor,
|
||||
excerpts,
|
||||
} => follower.insert_excerpts_with_ids_after(predecessor, buffer, excerpts, cx),
|
||||
Event::ExcerptsRemoved { ids } => follower.remove_excerpts(ids, cx),
|
||||
Event::Edited => {
|
||||
*follower_edit_event_count.borrow_mut() += 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
});
|
||||
|
||||
@@ -4131,6 +4140,7 @@ mod tests {
|
||||
leader_multibuffer.read(cx).snapshot(cx).text(),
|
||||
follower_multibuffer.read(cx).snapshot(cx).text(),
|
||||
);
|
||||
assert_eq!(*follower_edit_event_count.borrow(), 2);
|
||||
|
||||
leader_multibuffer.update(cx, |leader, cx| {
|
||||
let excerpt_ids = leader.excerpt_ids();
|
||||
@@ -4140,6 +4150,27 @@ mod tests {
|
||||
leader_multibuffer.read(cx).snapshot(cx).text(),
|
||||
follower_multibuffer.read(cx).snapshot(cx).text(),
|
||||
);
|
||||
assert_eq!(*follower_edit_event_count.borrow(), 3);
|
||||
|
||||
// Removing an empty set of excerpts is a noop.
|
||||
leader_multibuffer.update(cx, |leader, cx| {
|
||||
leader.remove_excerpts([], cx);
|
||||
});
|
||||
assert_eq!(
|
||||
leader_multibuffer.read(cx).snapshot(cx).text(),
|
||||
follower_multibuffer.read(cx).snapshot(cx).text(),
|
||||
);
|
||||
assert_eq!(*follower_edit_event_count.borrow(), 3);
|
||||
|
||||
// Adding an empty set of excerpts is a noop.
|
||||
leader_multibuffer.update(cx, |leader, cx| {
|
||||
leader.push_excerpts::<usize>(buffer_2.clone(), [], cx);
|
||||
});
|
||||
assert_eq!(
|
||||
leader_multibuffer.read(cx).snapshot(cx).text(),
|
||||
follower_multibuffer.read(cx).snapshot(cx).text(),
|
||||
);
|
||||
assert_eq!(*follower_edit_event_count.borrow(), 3);
|
||||
|
||||
leader_multibuffer.update(cx, |leader, cx| {
|
||||
leader.clear(cx);
|
||||
@@ -4148,6 +4179,7 @@ mod tests {
|
||||
leader_multibuffer.read(cx).snapshot(cx).text(),
|
||||
follower_multibuffer.read(cx).snapshot(cx).text(),
|
||||
);
|
||||
assert_eq!(*follower_edit_event_count.borrow(), 4);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
||||
@@ -42,7 +42,7 @@ anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
backtrace = "0.3"
|
||||
futures.workspace = true
|
||||
glob.workspace = true
|
||||
globset.workspace = true
|
||||
ignore = "0.4"
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LspGlobSet {
|
||||
patterns: Vec<glob::Pattern>,
|
||||
}
|
||||
|
||||
impl LspGlobSet {
|
||||
pub fn clear(&mut self) {
|
||||
self.patterns.clear();
|
||||
}
|
||||
|
||||
/// Add a pattern to the glob set.
|
||||
///
|
||||
/// LSP's glob syntax supports bash-style brace expansion. For example,
|
||||
/// the pattern '*.{js,ts}' would match all JavaScript or TypeScript files.
|
||||
/// This is not a part of the standard libc glob syntax, and isn't supported
|
||||
/// by the `glob` crate. So we pre-process the glob patterns, producing a
|
||||
/// separate glob `Pattern` object for each part of a brace expansion.
|
||||
pub fn add_pattern(&mut self, pattern: &str) -> Result<()> {
|
||||
// Find all of the ranges of `pattern` that contain matched curly braces.
|
||||
let mut expansion_ranges = Vec::new();
|
||||
let mut expansion_start_ix = None;
|
||||
for (ix, c) in pattern.match_indices(|c| ['{', '}'].contains(&c)) {
|
||||
match c {
|
||||
"{" => {
|
||||
if expansion_start_ix.is_some() {
|
||||
return Err(anyhow!("nested braces in glob patterns aren't supported"));
|
||||
}
|
||||
expansion_start_ix = Some(ix);
|
||||
}
|
||||
"}" => {
|
||||
if let Some(start_ix) = expansion_start_ix {
|
||||
expansion_ranges.push(start_ix..ix + 1);
|
||||
}
|
||||
expansion_start_ix = None;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Starting with a single pattern, process each brace expansion by cloning
|
||||
// the pattern once per element of the expansion.
|
||||
let mut unexpanded_patterns = vec![];
|
||||
let mut expanded_patterns = vec![pattern.to_string()];
|
||||
|
||||
for outer_range in expansion_ranges.into_iter().rev() {
|
||||
let inner_range = (outer_range.start + 1)..(outer_range.end - 1);
|
||||
std::mem::swap(&mut unexpanded_patterns, &mut expanded_patterns);
|
||||
for unexpanded_pattern in unexpanded_patterns.drain(..) {
|
||||
for part in unexpanded_pattern[inner_range.clone()].split(',') {
|
||||
let mut expanded_pattern = unexpanded_pattern.clone();
|
||||
expanded_pattern.replace_range(outer_range.clone(), part);
|
||||
expanded_patterns.push(expanded_pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the final glob patterns and add them to the set.
|
||||
for pattern in expanded_patterns {
|
||||
let pattern = glob::Pattern::new(&pattern)?;
|
||||
self.patterns.push(pattern);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn matches(&self, path: &Path) -> bool {
|
||||
self.patterns
|
||||
.iter()
|
||||
.any(|pattern| pattern.matches_path(path))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_glob_set() {
|
||||
let mut watch = LspGlobSet::default();
|
||||
watch.add_pattern("/a/**/*.rs").unwrap();
|
||||
watch.add_pattern("/a/**/Cargo.toml").unwrap();
|
||||
|
||||
assert!(watch.matches("/a/b.rs".as_ref()));
|
||||
assert!(watch.matches("/a/b/c.rs".as_ref()));
|
||||
|
||||
assert!(!watch.matches("/b/c.rs".as_ref()));
|
||||
assert!(!watch.matches("/a/b.ts".as_ref()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_brace_expansion() {
|
||||
let mut watch = LspGlobSet::default();
|
||||
watch.add_pattern("/a/*.{ts,js,tsx}").unwrap();
|
||||
|
||||
assert!(watch.matches("/a/one.js".as_ref()));
|
||||
assert!(watch.matches("/a/two.ts".as_ref()));
|
||||
assert!(watch.matches("/a/three.tsx".as_ref()));
|
||||
|
||||
assert!(!watch.matches("/a/one.j".as_ref()));
|
||||
assert!(!watch.matches("/a/two.s".as_ref()));
|
||||
assert!(!watch.matches("/a/three.t".as_ref()));
|
||||
assert!(!watch.matches("/a/four.t".as_ref()));
|
||||
assert!(!watch.matches("/a/five.xt".as_ref()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_brace_expansion() {
|
||||
let mut watch = LspGlobSet::default();
|
||||
watch.add_pattern("/a/{one,two,three}.{b*c,d*e}").unwrap();
|
||||
|
||||
assert!(watch.matches("/a/one.bic".as_ref()));
|
||||
assert!(watch.matches("/a/two.dole".as_ref()));
|
||||
assert!(watch.matches("/a/three.deeee".as_ref()));
|
||||
|
||||
assert!(!watch.matches("/a/four.bic".as_ref()));
|
||||
assert!(!watch.matches("/a/one.be".as_ref()));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
mod ignore;
|
||||
mod lsp_command;
|
||||
mod lsp_glob_set;
|
||||
pub mod search;
|
||||
pub mod terminals;
|
||||
pub mod worktree;
|
||||
@@ -18,6 +17,7 @@ use futures::{
|
||||
future::{try_join_all, Shared},
|
||||
AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt,
|
||||
};
|
||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||
use gpui::{
|
||||
AnyModelHandle, AppContext, AsyncAppContext, BorrowAppContext, Entity, ModelContext,
|
||||
ModelHandle, Task, WeakModelHandle,
|
||||
@@ -39,7 +39,6 @@ use lsp::{
|
||||
DocumentHighlightKind, LanguageServer, LanguageServerId,
|
||||
};
|
||||
use lsp_command::*;
|
||||
use lsp_glob_set::LspGlobSet;
|
||||
use postage::watch;
|
||||
use rand::prelude::*;
|
||||
use search::SearchQuery;
|
||||
@@ -225,7 +224,7 @@ pub enum LanguageServerState {
|
||||
language: Arc<Language>,
|
||||
adapter: Arc<CachedLspAdapter>,
|
||||
server: Arc<LanguageServer>,
|
||||
watched_paths: LspGlobSet,
|
||||
watched_paths: HashMap<WorktreeId, GlobSet>,
|
||||
simulate_disk_based_diagnostics_completion: Option<Task<()>>,
|
||||
},
|
||||
}
|
||||
@@ -2859,10 +2858,39 @@ impl Project {
|
||||
if let Some(LanguageServerState::Running { watched_paths, .. }) =
|
||||
self.language_servers.get_mut(&language_server_id)
|
||||
{
|
||||
watched_paths.clear();
|
||||
eprintln!("change watch");
|
||||
let mut builders = HashMap::default();
|
||||
for watcher in params.watchers {
|
||||
watched_paths.add_pattern(&watcher.glob_pattern).log_err();
|
||||
eprintln!(" {}", watcher.glob_pattern);
|
||||
for worktree in &self.worktrees {
|
||||
if let Some(worktree) = worktree.upgrade(cx) {
|
||||
let worktree = worktree.read(cx);
|
||||
if let Some(abs_path) = worktree.abs_path().to_str() {
|
||||
if let Some(suffix) = watcher
|
||||
.glob_pattern
|
||||
.strip_prefix(abs_path)
|
||||
.and_then(|s| s.strip_prefix(std::path::MAIN_SEPARATOR))
|
||||
{
|
||||
if let Some(glob) = Glob::new(suffix).log_err() {
|
||||
builders
|
||||
.entry(worktree.id())
|
||||
.or_insert_with(|| GlobSetBuilder::new())
|
||||
.add(glob);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watched_paths.clear();
|
||||
for (worktree_id, builder) in builders {
|
||||
if let Ok(globset) = builder.build() {
|
||||
watched_paths.insert(worktree_id, globset);
|
||||
}
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
@@ -4706,25 +4734,39 @@ impl Project {
|
||||
changes: &HashMap<(Arc<Path>, ProjectEntryId), PathChange>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if changes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let worktree_id = worktree_handle.read(cx).id();
|
||||
let mut language_server_ids = self
|
||||
.language_server_ids
|
||||
.iter()
|
||||
.filter_map(|((server_worktree_id, _), server_id)| {
|
||||
(*server_worktree_id == worktree_id).then_some(*server_id)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
language_server_ids.sort();
|
||||
language_server_ids.dedup();
|
||||
|
||||
let abs_path = worktree_handle.read(cx).abs_path();
|
||||
for ((server_worktree_id, _), server_id) in &self.language_server_ids {
|
||||
if *server_worktree_id == worktree_id {
|
||||
if let Some(server) = self.language_servers.get(server_id) {
|
||||
if let LanguageServerState::Running {
|
||||
server,
|
||||
watched_paths,
|
||||
..
|
||||
} = server
|
||||
{
|
||||
for server_id in &language_server_ids {
|
||||
if let Some(server) = self.language_servers.get(server_id) {
|
||||
if let LanguageServerState::Running {
|
||||
server,
|
||||
watched_paths,
|
||||
..
|
||||
} = server
|
||||
{
|
||||
if let Some(watched_paths) = watched_paths.get(&worktree_id) {
|
||||
let params = lsp::DidChangeWatchedFilesParams {
|
||||
changes: changes
|
||||
.iter()
|
||||
.filter_map(|((path, _), change)| {
|
||||
let path = abs_path.join(path);
|
||||
if watched_paths.matches(&path) {
|
||||
if watched_paths.is_match(&path) {
|
||||
Some(lsp::FileEvent {
|
||||
uri: lsp::Url::from_file_path(path).unwrap(),
|
||||
uri: lsp::Url::from_file_path(abs_path.join(path))
|
||||
.unwrap(),
|
||||
typ: match change {
|
||||
PathChange::Added => lsp::FileChangeType::CREATED,
|
||||
PathChange::Removed => lsp::FileChangeType::DELETED,
|
||||
|
||||
@@ -2,8 +2,8 @@ use crate::{worktree::WorktreeHandle, Event, *};
|
||||
use fs::LineEnding;
|
||||
use fs::{FakeFs, RealFs};
|
||||
use futures::{future, StreamExt};
|
||||
use gpui::AppContext;
|
||||
use gpui::{executor::Deterministic, test::subscribe};
|
||||
use globset::Glob;
|
||||
use gpui::{executor::Deterministic, test::subscribe, AppContext};
|
||||
use language::{
|
||||
tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
|
||||
OffsetRangeExt, Point, ToPoint,
|
||||
@@ -503,7 +503,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
|
||||
register_options: serde_json::to_value(
|
||||
lsp::DidChangeWatchedFilesRegistrationOptions {
|
||||
watchers: vec![lsp::FileSystemWatcher {
|
||||
glob_pattern: "*.{rs,c}".to_string(),
|
||||
glob_pattern: "/the-root/*.{rs,c}".to_string(),
|
||||
kind: None,
|
||||
}],
|
||||
},
|
||||
@@ -3361,7 +3361,7 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
|
||||
search_query,
|
||||
false,
|
||||
true,
|
||||
vec![glob::Pattern::new("*.odd").unwrap()],
|
||||
vec![Glob::new("*.odd").unwrap().compile_matcher()],
|
||||
Vec::new()
|
||||
),
|
||||
cx
|
||||
@@ -3379,7 +3379,7 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
|
||||
search_query,
|
||||
false,
|
||||
true,
|
||||
vec![glob::Pattern::new("*.rs").unwrap()],
|
||||
vec![Glob::new("*.rs").unwrap().compile_matcher()],
|
||||
Vec::new()
|
||||
),
|
||||
cx
|
||||
@@ -3401,8 +3401,8 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
|
||||
false,
|
||||
true,
|
||||
vec![
|
||||
glob::Pattern::new("*.ts").unwrap(),
|
||||
glob::Pattern::new("*.odd").unwrap(),
|
||||
Glob::new("*.ts").unwrap().compile_matcher(),
|
||||
Glob::new("*.odd").unwrap().compile_matcher(),
|
||||
],
|
||||
Vec::new()
|
||||
),
|
||||
@@ -3425,9 +3425,9 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
|
||||
false,
|
||||
true,
|
||||
vec![
|
||||
glob::Pattern::new("*.rs").unwrap(),
|
||||
glob::Pattern::new("*.ts").unwrap(),
|
||||
glob::Pattern::new("*.odd").unwrap(),
|
||||
Glob::new("*.rs").unwrap().compile_matcher(),
|
||||
Glob::new("*.ts").unwrap().compile_matcher(),
|
||||
Glob::new("*.odd").unwrap().compile_matcher(),
|
||||
],
|
||||
Vec::new()
|
||||
),
|
||||
@@ -3470,7 +3470,7 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
|
||||
false,
|
||||
true,
|
||||
Vec::new(),
|
||||
vec![glob::Pattern::new("*.odd").unwrap()],
|
||||
vec![Glob::new("*.odd").unwrap().compile_matcher()],
|
||||
),
|
||||
cx
|
||||
)
|
||||
@@ -3493,7 +3493,7 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
|
||||
false,
|
||||
true,
|
||||
Vec::new(),
|
||||
vec![glob::Pattern::new("*.rs").unwrap()],
|
||||
vec![Glob::new("*.rs").unwrap().compile_matcher()],
|
||||
),
|
||||
cx
|
||||
)
|
||||
@@ -3515,8 +3515,8 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
|
||||
true,
|
||||
Vec::new(),
|
||||
vec![
|
||||
glob::Pattern::new("*.ts").unwrap(),
|
||||
glob::Pattern::new("*.odd").unwrap(),
|
||||
Glob::new("*.ts").unwrap().compile_matcher(),
|
||||
Glob::new("*.odd").unwrap().compile_matcher(),
|
||||
],
|
||||
),
|
||||
cx
|
||||
@@ -3539,9 +3539,9 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
|
||||
true,
|
||||
Vec::new(),
|
||||
vec![
|
||||
glob::Pattern::new("*.rs").unwrap(),
|
||||
glob::Pattern::new("*.ts").unwrap(),
|
||||
glob::Pattern::new("*.odd").unwrap(),
|
||||
Glob::new("*.rs").unwrap().compile_matcher(),
|
||||
Glob::new("*.ts").unwrap().compile_matcher(),
|
||||
Glob::new("*.odd").unwrap().compile_matcher(),
|
||||
],
|
||||
),
|
||||
cx
|
||||
@@ -3576,8 +3576,8 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
|
||||
search_query,
|
||||
false,
|
||||
true,
|
||||
vec![glob::Pattern::new("*.odd").unwrap()],
|
||||
vec![glob::Pattern::new("*.odd").unwrap()],
|
||||
vec![Glob::new("*.odd").unwrap().compile_matcher()],
|
||||
vec![Glob::new("*.odd").unwrap().compile_matcher()],
|
||||
),
|
||||
cx
|
||||
)
|
||||
@@ -3594,8 +3594,8 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
|
||||
search_query,
|
||||
false,
|
||||
true,
|
||||
vec![glob::Pattern::new("*.ts").unwrap()],
|
||||
vec![glob::Pattern::new("*.ts").unwrap()],
|
||||
vec![Glob::new("*.ts").unwrap().compile_matcher()],
|
||||
vec![Glob::new("*.ts").unwrap().compile_matcher()],
|
||||
),
|
||||
cx
|
||||
)
|
||||
@@ -3613,12 +3613,12 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
|
||||
false,
|
||||
true,
|
||||
vec![
|
||||
glob::Pattern::new("*.ts").unwrap(),
|
||||
glob::Pattern::new("*.odd").unwrap()
|
||||
Glob::new("*.ts").unwrap().compile_matcher(),
|
||||
Glob::new("*.odd").unwrap().compile_matcher()
|
||||
],
|
||||
vec![
|
||||
glob::Pattern::new("*.ts").unwrap(),
|
||||
glob::Pattern::new("*.odd").unwrap()
|
||||
Glob::new("*.ts").unwrap().compile_matcher(),
|
||||
Glob::new("*.odd").unwrap().compile_matcher()
|
||||
],
|
||||
),
|
||||
cx
|
||||
@@ -3637,12 +3637,12 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
|
||||
false,
|
||||
true,
|
||||
vec![
|
||||
glob::Pattern::new("*.ts").unwrap(),
|
||||
glob::Pattern::new("*.odd").unwrap()
|
||||
Glob::new("*.ts").unwrap().compile_matcher(),
|
||||
Glob::new("*.odd").unwrap().compile_matcher()
|
||||
],
|
||||
vec![
|
||||
glob::Pattern::new("*.rs").unwrap(),
|
||||
glob::Pattern::new("*.odd").unwrap()
|
||||
Glob::new("*.rs").unwrap().compile_matcher(),
|
||||
Glob::new("*.odd").unwrap().compile_matcher()
|
||||
],
|
||||
),
|
||||
cx
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use aho_corasick::{AhoCorasick, AhoCorasickBuilder};
|
||||
use anyhow::Result;
|
||||
use client::proto;
|
||||
use globset::{Glob, GlobMatcher};
|
||||
use itertools::Itertools;
|
||||
use language::{char_kind, Rope};
|
||||
use regex::{Regex, RegexBuilder};
|
||||
@@ -19,8 +20,8 @@ pub enum SearchQuery {
|
||||
query: Arc<str>,
|
||||
whole_word: bool,
|
||||
case_sensitive: bool,
|
||||
files_to_include: Vec<glob::Pattern>,
|
||||
files_to_exclude: Vec<glob::Pattern>,
|
||||
files_to_include: Vec<GlobMatcher>,
|
||||
files_to_exclude: Vec<GlobMatcher>,
|
||||
},
|
||||
Regex {
|
||||
regex: Regex,
|
||||
@@ -28,8 +29,8 @@ pub enum SearchQuery {
|
||||
multiline: bool,
|
||||
whole_word: bool,
|
||||
case_sensitive: bool,
|
||||
files_to_include: Vec<glob::Pattern>,
|
||||
files_to_exclude: Vec<glob::Pattern>,
|
||||
files_to_include: Vec<GlobMatcher>,
|
||||
files_to_exclude: Vec<GlobMatcher>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -38,8 +39,8 @@ impl SearchQuery {
|
||||
query: impl ToString,
|
||||
whole_word: bool,
|
||||
case_sensitive: bool,
|
||||
files_to_include: Vec<glob::Pattern>,
|
||||
files_to_exclude: Vec<glob::Pattern>,
|
||||
files_to_include: Vec<GlobMatcher>,
|
||||
files_to_exclude: Vec<GlobMatcher>,
|
||||
) -> Self {
|
||||
let query = query.to_string();
|
||||
let search = AhoCorasickBuilder::new()
|
||||
@@ -60,8 +61,8 @@ impl SearchQuery {
|
||||
query: impl ToString,
|
||||
whole_word: bool,
|
||||
case_sensitive: bool,
|
||||
files_to_include: Vec<glob::Pattern>,
|
||||
files_to_exclude: Vec<glob::Pattern>,
|
||||
files_to_include: Vec<GlobMatcher>,
|
||||
files_to_exclude: Vec<GlobMatcher>,
|
||||
) -> Result<Self> {
|
||||
let mut query = query.to_string();
|
||||
let initial_query = Arc::from(query.as_str());
|
||||
@@ -95,40 +96,16 @@ impl SearchQuery {
|
||||
message.query,
|
||||
message.whole_word,
|
||||
message.case_sensitive,
|
||||
message
|
||||
.files_to_include
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|glob_str| !glob_str.is_empty())
|
||||
.map(|glob_str| glob::Pattern::new(glob_str))
|
||||
.collect::<Result<_, _>>()?,
|
||||
message
|
||||
.files_to_exclude
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|glob_str| !glob_str.is_empty())
|
||||
.map(|glob_str| glob::Pattern::new(glob_str))
|
||||
.collect::<Result<_, _>>()?,
|
||||
deserialize_globs(&message.files_to_include)?,
|
||||
deserialize_globs(&message.files_to_exclude)?,
|
||||
)
|
||||
} else {
|
||||
Ok(Self::text(
|
||||
message.query,
|
||||
message.whole_word,
|
||||
message.case_sensitive,
|
||||
message
|
||||
.files_to_include
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|glob_str| !glob_str.is_empty())
|
||||
.map(|glob_str| glob::Pattern::new(glob_str))
|
||||
.collect::<Result<_, _>>()?,
|
||||
message
|
||||
.files_to_exclude
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|glob_str| !glob_str.is_empty())
|
||||
.map(|glob_str| glob::Pattern::new(glob_str))
|
||||
.collect::<Result<_, _>>()?,
|
||||
deserialize_globs(&message.files_to_include)?,
|
||||
deserialize_globs(&message.files_to_exclude)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -143,12 +120,12 @@ impl SearchQuery {
|
||||
files_to_include: self
|
||||
.files_to_include()
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.map(|g| g.glob().to_string())
|
||||
.join(","),
|
||||
files_to_exclude: self
|
||||
.files_to_exclude()
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.map(|g| g.glob().to_string())
|
||||
.join(","),
|
||||
}
|
||||
}
|
||||
@@ -289,7 +266,7 @@ impl SearchQuery {
|
||||
matches!(self, Self::Regex { .. })
|
||||
}
|
||||
|
||||
pub fn files_to_include(&self) -> &[glob::Pattern] {
|
||||
pub fn files_to_include(&self) -> &[GlobMatcher] {
|
||||
match self {
|
||||
Self::Text {
|
||||
files_to_include, ..
|
||||
@@ -300,7 +277,7 @@ impl SearchQuery {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn files_to_exclude(&self) -> &[glob::Pattern] {
|
||||
pub fn files_to_exclude(&self) -> &[GlobMatcher] {
|
||||
match self {
|
||||
Self::Text {
|
||||
files_to_exclude, ..
|
||||
@@ -317,14 +294,23 @@ impl SearchQuery {
|
||||
!self
|
||||
.files_to_exclude()
|
||||
.iter()
|
||||
.any(|exclude_glob| exclude_glob.matches_path(file_path))
|
||||
.any(|exclude_glob| exclude_glob.is_match(file_path))
|
||||
&& (self.files_to_include().is_empty()
|
||||
|| self
|
||||
.files_to_include()
|
||||
.iter()
|
||||
.any(|include_glob| include_glob.matches_path(file_path)))
|
||||
.any(|include_glob| include_glob.is_match(file_path)))
|
||||
}
|
||||
None => self.files_to_include().is_empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_globs(glob_set: &str) -> Result<Vec<GlobMatcher>> {
|
||||
glob_set
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|glob_str| !glob_str.is_empty())
|
||||
.map(|glob_str| Ok(Glob::new(glob_str)?.compile_matcher()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ impl Snapshot {
|
||||
let mut max_len = 0;
|
||||
let mut current_candidate = None;
|
||||
for (work_directory, repo) in (&self.repository_entries).iter() {
|
||||
if repo.contains(self, path) {
|
||||
if path.starts_with(&work_directory.0) {
|
||||
if work_directory.0.as_os_str().len() >= max_len {
|
||||
current_candidate = Some(repo);
|
||||
max_len = work_directory.0.as_os_str().len();
|
||||
@@ -169,10 +169,6 @@ impl RepositoryEntry {
|
||||
.map(|entry| RepositoryWorkDirectory(entry.path.clone()))
|
||||
}
|
||||
|
||||
pub(crate) fn contains(&self, snapshot: &Snapshot, path: &Path) -> bool {
|
||||
self.work_directory.contains(snapshot, path)
|
||||
}
|
||||
|
||||
pub fn status_for_file(&self, snapshot: &Snapshot, path: &Path) -> Option<GitFileStatus> {
|
||||
self.work_directory
|
||||
.relativize(snapshot, path)
|
||||
@@ -305,14 +301,6 @@ impl AsRef<Path> for RepositoryWorkDirectory {
|
||||
pub struct WorkDirectoryEntry(ProjectEntryId);
|
||||
|
||||
impl WorkDirectoryEntry {
|
||||
// Note that these paths should be relative to the worktree root.
|
||||
pub(crate) fn contains(&self, snapshot: &Snapshot, path: &Path) -> bool {
|
||||
snapshot
|
||||
.entry_for_id(self.0)
|
||||
.map(|entry| path.starts_with(&entry.path))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub(crate) fn relativize(&self, worktree: &Snapshot, path: &Path) -> Option<RepoPath> {
|
||||
worktree.entry_for_id(self.0).and_then(|entry| {
|
||||
path.strip_prefix(&entry.path)
|
||||
@@ -1643,8 +1631,38 @@ impl Snapshot {
|
||||
self.traverse_from_offset(true, include_ignored, 0)
|
||||
}
|
||||
|
||||
pub fn repositories(&self) -> impl Iterator<Item = &RepositoryEntry> {
|
||||
self.repository_entries.values()
|
||||
pub fn repositories(&self) -> impl Iterator<Item = (&Arc<Path>, &RepositoryEntry)> {
|
||||
self.repository_entries
|
||||
.iter()
|
||||
.map(|(path, entry)| (&path.0, entry))
|
||||
}
|
||||
|
||||
/// Given an ordered iterator of entries, returns an iterator of those entries,
|
||||
/// along with their containing git repository.
|
||||
pub fn entries_with_repos<'a>(
|
||||
&'a self,
|
||||
entries: impl 'a + Iterator<Item = &'a Entry>,
|
||||
) -> impl 'a + Iterator<Item = (&'a Entry, Option<&'a RepositoryEntry>)> {
|
||||
let mut containing_repos = Vec::<(&Arc<Path>, &RepositoryEntry)>::new();
|
||||
let mut repositories = self.repositories().peekable();
|
||||
entries.map(move |entry| {
|
||||
while let Some((repo_path, _)) = containing_repos.last() {
|
||||
if !entry.path.starts_with(repo_path) {
|
||||
containing_repos.pop();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
while let Some((repo_path, _)) = repositories.peek() {
|
||||
if entry.path.starts_with(repo_path) {
|
||||
containing_repos.push(repositories.next().unwrap());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let repo = containing_repos.last().map(|(_, repo)| *repo);
|
||||
(entry, repo)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn paths(&self) -> impl Iterator<Item = &Arc<Path>> {
|
||||
@@ -4008,6 +4026,7 @@ mod tests {
|
||||
#[gpui::test]
|
||||
async fn test_git_repository_for_path(cx: &mut TestAppContext) {
|
||||
let root = temp_tree(json!({
|
||||
"c.txt": "",
|
||||
"dir1": {
|
||||
".git": {},
|
||||
"deps": {
|
||||
@@ -4022,7 +4041,6 @@ mod tests {
|
||||
"b.txt": ""
|
||||
}
|
||||
},
|
||||
"c.txt": "",
|
||||
}));
|
||||
|
||||
let http_client = FakeHttpClient::with_404_response();
|
||||
@@ -4062,6 +4080,33 @@ mod tests {
|
||||
.map(|directory| directory.as_ref().to_owned()),
|
||||
Some(Path::new("dir1/deps/dep1").to_owned())
|
||||
);
|
||||
|
||||
let entries = tree.files(false, 0);
|
||||
|
||||
let paths_with_repos = tree
|
||||
.entries_with_repos(entries)
|
||||
.map(|(entry, repo)| {
|
||||
(
|
||||
entry.path.as_ref(),
|
||||
repo.and_then(|repo| {
|
||||
repo.work_directory(&tree)
|
||||
.map(|work_directory| work_directory.0.to_path_buf())
|
||||
}),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(
|
||||
paths_with_repos,
|
||||
&[
|
||||
(Path::new("c.txt"), None),
|
||||
(
|
||||
Path::new("dir1/deps/dep1/src/a.txt"),
|
||||
Some(Path::new("dir1/deps/dep1").into())
|
||||
),
|
||||
(Path::new("dir1/src/b.txt"), Some(Path::new("dir1").into())),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
let repo_update_events = Arc::new(Mutex::new(vec![]));
|
||||
|
||||
@@ -1011,14 +1011,11 @@ impl ProjectPanel {
|
||||
.unwrap_or(&[]);
|
||||
|
||||
let entry_range = range.start.saturating_sub(ix)..end_ix - ix;
|
||||
for entry in &visible_worktree_entries[entry_range] {
|
||||
let path = &entry.path;
|
||||
for (entry, repo) in
|
||||
snapshot.entries_with_repos(visible_worktree_entries[entry_range].iter())
|
||||
{
|
||||
let status = (entry.path.parent().is_some() && !entry.is_ignored)
|
||||
.then(|| {
|
||||
snapshot
|
||||
.repo_for(path)
|
||||
.and_then(|entry| entry.status_for_path(&snapshot, path))
|
||||
})
|
||||
.then(|| repo.and_then(|repo| repo.status_for_path(&snapshot, &entry.path)))
|
||||
.flatten();
|
||||
|
||||
let mut details = EntryDetails {
|
||||
|
||||
@@ -27,7 +27,7 @@ serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
glob.workspace = true
|
||||
globset.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
|
||||
@@ -2,12 +2,14 @@ use crate::{
|
||||
SearchOption, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex,
|
||||
ToggleWholeWord,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use editor::{
|
||||
items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, MultiBuffer,
|
||||
SelectAll, MAX_TAB_TITLE_LEN,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use globset::{Glob, GlobMatcher};
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::*,
|
||||
@@ -572,46 +574,30 @@ impl ProjectSearchView {
|
||||
|
||||
fn build_search_query(&mut self, cx: &mut ViewContext<Self>) -> Option<SearchQuery> {
|
||||
let text = self.query_editor.read(cx).text(cx);
|
||||
let included_files = match self
|
||||
.included_files_editor
|
||||
.read(cx)
|
||||
.text(cx)
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|glob_str| !glob_str.is_empty())
|
||||
.map(|glob_str| glob::Pattern::new(glob_str))
|
||||
.collect::<Result<_, _>>()
|
||||
{
|
||||
Ok(included_files) => {
|
||||
self.panels_with_errors.remove(&InputPanel::Include);
|
||||
included_files
|
||||
}
|
||||
Err(_e) => {
|
||||
self.panels_with_errors.insert(InputPanel::Include);
|
||||
cx.notify();
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let excluded_files = match self
|
||||
.excluded_files_editor
|
||||
.read(cx)
|
||||
.text(cx)
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|glob_str| !glob_str.is_empty())
|
||||
.map(|glob_str| glob::Pattern::new(glob_str))
|
||||
.collect::<Result<_, _>>()
|
||||
{
|
||||
Ok(excluded_files) => {
|
||||
self.panels_with_errors.remove(&InputPanel::Exclude);
|
||||
excluded_files
|
||||
}
|
||||
Err(_e) => {
|
||||
self.panels_with_errors.insert(InputPanel::Exclude);
|
||||
cx.notify();
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let included_files =
|
||||
match Self::load_glob_set(&self.included_files_editor.read(cx).text(cx)) {
|
||||
Ok(included_files) => {
|
||||
self.panels_with_errors.remove(&InputPanel::Include);
|
||||
included_files
|
||||
}
|
||||
Err(_e) => {
|
||||
self.panels_with_errors.insert(InputPanel::Include);
|
||||
cx.notify();
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let excluded_files =
|
||||
match Self::load_glob_set(&self.excluded_files_editor.read(cx).text(cx)) {
|
||||
Ok(excluded_files) => {
|
||||
self.panels_with_errors.remove(&InputPanel::Exclude);
|
||||
excluded_files
|
||||
}
|
||||
Err(_e) => {
|
||||
self.panels_with_errors.insert(InputPanel::Exclude);
|
||||
cx.notify();
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if self.regex {
|
||||
match SearchQuery::regex(
|
||||
text,
|
||||
@@ -641,6 +627,14 @@ impl ProjectSearchView {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_glob_set(text: &str) -> Result<Vec<GlobMatcher>> {
|
||||
text.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|glob_str| !glob_str.is_empty())
|
||||
.map(|glob_str| anyhow::Ok(Glob::new(glob_str)?.compile_matcher()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
|
||||
if let Some(index) = self.active_match_index {
|
||||
let match_ranges = self.model.read(cx).match_ranges.clone();
|
||||
|
||||
@@ -46,6 +46,7 @@ pub struct Settings {
|
||||
pub hover_popover_enabled: bool,
|
||||
pub show_completions_on_input: bool,
|
||||
pub show_call_status_icon: bool,
|
||||
pub show_scrollbars: ShowScrollbars,
|
||||
pub vim_mode: bool,
|
||||
pub autosave: Autosave,
|
||||
pub default_dock_anchor: DockAnchor,
|
||||
@@ -68,6 +69,16 @@ pub struct Settings {
|
||||
pub base_keymap: BaseKeymap,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ShowScrollbars {
|
||||
#[default]
|
||||
Auto,
|
||||
System,
|
||||
Always,
|
||||
Never,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
|
||||
pub enum BaseKeymap {
|
||||
#[default]
|
||||
@@ -390,6 +401,8 @@ pub struct SettingsFileContent {
|
||||
#[serde(default)]
|
||||
pub active_pane_magnification: Option<f32>,
|
||||
#[serde(default)]
|
||||
pub show_scrollbars: Option<ShowScrollbars>,
|
||||
#[serde(default)]
|
||||
pub cursor_blink: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub confirm_quit: Option<bool>,
|
||||
@@ -547,6 +560,7 @@ impl Settings {
|
||||
features: Features {
|
||||
copilot: defaults.features.copilot.unwrap(),
|
||||
},
|
||||
show_scrollbars: defaults.show_scrollbars.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,6 +612,7 @@ impl Settings {
|
||||
merge(&mut self.autosave, data.autosave);
|
||||
merge(&mut self.default_dock_anchor, data.default_dock_anchor);
|
||||
merge(&mut self.base_keymap, data.base_keymap);
|
||||
merge(&mut self.show_scrollbars, data.show_scrollbars);
|
||||
merge(&mut self.features.copilot, data.features.copilot);
|
||||
|
||||
if let Some(copilot) = data.copilot {
|
||||
@@ -830,6 +845,7 @@ impl Settings {
|
||||
auto_update: true,
|
||||
base_keymap: Default::default(),
|
||||
features: Features { copilot: true },
|
||||
show_scrollbars: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
|
||||
description = "The fast, collaborative code editor."
|
||||
edition = "2021"
|
||||
name = "zed"
|
||||
version = "0.87.0"
|
||||
version = "0.87.3"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
preview
|
||||
Reference in New Issue
Block a user