diff --git a/.zed/settings.json b/.zed/settings.json index 205d610046..8cf61a17bd 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -1,6 +1,6 @@ { - "JSON": { - "tab_size": 4 - }, - "formatter": "auto" + "JSON": { + "tab_size": 4 + }, + "formatter": "auto" } diff --git a/assets/settings/default.json b/assets/settings/default.json index 33efad6ceb..f78a4637e5 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -72,6 +72,17 @@ "show_wrap_guides": true, // Character counts at which to show wrap guides in the editor. "wrap_guides": [], + // Hide the values of in variables from visual display in private files + "redact_private_values": false, + // Globs to match against file paths to determine if a file is private. + "private_files": [ + "**/.env*", + "**/*.pem", + "**/*.key", + "**/*.cert", + "**/*.crt", + "**/secrets.yml" + ], // Whether to use additional LSP queries to format (and amend) the code after // every "trigger" symbol input, defined by LSP server capabilities. "use_on_type_format": true, diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 6f5c523400..16f941d16f 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -1251,6 +1251,10 @@ mod tests { fn worktree_id(&self) -> usize { 0 } + + fn is_private(&self) -> bool { + false + } } impl language::LocalFile for File { diff --git a/crates/copilot_ui/src/copilot_button.rs b/crates/copilot_ui/src/copilot_button.rs index 28b28ffe9a..8ea106a3ff 100644 --- a/crates/copilot_ui/src/copilot_button.rs +++ b/crates/copilot_ui/src/copilot_button.rs @@ -225,8 +225,9 @@ impl CopilotButton { let file = snapshot.file_at(suggestion_anchor).cloned(); self.editor_enabled = Some( - all_language_settings(self.file.as_ref(), cx) - .copilot_enabled(language, file.as_ref().map(|file| file.path().as_ref())), + file.as_ref().map(|file| !file.is_private()).unwrap_or(true) + && all_language_settings(self.file.as_ref(), cx) + .copilot_enabled(language, file.as_ref().map(|file| file.path().as_ref())), ); self.language = language.cloned(); self.file = file; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c795eecb04..47731b510d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -8493,6 +8493,31 @@ impl Editor { results } + /// Get the text ranges corresponding to the redaction query + pub fn redacted_ranges( + &self, + search_range: Range, + display_snapshot: &DisplaySnapshot, + cx: &mut ViewContext, + ) -> Vec> { + display_snapshot + .buffer_snapshot + .redacted_ranges(search_range, |file| { + if let Some(file) = file { + file.is_private() + && EditorSettings::get(Some((file.worktree_id(), file.path())), cx) + .redact_private_values + } else { + false + } + }) + .map(|range| { + range.start.to_display_point(display_snapshot) + ..range.end.to_display_point(display_snapshot) + }) + .collect() + } + pub fn highlight_text( &mut self, ranges: Vec>, diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index d4d71cd2bf..0cfc677e93 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -13,6 +13,7 @@ pub struct EditorSettings { pub scrollbar: Scrollbar, pub relative_line_numbers: bool, pub seed_search_query_from_cursor: SeedQuerySetting, + pub redact_private_values: bool, } /// When to populate a new search's query based on the text under the cursor. @@ -93,6 +94,13 @@ pub struct EditorSettingsContent { /// /// Default: always pub seed_search_query_from_cursor: Option, + + /// Hide the values of variables in `private` files, as defined by the + /// private_files setting. This only changes the visual representation, + /// the values are still present in the file and can be selected / copied / pasted + /// + /// Default: false + pub redact_private_values: Option, } /// Scrollbar related settings diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7f37f539d3..57389fd8f9 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1153,7 +1153,9 @@ impl EditorElement { ) } - cx.with_z_index(0, |cx| { + cx.with_z_index(0, |cx| self.paint_redactions(text_bounds, &layout, cx)); + + cx.with_z_index(1, |cx| { for cursor in cursors { cursor.paint(content_origin, cx); } @@ -1162,6 +1164,32 @@ impl EditorElement { ) } + fn paint_redactions( + &mut self, + text_bounds: Bounds, + layout: &LayoutState, + cx: &mut ElementContext, + ) { + let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO); + let line_end_overshoot = layout.line_end_overshoot(); + + // A softer than perfect black + let redaction_color = gpui::rgb(0x0e1111); + + for range in layout.redacted_ranges.iter() { + self.paint_highlighted_range( + range.clone(), + redaction_color.into(), + Pixels::ZERO, + line_end_overshoot, + layout, + content_origin, + text_bounds, + cx, + ); + } + } + fn paint_overlays( &mut self, text_bounds: Bounds, @@ -1957,6 +1985,8 @@ impl EditorElement { cx.theme().colors(), ); + let redacted_ranges = editor.redacted_ranges(start_anchor..end_anchor, &snapshot.display_snapshot, cx); + let mut newest_selection_head = None; if editor.show_local_selections { @@ -2298,6 +2328,7 @@ impl EditorElement { active_rows, highlighted_rows, highlighted_ranges, + redacted_ranges, line_numbers, display_hunks, blocks, @@ -3082,6 +3113,7 @@ pub struct LayoutState { display_hunks: Vec, blocks: Vec, highlighted_ranges: Vec<(Range, Hsla)>, + redacted_ranges: Vec>, selections: Vec<(PlayerColor, Vec)>, scrollbar_row_range: Range, show_scrollbars: bool, @@ -3095,6 +3127,12 @@ pub struct LayoutState { space_invisible: ShapedLine, } +impl LayoutState { + fn line_end_overshoot(&self) -> Pixels { + 0.15 * self.position_map.line_height + } +} + struct CodeActionsIndicator { row: u32, button: IconButton, diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index d9ea6385cb..3a4a14da0e 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1364,5 +1364,9 @@ mod tests { fn to_proto(&self) -> rpc::proto::File { unimplemented!() } + + fn is_private(&self) -> bool { + false + } } } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 7a658d1b03..0bb069729f 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -383,6 +383,9 @@ pub trait File: Send + Sync { /// Converts this file into a protobuf message. fn to_proto(&self) -> rpc::proto::File; + + /// Return whether Zed considers this to be a dotenv file. + fn is_private(&self) -> bool; } /// The file associated with a buffer, in the case where the file is on the local disk. @@ -2877,6 +2880,43 @@ impl BufferSnapshot { }) } + /// Returns anchor ranges for any matches of the redaction query. + /// The buffer can be associated with multiple languages, and the redaction query associated with each + /// will be run on the relevant section of the buffer. + pub fn redacted_ranges<'a, T: ToOffset>( + &'a self, + range: Range, + ) -> impl Iterator> + 'a { + let offset_range = range.start.to_offset(self)..range.end.to_offset(self); + let mut syntax_matches = self.syntax.matches(offset_range, self, |grammar| { + grammar + .redactions_config + .as_ref() + .map(|config| &config.query) + }); + + let configs = syntax_matches + .grammars() + .iter() + .map(|grammar| grammar.redactions_config.as_ref()) + .collect::>(); + + iter::from_fn(move || { + let redacted_range = syntax_matches + .peek() + .and_then(|mat| { + configs[mat.grammar_index].and_then(|config| { + mat.captures + .iter() + .find(|capture| capture.index == config.redaction_capture_ix) + }) + }) + .map(|mat| mat.node.byte_range()); + syntax_matches.advance(); + redacted_range + }) + } + /// Returns selections for remote peers intersecting the given range. #[allow(clippy::type_complexity)] pub fn remote_selections_in_range( diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index bd4c7f5d93..70e85761d2 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -453,6 +453,7 @@ pub struct LanguageQueries { pub embedding: Option>, pub injections: Option>, pub overrides: Option>, + pub redactions: Option>, } /// Represents a language for the given range. Some languages (e.g. HTML) @@ -623,6 +624,7 @@ pub struct Grammar { pub(crate) error_query: Query, pub(crate) highlights_query: Option, pub(crate) brackets_config: Option, + pub(crate) redactions_config: Option, pub(crate) indents_config: Option, pub outline_config: Option, pub embedding_config: Option, @@ -664,6 +666,11 @@ struct InjectionConfig { patterns: Vec, } +struct RedactionConfig { + pub query: Query, + pub redaction_capture_ix: u32, +} + struct OverrideConfig { query: Query, values: HashMap, @@ -1303,6 +1310,7 @@ impl Language { indents_config: None, injection_config: None, override_config: None, + redactions_config: None, error_query: Query::new(&ts_language, "(ERROR) @error").unwrap(), ts_language, highlight_map: Default::default(), @@ -1359,6 +1367,11 @@ impl Language { .with_override_query(query.as_ref()) .context("Error loading override query")?; } + if let Some(query) = queries.redactions { + self = self + .with_redaction_query(query.as_ref()) + .context("Error loading redaction query")?; + } Ok(self) } @@ -1589,6 +1602,22 @@ impl Language { Ok(self) } + pub fn with_redaction_query(mut self, source: &str) -> anyhow::Result { + let grammar = self.grammar_mut(); + let query = Query::new(&grammar.ts_language, source)?; + let mut redaction_capture_ix = None; + get_capture_indices(&query, &mut [("redact", &mut redaction_capture_ix)]); + + if let Some(redaction_capture_ix) = redaction_capture_ix { + grammar.redactions_config = Some(RedactionConfig { + query, + redaction_capture_ix, + }); + } + + Ok(self) + } + fn grammar_mut(&mut self) -> &mut Grammar { Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap() } diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 6ca6a70e4a..4c20a32a12 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -1059,7 +1059,7 @@ impl<'a> SyntaxMapMatches<'a> { .position(|later_layer| key < later_layer.sort_key()) .unwrap_or(self.active_layer_count - 1); self.layers[0..i].rotate_left(1); - } else { + } else if self.active_layer_count != 0 { self.layers[0..self.active_layer_count].rotate_left(1); self.active_layer_count -= 1; } diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 0f90612358..e16fc3a504 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -36,6 +36,7 @@ use text::{ BufferId, Edit, TextSummary, }; use theme::SyntaxTheme; + use util::post_inc; #[cfg(any(test, feature = "test-support"))] @@ -2784,6 +2785,26 @@ impl MultiBufferSnapshot { .map(|excerpt| (excerpt.id, &excerpt.buffer, excerpt.range.clone())) } + fn excerpts_for_range<'a, T: ToOffset>( + &'a self, + range: Range, + ) -> impl Iterator + 'a { + let range = range.start.to_offset(self)..range.end.to_offset(self); + + let mut cursor = self.excerpts.cursor::(); + cursor.seek(&range.start, Bias::Right, &()); + cursor.prev(&()); + + iter::from_fn(move || { + cursor.next(&()); + if cursor.start() < &range.end { + cursor.item().map(|item| (item, *cursor.start())) + } else { + None + } + }) + } + pub fn excerpt_boundaries_in_range( &self, range: R, @@ -2942,6 +2963,37 @@ impl MultiBufferSnapshot { }) } + pub fn redacted_ranges<'a, T: ToOffset>( + &'a self, + range: Range, + redaction_enabled: impl Fn(Option<&Arc>) -> bool + 'a, + ) -> impl Iterator> + 'a { + let range = range.start.to_offset(self)..range.end.to_offset(self); + self.excerpts_for_range(range.clone()) + .filter_map(move |(excerpt, excerpt_offset)| { + redaction_enabled(excerpt.buffer.file()).then(move || { + let excerpt_buffer_start = + excerpt.range.context.start.to_offset(&excerpt.buffer); + + excerpt + .buffer + .redacted_ranges(excerpt.range.context.clone()) + .map(move |mut redacted_range| { + // Re-base onto the excerpts coordinates in the multibuffer + redacted_range.start = + excerpt_offset + (redacted_range.start - excerpt_buffer_start); + redacted_range.end = + excerpt_offset + (redacted_range.end - excerpt_buffer_start); + + redacted_range + }) + .skip_while(move |redacted_range| redacted_range.end < range.start) + .take_while(move |redacted_range| redacted_range.start < range.end) + }) + }) + .flatten() + } + pub fn diagnostics_update_count(&self) -> usize { self.diagnostics_update_count } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index fc7c80c7f9..bc13c5cf31 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -6470,6 +6470,7 @@ impl Project { path: entry.path.clone(), worktree: worktree_handle.clone(), is_deleted: false, + is_private: entry.is_private, } } else if let Some(entry) = snapshot.entry_for_path(old_file.path().as_ref()) { File { @@ -6479,6 +6480,7 @@ impl Project { path: entry.path.clone(), worktree: worktree_handle.clone(), is_deleted: false, + is_private: entry.is_private, } } else { File { @@ -6488,6 +6490,7 @@ impl Project { mtime: old_file.mtime(), worktree: worktree_handle.clone(), is_deleted: true, + is_private: old_file.is_private, } }; diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index 9ec07bc088..d26209043e 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -20,6 +20,7 @@ pub struct ProjectSettings { /// Configuration for Git-related features #[serde(default)] pub git: GitSettings, + /// Completely ignore files matching globs from `file_scan_exclusions` /// /// Default: [ @@ -34,6 +35,10 @@ pub struct ProjectSettings { /// ] #[serde(default)] pub file_scan_exclusions: Option>, + + /// Treat the files matching these globs as `.env` files. + /// Default: [ "**/.env*" ] + pub private_files: Option>, } #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 959a63a7c7..21994397fe 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -230,6 +230,7 @@ pub struct LocalSnapshot { /// id of their parent directory. git_repositories: TreeMap, file_scan_exclusions: Vec, + private_files: Vec, } struct BackgroundScannerState { @@ -319,16 +320,34 @@ impl Worktree { cx.new_model(move |cx: &mut ModelContext| { cx.observe_global::(move |this, cx| { if let Self::Local(this) = this { - let new_file_scan_exclusions = - file_scan_exclusions(ProjectSettings::get_global(cx)); - if new_file_scan_exclusions != this.snapshot.file_scan_exclusions { + let new_file_scan_exclusions = path_matchers( + ProjectSettings::get_global(cx) + .file_scan_exclusions + .as_deref(), + "file_scan_exclusions", + ); + let new_private_files = path_matchers( + ProjectSettings::get(Some((cx.handle().entity_id().as_u64() as usize, &Path::new(""))), cx).private_files.as_deref(), + "private_files", + ); + + if new_file_scan_exclusions != this.snapshot.file_scan_exclusions + || new_private_files != this.snapshot.private_files + { this.snapshot.file_scan_exclusions = new_file_scan_exclusions; + this.snapshot.private_files = new_private_files; + log::info!( - "Re-scanning directories, new scan exclude files: {:?}", + "Re-scanning directories, new scan exclude files: {:?}, new dotenv files: {:?}", this.snapshot .file_scan_exclusions .iter() .map(ToString::to_string) + .collect::>(), + this.snapshot + .private_files + .iter() + .map(ToString::to_string) .collect::>() ); @@ -357,7 +376,16 @@ impl Worktree { .map_or(String::new(), |f| f.to_string_lossy().to_string()); let mut snapshot = LocalSnapshot { - file_scan_exclusions: file_scan_exclusions(ProjectSettings::get_global(cx)), + file_scan_exclusions: path_matchers( + ProjectSettings::get_global(cx) + .file_scan_exclusions + .as_deref(), + "file_scan_exclusions", + ), + private_files: path_matchers( + ProjectSettings::get(Some((cx.handle().entity_id().as_u64() as usize, &Path::new(""))), cx).private_files.as_deref(), + "private_files", + ), ignores_by_parent_abs_path: Default::default(), git_repositories: Default::default(), snapshot: Snapshot { @@ -650,20 +678,22 @@ fn start_background_scan_tasks( vec![background_scanner, scan_state_updater] } -fn file_scan_exclusions(project_settings: &ProjectSettings) -> Vec { - project_settings.file_scan_exclusions.as_deref().unwrap_or(&[]).iter() - .sorted() - .filter_map(|pattern| { - PathMatcher::new(pattern) - .map(Some) - .unwrap_or_else(|e| { - log::error!( - "Skipping pattern {pattern} in `file_scan_exclusions` project settings due to parsing error: {e:#}" - ); - None - }) - }) - .collect() +fn path_matchers(values: Option<&[String]>, context: &'static str) -> Vec { + values + .unwrap_or(&[]) + .iter() + .sorted() + .filter_map(|pattern| { + PathMatcher::new(pattern) + .map(Some) + .unwrap_or_else(|e| { + log::error!( + "Skipping pattern {pattern} in `{}` project settings due to parsing error: {e:#}", context + ); + None + }) + }) + .collect() } impl LocalWorktree { @@ -1003,6 +1033,7 @@ impl LocalWorktree { mtime: entry.mtime, is_local: true, is_deleted: false, + is_private: entry.is_private, }, text, diff_base, @@ -1017,6 +1048,7 @@ impl LocalWorktree { .with_context(|| { format!("Excluded file {abs_path:?} got removed during loading") })?; + let is_private = snapshot.is_path_private(path.as_ref()); Ok(( File { entry_id: None, @@ -1025,6 +1057,7 @@ impl LocalWorktree { mtime: metadata.mtime, is_local: true, is_deleted: false, + is_private, }, text, diff_base, @@ -1053,14 +1086,15 @@ impl LocalWorktree { let save = self.write_file(path.as_ref(), text, buffer.line_ending(), cx); let fs = Arc::clone(&self.fs); let abs_path = self.absolutize(&path); + let is_private = self.snapshot.is_path_private(&path); cx.spawn(move |this, mut cx| async move { let entry = save.await?; let abs_path = abs_path?; let this = this.upgrade().context("worktree dropped")?; - let (entry_id, mtime, path) = match entry { - Some(entry) => (Some(entry.id), entry.mtime, entry.path), + let (entry_id, mtime, path, is_dotenv) = match entry { + Some(entry) => (Some(entry.id), entry.mtime, entry.path, entry.is_private), None => { let metadata = fs .metadata(&abs_path) @@ -1073,7 +1107,7 @@ impl LocalWorktree { .with_context(|| { format!("Excluded buffer {path:?} got removed during saving") })?; - (None, metadata.mtime, path) + (None, metadata.mtime, path, is_private) } }; @@ -1085,6 +1119,7 @@ impl LocalWorktree { mtime, is_local: true, is_deleted: false, + is_private: is_dotenv, }); if let Some(project_id) = project_id { @@ -2295,6 +2330,14 @@ impl LocalSnapshot { paths } + pub fn is_path_private(&self, path: &Path) -> bool { + path.ancestors().any(|ancestor| { + self.private_files + .iter() + .any(|exclude_matcher| exclude_matcher.is_match(&ancestor)) + }) + } + pub fn is_path_excluded(&self, mut path: PathBuf) -> bool { loop { if self @@ -2747,6 +2790,7 @@ pub struct File { pub(crate) entry_id: Option, pub(crate) is_local: bool, pub(crate) is_deleted: bool, + pub(crate) is_private: bool, } impl language::File for File { @@ -2819,6 +2863,10 @@ impl language::File for File { is_deleted: self.is_deleted, } } + + fn is_private(&self) -> bool { + self.is_private + } } impl language::LocalFile for File { @@ -2874,6 +2922,7 @@ impl File { entry_id: Some(entry.id), is_local: true, is_deleted: false, + is_private: entry.is_private, }) } @@ -2899,6 +2948,7 @@ impl File { entry_id: proto.entry_id.map(ProjectEntryId::from_proto), is_local: false, is_deleted: proto.is_deleted, + is_private: false, }) } @@ -2943,6 +2993,8 @@ pub struct Entry { /// entries in that they are not included in searches. pub is_external: bool, pub git_status: Option, + /// Whether this entry is considered to be a `.env` file. + pub is_private: bool, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -2997,6 +3049,7 @@ impl Entry { is_symlink: metadata.is_symlink, is_ignored: false, is_external: false, + is_private: false, git_status: None, } } @@ -3732,7 +3785,7 @@ impl BackgroundScanner { ancestor_inodes.insert(child_entry.inode); new_jobs.push(Some(ScanJob { - abs_path: child_abs_path, + abs_path: child_abs_path.clone(), path: child_path, is_external: child_entry.is_external, ignore_stack: if child_entry.is_ignored { @@ -3766,6 +3819,16 @@ impl BackgroundScanner { } } + { + let relative_path = job.path.join(child_name); + let state = self.state.lock(); + if state.snapshot.is_path_private(&relative_path) { + log::debug!("detected private file: {relative_path:?}"); + child_entry.is_private = true; + } + drop(state) + } + new_entries.push(child_entry); } @@ -3866,6 +3929,7 @@ impl BackgroundScanner { let is_dir = fs_entry.is_dir(); fs_entry.is_ignored = ignore_stack.is_abs_path_ignored(&abs_path, is_dir); fs_entry.is_external = !canonical_path.starts_with(&root_canonical_path); + fs_entry.is_private = state.snapshot.is_path_private(path); if !is_dir && !fs_entry.is_ignored && !fs_entry.is_external { if let Some((work_dir, repo)) = state.snapshot.local_repo_for_path(path) { @@ -4548,6 +4612,7 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry { is_ignored: entry.is_ignored, is_external: entry.is_external, git_status: git_status_from_proto(entry.git_status), + is_private: false, }) } else { Err(anyhow!( diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 183f454d19..290969d0f4 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -101,6 +101,7 @@ pub struct EntryDetails { is_processing: bool, is_cut: bool, git_status: Option, + is_dotenv: bool, } actions!( @@ -1137,6 +1138,7 @@ impl ProjectPanel { is_symlink: false, is_ignored: false, is_external: false, + is_private: false, git_status: entry.git_status, }); } @@ -1298,6 +1300,7 @@ impl ProjectPanel { .clipboard_entry .map_or(false, |e| e.is_cut() && e.entry_id() == entry.id), git_status: status, + is_dotenv: entry.is_private, }; if let Some(edit_state) = &self.edit_state { diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 41dfa521ed..7398876137 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -86,6 +86,7 @@ pub trait Settings: 'static + Send + Sync { }); } + /// path is a (worktree ID, Path) #[track_caller] fn get<'a>(path: Option<(usize, &Path)>, cx: &'a AppContext) -> &'a Self where diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 64bec543c2..f0217af1d9 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -357,6 +357,7 @@ const QUERY_FILENAME_PREFIXES: &[( ("embedding", |q| &mut q.embedding), ("injections", |q| &mut q.injections), ("overrides", |q| &mut q.overrides), + ("redactions", |q| &mut q.redactions), ]; fn load_queries(name: &str) -> LanguageQueries { diff --git a/crates/zed/src/languages/bash/config.toml b/crates/zed/src/languages/bash/config.toml index cbf186fd3d..8b86318ecd 100644 --- a/crates/zed/src/languages/bash/config.toml +++ b/crates/zed/src/languages/bash/config.toml @@ -1,5 +1,5 @@ name = "Shell Script" -path_suffixes = ["sh", "bash", "bashrc", "bash_profile", "bash_aliases", "bash_logout", "profile", "zsh", "zshrc", "zshenv", "zsh_profile", "zsh_aliases", "zsh_histfile", "zlogin", "zprofile"] +path_suffixes = ["sh", "bash", "bashrc", "bash_profile", "bash_aliases", "bash_logout", "profile", "zsh", "zshrc", "zshenv", "zsh_profile", "zsh_aliases", "zsh_histfile", "zlogin", "zprofile", ".env"] line_comments = ["# "] first_line_pattern = "^#!.*\\b(?:ba|z)?sh\\b" brackets = [ diff --git a/crates/zed/src/languages/bash/redactions.scm b/crates/zed/src/languages/bash/redactions.scm new file mode 100644 index 0000000000..88b38f42fc --- /dev/null +++ b/crates/zed/src/languages/bash/redactions.scm @@ -0,0 +1,2 @@ +(variable_assignment + value: (_) @redact) \ No newline at end of file diff --git a/crates/zed/src/languages/json/redactions.scm b/crates/zed/src/languages/json/redactions.scm new file mode 100644 index 0000000000..be985f018c --- /dev/null +++ b/crates/zed/src/languages/json/redactions.scm @@ -0,0 +1,4 @@ +(pair value: (number) @redact) +(pair value: (string) @redact) +(array (number) @redact) +(array (string) @redact) \ No newline at end of file diff --git a/crates/zed/src/languages/toml/redactions.scm b/crates/zed/src/languages/toml/redactions.scm new file mode 100644 index 0000000000..fd11a02927 --- /dev/null +++ b/crates/zed/src/languages/toml/redactions.scm @@ -0,0 +1 @@ +(pair (bare_key) "=" (_) @redact) \ No newline at end of file diff --git a/crates/zed/src/languages/yaml/redactions.scm b/crates/zed/src/languages/yaml/redactions.scm new file mode 100644 index 0000000000..85fdbd26ea --- /dev/null +++ b/crates/zed/src/languages/yaml/redactions.scm @@ -0,0 +1 @@ +(block_mapping_pair value: (flow_node) @redact)