Compare commits

...

2 Commits

Author SHA1 Message Date
Piotr Osiewicz
8f44ff7d31 Cargo fmt 2025-03-31 15:31:27 +02:00
Piotr Osiewicz
e85ec8ffb7 chore: Prepare for Rust 2024
This is just a result of `cargo fix --edition`, without actually bumping the edition just yet.
2025-03-31 15:29:44 +02:00
320 changed files with 11821 additions and 10396 deletions

24
Cargo.lock generated
View File

@@ -1910,6 +1910,24 @@ name = "bindgen"
version = "0.70.1" version = "0.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
dependencies = [
"bitflags 2.9.0",
"cexpr",
"clang-sys",
"itertools 0.12.1",
"proc-macro2",
"quote",
"regex",
"rustc-hash 1.1.0",
"shlex",
"syn 2.0.100",
]
[[package]]
name = "bindgen"
version = "0.71.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.0",
"cexpr", "cexpr",
@@ -1920,7 +1938,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"regex", "regex",
"rustc-hash 1.1.0", "rustc-hash 2.1.1",
"shlex", "shlex",
"syn 2.0.100", "syn 2.0.100",
] ]
@@ -5848,7 +5866,7 @@ dependencies = [
"ashpd", "ashpd",
"async-task", "async-task",
"backtrace", "backtrace",
"bindgen 0.70.1", "bindgen 0.71.1",
"blade-graphics", "blade-graphics",
"blade-macros", "blade-macros",
"blade-util", "blade-util",
@@ -8220,7 +8238,7 @@ name = "media"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bindgen 0.70.1", "bindgen 0.71.1",
"core-foundation 0.10.0", "core-foundation 0.10.0",
"core-video", "core-video",
"ctor", "ctor",

View File

@@ -190,7 +190,7 @@ impl ActivityIndicator {
fn pending_language_server_work<'a>( fn pending_language_server_work<'a>(
&self, &self,
cx: &'a App, cx: &'a App,
) -> impl Iterator<Item = PendingWork<'a>> { ) -> impl Iterator<Item = PendingWork<'a>> + use<'a> {
self.project self.project
.read(cx) .read(cx)
.language_server_statuses(cx) .language_server_statuses(cx)

View File

@@ -89,22 +89,25 @@ impl AskPassSession {
buffer.clear(); buffer.clear();
} }
let prompt = String::from_utf8_lossy(&buffer); let prompt = String::from_utf8_lossy(&buffer);
if let Some(password) = delegate match delegate
.ask_password(prompt.to_string()) .ask_password(prompt.to_string())
.await .await
.context("failed to get askpass password") .context("failed to get askpass password")
.log_err() .log_err()
{ {
stream.write_all(password.as_bytes()).await.log_err(); Some(password) => {
} else { stream.write_all(password.as_bytes()).await.log_err();
if let Some(kill_tx) = kill_tx.take() { }
kill_tx.send(()).log_err(); _ => {
if let Some(kill_tx) = kill_tx.take() {
kill_tx.send(()).log_err();
}
// note: we expect the caller to drop this task when it's done.
// We need to keep the stream open until the caller is done to avoid
// spurious errors from ssh.
std::future::pending::<()>().await;
drop(stream);
} }
// note: we expect the caller to drop this task when it's done.
// We need to keep the stream open until the caller is done to avoid
// spurious errors from ssh.
std::future::pending::<()>().await;
drop(stream);
} }
} }
drop(temp_dir) drop(temp_dir)

View File

@@ -741,20 +741,29 @@ impl AssistantPanel {
} }
}); });
if let Some(context_editor) = context_editor { match context_editor {
Some(InlineAssistTarget::Editor(context_editor, false)) Some(context_editor) => Some(InlineAssistTarget::Editor(context_editor, false)),
} else if let Some(workspace_editor) = workspace _ => {
.active_item(cx) match workspace
.and_then(|item| item.act_as::<Editor>(cx)) .active_item(cx)
{ .and_then(|item| item.act_as::<Editor>(cx))
Some(InlineAssistTarget::Editor(workspace_editor, true)) {
} else if let Some(terminal_view) = workspace Some(workspace_editor) => {
.active_item(cx) Some(InlineAssistTarget::Editor(workspace_editor, true))
.and_then(|item| item.act_as::<TerminalView>(cx)) }
{ _ => {
Some(InlineAssistTarget::Terminal(terminal_view)) match workspace
} else { .active_item(cx)
None .and_then(|item| item.act_as::<TerminalView>(cx))
{
Some(terminal_view) => {
Some(InlineAssistTarget::Terminal(terminal_view))
}
_ => None,
}
}
}
}
} }
} }

View File

