logiguard fork v3: full patch set on verified 8c74db0 tree
Includes prior-session patches (carry forward so the app compiles): - crates/gpui/build.rs: cross-compile manifest fix - crates/gpui/src/platform.rs: PlatformWindow::activate_with_token trait method - crates/gpui/src/window.rs: Window::activate_with_token public API - crates/gpui_linux/src/linux/wayland/window.rs: WaylandWindow::activate_with_token + activate() keyboard-serial fix Plus the focus-serial fix: - serial.rs: SerialKind::KeyboardEnter - client.rs: store wl_keyboard.enter serial; latest_serial_of() Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
76
crates/worktree/Cargo.toml
Normal file
76
crates/worktree/Cargo.toml
Normal file
@@ -0,0 +1,76 @@
|
||||
[package]
|
||||
name = "worktree"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lib]
|
||||
path = "src/worktree.rs"
|
||||
doctest = false
|
||||
test = false
|
||||
|
||||
[[test]]
|
||||
name = "integration"
|
||||
required-features = ["test-support"]
|
||||
path = "tests/integration/main.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
test-support = [
|
||||
"gpui/test-support",
|
||||
|
||||
"language/test-support",
|
||||
"pretty_assertions",
|
||||
"settings/test-support",
|
||||
"text/test-support",
|
||||
"util/test-support",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-channel.workspace = true
|
||||
async-lock.workspace = true
|
||||
chardetng.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
encoding_rs.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
ignore.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
postage.workspace = true
|
||||
pretty_assertions = { workspace = true, optional = true }
|
||||
rpc = { workspace = true, features = ["gpui"] }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
sum_tree.workspace = true
|
||||
text.workspace = true
|
||||
tracing.workspace = true
|
||||
util.workspace = true
|
||||
ztracing.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
clock = { workspace = true, features = ["test-support"] }
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
paths = { workspace = true, features = ["test-support"] }
|
||||
rand.workspace = true
|
||||
rpc = { workspace = true, features = ["test-support"] }
|
||||
settings = { workspace = true, features = ["test-support"] }
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
worktree = { workspace = true, features = ["test-support"] }
|
||||
zlog.workspace = true
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["tracing"]
|
||||
1
crates/worktree/LICENSE-GPL
Symbolic link
1
crates/worktree/LICENSE-GPL
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
||||
129
crates/worktree/src/ignore.rs
Normal file
129
crates/worktree/src/ignore.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
use ignore::gitignore::Gitignore;
|
||||
use std::{ffi::OsStr, path::Path, sync::Arc};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IgnoreStack {
|
||||
pub repo_root: Option<Arc<Path>>,
|
||||
pub top: Arc<IgnoreStackEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum IgnoreStackEntry {
|
||||
None,
|
||||
Global {
|
||||
ignore: Arc<Gitignore>,
|
||||
},
|
||||
RepoExclude {
|
||||
ignore: Arc<Gitignore>,
|
||||
parent: Arc<IgnoreStackEntry>,
|
||||
},
|
||||
Some {
|
||||
abs_base_path: Arc<Path>,
|
||||
ignore: Arc<Gitignore>,
|
||||
parent: Arc<IgnoreStackEntry>,
|
||||
},
|
||||
All,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum IgnoreKind {
|
||||
Gitignore(Arc<Path>),
|
||||
RepoExclude,
|
||||
}
|
||||
|
||||
impl IgnoreStack {
|
||||
pub fn none() -> Self {
|
||||
Self {
|
||||
repo_root: None,
|
||||
top: Arc::new(IgnoreStackEntry::None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all() -> Self {
|
||||
Self {
|
||||
repo_root: None,
|
||||
top: Arc::new(IgnoreStackEntry::All),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn global(ignore: Arc<Gitignore>) -> Self {
|
||||
Self {
|
||||
repo_root: None,
|
||||
top: Arc::new(IgnoreStackEntry::Global { ignore }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append(self, kind: IgnoreKind, ignore: Arc<Gitignore>) -> Self {
|
||||
let top = match self.top.as_ref() {
|
||||
IgnoreStackEntry::All => self.top.clone(),
|
||||
_ => Arc::new(match kind {
|
||||
IgnoreKind::Gitignore(abs_base_path) => IgnoreStackEntry::Some {
|
||||
abs_base_path,
|
||||
ignore,
|
||||
parent: self.top.clone(),
|
||||
},
|
||||
IgnoreKind::RepoExclude => IgnoreStackEntry::RepoExclude {
|
||||
ignore,
|
||||
parent: self.top.clone(),
|
||||
},
|
||||
}),
|
||||
};
|
||||
Self {
|
||||
repo_root: self.repo_root,
|
||||
top,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_abs_path_ignored(&self, abs_path: &Path, is_dir: bool) -> bool {
|
||||
if is_dir && abs_path.file_name() == Some(OsStr::new(".git")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
match self.top.as_ref() {
|
||||
IgnoreStackEntry::None => false,
|
||||
IgnoreStackEntry::All => true,
|
||||
IgnoreStackEntry::Global { ignore } => {
|
||||
let combined_path;
|
||||
let abs_path = if let Some(repo_root) = self.repo_root.as_ref() {
|
||||
combined_path = ignore.path().join(
|
||||
abs_path
|
||||
.strip_prefix(repo_root)
|
||||
.expect("repo root should be a parent of matched path"),
|
||||
);
|
||||
&combined_path
|
||||
} else {
|
||||
abs_path
|
||||
};
|
||||
match ignore.matched(abs_path, is_dir) {
|
||||
ignore::Match::None => false,
|
||||
ignore::Match::Ignore(_) => true,
|
||||
ignore::Match::Whitelist(_) => false,
|
||||
}
|
||||
}
|
||||
IgnoreStackEntry::RepoExclude { ignore, parent } => {
|
||||
match ignore.matched(abs_path, is_dir) {
|
||||
ignore::Match::None => IgnoreStack {
|
||||
repo_root: self.repo_root.clone(),
|
||||
top: parent.clone(),
|
||||
}
|
||||
.is_abs_path_ignored(abs_path, is_dir),
|
||||
ignore::Match::Ignore(_) => true,
|
||||
ignore::Match::Whitelist(_) => false,
|
||||
}
|
||||
}
|
||||
IgnoreStackEntry::Some {
|
||||
abs_base_path,
|
||||
ignore,
|
||||
parent: prev,
|
||||
} => match ignore.matched(abs_path.strip_prefix(abs_base_path).unwrap(), is_dir) {
|
||||
ignore::Match::None => IgnoreStack {
|
||||
repo_root: self.repo_root.clone(),
|
||||
top: prev.clone(),
|
||||
}
|
||||
.is_abs_path_ignored(abs_path, is_dir),
|
||||
ignore::Match::Ignore(_) => true,
|
||||
ignore::Match::Whitelist(_) => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
6687
crates/worktree/src/worktree.rs
Normal file
6687
crates/worktree/src/worktree.rs
Normal file
File diff suppressed because it is too large
Load Diff
106
crates/worktree/src/worktree_settings.rs
Normal file
106
crates/worktree/src/worktree_settings.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use settings::{RegisterSetting, Settings};
|
||||
use util::{
|
||||
ResultExt,
|
||||
paths::{PathMatcher, PathStyle},
|
||||
rel_path::RelPath,
|
||||
};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, RegisterSetting)]
|
||||
pub struct WorktreeSettings {
|
||||
/// Whether to prevent this project from being shared in public channels.
|
||||
pub prevent_sharing_in_public_channels: bool,
|
||||
pub file_scan_exclusions: PathMatcher,
|
||||
pub file_scan_inclusions: PathMatcher,
|
||||
/// This field contains all ancestors of the `file_scan_inclusions`. It's used to
|
||||
/// determine whether to terminate worktree scanning for a given dir.
|
||||
pub parent_dir_scan_inclusions: PathMatcher,
|
||||
pub private_files: PathMatcher,
|
||||
pub hidden_files: PathMatcher,
|
||||
pub read_only_files: PathMatcher,
|
||||
}
|
||||
|
||||
impl WorktreeSettings {
|
||||
pub fn is_path_private(&self, path: &RelPath) -> bool {
|
||||
path.ancestors()
|
||||
.any(|ancestor| self.private_files.is_match(ancestor))
|
||||
}
|
||||
|
||||
pub fn is_path_excluded(&self, path: &RelPath) -> bool {
|
||||
path.ancestors()
|
||||
.any(|ancestor| self.file_scan_exclusions.is_match(ancestor))
|
||||
}
|
||||
|
||||
pub fn is_path_always_included(&self, path: &RelPath, is_dir: bool) -> bool {
|
||||
if is_dir {
|
||||
self.parent_dir_scan_inclusions.is_match(path)
|
||||
} else {
|
||||
self.file_scan_inclusions.is_match(path)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_path_hidden(&self, path: &RelPath) -> bool {
|
||||
path.ancestors()
|
||||
.any(|ancestor| self.hidden_files.is_match(ancestor))
|
||||
}
|
||||
|
||||
pub fn is_path_read_only(&self, path: &RelPath) -> bool {
|
||||
self.read_only_files.is_match(path)
|
||||
}
|
||||
|
||||
pub fn is_std_path_read_only(&self, path: &Path) -> bool {
|
||||
self.read_only_files.is_match_std_path(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl Settings for WorktreeSettings {
|
||||
fn from_settings(content: &settings::SettingsContent) -> Self {
|
||||
let worktree = content.project.worktree.clone();
|
||||
let file_scan_exclusions = worktree.file_scan_exclusions.unwrap();
|
||||
let file_scan_inclusions = worktree.file_scan_inclusions.unwrap();
|
||||
let private_files = worktree.private_files.unwrap().0;
|
||||
let hidden_files = worktree.hidden_files.unwrap();
|
||||
let read_only_files = worktree.read_only_files.unwrap_or_default();
|
||||
let parsed_file_scan_inclusions: Vec<String> = file_scan_inclusions
|
||||
.iter()
|
||||
.flat_map(|glob| {
|
||||
Path::new(glob)
|
||||
.ancestors()
|
||||
.skip(1)
|
||||
.map(|a| a.to_string_lossy().into())
|
||||
})
|
||||
.filter(|p: &String| !p.is_empty())
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
prevent_sharing_in_public_channels: worktree.prevent_sharing_in_public_channels,
|
||||
file_scan_exclusions: path_matchers(file_scan_exclusions, "file_scan_exclusions")
|
||||
.log_err()
|
||||
.unwrap_or_default(),
|
||||
parent_dir_scan_inclusions: path_matchers(
|
||||
parsed_file_scan_inclusions,
|
||||
"file_scan_inclusions",
|
||||
)
|
||||
.unwrap(),
|
||||
file_scan_inclusions: path_matchers(file_scan_inclusions, "file_scan_inclusions")
|
||||
.unwrap(),
|
||||
private_files: path_matchers(private_files, "private_files")
|
||||
.log_err()
|
||||
.unwrap_or_default(),
|
||||
hidden_files: path_matchers(hidden_files, "hidden_files")
|
||||
.log_err()
|
||||
.unwrap_or_default(),
|
||||
read_only_files: path_matchers(read_only_files, "read_only_files")
|
||||
.log_err()
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn path_matchers(mut values: Vec<String>, context: &'static str) -> anyhow::Result<PathMatcher> {
|
||||
values.sort();
|
||||
PathMatcher::new(values, PathStyle::local())
|
||||
.with_context(|| format!("Failed to parse globs from {}", context))
|
||||
}
|
||||
4406
crates/worktree/tests/integration/main.rs
Normal file
4406
crates/worktree/tests/integration/main.rs
Normal file
File diff suppressed because it is too large
Load Diff
120
crates/worktree/tests/integration/worktree_settings.rs
Normal file
120
crates/worktree/tests/integration/worktree_settings.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
use std::path::Path;
|
||||
use util::{
|
||||
paths::{PathMatcher, PathStyle},
|
||||
rel_path::RelPath,
|
||||
};
|
||||
use worktree::*;
|
||||
|
||||
fn make_settings_with_read_only(patterns: &[&str]) -> WorktreeSettings {
|
||||
WorktreeSettings {
|
||||
prevent_sharing_in_public_channels: false,
|
||||
file_scan_exclusions: PathMatcher::default(),
|
||||
file_scan_inclusions: PathMatcher::default(),
|
||||
parent_dir_scan_inclusions: PathMatcher::default(),
|
||||
private_files: PathMatcher::default(),
|
||||
hidden_files: PathMatcher::default(),
|
||||
read_only_files: PathMatcher::new(
|
||||
patterns.iter().map(|s| s.to_string()),
|
||||
PathStyle::local(),
|
||||
)
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_path_read_only_with_glob_patterns() {
|
||||
let settings = make_settings_with_read_only(&["**/generated/**", "**/*.gen.rs"]);
|
||||
|
||||
let generated_file =
|
||||
RelPath::new(Path::new("src/generated/schema.rs"), PathStyle::local()).unwrap();
|
||||
assert!(
|
||||
settings.is_path_read_only(&generated_file),
|
||||
"Files in generated directory should be read-only"
|
||||
);
|
||||
|
||||
let gen_rs_file = RelPath::new(Path::new("src/types.gen.rs"), PathStyle::local()).unwrap();
|
||||
assert!(
|
||||
settings.is_path_read_only(&gen_rs_file),
|
||||
"Files with .gen.rs extension should be read-only"
|
||||
);
|
||||
|
||||
let regular_file = RelPath::new(Path::new("src/main.rs"), PathStyle::local()).unwrap();
|
||||
assert!(
|
||||
!settings.is_path_read_only(®ular_file),
|
||||
"Regular files should not be read-only"
|
||||
);
|
||||
|
||||
let similar_name = RelPath::new(Path::new("src/generator.rs"), PathStyle::local()).unwrap();
|
||||
assert!(
|
||||
!settings.is_path_read_only(&similar_name),
|
||||
"Files with 'generator' in name but not in generated dir should not be read-only"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_path_read_only_with_specific_paths() {
|
||||
let settings = make_settings_with_read_only(&["vendor/**", "node_modules/**"]);
|
||||
|
||||
let vendor_file = RelPath::new(Path::new("vendor/lib/package.js"), PathStyle::local()).unwrap();
|
||||
assert!(
|
||||
settings.is_path_read_only(&vendor_file),
|
||||
"Files in vendor directory should be read-only"
|
||||
);
|
||||
|
||||
let node_modules_file = RelPath::new(
|
||||
Path::new("node_modules/lodash/index.js"),
|
||||
PathStyle::local(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
settings.is_path_read_only(&node_modules_file),
|
||||
"Files in node_modules should be read-only"
|
||||
);
|
||||
|
||||
let src_file = RelPath::new(Path::new("src/app.js"), PathStyle::local()).unwrap();
|
||||
assert!(
|
||||
!settings.is_path_read_only(&src_file),
|
||||
"Files in src should not be read-only"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_path_read_only_empty_patterns() {
|
||||
let settings = make_settings_with_read_only(&[]);
|
||||
|
||||
let any_file = RelPath::new(Path::new("src/main.rs"), PathStyle::local()).unwrap();
|
||||
assert!(
|
||||
!settings.is_path_read_only(&any_file),
|
||||
"No files should be read-only when patterns are empty"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_path_read_only_with_extension_pattern() {
|
||||
let settings = make_settings_with_read_only(&["**/*.lock", "**/*.min.js"]);
|
||||
|
||||
let lock_file = RelPath::new(Path::new("Cargo.lock"), PathStyle::local()).unwrap();
|
||||
assert!(
|
||||
settings.is_path_read_only(&lock_file),
|
||||
"Lock files should be read-only"
|
||||
);
|
||||
|
||||
let nested_lock =
|
||||
RelPath::new(Path::new("packages/app/yarn.lock"), PathStyle::local()).unwrap();
|
||||
assert!(
|
||||
settings.is_path_read_only(&nested_lock),
|
||||
"Nested lock files should be read-only"
|
||||
);
|
||||
|
||||
let minified_js = RelPath::new(Path::new("dist/bundle.min.js"), PathStyle::local()).unwrap();
|
||||
assert!(
|
||||
settings.is_path_read_only(&minified_js),
|
||||
"Minified JS files should be read-only"
|
||||
);
|
||||
|
||||
let regular_js = RelPath::new(Path::new("src/app.js"), PathStyle::local()).unwrap();
|
||||
assert!(
|
||||
!settings.is_path_read_only(®ular_js),
|
||||
"Regular JS files should not be read-only"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user