Track read_only per project and buffer

This uses a new enum to avoid confusing booleans
This commit is contained in:
Conrad Irwin 2024-01-03 12:08:07 -07:00
parent bf304b3fe7
commit 84171787a5
19 changed files with 161 additions and 90 deletions

View file

@ -2818,8 +2818,8 @@ impl InlineAssistant {
fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) { fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) {
let is_read_only = !self.codegen.read(cx).idle(); let is_read_only = !self.codegen.read(cx).idle();
self.prompt_editor.update(cx, |editor, _cx| { self.prompt_editor.update(cx, |editor, cx| {
let was_read_only = editor.read_only(); let was_read_only = editor.read_only(cx);
if was_read_only != is_read_only { if was_read_only != is_read_only {
if is_read_only { if is_read_only {
editor.set_read_only(true); editor.set_read_only(true);
@ -3054,7 +3054,7 @@ impl InlineAssistant {
fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx); let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle { let text_style = TextStyle {
color: if self.prompt_editor.read(cx).read_only() { color: if self.prompt_editor.read(cx).read_only(cx) {
cx.theme().colors().text_disabled cx.theme().colors().text_disabled
} else { } else {
cx.theme().colors().text cx.theme().colors().text

View file

@ -62,7 +62,12 @@ impl ChannelBuffer {
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let buffer = cx.new_model(|_| { let buffer = cx.new_model(|_| {
language::Buffer::remote(response.buffer_id, response.replica_id as u16, base_text) language::Buffer::remote(
response.buffer_id,
response.replica_id as u16,
channel.channel_buffer_capability(),
base_text,
)
})?; })?;
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))??; buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))??;

View file

@ -11,6 +11,7 @@ use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, SharedString, Task, AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, SharedString, Task,
WeakModel, WeakModel,
}; };
use language::Capability;
use rpc::{ use rpc::{
proto::{self, ChannelVisibility}, proto::{self, ChannelVisibility},
TypedEnvelope, TypedEnvelope,
@ -74,8 +75,12 @@ impl Channel {
slug.trim_matches(|c| c == '-').to_string() slug.trim_matches(|c| c == '-').to_string()
} }
pub fn can_edit_notes(&self) -> bool { pub fn channel_buffer_capability(&self) -> Capability {
self.role == proto::ChannelRole::Member || self.role == proto::ChannelRole::Admin if self.role == proto::ChannelRole::Member || self.role == proto::ChannelRole::Admin {
Capability::ReadWrite
} else {
Capability::ReadOnly
}
} }
} }

View file

@ -138,12 +138,6 @@ impl ChannelView {
editor.set_collaboration_hub(Box::new(ChannelBufferCollaborationHub( editor.set_collaboration_hub(Box::new(ChannelBufferCollaborationHub(
channel_buffer.clone(), channel_buffer.clone(),
))); )));
editor.set_read_only(
!channel_buffer
.read(cx)
.channel(cx)
.is_some_and(|c| c.can_edit_notes()),
);
editor editor
}); });
let _editor_event_subscription = let _editor_event_subscription =
@ -179,7 +173,6 @@ impl ChannelView {
}), }),
ChannelBufferEvent::ChannelChanged => { ChannelBufferEvent::ChannelChanged => {
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
editor.set_read_only(!self.channel(cx).is_some_and(|c| c.can_edit_notes()));
cx.emit(editor::EditorEvent::TitleChanged); cx.emit(editor::EditorEvent::TitleChanged);
cx.notify() cx.notify()
}); });
@ -254,11 +247,11 @@ impl Item for ChannelView {
fn tab_content(&self, _: Option<usize>, selected: bool, cx: &WindowContext) -> AnyElement { fn tab_content(&self, _: Option<usize>, selected: bool, cx: &WindowContext) -> AnyElement {
let label = if let Some(channel) = self.channel(cx) { let label = if let Some(channel) = self.channel(cx) {
match ( match (
channel.can_edit_notes(), self.channel_buffer.read(cx).buffer().read(cx).read_only(),
self.channel_buffer.read(cx).is_connected(), self.channel_buffer.read(cx).is_connected(),
) { ) {
(true, true) => format!("#{}", channel.name), (false, true) => format!("#{}", channel.name),
(false, true) => format!("#{} (read-only)", channel.name), (true, true) => format!("#{} (read-only)", channel.name),
(_, false) => format!("#{} (disconnected)", channel.name), (_, false) => format!("#{} (disconnected)", channel.name),
} }
} else { } else {

View file

@ -151,7 +151,12 @@ impl ProjectDiagnosticsEditor {
let focus_in_subscription = let focus_in_subscription =
cx.on_focus_in(&focus_handle, |diagnostics, cx| diagnostics.focus_in(cx)); cx.on_focus_in(&focus_handle, |diagnostics, cx| diagnostics.focus_in(cx));
let excerpts = cx.new_model(|cx| MultiBuffer::new(project_handle.read(cx).replica_id())); let excerpts = cx.new_model(|cx| {
MultiBuffer::new(
project_handle.read(cx).replica_id(),
project_handle.read(cx).capability(),
)
});
let editor = cx.new_view(|cx| { let editor = cx.new_view(|cx| {
let mut editor = let mut editor =
Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), cx); Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), cx);

View file

@ -54,10 +54,10 @@ use itertools::Itertools;
pub use language::{char_kind, CharKind}; pub use language::{char_kind, CharKind};
use language::{ use language::{
language_settings::{self, all_language_settings, InlayHintSettings}, language_settings::{self, all_language_settings, InlayHintSettings},
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CodeAction,
Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, CodeLabel, Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize,
LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal, Language, LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection,
TransactionId, SelectionGoal, TransactionId,
}; };
use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState}; use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
@ -2050,8 +2050,8 @@ impl Editor {
} }
} }
pub fn read_only(&self) -> bool { pub fn read_only(&self, cx: &AppContext) -> bool {
self.read_only self.read_only || self.buffer.read(cx).read_only()
} }
pub fn set_read_only(&mut self, read_only: bool) { pub fn set_read_only(&mut self, read_only: bool) {
@ -2200,7 +2200,7 @@ impl Editor {
S: ToOffset, S: ToOffset,
T: Into<Arc<str>>, T: Into<Arc<str>>,
{ {
if self.read_only { if self.read_only(cx) {
return; return;
} }
@ -2214,7 +2214,7 @@ impl Editor {
S: ToOffset, S: ToOffset,
T: Into<Arc<str>>, T: Into<Arc<str>>,
{ {
if self.read_only { if self.read_only(cx) {
return; return;
} }
@ -2233,7 +2233,7 @@ impl Editor {
S: ToOffset, S: ToOffset,
T: Into<Arc<str>>, T: Into<Arc<str>>,
{ {
if self.read_only { if self.read_only(cx) {
return; return;
} }
@ -2597,7 +2597,7 @@ impl Editor {
pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) { pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
let text: Arc<str> = text.into(); let text: Arc<str> = text.into();
if self.read_only { if self.read_only(cx) {
return; return;
} }
@ -3050,7 +3050,7 @@ impl Editor {
autoindent_mode: Option<AutoindentMode>, autoindent_mode: Option<AutoindentMode>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
if self.read_only { if self.read_only(cx) {
return; return;
} }
@ -3787,7 +3787,8 @@ impl Editor {
let mut ranges_to_highlight = Vec::new(); let mut ranges_to_highlight = Vec::new();
let excerpt_buffer = cx.new_model(|cx| { let excerpt_buffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(replica_id).with_title(title); let mut multibuffer =
MultiBuffer::new(replica_id, Capability::ReadWrite).with_title(title);
for (buffer_handle, transaction) in &entries { for (buffer_handle, transaction) in &entries {
let buffer = buffer_handle.read(cx); let buffer = buffer_handle.read(cx);
ranges_to_highlight.extend( ranges_to_highlight.extend(
@ -7492,9 +7493,10 @@ impl Editor {
locations.sort_by_key(|location| location.buffer.read(cx).remote_id()); locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
let mut locations = locations.into_iter().peekable(); let mut locations = locations.into_iter().peekable();
let mut ranges_to_highlight = Vec::new(); let mut ranges_to_highlight = Vec::new();
let capability = workspace.project().read(cx).capability();
let excerpt_buffer = cx.new_model(|cx| { let excerpt_buffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(replica_id); let mut multibuffer = MultiBuffer::new(replica_id, capability);
while let Some(location) = locations.next() { while let Some(location) = locations.next() {
let buffer = location.buffer.read(cx); let buffer = location.buffer.read(cx);
let mut ranges_for_buffer = Vec::new(); let mut ranges_for_buffer = Vec::new();

View file

@ -17,8 +17,9 @@ use gpui::{
use indoc::indoc; use indoc::indoc;
use language::{ use language::{
language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent}, language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageRegistry, BracketPairConfig,
Override, Point, Capability::ReadWrite,
FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageRegistry, Override, Point,
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use project::project_settings::{LspSettings, ProjectSettings}; use project::project_settings::{LspSettings, ProjectSettings};
@ -2355,7 +2356,7 @@ fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
.with_language(rust_language, cx) .with_language(rust_language, cx)
}); });
let multibuffer = cx.new_model(|cx| { let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0); let mut multibuffer = MultiBuffer::new(0, ReadWrite);
multibuffer.push_excerpts( multibuffer.push_excerpts(
toml_buffer.clone(), toml_buffer.clone(),
[ExcerptRange { [ExcerptRange {
@ -6019,7 +6020,7 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a'))); let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
let multibuffer = cx.new_model(|cx| { let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0); let mut multibuffer = MultiBuffer::new(0, ReadWrite);
multibuffer.push_excerpts( multibuffer.push_excerpts(
buffer.clone(), buffer.clone(),
[ [
@ -6103,7 +6104,7 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
}); });
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), initial_text)); let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), initial_text));
let multibuffer = cx.new_model(|cx| { let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0); let mut multibuffer = MultiBuffer::new(0, ReadWrite);
multibuffer.push_excerpts(buffer, excerpt_ranges, cx); multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
multibuffer multibuffer
}); });
@ -6162,7 +6163,7 @@ fn test_refresh_selections(cx: &mut TestAppContext) {
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a'))); let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
let mut excerpt1_id = None; let mut excerpt1_id = None;
let multibuffer = cx.new_model(|cx| { let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0); let mut multibuffer = MultiBuffer::new(0, ReadWrite);
excerpt1_id = multibuffer excerpt1_id = multibuffer
.push_excerpts( .push_excerpts(
buffer.clone(), buffer.clone(),
@ -6247,7 +6248,7 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a'))); let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
let mut excerpt1_id = None; let mut excerpt1_id = None;
let multibuffer = cx.new_model(|cx| { let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0); let mut multibuffer = MultiBuffer::new(0, ReadWrite);
excerpt1_id = multibuffer excerpt1_id = multibuffer
.push_excerpts( .push_excerpts(
buffer.clone(), buffer.clone(),
@ -6636,7 +6637,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx); let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
let leader = pane.update(cx, |_, cx| { let leader = pane.update(cx, |_, cx| {
let multibuffer = cx.new_model(|_| MultiBuffer::new(0)); let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
cx.new_view(|cx| build_editor(multibuffer.clone(), cx)) cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
}); });
@ -7425,7 +7426,7 @@ async fn test_copilot_multibuffer(executor: BackgroundExecutor, cx: &mut gpui::T
let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "a = 1\nb = 2\n")); let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "a = 1\nb = 2\n"));
let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "c = 3\nd = 4\n")); let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "c = 3\nd = 4\n"));
let multibuffer = cx.new_model(|cx| { let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0); let mut multibuffer = MultiBuffer::new(0, ReadWrite);
multibuffer.push_excerpts( multibuffer.push_excerpts(
buffer_1.clone(), buffer_1.clone(),
[ExcerptRange { [ExcerptRange {
@ -7552,7 +7553,7 @@ async fn test_copilot_disabled_globs(executor: BackgroundExecutor, cx: &mut gpui
.unwrap(); .unwrap();
let multibuffer = cx.new_model(|cx| { let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0); let mut multibuffer = MultiBuffer::new(0, ReadWrite);
multibuffer.push_excerpts( multibuffer.push_excerpts(
private_buffer.clone(), private_buffer.clone(),
[ExcerptRange { [ExcerptRange {

View file

@ -93,6 +93,7 @@ mod tests {
use crate::editor_tests::init_test; use crate::editor_tests::init_test;
use crate::Point; use crate::Point;
use gpui::{Context, TestAppContext}; use gpui::{Context, TestAppContext};
use language::Capability::ReadWrite;
use multi_buffer::{ExcerptRange, MultiBuffer}; use multi_buffer::{ExcerptRange, MultiBuffer};
use project::{FakeFs, Project}; use project::{FakeFs, Project};
use unindent::Unindent; use unindent::Unindent;
@ -183,7 +184,7 @@ mod tests {
cx.background_executor.run_until_parked(); cx.background_executor.run_until_parked();
let multibuffer = cx.new_model(|cx| { let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0); let mut multibuffer = MultiBuffer::new(0, ReadWrite);
multibuffer.push_excerpts( multibuffer.push_excerpts(
buffer_1.clone(), buffer_1.clone(),
[ [

View file

@ -1206,7 +1206,8 @@ pub mod tests {
use gpui::{Context, TestAppContext, WindowHandle}; use gpui::{Context, TestAppContext, WindowHandle};
use itertools::Itertools; use itertools::Itertools;
use language::{ use language::{
language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig, language_settings::AllLanguageSettingsContent, Capability, FakeLspAdapter, Language,
LanguageConfig,
}; };
use lsp::FakeLanguageServer; use lsp::FakeLanguageServer;
use parking_lot::Mutex; use parking_lot::Mutex;
@ -2459,7 +2460,7 @@ pub mod tests {
.await .await
.unwrap(); .unwrap();
let multibuffer = cx.new_model(|cx| { let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0); let mut multibuffer = MultiBuffer::new(0, Capability::ReadWrite);
multibuffer.push_excerpts( multibuffer.push_excerpts(
buffer_1.clone(), buffer_1.clone(),
[ [
@ -2798,7 +2799,7 @@ pub mod tests {
}) })
.await .await
.unwrap(); .unwrap();
let multibuffer = cx.new_model(|_| MultiBuffer::new(0)); let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| { let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
let buffer_1_excerpts = multibuffer.push_excerpts( let buffer_1_excerpts = multibuffer.push_excerpts(
buffer_1.clone(), buffer_1.clone(),

View file

@ -101,7 +101,8 @@ impl FollowableItem for Editor {
if state.singleton && buffers.len() == 1 { if state.singleton && buffers.len() == 1 {
multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx) multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
} else { } else {
multibuffer = MultiBuffer::new(replica_id); multibuffer =
MultiBuffer::new(replica_id, project.read(cx).capability());
let mut excerpts = state.excerpts.into_iter().peekable(); let mut excerpts = state.excerpts.into_iter().peekable();
while let Some(excerpt) = excerpts.peek() { while let Some(excerpt) = excerpts.peek() {
let buffer_id = excerpt.buffer_id; let buffer_id = excerpt.buffer_id;

View file

@ -461,6 +461,7 @@ mod tests {
Buffer, DisplayMap, ExcerptRange, InlayId, MultiBuffer, Buffer, DisplayMap, ExcerptRange, InlayId, MultiBuffer,
}; };
use gpui::{font, Context as _}; use gpui::{font, Context as _};
use language::Capability;
use project::Project; use project::Project;
use settings::SettingsStore; use settings::SettingsStore;
use util::post_inc; use util::post_inc;
@ -766,7 +767,7 @@ mod tests {
let buffer = let buffer =
cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abc\ndefg\nhijkl\nmn")); cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abc\ndefg\nhijkl\nmn"));
let multibuffer = cx.new_model(|cx| { let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0); let mut multibuffer = MultiBuffer::new(0, Capability::ReadWrite);
multibuffer.push_excerpts( multibuffer.push_excerpts(
buffer.clone(), buffer.clone(),
[ [

View file

@ -57,6 +57,12 @@ lazy_static! {
pub static ref BUFFER_DIFF_TASK: TaskLabel = TaskLabel::new(); pub static ref BUFFER_DIFF_TASK: TaskLabel = TaskLabel::new();
} }
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Capability {
ReadWrite,
ReadOnly,
}
pub struct Buffer { pub struct Buffer {
text: TextBuffer, text: TextBuffer,
diff_base: Option<String>, diff_base: Option<String>,
@ -90,6 +96,7 @@ pub struct Buffer {
completion_triggers: Vec<String>, completion_triggers: Vec<String>,
completion_triggers_timestamp: clock::Lamport, completion_triggers_timestamp: clock::Lamport,
deferred_ops: OperationQueue<Operation>, deferred_ops: OperationQueue<Operation>,
capability: Capability,
} }
pub struct BufferSnapshot { pub struct BufferSnapshot {
@ -405,19 +412,27 @@ impl Buffer {
TextBuffer::new(replica_id, id, base_text.into()), TextBuffer::new(replica_id, id, base_text.into()),
None, None,
None, None,
Capability::ReadWrite,
) )
} }
pub fn remote(remote_id: u64, replica_id: ReplicaId, base_text: String) -> Self { pub fn remote(
remote_id: u64,
replica_id: ReplicaId,
capability: Capability,
base_text: String,
) -> Self {
Self::build( Self::build(
TextBuffer::new(replica_id, remote_id, base_text), TextBuffer::new(replica_id, remote_id, base_text),
None, None,
None, None,
capability,
) )
} }
pub fn from_proto( pub fn from_proto(
replica_id: ReplicaId, replica_id: ReplicaId,
capability: Capability,
message: proto::BufferState, message: proto::BufferState,
file: Option<Arc<dyn File>>, file: Option<Arc<dyn File>>,
) -> Result<Self> { ) -> Result<Self> {
@ -426,6 +441,7 @@ impl Buffer {
buffer, buffer,
message.diff_base.map(|text| text.into_boxed_str().into()), message.diff_base.map(|text| text.into_boxed_str().into()),
file, file,
capability,
); );
this.text.set_line_ending(proto::deserialize_line_ending( this.text.set_line_ending(proto::deserialize_line_ending(
rpc::proto::LineEnding::from_i32(message.line_ending) rpc::proto::LineEnding::from_i32(message.line_ending)
@ -504,10 +520,19 @@ impl Buffer {
self self
} }
pub fn capability(&self) -> Capability {
self.capability
}
pub fn read_only(&self) -> bool {
self.capability == Capability::ReadOnly
}
pub fn build( pub fn build(
buffer: TextBuffer, buffer: TextBuffer,
diff_base: Option<String>, diff_base: Option<String>,
file: Option<Arc<dyn File>>, file: Option<Arc<dyn File>>,
capability: Capability,
) -> Self { ) -> Self {
let saved_mtime = if let Some(file) = file.as_ref() { let saved_mtime = if let Some(file) = file.as_ref() {
file.mtime() file.mtime()
@ -526,6 +551,7 @@ impl Buffer {
diff_base, diff_base,
git_diff: git::diff::BufferDiff::new(), git_diff: git::diff::BufferDiff::new(),
file, file,
capability,
syntax_map: Mutex::new(SyntaxMap::new()), syntax_map: Mutex::new(SyntaxMap::new()),
parsing_in_background: false, parsing_in_background: false,
parse_count: 0, parse_count: 0,

View file

@ -1926,7 +1926,7 @@ fn test_serialization(cx: &mut gpui::AppContext) {
.background_executor() .background_executor()
.block(buffer1.read(cx).serialize_ops(None, cx)); .block(buffer1.read(cx).serialize_ops(None, cx));
let buffer2 = cx.new_model(|cx| { let buffer2 = cx.new_model(|cx| {
let mut buffer = Buffer::from_proto(1, state, None).unwrap(); let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
buffer buffer
.apply_ops( .apply_ops(
ops.into_iter() ops.into_iter()
@ -1967,7 +1967,8 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
let ops = cx let ops = cx
.background_executor() .background_executor()
.block(base_buffer.read(cx).serialize_ops(None, cx)); .block(base_buffer.read(cx).serialize_ops(None, cx));
let mut buffer = Buffer::from_proto(i as ReplicaId, state, None).unwrap(); let mut buffer =
Buffer::from_proto(i as ReplicaId, Capability::ReadWrite, state, None).unwrap();
buffer buffer
.apply_ops( .apply_ops(
ops.into_iter() ops.into_iter()
@ -2083,8 +2084,13 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
replica_id replica_id
); );
new_buffer = Some(cx.new_model(|cx| { new_buffer = Some(cx.new_model(|cx| {
let mut new_buffer = let mut new_buffer = Buffer::from_proto(
Buffer::from_proto(new_replica_id, old_buffer_state, None).unwrap(); new_replica_id,
Capability::ReadWrite,
old_buffer_state,
None,
)
.unwrap();
new_buffer new_buffer
.apply_ops( .apply_ops(
old_buffer_ops old_buffer_ops

View file

@ -11,7 +11,7 @@ pub use language::Completion;
use language::{ use language::{
char_kind, char_kind,
language_settings::{language_settings, LanguageSettings}, language_settings::{language_settings, LanguageSettings},
AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, Capability, CharKind, Chunk, CursorShape,
DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16,
Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _,
ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,
@ -55,6 +55,7 @@ pub struct MultiBuffer {
replica_id: ReplicaId, replica_id: ReplicaId,
history: History, history: History,
title: Option<String>, title: Option<String>,
capability: Capability,
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
@ -225,13 +226,14 @@ struct ExcerptBytes<'a> {
} }
impl MultiBuffer { impl MultiBuffer {
pub fn new(replica_id: ReplicaId) -> Self { pub fn new(replica_id: ReplicaId, capability: Capability) -> Self {
Self { Self {
snapshot: Default::default(), snapshot: Default::default(),
buffers: Default::default(), buffers: Default::default(),
next_excerpt_id: 1, next_excerpt_id: 1,
subscriptions: Default::default(), subscriptions: Default::default(),
singleton: false, singleton: false,
capability,
replica_id, replica_id,
history: History { history: History {
next_transaction_id: Default::default(), next_transaction_id: Default::default(),
@ -271,6 +273,7 @@ impl MultiBuffer {
next_excerpt_id: 1, next_excerpt_id: 1,
subscriptions: Default::default(), subscriptions: Default::default(),
singleton: self.singleton, singleton: self.singleton,
capability: self.capability,
replica_id: self.replica_id, replica_id: self.replica_id,
history: self.history.clone(), history: self.history.clone(),
title: self.title.clone(), title: self.title.clone(),
@ -282,8 +285,12 @@ impl MultiBuffer {
self self
} }
pub fn read_only(&self) -> bool {
self.capability == Capability::ReadOnly
}
pub fn singleton(buffer: Model<Buffer>, cx: &mut ModelContext<Self>) -> Self { pub fn singleton(buffer: Model<Buffer>, cx: &mut ModelContext<Self>) -> Self {
let mut this = Self::new(buffer.read(cx).replica_id()); let mut this = Self::new(buffer.read(cx).replica_id(), buffer.read(cx).capability());
this.singleton = true; this.singleton = true;
this.push_excerpts( this.push_excerpts(
buffer, buffer,
@ -1657,7 +1664,7 @@ impl MultiBuffer {
excerpts: [(&str, Vec<Range<Point>>); COUNT], excerpts: [(&str, Vec<Range<Point>>); COUNT],
cx: &mut gpui::AppContext, cx: &mut gpui::AppContext,
) -> Model<Self> { ) -> Model<Self> {
let multi = cx.new_model(|_| Self::new(0)); let multi = cx.new_model(|_| Self::new(0, Capability::ReadWrite));
for (text, ranges) in excerpts { for (text, ranges) in excerpts {
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text)); let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
let excerpt_ranges = ranges.into_iter().map(|range| ExcerptRange { let excerpt_ranges = ranges.into_iter().map(|range| ExcerptRange {
@ -1678,7 +1685,7 @@ impl MultiBuffer {
pub fn build_random(rng: &mut impl rand::Rng, cx: &mut gpui::AppContext) -> Model<Self> { pub fn build_random(rng: &mut impl rand::Rng, cx: &mut gpui::AppContext) -> Model<Self> {
cx.new_model(|cx| { cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0); let mut multibuffer = MultiBuffer::new(0, Capability::ReadWrite);
let mutation_count = rng.gen_range(1..=5); let mutation_count = rng.gen_range(1..=5);
multibuffer.randomly_edit_excerpts(rng, mutation_count, cx); multibuffer.randomly_edit_excerpts(rng, mutation_count, cx);
multibuffer multibuffer
@ -4176,7 +4183,7 @@ mod tests {
let ops = cx let ops = cx
.background_executor() .background_executor()
.block(host_buffer.read(cx).serialize_ops(None, cx)); .block(host_buffer.read(cx).serialize_ops(None, cx));
let mut buffer = Buffer::from_proto(1, state, None).unwrap(); let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
buffer buffer
.apply_ops( .apply_ops(
ops.into_iter() ops.into_iter()
@ -4205,7 +4212,7 @@ mod tests {
cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'a'))); cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'a')));
let buffer_2 = let buffer_2 =
cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'g'))); cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'g')));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0)); let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let events = Arc::new(RwLock::new(Vec::<Event>::new())); let events = Arc::new(RwLock::new(Vec::<Event>::new()));
multibuffer.update(cx, |_, cx| { multibuffer.update(cx, |_, cx| {
@ -4442,8 +4449,8 @@ mod tests {
let buffer_2 = let buffer_2 =
cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(10, 3, 'm'))); cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(10, 3, 'm')));
let leader_multibuffer = cx.new_model(|_| MultiBuffer::new(0)); let leader_multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let follower_multibuffer = cx.new_model(|_| MultiBuffer::new(0)); let follower_multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let follower_edit_event_count = Arc::new(RwLock::new(0)); let follower_edit_event_count = Arc::new(RwLock::new(0));
follower_multibuffer.update(cx, |_, cx| { follower_multibuffer.update(cx, |_, cx| {
@ -4547,7 +4554,7 @@ mod tests {
fn test_push_excerpts_with_context_lines(cx: &mut AppContext) { fn test_push_excerpts_with_context_lines(cx: &mut AppContext) {
let buffer = let buffer =
cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(20, 3, 'a'))); cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(20, 3, 'a')));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0)); let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| { let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
multibuffer.push_excerpts_with_context_lines( multibuffer.push_excerpts_with_context_lines(
buffer.clone(), buffer.clone(),
@ -4584,7 +4591,7 @@ mod tests {
async fn test_stream_excerpts_with_context_lines(cx: &mut TestAppContext) { async fn test_stream_excerpts_with_context_lines(cx: &mut TestAppContext) {
let buffer = let buffer =
cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(20, 3, 'a'))); cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(20, 3, 'a')));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0)); let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| { let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
let snapshot = buffer.read(cx); let snapshot = buffer.read(cx);
let ranges = vec![ let ranges = vec![
@ -4619,7 +4626,7 @@ mod tests {
#[gpui::test] #[gpui::test]
fn test_empty_multibuffer(cx: &mut AppContext) { fn test_empty_multibuffer(cx: &mut AppContext) {
let multibuffer = cx.new_model(|_| MultiBuffer::new(0)); let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let snapshot = multibuffer.read(cx).snapshot(cx); let snapshot = multibuffer.read(cx).snapshot(cx);
assert_eq!(snapshot.text(), ""); assert_eq!(snapshot.text(), "");
@ -4652,7 +4659,7 @@ mod tests {
let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd")); let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd"));
let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "efghi")); let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "efghi"));
let multibuffer = cx.new_model(|cx| { let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0); let mut multibuffer = MultiBuffer::new(0, Capability::ReadWrite);
multibuffer.push_excerpts( multibuffer.push_excerpts(
buffer_1.clone(), buffer_1.clone(),
[ExcerptRange { [ExcerptRange {
@ -4710,7 +4717,7 @@ mod tests {
let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd")); let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd"));
let buffer_2 = let buffer_2 =
cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "ABCDEFGHIJKLMNOP")); cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "ABCDEFGHIJKLMNOP"));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0)); let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
// Create an insertion id in buffer 1 that doesn't exist in buffer 2. // Create an insertion id in buffer 1 that doesn't exist in buffer 2.
// Add an excerpt from buffer 1 that spans this new insertion. // Add an excerpt from buffer 1 that spans this new insertion.
@ -4844,7 +4851,7 @@ mod tests {
.unwrap_or(10); .unwrap_or(10);
let mut buffers: Vec<Model<Buffer>> = Vec::new(); let mut buffers: Vec<Model<Buffer>> = Vec::new();
let multibuffer = cx.new_model(|_| MultiBuffer::new(0)); let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let mut excerpt_ids = Vec::<ExcerptId>::new(); let mut excerpt_ids = Vec::<ExcerptId>::new();
let mut expected_excerpts = Vec::<(Model<Buffer>, Range<text::Anchor>)>::new(); let mut expected_excerpts = Vec::<(Model<Buffer>, Range<text::Anchor>)>::new();
let mut anchors = Vec::new(); let mut anchors = Vec::new();
@ -5266,7 +5273,7 @@ mod tests {
let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "1234")); let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "1234"));
let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "5678")); let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "5678"));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0)); let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let group_interval = multibuffer.read(cx).history.group_interval; let group_interval = multibuffer.read(cx).history.group_interval;
multibuffer.update(cx, |multibuffer, cx| { multibuffer.update(cx, |multibuffer, cx| {
multibuffer.push_excerpts( multibuffer.push_excerpts(

View file

@ -39,11 +39,11 @@ use language::{
deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
serialize_anchor, serialize_version, split_operations, serialize_anchor, serialize_version, split_operations,
}, },
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeAction, range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability,
CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff,
File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, LspAdapterDelegate, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile,
OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot, LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16,
ToOffset, ToPointUtf16, Transaction, Unclipped, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
}; };
use log::error; use log::error;
use lsp::{ use lsp::{
@ -262,8 +262,7 @@ enum ProjectClientState {
}, },
Remote { Remote {
sharing_has_stopped: bool, sharing_has_stopped: bool,
// todo!() this should be represented differently! capability: Capability,
is_read_only: bool,
remote_id: u64, remote_id: u64,
replica_id: ReplicaId, replica_id: ReplicaId,
}, },
@ -760,7 +759,7 @@ impl Project {
client: client.clone(), client: client.clone(),
client_state: Some(ProjectClientState::Remote { client_state: Some(ProjectClientState::Remote {
sharing_has_stopped: false, sharing_has_stopped: false,
is_read_only: false, capability: Capability::ReadWrite,
remote_id, remote_id,
replica_id, replica_id,
}), }),
@ -1625,9 +1624,13 @@ impl Project {
} }
pub fn set_role(&mut self, role: proto::ChannelRole) { pub fn set_role(&mut self, role: proto::ChannelRole) {
if let Some(ProjectClientState::Remote { is_read_only, .. }) = &mut self.client_state { if let Some(ProjectClientState::Remote { capability, .. }) = &mut self.client_state {
*is_read_only = *capability = if role == proto::ChannelRole::Member || role == proto::ChannelRole::Admin
!(role == proto::ChannelRole::Member || role == proto::ChannelRole::Admin) {
Capability::ReadWrite
} else {
Capability::ReadOnly
};
} }
} }
@ -1682,12 +1685,15 @@ impl Project {
} }
} }
pub fn capability(&self) -> Capability {
match &self.client_state {
Some(ProjectClientState::Remote { capability, .. }) => *capability,
Some(ProjectClientState::Local { .. }) | None => Capability::ReadWrite,
}
}
pub fn is_read_only(&self) -> bool { pub fn is_read_only(&self) -> bool {
self.is_disconnected() self.is_disconnected() || self.capability() == Capability::ReadOnly
|| match &self.client_state {
Some(ProjectClientState::Remote { is_read_only, .. }) => *is_read_only,
_ => false,
}
} }
pub fn is_local(&self) -> bool { pub fn is_local(&self) -> bool {
@ -7215,7 +7221,8 @@ impl Project {
let buffer_id = state.id; let buffer_id = state.id;
let buffer = cx.new_model(|_| { let buffer = cx.new_model(|_| {
Buffer::from_proto(this.replica_id(), state, buffer_file).unwrap() Buffer::from_proto(this.replica_id(), this.capability(), state, buffer_file)
.unwrap()
}); });
this.incomplete_remote_buffers this.incomplete_remote_buffers
.insert(buffer_id, Some(buffer)); .insert(buffer_id, Some(buffer));

View file

@ -32,7 +32,8 @@ use language::{
deserialize_fingerprint, deserialize_version, serialize_fingerprint, serialize_line_ending, deserialize_fingerprint, deserialize_version, serialize_fingerprint, serialize_line_ending,
serialize_version, serialize_version,
}, },
Buffer, DiagnosticEntry, File as _, LineEnding, PointUtf16, Rope, RopeFingerprint, Unclipped, Buffer, Capability, DiagnosticEntry, File as _, LineEnding, PointUtf16, Rope, RopeFingerprint,
Unclipped,
}; };
use lsp::LanguageServerId; use lsp::LanguageServerId;
use parking_lot::Mutex; use parking_lot::Mutex;
@ -682,7 +683,14 @@ impl LocalWorktree {
.background_executor() .background_executor()
.spawn(async move { text::Buffer::new(0, id, contents) }) .spawn(async move { text::Buffer::new(0, id, contents) })
.await; .await;
cx.new_model(|_| Buffer::build(text_buffer, diff_base, Some(Arc::new(file)))) cx.new_model(|_| {
Buffer::build(
text_buffer,
diff_base,
Some(Arc::new(file)),
Capability::ReadWrite,
)
})
}) })
} }

View file

@ -70,7 +70,7 @@ impl BufferSearchBar {
fn render_text_input(&self, editor: &View<Editor>, cx: &ViewContext<Self>) -> impl IntoElement { fn render_text_input(&self, editor: &View<Editor>, cx: &ViewContext<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx); let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle { let text_style = TextStyle {
color: if editor.read(cx).read_only() { color: if editor.read(cx).read_only(cx) {
cx.theme().colors().text_disabled cx.theme().colors().text_disabled
} else { } else {
cx.theme().colors().text cx.theme().colors().text

View file

@ -132,9 +132,11 @@ pub struct ProjectSearchBar {
impl ProjectSearch { impl ProjectSearch {
fn new(project: Model<Project>, cx: &mut ModelContext<Self>) -> Self { fn new(project: Model<Project>, cx: &mut ModelContext<Self>) -> Self {
let replica_id = project.read(cx).replica_id(); let replica_id = project.read(cx).replica_id();
let capability = project.read(cx).capability();
Self { Self {
project, project,
excerpts: cx.new_model(|_| MultiBuffer::new(replica_id)), excerpts: cx.new_model(|_| MultiBuffer::new(replica_id, capability)),
pending_search: Default::default(), pending_search: Default::default(),
match_ranges: Default::default(), match_ranges: Default::default(),
active_query: None, active_query: None,
@ -1519,7 +1521,7 @@ impl ProjectSearchBar {
fn render_text_input(&self, editor: &View<Editor>, cx: &ViewContext<Self>) -> impl IntoElement { fn render_text_input(&self, editor: &View<Editor>, cx: &ViewContext<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx); let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle { let text_style = TextStyle {
color: if editor.read(cx).read_only() { color: if editor.read(cx).read_only(cx) {
cx.theme().colors().text_disabled cx.theme().colors().text_disabled
} else { } else {
cx.theme().colors().text cx.theme().colors().text

View file

@ -8,7 +8,7 @@ set -e
cd crates/collab cd crates/collab
# Export contents of .env.toml # Export contents of .env.toml
eval "$(cargo run --quiet --bin dotenv)" eval "$(cargo run --quiet --bin dotenv2)"
# Run sqlx command # Run sqlx command
sqlx $@ sqlx $@