@@ -250,33 +250,35 @@ impl InlineAssistant {
selection.end.column = snapshot selection.end.column = snapshot
.buffer_snapshot .buffer_snapshot
.line_len(MultiBufferRow(selection.end.row)); .line_len(MultiBufferRow(selection.end.row));
} else if let Some(fold) = } else {
snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row)) match snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row)) {
{ Some(fold) => {
selection.start = fold.range().start; selection.start = fold.range().start;
selection.end = fold.range().end; selection.end = fold.range().end;
if MultiBufferRow(selection.end.row) < snapshot.buffer_snapshot.max_row() { if MultiBufferRow(selection.end.row) < snapshot.buffer_snapshot.max_row() {
let chars = snapshot let chars = snapshot
.buffer_snapshot
.chars_at(Point::new(selection.end.row + 1, 0));
for c in chars {
if c == '\n' {
break;
}
if c.is_whitespace() {
continue;
}
if snapshot
.language_at(selection.end)
.is_some_and(|language| language.config().brackets.is_closing_brace(c))
{
selection.end.row += 1;
selection.end.column = snapshot
.buffer_snapshot .buffer_snapshot
.line_len(MultiBufferRow(selection.end.row)); .chars_at(Point::new(selection.end.row + 1, 0));
for c in chars {
if c == '\n' {
break;
}
if c.is_whitespace() {
continue;
}
if snapshot.language_at(selection.end).is_some_and(|language| {
language.config().brackets.is_closing_brace(c)
}) {
selection.end.row += 1;
selection.end.column = snapshot
.buffer_snapshot
.line_len(MultiBufferRow(selection.end.row));
}
}
} }
} }
_ => {}
} }
} }
@@ -1031,24 +1033,27 @@ impl InlineAssistant {
let mut scroll_target_top; let mut scroll_target_top;
let mut scroll_target_bottom; let mut scroll_target_bottom;
if let Some(decorations) = assist.decorations.as_ref() { match assist.decorations.as_ref() {
scroll_target_top = editor Some(decorations) => {
.row_for_block(decorations.prompt_block_id, cx) scroll_target_top = editor
.unwrap() .row_for_block(decorations.prompt_block_id, cx)
.0 as f32; .unwrap()
scroll_target_bottom = editor .0 as f32;
.row_for_block(decorations.end_block_id, cx) scroll_target_bottom = editor
.unwrap() .row_for_block(decorations.end_block_id, cx)
.0 as f32; .unwrap()
} else { .0 as f32;
let snapshot = editor.snapshot(window, cx); }
let start_row = assist _ => {
.range let snapshot = editor.snapshot(window, cx);
.start let start_row = assist
.to_display_point(&snapshot.display_snapshot) .range
.row(); .start
scroll_target_top = start_row.0 as f32; .to_display_point(&snapshot.display_snapshot)
scroll_target_bottom = scroll_target_top + 1.; .row();
scroll_target_top = start_row.0 as f32;
scroll_target_bottom = scroll_target_top + 1.;
}
} }
scroll_target_top -= editor.vertical_scroll_margin() as f32; scroll_target_top -= editor.vertical_scroll_margin() as f32;
scroll_target_bottom += editor.vertical_scroll_margin() as f32; scroll_target_bottom += editor.vertical_scroll_margin() as f32;
@@ -1093,10 +1098,11 @@ impl InlineAssistant {
} }
pub fn start_assist(&mut self, assist_id: InlineAssistId, window: &mut Window, cx: &mut App) { pub fn start_assist(&mut self, assist_id: InlineAssistId, window: &mut Window, cx: &mut App) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) { let assist = match self.assists.get_mut(&assist_id) {
assist Some(assist) => assist,
} else { _ => {
return; return;
}
}; };
let assist_group_id = assist.group_id; let assist_group_id = assist.group_id;
@@ -1128,10 +1134,11 @@ impl InlineAssistant {
} }
pub fn stop_assist(&mut self, assist_id: InlineAssistId, cx: &mut App) { pub fn stop_assist(&mut self, assist_id: InlineAssistId, cx: &mut App) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) { let assist = match self.assists.get_mut(&assist_id) {
assist Some(assist) => assist,
} else { _ => {
return; return;
}
}; };
assist.codegen.update(cx, |codegen, cx| codegen.stop(cx)); assist.codegen.update(cx, |codegen, cx| codegen.stop(cx));
@@ -2184,7 +2191,7 @@ impl PromptEditor {
.into_any_element() .into_any_element()
} }
fn render_token_count(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> { fn render_token_count(&self, cx: &mut Context<Self>) -> Option<impl IntoElement + use<>> {
let model = LanguageModelRegistry::read_global(cx).active_model()?; let model = LanguageModelRegistry::read_global(cx).active_model()?;
let token_counts = self.token_counts?; let token_counts = self.token_counts?;
let max_token_count = model.max_token_count(); let max_token_count = model.max_token_count();
@@ -2212,40 +2219,43 @@ impl PromptEditor {
.size(LabelSize::Small) .size(LabelSize::Small)
.color(Color::Muted), .color(Color::Muted),
); );
if let Some(workspace) = self.workspace.clone() { match self.workspace.clone() {
token_count = token_count Some(workspace) => {
.tooltip(move |window, cx| { token_count = token_count
Tooltip::with_meta( .tooltip(move |window, cx| {
format!( Tooltip::with_meta(
"Tokens Used ({} from the Assistant Panel)", format!(
humanize_token_count(token_counts.assistant_panel) "Tokens Used ({} from the Assistant Panel)",
), humanize_token_count(token_counts.assistant_panel)
None, ),
"Click to open the Assistant Panel", None,
window, "Click to open the Assistant Panel",
cx, window,
) cx,
}) )
.cursor_pointer() })
.on_mouse_down(gpui::MouseButton::Left, |_, _, cx| cx.stop_propagation()) .cursor_pointer()
.on_click(move |_, window, cx| { .on_mouse_down(gpui::MouseButton::Left, |_, _, cx| cx.stop_propagation())
cx.stop_propagation(); .on_click(move |_, window, cx| {
workspace cx.stop_propagation();
.update(cx, |workspace, cx| { workspace
workspace.focus_panel::<AssistantPanel>(window, cx) .update(cx, |workspace, cx| {
}) workspace.focus_panel::<AssistantPanel>(window, cx)
.ok(); })
}); .ok();
} else { });
token_count = token_count }
.cursor_default() _ => {
.tooltip(Tooltip::text("Tokens used")); token_count = token_count
.cursor_default()
.tooltip(Tooltip::text("Tokens used"));
}
} }
Some(token_count) Some(token_count)
} }
fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement { fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement + use<> {
let settings = ThemeSettings::get_global(cx); let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle { let text_style = TextStyle {
color: if self.editor.read(cx).read_only(cx) { color: if self.editor.read(cx).read_only(cx) {
@@ -2271,7 +2281,7 @@ impl PromptEditor {
) )
} }
fn render_rate_limit_notice(&self, cx: &mut Context<Self>) -> impl IntoElement { fn render_rate_limit_notice(&self, cx: &mut Context<Self>) -> impl IntoElement + use<> {
Popover::new().child( Popover::new().child(
v_flex() v_flex()
.occlude() .occlude()
@@ -2430,10 +2440,11 @@ impl InlineAssist {
InlineAssistant::update_global(cx, |this, cx| match event { InlineAssistant::update_global(cx, |this, cx| match event {
CodegenEvent::Undone => this.finish_assist(assist_id, false, window, cx), CodegenEvent::Undone => this.finish_assist(assist_id, false, window, cx),
CodegenEvent::Finished => { CodegenEvent::Finished => {
let assist = if let Some(assist) = this.assists.get(&assist_id) { let assist = match this.assists.get(&assist_id) {
assist Some(assist) => assist,
} else { _ => {
return; return;
}
}; };
if let CodegenStatus::Error(error) = codegen.read(cx).status(cx) { if let CodegenStatus::Error(error) = codegen.read(cx).status(cx) {
@@ -2865,27 +2876,28 @@ impl CodegenAlternative {
assistant_panel_context: Option<LanguageModelRequest>, assistant_panel_context: Option<LanguageModelRequest>,
cx: &App, cx: &App,
) -> BoxFuture<'static, Result<TokenCounts>> { ) -> BoxFuture<'static, Result<TokenCounts>> {
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() { match LanguageModelRegistry::read_global(cx).active_model() {
let request = self.build_request(user_prompt, assistant_panel_context.clone(), cx); Some(model) => {
match request { let request = self.build_request(user_prompt, assistant_panel_context.clone(), cx);
Ok(request) => { match request {
let total_count = model.count_tokens(request.clone(), cx); Ok(request) => {
let assistant_panel_count = assistant_panel_context let total_count = model.count_tokens(request.clone(), cx);
.map(|context| model.count_tokens(context, cx)) let assistant_panel_count = assistant_panel_context
.unwrap_or_else(|| future::ready(Ok(0)).boxed()); .map(|context| model.count_tokens(context, cx))
.unwrap_or_else(|| future::ready(Ok(0)).boxed());
async move { async move {
Ok(TokenCounts { Ok(TokenCounts {
total: total_count.await?, total: total_count.await?,
assistant_panel: assistant_panel_count.await?, assistant_panel: assistant_panel_count.await?,
}) })
}
.boxed()
} }
.boxed() Err(error) => futures::future::ready(Err(error)).boxed(),
} }
Err(error) => futures::future::ready(Err(error)).boxed(),
} }
} else { _ => future::ready(Err(anyhow!("no active model"))).boxed(),
future::ready(Err(anyhow!("no active model"))).boxed()
} }
} }
@@ -3221,10 +3233,13 @@ impl CodegenAlternative {
.update(cx, |this, cx| { .update(cx, |this, cx| {
this.message_id = message_id; this.message_id = message_id;
this.last_equal_ranges.clear(); this.last_equal_ranges.clear();
if let Err(error) = result { match result {
this.status = CodegenStatus::Error(error); Err(error) => {
} else { this.status = CodegenStatus::Error(error);
this.status = CodegenStatus::Done; }
_ => {
this.status = CodegenStatus::Done;
}
} }
this.elapsed_time = Some(elapsed_time); this.elapsed_time = Some(elapsed_time);
this.completion = Some(completion.lock().clone()); this.completion = Some(completion.lock().clone());

View File

@@ -184,10 +184,11 @@ impl TerminalInlineAssistant {
} }
fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) { fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) { let assist = match self.assists.get_mut(&assist_id) {
assist Some(assist) => assist,
} else { _ => {
return; return;
}
}; };
let Some(user_prompt) = assist let Some(user_prompt) = assist
@@ -222,10 +223,11 @@ impl TerminalInlineAssistant {
} }
fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) { fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) { let assist = match self.assists.get_mut(&assist_id) {
assist Some(assist) => assist,
} else { _ => {
return; return;
}
}; };
assist.codegen.update(cx, |codegen, cx| codegen.stop(cx)); assist.codegen.update(cx, |codegen, cx| codegen.stop(cx));
@@ -436,10 +438,11 @@ impl TerminalInlineAssist {
window.subscribe(&codegen, cx, move |codegen, event, window, cx| { window.subscribe(&codegen, cx, move |codegen, event, window, cx| {
TerminalInlineAssistant::update_global(cx, |this, cx| match event { TerminalInlineAssistant::update_global(cx, |this, cx| match event {
CodegenEvent::Finished => { CodegenEvent::Finished => {
let assist = if let Some(assist) = this.assists.get(&assist_id) { let assist = match this.assists.get(&assist_id) {
assist Some(assist) => assist,
} else { _ => {
return; return;
}
}; };
if let CodegenStatus::Error(error) = &codegen.read(cx).status { if let CodegenStatus::Error(error) = &codegen.read(cx).status {
@@ -664,8 +667,8 @@ impl Render for PromptEditor {
}, },
gpui::Corner::TopRight, gpui::Corner::TopRight,
)) ))
.children( .children(match &self.codegen.read(cx).status {
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status { CodegenStatus::Error(error) => {
let error_message = SharedString::from(error.to_string()); let error_message = SharedString::from(error.to_string());
Some( Some(
div() div()
@@ -677,10 +680,9 @@ impl Render for PromptEditor {
.color(Color::Error), .color(Color::Error),
), ),
) )
} else { }
None _ => None,
}, }),
),
) )
.child(div().flex_1().child(self.render_prompt_editor(cx))) .child(div().flex_1().child(self.render_prompt_editor(cx)))
.child( .child(
@@ -979,7 +981,7 @@ impl PromptEditor {
} }
} }
fn render_token_count(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> { fn render_token_count(&self, cx: &mut Context<Self>) -> Option<impl IntoElement + use<>> {
let model = LanguageModelRegistry::read_global(cx).active_model()?; let model = LanguageModelRegistry::read_global(cx).active_model()?;
let token_count = self.token_count?; let token_count = self.token_count?;
let max_token_count = model.max_token_count(); let max_token_count = model.max_token_count();
@@ -1007,37 +1009,40 @@ impl PromptEditor {
.size(LabelSize::Small) .size(LabelSize::Small)
.color(Color::Muted), .color(Color::Muted),
); );
if let Some(workspace) = self.workspace.clone() { match self.workspace.clone() {
token_count = token_count Some(workspace) => {
.tooltip(|window, cx| { token_count = token_count
Tooltip::with_meta( .tooltip(|window, cx| {
"Tokens Used by Inline Assistant", Tooltip::with_meta(
None, "Tokens Used by Inline Assistant",
"Click to Open Assistant Panel", None,
window, "Click to Open Assistant Panel",
cx, window,
) cx,
}) )
.cursor_pointer() })
.on_mouse_down(gpui::MouseButton::Left, |_, _, cx| cx.stop_propagation()) .cursor_pointer()
.on_click(move |_, window, cx| { .on_mouse_down(gpui::MouseButton::Left, |_, _, cx| cx.stop_propagation())
cx.stop_propagation(); .on_click(move |_, window, cx| {
workspace cx.stop_propagation();
.update(cx, |workspace, cx| { workspace
workspace.focus_panel::<AssistantPanel>(window, cx) .update(cx, |workspace, cx| {
}) workspace.focus_panel::<AssistantPanel>(window, cx)
.ok(); })
}); .ok();
} else { });
token_count = token_count }
.cursor_default() _ => {
.tooltip(Tooltip::text("Tokens Used by Inline Assistant")); token_count = token_count
.cursor_default()
.tooltip(Tooltip::text("Tokens Used by Inline Assistant"));
}
} }
Some(token_count) Some(token_count)
} }
fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement { fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement + use<> {
let settings = ThemeSettings::get_global(cx); let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle { let text_style = TextStyle {
color: if self.editor.read(cx).read_only(cx) { color: if self.editor.read(cx).read_only(cx) {
@@ -1217,10 +1222,13 @@ impl Codegen {
let result = generate.await; let result = generate.await;
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
if let Err(error) = result { match result {
this.status = CodegenStatus::Error(error); Err(error) => {
} else { this.status = CodegenStatus::Error(error);
this.status = CodegenStatus::Done; }
_ => {
this.status = CodegenStatus::Done;
}
} }
cx.emit(CodegenEvent::Finished); cx.emit(CodegenEvent::Finished);
cx.notify(); cx.notify();

View File

@@ -77,34 +77,44 @@ impl RenderedMessage {
} }
fn append_thinking(&mut self, text: &String, window: &Window, cx: &mut App) { fn append_thinking(&mut self, text: &String, window: &Window, cx: &mut App) {
if let Some(RenderedMessageSegment::Thinking { match self.segments.last_mut() {
content, Some(RenderedMessageSegment::Thinking {
scroll_handle, content,
}) = self.segments.last_mut() scroll_handle,
{ }) => {
content.update(cx, |markdown, cx| { content.update(cx, |markdown, cx| {
markdown.append(text, cx); markdown.append(text, cx);
}); });
scroll_handle.scroll_to_bottom(); scroll_handle.scroll_to_bottom();
} else { }
self.segments.push(RenderedMessageSegment::Thinking { _ => {
content: render_markdown(text.into(), self.language_registry.clone(), window, cx), self.segments.push(RenderedMessageSegment::Thinking {
scroll_handle: ScrollHandle::default(), content: render_markdown(
}); text.into(),
self.language_registry.clone(),
window,
cx,
),
scroll_handle: ScrollHandle::default(),
});
}
} }
} }
fn append_text(&mut self, text: &String, window: &Window, cx: &mut App) { fn append_text(&mut self, text: &String, window: &Window, cx: &mut App) {
if let Some(RenderedMessageSegment::Text(markdown)) = self.segments.last_mut() { match self.segments.last_mut() {
markdown.update(cx, |markdown, cx| markdown.append(text, cx)); Some(RenderedMessageSegment::Text(markdown)) => {
} else { markdown.update(cx, |markdown, cx| markdown.append(text, cx));
self.segments }
.push(RenderedMessageSegment::Text(render_markdown( _ => {
SharedString::from(text), self.segments
self.language_registry.clone(), .push(RenderedMessageSegment::Text(render_markdown(
window, SharedString::from(text),
cx, self.language_registry.clone(),
))); window,
cx,
)));
}
} }
} }
@@ -929,21 +939,18 @@ impl ActiveThread {
let message_content = let message_content =
v_flex() v_flex()
.gap_1p5() .gap_1p5()
.child( .child(match edit_message_editor.clone() {
if let Some(edit_message_editor) = edit_message_editor.clone() { Some(edit_message_editor) => div()
div() .key_context("EditMessageEditor")
.key_context("EditMessageEditor") .on_action(cx.listener(Self::cancel_editing_message))
.on_action(cx.listener(Self::cancel_editing_message)) .on_action(cx.listener(Self::confirm_editing_message))
.on_action(cx.listener(Self::confirm_editing_message)) .min_h_6()
.min_h_6() .child(edit_message_editor),
.child(edit_message_editor) _ => div()
} else { .min_h_6()
div() .text_ui(cx)
.min_h_6() .child(self.render_message_content(message_id, rendered_message, cx)),
.text_ui(cx) })
.child(self.render_message_content(message_id, rendered_message, cx))
},
)
.when_some(context, |parent, context| { .when_some(context, |parent, context| {
if !context.is_empty() { if !context.is_empty() {
parent.child(h_flex().flex_wrap().gap_1().children( parent.child(h_flex().flex_wrap().gap_1().children(
@@ -1204,7 +1211,7 @@ impl ActiveThread {
message_id: MessageId, message_id: MessageId,
rendered_message: &RenderedMessage, rendered_message: &RenderedMessage,
cx: &Context<Self>, cx: &Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let pending_thinking_segment_index = rendered_message let pending_thinking_segment_index = rendered_message
.segments .segments
.iter() .iter()
@@ -1259,7 +1266,7 @@ impl ActiveThread {
scroll_handle: &ScrollHandle, scroll_handle: &ScrollHandle,
pending: bool, pending: bool,
cx: &Context<Self>, cx: &Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let is_open = self let is_open = self
.expanded_thinking_segments .expanded_thinking_segments
.get(&(message_id, ix)) .get(&(message_id, ix))
@@ -1412,7 +1419,11 @@ impl ActiveThread {
) )
} }
fn render_tool_use(&self, tool_use: ToolUse, cx: &mut Context<Self>) -> impl IntoElement { fn render_tool_use(
&self,
tool_use: ToolUse,
cx: &mut Context<Self>,
) -> impl IntoElement + use<> {
let is_open = self let is_open = self
.expanded_tool_uses .expanded_tool_uses
.get(&tool_use.id) .get(&tool_use.id)
@@ -1822,7 +1833,7 @@ impl ActiveThread {
fn render_confirmations<'a>( fn render_confirmations<'a>(
&'a mut self, &'a mut self,
cx: &'a mut Context<Self>, cx: &'a mut Context<Self>,
) -> impl Iterator<Item = AnyElement> + 'a { ) -> impl Iterator<Item = AnyElement> + 'a + use<'a> {
let thread = self.thread.read(cx); let thread = self.thread.read(cx);
thread thread

View File

@@ -105,7 +105,7 @@ impl AssistantConfiguration {
&mut self, &mut self,
provider: &Arc<dyn LanguageModelProvider>, provider: &Arc<dyn LanguageModelProvider>,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let provider_id = provider.id().0.clone(); let provider_id = provider.id().0.clone();
let provider_name = provider.name().0.clone(); let provider_name = provider.name().0.clone();
let configuration_view = self let configuration_view = self
@@ -167,7 +167,10 @@ impl AssistantConfiguration {
) )
} }
fn render_context_servers_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement { fn render_context_servers_section(
&mut self,
cx: &mut Context<Self>,
) -> impl IntoElement + use<> {
let context_servers = self.context_server_manager.read(cx).all_servers().clone(); let context_servers = self.context_server_manager.read(cx).all_servers().clone();
let tools_by_source = self.tools.tools_by_source(cx); let tools_by_source = self.tools.tools_by_source(cx);
let empty = Vec::new(); let empty = Vec::new();

View File

@@ -318,7 +318,7 @@ impl ManageProfilesModal {
mode: ChooseProfileMode, mode: ChooseProfileMode,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
Navigable::new( Navigable::new(
div() div()
.track_focus(&self.focus_handle(cx)) .track_focus(&self.focus_handle(cx))
@@ -418,7 +418,7 @@ impl ManageProfilesModal {
mode: NewProfileMode, mode: NewProfileMode,
_window: &mut Window, _window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let settings = AssistantSettings::get_global(cx); let settings = AssistantSettings::get_global(cx);
let base_profile_name = mode.base_profile_id.as_ref().map(|base_profile_id| { let base_profile_name = mode.base_profile_id.as_ref().map(|base_profile_id| {
@@ -448,7 +448,7 @@ impl ManageProfilesModal {
mode: ViewProfileMode, mode: ViewProfileMode,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let settings = AssistantSettings::get_global(cx); let settings = AssistantSettings::get_global(cx);
let profile_name = settings let profile_name = settings

View File

@@ -48,16 +48,17 @@ impl AssistantDiff {
.items_of_type::<AssistantDiff>(cx) .items_of_type::<AssistantDiff>(cx)
.find(|diff| diff.read(cx).thread == thread) .find(|diff| diff.read(cx).thread == thread)
})?; })?;
if let Some(existing_diff) = existing_diff { match existing_diff {
workspace.update(cx, |workspace, cx| { Some(existing_diff) => workspace.update(cx, |workspace, cx| {
workspace.activate_item(&existing_diff, true, true, window, cx); workspace.activate_item(&existing_diff, true, true, window, cx);
}) }),
} else { _ => {
let assistant_diff = let assistant_diff =
cx.new(|cx| AssistantDiff::new(thread.clone(), workspace.clone(), window, cx)); cx.new(|cx| AssistantDiff::new(thread.clone(), workspace.clone(), window, cx));
workspace.update(cx, |workspace, cx| { workspace.update(cx, |workspace, cx| {
workspace.add_item_to_center(Box::new(assistant_diff), window, cx); workspace.add_item_to_center(Box::new(assistant_diff), window, cx);
}) })
}
} }
} }

View File

@@ -584,20 +584,14 @@ impl Focusable for AssistantPanel {
match self.active_view { match self.active_view {
ActiveView::Thread => self.message_editor.focus_handle(cx), ActiveView::Thread => self.message_editor.focus_handle(cx),
ActiveView::History => self.history.focus_handle(cx), ActiveView::History => self.history.focus_handle(cx),
ActiveView::PromptEditor => { ActiveView::PromptEditor => match self.context_editor.as_ref() {
if let Some(context_editor) = self.context_editor.as_ref() { Some(context_editor) => context_editor.focus_handle(cx),
context_editor.focus_handle(cx) _ => cx.focus_handle(),
} else { },
cx.focus_handle() ActiveView::Configuration => match self.configuration.as_ref() {
} Some(configuration) => configuration.focus_handle(cx),
} _ => cx.focus_handle(),
ActiveView::Configuration => { },
if let Some(configuration) = self.configuration.as_ref() {
configuration.focus_handle(cx)
} else {
cx.focus_handle()
}
}
} }
} }
} }
@@ -683,7 +677,11 @@ impl Panel for AssistantPanel {
} }
impl AssistantPanel { impl AssistantPanel {
fn render_toolbar(&self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render_toolbar(
&self,
_window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement + use<> {
let thread = self.thread.read(cx); let thread = self.thread.read(cx);
let focus_handle = self.focus_handle(cx); let focus_handle = self.focus_handle(cx);
@@ -825,7 +823,7 @@ impl AssistantPanel {
&self, &self,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let recent_history = self let recent_history = self
.history_store .history_store
.update(cx, |this, cx| this.recent_entries(6, cx)); .update(cx, |this, cx| this.recent_entries(6, cx));

View File

@@ -676,10 +676,13 @@ impl CodegenAlternative {
.update(cx, |this, cx| { .update(cx, |this, cx| {
this.message_id = message_id; this.message_id = message_id;
this.last_equal_ranges.clear(); this.last_equal_ranges.clear();
if let Err(error) = result { match result {
this.status = CodegenStatus::Error(error); Err(error) => {
} else { this.status = CodegenStatus::Error(error);
this.status = CodegenStatus::Done; }
_ => {
this.status = CodegenStatus::Done;
}
} }
this.elapsed_time = Some(elapsed_time); this.elapsed_time = Some(elapsed_time);
this.completion = Some(completion.lock().clone()); this.completion = Some(completion.lock().clone());

View File

@@ -581,15 +581,14 @@ impl CompletionProvider for ContextPickerCompletionProvider {
let line_start = Point::new(position.row, 0); let line_start = Point::new(position.row, 0);
let offset_to_line = buffer.point_to_offset(line_start); let offset_to_line = buffer.point_to_offset(line_start);
let mut lines = buffer.text_for_range(line_start..position).lines(); let mut lines = buffer.text_for_range(line_start..position).lines();
if let Some(line) = lines.next() { match lines.next() {
MentionCompletion::try_parse(line, offset_to_line) Some(line) => MentionCompletion::try_parse(line, offset_to_line)
.map(|completion| { .map(|completion| {
completion.source_range.start <= offset_to_line + position.column as usize completion.source_range.start <= offset_to_line + position.column as usize
&& completion.source_range.end >= offset_to_line + position.column as usize && completion.source_range.end >= offset_to_line + position.column as usize
}) })
.unwrap_or(false) .unwrap_or(false),
} else { _ => false,
false
} }
} }

View File

@@ -360,12 +360,15 @@ impl ContextStore {
remove_if_exists: bool, remove_if_exists: bool,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
if let Some(context_id) = self.includes_thread(&thread.read(cx).id()) { match self.includes_thread(&thread.read(cx).id()) {
if remove_if_exists { Some(context_id) => {
self.remove_context(context_id); if remove_if_exists {
self.remove_context(context_id);
}
}
_ => {
self.insert_thread(thread, cx);
} }
} else {
self.insert_thread(thread, cx);
} }
} }
@@ -687,7 +690,7 @@ pub fn refresh_context_store_text(
context_store: Entity<ContextStore>, context_store: Entity<ContextStore>,
changed_buffers: &HashSet<Entity<Buffer>>, changed_buffers: &HashSet<Entity<Buffer>>,
cx: &App, cx: &App,
) -> impl Future<Output = Vec<ContextId>> { ) -> impl Future<Output = Vec<ContextId>> + use<> {
let mut tasks = Vec::new(); let mut tasks = Vec::new();
for context in &context_store.read(cx).context { for context in &context_store.read(cx).context {
@@ -756,8 +759,8 @@ fn refresh_file_text(
) -> Option<Task<()>> { ) -> Option<Task<()>> {
let id = file_context.id; let id = file_context.id;
let task = refresh_context_buffer(&file_context.context_buffer, cx); let task = refresh_context_buffer(&file_context.context_buffer, cx);
if let Some(task) = task { match task {
Some(cx.spawn(async move |cx| { Some(task) => Some(cx.spawn(async move |cx| {
let context_buffer = task.await; let context_buffer = task.await;
context_store context_store
.update(cx, |context_store, _| { .update(cx, |context_store, _| {
@@ -765,9 +768,8 @@ fn refresh_file_text(
context_store.replace_context(AssistantContext::File(new_file_context)); context_store.replace_context(AssistantContext::File(new_file_context));
}) })
.ok(); .ok();
})) })),
} else { _ => None,
None
} }
} }
@@ -780,14 +782,15 @@ fn refresh_directory_text(
let futures = directory_context let futures = directory_context
.context_buffers .context_buffers
.iter() .iter()
.map(|context_buffer| { .map(
if let Some(refresh_task) = refresh_context_buffer(context_buffer, cx) { |context_buffer| match refresh_context_buffer(context_buffer, cx) {
stale = true; Some(refresh_task) => {
future::Either::Left(refresh_task) stale = true;
} else { future::Either::Left(refresh_task)
future::Either::Right(future::ready((*context_buffer).clone())) }
} _ => future::Either::Right(future::ready((*context_buffer).clone())),
}) },
)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !stale { if !stale {
@@ -816,8 +819,8 @@ fn refresh_symbol_text(
) -> Option<Task<()>> { ) -> Option<Task<()>> {
let id = symbol_context.id; let id = symbol_context.id;
let task = refresh_context_symbol(&symbol_context.context_symbol, cx); let task = refresh_context_symbol(&symbol_context.context_symbol, cx);
if let Some(task) = task { match task {
Some(cx.spawn(async move |cx| { Some(task) => Some(cx.spawn(async move |cx| {
let context_symbol = task.await; let context_symbol = task.await;
context_store context_store
.update(cx, |context_store, _| { .update(cx, |context_store, _| {
@@ -825,9 +828,8 @@ fn refresh_symbol_text(
context_store.replace_context(AssistantContext::Symbol(new_symbol_context)); context_store.replace_context(AssistantContext::Symbol(new_symbol_context));
}) })
.ok(); .ok();
})) })),
} else { _ => None,
None
} }
} }
@@ -855,7 +857,7 @@ fn refresh_thread_text(
fn refresh_context_buffer( fn refresh_context_buffer(
context_buffer: &ContextBuffer, context_buffer: &ContextBuffer,
cx: &App, cx: &App,
) -> Option<impl Future<Output = ContextBuffer>> { ) -> Option<impl Future<Output = ContextBuffer> + use<>> {
let buffer = context_buffer.buffer.read(cx); let buffer = context_buffer.buffer.read(cx);
let path = buffer_path_log_err(buffer)?; let path = buffer_path_log_err(buffer)?;
if buffer.version.changed_since(&context_buffer.version) { if buffer.version.changed_since(&context_buffer.version) {
@@ -875,7 +877,7 @@ fn refresh_context_buffer(
fn refresh_context_symbol( fn refresh_context_symbol(
context_symbol: &ContextSymbol, context_symbol: &ContextSymbol,
cx: &App, cx: &App,
) -> Option<impl Future<Output = ContextSymbol>> { ) -> Option<impl Future<Output = ContextSymbol> + use<>> {
let buffer = context_symbol.buffer.read(cx); let buffer = context_symbol.buffer.read(cx);
let path = buffer_path_log_err(buffer)?; let path = buffer_path_log_err(buffer)?;
let project_path = buffer.project_path(cx)?; let project_path = buffer.project_path(cx)?;

View File

@@ -341,33 +341,35 @@ impl InlineAssistant {
selection.end.column = snapshot selection.end.column = snapshot
.buffer_snapshot .buffer_snapshot
.line_len(MultiBufferRow(selection.end.row)); .line_len(MultiBufferRow(selection.end.row));
} else if let Some(fold) = } else {
snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row)) match snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row)) {
{ Some(fold) => {
selection.start = fold.range().start; selection.start = fold.range().start;
selection.end = fold.range().end; selection.end = fold.range().end;
if MultiBufferRow(selection.end.row) < snapshot.buffer_snapshot.max_row() { if MultiBufferRow(selection.end.row) < snapshot.buffer_snapshot.max_row() {
let chars = snapshot let chars = snapshot
.buffer_snapshot
.chars_at(Point::new(selection.end.row + 1, 0));
for c in chars {
if c == '\n' {
break;
}
if c.is_whitespace() {
continue;
}
if snapshot
.language_at(selection.end)
.is_some_and(|language| language.config().brackets.is_closing_brace(c))
{
selection.end.row += 1;
selection.end.column = snapshot
.buffer_snapshot .buffer_snapshot
.line_len(MultiBufferRow(selection.end.row)); .chars_at(Point::new(selection.end.row + 1, 0));
for c in chars {
if c == '\n' {
break;
}
if c.is_whitespace() {
continue;
}
if snapshot.language_at(selection.end).is_some_and(|language| {
language.config().brackets.is_closing_brace(c)
}) {
selection.end.row += 1;
selection.end.column = snapshot
.buffer_snapshot
.line_len(MultiBufferRow(selection.end.row));
}
}
} }
} }
_ => {}
} }
} }
@@ -1129,24 +1131,27 @@ impl InlineAssistant {
let mut scroll_target_top; let mut scroll_target_top;
let mut scroll_target_bottom; let mut scroll_target_bottom;
if let Some(decorations) = assist.decorations.as_ref() { match assist.decorations.as_ref() {
scroll_target_top = editor Some(decorations) => {
.row_for_block(decorations.prompt_block_id, cx) scroll_target_top = editor
.unwrap() .row_for_block(decorations.prompt_block_id, cx)
.0 as f32; .unwrap()
scroll_target_bottom = editor .0 as f32;
.row_for_block(decorations.end_block_id, cx) scroll_target_bottom = editor
.unwrap() .row_for_block(decorations.end_block_id, cx)
.0 as f32; .unwrap()
} else { .0 as f32;
let snapshot = editor.snapshot(window, cx); }
let start_row = assist _ => {
.range let snapshot = editor.snapshot(window, cx);
.start let start_row = assist
.to_display_point(&snapshot.display_snapshot) .range
.row(); .start
scroll_target_top = start_row.0 as f32; .to_display_point(&snapshot.display_snapshot)
scroll_target_bottom = scroll_target_top + 1.; .row();
scroll_target_top = start_row.0 as f32;
scroll_target_bottom = scroll_target_top + 1.;
}
} }
scroll_target_top -= editor.vertical_scroll_margin() as f32; scroll_target_top -= editor.vertical_scroll_margin() as f32;
scroll_target_bottom += editor.vertical_scroll_margin() as f32; scroll_target_bottom += editor.vertical_scroll_margin() as f32;
@@ -1191,10 +1196,11 @@ impl InlineAssistant {
} }
pub fn start_assist(&mut self, assist_id: InlineAssistId, window: &mut Window, cx: &mut App) { pub fn start_assist(&mut self, assist_id: InlineAssistId, window: &mut Window, cx: &mut App) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) { let assist = match self.assists.get_mut(&assist_id) {
assist Some(assist) => assist,
} else { _ => {
return; return;
}
}; };
let assist_group_id = assist.group_id; let assist_group_id = assist.group_id;
@@ -1222,10 +1228,11 @@ impl InlineAssistant {
} }
pub fn stop_assist(&mut self, assist_id: InlineAssistId, cx: &mut App) { pub fn stop_assist(&mut self, assist_id: InlineAssistId, cx: &mut App) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) { let assist = match self.assists.get_mut(&assist_id) {
assist Some(assist) => assist,
} else { _ => {
return; return;
}
}; };
assist.codegen.update(cx, |codegen, cx| codegen.stop(cx)); assist.codegen.update(cx, |codegen, cx| codegen.stop(cx));
@@ -1449,20 +1456,27 @@ impl InlineAssistant {
} }
}); });
if let Some(context_editor) = context_editor { match context_editor {
Some(InlineAssistTarget::Editor(context_editor)) Some(context_editor) => Some(InlineAssistTarget::Editor(context_editor)),
} else if let Some(workspace_editor) = workspace _ => {
.active_item(cx) match workspace
.and_then(|item| item.act_as::<Editor>(cx)) .active_item(cx)
{ .and_then(|item| item.act_as::<Editor>(cx))
Some(InlineAssistTarget::Editor(workspace_editor)) {
} else if let Some(terminal_view) = workspace Some(workspace_editor) => Some(InlineAssistTarget::Editor(workspace_editor)),
.active_item(cx) _ => {
.and_then(|item| item.act_as::<TerminalView>(cx)) match workspace
{ .active_item(cx)
Some(InlineAssistTarget::Terminal(terminal_view)) .and_then(|item| item.act_as::<TerminalView>(cx))
} else { {
None Some(terminal_view) => {
Some(InlineAssistTarget::Terminal(terminal_view))
}
_ => None,
}
}
}
}
} }
} }
} }
@@ -1654,10 +1668,11 @@ impl InlineAssist {
InlineAssistant::update_global(cx, |this, cx| match event { InlineAssistant::update_global(cx, |this, cx| match event {
CodegenEvent::Undone => this.finish_assist(assist_id, false, window, cx), CodegenEvent::Undone => this.finish_assist(assist_id, false, window, cx),
CodegenEvent::Finished => { CodegenEvent::Finished => {
let assist = if let Some(assist) = this.assists.get(&assist_id) { let assist = match this.assists.get(&assist_id) {
assist Some(assist) => assist,
} else { _ => {
return; return;
}
}; };
if let CodegenStatus::Error(error) = codegen.read(cx).status(cx) { if let CodegenStatus::Error(error) = codegen.read(cx).status(cx) {

View File

@@ -677,7 +677,7 @@ impl<T: 'static> PromptEditor<T> {
.into_any_element() .into_any_element()
} }
fn render_rate_limit_notice(&self, cx: &mut Context<Self>) -> impl IntoElement { fn render_rate_limit_notice(&self, cx: &mut Context<Self>) -> impl IntoElement + use<T> {
Popover::new().child( Popover::new().child(
v_flex() v_flex()
.occlude() .occlude()

View File

@@ -117,10 +117,13 @@ impl TerminalCodegen {
let result = generate.await; let result = generate.await;
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
if let Err(error) = result { match result {
this.status = CodegenStatus::Error(error); Err(error) => {
} else { this.status = CodegenStatus::Error(error);
this.status = CodegenStatus::Done; }
_ => {
this.status = CodegenStatus::Done;
}
} }
cx.emit(CodegenEvent::Finished); cx.emit(CodegenEvent::Finished);
cx.notify(); cx.notify();

View File

@@ -164,10 +164,11 @@ impl TerminalInlineAssistant {
} }
fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) { fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) { let assist = match self.assists.get_mut(&assist_id) {
assist Some(assist) => assist,
} else { _ => {
return; return;
}
}; };
let Some(user_prompt) = assist let Some(user_prompt) = assist
@@ -202,10 +203,11 @@ impl TerminalInlineAssistant {
} }
fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) { fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) { let assist = match self.assists.get_mut(&assist_id) {
assist Some(assist) => assist,
} else { _ => {
return; return;
}
}; };
assist.codegen.update(cx, |codegen, cx| codegen.stop(cx)); assist.codegen.update(cx, |codegen, cx| codegen.stop(cx));
@@ -402,10 +404,11 @@ impl TerminalInlineAssist {
window.subscribe(&codegen, cx, move |codegen, event, window, cx| { window.subscribe(&codegen, cx, move |codegen, event, window, cx| {
TerminalInlineAssistant::update_global(cx, |this, cx| match event { TerminalInlineAssistant::update_global(cx, |this, cx| match event {
CodegenEvent::Finished => { CodegenEvent::Finished => {
let assist = if let Some(assist) = this.assists.get(&assist_id) { let assist = match this.assists.get(&assist_id) {
assist Some(assist) => assist,
} else { _ => {
return; return;
}
}; };
if let CodegenStatus::Error(error) = &codegen.read(cx).status { if let CodegenStatus::Error(error) = &codegen.read(cx).status {

View File

@@ -379,14 +379,17 @@ impl Thread {
cx.spawn(async move |this, cx| { cx.spawn(async move |this, cx| {
let result = restore.await; let result = restore.await;
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
if let Err(err) = result.as_ref() { match result.as_ref() {
this.last_restore_checkpoint = Some(LastRestoreCheckpoint::Error { Err(err) => {
message_id: checkpoint.message_id, this.last_restore_checkpoint = Some(LastRestoreCheckpoint::Error {
error: err.to_string(), message_id: checkpoint.message_id,
}); error: err.to_string(),
} else { });
this.truncate(checkpoint.message_id, cx); }
this.last_restore_checkpoint = None; _ => {
this.truncate(checkpoint.message_id, cx);
this.last_restore_checkpoint = None;
}
} }
this.pending_checkpoint = None; this.pending_checkpoint = None;
cx.emit(ThreadEvent::CheckpointChanged); cx.emit(ThreadEvent::CheckpointChanged);
@@ -721,8 +724,8 @@ impl Thread {
}) })
.next(); .next();
if let Some((rel_rules_path, abs_rules_path)) = selected_rules_file { match selected_rules_file {
cx.spawn(async move |_| { Some((rel_rules_path, abs_rules_path)) => cx.spawn(async move |_| {
let rules_file_result = maybe!(async move { let rules_file_result = maybe!(async move {
let abs_rules_path = abs_rules_path?; let abs_rules_path = abs_rules_path?;
let text = fs.load(&abs_rules_path).await.with_context(|| { let text = fs.load(&abs_rules_path).await.with_context(|| {
@@ -751,16 +754,15 @@ impl Thread {
rules_file, rules_file,
}; };
(worktree_info, rules_file_error) (worktree_info, rules_file_error)
}) }),
} else { _ => Task::ready((
Task::ready((
WorktreeInfoForSystemPrompt { WorktreeInfoForSystemPrompt {
root_name, root_name,
abs_path, abs_path,
rules_file: None, rules_file: None,
}, },
None, None,
)) )),
} }
} }
@@ -1186,7 +1188,7 @@ impl Thread {
pub fn use_pending_tools( pub fn use_pending_tools(
&mut self, &mut self,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoIterator<Item = PendingToolUse> { ) -> impl IntoIterator<Item = PendingToolUse> + use<> {
let request = self.to_completion_request(RequestKind::Chat, cx); let request = self.to_completion_request(RequestKind::Chat, cx);
let messages = Arc::new(request.messages); let messages = Arc::new(request.messages);
let pending_tool_uses = self let pending_tool_uses = self
@@ -1198,37 +1200,43 @@ impl Thread {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
for tool_use in pending_tool_uses.iter() { for tool_use in pending_tool_uses.iter() {
if let Some(tool) = self.tools.tool(&tool_use.name, cx) { match self.tools.tool(&tool_use.name, cx) {
if tool.needs_confirmation() Some(tool) => {
&& !AssistantSettings::get_global(cx).always_allow_tool_actions if tool.needs_confirmation()
{ && !AssistantSettings::get_global(cx).always_allow_tool_actions
self.tool_use.confirm_tool_use( {
tool_use.id.clone(), self.tool_use.confirm_tool_use(
tool_use.ui_text.clone(), tool_use.id.clone(),
tool_use.input.clone(), tool_use.ui_text.clone(),
messages.clone(), tool_use.input.clone(),
tool, messages.clone(),
); tool,
cx.emit(ThreadEvent::ToolConfirmationNeeded); );
} else { cx.emit(ThreadEvent::ToolConfirmationNeeded);
self.run_tool( } else {
tool_use.id.clone(), self.run_tool(
tool_use.ui_text.clone(), tool_use.id.clone(),
tool_use.input.clone(), tool_use.ui_text.clone(),
&messages, tool_use.input.clone(),
tool, &messages,
cx, tool,
); cx,
);
}
} }
} else if let Some(tool) = self.tools.tool(&tool_use.name, cx) { _ => match self.tools.tool(&tool_use.name, cx) {
self.run_tool( Some(tool) => {
tool_use.id.clone(), self.run_tool(
tool_use.ui_text.clone(), tool_use.id.clone(),
tool_use.input.clone(), tool_use.ui_text.clone(),
&messages, tool_use.input.clone(),
tool, &messages,
cx, tool,
); cx,
);
}
_ => {}
},
} }
} }

View File

@@ -166,8 +166,8 @@ impl ToolUseState {
}; };
} }
if let Some(pending_tool_use) = self.pending_tool_uses_by_id.get(&tool_use.id) { match self.pending_tool_uses_by_id.get(&tool_use.id) {
match pending_tool_use.status { Some(pending_tool_use) => match pending_tool_use.status {
PendingToolUseStatus::Idle => ToolUseStatus::Pending, PendingToolUseStatus::Idle => ToolUseStatus::Pending,
PendingToolUseStatus::NeedsConfirmation { .. } => { PendingToolUseStatus::NeedsConfirmation { .. } => {
ToolUseStatus::NeedsConfirmation ToolUseStatus::NeedsConfirmation
@@ -176,17 +176,14 @@ impl ToolUseState {
PendingToolUseStatus::Error(ref err) => { PendingToolUseStatus::Error(ref err) => {
ToolUseStatus::Error(err.clone().into()) ToolUseStatus::Error(err.clone().into())
} }
} },
} else { _ => ToolUseStatus::Pending,
ToolUseStatus::Pending
} }
})(); })();
let (icon, needs_confirmation) = if let Some(tool) = self.tools.tool(&tool_use.name, cx) let (icon, needs_confirmation) = match self.tools.tool(&tool_use.name, cx) {
{ Some(tool) => (tool.icon(), tool.needs_confirmation()),
(tool.icon(), tool.needs_confirmation()) _ => (IconName::Cog, false),
} else {
(IconName::Cog, false)
}; };
tool_uses.push(ToolUse { tool_uses.push(ToolUse {
@@ -209,10 +206,9 @@ impl ToolUseState {
input: &serde_json::Value, input: &serde_json::Value,
cx: &App, cx: &App,
) -> SharedString { ) -> SharedString {
if let Some(tool) = self.tools.tool(tool_name, cx) { match self.tools.tool(tool_name, cx) {
tool.ui_text(input).into() Some(tool) => tool.ui_text(input).into(),
} else { _ => format!("Unknown tool {tool_name:?}").into(),
format!("Unknown tool {tool_name:?}").into()
} }
} }

View File

@@ -1877,15 +1877,18 @@ impl AssistantContext {
if let Some(mut pending_patch) = pending_patch { if let Some(mut pending_patch) = pending_patch {
let patch_start = pending_patch.range.start.to_offset(buffer); let patch_start = pending_patch.range.start.to_offset(buffer);
if let Some(message) = self.message_for_offset(patch_start, cx) { match self.message_for_offset(patch_start, cx) {
if message.anchor_range.end == text::Anchor::MAX { Some(message) => {
pending_patch.range.end = text::Anchor::MAX; if message.anchor_range.end == text::Anchor::MAX {
} else { pending_patch.range.end = text::Anchor::MAX;
let message_end = buffer.anchor_after(message.offset_range.end - 1); } else {
pending_patch.range.end = message_end; let message_end = buffer.anchor_after(message.offset_range.end - 1);
pending_patch.range.end = message_end;
}
}
_ => {
pending_patch.range.end = text::Anchor::MAX;
} }
} else {
pending_patch.range.end = text::Anchor::MAX;
} }
new_patches.push(pending_patch); new_patches.push(pending_patch);
@@ -2453,7 +2456,7 @@ impl AssistantContext {
let result = stream_completion.await; let result = stream_completion.await;
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
let error_message = if let Some(error) = result.as_ref().err() { let error_message = match result.as_ref().err() { Some(error) => {
if error.is::<PaymentRequiredError>() { if error.is::<PaymentRequiredError>() {
cx.emit(ContextEvent::ShowPaymentRequiredError); cx.emit(ContextEvent::ShowPaymentRequiredError);
this.update_metadata(assistant_message_id, cx, |metadata| { this.update_metadata(assistant_message_id, cx, |metadata| {
@@ -2481,12 +2484,12 @@ impl AssistantContext {
}); });
Some(error_message) Some(error_message)
} }
} else { } _ => {
this.update_metadata(assistant_message_id, cx, |metadata| { this.update_metadata(assistant_message_id, cx, |metadata| {
metadata.status = MessageStatus::Done; metadata.status = MessageStatus::Done;
}); });
None None
}; }};
let language_name = this let language_name = this
.buffer .buffer
@@ -2631,15 +2634,16 @@ impl AssistantContext {
} }
pub fn cancel_last_assist(&mut self, cx: &mut Context<Self>) -> bool { pub fn cancel_last_assist(&mut self, cx: &mut Context<Self>) -> bool {
if let Some(pending_completion) = self.pending_completions.pop() { match self.pending_completions.pop() {
self.update_metadata(pending_completion.assistant_message_id, cx, |metadata| { Some(pending_completion) => {
if metadata.status == MessageStatus::Pending { self.update_metadata(pending_completion.assistant_message_id, cx, |metadata| {
metadata.status = MessageStatus::Canceled; if metadata.status == MessageStatus::Pending {
} metadata.status = MessageStatus::Canceled;
}); }
true });
} else { true
false }
_ => false,
} }
} }
@@ -2799,121 +2803,123 @@ impl AssistantContext {
) -> (Option<MessageAnchor>, Option<MessageAnchor>) { ) -> (Option<MessageAnchor>, Option<MessageAnchor>) {
let start_message = self.message_for_offset(range.start, cx); let start_message = self.message_for_offset(range.start, cx);
let end_message = self.message_for_offset(range.end, cx); let end_message = self.message_for_offset(range.end, cx);
if let Some((start_message, end_message)) = start_message.zip(end_message) { match start_message.zip(end_message) {
// Prevent splitting when range spans multiple messages. Some((start_message, end_message)) => {
if start_message.id != end_message.id { // Prevent splitting when range spans multiple messages.
return (None, None); if start_message.id != end_message.id {
} return (None, None);
let message = start_message;
let role = message.role;
let mut edited_buffer = false;
let mut suffix_start = None;
// TODO: why did this start panicking?
if range.start > message.offset_range.start
&& range.end < message.offset_range.end.saturating_sub(1)
{
if self.buffer.read(cx).chars_at(range.end).next() == Some('\n') {
suffix_start = Some(range.end + 1);
} else if self.buffer.read(cx).reversed_chars_at(range.end).next() == Some('\n') {
suffix_start = Some(range.end);
} }
}
let version = self.version.clone(); let message = start_message;
let suffix = if let Some(suffix_start) = suffix_start { let role = message.role;
MessageAnchor { let mut edited_buffer = false;
id: MessageId(self.next_timestamp()),
start: self.buffer.read(cx).anchor_before(suffix_start),
}
} else {
self.buffer.update(cx, |buffer, cx| {
buffer.edit([(range.end..range.end, "\n")], None, cx);
});
edited_buffer = true;
MessageAnchor {
id: MessageId(self.next_timestamp()),
start: self.buffer.read(cx).anchor_before(range.end + 1),
}
};
let suffix_metadata = MessageMetadata { let mut suffix_start = None;
role,
status: MessageStatus::Done,
timestamp: suffix.id.0,
cache: None,
};
self.insert_message(suffix.clone(), suffix_metadata.clone(), cx);
self.push_op(
ContextOperation::InsertMessage {
anchor: suffix.clone(),
metadata: suffix_metadata,
version,
},
cx,
);
let new_messages = // TODO: why did this start panicking?
if range.start == range.end || range.start == message.offset_range.start { if range.start > message.offset_range.start
(None, Some(suffix)) && range.end < message.offset_range.end.saturating_sub(1)
} else { {
let mut prefix_end = None; if self.buffer.read(cx).chars_at(range.end).next() == Some('\n') {
if range.start > message.offset_range.start suffix_start = Some(range.end + 1);
&& range.end < message.offset_range.end - 1 } else if self.buffer.read(cx).reversed_chars_at(range.end).next() == Some('\n')
{ {
if self.buffer.read(cx).chars_at(range.start).next() == Some('\n') { suffix_start = Some(range.end);
prefix_end = Some(range.start + 1);
} else if self.buffer.read(cx).reversed_chars_at(range.start).next()
== Some('\n')
{
prefix_end = Some(range.start);
}
} }
}
let version = self.version.clone(); let version = self.version.clone();
let selection = if let Some(prefix_end) = prefix_end { let suffix = if let Some(suffix_start) = suffix_start {
MessageAnchor { MessageAnchor {
id: MessageId(self.next_timestamp()), id: MessageId(self.next_timestamp()),
start: self.buffer.read(cx).anchor_before(prefix_end), start: self.buffer.read(cx).anchor_before(suffix_start),
} }
} else { } else {
self.buffer.update(cx, |buffer, cx| { self.buffer.update(cx, |buffer, cx| {
buffer.edit([(range.start..range.start, "\n")], None, cx) buffer.edit([(range.end..range.end, "\n")], None, cx);
}); });
edited_buffer = true; edited_buffer = true;
MessageAnchor { MessageAnchor {
id: MessageId(self.next_timestamp()), id: MessageId(self.next_timestamp()),
start: self.buffer.read(cx).anchor_before(range.end + 1), start: self.buffer.read(cx).anchor_before(range.end + 1),
} }
};
let selection_metadata = MessageMetadata {
role,
status: MessageStatus::Done,
timestamp: selection.id.0,
cache: None,
};
self.insert_message(selection.clone(), selection_metadata.clone(), cx);
self.push_op(
ContextOperation::InsertMessage {
anchor: selection.clone(),
metadata: selection_metadata,
version,
},
cx,
);
(Some(selection), Some(suffix))
}; };
if !edited_buffer { let suffix_metadata = MessageMetadata {
cx.emit(ContextEvent::MessagesEdited); role,
status: MessageStatus::Done,
timestamp: suffix.id.0,
cache: None,
};
self.insert_message(suffix.clone(), suffix_metadata.clone(), cx);
self.push_op(
ContextOperation::InsertMessage {
anchor: suffix.clone(),
metadata: suffix_metadata,
version,
},
cx,
);
let new_messages =
if range.start == range.end || range.start == message.offset_range.start {
(None, Some(suffix))
} else {
let mut prefix_end = None;
if range.start > message.offset_range.start
&& range.end < message.offset_range.end - 1
{
if self.buffer.read(cx).chars_at(range.start).next() == Some('\n') {
prefix_end = Some(range.start + 1);
} else if self.buffer.read(cx).reversed_chars_at(range.start).next()
== Some('\n')
{
prefix_end = Some(range.start);
}
}
let version = self.version.clone();
let selection = if let Some(prefix_end) = prefix_end {
MessageAnchor {
id: MessageId(self.next_timestamp()),
start: self.buffer.read(cx).anchor_before(prefix_end),
}
} else {
self.buffer.update(cx, |buffer, cx| {
buffer.edit([(range.start..range.start, "\n")], None, cx)
});
edited_buffer = true;
MessageAnchor {
id: MessageId(self.next_timestamp()),
start: self.buffer.read(cx).anchor_before(range.end + 1),
}
};
let selection_metadata = MessageMetadata {
role,
status: MessageStatus::Done,
timestamp: selection.id.0,
cache: None,
};
self.insert_message(selection.clone(), selection_metadata.clone(), cx);
self.push_op(
ContextOperation::InsertMessage {
anchor: selection.clone(),
metadata: selection_metadata,
version,
},
cx,
);
(Some(selection), Some(suffix))
};
if !edited_buffer {
cx.emit(ContextEvent::MessagesEdited);
}
new_messages
} }
new_messages _ => (None, None),
} else {
(None, None)
} }
} }

View File

@@ -403,23 +403,31 @@ impl ContextEditor {
if request_type == RequestType::SuggestEdits && !self.context.read(cx).contains_files(cx) { if request_type == RequestType::SuggestEdits && !self.context.read(cx).contains_files(cx) {
self.last_error = Some(AssistError::FileRequired); self.last_error = Some(AssistError::FileRequired);
cx.notify(); cx.notify();
} else if let Some(user_message) = self } else {
.context match self
.update(cx, |context, cx| context.assist(request_type, cx)) .context
{ .update(cx, |context, cx| context.assist(request_type, cx))
let new_selection = { {
let cursor = user_message Some(user_message) => {
.start let new_selection = {
.to_offset(self.context.read(cx).buffer().read(cx)); let cursor = user_message
cursor..cursor .start
}; .to_offset(self.context.read(cx).buffer().read(cx));
self.editor.update(cx, |editor, cx| { cursor..cursor
editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| { };
selections.select_ranges([new_selection]) self.editor.update(cx, |editor, cx| {
}); editor.change_selections(
}); Some(Autoscroll::fit()),
// Avoid scrolling to the new cursor position so the assistant's output is stable. window,
cx.defer_in(window, |this, _, _| this.scroll_position = None); cx,
|selections| selections.select_ranges([new_selection]),
);
});
// Avoid scrolling to the new cursor position so the assistant's output is stable.
cx.defer_in(window, |this, _, _| this.scroll_position = None);
}
_ => {}
}
} }
cx.notify(); cx.notify();
@@ -817,62 +825,68 @@ impl ContextEditor {
} }
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
if let Some(invoked_slash_command) = match self.context.read(cx).invoked_slash_command(&command_id) {
self.context.read(cx).invoked_slash_command(&command_id) Some(invoked_slash_command) => match invoked_slash_command.status {
{ InvokedSlashCommandStatus::Finished => {
if let InvokedSlashCommandStatus::Finished = invoked_slash_command.status { let buffer = editor.buffer().read(cx).snapshot(cx);
let buffer = editor.buffer().read(cx).snapshot(cx); let (&excerpt_id, _buffer_id, _buffer_snapshot) =
let (&excerpt_id, _buffer_id, _buffer_snapshot) = buffer.as_singleton().unwrap();
buffer.as_singleton().unwrap();
let start = buffer let start = buffer
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.start) .anchor_in_excerpt(excerpt_id, invoked_slash_command.range.start)
.unwrap(); .unwrap();
let end = buffer let end = buffer
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.end) .anchor_in_excerpt(excerpt_id, invoked_slash_command.range.end)
.unwrap(); .unwrap();
editor.remove_folds_with_type( editor.remove_folds_with_type(
&[start..end], &[start..end],
TypeId::of::<PendingSlashCommand>(), TypeId::of::<PendingSlashCommand>(),
false, false,
cx, cx,
); );
editor.remove_creases(
HashSet::from_iter(
self.invoked_slash_command_creases.remove(&command_id),
),
cx,
);
}
_ => {
if let hash_map::Entry::Vacant(entry) =
self.invoked_slash_command_creases.entry(command_id)
{
let buffer = editor.buffer().read(cx).snapshot(cx);
let (&excerpt_id, _buffer_id, _buffer_snapshot) =
buffer.as_singleton().unwrap();
let context = self.context.downgrade();
let crease_start = buffer
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.start)
.unwrap();
let crease_end = buffer
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.end)
.unwrap();
let crease = Crease::inline(
crease_start..crease_end,
invoked_slash_command_fold_placeholder(command_id, context),
fold_toggle("invoked-slash-command"),
|_row, _folded, _window, _cx| Empty.into_any(),
);
let crease_ids = editor.insert_creases([crease.clone()], cx);
editor.fold_creases(vec![crease], false, window, cx);
entry.insert(crease_ids[0]);
} else {
cx.notify()
}
}
},
_ => {
editor.remove_creases( editor.remove_creases(
HashSet::from_iter(self.invoked_slash_command_creases.remove(&command_id)), HashSet::from_iter(self.invoked_slash_command_creases.remove(&command_id)),
cx, cx,
); );
} else if let hash_map::Entry::Vacant(entry) = cx.notify();
self.invoked_slash_command_creases.entry(command_id)
{
let buffer = editor.buffer().read(cx).snapshot(cx);
let (&excerpt_id, _buffer_id, _buffer_snapshot) =
buffer.as_singleton().unwrap();
let context = self.context.downgrade();
let crease_start = buffer
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.start)
.unwrap();
let crease_end = buffer
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.end)
.unwrap();
let crease = Crease::inline(
crease_start..crease_end,
invoked_slash_command_fold_placeholder(command_id, context),
fold_toggle("invoked-slash-command"),
|_row, _folded, _window, _cx| Empty.into_any(),
);
let crease_ids = editor.insert_creases([crease.clone()], cx);
editor.fold_creases(vec![crease], false, window, cx);
entry.insert(crease_ids[0]);
} else {
cx.notify()
} }
} else {
editor.remove_creases(
HashSet::from_iter(self.invoked_slash_command_creases.remove(&command_id)),
cx,
);
cx.notify();
}; };
}); });
} }
@@ -958,34 +972,37 @@ impl ContextEditor {
); );
let should_refold; let should_refold;
if let Some(state) = self.patches.get_mut(&range) { match self.patches.get_mut(&range) {
if let Some(editor_state) = &state.editor { Some(state) => {
if editor_state.opened_patch != patch { if let Some(editor_state) = &state.editor {
state.update_task = Some({ if editor_state.opened_patch != patch {
let this = this.clone(); state.update_task = Some({
cx.spawn_in(window, async move |_, cx| { let this = this.clone();
Self::update_patch_editor(this.clone(), patch, cx) cx.spawn_in(window, async move |_, cx| {
.await Self::update_patch_editor(this.clone(), patch, cx)
.log_err(); .await
}) .log_err();
}); })
});
}
} }
should_refold = snapshot
.intersects_fold(patch_start.to_offset(&snapshot.buffer_snapshot));
} }
_ => {
let crease_id = editor.insert_creases([crease.clone()], cx)[0];
self.patches.insert(
range.clone(),
PatchViewState {
crease_id,
editor: None,
update_task: None,
},
);
should_refold = should_refold = true;
snapshot.intersects_fold(patch_start.to_offset(&snapshot.buffer_snapshot)); }
} else {
let crease_id = editor.insert_creases([crease.clone()], cx)[0];
self.patches.insert(
range.clone(),
PatchViewState {
crease_id,
editor: None,
update_task: None,
},
);
should_refold = true;
} }
if should_refold { if should_refold {
@@ -1175,16 +1192,20 @@ impl ContextEditor {
} }
} }
if let Some(editor) = editor { match editor {
self.workspace Some(editor) => {
.update(cx, |workspace, cx| { self.workspace
workspace.activate_item(&editor, true, false, window, cx); .update(cx, |workspace, cx| {
}) workspace.activate_item(&editor, true, false, window, cx);
.ok(); })
} else { .ok();
patch_state.update_task = Some(cx.spawn_in(window, async move |this, cx| { }
Self::open_patch_editor(this, new_patch, cx).await.log_err(); _ => {
})); patch_state.update_task =
Some(cx.spawn_in(window, async move |this, cx| {
Self::open_patch_editor(this, new_patch, cx).await.log_err();
}));
}
} }
} }
} }
@@ -1282,16 +1303,19 @@ impl ContextEditor {
.collect(); .collect();
if let Some(state) = &mut patch_state.editor { if let Some(state) = &mut patch_state.editor {
if let Some(editor) = state.editor.upgrade() { match state.editor.upgrade() {
editor.update(cx, |editor, cx| { Some(editor) => {
editor.set_title(patch.title.clone(), cx); editor.update(cx, |editor, cx| {
editor.reset_locations(locations, window, cx); editor.set_title(patch.title.clone(), cx);
resolved_patch.apply(editor, cx); editor.reset_locations(locations, window, cx);
}); resolved_patch.apply(editor, cx);
});
state.opened_patch = patch; state.opened_patch = patch;
} else { }
patch_state.editor.take(); _ => {
patch_state.editor.take();
}
} }
} }
patch_state.update_task.take(); patch_state.update_task.take();
@@ -1573,25 +1597,28 @@ impl ContextEditor {
let mut new_blocks = vec![]; let mut new_blocks = vec![];
let mut block_index_to_message = vec![]; let mut block_index_to_message = vec![];
for message in self.context.read(cx).messages(cx) { for message in self.context.read(cx).messages(cx) {
if let Some(_) = blocks_to_remove.remove(&message.id) { match blocks_to_remove.remove(&message.id) {
// This is an old message that we might modify. Some(_) => {
let Some((meta, block_id)) = old_blocks.get_mut(&message.id) else { // This is an old message that we might modify.
debug_assert!( let Some((meta, block_id)) = old_blocks.get_mut(&message.id) else {
false, debug_assert!(
"old_blocks should contain a message_id we've just removed." false,
); "old_blocks should contain a message_id we've just removed."
continue; );
}; continue;
// Should we modify it? };
let message_meta = MessageMetadata::from(&message); // Should we modify it?
if meta != &message_meta { let message_meta = MessageMetadata::from(&message);
blocks_to_replace.insert(*block_id, render_block(message_meta.clone())); if meta != &message_meta {
*meta = message_meta; blocks_to_replace.insert(*block_id, render_block(message_meta.clone()));
*meta = message_meta;
}
}
_ => {
// This is a new message.
new_blocks.push(create_block_properties(&message));
block_index_to_message.push((message.id, MessageMetadata::from(&message)));
} }
} else {
// This is a new message.
new_blocks.push(create_block_properties(&message));
block_index_to_message.push((message.id, MessageMetadata::from(&message)));
} }
} }
editor.replace_blocks(blocks_to_replace, None, cx); editor.replace_blocks(blocks_to_replace, None, cx);
@@ -2321,54 +2348,67 @@ impl ContextEditor {
) )
.into_any_element(), .into_any_element(),
) )
} else if let Some(configuration_error) = configuration_error(cx) {
let label = match configuration_error {
ConfigurationError::NoProvider => "No LLM provider selected.",
ConfigurationError::ProviderNotAuthenticated => "LLM provider is not configured.",
ConfigurationError::ProviderPendingTermsAcceptance(_) => {
"LLM provider requires accepting the Terms of Service."
}
};
Some(
h_flex()
.px_3()
.py_2()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.bg(cx.theme().colors().editor_background)
.justify_between()
.child(
h_flex()
.gap_3()
.child(
Icon::new(IconName::Warning)
.size(IconSize::Small)
.color(Color::Warning),
)
.child(Label::new(label)),
)
.child(
Button::new("open-configuration", "Configure Providers")
.size(ButtonSize::Compact)
.icon(Some(IconName::SlidersVertical))
.icon_size(IconSize::Small)
.icon_position(IconPosition::Start)
.style(ButtonStyle::Filled)
.on_click({
let focus_handle = self.focus_handle(cx).clone();
move |_event, window, cx| {
focus_handle.dispatch_action(&ShowConfiguration, window, cx);
}
}),
)
.into_any_element(),
)
} else { } else {
None match configuration_error(cx) {
Some(configuration_error) => {
let label = match configuration_error {
ConfigurationError::NoProvider => "No LLM provider selected.",
ConfigurationError::ProviderNotAuthenticated => {
"LLM provider is not configured."
}
ConfigurationError::ProviderPendingTermsAcceptance(_) => {
"LLM provider requires accepting the Terms of Service."
}
};
Some(
h_flex()
.px_3()
.py_2()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.bg(cx.theme().colors().editor_background)
.justify_between()
.child(
h_flex()
.gap_3()
.child(
Icon::new(IconName::Warning)
.size(IconSize::Small)
.color(Color::Warning),
)
.child(Label::new(label)),
)
.child(
Button::new("open-configuration", "Configure Providers")
.size(ButtonSize::Compact)
.icon(Some(IconName::SlidersVertical))
.icon_size(IconSize::Small)
.icon_position(IconPosition::Start)
.style(ButtonStyle::Filled)
.on_click({
let focus_handle = self.focus_handle(cx).clone();
move |_event, window, cx| {
focus_handle.dispatch_action(
&ShowConfiguration,
window,
cx,
);
}
}),
)
.into_any_element(),
)
}
_ => None,
}
} }
} }
fn render_send_button(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render_send_button(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement + use<> {
let focus_handle = self.focus_handle(cx).clone(); let focus_handle = self.focus_handle(cx).clone();
let (style, tooltip) = match token_state(&self.context, cx) { let (style, tooltip) = match token_state(&self.context, cx) {
@@ -2427,7 +2467,11 @@ impl ContextEditor {
}) })
} }
fn render_edit_button(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render_edit_button(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement + use<> {
let focus_handle = self.focus_handle(cx).clone(); let focus_handle = self.focus_handle(cx).clone();
let (style, tooltip) = match token_state(&self.context, cx) { let (style, tooltip) = match token_state(&self.context, cx) {
@@ -2480,7 +2524,7 @@ impl ContextEditor {
}) })
} }
fn render_inject_context_menu(&self, cx: &mut Context<Self>) -> impl IntoElement { fn render_inject_context_menu(&self, cx: &mut Context<Self>) -> impl IntoElement + use<> {
slash_command_picker::SlashCommandSelector::new( slash_command_picker::SlashCommandSelector::new(
self.slash_commands.clone(), self.slash_commands.clone(),
cx.entity().downgrade(), cx.entity().downgrade(),
@@ -2499,7 +2543,7 @@ impl ContextEditor {
) )
} }
fn render_language_model_selector(&self, cx: &mut Context<Self>) -> impl IntoElement { fn render_language_model_selector(&self, cx: &mut Context<Self>) -> impl IntoElement + use<> {
let active_model = LanguageModelRegistry::read_global(cx).active_model(); let active_model = LanguageModelRegistry::read_global(cx).active_model();
let focus_handle = self.editor().focus_handle(cx).clone(); let focus_handle = self.editor().focus_handle(cx).clone();
let model_name = match active_model { let model_name = match active_model {
@@ -3283,12 +3327,9 @@ impl FollowableItem for ContextEditor {
Some(proto::view::Variant::ContextEditor( Some(proto::view::Variant::ContextEditor(
proto::view::ContextEditor { proto::view::ContextEditor {
context_id: context.id().to_proto(), context_id: context.id().to_proto(),
editor: if let Some(proto::view::Variant::Editor(proto)) = editor: match self.editor.read(cx).to_state_proto(window, cx) {
self.editor.read(cx).to_state_proto(window, cx) Some(proto::view::Variant::Editor(proto)) => Some(proto),
{ _ => None,
Some(proto)
} else {
None
}, },
}, },
)) ))
@@ -3413,7 +3454,7 @@ impl ContextEditorToolbarItem {
pub fn render_remaining_tokens( pub fn render_remaining_tokens(
context_editor: &Entity<ContextEditor>, context_editor: &Entity<ContextEditor>,
cx: &App, cx: &App,
) -> Option<impl IntoElement> { ) -> Option<impl IntoElement + use<>> {
let context = &context_editor.read(cx).context; let context = &context_editor.read(cx).context;
let (token_count_color, token_count, max_token_count, tooltip) = match token_state(context, cx)? let (token_count_color, token_count, max_token_count, tooltip) = match token_state(context, cx)?

View File

@@ -299,13 +299,12 @@ impl ContextStore {
} }
if is_shared { if is_shared {
self.contexts.retain_mut(|context| { self.contexts.retain_mut(|context| match context.upgrade() {
if let Some(strong_context) = context.upgrade() { Some(strong_context) => {
*context = ContextHandle::Strong(strong_context); *context = ContextHandle::Strong(strong_context);
true true
} else {
false
} }
_ => false,
}); });
let remote_id = self.project.read(cx).remote_id().unwrap(); let remote_id = self.project.read(cx).remote_id().unwrap();
self.client_subscription = self self.client_subscription = self
@@ -336,8 +335,8 @@ impl ContextStore {
self.synchronize_contexts(cx); self.synchronize_contexts(cx);
} }
project::Event::DisconnectedFromHost => { project::Event::DisconnectedFromHost => {
self.contexts.retain_mut(|context| { self.contexts.retain_mut(|context| match context.upgrade() {
if let Some(strong_context) = context.upgrade() { Some(strong_context) => {
*context = ContextHandle::Weak(context.downgrade()); *context = ContextHandle::Weak(context.downgrade());
strong_context.update(cx, |context, cx| { strong_context.update(cx, |context, cx| {
if context.replica_id() != ReplicaId::default() { if context.replica_id() != ReplicaId::default() {
@@ -345,9 +344,8 @@ impl ContextStore {
} }
}); });
true true
} else {
false
} }
_ => false,
}); });
self.host_contexts.clear(); self.host_contexts.clear();
cx.notify(); cx.notify();
@@ -422,12 +420,13 @@ impl ContextStore {
.await?; .await?;
context.update(cx, |context, cx| context.apply_ops(operations, cx))?; context.update(cx, |context, cx| context.apply_ops(operations, cx))?;
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) { match this.loaded_context_for_id(&context_id, cx) {
existing_context Some(existing_context) => existing_context,
} else { _ => {
this.register_context(&context, cx); this.register_context(&context, cx);
this.synchronize_contexts(cx); this.synchronize_contexts(cx);
context context
}
} }
}) })
}) })
@@ -471,11 +470,12 @@ impl ContextStore {
) )
})?; })?;
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
if let Some(existing_context) = this.loaded_context_for_path(&path, cx) { match this.loaded_context_for_path(&path, cx) {
existing_context Some(existing_context) => existing_context,
} else { _ => {
this.register_context(&context, cx); this.register_context(&context, cx);
context context
}
} }
}) })
}) })
@@ -591,12 +591,13 @@ impl ContextStore {
.await?; .await?;
context.update(cx, |context, cx| context.apply_ops(operations, cx))?; context.update(cx, |context, cx| context.apply_ops(operations, cx))?;
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) { match this.loaded_context_for_id(&context_id, cx) {
existing_context Some(existing_context) => existing_context,
} else { _ => {
this.register_context(&context, cx); this.register_context(&context, cx);
this.synchronize_contexts(cx); this.synchronize_contexts(cx);
context context
}
} }
}) })
}) })

View File

@@ -151,29 +151,27 @@ impl SlashCommandCompletionProvider {
let mut flag = self.cancel_flag.lock(); let mut flag = self.cancel_flag.lock();
flag.store(true, SeqCst); flag.store(true, SeqCst);
*flag = new_cancel_flag.clone(); *flag = new_cancel_flag.clone();
if let Some(command) = self.slash_commands.command(command_name, cx) { match self.slash_commands.command(command_name, cx) {
let completions = command.complete_argument( Some(command) => {
arguments, let completions = command.complete_argument(
new_cancel_flag.clone(), arguments,
self.workspace.clone(), new_cancel_flag.clone(),
window, self.workspace.clone(),
cx, window,
); cx,
let command_name: Arc<str> = command_name.into(); );
let editor = self.editor.clone(); let command_name: Arc<str> = command_name.into();
let workspace = self.workspace.clone(); let editor = self.editor.clone();
let arguments = arguments.to_vec(); let workspace = self.workspace.clone();
cx.background_spawn(async move { let arguments = arguments.to_vec();
Ok(Some( cx.background_spawn(async move {
completions Ok(Some(
.await? completions
.into_iter() .await?
.map(|new_argument| { .into_iter()
let confirm = .map(|new_argument| {
editor let confirm = editor.clone().zip(workspace.clone()).map(
.clone() |(editor, workspace)| {
.zip(workspace.clone())
.map(|(editor, workspace)| {
Arc::new({ Arc::new({
let mut completed_arguments = arguments.clone(); let mut completed_arguments = arguments.clone();
if new_argument.replace_previous_arguments { if new_argument.replace_previous_arguments {
@@ -210,32 +208,33 @@ impl SlashCommandCompletionProvider {
} }
} }
}) as Arc<_> }) as Arc<_>
}); },
);
let mut new_text = new_argument.new_text.clone(); let mut new_text = new_argument.new_text.clone();
if new_argument.after_completion == AfterCompletion::Continue { if new_argument.after_completion == AfterCompletion::Continue {
new_text.push(' '); new_text.push(' ');
} }
project::Completion { project::Completion {
old_range: if new_argument.replace_previous_arguments { old_range: if new_argument.replace_previous_arguments {
argument_range.clone() argument_range.clone()
} else { } else {
last_argument_range.clone() last_argument_range.clone()
}, },
label: new_argument.label, label: new_argument.label,
icon_path: None, icon_path: None,
new_text, new_text,
documentation: None, documentation: None,
confirm, confirm,
source: CompletionSource::Custom, source: CompletionSource::Custom,
} }
}) })
.collect(), .collect(),
)) ))
}) })
} else { }
Task::ready(Ok(Some(Vec::new()))) _ => Task::ready(Ok(Some(Vec::new()))),
} }
} }
} }
@@ -333,10 +332,9 @@ impl CompletionProvider for SlashCommandCompletionProvider {
let position = position.to_point(buffer); let position = position.to_point(buffer);
let line_start = Point::new(position.row, 0); let line_start = Point::new(position.row, 0);
let mut lines = buffer.text_for_range(line_start..position).lines(); let mut lines = buffer.text_for_range(line_start..position).lines();
if let Some(line) = lines.next() { match lines.next() {
SlashCommandLine::parse(line).is_some() Some(line) => SlashCommandLine::parse(line).is_some(),
} else { _ => false,
false
} }
} }

View File

@@ -105,8 +105,8 @@ impl JsonSchema for AssistantSettingsContent {
VersionedAssistantSettingsContent::schema_name() VersionedAssistantSettingsContent::schema_name()
} }
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> Schema { fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> Schema {
VersionedAssistantSettingsContent::json_schema(gen) VersionedAssistantSettingsContent::json_schema(r#gen)
} }
fn is_referenceable() -> bool { fn is_referenceable() -> bool {
@@ -416,7 +416,7 @@ pub struct LanguageModelSelection {
pub model: String, pub model: String,
} }
fn providers_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { fn providers_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
schemars::schema::SchemaObject { schemars::schema::SchemaObject {
enum_values: Some(vec![ enum_values: Some(vec![
"anthropic".into(), "anthropic".into(),

View File

@@ -88,8 +88,8 @@ impl SlashCommand for ContextServerSlashCommand {
let server_id = self.server_id.clone(); let server_id = self.server_id.clone();
let prompt_name = self.prompt.name.clone(); let prompt_name = self.prompt.name.clone();
if let Some(server) = self.server_manager.read(cx).get_server(&server_id) { match self.server_manager.read(cx).get_server(&server_id) {
cx.foreground_executor().spawn(async move { Some(server) => cx.foreground_executor().spawn(async move {
let Some(protocol) = server.client() else { let Some(protocol) = server.client() else {
return Err(anyhow!("Context server not initialized")); return Err(anyhow!("Context server not initialized"));
}; };
@@ -118,9 +118,8 @@ impl SlashCommand for ContextServerSlashCommand {
}) })
.collect(); .collect();
Ok(completions) Ok(completions)
}) }),
} else { _ => Task::ready(Err(anyhow!("Context server not found"))),
Task::ready(Err(anyhow!("Context server not found")))
} }
} }
@@ -143,56 +142,57 @@ impl SlashCommand for ContextServerSlashCommand {
}; };
let manager = self.server_manager.read(cx); let manager = self.server_manager.read(cx);
if let Some(server) = manager.get_server(&server_id) { match manager.get_server(&server_id) {
cx.foreground_executor().spawn(async move { Some(server) => {
let Some(protocol) = server.client() else { cx.foreground_executor().spawn(async move {
return Err(anyhow!("Context server not initialized")); let Some(protocol) = server.client() else {
}; return Err(anyhow!("Context server not initialized"));
let result = protocol.run_prompt(&prompt_name, prompt_args).await?; };
let result = protocol.run_prompt(&prompt_name, prompt_args).await?;
// Check that there are only user roles // Check that there are only user roles
if result if result
.messages .messages
.iter() .iter()
.any(|msg| !matches!(msg.role, context_server::types::Role::User)) .any(|msg| !matches!(msg.role, context_server::types::Role::User))
{ {
return Err(anyhow!( return Err(anyhow!(
"Prompt contains non-user roles, which is not supported" "Prompt contains non-user roles, which is not supported"
)); ));
} }
// Extract text from user messages into a single prompt string // Extract text from user messages into a single prompt string
let mut prompt = result let mut prompt = result
.messages .messages
.into_iter() .into_iter()
.filter_map(|msg| match msg.content { .filter_map(|msg| match msg.content {
context_server::types::MessageContent::Text { text, .. } => Some(text), context_server::types::MessageContent::Text { text, .. } => Some(text),
_ => None, _ => None,
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n\n"); .join("\n\n");
// We must normalize the line endings here, since servers might return CR characters. // We must normalize the line endings here, since servers might return CR characters.
LineEnding::normalize(&mut prompt); LineEnding::normalize(&mut prompt);
Ok(SlashCommandOutput { Ok(SlashCommandOutput {
sections: vec![SlashCommandOutputSection { sections: vec![SlashCommandOutputSection {
range: 0..(prompt.len()), range: 0..(prompt.len()),
icon: IconName::ZedAssistant, icon: IconName::ZedAssistant,
label: SharedString::from( label: SharedString::from(
result result
.description .description
.unwrap_or(format!("Result from {}", prompt_name)), .unwrap_or(format!("Result from {}", prompt_name)),
), ),
metadata: None, metadata: None,
}], }],
text: prompt, text: prompt,
run_commands_in_text: false, run_commands_in_text: false,
} }
.to_event_stream()) .to_event_stream())
}) })
} else { }
Task::ready(Err(anyhow!("Context server not found"))) _ => Task::ready(Err(anyhow!("Context server not found"))),
} }
} }
} }

View File

@@ -229,19 +229,20 @@ fn collect_diagnostics(
options: Options, options: Options,
cx: &mut App, cx: &mut App,
) -> Task<Result<Option<SlashCommandOutput>>> { ) -> Task<Result<Option<SlashCommandOutput>>> {
let error_source = if let Some(path_matcher) = &options.path_matcher { let error_source = match &options.path_matcher {
debug_assert_eq!(path_matcher.sources().len(), 1); Some(path_matcher) => {
Some(path_matcher.sources().first().cloned().unwrap_or_default()) debug_assert_eq!(path_matcher.sources().len(), 1);
} else { Some(path_matcher.sources().first().cloned().unwrap_or_default())
None }
_ => None,
}; };
let glob_is_exact_file_match = if let Some(path) = options let glob_is_exact_file_match = match options
.path_matcher .path_matcher
.as_ref() .as_ref()
.and_then(|pm| pm.sources().first()) .and_then(|pm| pm.sources().first())
{ {
PathBuf::try_from(path) Some(path) => PathBuf::try_from(path)
.ok() .ok()
.and_then(|path| { .and_then(|path| {
project.read(cx).worktrees(cx).find_map(|worktree| { project.read(cx).worktrees(cx).find_map(|worktree| {
@@ -251,9 +252,8 @@ fn collect_diagnostics(
worktree.absolutize(&relative_path).ok() worktree.absolutize(&relative_path).ok()
}) })
}) })
.is_some() .is_some(),
} else { _ => false,
false
}; };
let project_handle = project.downgrade(); let project_handle = project.downgrade();

View File

@@ -221,7 +221,7 @@ fn collect_files(
project: Entity<Project>, project: Entity<Project>,
glob_inputs: &[String], glob_inputs: &[String],
cx: &mut App, cx: &mut App,
) -> impl Stream<Item = Result<SlashCommandEvent>> { ) -> impl Stream<Item = Result<SlashCommandEvent>> + use<> {
let Ok(matchers) = glob_inputs let Ok(matchers) = glob_inputs
.into_iter() .into_iter()
.map(|glob_input| { .map(|glob_input| {
@@ -285,22 +285,25 @@ fn collect_files(
if entry.is_dir() { if entry.is_dir() {
// Auto-fold directories that contain no files // Auto-fold directories that contain no files
let mut child_entries = snapshot.child_entries(&entry.path); let mut child_entries = snapshot.child_entries(&entry.path);
if let Some(child) = child_entries.next() { match child_entries.next() {
if child_entries.next().is_none() && child.kind.is_dir() { Some(child) => {
if is_top_level_directory { if child_entries.next().is_none() && child.kind.is_dir() {
is_top_level_directory = false; if is_top_level_directory {
folded_directory_names_stack.push( is_top_level_directory = false;
path_including_worktree_name.to_string_lossy().to_string(), folded_directory_names_stack.push(
); path_including_worktree_name.to_string_lossy().to_string(),
} else { );
folded_directory_names_stack.push(filename.to_string()); } else {
folded_directory_names_stack.push(filename.to_string());
}
continue;
} }
}
_ => {
// Skip empty directories
folded_directory_names_stack.clear();
continue; continue;
} }
} else {
// Skip empty directories
folded_directory_names_stack.clear();
continue;
} }
let prefix_paths = folded_directory_names_stack.drain(..).as_slice().join("/"); let prefix_paths = folded_directory_names_stack.drain(..).as_slice().join("/");
if prefix_paths.is_empty() { if prefix_paths.is_empty() {

View File

@@ -218,12 +218,9 @@ async fn project_symbols(
for symbol in symbols for symbol in symbols
.iter() .iter()
.filter(|symbol| { .filter(|symbol| match &regex {
if let Some(regex) = &regex { Some(regex) => regex.is_match(&symbol.name),
regex.is_match(&symbol.name) _ => true,
} else {
true
}
}) })
.skip(offset as usize) .skip(offset as usize)
// Take 1 more than RESULTS_PER_PAGE so we can tell if there are more results. // Take 1 more than RESULTS_PER_PAGE so we can tell if there are more results.
@@ -316,13 +313,12 @@ async fn render_outline(
.collect(); .collect();
let lang_name = lang.name(); let lang_name = lang.name();
if let Some(lsp_adapter) = registry.lsp_adapters(&lang_name).first().cloned() { match registry.lsp_adapters(&lang_name).first().cloned() {
lsp_adapter Some(lsp_adapter) => lsp_adapter
.labels_for_symbols(&entries_for_labels, lang) .labels_for_symbols(&entries_for_labels, lang)
.await .await
.ok() .ok(),
} else { _ => None,
None
} }
} }
None => None, None => None,

View File

@@ -63,16 +63,16 @@ impl Tool for DiagnosticsTool {
} }
fn ui_text(&self, input: &serde_json::Value) -> String { fn ui_text(&self, input: &serde_json::Value) -> String {
if let Some(path) = serde_json::from_value::<DiagnosticsToolInput>(input.clone()) match serde_json::from_value::<DiagnosticsToolInput>(input.clone())
.ok() .ok()
.and_then(|input| match input.path { .and_then(|input| match input.path {
Some(path) if !path.is_empty() => Some(MarkdownString::inline_code(&path)), Some(path) if !path.is_empty() => Some(MarkdownString::inline_code(&path)),
_ => None, _ => None,
}) }) {
{ Some(path) => {
format!("Check diagnostics for {path}") format!("Check diagnostics for {path}")
} else { }
"Check project diagnostics".to_string() _ => "Check project diagnostics".to_string(),
} }
} }

View File

@@ -188,7 +188,7 @@ impl Tool for FindReplaceFileTool {
}) })
.await; .await;
if let Some(diff) = result { match result { Some(diff) => {
let edit_ids = buffer.update(cx, |buffer, cx| { let edit_ids = buffer.update(cx, |buffer, cx| {
buffer.finalize_last_transaction(); buffer.finalize_last_transaction();
buffer.apply_diff(diff, false, cx); buffer.apply_diff(diff, false, cx);
@@ -205,7 +205,7 @@ impl Tool for FindReplaceFileTool {
})?.await?; })?.await?;
Ok(format!("Edited {}", input.path.display())) Ok(format!("Edited {}", input.path.display()))
} else { } _ => {
let err = buffer.read_with(cx, |buffer, _cx| { let err = buffer.read_with(cx, |buffer, _cx| {
let file_exists = buffer let file_exists = buffer
.file() .file()
@@ -224,7 +224,7 @@ impl Tool for FindReplaceFileTool {
})?; })?;
Err(err) Err(err)
} }}
}) })
} }
} }

View File

@@ -35,7 +35,7 @@ impl SoundRegistry {
cx.set_global(GlobalSoundRegistry(SoundRegistry::new(source))); cx.set_global(GlobalSoundRegistry(SoundRegistry::new(source)));
} }
pub fn get(&self, name: &str) -> Result<impl Source<Item = f32>> { pub fn get(&self, name: &str) -> Result<impl Source<Item = f32> + use<>> {
if let Some(wav) = self.cache.lock().get(name) { if let Some(wav) = self.cache.lock().get(name) {
return Ok(wav.clone()); return Ok(wav.clone());
} }

View File

@@ -201,16 +201,19 @@ pub fn check(_: &Check, window: &mut Window, cx: &mut App) {
return; return;
} }
if let Some(updater) = AutoUpdater::get(cx) { match AutoUpdater::get(cx) {
updater.update(cx, |updater, cx| updater.poll(cx)); Some(updater) => {
} else { updater.update(cx, |updater, cx| updater.poll(cx));
drop(window.prompt( }
gpui::PromptLevel::Info, _ => {
"Could not check for updates", drop(window.prompt(
Some("Auto-updates disabled for non-bundled app."), gpui::PromptLevel::Info,
&["Ok"], "Could not check for updates",
cx, Some("Auto-updates disabled for non-bundled app."),
)); &["Ok"],
cx,
));
}
} }
} }

View File

@@ -110,8 +110,8 @@ impl Render for Breadcrumbs {
} }
} }
}) })
.tooltip(move |window, cx| { .tooltip(move |window, cx| match editor.upgrade() {
if let Some(editor) = editor.upgrade() { Some(editor) => {
let focus_handle = editor.read(cx).focus_handle(cx); let focus_handle = editor.read(cx).focus_handle(cx);
Tooltip::for_action_in( Tooltip::for_action_in(
"Show Symbol Outline", "Show Symbol Outline",
@@ -120,14 +120,13 @@ impl Render for Breadcrumbs {
window, window,
cx, cx,
) )
} else {
Tooltip::for_action(
"Show Symbol Outline",
&zed_actions::outline::ToggleOutline,
window,
cx,
)
} }
_ => Tooltip::for_action(
"Show Symbol Outline",
&zed_actions::outline::ToggleOutline,
window,
cx,
),
}), }),
), ),
None => element None => element

View File

@@ -608,55 +608,58 @@ fn compute_hunks(
) -> SumTree<InternalDiffHunk> { ) -> SumTree<InternalDiffHunk> {
let mut tree = SumTree::new(&buffer); let mut tree = SumTree::new(&buffer);
if let Some((diff_base, diff_base_rope)) = diff_base { match diff_base {
let buffer_text = buffer.as_rope().to_string(); Some((diff_base, diff_base_rope)) => {
let buffer_text = buffer.as_rope().to_string();
let mut options = GitOptions::default(); let mut options = GitOptions::default();
options.context_lines(0); options.context_lines(0);
let patch = GitPatch::from_buffers( let patch = GitPatch::from_buffers(
diff_base.as_bytes(), diff_base.as_bytes(),
None, None,
buffer_text.as_bytes(), buffer_text.as_bytes(),
None, None,
Some(&mut options), Some(&mut options),
) )
.log_err(); .log_err();
// A common case in Zed is that the empty buffer is represented as just a newline, // A common case in Zed is that the empty buffer is represented as just a newline,
// but if we just compute a naive diff you get a "preserved" line in the middle, // but if we just compute a naive diff you get a "preserved" line in the middle,
// which is a bit odd. // which is a bit odd.
if buffer_text == "\n" && diff_base.ends_with("\n") && diff_base.len() > 1 { if buffer_text == "\n" && diff_base.ends_with("\n") && diff_base.len() > 1 {
tree.push(
InternalDiffHunk {
buffer_range: buffer.anchor_before(0)..buffer.anchor_before(0),
diff_base_byte_range: 0..diff_base.len() - 1,
},
&buffer,
);
return tree;
}
if let Some(patch) = patch {
let mut divergence = 0;
for hunk_index in 0..patch.num_hunks() {
let hunk = process_patch_hunk(
&patch,
hunk_index,
&diff_base_rope,
&buffer,
&mut divergence,
);
tree.push(hunk, &buffer);
}
}
}
_ => {
tree.push( tree.push(
InternalDiffHunk { InternalDiffHunk {
buffer_range: buffer.anchor_before(0)..buffer.anchor_before(0), buffer_range: Anchor::MIN..Anchor::MAX,
diff_base_byte_range: 0..diff_base.len() - 1, diff_base_byte_range: 0..0,
}, },
&buffer, &buffer,
); );
return tree;
} }
if let Some(patch) = patch {
let mut divergence = 0;
for hunk_index in 0..patch.num_hunks() {
let hunk = process_patch_hunk(
&patch,
hunk_index,
&diff_base_rope,
&buffer,
&mut divergence,
);
tree.push(hunk, &buffer);
}
}
} else {
tree.push(
InternalDiffHunk {
buffer_range: Anchor::MIN..Anchor::MAX,
diff_base_byte_range: 0..0,
},
&buffer,
);
} }
tree tree
@@ -776,7 +779,7 @@ impl BufferDiff {
language: Option<Arc<Language>>, language: Option<Arc<Language>>,
language_registry: Option<Arc<LanguageRegistry>>, language_registry: Option<Arc<LanguageRegistry>>,
cx: &mut App, cx: &mut App,
) -> impl Future<Output = BufferDiffInner> { ) -> impl Future<Output = BufferDiffInner> + use<> {
let base_text_pair; let base_text_pair;
let base_text_exists; let base_text_exists;
let base_text_snapshot; let base_text_snapshot;
@@ -818,7 +821,7 @@ impl BufferDiff {
base_text: Option<Arc<String>>, base_text: Option<Arc<String>>,
base_text_snapshot: language::BufferSnapshot, base_text_snapshot: language::BufferSnapshot,
cx: &App, cx: &App,
) -> impl Future<Output = BufferDiffInner> { ) -> impl Future<Output = BufferDiffInner> + use<> {
let base_text_exists = base_text.is_some(); let base_text_exists = base_text.is_some();
let base_text_pair = base_text.map(|text| (text, base_text_snapshot.as_rope().clone())); let base_text_pair = base_text.map(|text| (text, base_text_snapshot.as_rope().clone()));
cx.background_spawn(async move { cx.background_spawn(async move {
@@ -2071,7 +2074,7 @@ mod tests {
) )
}); });
let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot()); let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
let mut index_text = if rng.gen() { let mut index_text = if rng.r#gen() {
Rope::from(head_text.as_str()) Rope::from(head_text.as_str())
} else { } else {
working_copy.as_rope().clone() working_copy.as_rope().clone()

View File

@@ -179,23 +179,21 @@ impl ActiveCall {
return Task::ready(Ok(())); return Task::ready(Ok(()));
} }
let room = if let Some(room) = self.room().cloned() { let room = match self.room().cloned() {
Some(Task::ready(Ok(room)).shared()) Some(room) => Some(Task::ready(Ok(room)).shared()),
} else { _ => self.pending_room_creation.clone(),
self.pending_room_creation.clone()
}; };
let invite = if let Some(room) = room { let invite = match room {
cx.spawn(async move |_, cx| { Some(room) => cx.spawn(async move |_, cx| {
let room = room.await.map_err(|err| anyhow!("{:?}", err))?; let room = room.await.map_err(|err| anyhow!("{:?}", err))?;
let initial_project_id = if let Some(initial_project) = initial_project { let initial_project_id = match initial_project {
Some( Some(initial_project) => Some(
room.update(cx, |room, cx| room.share_project(initial_project, cx))? room.update(cx, |room, cx| room.share_project(initial_project, cx))?
.await?, .await?,
) ),
} else { _ => None,
None
}; };
room.update(cx, move |room, cx| { room.update(cx, move |room, cx| {
@@ -204,41 +202,42 @@ impl ActiveCall {
.await?; .await?;
anyhow::Ok(()) anyhow::Ok(())
}) }),
} else { _ => {
let client = self.client.clone(); let client = self.client.clone();
let user_store = self.user_store.clone(); let user_store = self.user_store.clone();
let room = cx let room = cx
.spawn(async move |this, cx| { .spawn(async move |this, cx| {
let create_room = async { let create_room = async {
let room = cx let room = cx
.update(|cx| { .update(|cx| {
Room::create( Room::create(
called_user_id, called_user_id,
initial_project, initial_project,
client, client,
user_store, user_store,
cx, cx,
) )
})? })?
.await?; .await?;
this.update(cx, |this, cx| this.set_room(Some(room.clone()), cx))? this.update(cx, |this, cx| this.set_room(Some(room.clone()), cx))?
.await?; .await?;
anyhow::Ok(room) anyhow::Ok(room)
}; };
let room = create_room.await; let room = create_room.await;
this.update(cx, |this, _| this.pending_room_creation = None)?; this.update(cx, |this, _| this.pending_room_creation = None)?;
room.map_err(Arc::new) room.map_err(Arc::new)
})
.shared();
self.pending_room_creation = Some(room.clone());
cx.background_spawn(async move {
room.await.map_err(|err| anyhow!("{:?}", err))?;
anyhow::Ok(())
}) })
.shared(); }
self.pending_room_creation = Some(room.clone());
cx.background_spawn(async move {
room.await.map_err(|err| anyhow!("{:?}", err))?;
anyhow::Ok(())
})
}; };
cx.spawn(async move |this, cx| { cx.spawn(async move |this, cx| {
@@ -292,10 +291,11 @@ impl ActiveCall {
return Task::ready(Err(anyhow!("cannot join while on another call"))); return Task::ready(Err(anyhow!("cannot join while on another call")));
} }
let call = if let Some(call) = self.incoming_call.0.borrow_mut().take() { let call = match self.incoming_call.0.borrow_mut().take() {
call Some(call) => call,
} else { _ => {
return Task::ready(Err(anyhow!("no incoming call"))); return Task::ready(Err(anyhow!("no incoming call")));
}
}; };
if self.pending_room_creation.is_some() { if self.pending_room_creation.is_some() {
@@ -373,11 +373,12 @@ impl ActiveCall {
Audio::end_call(cx); Audio::end_call(cx);
let channel_id = self.channel_id(cx); let channel_id = self.channel_id(cx);
if let Some((room, _)) = self.room.take() { match self.room.take() {
cx.emit(Event::RoomLeft { channel_id }); Some((room, _)) => {
room.update(cx, |room, cx| room.leave(cx)) cx.emit(Event::RoomLeft { channel_id });
} else { room.update(cx, |room, cx| room.leave(cx))
Task::ready(Ok(())) }
_ => Task::ready(Ok(())),
} }
} }
@@ -386,11 +387,12 @@ impl ActiveCall {
project: Entity<Project>, project: Entity<Project>,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Task<Result<u64>> { ) -> Task<Result<u64>> {
if let Some((room, _)) = self.room.as_ref() { match self.room.as_ref() {
self.report_call_event("Project Shared", cx); Some((room, _)) => {
room.update(cx, |room, cx| room.share_project(project, cx)) self.report_call_event("Project Shared", cx);
} else { room.update(cx, |room, cx| room.share_project(project, cx))
Task::ready(Err(anyhow!("no active call"))) }
_ => Task::ready(Err(anyhow!("no active call"))),
} }
} }
@@ -399,11 +401,12 @@ impl ActiveCall {
project: Entity<Project>, project: Entity<Project>,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Result<()> { ) -> Result<()> {
if let Some((room, _)) = self.room.as_ref() { match self.room.as_ref() {
self.report_call_event("Project Unshared", cx); Some((room, _)) => {
room.update(cx, |room, cx| room.unshare_project(project, cx)) self.report_call_event("Project Unshared", cx);
} else { room.update(cx, |room, cx| room.unshare_project(project, cx))
Err(anyhow!("no active call")) }
_ => Err(anyhow!("no active call")),
} }
} }
@@ -430,33 +433,36 @@ impl ActiveCall {
Task::ready(Ok(())) Task::ready(Ok(()))
} else { } else {
cx.notify(); cx.notify();
if let Some(room) = room { match room {
if room.read(cx).status().is_offline() { Some(room) => {
if room.read(cx).status().is_offline() {
self.room = None;
Task::ready(Ok(()))
} else {
let subscriptions = vec![
cx.observe(&room, |this, room, cx| {
if room.read(cx).status().is_offline() {
this.set_room(None, cx).detach_and_log_err(cx);
}
cx.notify();
}),
cx.subscribe(&room, |_, _, event, cx| cx.emit(event.clone())),
];
self.room = Some((room.clone(), subscriptions));
let location = self
.location
.as_ref()
.and_then(|location| location.upgrade());
let channel_id = room.read(cx).channel_id();
cx.emit(Event::RoomJoined { channel_id });
room.update(cx, |room, cx| room.set_location(location.as_ref(), cx))
}
}
_ => {
self.room = None; self.room = None;
Task::ready(Ok(())) Task::ready(Ok(()))
} else {
let subscriptions = vec![
cx.observe(&room, |this, room, cx| {
if room.read(cx).status().is_offline() {
this.set_room(None, cx).detach_and_log_err(cx);
}
cx.notify();
}),
cx.subscribe(&room, |_, _, event, cx| cx.emit(event.clone())),
];
self.room = Some((room.clone(), subscriptions));
let location = self
.location
.as_ref()
.and_then(|location| location.upgrade());
let channel_id = room.read(cx).channel_id();
cx.emit(Event::RoomJoined { channel_id });
room.update(cx, |room, cx| room.set_location(location.as_ref(), cx))
} }
} else {
self.room = None;
Task::ready(Ok(()))
} }
} }
} }

View File

@@ -96,10 +96,11 @@ impl Room {
} }
pub fn is_connected(&self, _: &App) -> bool { pub fn is_connected(&self, _: &App) -> bool {
if let Some(live_kit) = self.live_kit.as_ref() { match self.live_kit.as_ref() {
live_kit.room.connection_state() == livekit::ConnectionState::Connected Some(live_kit) => {
} else { live_kit.room.connection_state() == livekit::ConnectionState::Connected
false }
_ => false,
} }
} }
@@ -181,15 +182,16 @@ impl Room {
room room
})?; })?;
let initial_project_id = if let Some(initial_project) = initial_project { let initial_project_id = match initial_project {
let initial_project_id = room Some(initial_project) => {
.update(cx, |room, cx| { let initial_project_id = room
room.share_project(initial_project.clone(), cx) .update(cx, |room, cx| {
})? room.share_project(initial_project.clone(), cx)
.await?; })?
Some(initial_project_id) .await?;
} else { Some(initial_project_id)
None }
_ => None,
}; };
let did_join = room let did_join = room
@@ -243,7 +245,7 @@ impl Room {
} }
} }
fn app_will_quit(&mut self, cx: &mut Context<Self>) -> impl Future<Output = ()> { fn app_will_quit(&mut self, cx: &mut Context<Self>) -> impl Future<Output = ()> + use<> {
let task = if self.status.is_online() { let task = if self.status.is_online() {
let leave = self.leave_internal(cx); let leave = self.leave_internal(cx);
Some(cx.background_spawn(async move { Some(cx.background_spawn(async move {
@@ -665,7 +667,7 @@ impl Room {
Ok(()) Ok(())
} }
pub fn room_update_completed(&mut self) -> impl Future<Output = ()> { pub fn room_update_completed(&mut self) -> impl Future<Output = ()> + use<> {
let mut done_rx = self.room_update_completed_rx.clone(); let mut done_rx = self.room_update_completed_rx.clone();
async move { async move {
while let Some(result) = done_rx.next().await { while let Some(result) = done_rx.next().await {
@@ -728,14 +730,14 @@ impl Room {
} }
} }
this.joined_projects.retain(|project| { this.joined_projects
if let Some(project) = project.upgrade() { .retain(|project| match project.upgrade() {
project.update(cx, |project, cx| project.set_role(role, cx)); Some(project) => {
true project.update(cx, |project, cx| project.set_role(role, cx));
} else { true
false }
} _ => false,
}); });
} }
} else { } else {
this.local_participant.projects.clear(); this.local_participant.projects.clear();
@@ -778,20 +780,18 @@ impl Room {
} }
for unshared_project_id in old_projects.difference(&new_projects) { for unshared_project_id in old_projects.difference(&new_projects) {
this.joined_projects.retain(|project| { this.joined_projects
if let Some(project) = project.upgrade() { .retain(|project| match project.upgrade() {
project.update(cx, |project, cx| { Some(project) => project.update(cx, |project, cx| {
if project.remote_id() == Some(*unshared_project_id) { if project.remote_id() == Some(*unshared_project_id) {
project.disconnected_from_host(cx); project.disconnected_from_host(cx);
false false
} else { } else {
true true
} }
}) }),
} else { _ => false,
false });
}
});
cx.emit(Event::RemoteProjectUnshared { cx.emit(Event::RemoteProjectUnshared {
project_id: *unshared_project_id, project_id: *unshared_project_id,
}); });
@@ -800,56 +800,57 @@ impl Room {
let role = participant.role(); let role = participant.role();
let location = ParticipantLocation::from_proto(participant.location) let location = ParticipantLocation::from_proto(participant.location)
.unwrap_or(ParticipantLocation::External); .unwrap_or(ParticipantLocation::External);
if let Some(remote_participant) = match this.remote_participants.get_mut(&participant.user_id) {
this.remote_participants.get_mut(&participant.user_id) Some(remote_participant) => {
{ remote_participant.peer_id = peer_id;
remote_participant.peer_id = peer_id; remote_participant.projects = participant.projects;
remote_participant.projects = participant.projects; remote_participant.participant_index = participant_index;
remote_participant.participant_index = participant_index; if location != remote_participant.location
if location != remote_participant.location || role != remote_participant.role
|| role != remote_participant.role
{
remote_participant.location = location;
remote_participant.role = role;
cx.emit(Event::ParticipantLocationChanged {
participant_id: peer_id,
});
}
} else {
this.remote_participants.insert(
participant.user_id,
RemoteParticipant {
user: user.clone(),
participant_index,
peer_id,
projects: participant.projects,
location,
role,
muted: true,
speaking: false,
video_tracks: Default::default(),
audio_tracks: Default::default(),
},
);
Audio::play_sound(Sound::Joined, cx);
if let Some(livekit_participants) = &livekit_participants {
if let Some(livekit_participant) = livekit_participants
.get(&ParticipantIdentity(user.id.to_string()))
{ {
for publication in remote_participant.location = location;
livekit_participant.track_publications().into_values() remote_participant.role = role;
cx.emit(Event::ParticipantLocationChanged {
participant_id: peer_id,
});
}
}
_ => {
this.remote_participants.insert(
participant.user_id,
RemoteParticipant {
user: user.clone(),
participant_index,
peer_id,
projects: participant.projects,
location,
role,
muted: true,
speaking: false,
video_tracks: Default::default(),
audio_tracks: Default::default(),
},
);
Audio::play_sound(Sound::Joined, cx);
if let Some(livekit_participants) = &livekit_participants {
if let Some(livekit_participant) = livekit_participants
.get(&ParticipantIdentity(user.id.to_string()))
{ {
if let Some(track) = publication.track() { for publication in
this.livekit_room_updated( livekit_participant.track_publications().into_values()
RoomEvent::TrackSubscribed { {
track, if let Some(track) = publication.track() {
publication, this.livekit_room_updated(
participant: livekit_participant.clone(), RoomEvent::TrackSubscribed {
}, track,
cx, publication,
) participant: livekit_participant.clone(),
.warn_on_err(); },
cx,
)
.warn_on_err();
}
} }
} }
} }
@@ -1141,13 +1142,11 @@ impl Room {
Project::in_room(id, client, user_store, language_registry, fs, cx.clone()).await?; Project::in_room(id, client, user_store, language_registry, fs, cx.clone()).await?;
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.joined_projects.retain(|project| { this.joined_projects
if let Some(project) = project.upgrade() { .retain(|project| match project.upgrade() {
!project.read(cx).is_disconnected(cx) Some(project) => !project.read(cx).is_disconnected(cx),
} else { _ => false,
false });
}
});
this.joined_projects.insert(project.downgrade()); this.joined_projects.insert(project.downgrade());
})?; })?;
Ok(project) Ok(project)
@@ -1309,13 +1308,16 @@ impl Room {
return Task::ready(Err(anyhow!("room is offline"))); return Task::ready(Err(anyhow!("room is offline")));
} }
let (room, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() { let (room, publish_id) = match self.live_kit.as_mut() {
let publish_id = post_inc(&mut live_kit.next_publish_id); Some(live_kit) => {
live_kit.microphone_track = LocalTrack::Pending { publish_id }; let publish_id = post_inc(&mut live_kit.next_publish_id);
cx.notify(); live_kit.microphone_track = LocalTrack::Pending { publish_id };
(live_kit.room.clone(), publish_id) cx.notify();
} else { (live_kit.room.clone(), publish_id)
return Task::ready(Err(anyhow!("live-kit was not initialized"))); }
_ => {
return Task::ready(Err(anyhow!("live-kit was not initialized")));
}
}; };
cx.spawn(async move |this, cx| { cx.spawn(async move |this, cx| {
@@ -1326,13 +1328,11 @@ impl Room {
.as_mut() .as_mut()
.ok_or_else(|| anyhow!("live-kit was not initialized"))?; .ok_or_else(|| anyhow!("live-kit was not initialized"))?;
let canceled = if let LocalTrack::Pending { let canceled = match &live_kit.microphone_track {
publish_id: cur_publish_id, LocalTrack::Pending {
} = &live_kit.microphone_track publish_id: cur_publish_id,
{ } => *cur_publish_id != publish_id,
*cur_publish_id != publish_id _ => true,
} else {
true
}; };
match publication { match publication {
@@ -1376,13 +1376,16 @@ impl Room {
return Task::ready(Err(anyhow!("screen was already shared"))); return Task::ready(Err(anyhow!("screen was already shared")));
} }
let (participant, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() { let (participant, publish_id) = match self.live_kit.as_mut() {
let publish_id = post_inc(&mut live_kit.next_publish_id); Some(live_kit) => {
live_kit.screen_track = LocalTrack::Pending { publish_id }; let publish_id = post_inc(&mut live_kit.next_publish_id);
cx.notify(); live_kit.screen_track = LocalTrack::Pending { publish_id };
(live_kit.room.local_participant(), publish_id) cx.notify();
} else { (live_kit.room.local_participant(), publish_id)
return Task::ready(Err(anyhow!("live-kit was not initialized"))); }
_ => {
return Task::ready(Err(anyhow!("live-kit was not initialized")));
}
}; };
let sources = cx.screen_capture_sources(); let sources = cx.screen_capture_sources();
@@ -1399,13 +1402,11 @@ impl Room {
.as_mut() .as_mut()
.ok_or_else(|| anyhow!("live-kit was not initialized"))?; .ok_or_else(|| anyhow!("live-kit was not initialized"))?;
let canceled = if let LocalTrack::Pending { let canceled = match &live_kit.screen_track {
publish_id: cur_publish_id, LocalTrack::Pending {
} = &live_kit.screen_track publish_id: cur_publish_id,
{ } => *cur_publish_id != publish_id,
*cur_publish_id != publish_id _ => true,
} else {
true
}; };
match publication { match publication {

View File

@@ -183,7 +183,7 @@ impl ChannelChat {
let channel_id = self.channel_id; let channel_id = self.channel_id;
let pending_id = ChannelMessageId::Pending(post_inc(&mut self.next_pending_message_id)); let pending_id = ChannelMessageId::Pending(post_inc(&mut self.next_pending_message_id));
let nonce = self.rng.gen(); let nonce = self.rng.r#gen();
self.insert_messages( self.insert_messages(
SumTree::from_item( SumTree::from_item(
ChannelMessage { ChannelMessage {
@@ -257,7 +257,7 @@ impl ChannelChat {
cx, cx,
); );
let nonce: u128 = self.rng.gen(); let nonce: u128 = self.rng.r#gen();
let request = self.rpc.request(proto::UpdateChannelMessage { let request = self.rpc.request(proto::UpdateChannelMessage {
channel_id: self.channel_id.0, channel_id: self.channel_id.0,

View File

@@ -329,17 +329,16 @@ impl ChannelStore {
.request(proto::GetChannelMessagesById { message_ids }), .request(proto::GetChannelMessagesById { message_ids }),
) )
}; };
cx.spawn(async move |this, cx| { cx.spawn(async move |this, cx| match request {
if let Some(request) = request { Some(request) => {
let response = request.await?; let response = request.await?;
let this = this let this = this
.upgrade() .upgrade()
.ok_or_else(|| anyhow!("channel store dropped"))?; .ok_or_else(|| anyhow!("channel store dropped"))?;
let user_store = this.update(cx, |this, _| this.user_store.clone())?; let user_store = this.update(cx, |this, _| this.user_store.clone())?;
ChannelMessage::from_proto_vec(response.messages, &user_store, cx).await ChannelMessage::from_proto_vec(response.messages, &user_store, cx).await
} else {
Ok(Vec::new())
} }
_ => Ok(Vec::new()),
}) })
} }
@@ -465,14 +464,15 @@ impl ChannelStore {
let task = loop { let task = loop {
match get_map(self).entry(channel_id) { match get_map(self).entry(channel_id) {
hash_map::Entry::Occupied(e) => match e.get() { hash_map::Entry::Occupied(e) => match e.get() {
OpenEntityHandle::Open(entity) => { OpenEntityHandle::Open(entity) => match entity.upgrade() {
if let Some(entity) = entity.upgrade() { Some(entity) => {
break Task::ready(Ok(entity)).shared(); break Task::ready(Ok(entity)).shared();
} else { }
_ => {
get_map(self).remove(&channel_id); get_map(self).remove(&channel_id);
continue; continue;
} }
} },
OpenEntityHandle::Loading(task) => { OpenEntityHandle::Loading(task) => {
break task.clone(); break task.clone();
} }
@@ -824,7 +824,10 @@ impl ChannelStore {
}) })
} }
pub fn remove_channel(&self, channel_id: ChannelId) -> impl Future<Output = Result<()>> { pub fn remove_channel(
&self,
channel_id: ChannelId,
) -> impl Future<Output = Result<()>> + use<> {
let client = self.client.clone(); let client = self.client.clone();
async move { async move {
client client

View File

@@ -228,13 +228,16 @@ fn main() -> Result<()> {
paths.push(file.path().to_string_lossy().to_string()); paths.push(file.path().to_string_lossy().to_string());
let (file, _) = file.keep()?; let (file, _) = file.keep()?;
stdin_tmp_file = Some(file); stdin_tmp_file = Some(file);
} else if let Some(file) = anonymous_fd(path) {
let tmp_file = NamedTempFile::new()?;
paths.push(tmp_file.path().to_string_lossy().to_string());
let (tmp_file, _) = tmp_file.keep()?;
anonymous_fd_tmp_files.push((file, tmp_file));
} else { } else {
paths.push(parse_path_with_position(path)?) match anonymous_fd(path) {
Some(file) => {
let tmp_file = NamedTempFile::new()?;
paths.push(tmp_file.path().to_string_lossy().to_string());
let (tmp_file, _) = tmp_file.keep()?;
anonymous_fd_tmp_files.push((file, tmp_file));
}
_ => paths.push(parse_path_with_position(path)?),
}
} }
} }
@@ -775,7 +778,7 @@ mod mac_os {
} }
impl Detect { impl Detect {
pub fn detect(path: Option<&Path>) -> anyhow::Result<impl InstalledApp> { pub fn detect(path: Option<&Path>) -> anyhow::Result<impl InstalledApp + use<>> {
let bundle_path = if let Some(bundle_path) = path { let bundle_path = if let Some(bundle_path) = path {
bundle_path bundle_path
.canonicalize() .canonicalize()

View File

@@ -618,10 +618,9 @@ impl Client {
} }
pub fn peer_id(&self) -> Option<PeerId> { pub fn peer_id(&self) -> Option<PeerId> {
if let Status::Connected { peer_id, .. } = &*self.status().borrow() { match &*self.status().borrow() {
Some(*peer_id) Status::Connected { peer_id, .. } => Some(*peer_id),
} else { _ => None,
None
} }
} }
@@ -1024,7 +1023,7 @@ impl Client {
&self, &self,
http: Arc<HttpClientWithUrl>, http: Arc<HttpClientWithUrl>,
release_channel: Option<ReleaseChannel>, release_channel: Option<ReleaseChannel>,
) -> impl Future<Output = Result<url::Url>> { ) -> impl Future<Output = Result<url::Url>> + use<> {
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
let url_override = self.rpc_url.read().clone(); let url_override = self.rpc_url.read().clone();
@@ -1429,10 +1428,9 @@ impl Client {
} }
fn connection_id(&self) -> Result<ConnectionId> { fn connection_id(&self) -> Result<ConnectionId> {
if let Status::Connected { connection_id, .. } = *self.status().borrow() { match *self.status().borrow() {
Ok(connection_id) Status::Connected { connection_id, .. } => Ok(connection_id),
} else { _ => Err(anyhow!("not connected")),
Err(anyhow!("not connected"))
} }
} }
@@ -1444,7 +1442,7 @@ impl Client {
pub fn request<T: RequestMessage>( pub fn request<T: RequestMessage>(
&self, &self,
request: T, request: T,
) -> impl Future<Output = Result<T::Response>> { ) -> impl Future<Output = Result<T::Response>> + use<T> {
self.request_envelope(request) self.request_envelope(request)
.map_ok(|envelope| envelope.payload) .map_ok(|envelope| envelope.payload)
} }
@@ -1452,7 +1450,8 @@ impl Client {
pub fn request_stream<T: RequestMessage>( pub fn request_stream<T: RequestMessage>(
&self, &self,
request: T, request: T,
) -> impl Future<Output = Result<impl Stream<Item = Result<T::Response>>>> { ) -> impl Future<Output = Result<impl Stream<Item = Result<T::Response>> + use<T>>> + use<T>
{
let client_id = self.id.load(Ordering::SeqCst); let client_id = self.id.load(Ordering::SeqCst);
log::debug!( log::debug!(
"rpc request start. client_id:{}. name:{}", "rpc request start. client_id:{}. name:{}",
@@ -1476,7 +1475,7 @@ impl Client {
pub fn request_envelope<T: RequestMessage>( pub fn request_envelope<T: RequestMessage>(
&self, &self,
request: T, request: T,
) -> impl Future<Output = Result<TypedEnvelope<T::Response>>> { ) -> impl Future<Output = Result<TypedEnvelope<T::Response>>> + use<T> {
let client_id = self.id(); let client_id = self.id();
log::debug!( log::debug!(
"rpc request start. client_id:{}. name:{}", "rpc request start. client_id:{}. name:{}",
@@ -1501,7 +1500,7 @@ impl Client {
&self, &self,
envelope: proto::Envelope, envelope: proto::Envelope,
request_type: &'static str, request_type: &'static str,
) -> impl Future<Output = Result<proto::Envelope>> { ) -> impl Future<Output = Result<proto::Envelope>> + use<> {
let client_id = self.id(); let client_id = self.id();
log::debug!( log::debug!(
"rpc request start. client_id:{}. name:{}", "rpc request start. client_id:{}. name:{}",
@@ -1528,44 +1527,47 @@ impl Client {
let type_name = message.payload_type_name(); let type_name = message.payload_type_name();
let original_sender_id = message.original_sender_id(); let original_sender_id = message.original_sender_id();
if let Some(future) = ProtoMessageHandlerSet::handle_message( match ProtoMessageHandlerSet::handle_message(
&self.handler_set, &self.handler_set,
message, message,
self.clone().into(), self.clone().into(),
cx.clone(), cx.clone(),
) { ) {
let client_id = self.id(); Some(future) => {
log::debug!( let client_id = self.id();
"rpc message received. client_id:{}, sender_id:{:?}, type:{}", log::debug!(
client_id, "rpc message received. client_id:{}, sender_id:{:?}, type:{}",
original_sender_id, client_id,
type_name original_sender_id,
); type_name
cx.spawn(async move |_| match future.await { );
Ok(()) => { cx.spawn(async move |_| match future.await {
log::debug!( Ok(()) => {
"rpc message handled. client_id:{}, sender_id:{:?}, type:{}", log::debug!(
client_id, "rpc message handled. client_id:{}, sender_id:{:?}, type:{}",
original_sender_id, client_id,
type_name original_sender_id,
); type_name
} );
Err(error) => { }
log::error!( Err(error) => {
log::error!(
"error handling message. client_id:{}, sender_id:{:?}, type:{}, error:{:?}", "error handling message. client_id:{}, sender_id:{:?}, type:{}, error:{:?}",
client_id, client_id,
original_sender_id, original_sender_id,
type_name, type_name,
error error
); );
} }
}) })
.detach(); .detach();
} else { }
log::info!("unhandled message {}", type_name); _ => {
self.peer log::info!("unhandled message {}", type_name);
.respond_with_unhandled_message(sender_id.into(), request_id, type_name) self.peer
.log_err(); .respond_with_unhandled_message(sender_id.into(), request_id, type_name)
.log_err();
}
} }
} }

View File

@@ -273,7 +273,7 @@ impl Telemetry {
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
fn shutdown_telemetry(self: &Arc<Self>) -> impl Future<Output = ()> { fn shutdown_telemetry(self: &Arc<Self>) -> impl Future<Output = ()> + use<> {
Task::ready(()) Task::ready(())
} }

View File

@@ -171,11 +171,13 @@ impl UserStore {
_maintain_contacts: cx.spawn(async move |this, cx| { _maintain_contacts: cx.spawn(async move |this, cx| {
let _subscriptions = rpc_subscriptions; let _subscriptions = rpc_subscriptions;
while let Some(message) = update_contacts_rx.next().await { while let Some(message) = update_contacts_rx.next().await {
if let Ok(task) = this.update(cx, |this, cx| this.update_contacts(message, cx)) match this.update(cx, |this, cx| this.update_contacts(message, cx)) {
{ Ok(task) => {
task.log_err().await; task.log_err().await;
} else { }
break; _ => {
break;
}
} }
} }
}), }),
@@ -191,12 +193,13 @@ impl UserStore {
match status { match status {
Status::Connected { .. } => { Status::Connected { .. } => {
if let Some(user_id) = client.user_id() { if let Some(user_id) = client.user_id() {
let fetch_user = if let Ok(fetch_user) = let fetch_user = match this
this.update(cx, |this, cx| this.get_user(user_id, cx).log_err()) .update(cx, |this, cx| this.get_user(user_id, cx).log_err())
{ {
fetch_user Ok(fetch_user) => fetch_user,
} else { _ => {
break; break;
}
}; };
let fetch_private_user_info = let fetch_private_user_info =
client.request(proto::GetPrivateUserInfo {}).log_err(); client.request(proto::GetPrivateUserInfo {}).log_err();
@@ -581,7 +584,7 @@ impl UserStore {
}) })
} }
pub fn clear_contacts(&self) -> impl Future<Output = ()> { pub fn clear_contacts(&self) -> impl Future<Output = ()> + use<> {
let (tx, mut rx) = postage::barrier::channel(); let (tx, mut rx) = postage::barrier::channel();
self.update_contacts_tx self.update_contacts_tx
.unbounded_send(UpdateContacts::Clear(tx)) .unbounded_send(UpdateContacts::Clear(tx))
@@ -591,7 +594,7 @@ impl UserStore {
} }
} }
pub fn contact_updates_done(&self) -> impl Future<Output = ()> { pub fn contact_updates_done(&self) -> impl Future<Output = ()> + use<> {
let (tx, mut rx) = postage::barrier::channel(); let (tx, mut rx) = postage::barrier::channel();
self.update_contacts_tx self.update_contacts_tx
.unbounded_send(UpdateContacts::Wait(tx)) .unbounded_send(UpdateContacts::Wait(tx))
@@ -703,8 +706,8 @@ impl UserStore {
}; };
let client = self.client.clone(); let client = self.client.clone();
cx.spawn(async move |this, cx| { cx.spawn(async move |this, cx| match client.upgrade() {
if let Some(client) = client.upgrade() { Some(client) => {
let response = client let response = client
.request(proto::AcceptTermsOfService {}) .request(proto::AcceptTermsOfService {})
.await .await
@@ -714,9 +717,8 @@ impl UserStore {
this.set_current_user_accepted_tos_at(Some(response.accepted_tos_at)); this.set_current_user_accepted_tos_at(Some(response.accepted_tos_at));
cx.emit(Event::PrivateUserInfoUpdated); cx.emit(Event::PrivateUserInfoUpdated);
}) })
} else {
Err(anyhow!("client not found"))
} }
_ => Err(anyhow!("client not found")),
}) })
} }
@@ -732,15 +734,14 @@ impl UserStore {
cx: &Context<Self>, cx: &Context<Self>,
) -> Task<Result<Vec<Arc<User>>>> { ) -> Task<Result<Vec<Arc<User>>>> {
let client = self.client.clone(); let client = self.client.clone();
cx.spawn(async move |this, cx| { cx.spawn(async move |this, cx| match client.upgrade() {
if let Some(rpc) = client.upgrade() { Some(rpc) => {
let response = rpc.request(request).await.context("error loading users")?; let response = rpc.request(request).await.context("error loading users")?;
let users = response.users; let users = response.users;
this.update(cx, |this, _| this.insert(users)) this.update(cx, |this, _| this.insert(users))
} else {
Ok(Vec::new())
} }
_ => Ok(Vec::new()),
}) })
} }

View File

@@ -219,13 +219,16 @@ async fn create_access_token(
let mut impersonated_user_id = None; let mut impersonated_user_id = None;
if let Some(impersonate) = params.impersonate { if let Some(impersonate) = params.impersonate {
if user.admin { if user.admin {
if let Some(impersonated_user) = app.db.get_user_by_github_login(&impersonate).await? { match app.db.get_user_by_github_login(&impersonate).await? {
impersonated_user_id = Some(impersonated_user.id); Some(impersonated_user) => {
} else { impersonated_user_id = Some(impersonated_user.id);
return Err(Error::http( }
StatusCode::UNPROCESSABLE_ENTITY, _ => {
format!("user {impersonate} does not exist"), return Err(Error::http(
)); StatusCode::UNPROCESSABLE_ENTITY,
format!("user {impersonate} does not exist"),
));
}
} }
} else { } else {
return Err(Error::http( return Err(Error::http(

View File

@@ -103,8 +103,8 @@ async fn update_billing_preferences(
let max_monthly_llm_usage_spending_in_cents = let max_monthly_llm_usage_spending_in_cents =
body.max_monthly_llm_usage_spending_in_cents.max(0); body.max_monthly_llm_usage_spending_in_cents.max(0);
let billing_preferences = let billing_preferences = match app.db.get_billing_preferences(user.id).await? {
if let Some(_billing_preferences) = app.db.get_billing_preferences(user.id).await? { Some(_billing_preferences) => {
app.db app.db
.update_billing_preferences( .update_billing_preferences(
user.id, user.id,
@@ -115,7 +115,8 @@ async fn update_billing_preferences(
}, },
) )
.await? .await?
} else { }
_ => {
app.db app.db
.create_billing_preferences( .create_billing_preferences(
user.id, user.id,
@@ -124,7 +125,8 @@ async fn update_billing_preferences(
}, },
) )
.await? .await?
}; }
};
SnowflakeRow::new( SnowflakeRow::new(
"Spend Limit Updated", "Spend Limit Updated",
@@ -624,28 +626,31 @@ async fn handle_customer_event(
return Ok(()); return Ok(());
}; };
if let Some(existing_customer) = app match app
.db .db
.get_billing_customer_by_stripe_customer_id(&customer.id) .get_billing_customer_by_stripe_customer_id(&customer.id)
.await? .await?
{ {
app.db Some(existing_customer) => {
.update_billing_customer( app.db
existing_customer.id, .update_billing_customer(
&UpdateBillingCustomerParams { existing_customer.id,
// For now we just leave the information as-is, as it is not &UpdateBillingCustomerParams {
// likely to change. // For now we just leave the information as-is, as it is not
..Default::default() // likely to change.
}, ..Default::default()
) },
.await?; )
} else { .await?;
app.db }
.create_billing_customer(&CreateBillingCustomerParams { _ => {
user_id: user.id, app.db
stripe_customer_id: customer.id.to_string(), .create_billing_customer(&CreateBillingCustomerParams {
}) user_id: user.id,
.await?; stripe_customer_id: customer.id.to_string(),
})
.await?;
}
} }
Ok(()) Ok(())
@@ -689,72 +694,75 @@ async fn handle_customer_subscription_event(
.await?; .await?;
} }
if let Some(existing_subscription) = app match app
.db .db
.get_billing_subscription_by_stripe_subscription_id(&subscription.id) .get_billing_subscription_by_stripe_subscription_id(&subscription.id)
.await? .await?
{ {
app.db Some(existing_subscription) => {
.update_billing_subscription( app.db
existing_subscription.id, .update_billing_subscription(
&UpdateBillingSubscriptionParams { existing_subscription.id,
billing_customer_id: ActiveValue::set(billing_customer.id), &UpdateBillingSubscriptionParams {
stripe_subscription_id: ActiveValue::set(subscription.id.to_string()), billing_customer_id: ActiveValue::set(billing_customer.id),
stripe_subscription_status: ActiveValue::set(subscription.status.into()), stripe_subscription_id: ActiveValue::set(subscription.id.to_string()),
stripe_cancel_at: ActiveValue::set( stripe_subscription_status: ActiveValue::set(subscription.status.into()),
subscription stripe_cancel_at: ActiveValue::set(
.cancel_at subscription
.and_then(|cancel_at| DateTime::from_timestamp(cancel_at, 0)) .cancel_at
.map(|time| time.naive_utc()), .and_then(|cancel_at| DateTime::from_timestamp(cancel_at, 0))
), .map(|time| time.naive_utc()),
stripe_cancellation_reason: ActiveValue::set( ),
subscription stripe_cancellation_reason: ActiveValue::set(
.cancellation_details subscription
.and_then(|details| details.reason) .cancellation_details
.map(|reason| reason.into()), .and_then(|details| details.reason)
), .map(|reason| reason.into()),
}, ),
) },
.await?; )
} else { .await?;
// If the user already has an active billing subscription, ignore the }
// event and return an `Ok` to signal that it was processed _ => {
// successfully. // If the user already has an active billing subscription, ignore the
// // event and return an `Ok` to signal that it was processed
// There is the possibility that this could cause us to not create a // successfully.
// subscription in the following scenario: //
// // There is the possibility that this could cause us to not create a
// 1. User has an active subscription A // subscription in the following scenario:
// 2. User cancels subscription A //
// 3. User creates a new subscription B // 1. User has an active subscription A
// 4. We process the new subscription B before the cancellation of subscription A // 2. User cancels subscription A
// 5. User ends up with no subscriptions // 3. User creates a new subscription B
// // 4. We process the new subscription B before the cancellation of subscription A
// In theory this situation shouldn't arise as we try to process the events in the order they occur. // 5. User ends up with no subscriptions
if app //
.db // In theory this situation shouldn't arise as we try to process the events in the order they occur.
.has_active_billing_subscription(billing_customer.user_id) if app
.await? .db
{ .has_active_billing_subscription(billing_customer.user_id)
log::info!( .await?
{
log::info!(
"user {user_id} already has an active subscription, skipping creation of subscription {subscription_id}", "user {user_id} already has an active subscription, skipping creation of subscription {subscription_id}",
user_id = billing_customer.user_id, user_id = billing_customer.user_id,
subscription_id = subscription.id subscription_id = subscription.id
); );
return Ok(()); return Ok(());
} }
app.db app.db
.create_billing_subscription(&CreateBillingSubscriptionParams { .create_billing_subscription(&CreateBillingSubscriptionParams {
billing_customer_id: billing_customer.id, billing_customer_id: billing_customer.id,
stripe_subscription_id: subscription.id.to_string(), stripe_subscription_id: subscription.id.to_string(),
stripe_subscription_status: subscription.status.into(), stripe_subscription_status: subscription.status.into(),
stripe_cancellation_reason: subscription stripe_cancellation_reason: subscription
.cancellation_details .cancellation_details
.and_then(|details| details.reason) .and_then(|details| details.reason)
.map(|reason| reason.into()), .map(|reason| reason.into()),
}) })
.await?; .await?;
}
} }
// When the user's subscription changes, we want to refresh their LLM tokens // When the user's subscription changes, we want to refresh their LLM tokens

View File

@@ -47,10 +47,15 @@ impl IpsFile {
Some(panic_message) => format!("Panic `{}`", panic_message), Some(panic_message) => format!("Panic `{}`", panic_message),
None => "Crash `Abort trap: 6` (possible panic)".into(), None => "Crash `Abort trap: 6` (possible panic)".into(),
} }
} else if let Some(msg) = &self.body.exception.message {
format!("Exception `{}`", msg)
} else { } else {
format!("Crash `{}`", self.body.termination.indicator) match &self.body.exception.message {
Some(msg) => {
format!("Exception `{}`", msg)
}
_ => {
format!("Crash `{}`", self.body.termination.indicator)
}
}
}; };
if let Some(thread) = self.faulting_thread() { if let Some(thread) = self.faulting_thread() {
if let Some(queue) = thread.queue.as_ref() { if let Some(queue) = thread.queue.as_ref() {
@@ -81,10 +86,13 @@ impl IpsFile {
return None; return None;
} }
Some(format!("{:#}", rustc_demangle::demangle(name))) Some(format!("{:#}", rustc_demangle::demangle(name)))
} else if let Some(image) = self.body.used_images.get(frame.image_index) {
Some(image.name.clone().unwrap_or("<unknown-image>".into()))
} else { } else {
Some("<unknown>".into()) match self.body.used_images.get(frame.image_index) {
Some(image) => {
Some(image.name.clone().unwrap_or("<unknown-image>".into()))
}
_ => Some("<unknown>".into()),
}
} }
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View File

@@ -105,47 +105,52 @@ impl Database {
let mut accept_invite_result = None; let mut accept_invite_result = None;
if role.is_none() { if role.is_none() {
if let Some(invitation) = self match self
.pending_invite_for_channel(&channel, user_id, &tx) .pending_invite_for_channel(&channel, user_id, &tx)
.await? .await?
{ {
// note, this may be a parent channel Some(invitation) => {
role = Some(invitation.role); // note, this may be a parent channel
channel_member::Entity::update(channel_member::ActiveModel { role = Some(invitation.role);
accepted: ActiveValue::Set(true), channel_member::Entity::update(channel_member::ActiveModel {
..invitation.into_active_model() accepted: ActiveValue::Set(true),
}) ..invitation.into_active_model()
.exec(&*tx) })
.await?; .exec(&*tx)
.await?;
accept_invite_result = Some( accept_invite_result = Some(
self.calculate_membership_updated(&channel, user_id, &tx) self.calculate_membership_updated(&channel, user_id, &tx)
.await?, .await?,
); );
debug_assert!( debug_assert!(
self.channel_role_for_user(&channel, user_id, &tx).await? == role self.channel_role_for_user(&channel, user_id, &tx).await? == role
); );
} else if channel.visibility == ChannelVisibility::Public { }
role = Some(ChannelRole::Guest); _ => {
channel_member::Entity::insert(channel_member::ActiveModel { if channel.visibility == ChannelVisibility::Public {
id: ActiveValue::NotSet, role = Some(ChannelRole::Guest);
channel_id: ActiveValue::Set(channel.root_id()), channel_member::Entity::insert(channel_member::ActiveModel {
user_id: ActiveValue::Set(user_id), id: ActiveValue::NotSet,
accepted: ActiveValue::Set(true), channel_id: ActiveValue::Set(channel.root_id()),
role: ActiveValue::Set(ChannelRole::Guest), user_id: ActiveValue::Set(user_id),
}) accepted: ActiveValue::Set(true),
.exec(&*tx) role: ActiveValue::Set(ChannelRole::Guest),
.await?; })
.exec(&*tx)
.await?;
accept_invite_result = Some( accept_invite_result = Some(
self.calculate_membership_updated(&channel, user_id, &tx) self.calculate_membership_updated(&channel, user_id, &tx)
.await?, .await?,
); );
debug_assert!( debug_assert!(
self.channel_role_for_user(&channel, user_id, &tx).await? == role self.channel_role_for_user(&channel, user_id, &tx).await? == role
); );
}
}
} }
} }

View File

@@ -101,12 +101,15 @@ impl Database {
} }
if let Some(wasm_api_version) = version.wasm_api_version.as_ref() { if let Some(wasm_api_version) = version.wasm_api_version.as_ref() {
if let Some(version) = SemanticVersion::from_str(wasm_api_version).log_err() { match SemanticVersion::from_str(wasm_api_version).log_err() {
if !constraints.wasm_api_versions.contains(&version) { Some(version) => {
if !constraints.wasm_api_versions.contains(&version) {
continue;
}
}
_ => {
continue; continue;
} }
} else {
continue;
} }
} }
} }

View File

@@ -192,28 +192,29 @@ impl Database {
response: Option<bool>, response: Option<bool>,
tx: &DatabaseTransaction, tx: &DatabaseTransaction,
) -> Result<Option<(UserId, proto::Notification)>> { ) -> Result<Option<(UserId, proto::Notification)>> {
if let Some(id) = self match self
.find_notification(recipient_id, notification, tx) .find_notification(recipient_id, notification, tx)
.await? .await?
{ {
let row = notification::Entity::update(notification::ActiveModel { Some(id) => {
id: ActiveValue::Unchanged(id), let row = notification::Entity::update(notification::ActiveModel {
recipient_id: ActiveValue::Unchanged(recipient_id), id: ActiveValue::Unchanged(id),
is_read: ActiveValue::Set(true), recipient_id: ActiveValue::Unchanged(recipient_id),
response: if let Some(response) = response { is_read: ActiveValue::Set(true),
ActiveValue::Set(Some(response)) response: if let Some(response) = response {
} else { ActiveValue::Set(Some(response))
ActiveValue::NotSet } else {
}, ActiveValue::NotSet
..Default::default() },
}) ..Default::default()
.exec(tx) })
.await?; .exec(tx)
Ok(model_to_proto(self, row) .await?;
.map(|notification| (recipient_id, notification)) Ok(model_to_proto(self, row)
.ok()) .map(|notification| (recipient_id, notification))
} else { .ok())
Ok(None) }
_ => Ok(None),
} }
} }

View File

@@ -132,50 +132,54 @@ impl Database {
initial_channel_id: Option<ChannelId>, initial_channel_id: Option<ChannelId>,
tx: &DatabaseTransaction, tx: &DatabaseTransaction,
) -> Result<User> { ) -> Result<User> {
if let Some(existing_user) = self match self
.get_user_by_github_user_id_or_github_login(github_user_id, github_login, tx) .get_user_by_github_user_id_or_github_login(github_user_id, github_login, tx)
.await? .await?
{ {
let mut existing_user = existing_user.into_active_model(); Some(existing_user) => {
existing_user.github_login = ActiveValue::set(github_login.into()); let mut existing_user = existing_user.into_active_model();
existing_user.github_user_created_at = ActiveValue::set(Some(github_user_created_at)); existing_user.github_login = ActiveValue::set(github_login.into());
existing_user.github_user_created_at =
ActiveValue::set(Some(github_user_created_at));
if let Some(github_email) = github_email { if let Some(github_email) = github_email {
existing_user.email_address = ActiveValue::set(Some(github_email.into())); existing_user.email_address = ActiveValue::set(Some(github_email.into()));
}
if let Some(github_name) = github_name {
existing_user.name = ActiveValue::set(Some(github_name.into()));
}
Ok(existing_user.update(tx).await?)
} }
_ => {
if let Some(github_name) = github_name { let user = user::Entity::insert(user::ActiveModel {
existing_user.name = ActiveValue::set(Some(github_name.into())); email_address: ActiveValue::set(github_email.map(|email| email.into())),
} name: ActiveValue::set(github_name.map(|name| name.into())),
github_login: ActiveValue::set(github_login.into()),
Ok(existing_user.update(tx).await?) github_user_id: ActiveValue::set(github_user_id),
} else { github_user_created_at: ActiveValue::set(Some(github_user_created_at)),
let user = user::Entity::insert(user::ActiveModel { admin: ActiveValue::set(false),
email_address: ActiveValue::set(github_email.map(|email| email.into())), invite_count: ActiveValue::set(0),
name: ActiveValue::set(github_name.map(|name| name.into())), invite_code: ActiveValue::set(None),
github_login: ActiveValue::set(github_login.into()), metrics_id: ActiveValue::set(Uuid::new_v4()),
github_user_id: ActiveValue::set(github_user_id), ..Default::default()
github_user_created_at: ActiveValue::set(Some(github_user_created_at)),
admin: ActiveValue::set(false),
invite_count: ActiveValue::set(0),
invite_code: ActiveValue::set(None),
metrics_id: ActiveValue::set(Uuid::new_v4()),
..Default::default()
})
.exec_with_returning(tx)
.await?;
if let Some(channel_id) = initial_channel_id {
channel_member::Entity::insert(channel_member::ActiveModel {
id: ActiveValue::NotSet,
channel_id: ActiveValue::Set(channel_id),
user_id: ActiveValue::Set(user.id),
accepted: ActiveValue::Set(true),
role: ActiveValue::Set(ChannelRole::Guest),
}) })
.exec(tx) .exec_with_returning(tx)
.await?; .await?;
if let Some(channel_id) = initial_channel_id {
channel_member::Entity::insert(channel_member::ActiveModel {
id: ActiveValue::NotSet,
channel_id: ActiveValue::Set(channel_id),
user_id: ActiveValue::Set(user.id),
accepted: ActiveValue::Set(true),
role: ActiveValue::Set(ChannelRole::Guest),
})
.exec(tx)
.await?;
}
Ok(user)
} }
Ok(user)
} }
} }

View File

@@ -74,7 +74,7 @@ impl TestDb {
let mut rng = StdRng::from_entropy(); let mut rng = StdRng::from_entropy();
let url = format!( let url = format!(
"postgres://postgres@localhost/zed-test-{}", "postgres://postgres@localhost/zed-test-{}",
rng.gen::<u128>() rng.r#gen::<u128>()
); );
let runtime = tokio::runtime::Builder::new_current_thread() let runtime = tokio::runtime::Builder::new_current_thread()
.enable_io() .enable_io()

View File

@@ -101,10 +101,11 @@ async fn test_channel_buffers(db: &Arc<Database>) {
); );
buffer_b.apply_ops(buffer_response_b.operations.into_iter().map(|operation| { buffer_b.apply_ops(buffer_response_b.operations.into_iter().map(|operation| {
let operation = proto::deserialize_operation(operation).unwrap(); let operation = proto::deserialize_operation(operation).unwrap();
if let language::Operation::Buffer(operation) = operation { match operation {
operation language::Operation::Buffer(operation) => operation,
} else { _ => {
unreachable!() unreachable!()
}
} }
})); }));

View File

@@ -20,7 +20,8 @@ pub fn get_dotenv_vars(current_dir: impl AsRef<Path>) -> Result<Vec<(String, Str
pub fn load_dotenv() -> Result<()> { pub fn load_dotenv() -> Result<()> {
for (key, value) in get_dotenv_vars("./crates/collab")? { for (key, value) in get_dotenv_vars("./crates/collab")? {
std::env::set_var(key, value); // TODO: Audit that the environment access only happens in single-threaded code.
unsafe { std::env::set_var(key, value) };
} }
Ok(()) Ok(())
} }

View File

@@ -26,7 +26,7 @@ impl Executor {
} }
} }
pub fn sleep(&self, duration: Duration) -> impl Future<Output = ()> { pub fn sleep(&self, duration: Duration) -> impl Future<Output = ()> + use<> {
let this = self.clone(); let this = self.clone();
async move { async move {
match this { match this {

View File

@@ -26,7 +26,7 @@ impl TestLlmDb {
let mut rng = StdRng::from_entropy(); let mut rng = StdRng::from_entropy();
let url = format!( let url = format!(
"postgres://postgres@localhost/zed-llm-test-{}", "postgres://postgres@localhost/zed-llm-test-{}",
rng.gen::<u128>() rng.r#gen::<u128>()
); );
let runtime = tokio::runtime::Builder::new_current_thread() let runtime = tokio::runtime::Builder::new_current_thread()
.enable_io() .enable_io()

View File

@@ -716,7 +716,7 @@ impl Server {
system_id: Option<String>, system_id: Option<String>,
send_connection_id: Option<oneshot::Sender<ConnectionId>>, send_connection_id: Option<oneshot::Sender<ConnectionId>>,
executor: Executor, executor: Executor,
) -> impl Future<Output = ()> { ) -> impl Future<Output = ()> + use<> {
let this = self.clone(); let this = self.clone();
let span = info_span!("handle connection", %address, let span = info_span!("handle connection", %address,
connection_id=field::Empty, connection_id=field::Empty,
@@ -810,7 +810,7 @@ impl Server {
_ = foreground_message_handlers.next() => {} _ = foreground_message_handlers.next() => {}
next_message = next_message => { next_message = next_message => {
let (permit, message) = next_message; let (permit, message) = next_message;
if let Some(message) = message { match message { Some(message) => {
let type_name = message.payload_type_name(); let type_name = message.payload_type_name();
// note: we copy all the fields from the parent span so we can query them in the logs. // note: we copy all the fields from the parent span so we can query them in the logs.
// (https://github.com/tokio-rs/tracing/issues/2670). // (https://github.com/tokio-rs/tracing/issues/2670).
@@ -821,7 +821,7 @@ impl Server {
); );
principal.update_span(&span); principal.update_span(&span);
let span_enter = span.enter(); let span_enter = span.enter();
if let Some(handler) = this.handlers.get(&message.payload_type_id()) { match this.handlers.get(&message.payload_type_id()) { Some(handler) => {
let is_background = message.is_background(); let is_background = message.is_background();
let handle_message = (handler)(message, session.clone()); let handle_message = (handler)(message, session.clone());
drop(span_enter); drop(span_enter);
@@ -835,13 +835,13 @@ impl Server {
} else { } else {
foreground_message_handlers.push(handle_message); foreground_message_handlers.push(handle_message);
} }
} else { } _ => {
tracing::error!("no message handler"); tracing::error!("no message handler");
} }}
} else { } _ => {
tracing::info!("connection closed"); tracing::info!("connection closed");
break; break;
} }}
} }
} }
} }
@@ -1313,9 +1313,8 @@ async fn join_room(
.trace_err(); .trace_err();
} }
let live_kit_connection_info = if let Some(live_kit) = session.app_state.livekit_client.as_ref() let live_kit_connection_info = match session.app_state.livekit_client.as_ref() {
{ Some(live_kit) => live_kit
live_kit
.room_token( .room_token(
&joined_room.room.livekit_room, &joined_room.room.livekit_room,
&session.user_id().to_string(), &session.user_id().to_string(),
@@ -1325,9 +1324,8 @@ async fn join_room(
server_url: live_kit.url().into(), server_url: live_kit.url().into(),
token, token,
can_publish: true, can_publish: true,
}) }),
} else { _ => None,
None
}; };
response.send(proto::JoinRoomResponse { response.send(proto::JoinRoomResponse {
@@ -4393,23 +4391,26 @@ async fn leave_room_for_session(session: &Session, connection_id: ConnectionId)
let room; let room;
let channel; let channel;
if let Some(mut left_room) = session.db().await.leave_room(connection_id).await? { match session.db().await.leave_room(connection_id).await? {
contacts_to_update.insert(session.user_id()); Some(mut left_room) => {
contacts_to_update.insert(session.user_id());
for project in left_room.left_projects.values() { for project in left_room.left_projects.values() {
project_left(project, session); project_left(project, session);
}
room_id = RoomId::from_proto(left_room.room.id);
canceled_calls_to_user_ids = mem::take(&mut left_room.canceled_calls_to_user_ids);
livekit_room = mem::take(&mut left_room.room.livekit_room);
delete_livekit_room = left_room.deleted;
room = mem::take(&mut left_room.room);
channel = mem::take(&mut left_room.channel);
room_updated(&room, &session.peer);
}
_ => {
return Ok(());
} }
room_id = RoomId::from_proto(left_room.room.id);
canceled_calls_to_user_ids = mem::take(&mut left_room.canceled_calls_to_user_ids);
livekit_room = mem::take(&mut left_room.room.livekit_room);
delete_livekit_room = left_room.deleted;
room = mem::take(&mut left_room.room);
channel = mem::take(&mut left_room.channel);
room_updated(&room, &session.peer);
} else {
return Ok(());
} }
if let Some(channel) = channel { if let Some(channel) = channel {

View File

@@ -130,74 +130,76 @@ impl StripeBilling {
} }
let mut state = self.state.write().await; let mut state = self.state.write().await;
let meter = if let Some(meter) = state.meters_by_event_name.get(meter_event_name) { let meter = match state.meters_by_event_name.get(meter_event_name) {
meter.clone() Some(meter) => meter.clone(),
} else { _ => {
let meter = StripeMeter::create( let meter = StripeMeter::create(
&self.client, &self.client,
StripeCreateMeterParams { StripeCreateMeterParams {
default_aggregation: DefaultAggregation { formula: "sum" }, default_aggregation: DefaultAggregation { formula: "sum" },
display_name: price_description.to_string(), display_name: price_description.to_string(),
event_name: meter_event_name, event_name: meter_event_name,
}, },
) )
.await?; .await?;
state state
.meters_by_event_name .meters_by_event_name
.insert(meter_event_name.to_string(), meter.clone()); .insert(meter_event_name.to_string(), meter.clone());
meter meter
}
}; };
let price_id = if let Some(price_id) = state.price_ids_by_meter_id.get(&meter.id) { let price_id = match state.price_ids_by_meter_id.get(&meter.id) {
price_id.clone() Some(price_id) => price_id.clone(),
} else { _ => {
let price = stripe::Price::create( let price = stripe::Price::create(
&self.client, &self.client,
stripe::CreatePrice { stripe::CreatePrice {
active: Some(true),
billing_scheme: Some(stripe::PriceBillingScheme::PerUnit),
currency: stripe::Currency::USD,
currency_options: None,
custom_unit_amount: None,
expand: &[],
lookup_key: None,
metadata: None,
nickname: None,
product: None,
product_data: Some(stripe::CreatePriceProductData {
id: None,
active: Some(true), active: Some(true),
billing_scheme: Some(stripe::PriceBillingScheme::PerUnit),
currency: stripe::Currency::USD,
currency_options: None,
custom_unit_amount: None,
expand: &[],
lookup_key: None,
metadata: None, metadata: None,
name: price_description.to_string(), nickname: None,
statement_descriptor: None, product: None,
tax_code: None, product_data: Some(stripe::CreatePriceProductData {
unit_label: None, id: None,
}), active: Some(true),
recurring: Some(stripe::CreatePriceRecurring { metadata: None,
aggregate_usage: None, name: price_description.to_string(),
interval: stripe::CreatePriceRecurringInterval::Month, statement_descriptor: None,
interval_count: None, tax_code: None,
trial_period_days: None, unit_label: None,
usage_type: Some(stripe::CreatePriceRecurringUsageType::Metered), }),
meter: Some(meter.id.clone()), recurring: Some(stripe::CreatePriceRecurring {
}), aggregate_usage: None,
tax_behavior: None, interval: stripe::CreatePriceRecurringInterval::Month,
tiers: None, interval_count: None,
tiers_mode: None, trial_period_days: None,
transfer_lookup_key: None, usage_type: Some(stripe::CreatePriceRecurringUsageType::Metered),
transform_quantity: None, meter: Some(meter.id.clone()),
unit_amount: None, }),
unit_amount_decimal: Some(&format!( tax_behavior: None,
"{:.12}", tiers: None,
price_per_million_tokens.0 as f64 / 1_000_000f64 tiers_mode: None,
)), transfer_lookup_key: None,
}, transform_quantity: None,
) unit_amount: None,
.await?; unit_amount_decimal: Some(&format!(
state "{:.12}",
.price_ids_by_meter_id price_per_million_tokens.0 as f64 / 1_000_000f64
.insert(meter.id, price.id.clone()); )),
price.id },
)
.await?;
state
.price_ids_by_meter_id
.insert(meter.id, price.id.clone());
price.id
}
}; };
Ok(StripeBillingPrice { Ok(StripeBillingPrice {

View File

@@ -5602,7 +5602,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
let definitions; let definitions;
let buffer_b2; let buffer_b2;
if rng.gen() { if rng.r#gen() {
definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx)); definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
(buffer_b2, _) = project_b (buffer_b2, _) = project_b
.update(cx_b, |p, cx| { .update(cx_b, |p, cx| {

View File

@@ -216,45 +216,47 @@ impl RandomizedTest for ProjectCollaborationTest {
// Open a new project // Open a new project
0..=70 => { 0..=70 => {
// Open a remote project // Open a remote project
if let Some(room) = call.read_with(cx, |call, _| call.room().cloned()) { match call.read_with(cx, |call, _| call.room().cloned()) {
let existing_dev_server_project_ids = cx.read(|cx| { Some(room) => {
client let existing_dev_server_project_ids = cx.read(|cx| {
.dev_server_projects() client
.iter() .dev_server_projects()
.map(|p| p.read(cx).remote_id().unwrap()) .iter()
.collect::<Vec<_>>() .map(|p| p.read(cx).remote_id().unwrap())
}); .collect::<Vec<_>>()
let new_dev_server_projects = room.read_with(cx, |room, _| { });
room.remote_participants() let new_dev_server_projects = room.read_with(cx, |room, _| {
.values() room.remote_participants()
.flat_map(|participant| { .values()
participant.projects.iter().filter_map(|project| { .flat_map(|participant| {
if existing_dev_server_project_ids.contains(&project.id) participant.projects.iter().filter_map(|project| {
{ if existing_dev_server_project_ids
None .contains(&project.id)
} else { {
Some(( None
UserId::from_proto(participant.user.id), } else {
project.worktree_root_names[0].clone(), Some((
)) UserId::from_proto(participant.user.id),
} project.worktree_root_names[0].clone(),
))
}
})
}) })
}) .collect::<Vec<_>>()
.collect::<Vec<_>>() });
}); if !new_dev_server_projects.is_empty() {
if !new_dev_server_projects.is_empty() { let (host_id, first_root_name) =
let (host_id, first_root_name) = new_dev_server_projects.choose(rng).unwrap().clone();
new_dev_server_projects.choose(rng).unwrap().clone(); break ClientOperation::OpenRemoteProject {
break ClientOperation::OpenRemoteProject { host_id,
host_id, first_root_name,
first_root_name, };
}; }
}
_ => {
let first_root_name = plan.next_root_dir_name();
break ClientOperation::OpenLocalProject { first_root_name };
} }
}
// Open a local project
else {
let first_root_name = plan.next_root_dir_name();
break ClientOperation::OpenLocalProject { first_root_name };
} }
} }
@@ -279,7 +281,7 @@ impl RandomizedTest for ProjectCollaborationTest {
let project_root_name = root_name_for_project(&project, cx); let project_root_name = root_name_for_project(&project, cx);
let mut paths = client.fs().paths(false); let mut paths = client.fs().paths(false);
paths.remove(0); paths.remove(0);
let new_root_path = if paths.is_empty() || rng.gen() { let new_root_path = if paths.is_empty() || rng.r#gen() {
Path::new(path!("/")).join(plan.next_root_dir_name()) Path::new(path!("/")).join(plan.next_root_dir_name())
} else { } else {
paths.choose(rng).unwrap().clone() paths.choose(rng).unwrap().clone()
@@ -309,7 +311,7 @@ impl RandomizedTest for ProjectCollaborationTest {
.choose(rng) .choose(rng)
}); });
let Some(worktree) = worktree else { continue }; let Some(worktree) = worktree else { continue };
let is_dir = rng.gen::<bool>(); let is_dir = rng.r#gen::<bool>();
let mut full_path = let mut full_path =
worktree.read_with(cx, |w, _| PathBuf::from(w.root_name())); worktree.read_with(cx, |w, _| PathBuf::from(w.root_name()));
full_path.push(gen_file_name(rng)); full_path.push(gen_file_name(rng));
@@ -387,7 +389,7 @@ impl RandomizedTest for ProjectCollaborationTest {
language::Bias::Left, language::Bias::Left,
) )
}); });
let detach = rng.gen(); let detach = rng.r#gen();
break ClientOperation::RequestLspDataInBuffer { break ClientOperation::RequestLspDataInBuffer {
project_root_name, project_root_name,
full_path, full_path,
@@ -460,7 +462,7 @@ impl RandomizedTest for ProjectCollaborationTest {
// Create or update a file or directory // Create or update a file or directory
96.. => { 96.. => {
let is_dir = rng.gen::<bool>(); let is_dir = rng.r#gen::<bool>();
let content; let content;
let mut path; let mut path;
let dir_paths = client.fs().directories(false); let dir_paths = client.fs().directories(false);
@@ -1270,12 +1272,14 @@ impl RandomizedTest for ProjectCollaborationTest {
Some((client.user_id().unwrap(), project, cx)) Some((client.user_id().unwrap(), project, cx))
}); });
let (host_user_id, host_project, host_cx) = let (host_user_id, host_project, host_cx) = match host_project {
if let Some((host_user_id, host_project, host_cx)) = host_project { Some((host_user_id, host_project, host_cx)) => {
(host_user_id, host_project, host_cx) (host_user_id, host_project, host_cx)
} else { }
_ => {
continue; continue;
}; }
};
for guest_buffer in guest_buffers { for guest_buffer in guest_buffers {
let buffer_id = let buffer_id =

View File

@@ -175,26 +175,26 @@ impl TestServer {
let clock = Arc::new(FakeSystemClock::new()); let clock = Arc::new(FakeSystemClock::new());
let http = FakeHttpClient::with_404_response(); let http = FakeHttpClient::with_404_response();
let user_id = if let Ok(Some(user)) = self.app_state.db.get_user_by_github_login(name).await let user_id = match self.app_state.db.get_user_by_github_login(name).await {
{ Ok(Some(user)) => user.id,
user.id _ => {
} else { let github_user_id = self.next_github_user_id;
let github_user_id = self.next_github_user_id; self.next_github_user_id += 1;
self.next_github_user_id += 1; self.app_state
self.app_state .db
.db .create_user(
.create_user( &format!("{name}@example.com"),
&format!("{name}@example.com"), None,
None, false,
false, NewUserParams {
NewUserParams { github_login: name.into(),
github_login: name.into(), github_user_id,
github_user_id, },
}, )
) .await
.await .expect("creating user failed")
.expect("creating user failed") .user_id
.user_id }
}; };
let client_name = name.to_string(); let client_name = name.to_string();
let mut client = cx.update(|cx| Client::new(clock, http.clone(), cx)); let mut client = cx.update(|cx| Client::new(clock, http.clone(), cx));
@@ -660,7 +660,7 @@ impl TestClient {
pub fn buffers_for_project<'a>( pub fn buffers_for_project<'a>(
&'a self, &'a self,
project: &Entity<Project>, project: &Entity<Project>,
) -> impl DerefMut<Target = HashSet<Entity<language::Buffer>>> + 'a { ) -> impl DerefMut<Target = HashSet<Entity<language::Buffer>>> + 'a + use<'a> {
RefMut::map(self.state.borrow_mut(), |state| { RefMut::map(self.state.borrow_mut(), |state| {
state.buffers.entry(project.clone()).or_default() state.buffers.entry(project.clone()).or_default()
}) })

View File

@@ -556,12 +556,9 @@ impl FollowableItem for ChannelView {
Some(proto::view::Variant::ChannelView( Some(proto::view::Variant::ChannelView(
proto::view::ChannelView { proto::view::ChannelView {
channel_id: channel_buffer.channel_id.0, channel_id: channel_buffer.channel_id.0,
editor: if let Some(proto::view::Variant::Editor(proto)) = editor: match self.editor.read(cx).to_state_proto(window, cx) {
self.editor.read(cx).to_state_proto(window, cx) Some(proto::view::Variant::Editor(proto)) => Some(proto),
{ _ => None,
Some(proto)
} else {
None
}, },
}, },
)) ))

View File

@@ -102,14 +102,11 @@ impl ChatPanel {
0, 0,
gpui::ListAlignment::Bottom, gpui::ListAlignment::Bottom,
px(1000.), px(1000.),
move |ix, window, cx| { move |ix, window, cx| match entity.upgrade() {
if let Some(entity) = entity.upgrade() { Some(entity) => entity.update(cx, |this: &mut Self, cx| {
entity.update(cx, |this: &mut Self, cx| { this.render_message(ix, window, cx).into_any_element()
this.render_message(ix, window, cx).into_any_element() }),
}) _ => div().into_any(),
} else {
div().into_any()
}
}, },
); );
@@ -200,15 +197,14 @@ impl ChatPanel {
cx: AsyncWindowContext, cx: AsyncWindowContext,
) -> Task<Result<Entity<Self>>> { ) -> Task<Result<Entity<Self>>> {
cx.spawn(async move |cx| { cx.spawn(async move |cx| {
let serialized_panel = if let Some(panel) = cx let serialized_panel = match cx
.background_spawn(async move { KEY_VALUE_STORE.read_kvp(CHAT_PANEL_KEY) }) .background_spawn(async move { KEY_VALUE_STORE.read_kvp(CHAT_PANEL_KEY) })
.await .await
.log_err() .log_err()
.flatten() .flatten()
{ {
Some(serde_json::from_str::<SerializedChatPanel>(&panel)?) Some(panel) => Some(serde_json::from_str::<SerializedChatPanel>(&panel)?),
} else { _ => None,
None
}; };
workspace.update_in(cx, |workspace, window, cx| { workspace.update_in(cx, |workspace, window, cx| {
@@ -314,7 +310,7 @@ impl ChatPanel {
message_id: Option<ChannelMessageId>, message_id: Option<ChannelMessageId>,
reply_to_message: &Option<ChannelMessage>, reply_to_message: &Option<ChannelMessage>,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let reply_to_message = match reply_to_message { let reply_to_message = match reply_to_message {
None => { None => {
return div().child( return div().child(
@@ -393,7 +389,7 @@ impl ChatPanel {
ix: usize, ix: usize,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let active_chat = &self.active_chat.as_ref().unwrap().0; let active_chat = &self.active_chat.as_ref().unwrap().0;
let (message, is_continuation_from_previous, is_admin) = let (message, is_continuation_from_previous, is_admin) =
active_chat.update(cx, |active_chat, cx| { active_chat.update(cx, |active_chat, cx| {
@@ -812,22 +808,30 @@ impl ChatPanel {
.message_editor .message_editor
.update(cx, |editor, cx| editor.take_message(window, cx)); .update(cx, |editor, cx| editor.take_message(window, cx));
if let Some(id) = self.message_editor.read(cx).edit_message_id() { match self.message_editor.read(cx).edit_message_id() {
self.message_editor.update(cx, |editor, _| { Some(id) => {
editor.clear_edit_message_id(); self.message_editor.update(cx, |editor, _| {
}); editor.clear_edit_message_id();
});
if let Some(task) = chat if let Some(task) = chat
.update(cx, |chat, cx| chat.update_message(id, message, cx)) .update(cx, |chat, cx| chat.update_message(id, message, cx))
.log_err() .log_err()
{ {
task.detach(); task.detach();
}
}
_ => {
match chat
.update(cx, |chat, cx| chat.send_message(message, cx))
.log_err()
{
Some(task) => {
task.detach();
}
_ => {}
}
} }
} else if let Some(task) = chat
.update(cx, |chat, cx| chat.send_message(message, cx))
.log_err()
{
task.detach();
} }
} }
} }

View File

@@ -244,12 +244,11 @@ impl CollabPanel {
0, 0,
gpui::ListAlignment::Top, gpui::ListAlignment::Top,
px(1000.), px(1000.),
move |ix, window, cx| { move |ix, window, cx| match entity.upgrade() {
if let Some(entity) = entity.upgrade() { Some(entity) => {
entity.update(cx, |this, cx| this.render_list_entry(ix, window, cx)) entity.update(cx, |this, cx| this.render_list_entry(ix, window, cx))
} else {
div().into_any()
} }
_ => div().into_any(),
}, },
); );
@@ -878,7 +877,7 @@ impl CollabPanel {
is_selected: bool, is_selected: bool,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let project_name: SharedString = if worktree_root_names.is_empty() { let project_name: SharedString = if worktree_root_names.is_empty() {
"untitled".to_string() "untitled".to_string()
} else { } else {
@@ -919,7 +918,7 @@ impl CollabPanel {
is_selected: bool, is_selected: bool,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let id = peer_id.map_or(usize::MAX, |id| id.as_u64() as usize); let id = peer_id.map_or(usize::MAX, |id| id.as_u64() as usize);
ListItem::new(("screen", id)) ListItem::new(("screen", id))
@@ -960,7 +959,7 @@ impl CollabPanel {
is_selected: bool, is_selected: bool,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let channel_store = self.channel_store.read(cx); let channel_store = self.channel_store.read(cx);
let has_channel_buffer_changed = channel_store.has_channel_buffer_changed(channel_id); let has_channel_buffer_changed = channel_store.has_channel_buffer_changed(channel_id);
ListItem::new("channel-notes") ListItem::new("channel-notes")
@@ -993,7 +992,7 @@ impl CollabPanel {
is_selected: bool, is_selected: bool,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let channel_store = self.channel_store.read(cx); let channel_store = self.channel_store.read(cx);
let has_messages_notification = channel_store.has_new_messages(channel_id); let has_messages_notification = channel_store.has_new_messages(channel_id);
ListItem::new("channel-chat") ListItem::new("channel-chat")
@@ -2278,7 +2277,7 @@ impl CollabPanel {
&self, &self,
editor: &Entity<Editor>, editor: &Entity<Editor>,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
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(cx) { color: if editor.read(cx).read_only(cx) {
@@ -2312,7 +2311,7 @@ impl CollabPanel {
is_selected: bool, is_selected: bool,
is_collapsed: bool, is_collapsed: bool,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let mut channel_link = None; let mut channel_link = None;
let mut channel_tooltip_text = None; let mut channel_tooltip_text = None;
let mut channel_icon = None; let mut channel_icon = None;
@@ -2411,7 +2410,7 @@ impl CollabPanel {
calling: bool, calling: bool,
is_selected: bool, is_selected: bool,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let online = contact.online; let online = contact.online;
let busy = contact.busy || calling; let busy = contact.busy || calling;
let github_login = SharedString::from(contact.user.github_login.clone()); let github_login = SharedString::from(contact.user.github_login.clone());
@@ -2492,7 +2491,7 @@ impl CollabPanel {
is_incoming: bool, is_incoming: bool,
is_selected: bool, is_selected: bool,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let github_login = SharedString::from(user.github_login.clone()); let github_login = SharedString::from(user.github_login.clone());
let user_id = user.id; let user_id = user.id;
let is_response_pending = self.user_store.read(cx).is_contact_request_pending(user); let is_response_pending = self.user_store.read(cx).is_contact_request_pending(user);
@@ -2605,7 +2604,7 @@ impl CollabPanel {
is_selected: bool, is_selected: bool,
ix: usize, ix: usize,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let channel_id = channel.id; let channel_id = channel.id;
let is_active = maybe!({ let is_active = maybe!({
@@ -2803,7 +2802,7 @@ impl CollabPanel {
depth: usize, depth: usize,
_window: &mut Window, _window: &mut Window,
_cx: &mut Context<Self>, _cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let item = ListItem::new("channel-editor") let item = ListItem::new("channel-editor")
.inset(false) .inset(false)
// Add one level of depth for the disclosure arrow. // Add one level of depth for the disclosure arrow.
@@ -2832,7 +2831,7 @@ fn render_tree_branch(
overdraw: bool, overdraw: bool,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let rem_size = window.rem_size(); let rem_size = window.rem_size();
let line_height = window.text_style().line_height_in_pixels(rem_size); let line_height = window.text_style().line_height_in_pixels(rem_size);
let width = rem_size * 1.5; let width = rem_size * 1.5;

View File

@@ -421,20 +421,17 @@ impl PickerDelegate for ChannelModalDelegate {
el.child(IconButton::new("ellipsis", IconName::Ellipsis)) el.child(IconButton::new("ellipsis", IconName::Ellipsis))
}) })
.when(is_me, |el| el.child(Label::new("You").color(Color::Muted))) .when(is_me, |el| el.child(Label::new("You").color(Color::Muted)))
.children( .children(match (&self.context_menu, selected) {
if let (Some((menu, _)), true) = (&self.context_menu, selected) { (Some((menu, _)), true) => Some(
Some( deferred(
deferred( anchored()
anchored() .anchor(gpui::Corner::TopRight)
.anchor(gpui::Corner::TopRight) .child(menu.clone()),
.child(menu.clone()),
)
.with_priority(1),
) )
} else { .with_priority(1),
None ),
}, _ => None,
), }),
Mode::InviteMembers => match request_status { Mode::InviteMembers => match request_status {
Some(proto::channel_member::Kind::Invitee) => { Some(proto::channel_member::Kind::Invitee) => {
slot.children(Some(Label::new("Invited"))) slot.children(Some(Label::new("Invited")))

View File

@@ -182,15 +182,14 @@ impl NotificationPanel {
cx: AsyncWindowContext, cx: AsyncWindowContext,
) -> Task<Result<Entity<Self>>> { ) -> Task<Result<Entity<Self>>> {
cx.spawn(async move |cx| { cx.spawn(async move |cx| {
let serialized_panel = if let Some(panel) = cx let serialized_panel = match cx
.background_spawn(async move { KEY_VALUE_STORE.read_kvp(NOTIFICATION_PANEL_KEY) }) .background_spawn(async move { KEY_VALUE_STORE.read_kvp(NOTIFICATION_PANEL_KEY) })
.await .await
.log_err() .log_err()
.flatten() .flatten()
{ {
Some(serde_json::from_str::<SerializedNotificationPanel>(&panel)?) Some(panel) => Some(serde_json::from_str::<SerializedNotificationPanel>(&panel)?),
} else { _ => None,
None
}; };
workspace.update_in(cx, |workspace, window, cx| { workspace.update_in(cx, |workspace, window, cx| {
@@ -494,14 +493,15 @@ impl NotificationPanel {
if let Notification::ChannelMessageMention { channel_id, .. } = &notification { if let Notification::ChannelMessageMention { channel_id, .. } = &notification {
if let Some(workspace) = self.workspace.upgrade() { if let Some(workspace) = self.workspace.upgrade() {
return if let Some(panel) = workspace.read(cx).panel::<ChatPanel>(cx) { return match workspace.read(cx).panel::<ChatPanel>(cx) {
let panel = panel.read(cx); Some(panel) => {
panel.is_scrolled_to_bottom() let panel = panel.read(cx);
&& panel panel.is_scrolled_to_bottom()
.active_chat() && panel
.map_or(false, |chat| chat.read(cx).channel_id.0 == *channel_id) .active_chat()
} else { .map_or(false, |chat| chat.read(cx).channel_id.0 == *channel_id)
false }
_ => false,
}; };
} }
} }

View File

@@ -133,10 +133,9 @@ impl CommandPaletteInterceptor {
/// Intercepts the given query from the command palette. /// Intercepts the given query from the command palette.
pub fn intercept(&self, query: &str, cx: &App) -> Vec<CommandInterceptResult> { pub fn intercept(&self, query: &str, cx: &App) -> Vec<CommandInterceptResult> {
if let Some(handler) = self.0.as_ref() { match self.0.as_ref() {
(handler)(query, cx) Some(handler) => (handler)(query, cx),
} else { _ => Vec::new(),
Vec::new()
} }
} }

View File

@@ -158,7 +158,7 @@ pub fn components() -> AllComponents {
let data = COMPONENT_DATA.read(); let data = COMPONENT_DATA.read();
let mut all_components = AllComponents::new(); let mut all_components = AllComponents::new();
for (ref scope, name, description) in &data.components { for (scope, name, description) in &data.components {
let preview = data.previews.get(name).cloned(); let preview = data.previews.get(name).cloned();
let component_name = SharedString::new_static(name); let component_name = SharedString::new_static(name);
let id = ComponentId(name); let id = ComponentId(name);

View File

@@ -244,7 +244,7 @@ impl ComponentPreview {
ix: usize, ix: usize,
entry: &PreviewEntry, entry: &PreviewEntry,
cx: &Context<Self>, cx: &Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
match entry { match entry {
PreviewEntry::Component(component_metadata) => { PreviewEntry::Component(component_metadata) => {
let id = component_metadata.id(); let id = component_metadata.id();
@@ -318,7 +318,7 @@ impl ComponentPreview {
title: SharedString, title: SharedString,
_window: &Window, _window: &Window,
_cx: &App, _cx: &App,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
h_flex() h_flex()
.w_full() .w_full()
.h_10() .h_10()
@@ -332,7 +332,7 @@ impl ComponentPreview {
component: &ComponentMetadata, component: &ComponentMetadata,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let name = component.name(); let name = component.name();
let scope = component.scope(); let scope = component.scope();
@@ -379,7 +379,7 @@ impl ComponentPreview {
.into_any_element() .into_any_element()
} }
fn render_all_components(&self) -> impl IntoElement { fn render_all_components(&self) -> impl IntoElement + use<> {
v_flex() v_flex()
.id("component-list") .id("component-list")
.px_8() .px_8()
@@ -397,7 +397,7 @@ impl ComponentPreview {
component_id: &ComponentId, component_id: &ComponentId,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement + use<> {
let component = self.component_map.get(&component_id); let component = self.component_map.get(&component_id);
if let Some(component) = component { if let Some(component) = component {

View File

@@ -62,10 +62,9 @@ pub struct Client {
pub struct ContextServerId(pub Arc<str>); pub struct ContextServerId(pub Arc<str>);
fn is_null_value<T: Serialize>(value: &T) -> bool { fn is_null_value<T: Serialize>(value: &T) -> bool {
if let Ok(Value::Null) = serde_json::to_value(value) { match serde_json::to_value(value) {
true Ok(Value::Null) => true,
} else { _ => false,
false
} }
} }
@@ -232,10 +231,17 @@ impl Client {
handler(Ok(message.to_string())); handler(Ok(message.to_string()));
} }
} }
} else if let Ok(notification) = serde_json::from_str::<AnyNotification>(&message) { } else {
let mut notification_handlers = notification_handlers.lock(); match serde_json::from_str::<AnyNotification>(&message) {
if let Some(handler) = notification_handlers.get_mut(notification.method.as_str()) { Ok(notification) => {
handler(notification.params.unwrap_or(Value::Null), cx.clone()); let mut notification_handlers = notification_handlers.lock();
if let Some(handler) =
notification_handlers.get_mut(notification.method.as_str())
{
handler(notification.params.unwrap_or(Value::Null), cx.clone());
}
}
_ => {}
} }
} }
} }

View File

@@ -77,47 +77,47 @@ impl Tool for ContextServerTool {
_action_log: Entity<ActionLog>, _action_log: Entity<ActionLog>,
cx: &mut App, cx: &mut App,
) -> Task<Result<String>> { ) -> Task<Result<String>> {
if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) { match self.server_manager.read(cx).get_server(&self.server_id) {
let tool_name = self.tool.name.clone(); Some(server) => {
let server_clone = server.clone(); let tool_name = self.tool.name.clone();
let input_clone = input.clone(); let server_clone = server.clone();
let input_clone = input.clone();
cx.spawn(async move |_cx| { cx.spawn(async move |_cx| {
let Some(protocol) = server_clone.client() else { let Some(protocol) = server_clone.client() else {
bail!("Context server not initialized"); bail!("Context server not initialized");
}; };
let arguments = if let serde_json::Value::Object(map) = input_clone { let arguments = match input_clone {
Some(map.into_iter().collect()) serde_json::Value::Object(map) => Some(map.into_iter().collect()),
} else { _ => None,
None };
};
log::trace!( log::trace!(
"Running tool: {} with arguments: {:?}", "Running tool: {} with arguments: {:?}",
tool_name, tool_name,
arguments arguments
); );
let response = protocol.run_tool(tool_name, arguments).await?; let response = protocol.run_tool(tool_name, arguments).await?;
let mut result = String::new(); let mut result = String::new();
for content in response.content { for content in response.content {
match content { match content {
types::ToolResponseContent::Text { text } => { types::ToolResponseContent::Text { text } => {
result.push_str(&text); result.push_str(&text);
} }
types::ToolResponseContent::Image { .. } => { types::ToolResponseContent::Image { .. } => {
log::warn!("Ignoring image content from tool response"); log::warn!("Ignoring image content from tool response");
} }
types::ToolResponseContent::Resource { .. } => { types::ToolResponseContent::Resource { .. } => {
log::warn!("Ignoring resource content from tool response"); log::warn!("Ignoring resource content from tool response");
}
} }
} }
} Ok(result)
Ok(result) })
}) }
} else { _ => Task::ready(Err(anyhow!("Context server not found"))),
Task::ready(Err(anyhow!("Context server not found")))
} }
} }
} }

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use collections::HashMap; use collections::HashMap;
use gpui::App; use gpui::App;
use schemars::gen::SchemaGenerator; use schemars::r#gen::SchemaGenerator;
use schemars::schema::{InstanceType, Schema, SchemaObject}; use schemars::schema::{InstanceType, Schema, SchemaObject};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@@ -348,7 +348,10 @@ impl Copilot {
this this
} }
fn shutdown_language_server(&mut self, _cx: &mut Context<Self>) -> impl Future<Output = ()> { fn shutdown_language_server(
&mut self,
_cx: &mut Context<Self>,
) -> impl Future<Output = ()> + use<> {
let shutdown = match mem::replace(&mut self.server, CopilotServer::Disabled) { let shutdown = match mem::replace(&mut self.server, CopilotServer::Disabled) {
CopilotServer::Running(server) => Some(Box::pin(async move { server.lsp.shutdown() })), CopilotServer::Running(server) => Some(Box::pin(async move { server.lsp.shutdown() })),
_ => None, _ => None,
@@ -553,24 +556,27 @@ impl Copilot {
} }
pub fn sign_in(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> { pub fn sign_in(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
if let CopilotServer::Running(server) = &mut self.server { match &mut self.server {
let task = match &server.sign_in_status { CopilotServer::Running(server) => {
SignInStatus::Authorized { .. } => Task::ready(Ok(())).shared(), let task =
SignInStatus::SigningIn { task, .. } => { match &server.sign_in_status {
cx.notify(); SignInStatus::Authorized { .. } => Task::ready(Ok(())).shared(),
task.clone() SignInStatus::SigningIn { task, .. } => {
} cx.notify();
SignInStatus::SignedOut { .. } | SignInStatus::Unauthorized { .. } => { task.clone()
let lsp = server.lsp.clone(); }
let task = cx SignInStatus::SignedOut { .. } | SignInStatus::Unauthorized { .. } => {
.spawn(async move |this, cx| { let lsp = server.lsp.clone();
let sign_in = async { let task = cx
let sign_in = lsp .spawn(async move |this, cx| {
.request::<request::SignInInitiate>( let sign_in =
request::SignInInitiateParams {}, async {
) let sign_in = lsp
.await?; .request::<request::SignInInitiate>(
match sign_in { request::SignInInitiateParams {},
)
.await?;
match sign_in {
request::SignInInitiateResult::AlreadySignedIn { user } => { request::SignInInitiateResult::AlreadySignedIn { user } => {
Ok(request::SignInStatus::Ok { user: Some(user) }) Ok(request::SignInStatus::Ok { user: Some(user) })
} }
@@ -601,38 +607,40 @@ impl Copilot {
Ok(response) Ok(response)
} }
} }
};
let sign_in = sign_in.await;
this.update(cx, |this, cx| match sign_in {
Ok(status) => {
this.update_sign_in_status(status, cx);
Ok(())
}
Err(error) => {
this.update_sign_in_status(
request::SignInStatus::NotSignedIn,
cx,
);
Err(Arc::new(error))
}
})?
})
.shared();
server.sign_in_status = SignInStatus::SigningIn {
prompt: None,
task: task.clone(),
}; };
cx.notify();
let sign_in = sign_in.await; task
this.update(cx, |this, cx| match sign_in { }
Ok(status) => {
this.update_sign_in_status(status, cx);
Ok(())
}
Err(error) => {
this.update_sign_in_status(
request::SignInStatus::NotSignedIn,
cx,
);
Err(Arc::new(error))
}
})?
})
.shared();
server.sign_in_status = SignInStatus::SigningIn {
prompt: None,
task: task.clone(),
}; };
cx.notify();
task
}
};
cx.background_spawn(task.map_err(|err| anyhow!("{:?}", err))) cx.background_spawn(task.map_err(|err| anyhow!("{:?}", err)))
} else { }
// If we're downloading, wait until download is finished _ => {
// If we're in a stuck state, display to the user // If we're downloading, wait until download is finished
Task::ready(Err(anyhow!("copilot hasn't started yet"))) // If we're in a stuck state, display to the user
Task::ready(Err(anyhow!("copilot hasn't started yet")))
}
} }
} }
@@ -680,10 +688,9 @@ impl Copilot {
} }
pub fn language_server(&self) -> Option<&Arc<LanguageServer>> { pub fn language_server(&self) -> Option<&Arc<LanguageServer>> {
if let CopilotServer::Running(server) = &self.server { match &self.server {
Some(&server.lsp) CopilotServer::Running(server) => Some(&server.lsp),
} else { _ => None,
None
} }
} }

View File

@@ -1078,7 +1078,7 @@ mod tests {
cx: &mut EditorLspTestContext, cx: &mut EditorLspTestContext,
marked_string: &str, marked_string: &str,
completions: Vec<&'static str>, completions: Vec<&'static str>,
) -> impl Future<Output = ()> { ) -> impl Future<Output = ()> + use<> {
let complete_from_marker: TextRangeMarker = '|'.into(); let complete_from_marker: TextRangeMarker = '|'.into();
let replace_range_marker: TextRangeMarker = ('<', '>').into(); let replace_range_marker: TextRangeMarker = ('<', '>').into();
let (_, mut marked_ranges) = marked_text_ranges_by( let (_, mut marked_ranges) = marked_text_ranges_by(

View File

@@ -139,7 +139,10 @@ impl CopilotCodeVerification {
cx.notify(); cx.notify();
} }
fn render_device_code(data: &PromptUserDeviceFlow, cx: &mut Context<Self>) -> impl IntoElement { fn render_device_code(
data: &PromptUserDeviceFlow,
cx: &mut Context<Self>,
) -> impl IntoElement + use<> {
let copied = cx let copied = cx
.read_from_clipboard() .read_from_clipboard()
.map(|item| item.text().as_ref() == Some(&data.user_code)) .map(|item| item.text().as_ref() == Some(&data.user_code))
@@ -172,7 +175,7 @@ impl CopilotCodeVerification {
data: &PromptUserDeviceFlow, data: &PromptUserDeviceFlow,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl Element { ) -> impl Element + use<> {
let connect_button_label = if connect_clicked { let connect_button_label = if connect_clicked {
"Waiting for connection..." "Waiting for connection..."
} else { } else {
@@ -213,7 +216,7 @@ impl CopilotCodeVerification {
) )
} }
fn render_enabled_modal(cx: &mut Context<Self>) -> impl Element { fn render_enabled_modal(cx: &mut Context<Self>) -> impl Element + use<> {
v_flex() v_flex()
.gap_2() .gap_2()
.child(Headline::new("Copilot Enabled!").size(HeadlineSize::Large)) .child(Headline::new("Copilot Enabled!").size(HeadlineSize::Large))
@@ -227,7 +230,7 @@ impl CopilotCodeVerification {
) )
} }
fn render_unauthorized_modal(cx: &mut Context<Self>) -> impl Element { fn render_unauthorized_modal(cx: &mut Context<Self>) -> impl Element + use<> {
v_flex() v_flex()
.child(Headline::new("You must have an active GitHub Copilot subscription.").size(HeadlineSize::Large)) .child(Headline::new("You must have an active GitHub Copilot subscription.").size(HeadlineSize::Large))
@@ -246,7 +249,7 @@ impl CopilotCodeVerification {
) )
} }
fn render_loading(window: &mut Window, _: &mut Context<Self>) -> impl Element { fn render_loading(window: &mut Window, _: &mut Context<Self>) -> impl Element + use<> {
let loading_icon = svg() let loading_icon = svg()
.size_8() .size_8()
.path(IconName::ArrowCircle.path()) .path(IconName::ArrowCircle.path())

View File

@@ -222,13 +222,12 @@ impl TransportDelegate {
} }
pub(crate) async fn send_message(&self, message: Message) -> Result<()> { pub(crate) async fn send_message(&self, message: Message) -> Result<()> {
if let Some(server_tx) = self.server_tx.lock().await.as_ref() { match self.server_tx.lock().await.as_ref() {
server_tx Some(server_tx) => server_tx
.send(message) .send(message)
.await .await
.map_err(|e| anyhow!("Failed to send message: {}", e)) .map_err(|e| anyhow!("Failed to send message: {}", e)),
} else { _ => Err(anyhow!("Server tx already dropped")),
Err(anyhow!("Server tx already dropped"))
} }
} }
@@ -343,12 +342,15 @@ impl TransportDelegate {
match message { match message {
Ok(Message::Response(res)) => { Ok(Message::Response(res)) => {
if let Some(tx) = pending_requests.lock().await.remove(&res.request_seq) { match pending_requests.lock().await.remove(&res.request_seq) {
if let Err(e) = tx.send(Self::process_response(res)) { Some(tx) => {
log::trace!("Did not send response `{:?}` for a cancelled", e); if let Err(e) = tx.send(Self::process_response(res)) {
log::trace!("Did not send response `{:?}` for a cancelled", e);
}
}
_ => {
client_tx.send(Message::Response(res)).await?;
} }
} else {
client_tx.send(Message::Response(res)).await?;
}; };
} }
Ok(message) => { Ok(message) => {
@@ -816,22 +818,25 @@ impl FakeTransport {
.unwrap(); .unwrap();
writer.flush().await.unwrap(); writer.flush().await.unwrap();
} else { } else {
if let Some(handle) = request_handlers match request_handlers
.lock() .lock()
.await .await
.get_mut(request.command.as_str()) .get_mut(request.command.as_str())
{ {
handle( Some(handle) => {
request.seq, handle(
request.arguments.unwrap_or(json!({})), request.seq,
stdout_writer.clone(), request.arguments.unwrap_or(json!({})),
) stdout_writer.clone(),
.await; )
} else { .await;
log::error!( }
"No request handler for {}", _ => {
request.command log::error!(
); "No request handler for {}",
request.command
);
}
} }
} }
} }
@@ -850,14 +855,20 @@ impl FakeTransport {
writer.flush().await.unwrap(); writer.flush().await.unwrap();
} }
Message::Response(response) => { Message::Response(response) => {
if let Some(handle) = response_handlers match response_handlers
.lock() .lock()
.await .await
.get(response.command.as_str()) .get(response.command.as_str())
{ {
handle(response); Some(handle) => {
} else { handle(response);
log::error!("No response handler for {}", response.command); }
_ => {
log::error!(
"No response handler for {}",
response.command
);
}
} }
} }
} }

View File

@@ -94,17 +94,16 @@ impl DebugAdapter for PythonDebugAdapter {
) )
.await; .await;
let python_path = if let Some(toolchain) = toolchain { let python_path = match toolchain {
Some(toolchain.path.to_string()) Some(toolchain) => Some(toolchain.path.to_string()),
} else { _ => BINARY_NAMES
BINARY_NAMES
.iter() .iter()
.filter_map(|cmd| { .filter_map(|cmd| {
delegate delegate
.which(OsStr::new(cmd)) .which(OsStr::new(cmd))
.map(|path| path.to_string_lossy().to_string()) .map(|path| path.to_string_lossy().to_string())
}) })
.find(|_| true) .find(|_| true),
}; };
Ok(DebugAdapterBinary { Ok(DebugAdapterBinary {

View File

@@ -112,7 +112,7 @@ pub async fn open_test_db<M: Migrator>(db_name: &str) -> ThreadSafeConnection<M>
/// Implements a basic DB wrapper for a given domain /// Implements a basic DB wrapper for a given domain
#[macro_export] #[macro_export]
macro_rules! define_connection { macro_rules! define_connection {
(pub static ref $id:ident: $t:ident<()> = $migrations:expr; $($global:ident)?) => { (pub static ref $id:ident: $t:ident<()> = $migrations:expr_2021; $($global:ident)?) => {
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<$t>); pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<$t>);
impl ::std::ops::Deref for $t { impl ::std::ops::Deref for $t {
@@ -149,7 +149,7 @@ macro_rules! define_connection {
$t($crate::smol::block_on($crate::open_db(db_dir, scope))) $t($crate::smol::block_on($crate::open_db(db_dir, scope)))
}); });
}; };
(pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr; $($global:ident)?) => { (pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr_2021; $($global:ident)?) => {
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<( $($d),+, $t )>); pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<( $($d),+, $t )>);
impl ::std::ops::Deref for $t { impl ::std::ops::Deref for $t {

View File

@@ -284,7 +284,7 @@ impl Render for InertState {
} }
impl InertState { impl InertState {
fn render_editor(editor: &Entity<Editor>, cx: &Context<Self>) -> impl IntoElement { fn render_editor(editor: &Entity<Editor>, cx: &Context<Self>) -> impl IntoElement + use<> {
let settings = ThemeSettings::get_global(cx); let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle { let text_style = TextStyle {
color: cx.theme().colors().text, color: cx.theme().colors().text,

View File

@@ -153,7 +153,7 @@ impl Console {
}); });
} }
fn render_console(&self, cx: &Context<Self>) -> impl IntoElement { fn render_console(&self, cx: &Context<Self>) -> impl IntoElement + use<> {
let settings = ThemeSettings::get_global(cx); let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle { let text_style = TextStyle {
color: if self.console.read(cx).read_only(cx) { color: if self.console.read(cx).read_only(cx) {
@@ -180,7 +180,7 @@ impl Console {
) )
} }
fn render_query_bar(&self, cx: &Context<Self>) -> impl IntoElement { fn render_query_bar(&self, cx: &Context<Self>) -> impl IntoElement + use<> {
let settings = ThemeSettings::get_global(cx); let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle { let text_style = TextStyle {
color: if self.console.read(cx).read_only(cx) { color: if self.console.read(cx).read_only(cx) {

View File

@@ -807,14 +807,16 @@ impl VariableList {
) )
.when(!dap.value.is_empty(), |this| { .when(!dap.value.is_empty(), |this| {
this.child(div().w_full().id(variable.item_value_id()).map(|this| { this.child(div().w_full().id(variable.item_value_id()).map(|this| {
if let Some((_, editor)) = self match self
.edited_path .edited_path
.as_ref() .as_ref()
.filter(|(path, _)| path == &variable.path) .filter(|(path, _)| path == &variable.path)
{ {
this.child(div().size_full().px_2().child(editor.clone())) Some((_, editor)) => {
} else { this.child(div().size_full().px_2().child(editor.clone()))
this.text_color(cx.theme().colors().text_muted) }
_ => this
.text_color(cx.theme().colors().text_muted)
.when( .when(
!self.disabled !self.disabled
&& self && self
@@ -853,7 +855,7 @@ impl VariableList {
.when_some(variable_color, |this, color| { .when_some(variable_color, |this, color| {
this.color(Color::from(color)) this.color(Color::from(color))
}), }),
) ),
} }
})) }))
}), }),

View File

@@ -32,12 +32,9 @@ impl StartingState {
let _notify_parent = cx.spawn(async move |this, cx| { let _notify_parent = cx.spawn(async move |this, cx| {
let entity = task.await; let entity = task.await;
this.update(cx, |_, cx| { this.update(cx, |_, cx| match entity {
if let Ok(entity) = entity { Ok(entity) => cx.emit(StartingEvent::Finished(entity)),
cx.emit(StartingEvent::Finished(entity)) _ => cx.emit(StartingEvent::Failed),
} else {
cx.emit(StartingEvent::Failed)
}
}) })
.ok(); .ok();
}); });

View File

@@ -305,29 +305,32 @@ impl ProjectDiagnosticsEditor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Workspace>, cx: &mut Context<Workspace>,
) { ) {
if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) { match workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
let is_active = workspace Some(existing) => {
.active_item(cx) let is_active = workspace
.is_some_and(|item| item.item_id() == existing.item_id()); .active_item(cx)
workspace.activate_item(&existing, true, !is_active, window, cx); .is_some_and(|item| item.item_id() == existing.item_id());
} else { workspace.activate_item(&existing, true, !is_active, window, cx);
let workspace_handle = cx.entity().downgrade(); }
_ => {
let workspace_handle = cx.entity().downgrade();
let include_warnings = match cx.try_global::<IncludeWarnings>() { let include_warnings = match cx.try_global::<IncludeWarnings>() {
Some(include_warnings) => include_warnings.0, Some(include_warnings) => include_warnings.0,
None => ProjectSettings::get_global(cx).diagnostics.include_warnings, None => ProjectSettings::get_global(cx).diagnostics.include_warnings,
}; };
let diagnostics = cx.new(|cx| { let diagnostics = cx.new(|cx| {
ProjectDiagnosticsEditor::new( ProjectDiagnosticsEditor::new(
workspace.project().clone(), workspace.project().clone(),
include_warnings, include_warnings,
workspace_handle, workspace_handle,
window, window,
cx, cx,
) )
}); });
workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, window, cx); workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, window, cx);
}
} }
} }
@@ -472,129 +475,143 @@ impl ProjectDiagnosticsEditor {
} }
} }
if let Some((language_server_id, group)) = to_insert { match to_insert {
let mut group_state = DiagnosticGroupState { Some((language_server_id, group)) => {
language_server_id, let mut group_state = DiagnosticGroupState {
primary_diagnostic: group.entries[group.primary_ix].clone(), language_server_id,
primary_excerpt_ix: 0, primary_diagnostic: group.entries[group.primary_ix].clone(),
excerpts: Default::default(), primary_excerpt_ix: 0,
blocks: Default::default(), excerpts: Default::default(),
block_count: 0, blocks: Default::default(),
}; block_count: 0,
let mut pending_range: Option<(Range<Point>, Range<Point>, usize)> = None;
let mut is_first_excerpt_for_group = true;
for (ix, entry) in group.entries.iter().map(Some).chain([None]).enumerate() {
let resolved_entry = entry.map(|e| e.resolve::<Point>(&snapshot));
let expanded_range = if let Some(entry) = &resolved_entry {
Some(
context_range_for_entry(
entry.range.clone(),
context,
snapshot.clone(),
(**cx).clone(),
)
.await,
)
} else {
None
}; };
if let Some((range, context_range, start_ix)) = &mut pending_range { let mut pending_range: Option<(Range<Point>, Range<Point>, usize)> = None;
if let Some(expanded_range) = expanded_range.clone() { let mut is_first_excerpt_for_group = true;
// If the entries are overlapping or next to each-other, merge them into one excerpt. for (ix, entry) in group.entries.iter().map(Some).chain([None]).enumerate()
if context_range.end.row + 1 >= expanded_range.start.row { {
context_range.end = context_range.end.max(expanded_range.end); let resolved_entry = entry.map(|e| e.resolve::<Point>(&snapshot));
continue; let expanded_range = match &resolved_entry {
} Some(entry) => Some(
} context_range_for_entry(
entry.range.clone(),
let excerpt_id = excerpts.update(cx, |excerpts, cx| { context,
excerpts snapshot.clone(),
.insert_excerpts_after( (**cx).clone(),
prev_excerpt_id,
buffer.clone(),
[ExcerptRange {
context: context_range.clone(),
primary: Some(range.clone()),
}],
cx,
) )
.pop() .await,
.unwrap() ),
})?; _ => None,
};
prev_excerpt_id = excerpt_id; if let Some((range, context_range, start_ix)) = &mut pending_range {
first_excerpt_id.get_or_insert(prev_excerpt_id); if let Some(expanded_range) = expanded_range.clone() {
group_state.excerpts.push(excerpt_id); // If the entries are overlapping or next to each-other, merge them into one excerpt.
let header_position = (excerpt_id, language::Anchor::MIN); if context_range.end.row + 1 >= expanded_range.start.row {
context_range.end =
if is_first_excerpt_for_group { context_range.end.max(expanded_range.end);
is_first_excerpt_for_group = false; continue;
let mut primary = }
group.entries[group.primary_ix].diagnostic.clone();
primary.message =
primary.message.split('\n').next().unwrap().to_string();
group_state.block_count += 1;
blocks_to_add.push(BlockProperties {
placement: BlockPlacement::Above(header_position),
height: 2,
style: BlockStyle::Sticky,
render: diagnostic_header_renderer(primary),
priority: 0,
});
}
for entry in &group.entries[*start_ix..ix] {
let mut diagnostic = entry.diagnostic.clone();
if diagnostic.is_primary {
group_state.primary_excerpt_ix = group_state.excerpts.len() - 1;
diagnostic.message =
entry.diagnostic.message.split('\n').skip(1).collect();
} }
if !diagnostic.message.is_empty() { let excerpt_id = excerpts.update(cx, |excerpts, cx| {
excerpts
.insert_excerpts_after(
prev_excerpt_id,
buffer.clone(),
[ExcerptRange {
context: context_range.clone(),
primary: Some(range.clone()),
}],
cx,
)
.pop()
.unwrap()
})?;
prev_excerpt_id = excerpt_id;
first_excerpt_id.get_or_insert(prev_excerpt_id);
group_state.excerpts.push(excerpt_id);
let header_position = (excerpt_id, language::Anchor::MIN);
if is_first_excerpt_for_group {
is_first_excerpt_for_group = false;
let mut primary =
group.entries[group.primary_ix].diagnostic.clone();
primary.message =
primary.message.split('\n').next().unwrap().to_string();
group_state.block_count += 1; group_state.block_count += 1;
blocks_to_add.push(BlockProperties { blocks_to_add.push(BlockProperties {
placement: BlockPlacement::Below(( placement: BlockPlacement::Above(header_position),
excerpt_id, height: 2,
entry.range.start, style: BlockStyle::Sticky,
)), render: diagnostic_header_renderer(primary),
height: diagnostic.message.matches('\n').count() as u32 + 1,
style: BlockStyle::Fixed,
render: diagnostic_block_renderer(diagnostic, None, true),
priority: 0, priority: 0,
}); });
} }
for entry in &group.entries[*start_ix..ix] {
let mut diagnostic = entry.diagnostic.clone();
if diagnostic.is_primary {
group_state.primary_excerpt_ix =
group_state.excerpts.len() - 1;
diagnostic.message =
entry.diagnostic.message.split('\n').skip(1).collect();
}
if !diagnostic.message.is_empty() {
group_state.block_count += 1;
blocks_to_add.push(BlockProperties {
placement: BlockPlacement::Below((
excerpt_id,
entry.range.start,
)),
height: diagnostic.message.matches('\n').count() as u32
+ 1,
style: BlockStyle::Fixed,
render: diagnostic_block_renderer(
diagnostic, None, true,
),
priority: 0,
});
}
}
pending_range.take();
} }
pending_range.take(); if let Some(entry) = resolved_entry.as_ref() {
let range = entry.range.clone();
pending_range = Some((range, expanded_range.unwrap(), ix));
}
} }
if let Some(entry) = resolved_entry.as_ref() { this.update(cx, |this, _| {
let range = entry.range.clone(); new_group_ixs.push(this.path_states[path_ix].diagnostic_groups.len());
pending_range = Some((range, expanded_range.unwrap(), ix)); this.path_states[path_ix]
} .diagnostic_groups
.push(group_state);
})?;
} }
_ => match to_remove {
Some((_, group_state)) => {
excerpts.update(cx, |excerpts, cx| {
excerpts.remove_excerpts(group_state.excerpts.iter().copied(), cx)
})?;
blocks_to_remove.extend(group_state.blocks.iter().copied());
}
_ => match to_keep {
Some((_, group_state)) => {
prev_excerpt_id = *group_state.excerpts.last().unwrap();
first_excerpt_id.get_or_insert(prev_excerpt_id);
this.update(cx, |this, _| { this.update(cx, |this, _| {
new_group_ixs.push(this.path_states[path_ix].diagnostic_groups.len()); this.path_states[path_ix]
this.path_states[path_ix] .diagnostic_groups
.diagnostic_groups .push(group_state)
.push(group_state); })?;
})?; }
} else if let Some((_, group_state)) = to_remove { _ => {}
excerpts.update(cx, |excerpts, cx| { },
excerpts.remove_excerpts(group_state.excerpts.iter().copied(), cx) },
})?;
blocks_to_remove.extend(group_state.blocks.iter().copied());
} else if let Some((_, group_state)) = to_keep {
prev_excerpt_id = *group_state.excerpts.last().unwrap();
first_excerpt_id.get_or_insert(prev_excerpt_id);
this.update(cx, |this, _| {
this.path_states[path_ix]
.diagnostic_groups
.push(group_state)
})?;
} }
} }

View File

@@ -62,26 +62,27 @@ impl Render for DiagnosticIndicator {
.child(Label::new(warning_count.to_string()).size(LabelSize::Small)), .child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
}; };
let status = if let Some(diagnostic) = &self.current_diagnostic { let status = match &self.current_diagnostic {
let message = diagnostic.message.split('\n').next().unwrap().to_string(); Some(diagnostic) => {
Some( let message = diagnostic.message.split('\n').next().unwrap().to_string();
Button::new("diagnostic_message", message) Some(
.label_size(LabelSize::Small) Button::new("diagnostic_message", message)
.tooltip(|window, cx| { .label_size(LabelSize::Small)
Tooltip::for_action( .tooltip(|window, cx| {
"Next Diagnostic", Tooltip::for_action(
&editor::actions::GoToDiagnostic, "Next Diagnostic",
window, &editor::actions::GoToDiagnostic,
cx, window,
) cx,
}) )
.on_click(cx.listener(|this, _, window, cx| { })
this.go_to_next_diagnostic(window, cx); .on_click(cx.listener(|this, _, window, cx| {
})) this.go_to_next_diagnostic(window, cx);
.into_any_element(), }))
) .into_any_element(),
} else { )
None }
_ => None,
}; };
h_flex() h_flex()
@@ -187,14 +188,17 @@ impl StatusItemView for DiagnosticIndicator {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) { match active_pane_item.and_then(|item| item.downcast::<Editor>()) {
self.active_editor = Some(editor.downgrade()); Some(editor) => {
self._observe_active_editor = Some(cx.observe_in(&editor, window, Self::update)); self.active_editor = Some(editor.downgrade());
self.update(editor, window, cx); self._observe_active_editor = Some(cx.observe_in(&editor, window, Self::update));
} else { self.update(editor, window, cx);
self.active_editor = None; }
self.current_diagnostic = None; _ => {
self._observe_active_editor = None; self.active_editor = None;
self.current_diagnostic = None;
self._observe_active_editor = None;
}
} }
cx.notify(); cx.notify();
} }

View File

@@ -83,11 +83,12 @@ impl ToolbarItemView for ToolbarControls {
_: &mut Context<Self>, _: &mut Context<Self>,
) -> ToolbarItemLocation { ) -> ToolbarItemLocation {
if let Some(pane_item) = active_pane_item.as_ref() { if let Some(pane_item) = active_pane_item.as_ref() {
if let Some(editor) = pane_item.downcast::<ProjectDiagnosticsEditor>() { match pane_item.downcast::<ProjectDiagnosticsEditor>() {
self.editor = Some(editor.downgrade()); Some(editor) => {
ToolbarItemLocation::PrimaryRight self.editor = Some(editor.downgrade());
} else { ToolbarItemLocation::PrimaryRight
ToolbarItemLocation::Hidden }
_ => ToolbarItemLocation::Hidden,
} }
} else { } else {
ToolbarItemLocation::Hidden ToolbarItemLocation::Hidden

View File

@@ -21,10 +21,13 @@ fn main() -> Result<()> {
let preprocessor = let preprocessor =
ZedDocsPreprocessor::new().context("Failed to create ZedDocsPreprocessor")?; ZedDocsPreprocessor::new().context("Failed to create ZedDocsPreprocessor")?;
if let Some(sub_args) = matches.subcommand_matches("supports") { match matches.subcommand_matches("supports") {
handle_supports(&preprocessor, sub_args); Some(sub_args) => {
} else { handle_supports(&preprocessor, sub_args);
handle_preprocessing(&preprocessor)?; }
_ => {
handle_preprocessing(&preprocessor)?;
}
} }
Ok(()) Ok(())

View File

@@ -735,12 +735,12 @@ impl CompletionsMenu {
let completion = &completions[mat.candidate_id]; let completion = &completions[mat.candidate_id];
let sort_key = completion.sort_key(); let sort_key = completion.sort_key();
let sort_text = let sort_text = match &completion.source {
if let CompletionSource::Lsp { lsp_completion, .. } = &completion.source { CompletionSource::Lsp { lsp_completion, .. } => {
lsp_completion.sort_text.as_deref() lsp_completion.sort_text.as_deref()
} else { }
None _ => None,
}; };
let score = Reverse(OrderedFloat(mat.score)); let score = Reverse(OrderedFloat(mat.score));
if mat.score >= 0.2 { if mat.score >= 0.2 {

View File

@@ -49,7 +49,7 @@ impl<'a> CommitAvatar<'a> {
&'a self, &'a self,
window: &mut Window, window: &mut Window,
cx: &mut Context<CommitTooltip>, cx: &mut Context<CommitTooltip>,
) -> Option<impl IntoElement> { ) -> Option<impl IntoElement + use<>> {
let remote = self let remote = self
.commit .commit
.message .message

View File

@@ -226,26 +226,22 @@ impl DisplayMap {
.wrap_map .wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx)); .update(cx, |map, cx| map.sync(snapshot, edits, cx));
let mut block_map = self.block_map.write(snapshot, edits); let mut block_map = self.block_map.write(snapshot, edits);
let blocks = creases.into_iter().filter_map(|crease| { let blocks = creases.into_iter().filter_map(|crease| match crease {
if let Crease::Block { Crease::Block {
range, range,
block_height, block_height,
render_block, render_block,
block_style, block_style,
block_priority, block_priority,
.. ..
} = crease } => Some((
{ range,
Some(( render_block,
range, block_height,
render_block, block_style,
block_height, block_priority,
block_style, )),
block_priority, _ => None,
))
} else {
None
}
}); });
block_map.insert( block_map.insert(
blocks blocks
@@ -954,10 +950,9 @@ impl DisplaySnapshot {
for chunk in self.highlighted_chunks(range, false, editor_style) { for chunk in self.highlighted_chunks(range, false, editor_style) {
line.push_str(chunk.text); line.push_str(chunk.text);
let text_style = if let Some(style) = chunk.style { let text_style = match chunk.style {
Cow::Owned(editor_style.text.clone().highlight(style)) Some(style) => Cow::Owned(editor_style.text.clone().highlight(style)),
} else { _ => Cow::Borrowed(&editor_style.text),
Cow::Borrowed(&editor_style.text)
}; };
runs.push(text_style.to_run(chunk.text.len())) runs.push(text_style.to_run(chunk.text.len()))
@@ -1186,11 +1181,11 @@ impl DisplaySnapshot {
pub fn crease_for_buffer_row(&self, buffer_row: MultiBufferRow) -> Option<Crease<Point>> { pub fn crease_for_buffer_row(&self, buffer_row: MultiBufferRow) -> Option<Crease<Point>> {
let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row)); let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row));
if let Some(crease) = self match self
.crease_snapshot .crease_snapshot
.query_row(buffer_row, &self.buffer_snapshot) .query_row(buffer_row, &self.buffer_snapshot)
{ {
match crease { Some(crease) => match crease {
Crease::Inline { Crease::Inline {
range, range,
placeholder, placeholder,
@@ -1219,52 +1214,55 @@ impl DisplaySnapshot {
block_priority: *block_priority, block_priority: *block_priority,
render_toggle: render_toggle.clone(), render_toggle: render_toggle.clone(),
}), }),
} },
} else if self.starts_indent(MultiBufferRow(start.row)) _ => {
&& !self.is_line_folded(MultiBufferRow(start.row)) if self.starts_indent(MultiBufferRow(start.row))
{ && !self.is_line_folded(MultiBufferRow(start.row))
let start_line_indent = self.line_indent_for_buffer_row(buffer_row);
let max_point = self.buffer_snapshot.max_point();
let mut end = None;
for row in (buffer_row.0 + 1)..=max_point.row {
let line_indent = self.line_indent_for_buffer_row(MultiBufferRow(row));
if !line_indent.is_line_blank()
&& line_indent.raw_len() <= start_line_indent.raw_len()
{ {
let prev_row = row - 1; let start_line_indent = self.line_indent_for_buffer_row(buffer_row);
end = Some(Point::new( let max_point = self.buffer_snapshot.max_point();
prev_row, let mut end = None;
self.buffer_snapshot.line_len(MultiBufferRow(prev_row)),
)); for row in (buffer_row.0 + 1)..=max_point.row {
break; let line_indent = self.line_indent_for_buffer_row(MultiBufferRow(row));
if !line_indent.is_line_blank()
&& line_indent.raw_len() <= start_line_indent.raw_len()
{
let prev_row = row - 1;
end = Some(Point::new(
prev_row,
self.buffer_snapshot.line_len(MultiBufferRow(prev_row)),
));
break;
}
}
let mut row_before_line_breaks = end.unwrap_or(max_point);
while row_before_line_breaks.row > start.row
&& self
.buffer_snapshot
.is_line_blank(MultiBufferRow(row_before_line_breaks.row))
{
row_before_line_breaks.row -= 1;
}
row_before_line_breaks = Point::new(
row_before_line_breaks.row,
self.buffer_snapshot
.line_len(MultiBufferRow(row_before_line_breaks.row)),
);
Some(Crease::Inline {
range: start..row_before_line_breaks,
placeholder: self.fold_placeholder.clone(),
render_toggle: None,
render_trailer: None,
metadata: None,
})
} else {
None
} }
} }
let mut row_before_line_breaks = end.unwrap_or(max_point);
while row_before_line_breaks.row > start.row
&& self
.buffer_snapshot
.is_line_blank(MultiBufferRow(row_before_line_breaks.row))
{
row_before_line_breaks.row -= 1;
}
row_before_line_breaks = Point::new(
row_before_line_breaks.row,
self.buffer_snapshot
.line_len(MultiBufferRow(row_before_line_breaks.row)),
);
Some(Crease::Inline {
range: start..row_before_line_breaks,
placeholder: self.fold_placeholder.clone(),
render_toggle: None,
render_trailer: None,
metadata: None,
})
} else {
None
} }
} }
@@ -1480,7 +1478,7 @@ pub mod tests {
}); });
let buffer = cx.update(|cx| { let buffer = cx.update(|cx| {
if rng.gen() { if rng.r#gen() {
let len = rng.gen_range(0..10); let len = rng.gen_range(0..10);
let text = util::RandomCharIter::new(&mut rng) let text = util::RandomCharIter::new(&mut rng)
.take(len) .take(len)
@@ -1542,7 +1540,7 @@ pub mod tests {
} }
30..=44 => { 30..=44 => {
map.update(cx, |map, cx| { map.update(cx, |map, cx| {
if rng.gen() || blocks.is_empty() { if rng.r#gen() || blocks.is_empty() {
let buffer = map.snapshot(cx).buffer_snapshot; let buffer = map.snapshot(cx).buffer_snapshot;
let block_properties = (0..rng.gen_range(1..=1)) let block_properties = (0..rng.gen_range(1..=1))
.map(|_| { .map(|_| {
@@ -1552,7 +1550,7 @@ pub mod tests {
Bias::Left, Bias::Left,
)); ));
let placement = if rng.gen() { let placement = if rng.r#gen() {
BlockPlacement::Above(position) BlockPlacement::Above(position)
} else { } else {
BlockPlacement::Below(position) BlockPlacement::Below(position)
@@ -1596,7 +1594,7 @@ pub mod tests {
}); });
} }
if rng.gen() && fold_count > 0 { if rng.r#gen() && fold_count > 0 {
log::info!("unfolding ranges: {:?}", ranges); log::info!("unfolding ranges: {:?}", ranges);
map.update(cx, |map, cx| { map.update(cx, |map, cx| {
map.unfold_intersecting(ranges, true, cx); map.unfold_intersecting(ranges, true, cx);

View File

@@ -1351,11 +1351,14 @@ impl BlockSnapshot {
{ {
break; break;
} }
if let Some(block) = &transform.block { match &transform.block {
cursor.next(&()); Some(block) => {
return Some((start_row, block)); cursor.next(&());
} else { return Some((start_row, block));
cursor.next(&()); }
_ => {
cursor.next(&());
}
} }
} }
None None
@@ -1405,12 +1408,17 @@ impl BlockSnapshot {
cursor.seek(&wrap_row, Bias::Left, &()); cursor.seek(&wrap_row, Bias::Left, &());
while let Some(transform) = cursor.item() { while let Some(transform) = cursor.item() {
if let Some(block) = transform.block.as_ref() { match transform.block.as_ref() {
if block.id() == block_id { Some(block) => {
return Some(block.clone()); if block.id() == block_id {
return Some(block.clone());
}
}
_ => {
if *cursor.start() > wrap_row {
break;
}
} }
} else if *cursor.start() > wrap_row {
break;
} }
cursor.next(&()); cursor.next(&());
@@ -1482,18 +1490,23 @@ impl BlockSnapshot {
pub(super) fn line_len(&self, row: BlockRow) -> u32 { pub(super) fn line_len(&self, row: BlockRow) -> u32 {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&()); let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
cursor.seek(&BlockRow(row.0), Bias::Right, &()); cursor.seek(&BlockRow(row.0), Bias::Right, &());
if let Some(transform) = cursor.item() { match cursor.item() {
let (output_start, input_start) = cursor.start(); Some(transform) => {
let overshoot = row.0 - output_start.0; let (output_start, input_start) = cursor.start();
if transform.block.is_some() { let overshoot = row.0 - output_start.0;
0 if transform.block.is_some() {
} else { 0
self.wrap_snapshot.line_len(input_start.0 + overshoot) } else {
self.wrap_snapshot.line_len(input_start.0 + overshoot)
}
}
_ => {
if row.0 == 0 {
0
} else {
panic!("row out of range");
}
} }
} else if row.0 == 0 {
0
} else {
panic!("row out of range");
} }
} }
@@ -1536,52 +1549,57 @@ impl BlockSnapshot {
let mut reversed = false; let mut reversed = false;
loop { loop {
if let Some(transform) = cursor.item() { match cursor.item() {
let (output_start_row, input_start_row) = cursor.start(); Some(transform) => {
let (output_end_row, input_end_row) = cursor.end(&()); let (output_start_row, input_start_row) = cursor.start();
let output_start = Point::new(output_start_row.0, 0); let (output_end_row, input_end_row) = cursor.end(&());
let input_start = Point::new(input_start_row.0, 0); let output_start = Point::new(output_start_row.0, 0);
let input_end = Point::new(input_end_row.0, 0); let input_start = Point::new(input_start_row.0, 0);
let input_end = Point::new(input_end_row.0, 0);
match transform.block.as_ref() { match transform.block.as_ref() {
Some(block) => { Some(block) => {
if block.is_replacement() { if block.is_replacement() {
if ((bias == Bias::Left || search_left) && output_start <= point.0) if ((bias == Bias::Left || search_left) && output_start <= point.0)
|| (!search_left && output_start >= point.0) || (!search_left && output_start >= point.0)
{ {
return BlockPoint(output_start); return BlockPoint(output_start);
}
}
}
None => {
let input_point = if point.row >= output_end_row.0 {
let line_len = self.wrap_snapshot.line_len(input_end_row.0 - 1);
self.wrap_snapshot
.clip_point(WrapPoint::new(input_end_row.0 - 1, line_len), bias)
} else {
let output_overshoot = point.0.saturating_sub(output_start);
self.wrap_snapshot
.clip_point(WrapPoint(input_start + output_overshoot), bias)
};
if (input_start..input_end).contains(&input_point.0) {
let input_overshoot = input_point.0.saturating_sub(input_start);
return BlockPoint(output_start + input_overshoot);
} }
} }
} }
None => {
let input_point = if point.row >= output_end_row.0 {
let line_len = self.wrap_snapshot.line_len(input_end_row.0 - 1);
self.wrap_snapshot
.clip_point(WrapPoint::new(input_end_row.0 - 1, line_len), bias)
} else {
let output_overshoot = point.0.saturating_sub(output_start);
self.wrap_snapshot
.clip_point(WrapPoint(input_start + output_overshoot), bias)
};
if (input_start..input_end).contains(&input_point.0) { if search_left {
let input_overshoot = input_point.0.saturating_sub(input_start); cursor.prev(&());
return BlockPoint(output_start + input_overshoot); } else {
} cursor.next(&());
} }
} }
_ => {
if search_left { if reversed {
cursor.prev(&()); return self.max_point();
} else { } else {
cursor.next(&()); reversed = true;
search_left = !search_left;
cursor.seek(&BlockRow(point.row), Bias::Right, &());
}
} }
} else if reversed {
return self.max_point();
} else {
reversed = true;
search_left = !search_left;
cursor.seek(&BlockRow(point.row), Bias::Right, &());
} }
} }
} }
@@ -1589,26 +1607,27 @@ impl BlockSnapshot {
pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint { pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&()); let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &()); cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
if let Some(transform) = cursor.item() { match cursor.item() {
if transform.block.is_some() { Some(transform) => {
BlockPoint::new(cursor.start().1 .0, 0) if transform.block.is_some() {
} else { BlockPoint::new(cursor.start().1 .0, 0)
let (input_start_row, output_start_row) = cursor.start(); } else {
let input_start = Point::new(input_start_row.0, 0); let (input_start_row, output_start_row) = cursor.start();
let output_start = Point::new(output_start_row.0, 0); let input_start = Point::new(input_start_row.0, 0);
let input_overshoot = wrap_point.0 - input_start; let output_start = Point::new(output_start_row.0, 0);
BlockPoint(output_start + input_overshoot) let input_overshoot = wrap_point.0 - input_start;
BlockPoint(output_start + input_overshoot)
}
} }
} else { _ => self.max_point(),
self.max_point()
} }
} }
pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint { pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&()); let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
cursor.seek(&BlockRow(block_point.row), Bias::Right, &()); cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
if let Some(transform) = cursor.item() { match cursor.item() {
match transform.block.as_ref() { Some(transform) => match transform.block.as_ref() {
Some(block) => { Some(block) => {
if block.place_below() { if block.place_below() {
let wrap_row = cursor.start().1 .0 - 1; let wrap_row = cursor.start().1 .0 - 1;
@@ -1627,9 +1646,8 @@ impl BlockSnapshot {
let wrap_row = cursor.start().1 .0 + overshoot; let wrap_row = cursor.start().1 .0 + overshoot;
WrapPoint::new(wrap_row, block_point.column) WrapPoint::new(wrap_row, block_point.column)
} }
} },
} else { _ => self.wrap_snapshot.max_point(),
self.wrap_snapshot.max_point()
} }
} }
} }
@@ -1702,20 +1720,23 @@ impl<'a> Iterator for BlockChunks<'a> {
} }
if self.input_chunk.text.is_empty() { if self.input_chunk.text.is_empty() {
if let Some(input_chunk) = self.input_chunks.next() { match self.input_chunks.next() {
self.input_chunk = input_chunk; Some(input_chunk) => {
} else { self.input_chunk = input_chunk;
if self.output_row < self.max_output_row { }
self.output_row += 1; _ => {
self.advance(); if self.output_row < self.max_output_row {
if self.transforms.item().is_some() { self.output_row += 1;
return Some(Chunk { self.advance();
text: "\n", if self.transforms.item().is_some() {
..Default::default() return Some(Chunk {
}); text: "\n",
} ..Default::default()
});
}
}
return None;
} }
return None;
} }
} }
@@ -1783,18 +1804,19 @@ impl Iterator for BlockRows<'_> {
} }
let transform = self.transforms.item()?; let transform = self.transforms.item()?;
if let Some(block) = transform.block.as_ref() { match transform.block.as_ref() {
if block.is_replacement() && self.transforms.start().0 == self.output_row { Some(block) => {
if matches!(block, Block::FoldedBuffer { .. }) { if block.is_replacement() && self.transforms.start().0 == self.output_row {
Some(RowInfo::default()) if matches!(block, Block::FoldedBuffer { .. }) {
Some(RowInfo::default())
} else {
Some(self.input_rows.next().unwrap())
}
} else { } else {
Some(self.input_rows.next().unwrap()) Some(RowInfo::default())
} }
} else {
Some(RowInfo::default())
} }
} else { _ => Some(self.input_rows.next().unwrap()),
Some(self.input_rows.next().unwrap())
} }
} }
} }
@@ -2913,7 +2935,7 @@ mod tests {
log::info!("Wrap width: {:?}", wrap_width); log::info!("Wrap width: {:?}", wrap_width);
log::info!("Excerpt Header Height: {:?}", excerpt_header_height); log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
let is_singleton = rng.gen(); let is_singleton = rng.r#gen();
let buffer = if is_singleton { let buffer = if is_singleton {
let len = rng.gen_range(0..10); let len = rng.gen_range(0..10);
let text = RandomCharIter::new(&mut rng).take(len).collect::<String>(); let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();

View File

@@ -602,21 +602,24 @@ impl FoldSnapshot {
if let Some(transform) = cursor.item() { if let Some(transform) = cursor.item() {
let start_in_transform = range.start.0 - cursor.start().0 .0; let start_in_transform = range.start.0 - cursor.start().0 .0;
let end_in_transform = cmp::min(range.end, cursor.end(&()).0).0 - cursor.start().0 .0; let end_in_transform = cmp::min(range.end, cursor.end(&()).0).0 - cursor.start().0 .0;
if let Some(placeholder) = transform.placeholder.as_ref() { match transform.placeholder.as_ref() {
summary = TextSummary::from( Some(placeholder) => {
&placeholder.text summary = TextSummary::from(
[start_in_transform.column as usize..end_in_transform.column as usize], &placeholder.text
); [start_in_transform.column as usize..end_in_transform.column as usize],
} else { );
let inlay_start = self }
.inlay_snapshot _ => {
.to_offset(InlayPoint(cursor.start().1 .0 + start_in_transform)); let inlay_start = self
let inlay_end = self .inlay_snapshot
.inlay_snapshot .to_offset(InlayPoint(cursor.start().1 .0 + start_in_transform));
.to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform)); let inlay_end = self
summary = self .inlay_snapshot
.inlay_snapshot .to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform));
.text_summary_for_range(inlay_start..inlay_end); summary = self
.inlay_snapshot
.text_summary_for_range(inlay_start..inlay_end);
}
} }
} }
@@ -627,17 +630,21 @@ impl FoldSnapshot {
.output; .output;
if let Some(transform) = cursor.item() { if let Some(transform) = cursor.item() {
let end_in_transform = range.end.0 - cursor.start().0 .0; let end_in_transform = range.end.0 - cursor.start().0 .0;
if let Some(placeholder) = transform.placeholder.as_ref() { match transform.placeholder.as_ref() {
summary += Some(placeholder) => {
TextSummary::from(&placeholder.text[..end_in_transform.column as usize]); summary += TextSummary::from(
} else { &placeholder.text[..end_in_transform.column as usize],
let inlay_start = self.inlay_snapshot.to_offset(cursor.start().1); );
let inlay_end = self }
.inlay_snapshot _ => {
.to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform)); let inlay_start = self.inlay_snapshot.to_offset(cursor.start().1);
summary += self let inlay_end = self
.inlay_snapshot .inlay_snapshot
.text_summary_for_range(inlay_start..inlay_end); .to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform));
summary += self
.inlay_snapshot
.text_summary_for_range(inlay_start..inlay_end);
}
} }
} }
} }
@@ -822,22 +829,23 @@ impl FoldSnapshot {
pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint { pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint {
let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&()); let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&());
cursor.seek(&point, Bias::Right, &()); cursor.seek(&point, Bias::Right, &());
if let Some(transform) = cursor.item() { match cursor.item() {
let transform_start = cursor.start().0 .0; Some(transform) => {
if transform.placeholder.is_some() { let transform_start = cursor.start().0 .0;
if point.0 == transform_start || matches!(bias, Bias::Left) { if transform.placeholder.is_some() {
FoldPoint(transform_start) if point.0 == transform_start || matches!(bias, Bias::Left) {
FoldPoint(transform_start)
} else {
FoldPoint(cursor.end(&()).0 .0)
}
} else { } else {
FoldPoint(cursor.end(&()).0 .0) let overshoot = InlayPoint(point.0 - transform_start);
let inlay_point = cursor.start().1 + overshoot;
let clipped_inlay_point = self.inlay_snapshot.clip_point(inlay_point, bias);
FoldPoint(cursor.start().0 .0 + (clipped_inlay_point - cursor.start().1).0)
} }
} else {
let overshoot = InlayPoint(point.0 - transform_start);
let inlay_point = cursor.start().1 + overshoot;
let clipped_inlay_point = self.inlay_snapshot.clip_point(inlay_point, bias);
FoldPoint(cursor.start().0 .0 + (clipped_inlay_point - cursor.start().1).0)
} }
} else { _ => FoldPoint(self.transforms.summary().output.lines),
FoldPoint(self.transforms.summary().output.lines)
} }
} }
} }
@@ -1616,7 +1624,7 @@ mod tests {
let len = rng.gen_range(0..10); let len = rng.gen_range(0..10);
let text = RandomCharIter::new(&mut rng).take(len).collect::<String>(); let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
let buffer = if rng.gen() { let buffer = if rng.r#gen() {
MultiBuffer::build_simple(&text, cx) MultiBuffer::build_simple(&text, cx)
} else { } else {
MultiBuffer::build_random(&mut rng, cx) MultiBuffer::build_random(&mut rng, cx)
@@ -1962,7 +1970,7 @@ mod tests {
let start = buffer.clip_offset(rng.gen_range(0..=end), Left); let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
to_unfold.push(start..end); to_unfold.push(start..end);
} }
let inclusive = rng.gen(); let inclusive = rng.r#gen();
log::info!("unfolding {:?} (inclusive: {})", to_unfold, inclusive); log::info!("unfolding {:?} (inclusive: {})", to_unfold, inclusive);
let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]); let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
snapshot_edits.push((snapshot, edits)); snapshot_edits.push((snapshot, edits));

View File

@@ -610,9 +610,9 @@ impl InlayMap {
let mut to_insert = Vec::new(); let mut to_insert = Vec::new();
let snapshot = &mut self.snapshot; let snapshot = &mut self.snapshot;
for i in 0..rng.gen_range(1..=5) { for i in 0..rng.gen_range(1..=5) {
if self.inlays.is_empty() || rng.gen() { if self.inlays.is_empty() || rng.r#gen() {
let position = snapshot.buffer.random_byte_range(0, rng).start; let position = snapshot.buffer.random_byte_range(0, rng).start;
let bias = if rng.gen() { Bias::Left } else { Bias::Right }; let bias = if rng.r#gen() { Bias::Left } else { Bias::Right };
let len = if rng.gen_bool(0.01) { let len = if rng.gen_bool(0.01) {
0 0
} else { } else {
@@ -809,33 +809,39 @@ impl InlaySnapshot {
match cursor.item() { match cursor.item() {
Some(Transform::Isomorphic(transform)) => { Some(Transform::Isomorphic(transform)) => {
if cursor.start().0 == point { if cursor.start().0 == point {
if let Some(Transform::Inlay(inlay)) = cursor.prev_item() { match cursor.prev_item() {
if inlay.position.bias() == Bias::Left { Some(Transform::Inlay(inlay)) => {
return point; if inlay.position.bias() == Bias::Left {
} else if bias == Bias::Left { return point;
cursor.prev(&()); } else if bias == Bias::Left {
} else if transform.first_line_chars == 0 { cursor.prev(&());
point.0 += Point::new(1, 0); } else if transform.first_line_chars == 0 {
} else { point.0 += Point::new(1, 0);
point.0 += Point::new(0, 1); } else {
point.0 += Point::new(0, 1);
}
}
_ => {
return point;
} }
} else {
return point;
} }
} else if cursor.end(&()).0 == point { } else if cursor.end(&()).0 == point {
if let Some(Transform::Inlay(inlay)) = cursor.next_item() { match cursor.next_item() {
if inlay.position.bias() == Bias::Right { Some(Transform::Inlay(inlay)) => {
return point; if inlay.position.bias() == Bias::Right {
} else if bias == Bias::Right { return point;
cursor.next(&()); } else if bias == Bias::Right {
} else if point.0.column == 0 { cursor.next(&());
point.0.row -= 1; } else if point.0.column == 0 {
point.0.column = self.line_len(point.0.row); point.0.row -= 1;
} else { point.0.column = self.line_len(point.0.row);
point.0.column -= 1; } else {
point.0.column -= 1;
}
}
_ => {
return point;
} }
} else {
return point;
} }
} else { } else {
let overshoot = point.0 - cursor.start().0 .0; let overshoot = point.0 - cursor.start().0 .0;
@@ -1500,7 +1506,7 @@ mod tests {
.unwrap_or(10); .unwrap_or(10);
let len = rng.gen_range(0..30); let len = rng.gen_range(0..30);
let buffer = if rng.gen() { let buffer = if rng.r#gen() {
let text = util::RandomCharIter::new(&mut rng) let text = util::RandomCharIter::new(&mut rng)
.take(len) .take(len)
.collect::<String>(); .collect::<String>();
@@ -1709,19 +1715,22 @@ mod tests {
buffer_point buffer_point
); );
if let Some(ch) = buffer_chars.next() { match buffer_chars.next() {
if ch == '\n' { Some(ch) => {
buffer_point += Point::new(1, 0); if ch == '\n' {
} else { buffer_point += Point::new(1, 0);
buffer_point += Point::new(0, ch.len_utf8() as u32); } else {
} buffer_point += Point::new(0, ch.len_utf8() as u32);
}
// Ensure that moving forward in the buffer always moves the inlay point forward as well. // Ensure that moving forward in the buffer always moves the inlay point forward as well.
let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point); let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
assert!(new_inlay_point > inlay_point); assert!(new_inlay_point > inlay_point);
inlay_point = new_inlay_point; inlay_point = new_inlay_point;
} else { }
break; _ => {
break;
}
} }
} }

View File

@@ -534,15 +534,18 @@ impl<'a> Iterator for TabChunks<'a> {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if self.chunk.text.is_empty() { if self.chunk.text.is_empty() {
if let Some(chunk) = self.fold_chunks.next() { match self.fold_chunks.next() {
self.chunk = chunk; Some(chunk) => {
if self.inside_leading_tab { self.chunk = chunk;
self.chunk.text = &self.chunk.text[1..]; if self.inside_leading_tab {
self.inside_leading_tab = false; self.chunk.text = &self.chunk.text[1..];
self.input_column += 1; self.inside_leading_tab = false;
self.input_column += 1;
}
}
_ => {
return None;
} }
} else {
return None;
} }
} }
@@ -738,7 +741,7 @@ mod tests {
fn test_random_tabs(cx: &mut gpui::App, mut rng: StdRng) { fn test_random_tabs(cx: &mut gpui::App, mut rng: StdRng) {
let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap(); let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap();
let len = rng.gen_range(0..30); let len = rng.gen_range(0..30);
let buffer = if rng.gen() { let buffer = if rng.r#gen() {
let text = util::RandomCharIter::new(&mut rng) let text = util::RandomCharIter::new(&mut rng)
.take(len) .take(len)
.collect::<String>(); .collect::<String>();

View File

@@ -1207,7 +1207,7 @@ mod tests {
log::info!("Wrap width: {:?}", wrap_width); log::info!("Wrap width: {:?}", wrap_width);
let buffer = cx.update(|cx| { let buffer = cx.update(|cx| {
if rng.gen() { if rng.r#gen() {
MultiBuffer::build_random(&mut rng, cx) MultiBuffer::build_random(&mut rng, cx)
} else { } else {
let len = rng.gen_range(0..10); let len = rng.gen_range(0..10);

File diff suppressed because it is too large Load Diff

View File

@@ -9526,15 +9526,17 @@ async fn test_word_completion(cx: &mut TestAppContext) {
cx.condition(|editor, _| editor.context_menu_visible()) cx.condition(|editor, _| editor.context_menu_visible())
.await; .await;
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() match editor.context_menu.borrow_mut().as_ref() {
{ Some(CodeContextMenu::Completions(menu)) => {
assert_eq!( assert_eq!(
completion_menu_entries(&menu), completion_menu_entries(&menu),
&["first", "last"], &["first", "last"],
"When LSP server is fast to reply, no fallback word completions are used" "When LSP server is fast to reply, no fallback word completions are used"
); );
} else { }
panic!("expected completion menu to be open"); _ => {
panic!("expected completion menu to be open");
}
} }
editor.cancel(&Cancel, window, cx); editor.cancel(&Cancel, window, cx);
}); });
@@ -9550,13 +9552,13 @@ async fn test_word_completion(cx: &mut TestAppContext) {
cx.condition(|editor, _| editor.context_menu_visible()) cx.condition(|editor, _| editor.context_menu_visible())
.await; .await;
cx.update_editor(|editor, _, _| { cx.update_editor(|editor, _, _| {
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() match editor.context_menu.borrow_mut().as_ref()
{ { Some(CodeContextMenu::Completions(menu)) => {
assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"], assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
"When LSP server is slow, document words can be shown instead, if configured accordingly"); "When LSP server is slow, document words can be shown instead, if configured accordingly");
} else { } _ => {
panic!("expected completion menu to be open"); panic!("expected completion menu to be open");
} }}
}); });
} }
@@ -9609,16 +9611,16 @@ async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext
cx.condition(|editor, _| editor.context_menu_visible()) cx.condition(|editor, _| editor.context_menu_visible())
.await; .await;
cx.update_editor(|editor, _, _| { cx.update_editor(|editor, _, _| {
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() match editor.context_menu.borrow_mut().as_ref()
{ { Some(CodeContextMenu::Completions(menu)) => {
assert_eq!( assert_eq!(
completion_menu_entries(&menu), completion_menu_entries(&menu),
&["first", "last", "second"], &["first", "last", "second"],
"Word completions that has the same edit as the any of the LSP ones, should not be proposed" "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
); );
} else { } _ => {
panic!("expected completion menu to be open"); panic!("expected completion menu to be open");
} }}
}); });
} }
@@ -9663,34 +9665,36 @@ async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
cx.executor().run_until_parked(); cx.executor().run_until_parked();
cx.condition(|editor, _| editor.context_menu_visible()) cx.condition(|editor, _| editor.context_menu_visible())
.await; .await;
cx.update_editor(|editor, _, _| { cx.update_editor(
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() |editor, _, _| match editor.context_menu.borrow_mut().as_ref() {
{ Some(CodeContextMenu::Completions(menu)) => {
assert_eq!( assert_eq!(
completion_menu_entries(&menu), completion_menu_entries(&menu),
&["first", "last", "second"], &["first", "last", "second"],
"`ShowWordCompletions` action should show word completions" "`ShowWordCompletions` action should show word completions"
); );
} else { }
panic!("expected completion menu to be open"); _ => {
} panic!("expected completion menu to be open");
}); }
},
);
cx.simulate_keystroke("l"); cx.simulate_keystroke("l");
cx.executor().run_until_parked(); cx.executor().run_until_parked();
cx.condition(|editor, _| editor.context_menu_visible()) cx.condition(|editor, _| editor.context_menu_visible())
.await; .await;
cx.update_editor(|editor, _, _| { cx.update_editor(|editor, _, _| {
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() match editor.context_menu.borrow_mut().as_ref()
{ { Some(CodeContextMenu::Completions(menu)) => {
assert_eq!( assert_eq!(
completion_menu_entries(&menu), completion_menu_entries(&menu),
&["last"], &["last"],
"After showing word completions, further editing should filter them and not query the LSP" "After showing word completions, further editing should filter them and not query the LSP"
); );
} else { } _ => {
panic!("expected completion menu to be open"); panic!("expected completion menu to be open");
} }}
}); });
} }
@@ -9719,16 +9723,16 @@ async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
cx.condition(|editor, _| editor.context_menu_visible()) cx.condition(|editor, _| editor.context_menu_visible())
.await; .await;
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() match editor.context_menu.borrow_mut().as_ref()
{ { Some(CodeContextMenu::Completions(menu)) => {
assert_eq!( assert_eq!(
completion_menu_entries(&menu), completion_menu_entries(&menu),
&["let"], &["let"],
"With no digits in the completion query, no digits should be in the word completions" "With no digits in the completion query, no digits should be in the word completions"
); );
} else { } _ => {
panic!("expected completion menu to be open"); panic!("expected completion menu to be open");
} }}
editor.cancel(&Cancel, window, cx); editor.cancel(&Cancel, window, cx);
}); });
@@ -9745,13 +9749,13 @@ async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
cx.condition(|editor, _| editor.context_menu_visible()) cx.condition(|editor, _| editor.context_menu_visible())
.await; .await;
cx.update_editor(|editor, _, _| { cx.update_editor(|editor, _, _| {
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() match editor.context_menu.borrow_mut().as_ref()
{ { Some(CodeContextMenu::Completions(menu)) => {
assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \ assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)"); return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
} else { } _ => {
panic!("expected completion menu to be open"); panic!("expected completion menu to be open");
} }}
}); });
} }
@@ -9914,8 +9918,8 @@ async fn test_multiline_completion(cx: &mut TestAppContext) {
editor.update(cx, |editor, _| { editor.update(cx, |editor, _| {
assert!(editor.context_menu_visible()); assert!(editor.context_menu_visible());
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() match editor.context_menu.borrow_mut().as_ref()
{ { Some(CodeContextMenu::Completions(menu)) => {
let completion_labels = menu let completion_labels = menu
.completions .completions
.borrow() .borrow()
@@ -9944,9 +9948,9 @@ async fn test_multiline_completion(cx: &mut TestAppContext) {
"Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}" "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
); );
} }
} else { } _ => {
panic!("expected completion menu to be open"); panic!("expected completion menu to be open");
} }}
}); });
} }
@@ -9981,38 +9985,44 @@ async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
cx.simulate_keystroke("."); cx.simulate_keystroke(".");
cx.executor().run_until_parked(); cx.executor().run_until_parked();
cx.update_editor(|editor, _, _| { cx.update_editor(
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() |editor, _, _| match editor.context_menu.borrow_mut().as_ref() {
{ Some(CodeContextMenu::Completions(menu)) => {
assert_eq!(completion_menu_entries(&menu), &["first", "last"]); assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
} else { }
panic!("expected completion menu to be open"); _ => {
} panic!("expected completion menu to be open");
}); }
},
);
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
editor.move_page_down(&MovePageDown::default(), window, cx); editor.move_page_down(&MovePageDown::default(), window, cx);
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() match editor.context_menu.borrow_mut().as_ref() {
{ Some(CodeContextMenu::Completions(menu)) => {
assert!( assert!(
menu.selected_item == 1, menu.selected_item == 1,
"expected PageDown to select the last item from the context menu" "expected PageDown to select the last item from the context menu"
); );
} else { }
panic!("expected completion menu to stay open after PageDown"); _ => {
panic!("expected completion menu to stay open after PageDown");
}
} }
}); });
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
editor.move_page_up(&MovePageUp::default(), window, cx); editor.move_page_up(&MovePageUp::default(), window, cx);
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() match editor.context_menu.borrow_mut().as_ref() {
{ Some(CodeContextMenu::Completions(menu)) => {
assert!( assert!(
menu.selected_item == 0, menu.selected_item == 0,
"expected PageUp to select the first item from the context menu" "expected PageUp to select the first item from the context menu"
); );
} else { }
panic!("expected completion menu to stay open after PageUp"); _ => {
panic!("expected completion menu to stay open after PageUp");
}
} }
}); });
} }
@@ -10074,17 +10084,19 @@ async fn test_completion_sort(cx: &mut TestAppContext) {
}); });
cx.executor().run_until_parked(); cx.executor().run_until_parked();
cx.update_editor(|editor, _, _| { cx.update_editor(
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() |editor, _, _| match editor.context_menu.borrow_mut().as_ref() {
{ Some(CodeContextMenu::Completions(menu)) => {
assert_eq!( assert_eq!(
completion_menu_entries(&menu), completion_menu_entries(&menu),
&["r", "ret", "Range", "return"] &["r", "ret", "Range", "return"]
); );
} else { }
panic!("expected completion menu to be open"); _ => {
} panic!("expected completion menu to be open");
}); }
},
);
} }
#[gpui::test] #[gpui::test]
@@ -13056,42 +13068,48 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestA
// word character in the 'element' scope, which contains the cursor. // word character in the 'element' scope, which contains the cursor.
cx.simulate_keystroke("-"); cx.simulate_keystroke("-");
cx.executor().run_until_parked(); cx.executor().run_until_parked();
cx.update_editor(|editor, _, _| { cx.update_editor(
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() |editor, _, _| match editor.context_menu.borrow_mut().as_ref() {
{ Some(CodeContextMenu::Completions(menu)) => {
assert_eq!( assert_eq!(
completion_menu_entries(&menu), completion_menu_entries(&menu),
&["bg-red", "bg-blue", "bg-yellow"] &["bg-red", "bg-blue", "bg-yellow"]
); );
} else { }
panic!("expected completion menu to be open"); _ => {
} panic!("expected completion menu to be open");
}); }
},
);
cx.simulate_keystroke("l"); cx.simulate_keystroke("l");
cx.executor().run_until_parked(); cx.executor().run_until_parked();
cx.update_editor(|editor, _, _| { cx.update_editor(
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() |editor, _, _| match editor.context_menu.borrow_mut().as_ref() {
{ Some(CodeContextMenu::Completions(menu)) => {
assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]); assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
} else { }
panic!("expected completion menu to be open"); _ => {
} panic!("expected completion menu to be open");
}); }
},
);
// When filtering completions, consider the character after the '-' to // When filtering completions, consider the character after the '-' to
// be the start of a subword. // be the start of a subword.
cx.set_state(r#"<p class="yelˇ" />"#); cx.set_state(r#"<p class="yelˇ" />"#);
cx.simulate_keystroke("l"); cx.simulate_keystroke("l");
cx.executor().run_until_parked(); cx.executor().run_until_parked();
cx.update_editor(|editor, _, _| { cx.update_editor(
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref() |editor, _, _| match editor.context_menu.borrow_mut().as_ref() {
{ Some(CodeContextMenu::Completions(menu)) => {
assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]); assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
} else { }
panic!("expected completion menu to be open"); _ => {
} panic!("expected completion menu to be open");
}); }
},
);
} }
fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> { fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
@@ -18691,7 +18709,7 @@ fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Cont
pub fn handle_signature_help_request( pub fn handle_signature_help_request(
cx: &mut EditorLspTestContext, cx: &mut EditorLspTestContext,
mocked_response: lsp::SignatureHelp, mocked_response: lsp::SignatureHelp,
) -> impl Future<Output = ()> { ) -> impl Future<Output = ()> + use<> {
let mut request = let mut request =
cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| { cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
let mocked_response = mocked_response.clone(); let mocked_response = mocked_response.clone();
@@ -18711,7 +18729,7 @@ pub fn handle_completion_request(
marked_string: &str, marked_string: &str,
completions: Vec<&'static str>, completions: Vec<&'static str>,
counter: Arc<AtomicUsize>, counter: Arc<AtomicUsize>,
) -> impl Future<Output = ()> { ) -> impl Future<Output = ()> + use<> {
let complete_from_marker: TextRangeMarker = '|'.into(); let complete_from_marker: TextRangeMarker = '|'.into();
let replace_range_marker: TextRangeMarker = ('<', '>').into(); let replace_range_marker: TextRangeMarker = ('<', '>').into();
let (_, mut marked_ranges) = marked_text_ranges_by( let (_, mut marked_ranges) = marked_text_ranges_by(
@@ -18758,7 +18776,7 @@ pub fn handle_completion_request(
fn handle_resolve_completion_request( fn handle_resolve_completion_request(
cx: &mut EditorLspTestContext, cx: &mut EditorLspTestContext,
edits: Option<Vec<(&'static str, &'static str)>>, edits: Option<Vec<(&'static str, &'static str)>>,
) -> impl Future<Output = ()> { ) -> impl Future<Output = ()> + use<> {
let edits = edits.map(|edits| { let edits = edits.map(|edits| {
edits edits
.iter() .iter()

View File

@@ -432,68 +432,95 @@ impl EditorElement {
register_action(editor, window, Editor::expand_all_diff_hunks); register_action(editor, window, Editor::expand_all_diff_hunks);
register_action(editor, window, |editor, action, window, cx| { register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.format(action, window, cx) { match editor.format(action, window, cx) {
task.detach_and_notify_err(window, cx); Some(task) => {
} else { task.detach_and_notify_err(window, cx);
cx.propagate(); }
_ => {
cx.propagate();
}
} }
}); });
register_action(editor, window, |editor, action, window, cx| { register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.format_selections(action, window, cx) { match editor.format_selections(action, window, cx) {
task.detach_and_notify_err(window, cx); Some(task) => {
} else { task.detach_and_notify_err(window, cx);
cx.propagate(); }
_ => {
cx.propagate();
}
} }
}); });
register_action(editor, window, |editor, action, window, cx| { register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.organize_imports(action, window, cx) { match editor.organize_imports(action, window, cx) {
task.detach_and_notify_err(window, cx); Some(task) => {
} else { task.detach_and_notify_err(window, cx);
cx.propagate(); }
_ => {
cx.propagate();
}
} }
}); });
register_action(editor, window, Editor::restart_language_server); register_action(editor, window, Editor::restart_language_server);
register_action(editor, window, Editor::show_character_palette); register_action(editor, window, Editor::show_character_palette);
register_action(editor, window, |editor, action, window, cx| { register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.confirm_completion(action, window, cx) { match editor.confirm_completion(action, window, cx) {
task.detach_and_notify_err(window, cx); Some(task) => {
} else { task.detach_and_notify_err(window, cx);
cx.propagate(); }
_ => {
cx.propagate();
}
} }
}); });
register_action(editor, window, |editor, action, window, cx| { register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.compose_completion(action, window, cx) { match editor.compose_completion(action, window, cx) {
task.detach_and_notify_err(window, cx); Some(task) => {
} else { task.detach_and_notify_err(window, cx);
cx.propagate(); }
_ => {
cx.propagate();
}
} }
}); });
register_action(editor, window, |editor, action, window, cx| { register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.confirm_code_action(action, window, cx) { match editor.confirm_code_action(action, window, cx) {
task.detach_and_notify_err(window, cx); Some(task) => {
} else { task.detach_and_notify_err(window, cx);
cx.propagate(); }
_ => {
cx.propagate();
}
} }
}); });
register_action(editor, window, |editor, action, window, cx| { register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.rename(action, window, cx) { match editor.rename(action, window, cx) {
task.detach_and_notify_err(window, cx); Some(task) => {
} else { task.detach_and_notify_err(window, cx);
cx.propagate(); }
_ => {
cx.propagate();
}
} }
}); });
register_action(editor, window, |editor, action, window, cx| { register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.confirm_rename(action, window, cx) { match editor.confirm_rename(action, window, cx) {
task.detach_and_notify_err(window, cx); Some(task) => {
} else { task.detach_and_notify_err(window, cx);
cx.propagate(); }
_ => {
cx.propagate();
}
} }
}); });
register_action(editor, window, |editor, action, window, cx| { register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.find_all_references(action, window, cx) { match editor.find_all_references(action, window, cx) {
task.detach_and_log_err(cx); Some(task) => {
} else { task.detach_and_log_err(cx);
cx.propagate(); }
_ => {
cx.propagate();
}
} }
}); });
register_action(editor, window, Editor::show_signature_help); register_action(editor, window, Editor::show_signature_help);
@@ -1097,81 +1124,87 @@ impl EditorElement {
selections.push((player, layouts)); selections.push((player, layouts));
} }
if let Some(collaboration_hub) = &editor.collaboration_hub { match &editor.collaboration_hub {
// When following someone, render the local selections in their color. Some(collaboration_hub) => {
if let Some(leader_id) = editor.leader_peer_id { // When following someone, render the local selections in their color.
if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) if let Some(leader_id) = editor.leader_peer_id {
{ if let Some(collaborator) =
if let Some(participant_index) = collaboration_hub collaboration_hub.collaborators(cx).get(&leader_id)
.user_participant_indices(cx)
.get(&collaborator.user_id)
{ {
if let Some((local_selection_style, _)) = selections.first_mut() { if let Some(participant_index) = collaboration_hub
*local_selection_style = cx .user_participant_indices(cx)
.theme() .get(&collaborator.user_id)
.players() {
.color_for_participant(participant_index.0); if let Some((local_selection_style, _)) = selections.first_mut() {
*local_selection_style = cx
.theme()
.players()
.color_for_participant(participant_index.0);
}
} }
} }
} }
}
let mut remote_selections = HashMap::default(); let mut remote_selections = HashMap::default();
for selection in snapshot.remote_selections_in_range( for selection in snapshot.remote_selections_in_range(
&(start_anchor..end_anchor), &(start_anchor..end_anchor),
collaboration_hub.as_ref(), collaboration_hub.as_ref(),
cx, cx,
) { ) {
let selection_style = let selection_style =
Self::get_participant_color(selection.participant_index, cx); Self::get_participant_color(selection.participant_index, cx);
// Don't re-render the leader's selections, since the local selections // Don't re-render the leader's selections, since the local selections
// match theirs. // match theirs.
if Some(selection.peer_id) == editor.leader_peer_id { if Some(selection.peer_id) == editor.leader_peer_id {
continue; continue;
}
let key = HoveredCursor {
replica_id: selection.replica_id,
selection_id: selection.selection.id,
};
let is_shown =
editor.show_cursor_names || editor.hovered_cursors.contains_key(&key);
remote_selections
.entry(selection.replica_id)
.or_insert((selection_style, Vec::new()))
.1
.push(SelectionLayout::new(
selection.selection,
selection.line_mode,
selection.cursor_shape,
&snapshot.display_snapshot,
false,
false,
if is_shown { selection.user_name } else { None },
));
} }
let key = HoveredCursor {
replica_id: selection.replica_id,
selection_id: selection.selection.id,
};
let is_shown = selections.extend(remote_selections.into_values());
editor.show_cursor_names || editor.hovered_cursors.contains_key(&key); }
_ => {
remote_selections if !editor.is_focused(window) && editor.show_cursor_when_unfocused {
.entry(selection.replica_id) let layouts = snapshot
.or_insert((selection_style, Vec::new())) .buffer_snapshot
.1 .selections_in_range(&(start_anchor..end_anchor), true)
.push(SelectionLayout::new( .map(move |(_, line_mode, cursor_shape, selection)| {
selection.selection, SelectionLayout::new(
selection.line_mode, selection,
selection.cursor_shape, line_mode,
&snapshot.display_snapshot, cursor_shape,
false, &snapshot.display_snapshot,
false, false,
if is_shown { selection.user_name } else { None }, false,
)); None,
)
})
.collect::<Vec<_>>();
let player = editor.current_user_player_color(cx);
selections.push((player, layouts));
}
} }
selections.extend(remote_selections.into_values());
} else if !editor.is_focused(window) && editor.show_cursor_when_unfocused {
let layouts = snapshot
.buffer_snapshot
.selections_in_range(&(start_anchor..end_anchor), true)
.map(move |(_, line_mode, cursor_shape, selection)| {
SelectionLayout::new(
selection,
line_mode,
cursor_shape,
&snapshot.display_snapshot,
false,
false,
None,
)
})
.collect::<Vec<_>>();
let player = editor.current_user_player_color(cx);
selections.push((player, layouts));
} }
}); });
(selections, active_rows, newest_selection_head) (selections, active_rows, newest_selection_head)
@@ -2056,21 +2089,18 @@ impl EditorElement {
cx: &mut App, cx: &mut App,
) -> Vec<AnyElement> { ) -> Vec<AnyElement> {
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
let active_task_indicator_row = let active_task_indicator_row = match editor.context_menu.borrow().as_ref() {
if let Some(crate::CodeContextMenu::CodeActions(CodeActionsMenu { Some(crate::CodeContextMenu::CodeActions(CodeActionsMenu {
deployed_from_indicator, deployed_from_indicator,
actions, actions,
.. ..
})) = editor.context_menu.borrow().as_ref() })) => actions
{ .tasks
actions .as_ref()
.tasks .map(|tasks| tasks.position.to_display_point(snapshot).row())
.as_ref() .or(*deployed_from_indicator),
.map(|tasks| tasks.position.to_display_point(snapshot).row()) _ => None,
.or(*deployed_from_indicator) };
} else {
None
};
let offset_range_start = let offset_range_start =
snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left); snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left);
@@ -4079,11 +4109,12 @@ impl EditorElement {
); );
let maybe_element = self.editor.update(cx, |editor, cx| { let maybe_element = self.editor.update(cx, |editor, cx| {
if let Some(popover) = editor.signature_help_state.popover_mut() { match editor.signature_help_state.popover_mut() {
let element = popover.render(max_size, cx); Some(popover) => {
Some(element) let element = popover.render(max_size, cx);
} else { Some(element)
None }
_ => None,
} }
}); });
if let Some(mut element) = maybe_element { if let Some(mut element) = maybe_element {
@@ -4189,7 +4220,11 @@ impl EditorElement {
None; None;
for (&new_row, &new_background) in &layout.highlighted_rows { for (&new_row, &new_background) in &layout.highlighted_rows {
match &mut current_paint { match &mut current_paint {
Some((current_background, current_range, mut edges)) => { &mut Some((
ref mut current_background,
ref mut current_range,
mut edges,
)) => {
let current_background = *current_background; let current_background = *current_background;
let new_range_started = current_background != new_background let new_range_started = current_background != new_background
|| current_range.end.next_row() != new_row; || current_range.end.next_row() != new_row;
@@ -4916,7 +4951,7 @@ impl EditorElement {
} }
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
if let Some((scrollbar_layout, axis)) = event match event
.pressed_button .pressed_button
.filter(|button| *button == MouseButton::Left) .filter(|button| *button == MouseButton::Left)
.and(editor.scroll_manager.dragging_scrollbar_axis()) .and(editor.scroll_manager.dragging_scrollbar_axis())
@@ -4924,27 +4959,29 @@ impl EditorElement {
scrollbars_layout scrollbars_layout
.iter_scrollbars() .iter_scrollbars()
.find(|(_, a)| *a == axis) .find(|(_, a)| *a == axis)
}) }) {
{ Some((scrollbar_layout, axis)) => {
let ScrollbarLayout { let ScrollbarLayout {
hitbox, hitbox,
text_unit_size, text_unit_size,
.. ..
} = scrollbar_layout; } = scrollbar_layout;
let old_position = mouse_position.along(axis); let old_position = mouse_position.along(axis);
let new_position = event.position.along(axis); let new_position = event.position.along(axis);
if (hitbox.origin.along(axis)..hitbox.bottom_right().along(axis)) if (hitbox.origin.along(axis)..hitbox.bottom_right().along(axis))
.contains(&old_position) .contains(&old_position)
{ {
let position = editor.scroll_position(cx).apply_along(axis, |p| { let position = editor.scroll_position(cx).apply_along(axis, |p| {
(p + (new_position - old_position) / *text_unit_size).max(0.) (p + (new_position - old_position) / *text_unit_size).max(0.)
}); });
editor.set_scroll_position(position, window, cx); editor.set_scroll_position(position, window, cx);
}
cx.stop_propagation();
}
_ => {
editor.scroll_manager.reset_scrollbar_dragging_state(cx);
} }
cx.stop_propagation();
} else {
editor.scroll_manager.reset_scrollbar_dragging_state(cx);
} }
if scrollbars_layout.get_hovered_axis(window).is_some() { if scrollbars_layout.get_hovered_axis(window).is_some() {
@@ -5655,13 +5692,12 @@ pub struct AcceptEditPredictionBinding(pub(crate) Option<gpui::KeyBinding>);
impl AcceptEditPredictionBinding { impl AcceptEditPredictionBinding {
pub fn keystroke(&self) -> Option<&Keystroke> { pub fn keystroke(&self) -> Option<&Keystroke> {
if let Some(binding) = self.0.as_ref() { match self.0.as_ref() {
match &binding.keystrokes() { Some(binding) => match &binding.keystrokes() {
[keystroke] => Some(keystroke), [keystroke] => Some(keystroke),
_ => None, _ => None,
} },
} else { _ => None,
None
} }
} }
} }
@@ -5949,89 +5985,9 @@ impl LineWithInvisibles {
is_tab: false, is_tab: false,
replacement: None, replacement: None,
}]) { }]) {
if let Some(replacement) = highlighted_chunk.replacement { match highlighted_chunk.replacement {
if !line.is_empty() { Some(replacement) => {
let shaped_line = window if !line.is_empty() {
.text_system()
.shape_line(line.clone().into(), font_size, &styles)
.unwrap();
width += shaped_line.width;
len += shaped_line.len;
fragments.push(LineFragment::Text(shaped_line));
line.clear();
styles.clear();
}
match replacement {
ChunkReplacement::Renderer(renderer) => {
let available_width = if renderer.constrain_width {
let chunk = if highlighted_chunk.text == ellipsis.as_ref() {
ellipsis.clone()
} else {
SharedString::from(Arc::from(highlighted_chunk.text))
};
let shaped_line = window
.text_system()
.shape_line(
chunk,
font_size,
&[text_style.to_run(highlighted_chunk.text.len())],
)
.unwrap();
AvailableSpace::Definite(shaped_line.width)
} else {
AvailableSpace::MinContent
};
let mut element = (renderer.render)(&mut ChunkRendererContext {
context: cx,
window,
max_width: text_width,
});
let line_height = text_style.line_height_in_pixels(window.rem_size());
let size = element.layout_as_root(
size(available_width, AvailableSpace::Definite(line_height)),
window,
cx,
);
width += size.width;
len += highlighted_chunk.text.len();
fragments.push(LineFragment::Element {
element: Some(element),
size,
len: highlighted_chunk.text.len(),
});
}
ChunkReplacement::Str(x) => {
let text_style = if let Some(style) = highlighted_chunk.style {
Cow::Owned(text_style.clone().highlight(style))
} else {
Cow::Borrowed(text_style)
};
let run = TextRun {
len: x.len(),
font: text_style.font(),
color: text_style.color,
background_color: text_style.background_color,
underline: text_style.underline,
strikethrough: text_style.strikethrough,
};
let line_layout = window
.text_system()
.shape_line(x, font_size, &[run])
.unwrap()
.with_len(highlighted_chunk.text.len());
width += line_layout.width;
len += highlighted_chunk.text.len();
fragments.push(LineFragment::Text(line_layout))
}
}
} else {
for (ix, mut line_chunk) in highlighted_chunk.text.split('\n').enumerate() {
if ix > 0 {
let shaped_line = window let shaped_line = window
.text_system() .text_system()
.shape_line(line.clone().into(), font_size, &styles) .shape_line(line.clone().into(), font_size, &styles)
@@ -6039,80 +5995,161 @@ impl LineWithInvisibles {
width += shaped_line.width; width += shaped_line.width;
len += shaped_line.len; len += shaped_line.len;
fragments.push(LineFragment::Text(shaped_line)); fragments.push(LineFragment::Text(shaped_line));
layouts.push(Self {
width: mem::take(&mut width),
len: mem::take(&mut len),
fragments: mem::take(&mut fragments),
invisibles: std::mem::take(&mut invisibles),
font_size,
});
line.clear(); line.clear();
styles.clear(); styles.clear();
row += 1;
line_exceeded_max_len = false;
non_whitespace_added = false;
if row == max_line_count {
return layouts;
}
} }
if !line_chunk.is_empty() && !line_exceeded_max_len { match replacement {
let text_style = if let Some(style) = highlighted_chunk.style { ChunkReplacement::Renderer(renderer) => {
Cow::Owned(text_style.clone().highlight(style)) let available_width = if renderer.constrain_width {
} else { let chunk = if highlighted_chunk.text == ellipsis.as_ref() {
Cow::Borrowed(text_style) ellipsis.clone()
}; } else {
SharedString::from(Arc::from(highlighted_chunk.text))
if line.len() + line_chunk.len() > max_line_len { };
let mut chunk_len = max_line_len - line.len(); let shaped_line = window
while !line_chunk.is_char_boundary(chunk_len) { .text_system()
chunk_len -= 1; .shape_line(
} chunk,
line_chunk = &line_chunk[..chunk_len]; font_size,
line_exceeded_max_len = true; &[text_style.to_run(highlighted_chunk.text.len())],
} )
.unwrap();
styles.push(TextRun { AvailableSpace::Definite(shaped_line.width)
len: line_chunk.len(),
font: text_style.font(),
color: text_style.color,
background_color: text_style.background_color,
underline: text_style.underline,
strikethrough: text_style.strikethrough,
});
if editor_mode == EditorMode::Full {
// Line wrap pads its contents with fake whitespaces,
// avoid printing them
let is_soft_wrapped = is_row_soft_wrapped(row);
if highlighted_chunk.is_tab {
if non_whitespace_added || !is_soft_wrapped {
invisibles.push(Invisible::Tab {
line_start_offset: line.len(),
line_end_offset: line.len() + line_chunk.len(),
});
}
} else { } else {
invisibles.extend(line_chunk.char_indices().filter_map( AvailableSpace::MinContent
|(index, c)| { };
let is_whitespace = c.is_whitespace();
non_whitespace_added |= !is_whitespace; let mut element = (renderer.render)(&mut ChunkRendererContext {
if is_whitespace context: cx,
&& (non_whitespace_added || !is_soft_wrapped) window,
{ max_width: text_width,
Some(Invisible::Whitespace { });
line_offset: line.len() + index, let line_height = text_style.line_height_in_pixels(window.rem_size());
}) let size = element.layout_as_root(
} else { size(available_width, AvailableSpace::Definite(line_height)),
None window,
} cx,
}, );
))
width += size.width;
len += highlighted_chunk.text.len();
fragments.push(LineFragment::Element {
element: Some(element),
size,
len: highlighted_chunk.text.len(),
});
}
ChunkReplacement::Str(x) => {
let text_style = match highlighted_chunk.style {
Some(style) => Cow::Owned(text_style.clone().highlight(style)),
_ => Cow::Borrowed(text_style),
};
let run = TextRun {
len: x.len(),
font: text_style.font(),
color: text_style.color,
background_color: text_style.background_color,
underline: text_style.underline,
strikethrough: text_style.strikethrough,
};
let line_layout = window
.text_system()
.shape_line(x, font_size, &[run])
.unwrap()
.with_len(highlighted_chunk.text.len());
width += line_layout.width;
len += highlighted_chunk.text.len();
fragments.push(LineFragment::Text(line_layout))
}
}
}
_ => {
for (ix, mut line_chunk) in highlighted_chunk.text.split('\n').enumerate() {
if ix > 0 {
let shaped_line = window
.text_system()
.shape_line(line.clone().into(), font_size, &styles)
.unwrap();
width += shaped_line.width;
len += shaped_line.len;
fragments.push(LineFragment::Text(shaped_line));
layouts.push(Self {
width: mem::take(&mut width),
len: mem::take(&mut len),
fragments: mem::take(&mut fragments),
invisibles: std::mem::take(&mut invisibles),
font_size,
});
line.clear();
styles.clear();
row += 1;
line_exceeded_max_len = false;
non_whitespace_added = false;
if row == max_line_count {
return layouts;
} }
} }
line.push_str(line_chunk); if !line_chunk.is_empty() && !line_exceeded_max_len {
let text_style = match highlighted_chunk.style {
Some(style) => Cow::Owned(text_style.clone().highlight(style)),
_ => Cow::Borrowed(text_style),
};
if line.len() + line_chunk.len() > max_line_len {
let mut chunk_len = max_line_len - line.len();
while !line_chunk.is_char_boundary(chunk_len) {
chunk_len -= 1;
}
line_chunk = &line_chunk[..chunk_len];
line_exceeded_max_len = true;
}
styles.push(TextRun {
len: line_chunk.len(),
font: text_style.font(),
color: text_style.color,
background_color: text_style.background_color,
underline: text_style.underline,
strikethrough: text_style.strikethrough,
});
if editor_mode == EditorMode::Full {
// Line wrap pads its contents with fake whitespaces,
// avoid printing them
let is_soft_wrapped = is_row_soft_wrapped(row);
if highlighted_chunk.is_tab {
if non_whitespace_added || !is_soft_wrapped {
invisibles.push(Invisible::Tab {
line_start_offset: line.len(),
line_end_offset: line.len() + line_chunk.len(),
});
}
} else {
invisibles.extend(line_chunk.char_indices().filter_map(
|(index, c)| {
let is_whitespace = c.is_whitespace();
non_whitespace_added |= !is_whitespace;
if is_whitespace
&& (non_whitespace_added || !is_soft_wrapped)
{
Some(Invisible::Whitespace {
line_offset: line.len() + index,
})
} else {
None
}
},
))
}
}
line.push_str(line_chunk);
}
} }
} }
} }
@@ -8067,17 +8104,18 @@ impl PositionMap {
let x = position.x + (scroll_position.x * self.em_width); let x = position.x + (scroll_position.x * self.em_width);
let row = ((y / self.line_height) + scroll_position.y) as u32; let row = ((y / self.line_height) + scroll_position.y) as u32;
let (column, x_overshoot_after_line_end) = if let Some(line) = self let (column, x_overshoot_after_line_end) = match self
.line_layouts .line_layouts
.get(row as usize - scroll_position.y as usize) .get(row as usize - scroll_position.y as usize)
{ {
if let Some(ix) = line.index_for_x(x) { Some(line) => {
(ix as u32, px(0.)) if let Some(ix) = line.index_for_x(x) {
} else { (ix as u32, px(0.))
(line.len as u32, px(0.).max(x - line.width)) } else {
(line.len as u32, px(0.).max(x - line.width))
}
} }
} else { _ => (0, x),
(0, x)
}; };
let mut exact_unclipped = DisplayPoint::new(DisplayRow(row), column); let mut exact_unclipped = DisplayPoint::new(DisplayRow(row), column);

View File

@@ -192,7 +192,7 @@ impl GitBlame {
&'a mut self, &'a mut self,
rows: &'a [RowInfo], rows: &'a [RowInfo],
cx: &App, cx: &App,
) -> impl 'a + Iterator<Item = Option<BlameEntry>> { ) -> impl 'a + Iterator<Item = Option<BlameEntry>> + use<'a> {
self.sync(cx); self.sync(cx);
let buffer_id = self.buffer_snapshot.remote_id(); let buffer_id = self.buffer_snapshot.remote_id();
@@ -480,15 +480,14 @@ async fn parse_commit_messages(
.and_then(|remote_url| parse_git_remote_url(provider_registry, remote_url)); .and_then(|remote_url| parse_git_remote_url(provider_registry, remote_url));
for (oid, message) in messages { for (oid, message) in messages {
let permalink = if let Some((provider, git_remote)) = parsed_remote_url.as_ref() { let permalink = match parsed_remote_url.as_ref() {
Some(provider.build_commit_permalink( Some((provider, git_remote)) => Some(provider.build_commit_permalink(
git_remote, git_remote,
git::BuildCommitPermalinkParams { git::BuildCommitPermalinkParams {
sha: oid.to_string().as_str(), sha: oid.to_string().as_str(),
}, },
)) )),
} else { _ => None,
None
}; };
let remote = parsed_remote_url let remote = parsed_remote_url

View File

@@ -472,21 +472,19 @@ pub fn show_link_definition(
_ => GotoDefinitionKind::Type, _ => GotoDefinitionKind::Type,
}; };
let (mut hovered_link_state, is_cached) = let (mut hovered_link_state, is_cached) = match editor.hovered_link_state.take() {
if let Some(existing) = editor.hovered_link_state.take() { Some(existing) => (existing, true),
(existing, true) _ => (
} else { HoveredLinkState {
( last_trigger_point: trigger_point.clone(),
HoveredLinkState { symbol_range: None,
last_trigger_point: trigger_point.clone(), preferred_kind,
symbol_range: None, links: vec![],
preferred_kind, task: None,
links: vec![], },
task: None, false,
}, ),
false, };
)
};
if editor.pending_rename.is_some() { if editor.pending_rename.is_some() {
return; return;
@@ -537,9 +535,9 @@ pub fn show_link_definition(
hovered_link_state.task = Some(cx.spawn_in(window, async move |this, cx| { hovered_link_state.task = Some(cx.spawn_in(window, async move |this, cx| {
async move { async move {
let result = match &trigger_point { let result = match &trigger_point {
TriggerPoint::Text(_) => { TriggerPoint::Text(_) => match find_url(&buffer, buffer_position, cx.clone()) {
if let Some((url_range, url)) = find_url(&buffer, buffer_position, cx.clone()) { Some((url_range, url)) => this
this.update(cx, |_, _| { .update(cx, |_, _| {
let range = maybe!({ let range = maybe!({
let start = let start =
snapshot.anchor_in_excerpt(excerpt_id, url_range.start)?; snapshot.anchor_in_excerpt(excerpt_id, url_range.start)?;
@@ -548,46 +546,58 @@ pub fn show_link_definition(
}); });
(range, vec![HoverLink::Url(url)]) (range, vec![HoverLink::Url(url)])
}) })
.ok() .ok(),
} else if let Some((filename_range, filename)) = _ => match find_file(&buffer, project.clone(), buffer_position, cx).await {
find_file(&buffer, project.clone(), buffer_position, cx).await Some((filename_range, filename)) => {
{ let range = maybe!({
let range = maybe!({ let start =
let start = snapshot.anchor_in_excerpt(excerpt_id, filename_range.start)?;
snapshot.anchor_in_excerpt(excerpt_id, filename_range.start)?; let end =
let end = snapshot.anchor_in_excerpt(excerpt_id, filename_range.end)?; snapshot.anchor_in_excerpt(excerpt_id, filename_range.end)?;
Some(RangeInEditor::Text(start..end)) Some(RangeInEditor::Text(start..end))
}); });
Some((range, vec![HoverLink::File(filename)])) Some((range, vec![HoverLink::File(filename)]))
} else if let Some(provider) = provider {
let task = cx.update(|_, cx| {
provider.definitions(&buffer, buffer_position, preferred_kind, cx)
})?;
if let Some(task) = task {
task.await.ok().map(|definition_result| {
(
definition_result.iter().find_map(|link| {
link.origin.as_ref().and_then(|origin| {
let start = snapshot.anchor_in_excerpt(
excerpt_id,
origin.range.start,
)?;
let end = snapshot
.anchor_in_excerpt(excerpt_id, origin.range.end)?;
Some(RangeInEditor::Text(start..end))
})
}),
definition_result.into_iter().map(HoverLink::Text).collect(),
)
})
} else {
None
} }
} else { _ => match provider {
None Some(provider) => {
} let task = cx.update(|_, cx| {
} provider.definitions(
&buffer,
buffer_position,
preferred_kind,
cx,
)
})?;
match task {
Some(task) => task.await.ok().map(|definition_result| {
(
definition_result.iter().find_map(|link| {
link.origin.as_ref().and_then(|origin| {
let start = snapshot.anchor_in_excerpt(
excerpt_id,
origin.range.start,
)?;
let end = snapshot.anchor_in_excerpt(
excerpt_id,
origin.range.end,
)?;
Some(RangeInEditor::Text(start..end))
})
}),
definition_result
.into_iter()
.map(HoverLink::Text)
.collect(),
)
}),
_ => None,
}
}
_ => None,
},
},
},
TriggerPoint::InlayHint(highlight, lsp_location, server_id) => Some(( TriggerPoint::InlayHint(highlight, lsp_location, server_id) => Some((
Some(RangeInEditor::Inlay(highlight.clone())), Some(RangeInEditor::Inlay(highlight.clone())),
vec![HoverLink::InlayHint(lsp_location.clone(), *server_id)], vec![HoverLink::InlayHint(lsp_location.clone(), *server_id)],
@@ -606,46 +616,57 @@ pub fn show_link_definition(
.as_ref() .as_ref()
.and_then(|(symbol_range, _)| symbol_range.clone()); .and_then(|(symbol_range, _)| symbol_range.clone());
if let Some((symbol_range, definitions)) = result { match result {
hovered_link_state.links = definitions; Some((symbol_range, definitions)) => {
hovered_link_state.links = definitions;
let underline_hovered_link = !hovered_link_state.links.is_empty() let underline_hovered_link = !hovered_link_state.links.is_empty()
|| hovered_link_state.symbol_range.is_some(); || hovered_link_state.symbol_range.is_some();
if underline_hovered_link { if underline_hovered_link {
let style = gpui::HighlightStyle { let style = gpui::HighlightStyle {
underline: Some(gpui::UnderlineStyle { underline: Some(gpui::UnderlineStyle {
thickness: px(1.), thickness: px(1.),
..Default::default()
}),
color: Some(cx.theme().colors().link_text_hover),
..Default::default() ..Default::default()
}), };
color: Some(cx.theme().colors().link_text_hover), let highlight_range =
..Default::default() symbol_range.unwrap_or_else(|| match &trigger_point {
}; TriggerPoint::Text(trigger_anchor) => {
let highlight_range = // If no symbol range returned from language server, use the surrounding word.
symbol_range.unwrap_or_else(|| match &trigger_point { let (offset_range, _) =
TriggerPoint::Text(trigger_anchor) => { snapshot.surrounding_word(*trigger_anchor, false);
// If no symbol range returned from language server, use the surrounding word. RangeInEditor::Text(
let (offset_range, _) = snapshot.anchor_before(offset_range.start)
snapshot.surrounding_word(*trigger_anchor, false); ..snapshot.anchor_after(offset_range.end),
RangeInEditor::Text( )
snapshot.anchor_before(offset_range.start) }
..snapshot.anchor_after(offset_range.end), TriggerPoint::InlayHint(highlight, _, _) => {
) RangeInEditor::Inlay(highlight.clone())
} }
TriggerPoint::InlayHint(highlight, _, _) => { });
RangeInEditor::Inlay(highlight.clone())
}
});
match highlight_range { match highlight_range {
RangeInEditor::Text(text_range) => editor RangeInEditor::Text(text_range) => editor
.highlight_text::<HoveredLinkState>(vec![text_range], style, cx), .highlight_text::<HoveredLinkState>(
RangeInEditor::Inlay(highlight) => editor vec![text_range],
.highlight_inlays::<HoveredLinkState>(vec![highlight], style, cx), style,
cx,
),
RangeInEditor::Inlay(highlight) => editor
.highlight_inlays::<HoveredLinkState>(
vec![highlight],
style,
cx,
),
}
} }
} }
} else { _ => {
editor.hide_hovered_link(cx); editor.hide_hovered_link(cx);
}
} }
})?; })?;

View File

@@ -289,126 +289,134 @@ fn show_hover(
// Find the entry with the most specific range // Find the entry with the most specific range
.min_by_key(|entry| entry.range.len()); .min_by_key(|entry| entry.range.len());
let diagnostic_popover = if let Some(local_diagnostic) = local_diagnostic { let diagnostic_popover = match local_diagnostic {
let text = match local_diagnostic.diagnostic.source { Some(local_diagnostic) => {
Some(ref source) => { let text = match local_diagnostic.diagnostic.source {
format!("{source}: {}", local_diagnostic.diagnostic.message) Some(ref source) => {
} format!("{source}: {}", local_diagnostic.diagnostic.message)
None => local_diagnostic.diagnostic.message.clone(), }
}; None => local_diagnostic.diagnostic.message.clone(),
let local_diagnostic = DiagnosticEntry { };
diagnostic: local_diagnostic.diagnostic, let local_diagnostic = DiagnosticEntry {
range: snapshot diagnostic: local_diagnostic.diagnostic,
.buffer_snapshot range: snapshot
.anchor_before(local_diagnostic.range.start)
..snapshot
.buffer_snapshot .buffer_snapshot
.anchor_after(local_diagnostic.range.end), .anchor_before(local_diagnostic.range.start)
}; ..snapshot
.buffer_snapshot
.anchor_after(local_diagnostic.range.end),
};
let mut border_color: Option<Hsla> = None; let mut border_color: Option<Hsla> = None;
let mut background_color: Option<Hsla> = None; let mut background_color: Option<Hsla> = None;
let parsed_content = cx let parsed_content = cx
.new_window_entity(|window, cx| { .new_window_entity(|window, cx| {
let status_colors = cx.theme().status(); let status_colors = cx.theme().status();
match local_diagnostic.diagnostic.severity { match local_diagnostic.diagnostic.severity {
DiagnosticSeverity::ERROR => { DiagnosticSeverity::ERROR => {
background_color = Some(status_colors.error_background); background_color = Some(status_colors.error_background);
border_color = Some(status_colors.error_border); border_color = Some(status_colors.error_border);
} }
DiagnosticSeverity::WARNING => { DiagnosticSeverity::WARNING => {
background_color = Some(status_colors.warning_background); background_color = Some(status_colors.warning_background);
border_color = Some(status_colors.warning_border); border_color = Some(status_colors.warning_border);
} }
DiagnosticSeverity::INFORMATION => { DiagnosticSeverity::INFORMATION => {
background_color = Some(status_colors.info_background); background_color = Some(status_colors.info_background);
border_color = Some(status_colors.info_border); border_color = Some(status_colors.info_border);
} }
DiagnosticSeverity::HINT => { DiagnosticSeverity::HINT => {
background_color = Some(status_colors.hint_background); background_color = Some(status_colors.hint_background);
border_color = Some(status_colors.hint_border); border_color = Some(status_colors.hint_border);
} }
_ => { _ => {
background_color = Some(status_colors.ignored_background); background_color = Some(status_colors.ignored_background);
border_color = Some(status_colors.ignored_border); border_color = Some(status_colors.ignored_border);
} }
}; };
let settings = ThemeSettings::get_global(cx); let settings = ThemeSettings::get_global(cx);
let mut base_text_style = window.text_style(); let mut base_text_style = window.text_style();
base_text_style.refine(&TextStyleRefinement { base_text_style.refine(&TextStyleRefinement {
font_family: Some(settings.ui_font.family.clone()), font_family: Some(settings.ui_font.family.clone()),
font_fallbacks: settings.ui_font.fallbacks.clone(), font_fallbacks: settings.ui_font.fallbacks.clone(),
font_size: Some(settings.ui_font_size(cx).into()), font_size: Some(settings.ui_font_size(cx).into()),
color: Some(cx.theme().colors().editor_foreground), color: Some(cx.theme().colors().editor_foreground),
background_color: Some(gpui::transparent_black()), background_color: Some(gpui::transparent_black()),
..Default::default()
});
let markdown_style = MarkdownStyle {
base_text_style,
selection_background_color: { cx.theme().players().local().selection },
link: TextStyleRefinement {
underline: Some(gpui::UnderlineStyle {
thickness: px(1.),
color: Some(cx.theme().colors().editor_foreground),
wavy: false,
}),
..Default::default() ..Default::default()
}, });
..Default::default() let markdown_style = MarkdownStyle {
}; base_text_style,
Markdown::new_text(SharedString::new(text), markdown_style.clone(), cx) selection_background_color: {
.open_url(open_markdown_url) cx.theme().players().local().selection
}) },
.ok(); link: TextStyleRefinement {
underline: Some(gpui::UnderlineStyle {
thickness: px(1.),
color: Some(cx.theme().colors().editor_foreground),
wavy: false,
}),
..Default::default()
},
..Default::default()
};
Markdown::new_text(SharedString::new(text), markdown_style.clone(), cx)
.open_url(open_markdown_url)
})
.ok();
Some(DiagnosticPopover { Some(DiagnosticPopover {
local_diagnostic, local_diagnostic,
parsed_content, parsed_content,
border_color, border_color,
background_color, background_color,
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)), keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
anchor: Some(anchor), anchor: Some(anchor),
}) })
} else { }
None _ => None,
}; };
this.update(cx, |this, _| { this.update(cx, |this, _| {
this.hover_state.diagnostic_popover = diagnostic_popover; this.hover_state.diagnostic_popover = diagnostic_popover;
})?; })?;
let invisible_char = if let Some(invisible) = snapshot let invisible_char = match snapshot
.buffer_snapshot .buffer_snapshot
.chars_at(anchor) .chars_at(anchor)
.next() .next()
.filter(|&c| is_invisible(c)) .filter(|&c| is_invisible(c))
{ {
let after = snapshot.buffer_snapshot.anchor_after( Some(invisible) => {
anchor.to_offset(&snapshot.buffer_snapshot) + invisible.len_utf8(), let after = snapshot.buffer_snapshot.anchor_after(
); anchor.to_offset(&snapshot.buffer_snapshot) + invisible.len_utf8(),
Some((invisible, anchor..after)) );
} else if let Some(invisible) = snapshot Some((invisible, anchor..after))
.buffer_snapshot }
.reversed_chars_at(anchor) _ => {
.next() match snapshot
.filter(|&c| is_invisible(c)) .buffer_snapshot
{ .reversed_chars_at(anchor)
let before = snapshot.buffer_snapshot.anchor_before( .next()
anchor.to_offset(&snapshot.buffer_snapshot) - invisible.len_utf8(), .filter(|&c| is_invisible(c))
); {
Some(invisible) => {
let before = snapshot.buffer_snapshot.anchor_before(
anchor.to_offset(&snapshot.buffer_snapshot) - invisible.len_utf8(),
);
Some((invisible, before..anchor)) Some((invisible, before..anchor))
} else { }
None _ => None,
}
}
}; };
let hovers_response = if let Some(hover_request) = hover_request { let hovers_response = match hover_request {
hover_request.await Some(hover_request) => hover_request.await,
} else { _ => Vec::new(),
Vec::new()
}; };
let snapshot = this.update_in(cx, |this, window, cx| this.snapshot(window, cx))?; let snapshot = this.update_in(cx, |this, window, cx| this.snapshot(window, cx))?;
let mut hover_highlights = Vec::with_capacity(hovers_response.len()); let mut hover_highlights = Vec::with_capacity(hovers_response.len());
@@ -543,11 +551,12 @@ async fn parse_blocks(
language: Option<Arc<Language>>, language: Option<Arc<Language>>,
cx: &mut AsyncWindowContext, cx: &mut AsyncWindowContext,
) -> Option<Entity<Markdown>> { ) -> Option<Entity<Markdown>> {
let fallback_language_name = if let Some(ref l) = language { let fallback_language_name = match language {
let l = Arc::clone(l); Some(ref l) => {
Some(l.lsp_id().clone()) let l = Arc::clone(l);
} else { Some(l.lsp_id().clone())
None }
_ => None,
}; };
let combined_text = blocks let combined_text = blocks

View File

@@ -36,16 +36,17 @@ impl Editor {
cx: &mut Context<Editor>, cx: &mut Context<Editor>,
) -> Option<Vec<IndentGuide>> { ) -> Option<Vec<IndentGuide>> {
let show_indent_guides = self.should_show_indent_guides().unwrap_or_else(|| { let show_indent_guides = self.should_show_indent_guides().unwrap_or_else(|| {
if let Some(buffer) = self.buffer().read(cx).as_singleton() { match self.buffer().read(cx).as_singleton() {
language_settings( Some(buffer) => {
buffer.read(cx).language().map(|l| l.name()), language_settings(
buffer.read(cx).file(), buffer.read(cx).language().map(|l| l.name()),
cx, buffer.read(cx).file(),
) cx,
.indent_guides )
.enabled .indent_guides
} else { .enabled
true }
_ => true,
} }
}); });

Some files were not shown because too many files have changed in this diff Show More