Introduce a new mutable read capability

Lukas Wirth created

Change summary

crates/editor/src/editor.rs             | 12 ++++++------
crates/editor/src/element.rs            | 13 ++++---------
crates/language/src/buffer.rs           | 11 ++++++++++-
crates/multi_buffer/src/multi_buffer.rs |  2 +-
crates/project/src/buffer_store.rs      |  2 +-
crates/project/src/project.rs           |  2 +-
6 files changed, 23 insertions(+), 19 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -3353,7 +3353,7 @@ impl Editor {
                 let position_matches = start_offset == completion_position.to_offset(buffer);
                 let continue_showing = if let Some((snap, ..)) =
                     buffer.point_to_buffer_offset(completion_position)
-                    && snap.capability == Capability::ReadOnly
+                    && !snap.capability.editable()
                 {
                     false
                 } else if position_matches {
@@ -4322,13 +4322,13 @@ impl Editor {
         {
             if snapshot
                 .point_to_buffer_point(selection.head())
-                .is_none_or(|(snapshot, ..)| snapshot.capability == Capability::ReadOnly)
+                .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
             {
                 continue;
             }
             if snapshot
                 .point_to_buffer_point(selection.tail())
-                .is_none_or(|(snapshot, ..)| snapshot.capability == Capability::ReadOnly)
+                .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
             {
                 // note, ideally we'd clip the tail to the closest writeable region towards the head
                 continue;
@@ -11008,13 +11008,13 @@ impl Editor {
     }
 
     pub fn toggle_read_only(&mut self, _: &ToggleReadOnly, _: &mut Window, cx: &mut Context<Self>) {
-        // todo(lw): We should not allow toggling every editor to read-write. Some we want to keep read-only, always as they might be read-only replicated etc.
         if let Some(buffer) = self.buffer.read(cx).as_singleton() {
             buffer.update(cx, |buffer, cx| {
                 buffer.set_capability(
                     match buffer.capability() {
-                        Capability::ReadWrite => Capability::ReadOnly,
-                        Capability::ReadOnly => Capability::ReadWrite,
+                        Capability::ReadWrite => Capability::Read,
+                        Capability::Read => Capability::ReadWrite,
+                        Capability::ReadOnly => Capability::ReadOnly,
                     },
                     cx,
                 );

crates/editor/src/element.rs 🔗

@@ -51,7 +51,7 @@ use gpui::{
     quad, relative, size, solid_background, transparent_black,
 };
 use itertools::Itertools;
-use language::{Capability, IndentGuideSettings, language_settings::ShowWhitespaceSetting};
+use language::{IndentGuideSettings, language_settings::ShowWhitespaceSetting};
 use markdown::Markdown;
 use multi_buffer::{
     Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, ExpandInfo, MultiBufferPoint,
@@ -4147,14 +4147,9 @@ impl EditorElement {
                                                 }
                                             })),
                                     )
-                                    .when(
-                                        for_excerpt.buffer.capability == Capability::ReadOnly,
-                                        |el| {
-                                            el.child(
-                                                Icon::new(IconName::FileLock).color(Color::Muted),
-                                            )
-                                        },
-                                    )
+                                    .when(!for_excerpt.buffer.capability.editable(), |el| {
+                                        el.child(Icon::new(IconName::FileLock).color(Color::Muted))
+                                    })
                                     .when_some(parent_path, |then, path| {
                                         then.child(Label::new(path).truncate().color(
                                             if file_status.is_some_and(FileStatus::is_deleted) {

crates/language/src/buffer.rs 🔗

@@ -85,10 +85,19 @@ pub static BUFFER_DIFF_TASK: LazyLock<TaskLabel> = 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.
+    Read,
     /// The buffer is a read-only replica.
     ReadOnly,
 }
 
+impl Capability {
+    /// Returns `true` if the capability is `ReadWrite`.
+    pub fn editable(self) -> bool {
+        matches!(self, Capability::ReadWrite)
+    }
+}
+
 pub type BufferRow = u32;
 
 /// An in-memory representation of a source code file, including its text,
@@ -1063,7 +1072,7 @@ impl Buffer {
 
     /// Whether this buffer can only be read.
     pub fn read_only(&self) -> bool {
-        self.capability == Capability::ReadOnly
+        !self.capability.editable()
     }
 
     /// Builds a [`Buffer`] with the given underlying [`TextBuffer`], diff base, [`File`] and [`Capability`].

crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -1198,7 +1198,7 @@ impl MultiBuffer {
     }
 
     pub fn read_only(&self) -> bool {
-        self.capability == Capability::ReadOnly
+        !self.capability.editable()
     }
 
     /// Returns an up-to-date snapshot of the MultiBuffer.

crates/project/src/buffer_store.rs 🔗

@@ -677,7 +677,7 @@ impl LocalBufferStore {
                     };
                     if is_read_only {
                         buffer.update(cx, |buffer, cx| {
-                            buffer.set_capability(Capability::ReadOnly, cx);
+                            buffer.set_capability(Capability::Read, cx);
                         });
                     }
 

crates/project/src/project.rs 🔗

@@ -2650,7 +2650,7 @@ impl Project {
 
     #[inline]
     pub fn is_read_only(&self, cx: &App) -> bool {
-        self.is_disconnected(cx) || self.capability() == Capability::ReadOnly
+        self.is_disconnected(cx) || !self.capability().editable()
     }
 
     #[inline]