diff --git a/crates/diagnostics/src/buffer_diagnostics.rs b/crates/diagnostics/src/buffer_diagnostics.rs index 9a0adbd288e91ef42a01fb056d7656c338d6087d..adea53af17d81f691b5503effe781c6ab039a676 100644 --- a/crates/diagnostics/src/buffer_diagnostics.rs +++ b/crates/diagnostics/src/buffer_diagnostics.rs @@ -15,7 +15,7 @@ use gpui::{ InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Task, WeakEntity, Window, actions, div, }; -use language::{Buffer, DiagnosticEntry, DiagnosticEntryRef, Point}; +use language::{Buffer, Capability, DiagnosticEntry, DiagnosticEntryRef, Point}; use project::{ DiagnosticSummary, Event, Project, ProjectItem, ProjectPath, project_settings::{DiagnosticSeverity, ProjectSettings}, @@ -763,8 +763,8 @@ impl Item for BufferDiagnosticsEditor { self.multibuffer.read(cx).is_dirty(cx) } - fn is_read_only(&self, cx: &App) -> bool { - self.multibuffer.read(cx).read_only() + fn capability(&self, cx: &App) -> Capability { + self.multibuffer.read(cx).capability() } fn navigate( diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 44e91973e1d2c58d94afa1cb69cbfe33d7fee386..985b5d6bb31125a7d1bab7a986996be78ef96063 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3162,6 +3162,14 @@ impl Editor { } } + pub fn capability(&self, cx: &App) -> Capability { + if self.read_only { + Capability::ReadOnly + } else { + self.buffer.read(cx).capability() + } + } + pub fn read_only(&self, cx: &App) -> bool { self.read_only || self.buffer.read(cx).read_only() } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 0150559e5536f4df34cd3c8794ab1ed7ad9e39a0..97f75621c8231f5bed17f94ea43e342caa90a417 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -805,8 +805,8 @@ impl Item for Editor { self.buffer().read(cx).read(cx).is_dirty() } - fn is_read_only(&self, cx: &App) -> bool { - self.read_only(cx) + fn capability(&self, cx: &App) -> Capability { + self.capability(cx) } // Note: this mirrors the logic in `Editor::toggle_read_only`, but is reachable diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 6b3c90c40a5ce494567d60f1283eca3e72c5ef90..800bb92fc079f33c147af730d27914f252235cf6 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -85,7 +85,7 @@ pub static BUFFER_DIFF_TASK: LazyLock = LazyLock::new(TaskLabel::new) pub enum Capability { /// The buffer is a mutable replica. ReadWrite, - /// The buffer is a mutable replica, but toggled to read-only. + /// The buffer is a mutable replica, but toggled to be only readable. Read, /// The buffer is a read-only replica. ReadOnly, diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index cf48af98475190d15e04309b509f09f179886f20..6bb2b45e151143557eedfe2d3095976fe75776fb 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -1198,6 +1198,10 @@ impl MultiBuffer { !self.capability.editable() } + pub fn capability(&self) -> Capability { + self.capability + } + /// Returns an up-to-date snapshot of the MultiBuffer. #[ztracing::instrument(skip_all)] pub fn snapshot(&self, cx: &App) -> MultiBufferSnapshot { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 367cec0da617de612d76f559fe8bb589733b50e0..93a148c62ab6e395ccc63ac5c38204e5643b706d 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -15,6 +15,7 @@ use gpui::{ EventEmitter, FocusHandle, Focusable, Font, HighlightStyle, Pixels, Point, Render, SharedString, Task, WeakEntity, Window, }; +use language::Capability; use project::{Project, ProjectEntryId, ProjectPath}; pub use settings::{ ActivateOnClose, ClosePosition, RegisterSetting, Settings, SettingsLocation, ShowCloseButton, @@ -255,8 +256,8 @@ pub trait Item: Focusable + EventEmitter + Render + Sized { fn is_dirty(&self, _: &App) -> bool { false } - fn is_read_only(&self, _: &App) -> bool { - false + fn capability(&self, _: &App) -> Capability { + Capability::ReadWrite } fn toggle_read_only(&mut self, _window: &mut Window, _cx: &mut Context) {} @@ -482,7 +483,7 @@ pub trait ItemHandle: 'static + Send { fn item_id(&self) -> EntityId; fn to_any_view(&self) -> AnyView; fn is_dirty(&self, cx: &App) -> bool; - fn is_read_only(&self, cx: &App) -> bool; + fn capability(&self, cx: &App) -> Capability; fn toggle_read_only(&self, window: &mut Window, cx: &mut App); fn has_deleted_file(&self, cx: &App) -> bool; fn has_conflict(&self, cx: &App) -> bool; @@ -957,8 +958,8 @@ impl ItemHandle for Entity { self.read(cx).is_dirty(cx) } - fn is_read_only(&self, cx: &App) -> bool { - self.read(cx).is_read_only(cx) + fn capability(&self, cx: &App) -> Capability { + self.read(cx).capability(cx) } fn toggle_read_only(&self, window: &mut Window, cx: &mut App) { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index fdf886e8ca2bd01839654fe2d646282f50abe2ab..d1fefea4026305671b29bac07483fcb2ceb9da90 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -26,7 +26,7 @@ use gpui::{ actions, anchored, deferred, prelude::*, }; use itertools::Itertools; -use language::DiagnosticSeverity; +use language::{Capability, DiagnosticSeverity}; use parking_lot::Mutex; use project::{DirectoryLister, Project, ProjectEntryId, ProjectPath, WorktreeId}; use schemars::JsonSchema; @@ -2678,12 +2678,13 @@ impl Pane { let is_pinned = self.is_tab_pinned(ix); let position_relative_to_active_item = ix.cmp(&self.active_item_index); - let read_only_toggle = || { + let read_only_toggle = |toggleable: bool| { IconButton::new("toggle_read_only", IconName::FileLock) .size(ButtonSize::None) .shape(IconButtonShape::Square) .icon_color(Color::Muted) .icon_size(IconSize::Small) + .disabled(!toggleable) .tooltip(move |_, cx| { Tooltip::with_meta("Unlock File", None, "This will make this file editable", cx) }) @@ -2696,6 +2697,7 @@ impl Pane { let has_file_icon = icon.is_some() | decorated_icon.is_some(); + let capability = item.capability(cx); let tab = Tab::new(ix) .position(if is_first_item { TabPosition::First @@ -2836,21 +2838,21 @@ impl Pane { Some(decorated_icon.into_any_element()) } else if let Some(icon) = icon { Some(icon.into_any_element()) - } else if item.is_read_only(cx) { - Some(read_only_toggle().into_any_element()) + } else if !capability.editable() { + Some(read_only_toggle(capability == Capability::Read).into_any_element()) } else { None }) .child(label) .map(|this| match tab_tooltip_content { Some(TabTooltipContent::Text(text)) => { - if item.is_read_only(cx) { + if capability.editable() { + this.tooltip(Tooltip::text(text)) + } else { this.tooltip(move |_, cx| { let text = text.clone(); Tooltip::with_meta(text, None, "Read-Only File", cx) }) - } else { - this.tooltip(Tooltip::text(text)) } } Some(TabTooltipContent::Custom(element_fn)) => { @@ -2858,8 +2860,8 @@ impl Pane { } None => this, }) - .when(item.is_read_only(cx) && has_file_icon, |this| { - this.child(read_only_toggle()) + .when(capability == Capability::Read && has_file_icon, |this| { + this.child(read_only_toggle(true)) }), ); @@ -2876,7 +2878,6 @@ impl Pane { let has_items_to_right = ix < total_items - 1; let has_clean_items = self.items.iter().any(|item| !item.is_dirty(cx)); let is_pinned = self.is_tab_pinned(ix); - let is_read_only = item.is_read_only(cx); let pane = cx.entity().downgrade(); let menu_context = item.item_focus_handle(cx); @@ -3028,20 +3029,22 @@ impl Pane { }) }; - let read_only_label = if is_read_only { - "Make File Editable" - } else { - "Make File Read-Only" - }; - menu = menu.separator().entry( - read_only_label, - None, - window.handler_for(&pane, move |pane, window, cx| { - if let Some(item) = pane.item_for_index(ix) { - item.toggle_read_only(window, cx); - } - }), - ); + if capability != Capability::ReadOnly { + let read_only_label = if capability.editable() { + "Make File Read-Only" + } else { + "Make File Editable" + }; + menu = menu.separator().entry( + read_only_label, + None, + window.handler_for(&pane, move |pane, window, cx| { + if let Some(item) = pane.item_for_index(ix) { + item.toggle_read_only(window, cx); + } + }), + ); + } if let Some(entry) = single_entry_to_resolve { let project_path = pane